Inkrementalna ažuriranja PDF-a omogućavaju Delphi aplikaciji da izmeni dokument dodavanjem samo promenjenih objekata, ostavljajući svaki izvorni bajt netaknutim. losLab PDF Library to sprovodi kroz AppendToStream, koji upisuje samo inkrementalnu sekciju definisanu u ISO 32000-1 §7.5.6, pa izmena jednog obeleživača u fajlu od 2 GB košta kilobajte izlaza umesto potpunog prepisivanja. Isti mehanizam je razlog zašto potpisani dokumenti mogu da se ažuriraju bez poništavanja svojih potpisa
Muka koju ovo rešava je konkretna. Potpuno čuvanje prepisuje ceo fajl: svaki objekat se ponovo serijalizuje, svaki pomeraj unakrsne reference se ponovo izračunava, a izlaz nema nikakvu vezu na nivou bajtova sa ulazom. Za fakturu od 40 KB to je sasvim u redu. Za skeniranu arhivu od 2 GB u kojoj ste ispravili samo štamparsku grešku u naslovu dokumenta, prepisivanje dva gigabajta zarad izmene od dvadeset bajtova je apsurdno — a ako je fajl nosio digitalni potpis, prepisivanje ga je upravo uništilo
Zašto čuvanje PDF-a kvari njegov digitalni potpis?
PDF digitalni potpis ne potpisuje logički sadržaj dokumenta; on potpisuje opsege bajtova fizičkog fajla. Unos /ByteRange u rečniku potpisa beleži tačno koje raspone fajla pokriva kriptografski sažetak. Svaka operacija čuvanja koja ponovo serijalizuje te bajtove — čak i ona koja proizvodi semantički istovetan dokument — menja sažetak, a svaki validator će prijaviti potpis kao pokvaren. Ovo je namerno tako: potpis jemči za bajtove koje je potpisnik video, a ne za neki apstraktni model dokumenta
Inkrementalna ažuriranja su izlaz koji PDF specifikacija nudi. Pošto inkrementalno čuvanje dodaje nove podatke posle originalnog %%EOF i nikada ne dira potpisane opsege bajtova, postojeći potpis i dalje važi nad bajtovima koje pokriva. Validatori zatim zasebno klasifikuju dodate izmene — drugi potpis, popunjavanje obrasca, anotaciju — i odlučuju da li su to dozvoljene modifikacije. Svaki tok rada sa više potpisa počiva na ovome: svaki potpisnik dodaje inkrementalnu sekciju povrh prethodne. Ako gradite lance potpisivanja, prateći članak o PAdES potpisivanju i validaciji u Delphiju detaljno obrađuje kako opsezi bajtova potpisa i inkrementalne sekcije međusobno deluju
Kako inkrementalna ažuriranja rade po ISO 32000-1 §7.5.6
ISO 32000-1 §7.5.6 definiše model kroz tri pravila. Prvo, izvorni sadržaj fajla ostaje potpuno netaknut — nijedan bajt se ne pomera. Drugo, promenjeni i novostvoreni objekti dodaju se posle poslednjeg %%EOF, svaki sa istim brojem objekta koji je imao i ranije (promenjeni objekti prosto dobijaju noviju definiciju koja zaklanja staru). Treće, dodaju se nova sekcija unakrsnih referenci i novi trejler; unos /Prev u trejleru pokazuje nazad na pomeraj bajta prethodne sekcije unakrsnih referenci, čime nastaje lanac kroz koji čitač ide od najnovijeg ka najstarijem da bi svaki objekat razrešio do njegove najsvežije definicije
Iz ove strukture proizlaze dva korisna svojstva. Ažuriranja su jeftina srazmerno onome što se promenilo, a ne veličini dokumenta — cena dodavanja je veličina izmenjenih objekata plus mali režijski trošak xref-a i trejlera. A fajl postaje sopstvena istorija verzija: svaka ranija revizija je i dalje fizički prisutna, pa revizor može da odseče fajl na bilo kom ranijem %%EOF i povrati tačno onaj dokument koji je tada postojao. Za tokove rada usklađenosti koji moraju da dokažu kako je dokument izgledao pre svake dopune, ovaj ugrađeni revizorski trag je često presudan argument za inkrementalno čuvanje
Upisivanje inkrementalnog ažuriranja pomoću AppendToStream
losLab PDF Library izlaže inkrementalni izlaz kroz AppendToStream(AppendMode: Integer; OutStream: TStream): Integer, koji vraća 1 pri uspehu i 0 pri neuspehu. Parametar AppendMode bira šta završava u ciljnom toku. Režim 0 upisuje kompletan fajl: prvo se izvorni bajtovi kopiraju u tok, a zatim se dodaje inkrementalna sekcija. Režim 1 upisuje samo samu inkrementalnu sekciju — deltu — i potpuno preskače izvorne bajtove. Režim 2 najpre upisuje prefiks koji je pozivalac prijavio preko SetAppendInputFromString, a zatim povrh njega dodaje sekciju ažuriranja
var
Doc: TPDFlib;
Delta: TMemoryStream;
begin
Doc := TPDFlib.Create;
try
if Doc.LoadFromFile('contract.pdf', '') <= 0 then
Exit;
// Mala izmena: vrsta promene koja ne bi smela
// da izazove prepisivanje celog fajla
Doc.SetInformation(3, 'Amended 2026-07-04'); // ključ 3 = /Subject
Delta := TMemoryStream.Create;
try
// AppendMode = 1: upisuje samo inkrementalnu sekciju.
// Originalni bajtovi + Delta = kompletan, ispravan 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 zanimljiv za projektovanje sistema. Pošto je delta samodovoljna, možete je isporučiti nezavisno od originala: čuvajte revizije kao odvojene blobove u objektnom skladištu, replicirajte na udaljenu lokaciju samo delte ili rekonstruišite bilo koju reviziju spajanjem osnovnog fajla sa njegovim lancem inkremenata. Pravilo rekonstrukcije je obično nadovezivanje bajtova — prvo originalni fajl, pa svaka delta redom — jer je upravo to raspored koji §7.5.6 propisuje za inkrementalno ažuriran fajl
Kako biblioteka računa xref pomeraje bez kopiranja originalnog fajla?
Unosi unakrsnih referenci unutar inkrementalne sekcije moraju da sadrže apsolutne pomeraje bajtova — pozicije merene od početka kompletnog fajla, a ne od početka delte. To pravi zagonetku za režim 1: upisivač nikada ne emituje originalne bajtove, a ipak svaki pomeraj koji beleži mora da se pravi da su oni tu. losLab PDF Library to rešava internim adapterom toka, TPDFAppendSectionStream, koji serijalizatoru predstavlja virtuelni koordinatni prostor. Adapter se pravi sa dužinom originalnog fajla u bajtovima kao baznim pomerajem, prijavljuje svoju poziciju i veličinu kao tu bazu plus sve što je dosad dodato, i prosleđuje ciljnom toku pozivaoca samo novoupisane bajtove
Posledica je da režim 1 nikada ne materijalizuje kopiju izvornog dokumenta — ni na disku, ni u memoriji. Naivna implementacija (upisati ceo fajl u privremeni bafer, pa odseći rep) nosila bi prolaznu kopiju celog originalnog PDF-a, što je kod ulaza reda veličine gigabajta upravo onaj trošak zbog kog inkrementalna ažuriranja i postoje. Ova tehnika virtuelizacije pomeraja bliska je rođaka pomeranju referenci na bajtove koje se koristi drugde u biblioteci; članak o brzom spajanju PDF-ova uz pomeranje referenci na bajtove pokazuje istu ideju primenjenu na kombinovanje dokumenata, a vodič za spajanje i deljenje velikih PDF-ova uz direktan pristup fajlu obrađuje okolnu U/I arhitekturu za fajlove koji ne staju udobno u RAM
Tokovno potpuno čuvanje pomoću SaveToStream
Inkrementalni izlaz je polovina priče o tokovima; druga polovina je šta se dešava pri potpunom čuvanju. SaveToStream u losLab PDF Library vodi serijalizator dokumenta direktno u ciljni tok, umesto da prvo iscrta ceo dokument u međukorak tipa AnsiString pa taj bafer ispiše jednim pozivom. Stariji pristup je radio, ali je značio da svako potpuno čuvanje prolazno drži drugu kompletnu kopiju izlaza u memoriji — bezopasno na 10 MB, bolno na 500 MB, i tvrd zid za izlaze od više gigabajta u 32-bitnim procesima. Direktna serijalizacija čini da vršna memorija prati strukture objekata dokumenta umesto njegove serijalizovane dužine
var
Doc: TPDFlib;
Output: TFileStream;
begin
Doc := TPDFlib.Create;
try
if Doc.LoadFromFile('archive.pdf', '') <= 0 then
Exit;
// ... izmene koje opravdavaju potpuno prepisivanje ...
Output := TFileStream.Create('archive-rewritten.pdf', fmCreate);
try
if Doc.SaveToStream(Output) = 0 then
Writeln('Save failed, error ', Doc.LastErrorCode);
finally
Output.Free;
end;
finally
Doc.Free;
end;
end;
Lekcija o režimu deljenja: kada je AppendToFile vraćao 0
Jedna regresija u ovoj oblasti vredi prepričavanja jer se obrazac otkaza lako uopštava. AppendToFile(FileName) dodaje inkrementalno ažuriranje direktno na postojeći PDF na disku — prirodan poziv za tok rada revizorskog traga na licu mesta: učitati fajl, napraviti izmenu, dodati je na istu putanju. U verziji v3.71.2 upravo taj niz koraka počeo je da vraća 0. Uzrok je bio u učitavaču, ne u upisivaču: da bi podržao čitanje velikih dokumenata po potrebi, LoadFromFile drži otvorenu ručku izvornog fajla tokom celog života objekta dokumenta, a ta ručka je otvarana sa fmShareDenyWrite. Kada je AppendToFile potom pokušao da ponovo otvori isti fajl radi upisa, sopstveni režim deljenja učitavača mu je to odbio, i API je otkazao pre nego što je upisao ijedan bajt
Ispravka je olabavila režim deljenja učitavača na fmShareDenyNone, što je bezbedno upravo zbog toga šta inkrementalno dodavanje jeste: ono dodaje bajtove strogo iza kraja fajla i nikada ne prepisuje oblast koju opslužuje dugoživeća ručka čitača. Opšta pouka za svakoga ko oblaže ovu biblioteku — ili gradi slične tokovne učitavače — jeste da su lenji čitači koji drže ručke i upisivači nad istim fajlom u zategnutom odnosu, i da je režim deljenja koji birate pri otvaranju ugovor API-ja, a ne detalj implementacije. Ako AppendToFile ikada vrati 0 u vašem kodu, prvo proverite da li nešto drugo u vašem procesu i dalje drži ciljni fajl sa restriktivnim režimom deljenja
Iskrena cena: kada su inkrementalna ažuriranja pogrešan alat
Inkrementalna ažuriranja menjaju veličinu fajla za efikasnost upisa, i ta razmena nije uvek povoljna. Svaka revizija dodaje svoje izmenjene objekte dok prevaziđene definicije ostaju u fajlu, pa dokument izmenjen stotinama puta gomila mrtve objekte i dugačak /Prev lanac kroz koji svaki čitač mora da prođe. Gore od toga, „obrisan” sadržaj nije nestao: tekst uklonjen u petoj reviziji i dalje je fizički prisutan u bajtovima četvrte revizije i može ga povratiti svako ko odseče fajl. Redakcija, sanitizacija ili bilo kakvo uklanjanje osetljivog sadržaja zato traže potpuno prepisivanje — inkrementalno čuvanje redakcije je curenje podataka sa dodatnim koracima
Potpuno čuvanje je pravi izbor i kada je cilj sabijanje (isterivanje nagomilanih inkremenata i neiskorišćenih objekata), kada se menjaju svojstva na nivou celog dokumenta poput šifrovanja — ponovno šifrovanje dira svaki string i svaki tok, pa u toj izmeni ne ostaje ništa „inkrementalno” — ili kada pravite čist isporučivi dokument uz koji istorija uređivanja ne treba da putuje. Razumno pravilo: koristite AppendToStream ili AppendToFile dok je dokument živ i menja se, posebno kada nosi potpise; koristite potpuno prepisivanje preko SaveToStream na granicama životnog ciklusa, kada dokument napušta vaš sistem ili kada njegova istorija mora da se spljošti
Inkrementalna ažuriranja, izlaz delte sa virtuelnim pomerajima i serijalizacija direktno u tok deo su standardne biblioteke losLab PDF Library za Delphi, C# i VB.NET; stranica proizvoda navodi kompletnu API površinu za čuvanje i dodavanje uz funkcije potpisivanja i rada sa velikim fajlovima o kojima je bilo reči