Inkrementalna ažuriranja PDF-a omogućuju aplikaciji u Delphiju da modificira dokument dodavanjem samo izmijenjenih objekata, ostavljajući svaki izvorni bajt netaknutim. losLab PDF Library to implementira putem AppendToStream, koji zapisuje samo inkrementalni odjeljak definiran standardom ISO 32000-1 §7.5.6, pa uređivanje jedne oznake (bookmark) u datoteci od 2 GB košta samo nekoliko kilobajta izlaza umjesto potpunog prepisivanja. Isti je mehanizam razlog zašto se potpisani dokumenti mogu ažurirati bez poništavanja njihovih potpisa
Problem koji ovo rješava je konkretan. Potpuno spremanje ponovno prepisuje cijelu datoteku: svaki se objekt ponovno serijalizira, svaki se pomak unakrsne reference (cross-reference offset) ponovno izračunava, a izlaz nema nikakve veze s ulazom na razini bajtova. Za fakturu od 40 KB to je u redu. Ali za skeniranu arhivu od 2 GB u kojoj ste samo ispravili tipfeler u naslovu dokumenta, prepisivanje dva gigabajta radi promjene dvadeset bajtova je apsurdno — a ako je datoteka imala digitalni potpis, prepisivanje ga je upravo uništilo
Zašto spremanje PDF-a narušava njegov digitalni potpis?
Digitalni potpis PDF-a ne potpisuje logički sadržaj dokumenta; on potpisuje raspone bajtova fizičke datoteke. Unos /ByteRange u rječniku potpisa bilježi točno koje raspone datoteke pokriva kriptografski sažetak. Svaka operacija spremanja koja ponovno serijalizira te bajtove — čak i ona koja proizvodi semantički identičan dokument — mijenja sažetak, i svaki će validator prijaviti potpis kao nevažeći. To je tako dizajnirano: potpis svjedoči o bajtovima koje je potpisnik vidio, a ne o nekom apstraktnom modelu dokumenta
Inkrementalna ažuriranja su sigurnosni ventil koji pruža PDF specifikacija. Budući da inkrementalno spremanje dodaje nove podatke nakon izvornog %%EOF i nikada ne dira potpisane raspone bajtova, postojeći potpis ostaje važeći za bajtove koje pokriva. Validatori zatim zasebno klasificiraju dodane promjene — drugi potpis, ispunjavanje obrasca, anotaciju — i odlučuju jesu li to dopuštene izmjene. Svaki tijek rada s više potpisa ovisi o tome: svaki potpisnik dodaje inkrementalni odjeljak na vrh prethodnog. Ako gradite cjevovode za potpisivanje, popratni članak o PAdES potpisivanju i provjeri valjanosti u Delphiju detaljno pokriva kako rasponi bajtova potpisa i inkrementalni odjeljci međusobno djeluju
Kako funkcioniraju inkrementalna ažuriranja prema ISO 32000-1 §7.5.6
ISO 32000-1 §7.5.6 definira model u tri pravila. Prvo, izvorni sadržaj datoteke ostaje potpuno netaknut — niti jedan bajt se ne pomiče. Drugo, promijenjeni i novostvoreni objekti dodaju se nakon posljednjeg %%EOF, svaki s istim brojem objekta koji je imao prije (promijenjeni objekti jednostavno dobivaju novu definiciju koja zasjenjuje staru). Treće, dodaju se novi odjeljak unakrsnih referenci i trailer; unos /Prev trailera upućuje natrag na pomak u bajtovima prethodnog odjeljka unakrsnih referenci, tvoreći lanac kojim čitatelj prolazi od najnovijeg prema najstarijem kako bi razriješio svaki objekt u njegovu najnoviju definiciju
Dva korisna svojstva proizlaze iz ove strukture. Ažuriranja su jeftina u omjeru s onim što se promijenilo, a ne s veličinom dokumenta — trošak dodavanja je veličina modificiranih objekata plus mali trošak za xref/trailer. Također, datoteka postaje vlastita povijest verzija: svaka je prethodna revizija još uvijek fizički prisutna, pa revizor može skratiti datoteku na bilo kojem ranijem %%EOF i oporaviti točno onaj dokument koji je postojao u tom trenutku. Za tijekove rada usklađenosti koji moraju dokazati kako je dokument izgledao prije svake izmjene, ovaj ugrađeni revizijski trag često je odlučujući argument za inkrementalna spremanja
Zapisivanje inkrementalnog ažuriranja pomoću AppendToStream
losLab PDF Library izlaže inkrementalni izlaz kroz AppendToStream(AppendMode: Integer; OutStream: TStream): Integer, koji vraća 1 u slučaju uspjeha i 0 u slučaju neuspjeha. Parametar AppendMode odabire što se smješta u ciljni tok. Način 0 zapisuje potpunu datoteku: izvorni bajtovi izvora prvo se kopiraju u tok, a zatim se dodaje inkrementalni odjeljak. Način 1 zapisuje samo sam inkrementalni odjeljak — deltu — i potpuno preskače izvorne bajtove. Način 2 prvo zapisuje prefiks koji je isporučio pozivatelj i registrirao se putem SetAppendInputFromString, a zatim dodaje odjeljak za ažuriranje na njegov vrh
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;
Način 1 je zanimljiv za dizajn sustava. Budući da je delta samostalna, možete je isporučiti neovisno o izvornoj datoteci: pohraniti revizije kao zasebne objekte u skladištu objekata, replicirati samo delte na udaljeno mjesto ili rekonstruirati bilo koju reviziju spajanjem osnovne datoteke s njezinim lancem inkremenata. Pravilo rekonstrukcije je jednostavno spajanje bajtova — prvo izvorna datoteka, a zatim svaka delta redom — jer je to točno raspored koji §7.5.6 propisuje za inkrementalno ažuriranu datoteku
Kako knjižnica izračunava pomake xref bez kopiranja izvorne datoteke?
Unosi unakrsnih referenci unutar inkrementalnog odjeljka moraju sadržavati apsolutne pomake u bajtovima — pozicije mjerene od početka cjelovite datoteke, a ne od početka delte. To stvara zagonetku za način 1: pisac nikada ne emitira izvorne bajtove, no svaki pomak koji bilježi mora se pretvarati da su oni tamo. losLab PDF Library to rješava internim adapterom toka, TPDFAppendSectionStream, koji serijalizatoru predstavlja virtualni koordinatni prostor. Adapter se stvara s duljinom izvorne datoteke u bajtovima kao njezinim osnovnim pomakom, prijavljuje svoju poziciju i veličinu kao tu osnovicu plus sve što je do sada dodano, i prosljeđuje samo novozapisane bajtove u ciljni tok pozivatelja
Posljedica je da način 1 nikada ne materijalizira kopiju izvornog dokumenta — ni na disku, ni u memoriji. Jednostavna implementacija (pisanje cijele datoteke u privremeni međuspremnik, a zatim odsijecanje repa) nosila bi privremenu kopiju cijelog izvornog PDF-a, što je za ulaze na razini gigabajta upravo trošak koji inkrementalna ažuriranja nastoje izbjeći. Ova tehnika virtualizacije pomaka bliski je rođak pomicanja referenci bajtova koje se koristi drugdje u knjižnici; članak o brzom spajanju PDF-a s pomicanjem referenci bajtova prikazuje istu ideju primijenjenu na kombiniranje dokumenata, a vodič za spajanje i dijeljenje velikih PDF-ova s izravnim pristupom datoteci pokriva okolnu I/O arhitekturu za datoteke koje ne stanu komotno u RAM
Strujanje potpunih spremanja pomoću SaveToStream
Inkrementalni izlaz je polovica priče o strujanju; druga polovica je ono što se događa pri potpunom spremanju. SaveToStream u losLab PDF Libraryju pokreće serijalizator dokumenta izravno prema ciljnom toku, umjesto da prvo renderira cijeli dokument u privremeni AnsiString, a zatim ispisuje taj međuspremnik u jednom pozivu. Stariji je pristup funkcionirao, ali je značio da je svako potpuno spremanje prolazno držalo drugu cjelovitu kopiju izlaza u memoriji — bezopasno pri 10 MB, bolno pri 500 MB, i čvrsti zid za višetrajne gigabajtne izlaze na 32-bitnim procesima. Izravna serijalizacija čini da vršna memorija prati strukture objekata dokumenta umjesto njegove serijalizirane duljine
Lekcija o načinu dijeljenja (share-mode): kada je AppendToFile vratio 0
Jednu regresiju na ovom području vrijedi prepričati jer se obrazac neuspjeha može generalizirati. AppendToFile(FileName) dodaje inkrementalno ažuriranje izravno na postojeći PDF na disku — prirodan poziv za tijek rada revizijskog traga na licu mjesta: učitajte datoteku, napravite promjenu, dodajte na istu stazu. U verziji v3.71.2 taj je točan slijed počeo vraćati 0. Korijenski uzrok bio je u loaderu, a ne u piscu: da bi podržao čitanje velikih dokumenata na zahtjev, LoadFromFile drži otvorenu ručicu (handle) izvorne datoteke tijekom životnog vijeka objekta dokumenta, a ta je ručica bila otvorena s fmShareDenyWrite. Kada je AppendToFile potom pokušao ponovno otvoriti istu datoteku za pisanje, loaderov vlastiti način dijeljenja (share mode) ga je odbio, i API je zakazao prije pisanja ijednog bajta
Popravak je ublažio loaderov način dijeljenja na fmShareDenyNone, što je sigurno upravo zbog onoga što inkrementalno dodavanje jest: ono dodaje bajtove strogo nakon kraja datoteke i nikada ne prepisuje regiju koju poslužuje loaderova dugovječna ručica. Opća lekcija za svakoga tko omotava ovu knjižnicu — ili gradi slične stream loadere — jest da su lijeni čitatelji koji drže ručice i pisci koji pišu u istu datoteku u napetosti, a način dijeljenja koji odaberete u trenutku otvaranja je ugovor API-ja, a ne detalj implementacije. Ako AppendToFile ikada vrati 0 u vašem kodu, prvo provjerite drži li još nešto u vašem procesu ciljnu datoteku s restriktivnim načinom dijeljenja
Stvarni troškovi: kada su inkrementalna ažuriranja pogrešan alat
Inkrementalna ažuriranja trguju veličinom datoteke za učinkovitost pisanja, a ta trgovina nije uvijek povoljna. Svaka revizija dodaje svoje izmijenjene objekte dok zamijenjene definicije ostaju u datoteci, pa dokument koji je uređivan stotinama puta akumulira mrtve objekte i dugačak lanac /Prev kroz koji svaki čitatelj mora proći. Što je još gore, "obrisani" sadržaj nije nestao: tekst uklonjen u petoj reviziji i dalje je fizički prisutan u bajtovima četvrte revizije, te ga može oporaviti svatko tko skrati datoteku. Redakcija, sanitizacija ili bilo kakvo uklanjanje osjetljivog sadržaja stoga zahtijeva potpuno prepisivanje — inkrementalno spremanje redakcije je curenje podataka s dodatnim koracima
Potpuno spremanje je također ispravan izbor kada je cilj zbijanje (squeezing out nakupljenih inkremenata i neiskorištenih objekata), kada se mijenjaju svojstva na razini cijelog dokumenta kao što je šifriranje — ponovno šifriranje dodiruje svaki niz i tok, pa više nema ničeg "inkrementalnog" u toj promjeni — ili kada se proizvodi čisti isporučivi dokument gdje povijest uređivanja ne bi trebala putovati s datotekom. Razumno pravilo: koristite AppendToStream ili AppendToFile dok je dokument aktivan i mijenja se, osobito kada nosi potpise; koristite potpuno prepisivanje s SaveToStream na granicama životnog ciklusa, kada dokument napušta vaš sustav ili njegova povijest mora biti izravnana
Inkrementalna ažuriranja, izlaz delte s virtualnim pomakom i serijalizacija izravno u tok dio su standardne losLab PDF Libraryja za Delphi, C# i VB.NET; stranica proizvoda navodi cjelokupnu API površinu za spremanje i dodavanje zajedno s značajkama potpisivanja i velikih datoteka o kojima se raspravljalo gore