PDFium Component ger en PDF-redigerare riktig ångra och gör om på dokumentnivå, plus ett kraschåterställningspaket, via en redigeringsjournal. Du omsluter ändringar i BeginEditTransaction och CommitEditTransaction, och varje genomförd transaktion blir ett dubbelriktat delta som UndoEdit och RedoEdit kan spela upp i endera riktningen. SaveRecoverySnapshot sparar den journalen så att en applikation som dödas mitt i en session kan återuppbygga användarens arbete
Luckan detta fyller är specifik. Den inbyggda ångra-förmågan gäller bara det formulärfält som för tillfället har fokus, så en textruta minns sina egna redigeringar och inget annat gör det. Sidinfogning, sidborttagning och omordning, ändringar av sidobjekt, annoteringar, bilagor och metadata har alla ingen journal alls. En redigerare byggd enbart på det kan erbjuda ångra inuti ett fält och ingen annanstans, vilket inte är vad användare menar med ångra
Varför ögonblicksbilder snarare än en operationslogg?
Den uppenbara designen är att registrera semantiska operationer — "raderade sida 4", "flyttade annotering 7" — och invertera dem. Det fungerar tills operationsuppsättningen växer, vid vilken punkt varje ny funktion behöver en invers, och varje operation vars invers inte är exakt tyst korrumperar dokumentet över en lång ångra-kedja
Journalen fångar istället dokumenttillståndet vid transaktionsgränser och lagrar skillnaden. Varje delta behåller det längsta gemensamma prefixet och suffixet och bär både de borttagna och infogade byten, så det kan tillämpas framåt eller bakåt. Nya redigeringsfunktioner behöver inget ångra-stöd: vad de än ändrar finns inuti ögonblicksbilden
Detta tillvägagångssätt beror på en förutsättning. En vanlig fullständig sparning producerar icke-deterministiska identifierare och tidsfält, så att spara ett oförändrat dokument två gånger kan ge olika byte. Ögonblicksbilder normaliseras därför först genom den reproducerbara utdatavägen. Utan det steget skulle en tom transaktion se ut som en ändring, och ångra skulle misslyckas med sin egen verifiering eftersom den digest den beräknade vid genomförandet inte skulle matcha dokumentet efteråt
Transaktioner i praktiken
Varje transaktion tar en valfri etikett, vilket är vad en redigerare visar i sin ångra-meny:
uses
PDFium;
var
Pdf: TPdf;
Options: TPdfEditJournalOptions;
Info: TPdfEditJournalInfo;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'contract.pdf';
Pdf.LoadDocument;
Options := TPdfEditJournalOptions.Default;
Options.MaxEntries := 50; // ångra-djup
Options.MaxStoredBytes := 256 * 1024 * 1024;
Pdf.ConfigureEditJournal(Options);
Pdf.BeginEditTransaction('Delete cover page');
try
Pdf.DeletePage(1);
if not Pdf.CommitEditTransaction then
raise Exception.Create('commit failed - document was reloaded');
except
Pdf.RollbackEditTransaction;
raise;
end;
Info := Pdf.GetEditJournalInfo;
Writeln(Format('entries=%d position=%d stored=%d bytes',
[Info.EntryCount, Info.Position, Info.StoredBytes]));
if Pdf.EditCanUndo then
Pdf.UndoEdit; // sidan kommer tillbaka
finally
Pdf.Free;
end;
end;
Ett genomförande som misslyckas — för att fångst misslyckades, en budget överskreds eller deltat inte kunde byggas — återställer baslinjen som fångades vid BeginEditTransaction och laddar om, istället för att lämna en aktiv transaktion eller en halvskriven journal efter sig. Det här är det felläge som spelar störst roll, eftersom en journal i ett inkonsekvent tillstånd förgiftar varje efterföljande ångra
Verifiering vid varje steg
Varje delta lagrar SHA-256-digesten för dokumentet före och efter, tillsammans med prefixlängden och båda nyttolasterna. Ångra, gör om, importerad uppspelning och omvänd rekonstruktion kontrollerar alla den aktuella digesten och regionen de tänker ersätta innan de rör något, och verifierar sedan den resulterande digesten efteråt
Förflyttning genom journalen är lika försiktig. Positionen avancerar bara efter att mål-PDF:en har validerats av ett oberoende dokumentobjekt och framgångsrikt ersatt den aktuella. Om det misslyckas återställs den aktuella ögonblicksbilden som reserv, så en misslyckad ångra lämnar användaren där de var snarare än i ett trasigt tillstånd
Det serialiserade formatet kontrolleras som data, inte litas på som indata: magiskt tal, version, post- och positionsvärden, digestkedjan, etikettlängder, patchlängder och total lagringsbudget valideras alla. En importerad journal är en fil som kan ha trunkerats, redigerats eller producerats av en annan version, och att behandla den som fientlig är den enda säkra hållningen
Vad händer när journalen blir full
Tre gränser begränsar tillväxten: antal poster, kumulativa deltabyte och storleken på en enda ögonblicksbild. När kapaciteten nås vräks den äldsta posten ut och rotfingeravtrycket avancerar
Den sista delen har en konsekvens värd att designa runt. En exporterad journal kan bara tillämpas på ett dokument som matchar det rotfingeravtryck den rapporterar, eller ett fingeravtryck den registrerat. När vräkning väl har skett når journalen inte längre tillbaka till originalfilen. Autospara måste därför behandla den matchande rotögonblicksbilden och journalen som en enda atomär återställningsenhet — att behålla journalen utan dess bas är att behålla något som inte kan spelas upp
Kraschåterställningspaket
Återställningspaketet bäddar avsiktligt inte in käll-PDF:en. Det lagrar källans visningsnamn, SHA-256-fingeravtryck av käll- och återställda tillstånd, samt redigeringsjournalen. Återställning kräver därför originalfilen, som anroparen laddar först; paketet verifierar sedan båda fingeravtrycken genom att spela upp journalen bakåt och framåt:
// Efter varje lyckat genomförande, uppdatera återställningsfilen
Pdf.BeginEditTransaction('Redact account numbers');
if Pdf.CommitEditTransaction then
Pdf.SaveRecoverySnapshot('contract.recovery');
// Nästa session, efter en krasch: ladda originalet, återställ sedan
Pdf.FileName := 'contract.pdf';
Pdf.LoadDocument;
Pdf.RestoreRecoverySnapshot('contract.recovery');
Att inte bädda in källan håller återställningsfilerna små, vilket är det som gör autospara per genomförande överkomligt på ett dokument av vilken storlek som helst. En yttre digest täcker huvudet, källnamnet och journalen, så ett trunkerat eller manipulerat paket avvisas innan något delta parsas, och den totala lästa volymen är begränsad
Filskrivningar går till en temporär fil i målkatalogen och ersätts sedan atomärt, och att sätta återställningsvägen till käll-PDF:en avvisas uttryckligen. Autospara körs bara efter ett lyckat ändrat genomförande, ångra eller gör om. Om diskskrivningen misslyckas är dokument- och journaltillstånden i minnet redan giltiga, så undantaget rapporterar ett persistensfel snarare än dataförlust, och anroparen kan åtgärda sökvägen och försöka igen
Signerade dokument avvisas som standard
En transaktionsögonblicksbild är en ny normaliserad revision, inte de ursprungliga signerade byten, så att genomföra en transaktion på ett signerat dokument ogiltigförklarar signaturen. Att påbörja en transaktion på ett sådant dokument avvisas om inte anroparen skickar AllowSignedDocument, och journalimport och återställningsåterställning har som standard samma avvisning
Det här är ett avsiktligt farthinder snarare än ett förbud. Vissa arbetsflöden redigerar legitimt ett signerat dokument och signerar om det efteråt, och flaggan finns för dem. Vad den förhindrar är olyckshändelsen, där en redigerare tyst tar bort giltigheten från ett dokument en användare tror fortfarande är signerat. Verifieringssidan av den historien täcks i att inspektera digitala PDF-signaturer och PAdES-nivåer
För en redigerare gör tre vanor att journalen lönar sig. Omslut varje användarsynlig åtgärd i en enda etiketterad transaktion, så att ångra-menyn läses som användarens mentala modell snarare än som din anropsstack. Anpassa postgränsen efter dokumentet, eftersom femtio ögonblicksbilder av en 200 MB-skanning inte är en blygsam inställning. Och skriv återställningsögonblicksbilden vid varje genomförande snarare än på en timer, eftersom en timer garanterar att det förlorade arbetet är exakt vad som hände efter den senaste tickningen. Redigeringsoperationer på dokumentnivå som drar nytta av detta täcks i inkrementella uppdateringar av krypterad PDF
Redigeringsjournalföring, återställningspaket och redigeringsprimitiven under dem levereras i samma komponent för Delphi, C++Builder och Lazarus; det fullständiga API:et beskrivs på sidan för PDFium Component för Delphi