PDF incremental updates let a Delphi application modify a document by appending only the changed objects, leaving every original byte untouched. losLab PDF Library implements this through AppendToStream, which writes just the incremental section defined by ISO 32000-1 §7.5.6, so a one-bookmark edit to a 2 GB file costs kilobytes of output instead of a full rewrite. The same mechanism is why signed documents can be updated without invalidating their signatures
The pain this solves is concrete. A full save rewrites the whole file: every object is re-serialized, every cross-reference offset is recomputed, and the output bears no byte-level relationship to the input. For a 40 KB invoice that is fine. For a 2 GB scanned archive where you only fixed a typo in the document title, rewriting two gigabytes to change twenty bytes is absurd — and if the file carried a digital signature, the rewrite just destroyed it
Why does saving a PDF break its digital signature?
A PDF digital signature does not sign the document's logical content; it signs byte ranges of the physical file. The /ByteRange entry in the signature dictionary records exactly which spans of the file the cryptographic digest covers. Any save operation that re-serializes those bytes — even one that produces a semantically identical document — changes the digest, and every validator will report the signature as broken. This is by design: the signature attests to the bytes the signer saw, not to some abstract document model
Incremental updates are the escape hatch the PDF specification provides. Because an incremental save appends new data after the original %%EOF and never touches the signed byte ranges, the existing signature keeps validating against the bytes it covers. Validators then classify the appended changes separately — a second signature, a form-fill, an annotation — and decide whether they are permitted modifications. Every multi-signature workflow depends on this: each signer adds an incremental section on top of the last. If you are building signing pipelines, the companion article on PAdES signing and validation in Delphi covers how signature byte ranges and incremental sections interact in detail
How incremental updates work under ISO 32000-1 §7.5.6
ISO 32000-1 §7.5.6 defines the model in three rules. First, the original file content is left entirely intact — not one byte moves. Second, changed and newly created objects are appended after the last %%EOF, each with the same object number it had before (changed objects simply get a newer definition that shadows the old one). Third, a new cross-reference section and trailer are appended; the trailer's /Prev entry points back to the byte offset of the previous cross-reference section, forming a chain that a reader walks from newest to oldest to resolve each object to its most recent definition
Two useful properties fall out of this structure. Updates are cheap in proportion to what changed, not to document size — the append cost is the size of the modified objects plus a small xref/trailer overhead. And the file becomes its own version history: every prior revision is still physically present, so an auditor can truncate the file at any earlier %%EOF and recover exactly the document that existed at that point. For compliance workflows that must prove what a document looked like before each amendment, this built-in audit trail is often the deciding argument for incremental saves
Writing an incremental update with AppendToStream
losLab PDF Library exposes incremental output through AppendToStream(AppendMode: Integer; OutStream: TStream): Integer, which returns 1 on success and 0 on failure. The AppendMode parameter selects what lands in the target stream. Mode 0 writes a complete file: the original source bytes are copied to the stream first, then the incremental section is appended. Mode 1 writes only the incremental section itself — the delta — and skips the source bytes entirely. Mode 2 first writes a caller-supplied prefix registered via SetAppendInputFromString, then appends the update section on top of it
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;
Mode 1 is the interesting one for system design. Because the delta is self-contained, you can ship it independently of the original: store revisions as separate blobs in object storage, replicate only deltas to a remote site, or reconstruct any revision by concatenating the base file with its chain of increments. The reconstruction rule is plain byte concatenation — original file first, then each delta in order — because that is exactly the layout §7.5.6 prescribes for an incrementally updated file
How does the library compute xref offsets without copying the original file?
The cross-reference entries inside an incremental section must contain absolute byte offsets — positions measured from the start of the complete file, not from the start of the delta. That creates a puzzle for mode 1: the writer never emits the original bytes, yet every offset it records has to pretend they are there. losLab PDF Library solves this with an internal stream adapter, TPDFAppendSectionStream, that presents a virtual coordinate space to the serializer. The adapter is created with the original file's byte length as its base offset, reports its position and size as that base plus whatever has been appended so far, and forwards only the newly written bytes to the caller's target stream
The consequence is that mode 1 never materializes a copy of the source document — not on disk, not in memory. The naive implementation (write the full file to a temporary buffer, then slice off the tail) would carry a transient copy of the entire original PDF, which for gigabyte-scale inputs is precisely the cost incremental updates exist to avoid. This offset-virtualization technique is a close cousin of the byte-reference shifting used elsewhere in the library; the article on fast PDF merging with byte reference shifting shows the same idea applied to combining documents, and the guide to large PDF merge and split with direct file access covers the surrounding I/O architecture for files that do not fit comfortably in RAM
Streaming full saves with SaveToStream
Incremental output is half of the streaming story; the other half is what happens on a full save. SaveToStream in losLab PDF Library drives the document serializer directly against the target stream, rather than first rendering the entire document into an intermediate AnsiString and then writing that buffer out in one call. The older approach worked, but it meant every full save transiently held a second complete copy of the output in memory — harmless at 10 MB, painful at 500 MB, and a hard wall for multi-gigabyte outputs on 32-bit processes. Direct serialization makes peak memory track the document's object structures instead of its serialized length
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;
A share-mode lesson: when AppendToFile returned 0
One regression in this area is worth retelling because the failure pattern generalizes. AppendToFile(FileName) appends an incremental update directly to an existing PDF on disk — the natural call for an in-place audit-trail workflow: load a file, make a change, append to the same path. In v3.71.2 that exact sequence started returning 0. The root cause sat in the loader, not the writer: to support on-demand reading of large documents, LoadFromFile keeps the source file handle open for the lifetime of the document object, and that handle was opened with fmShareDenyWrite. When AppendToFile then tried to reopen the same file for writing, the loader's own share mode denied it, and the API failed before writing a byte
The fix relaxed the loader's share mode to fmShareDenyNone, which is safe precisely because of what an incremental append is: it adds bytes strictly after the end of the file and never rewrites the region the reader's long-lived handle is serving. The general lesson for anyone wrapping this library — or building similar streaming loaders — is that lazy, handle-holding readers and same-file writers are in tension, and the share mode you pick at open time is an API contract, not an implementation detail. If AppendToFile ever returns 0 in your code, check first whether something else in your process still holds the target file with a restrictive share mode
The honest costs: when incremental updates are the wrong tool
Incremental updates trade file size for write efficiency, and the trade is not always favorable. Every revision appends its changed objects while the superseded definitions stay in the file, so a document edited hundreds of times accumulates dead objects and a long /Prev chain that every reader must walk. Worse, "deleted" content is not gone: text removed in revision five is still physically present in the bytes of revision four, recoverable by anyone who truncates the file. Redaction, sanitization, or any removal of sensitive content therefore demands a full rewrite — an incremental save of a redaction is a data leak with extra steps
A full save is also the right call when the goal is compaction (squeezing out accumulated increments and unused objects), when changing document-wide properties such as encryption — re-encrypting touches every string and stream, so there is nothing "incremental" left about the change — or when producing a clean deliverable where the editing history should not travel with the file. A reasonable rule: use AppendToStream or AppendToFile while a document is alive and changing, especially once it carries signatures; use a full SaveToStream rewrite at lifecycle boundaries, when the document leaves your system or its history must be flattened
Incremental updates, virtual-offset delta output, and direct-to-stream serialization are all part of the standard losLab PDF Library for Delphi, C# and VB.NET; the product page lists the full save and append API surface alongside the signing and large-file features discussed above