Inkrementálne aktualizácie PDF umožňujú aplikácii v Delphi upravovať dokument pripojením iba zmenených objektov, pričom každý pôvodný bajt zostane nedotknutý. Knižnica losLab PDF Library to implementuje prostredníctvom AppendToStream, ktorý zapisuje len inkrementálnu časť definovanú normou ISO 32000-1 §7.5.6, takže úprava jednej záložky v 2 GB súbore stojí kilobajty výstupu namiesto úplného prepísania. Rovnaký mechanizmus je dôvodom, prečo je možné podpísané dokumenty aktualizovať bez toho, aby sa zneplatnili ich podpisy
Problém, ktorý to rieši, je konkrétny. Úplné uloženie prepíše celý súbor: každý objekt je znova serializovaný, každý posun krížových odkazov (xref offset) je prepočítaný a výstup nemá na úrovni bajtov žiadny vzťah k vstupu. Pre faktúru s veľkosťou 40 KB je to v poriadku. Pri 2 GB naskenovanom archíve, kde ste opravili iba preklep v názve dokumentu, je prepisovanie dvoch gigabajtov na zmenu dvadsiatich bajtov absurdné — a ak súbor obsahoval digitálny podpis, prepis ho práve zničil
Prečo uloženie PDF poruší jeho digitálny podpis?
Digitálny podpis PDF nepodpisuje logický obsah dokumentu; podpisuje bajtové rozsahy (byte ranges) fyzického súboru. Položka /ByteRange v slovníku podpisov zaznamenáva presne to, ktoré časti súboru kryptografický odtlačok pokrýva. Akákoľvek operácia uloženia, ktorá znova serializuje tieto bajty — dokonca aj tá, ktorá vytvorí sémanticky identický dokument — zmení odtlačok a každý validátor nahlási podpis ako poškodený. Toto je zámer: podpis potvrdzuje bajty, ktoré podpisovateľ videl, nie nejaký abstraktný model dokumentu
Inkrementálne aktualizácie sú únikovou cestou, ktorú špecifikácia PDF poskytuje. Keďže inkrementálne uloženie pripája nové údaje za pôvodný %%EOF a nikdy sa nedotýka podpísaných bajtových rozsahov, existujúci podpis sa naďalej overuje voči bajtom, ktoré pokrýva. Validátory potom klasifikujú pripojené zmeny samostatne — druhý podpis, vyplnenie formulára, anotácia — a rozhodnú, či ide o povolené úpravy. Každý tok práce s viacerými podpismi závisí od tohto: každý podpisovateľ pridá inkrementálnu časť na vrch predchádzajúcej. Ak budujete podpisovacie systémy, sprievodný článok o podpisovaní a validácii PAdES v Delphi podrobne popisuje, ako navzájom spolupracujú bajtové rozsahy podpisov a inkrementálne časti
Ako fungujú inkrementálne aktualizácie podľa ISO 32000-1 §7.5.6
Norma ISO 32000-1 §7.5.6 definuje model v troch pravidlách. Po prvé, pôvodný obsah súboru zostáva úplne nedotknutý — nepohne sa ani jeden bajt. Po druhé, zmenené a novo vytvorené objekty sa pripájajú za posledný %%EOF, každý s rovnakým číslom objektu, aké mal predtým (zmenené objekty jednoducho získajú novšiu definíciu, ktorá prekryje tú starú). Po tretie, pripojí sa nová časť s krížovými odkazmi (xref) a trailer; položka /Prev v traileri ukazuje späť na bajtový posun predchádzajúcej časti s krížovými odkazmi, čím sa vytvára reťazec, ktorým čítačka prechádza od najnovšieho po najstarší, aby našla najnovšiu definíciu každého objektu
Z tejto štruktúry vyplývajú dve užitočné vlastnosti. Aktualizácie sú lacné v pomere k tomu, čo sa zmenilo, nie k veľkosti dokumentu — náklady na pripojenie predstavujú veľkosť upravených objektov plus malú réžiu xref/traileru. A súbor sa stáva vlastnou históriou verzií: každá predchádzajúca revízia je stále fyzicky prítomná, takže audítor môže súbor skrátiť na ktoromkoľvek skoršom %%EOF a obnoviť presne ten dokument, ktorý existoval v tom čase. Pre toky práce na zabezpečenie súladu s predpismi (compliance), ktoré musia preukázať, ako dokument vyzeral pred každou zmenou, je táto vstavaná audítorská stopa často rozhodujúcim argumentom pre inkrementálne ukladanie
Zápis inkrementálnej aktualizácie pomocou AppendToStream
losLab PDF Library sprístupňuje inkrementálny výstup cez AppendToStream(AppendMode: Integer; OutStream: TStream): Integer, ktorý vracia 1 pri úspechu a 0 pri zlyhaní. Parameter AppendMode vyberá, čo sa zapíše do cieľového prúdu. Režim 0 zapíše kompletný súbor: pôvodné zdrojové bajty sa najprv skopírujú do prúdu, a potom sa pripojí inkrementálna časť. Režim 1 zapisuje iba samotnú inkrementálnu časť — deltu — a úplne vynecháva zdrojové bajty. Režim 2 najprv zapíše volajúcim dodaný prefix prefix registrovaný cez SetAppendInputFromString, a potom naň pripojí časť s aktualizáciou
var
Doc: TPDFlib;
Delta: TMemoryStream;
begin
Doc := TPDFlib.Create;
try
if Doc.LoadFromFile('contract.pdf', '') <= 0 then
Exit;
// Small edit: the kind of change that should not
// trigger a rewrite of the whole file
Doc.SetInformation(3, 'Amended 2026-07-04'); // key 3 = /Subject
Delta := TMemoryStream.Create;
try
// AppendMode = 1: write only the incremental section.
// Original bytes + Delta = a complete, valid PDF.
if Doc.AppendToStream(1, Delta) = 1 then
Delta.SaveToFile('contract.delta.bin');
finally
Delta.Free;
end;
finally
Doc.Free;
end;
end;
Režim 1 je zaujímavý pre návrh systému. Keďže delta je samostatná, môžete ju prenášať nezávisle od originálu: ukladať revízie ako samostatné objekty v úložisku objektov, replikovať iba delty na vzdialené miesto alebo rekonštruovať akúkoľvek revíziu zreťazením základného súboru s jeho reťazcom prírastkov. Pravidlom rekonštrukcie je obyčajné zreťazenie bajtov — najprv pôvodný súbor, potom každá delta v poradí — pretože to je presne to rozloženie, ktoré §7.5.6 predpisuje pre inkrementálne aktualizovaný súbor
Ako knižnica počíta posuny xref bez kopírovania pôvodného súboru?
Záznamy krížových odkazov vo vnútri inkrementálnej časti musia obsahovať absolútne bajtové posuny — pozície merané od začiatku celého súboru, nie od začiatku delty. To vytvára rébus pre režim 1: zapisovač nikdy nevysiela pôvodné bajty, no každý posun, ktorý zaznamenáva, sa musí tváriť, že tam sú. losLab PDF Library to rieši interným adaptérom prúdu, TPDFAppendSectionStream, ktorý serializátoru prezentuje virtuálny súradnicový priestor. Adaptér sa vytvorí s dĺžkou pôvodného súboru v bajtoch ako jeho základným posunom, hlási svoju pozíciu a veľkosť ako tento základ plus všetko, čo bolo doteraz pripojené, a do cieľového prúdu volajúceho odosiela iba novo zapísané bajty
Dôsledkom je, že režim 1 nikdy nevytvára kópiu zdrojového dokumentu — ani na disku, ani v pamäti. Naivná implementácia (zapísať celý súbor do dočasnej pamäte a potom odrezať koniec) by priniesla prechodnú kópiu celého pôvodného PDF, čo je pri gigabajtových vstupoch presne ten náklad, ktorému sa inkrementálne aktualizácie snažia vyhnúť. Táto technika virtualizácie posunu je blízkym príbuzným posunu bajtových referencií používaného inde v knižnici; článok o rýchlom zlučovaní PDF s posunom bajtových referencií ukazuje rovnakú myšlienku aplikovanú na spájanie dokumentov a príručka o zlučovaní a rozdeľovaní veľkých PDF s priamym prístupom k súborom sa zaoberá súvisiacou I/O architektúrou pre súbory, ktoré sa nezmestia pohodlne do RAM
Streamovanie úplných uložení pomocou SaveToStream
Inkrementálny výstup je polovicou streamovacieho príbehu; druhou polovicou je to, čo sa stane pri plnom uložení. SaveToStream v losLab PDF Library smeruje serializátor dokumentu priamo do cieľového prúdu, namiesto toho, aby najprv vykreslil celý dokument do medzipamäte AnsiString a potom tento vyrovnávací buffer zapísal v jednom volaní. Starší prístup fungoval, ale znamenal, že každé úplné uloženie prechodne držalo druhú kompletnú kópiu výstupu v pamäti — neškodné pri 10 MB, bolestivé pri 500 MB a neprekonateľná stena pre viacgigabajtové výstupy v 32-bitových procesoch. Priama serializácia spôsobuje, že špičková spotreba pamäte závisí od objektových štruktúr dokumentu a nie od jeho serializovanej dĺžky
Lekcia o režime zdieľania (share-mode): keď AppendToFile vrátil 0
Jednu regresiu v tejto oblasti stojí za to zopakovať, pretože tento chybový vzor sa dá zovšeobecniť. AppendToFile(FileName) pripája inkrementálnu aktualizáciu priamo k existujúcemu PDF na disku — prirodzené volanie pre pracovný postup úpravy na mieste s audítorskou stopou: načítať súbor, vykonať zmenu, pripojiť na rovnakú cestu. Vo verzii v3.71.2 táto presná sekvencia začala vracať 0. Hlavná príčina bola v loaderi, nie v zapisovači: kvôli podpore čítania veľkých dokumentov na vyžiadanie drží LoadFromFile otvorený handle zdrojového súboru počas celej životnosti objektu dokumentu a tento handle bol otvorený s fmShareDenyWrite. Keď sa potom AppendToFile pokúsil znova otvoriť rovnaký súbor na zápis, vlastný režim zdieľania loadera to odmietol a API zlyhalo skôr, ako stihlo zapísať jediný bajt
Oprava uvoľnila režim zdieľania loadera na fmShareDenyNone, čo je bezpečné práve kvôli tomu, čím inkrementálne pripojenie je: pridáva bajty striktne za koniec súboru a never prepisuje oblasť, ktorú obsluhuje dlho žijúci handle čítačky. Všeobecná lekcia pre každého, kto túto knižnicu používa — alebo vytvára podobné streamovacie loadery — je, že lenivé čítačky držiace handle a zapisovače do rovnakého súboru sú v napätí a režim zdieľania, ktorý zvolíte pri otvorení, je zmluvou API, nie detailom implementácie. Ak AppendToFile vo vašom kóde niekedy vráti 0, skontrolujte najprv, či niečo iné vo vašom procese stále nedrží cieľový súbor s obmedzujúcim režimom zdieľania
Skutočné náklady: kedy sú inkrementálne aktualizácie nevhodným nástrojom
Inkrementálne aktualizácie vymieňajú veľkosť súboru za efektívnosť zápisu a táto výmena nie je vždy výhodná. Každá revízia pripojí svoje zmenené objekty, zatiaľ čo nahradené definície zostávajú v súbore, takže dokument upravovaný stokrát hromadí mŕtve objekty a dlhý reťazec /Prev, ktorým musí prejsť každá čítačka. Čo je horšie, „odstránený“ obsah nezmizol: text odstránený v revízii päť je stále fyzicky prítomný v bajtoch revízie štyri, pričom ho môže obnoviť ktokoľvek, kto súbor skráti. Redigovanie, sanitácia alebo akékoľvek odstránenie citlivého obsahu preto vyžaduje úplný prepis — inkrementálne uloženie redigovania je únik dát s dodatočnými krokmi
Úplné uloženie je tiež správnou voľbou, keď je cieľom kompresia (odstránenie nahromadených prírastkov a nepoužívaných objektov), keď sa menia vlastnosti celého dokumentu, napríklad šifrovanie — rešifrovanie sa dotýka každého reťazca a prúdu, takže na zmene nezostáva nič „inkrementálne“ — alebo pri vytváraní čistého výsledného súboru, kde by história úprav nemala cestovať so súborom. Rozumné pravidlo: používajte AppendToStream alebo AppendToFile, kým je dokument aktívny a mení sa, najmä keď už obsahuje podpisy; úplný prepis pomocou SaveToStream použite na hraniciach životného cyklu, keď dokument opúšťa váš systém alebo sa musí jeho história zlúčiť
Inkrementálne aktualizácie, delta výstup s virtuálnym posunom a serializácia priamo do prúdu sú súčasťou štandardnej knižnice losLab PDF Library pre Delphi, C# a VB.NET; stránka produktu uvádza kompletné API pre ukladanie a pripájanie spolu s funkciami podpisovania a prácou s veľkými súbormi, o ktorých sme hovorili vyššie