Inkrementální aktualizace PDF umožňují aplikaci v Delphi upravovat dokument připojením pouze změněných objektů, přičemž každý původní bajt zůstane nedotčen. losLab PDF Library to implementuje prostřednictvím metody AppendToStream, která zapíše pouze inkrementální část definovanou normou ISO 32000-1 §7.5.6, takže úprava jedné záložky ve 2 GB souboru stojí kilobajty výstupu namísto úplného přepisu. Stejný mechanismus je důvodem, proč lze podepsané dokumenty aktualizovat bez zneplatnění jejich podpisů
Problém, který to řeší, je konkrétní. Úplné uložení přepíše celý soubor: každý objekt je znovu serializován, každý offset křížových odkazů je přepočítán a výstup nemá k vstupu na bajtové úrovni žádný vztah. Pro 40 KB fakturu je to v pořádku. Pro 2 GB naskenovaný archiv, kde jste pouze opravili překlep v názvu dokumentu, je přepisování dvou gigabajtů kvůli změně dvaceti bajtů absurdní — a pokud soubor obsahoval digitální podpis, přepis jej právě zničil
Proč uložení PDF poruší jeho digitální podpis?
Digitální podpis PDF nepodepisuje logický obsah dokumentu; podepisuje rozsahy bajtů fyzického souboru. Položka /ByteRange ve slovníku podpisu zaznamenává přesně to, které úseky souboru kryptografický otisk pokrývá. Jakákoli operace uložení, která tyto bajty znovu serializuje — dokonce i ta, která vytvoří sémanticky identický dokument — změní otisk a každý validátor nahlásí podpis jako poškozený. Toto je záměr: podpis potvrzuje bajty, které podepisující osoba viděla, nikoli nějaký abstraktní model dokumentu
Inkrementální aktualizace jsou únikovou cestou, kterou specifikace PDF poskytuje. Vzhledem k tomu, že inkrementální uložení připojuje nová data za původní %%EOF and nikdy se nedotýká podepsaných rozsahů bajtů, stávající podpis zůstává platný vůči bajtům, které pokrývá. Validátoři pak klasifikují připojené změny odděleně — druhý podpis, vyplnění formuláře, poznámka — a rozhodují, zda se jedná o povolené úpravy. Na tom závisí každý pracovní postup s více podpisy: každý podepisující přidá inkrementální část na konec té předchozí. Pokud vytváříte podpisové linky, doprovodný článek o podepisování a validaci PAdES v Delphi podrobně popisuje, jak spolu interagují rozsahy bajtů podpisu a inkrementální sekce
Jak fungují inkrementální aktualizace podle normy ISO 32000-1 §7.5.6
Norma ISO 32000-1 §7.5.6 definuje model ve třech pravidlech. Za prvé, původní obsah souboru zůstává zcela nedotčen — nepřesune se ani jeden bajt. Za druhé, změněné a nově vytvořené objekty jsou připojeny za poslední %%EOF, každý se stejným číslem objektu, jaké měl předtím (změněné objekty jednoduše získají novější definici, která zastíní tu starou). Za třetí, je připojena nová sekce křížových odkazů a trailer; položka /Prev traileru ukazuje zpět na bajtový offset předchozí sekce křížových odkazů, což tvoří řetězec, který čtečka prochází od nejnovějšího k nejstaršímu k vyhledání nejnovější definice každého objektu
Z této struktury vyplývají dvě užitečné vlastnosti. Aktualizace jsou levné v poměru k tomu, co se změnilo, nikoli k velikosti dokumentu — náklady na připojení představují velikost upravených objektů plus malou režii křížových odkazů (xref) a traileru. A soubor se stává svou vlastní historií verzí: každá předchozí revize je stále fyzicky přítomna, takže auditor může soubor zkrátit na kterémkoli dřívějším %%EOF a obnovit přesně ten dokument, který v daném okamžiku existoval. Pro pracovní postupy zajišťující shodu s předpisy, které musí prokázat, jak dokument vypadal před každou úpravou, je tato integrovaná auditní stopa často rozhodujícím argumentem pro inkrementální ukládání
Zápis inkrementální aktualizace pomocí AppendToStream
losLab PDF Library vystavuje inkrementální výstup prostřednictvím metody AppendToStream(AppendMode: Integer; OutStream: TStream): Integer, která vrací 1 při úspěchu a 0 při selhání. Parametr AppendMode určuje, co se zapíše do cílového streamu. Režim 0 zapisuje kompletní soubor: nejprve se do streamu zkopírují původní zdrojové bajty a poté se připojí inkrementální část. Režim 1 zapisuje pouze samotnou inkrementální část — deltu — a zdrojové bajty zcela vynechává. Režim 2 nejprve zapíše volajícím dodaný prefix zaregistrovaný pomocí SetAppendInputFromString a poté na jeho konec připojí sekci aktualizace
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 zajímavý pro návrh systému. Protože je delta samostatná, můžete ji posílat nezávisle na originálu: ukládat revize jako samostatné objekty v úložišti, replikovat na vzdálené pracoviště pouze delty nebo rekonstruovat jakoukoli revizi zřetězením základního souboru s jeho řetězcem přírůstků. Pravidlo rekonstrukce představuje prosté zřetězení bajtů — nejprve původní soubor, poté každá delta v pořadí — protože to je přesně to rozvržení, které §7.5.6 předepisuje pro inkrementálně aktualizovaný soubor
Jak knihovna počítá offsety křížových odkazů bez kopírování původního souboru?
Položky křížových odkazů uvnitř inkrementální sekce musí obsahovat absolutní bajtové offsety — pozice měřené od začátku kompletního souboru, nikoli od začátku delty. To vytváří rébus pro režim 1: zapisovač nikdy nevysílá původní bajty, přesto se každý zaznamenaný offset musí tvářit, že tam jsou. losLab PDF Library to řeší interním streamovým adaptérem TPDFAppendSectionStream, který serializátoru prezentuje virtuální souřadnicový prostor. Adaptér je vytvořen s délkou bajtů původního souboru jako výchozím offsetem, hlásí svou pozici a velikost jako tuto základnu plus cokoli, co bylo dosud připojeno, a předává pouze nově zapsané bajty do cílového streamu volajícího
Důsledkem je, že režim 1 nikdy nevytváří fyzickou kopii zdrojového dokumentu — ani na disku, ani v paměti. Naivní implementace (zápis celého souboru do dočasné paměti a následné odříznutí konce) by přenášela přechodnou kopii celého původního PDF, což je u gigabajtových vstupů přesně ten náklad, kterému se inkrementální aktualizace snaží vyhnout. Tato technika virtualizace offsetů je blízkým příbuzným posunu bajtových referencí používaného jinde v knihovně; článek o rychlém slučování PDF s posunem bajtových referencí ukazuje stejnou myšlenku aplikovanou na kombinování dokumentů a průvodce pro slučování a rozdělování velkých PDF s přímým přístupem k souborům popisuje související I/O architekturu pro soubory, které se pohodlně nevejdou do RAM
Streamování úplných uložení pomocí SaveToStream
Inkrementální výstup je pouze polovinou příběhu o streamování; druhou polovinou je to, co se děje při úplném uložení. Metoda SaveToStream v losLab PDF Library řídí serializátor dokumentu přímo proti cílovému streamu, namísto toho, aby nejprve vykreslila celý dokument do dočasného AnsiString a poté tento buffer zapsala v jednom volání. Starší přístup fungoval, ale znamenal, že každé úplné uložení přechodně drželo druhou kompletní kopii výstupu v paměti — což je neškodné při 10 MB, bolestivé při 500 MB a nepřekročitelná zeď pro vícedigabajtové výstupy na 32bitových procesech. Přímá serializace zajišťuje, že špičková spotřeba paměti sleduje strukturu objektů dokumentu namísto jeho serializované délky
var
Doc: TPDFlib;
Output: TFileStream;
begin
Doc := TPDFlib.Create;
try
if Doc.LoadFromFile('archive.pdf', '') <= 0 then
Exit;
// ... edits that justify a full rewrite ...
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;
Lekce o sdíleném režimu: když AppendToFile vrátil 0
Jednu regresi v této oblasti stojí za to zopakovat, protože se tento vzorec selhání zobecňuje. Metoda AppendToFile(FileName) připojuje inkrementální aktualizaci přímo do existujícího souboru PDF na disku — což je přirozené volání pro pracovní postup in-place auditní stopy: načíst soubor, provést změnu, připojit ke stejné cestě. Ve verzi v3.71.2 začala tato přesná sekvence vracet 0. Původní příčina spočívala v zavaděči (loaderu), nikoli v zapisovači: pro podporu čtení velkých dokumentů na vyžádání ponechává LoadFromFile popisovač (handle) zdrojového souboru otevřený po celou dobu životnosti objektu dokumentu a tento popisovač byl otevřen s režimem fmShareDenyWrite. Když se pak metoda AppendToFile pokusila znovu otevřít stejný soubor pro zápis, vlastní režim sdílení zavaděče to odmítl a rozhraní API selhalo před zapsáním jediného bajtu
Oprava zmírnila režim sdílení zavaděče na fmShareDenyNone, což je bezpečné právě proto, čím inkrementální připojení je: přidává bajty striktně za konec souboru a nikdy nepřepisuje oblast, kterou obsluhuje dlouhodobý popisovač čtečky. Obecné poučení pro každého, kdo tuto knihovnu obaluje — nebo vytváří podobné streamovací zavaděče — spočívá v tom, že líné popisovače držící čtečky a zapisovače do téhož souboru jsou v napětí a režim sdílení, který zvolíte při otevření, je kontraktem API, nikoli detailem implementace. Pokud metoda AppendToFile ve vašem kódu někdy vrátí 0, zkontrolujte nejprve, zda něco jiného ve vašem procesu stále nedrží cílový soubor s omezujícím režimem sdílení
Upřímné náklady: kdy jsou inkrementální aktualizace nesprávným nástrojem
Inkrementální aktualizace mění velikost souboru za efektivitu zápisu, přičemž tento obchod není vždy výhodný. Každá revize připojuje své změněné objekty, zatímco nahrazené definice zůstávají v souboru, takže dokument upravovaný stokrát hromadí mrtvé objekty a dlouhý řetězec /Prev, který musí každá čtečka projít. Co je horší, „smazaný“ obsah nezmizí: text odstraněný v páté revizi je stále fyzicky přítomen v bajtech čtvrté revize, obnovitelný kýmkoli, kdo soubor zkrátit. Anonymizace (redakce), sanitace nebo jakékoli odstranění citlivého obsahu proto vyžaduje úplný přepis — inkrementální uložení anonymizace je únikem dat s kroky navíc
Úplné uložení je také správnou volbou, když je cílem komprimace (odstranění nahromaděných přírůstků a nepoužívaných objektů), při změně vlastností celého dokumentu, jako je šifrování — opětovné zašifrování se dotkne každého řetězce a streamu, takže na změně nezůstane nic „inkrementálního“ — nebo při vytváření čistého výstupu, u něhož by historie úprav neměla se souborem cestovat. Rozumné pravidlo: používejte AppendToStream nebo AppendToFile, dokud je dokument živý a mění se, zejména jakmile obsahuje podpisy; úplný přepis pomocí SaveToStream použijte na hranicích životního cyklu, kdy dokument opouští váš systém nebo musí být jeho historie zploštěna
Inkrementální aktualizace, výstup delty s virtuálním offsetem a serializace přímo do streamu jsou součástí standardní knihovny losLab PDF Library pro Delphi, C# a VB.NET; stránka produktu uvádí kompletní rozhraní API pro ukládání a připojování spolu s funkcemi podepisování a velkých souborů diskutovanými výše