Inkrementalna ažuriranja PDF-a omogućuju Delphi aplikaciji da dokument izmijeni tako da doda samo promijenjene objekte, ostavljajući svaki izvorni bajt netaknutim. losLab PDF Library to ostvaruje metodom AppendToStream, koja zapisuje samo inkrementalni odsječak definiran u ISO 32000-1 §7.5.6, pa izmjena jedne knjižne oznake u datoteci od 2 GB stoji kilobajte izlaza umjesto potpunog prepisivanja. Isti je mehanizam razlog zašto se potpisani dokumenti mogu ažurirati bez poništavanja njihovih potpisa
Muka koju to rješava sasvim je opipljiva. Potpuno spremanje prepisuje cijelu datoteku: svaki se objekt iznova serijalizira, svaki se pomak u tablici unakrsnih referenci ponovno izračunava, a izlaz s ulazom nema nikakvu vezu na razini bajtova. Za račun od 40 KB to je u redu. Za skenirani arhiv od 2 GB u kojemu ste ispravili samo tipfeler u naslovu dokumenta, prepisivanje dva gigabajta radi promjene dvadeset bajtova je besmisleno — a ako je datoteka nosila digitalni potpis, prepisivanje ga je upravo uništilo
Zašto spremanje PDF-a razbija njegov digitalni potpis?
Digitalni potpis PDF-a ne potpisuje logički sadržaj dokumenta; potpisuje raspone bajtova fizičke datoteke. Unos /ByteRange u rječniku potpisa bilježi točno koje raspone datoteke kriptografski sažetak pokriva. Svaka operacija spremanja koja te bajtove iznova serijalizira — pa i ona koja proizvede semantički istovjetan dokument — mijenja sažetak, i svaki će validator potpis prijaviti kao neispravan. To je tako zamišljeno: potpis jamči za bajtove koje je potpisnik vidio, a ne za neki apstraktni model dokumenta
Inkrementalna ažuriranja izlaz su za nuždu koji PDF specifikacija predviđa. Budući da inkrementalno spremanje nove podatke dodaje iza izvornog %%EOF i nikada ne dira potpisane raspone bajtova, postojeći potpis i dalje uspješno prolazi provjeru nad bajtovima koje pokriva. Validatori potom dodane promjene razvrstavaju zasebno — drugi potpis, ispunjavanje obrasca, bilješku — i odlučuju jesu li to dopuštene izmjene. O tome ovisi svaki tijek rada s više potpisa: svaki potpisnik povrh prethodnoga dodaje svoj inkrementalni odsječak. Ako gradite cjevovode za potpisivanje, prateći članak o PAdES potpisivanju i provjeri u Delphiju potanko obrađuje kako se rasponi bajtova potpisa i inkrementalni odsječci međusobno odnose
Kako inkrementalna ažuriranja rade prema ISO 32000-1 §7.5.6
ISO 32000-1 §7.5.6 model definira trima pravilima. Prvo, sadržaj izvorne datoteke ostaje potpuno netaknut — nijedan se bajt ne pomiče. Drugo, promijenjeni i novostvoreni objekti dodaju se iza posljednjeg %%EOF, svaki s istim brojem objekta koji je imao i prije (promijenjeni objekti jednostavno dobiju noviju definiciju koja zasjenjuje staru). Treće, dodaju se novi odsječak unakrsnih referenci i novi trailer; unos /Prev u traileru pokazuje natrag na pomak u bajtovima prethodnog odsječka unakrsnih referenci i tvori lanac kojim čitač ide od najnovijeg prema najstarijem da bi svaki objekt razriješio u njegovu najnoviju definiciju
Iz te strukture proizlaze dva korisna svojstva. Ažuriranja su jeftina razmjerno onome što se promijenilo, a ne veličini dokumenta — trošak dodavanja veličina je izmijenjenih objekata uvećana za mali dodatak za xref i trailer. I datoteka postaje vlastita povijest verzija: svaka je ranija revizija još fizički prisutna, pa revizor datoteku može odsjeći na bilo kojem ranijem %%EOF i vratiti točno onaj dokument koji je u tom trenutku postojao. Za tijekove rada sa zahtjevima usklađenosti koji moraju dokazati kako je dokument izgledao prije svake izmjene, taj ugrađeni revizijski trag često je odlučujući argument za inkrementalno spremanje
Zapisivanje inkrementalnog ažuriranja metodom AppendToStream
losLab PDF Library inkrementalni izlaz izlaže kroz AppendToStream(AppendMode: Integer; OutStream: TStream): Integer, koja pri uspjehu vraća 1, a pri neuspjehu 0. Parametar AppendMode bira što će završiti u ciljnom toku. Način 0 zapisuje potpunu datoteku: u tok se najprije kopiraju izvorni bajtovi, a zatim se dodaje inkrementalni odsječak. Način 1 zapisuje samo inkrementalni odsječak — deltu — i izvorne bajtove potpuno preskače. Način 2 najprije zapisuje predmetak koji je pozivatelj prijavio putem SetAppendInputFromString, a zatim povrh njega dodaje odsječak ažuriranja
var
Doc: TPDFlib;
Delta: TMemoryStream;
begin
Doc := TPDFlib.Create;
try
if Doc.LoadFromFile('contract.pdf', '') <= 0 then
Exit;
// Mala izmjena: vrsta promjene koja ne bi smjela
// pokrenuti prepisivanje cijele datoteke
Doc.SetInformation(3, 'Amended 2026-07-04'); // ključ 3 = /Subject
Delta := TMemoryStream.Create;
try
// AppendMode = 1: zapiši samo inkrementalni odsječak.
// Izvorni bajtovi + Delta = potpun, valjan 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 zanimljiv je za projektiranje sustava. Budući da je delta samodostatna, možete je isporučiti neovisno o izvorniku: revizije pohranite kao zasebne binarne objekte u objektnoj pohrani, na udaljenu lokaciju replicirajte samo delte ili bilo koju reviziju rekonstruirajte spajanjem osnovne datoteke s njezinim lancem prirasta. Pravilo rekonstrukcije obično je nadovezivanje bajtova — najprije izvorna datoteka, zatim svaka delta redom — jer je upravo takav raspored §7.5.6 propisuje za inkrementalno ažuriranu datoteku
Kako biblioteka računa xref pomake bez kopiranja izvorne datoteke?
Unosi unakrsnih referenci unutar inkrementalnog odsječka moraju sadržavati apsolutne pomake u bajtovima — položaje mjerene od početka potpune datoteke, a ne od početka delte. Za način 1 to je zagonetka: zapisivač izvorne bajtove nikada ne ispisuje, a ipak se svaki pomak koji bilježi mora ponašati kao da su ondje. losLab PDF Library to rješava internim prilagodnikom toka, TPDFAppendSectionStream, koji serijalizatoru predstavlja virtualni koordinatni prostor. Prilagodnik se stvara s duljinom izvorne datoteke u bajtovima kao osnovnim pomakom, svoj položaj i veličinu prijavljuje kao tu osnovicu uvećanu za sve što je dosad dodano, a ciljnom toku pozivatelja prosljeđuje samo novozapisane bajtove
Posljedica je da način 1 nikada ne materijalizira kopiju izvornog dokumenta — ni na disku ni u memoriji. Naivna bi izvedba (zapiši cijelu datoteku u privremeni međuspremnik, pa odreži rep) nosila prolaznu kopiju cijelog izvornog PDF-a, a upravo je to trošak zbog kojeg inkrementalna ažuriranja i postoje kada se radi o ulazima veličine gigabajta. Ta tehnika virtualizacije pomaka bliska je rođakinja pomicanja referenci na bajtove koje se u biblioteci koristi drugdje; članak o brzom spajanju PDF-ova pomicanjem referenci na bajtove pokazuje istu zamisao primijenjenu na spajanje dokumenata, a vodič za spajanje i dijeljenje velikih PDF-ova s izravnim pristupom datoteci obrađuje okolnu ulazno-izlaznu arhitekturu za datoteke koje se ne uklapaju udobno u radnu memoriju
Potpuno spremanje u tok metodom SaveToStream
Inkrementalni izlaz polovica je priče o tokovima; druga je polovica ono što se događa pri potpunom spremanju. SaveToStream u losLab PDF Library serijalizator dokumenta pogoni izravno prema ciljnom toku, umjesto da cijeli dokument najprije prikaže u međuspremni AnsiString pa taj međuspremnik ispiše jednim pozivom. Stariji je pristup radio, ali je značio da svako potpuno spremanje prolazno drži drugu potpunu kopiju izlaza u memoriji — bezopasno na 10 MB, bolno na 500 MB, i nepremostiv zid za izlaze od više gigabajta u 32-bitnim procesima. Izravnom serijalizacijom vršna memorija prati objektne strukture dokumenta, a ne duljinu njegova serijaliziranog oblika
var
Doc: TPDFlib;
Output: TFileStream;
begin
Doc := TPDFlib.Create;
try
if Doc.LoadFromFile('archive.pdf', '') <= 0 then
Exit;
// ... izmjene 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 načinu dijeljenja: kada je AppendToFile vraćao 0
Jednu regresiju s ovog područja vrijedi prepričati jer se obrazac kvara može poopćiti. AppendToFile(FileName) inkrementalno ažuriranje dodaje izravno na postojeći PDF na disku — prirodan poziv za tijek rada s revizijskim tragom na mjestu: učitaj datoteku, napravi promjenu, dodaj na istu putanju. U verziji v3.71.2 upravo je taj slijed počeo vraćati 0. Uzrok je bio u učitavaču, a ne u zapisivaču: da bi podržao čitanje velikih dokumenata na zahtjev, LoadFromFile rukovatelj izvorne datoteke drži otvorenim koliko traje objekt dokumenta, a taj je rukovatelj bio otvoren s fmShareDenyWrite. Kada je AppendToFile potom pokušao istu datoteku ponovno otvoriti za pisanje, to mu je zabranio način dijeljenja samog učitavača, i API je zakazao prije nego što je zapisao ijedan bajt
Popravak je način dijeljenja u učitavaču ublažio na fmShareDenyNone, što je sigurno upravo zbog toga što inkrementalno dodavanje jest: ono bajtove dodaje strogo iza kraja datoteke i nikada ne prepisuje područje koje poslužuje dugoživući rukovatelj čitača. Opća pouka za svakoga tko ovu biblioteku omata — ili gradi slične učitavače nad tokovima — jest da su lijeni čitači koji drže rukovatelja i zapisivači nad istom datotekom u napetosti, te da je način dijeljenja koji odaberete pri otvaranju dio ugovora API-ja, a ne detalj izvedbe. Ako vam AppendToFile ikad vrati 0, prvo provjerite drži li nešto drugo u vašem procesu ciljnu datoteku s ograničavajućim načinom dijeljenja
Iskreni troškovi: kada su inkrementalna ažuriranja pogrešan alat
Inkrementalna ažuriranja veličinu datoteke mijenjaju za učinkovitost zapisivanja, a ta razmjena nije uvijek povoljna. Svaka revizija dodaje svoje promijenjene objekte, dok nadomještene definicije ostaju u datoteci, pa dokument uređivan više stotina puta nakuplja mrtve objekte i dugačak lanac /Prev koji svaki čitač mora proći. Još gore, „izbrisani“ sadržaj nije nestao: tekst uklonjen u petoj reviziji fizički je i dalje prisutan u bajtovima četvrte revizije i može ga vratiti svatko tko datoteku odsiječe. Redakcija, čišćenje ili bilo kakvo uklanjanje osjetljivog sadržaja stoga traži potpuno prepisivanje — inkrementalno spremanje redakcije curenje je podataka s nekoliko dodatnih koraka
Potpuno je spremanje ispravan potez i kada je cilj sažimanje (istiskivanje nakupljenih prirasta i neiskorištenih objekata), kada se mijenjaju svojstva koja vrijede za cijeli dokument, poput enkripcije — ponovno šifriranje dira svaki niz znakova i svaki tok, pa na promjeni ne ostaje ništa „inkrementalno“ — ili kada se izrađuje čist isporučivi dokument uz koji povijest uređivanja ne bi smjela putovati. Razumno pravilo: koristite AppendToStream ili AppendToFile dok je dokument živ i mijenja se, osobito otkad nosi potpise; posegnite za potpunim prepisivanjem metodom SaveToStream na granicama životnog ciklusa, kada dokument napušta vaš sustav ili kada njegovu povijest treba poravnati
Inkrementalna ažuriranja, izlaz delte s virtualnim pomacima i serijalizacija izravno u tok dio su standardne biblioteke losLab PDF Library za Delphi, C# i VB.NET; stranica proizvoda navodi potpunu površinu API-ja za spremanje i dodavanje uz značajke za potpisivanje i velike datoteke o kojima je bilo riječi