Skip to content

Incremental updates and why they matter

Evidence: Standard-backed

When a PDF changes after it is written, the safe way to save it is not to rewrite the file. Instead, you append the changed objects and a new cross-reference section to the end, leaving every original byte exactly where it was. This page explains how that works and why it is the reason a digital signature can survive a later edit.

A signature protects a range of bytes. If saving a one-word change rewrote the file, every byte offset would move. The signed range would no longer describe the same content. The signature would break, even though the signed content itself was untouched.

Incremental updates exist so that does not happen. The original bytes, including the bytes a signature covers, stay put. A reviewer can take a signed-then-edited document and verify the first signature against the original revision. The reviewer sees precisely what was signed and, separately, what changed afterward. Get this wrong and you either invalidate good signatures or, worse, lose the ability to prove what a signature actually attested to.

  • An incremental update appends: new and changed objects, then a new cross-reference section, then a new trailer, all at the end of the file.
  • The original file content is left intact — not edited in place.
  • The new trailer carries a /Prev entry: the byte offset of the previous cross-reference section. The sections form a backward chain.
  • A reader builds its index by walking that chain newest-first. For any object number, the most recent entry wins.
  • Because nothing was overwritten, the byte range an earlier signature covered is still byte-for-byte the same — so the signature still verifies, and you can recover the document exactly as it was signed.

NextPDF writes the base document as described on the previous page, then exposes the three things an incremental update needs.

After build(), the writer (src/Writer/PdfWriter.php) keeps:

  • the output buffer, retrievable via getBuffer(), so an update can be appended to the exact end of the existing bytes;
  • the byte offset of the last cross-reference section, via getLastXrefOffset(), which becomes the new section’s /Prev value;
  • the catalog dictionary entries, via getCatalogEntries(), so an update that must re-emit the catalog (for example to attach a signature reference) does not lose any prior keys.

An appended revision allocates new object numbers (or reuses existing ones for objects it replaces) against the same ObjectRegistry, so object numbering stays consistent across revisions. The new cross-reference section lists only the objects this revision touched. The new trailer repeats the previous trailer’s entries and adds /Prev, pointing back at the prior section. That chain is what a reader follows.

The clearest place this matters is signing. NextPDF’s ByteRangeCalculator (src/Security/Signature/ByteRangeCalculator.php) computes the /ByteRange array as two segments: everything before the signature value, and everything after it — so the signature covers the whole revision except its own bytes. Because a later edit is appended rather than written over those bytes, that range never moves.

  1. Write base revision Header, body, xref section, trailer — the original bytes.
  2. Sign A /ByteRange digest covers the whole revision except the signature value itself.
  3. Edit and save Changed objects + a new xref section are appended; originals are untouched.
  4. New trailer chains back The appended trailer carries /Prev = offset of the previous xref section.
  5. Verify The first signature still covers the same unchanged bytes; the chain shows what came after.
How a signed PDF that is later edited stays verifiable: each save appends, so the originally signed bytes are never overwritten and the trailer chain records the history.

The append-only rule is normative. Spec: ISO 32000-2, §7.5.6 states that the contents of a PDF can be updated incrementally without rewriting the entire file, and that when doing so, changes shall be appended to the end of the file, leaving the original contents intact. Evidence: Standard-backed

The same clause defines the mechanics. A cross-reference section for an incremental update contains entries only for objects that were changed, replaced, or deleted. Deleted objects are left in the file but marked deleted through their cross-reference entries. The added trailer shall contain a /Prev entry giving the location of the previous cross-reference section. The update’s entry for a changed object carries the byte offset of the new copy, overriding the old offset. A reader builds its cross-reference information so that the most recent copy of each object is the one accessed.

The signature consequence is stated directly by Spec: ISO 32000-2, §12.8.1 : a byte-range digest is computed over a range of the file — normally the whole file, excluding the signature value (the /Contents entry). The standard then notes that if a signed document is modified and saved by incremental update, the data corresponding to the original signature’s byte range is preserved, so if the signature is valid the document’s state at signing time can be recreated. Append-only is not a nicety. It is the property the signature model depends on.

A signed-then-edited PDF, viewed structurally. The original revision ends at its own %%EOF. The second revision is appended below it.

%PDF-2.0
... original objects, including the signature dictionary ...
xref
0 8
... entries for the original revision ...
trailer
<< /Size 8 /Root 1 0 R >>
startxref
920
%%EOF
<-- end of revision 1: the signed bytes stop here
9 0 obj <-- revision 2, appended
<< /Type /Annot /Subtype /Text /Contents (added after signing) >>
endobj
xref
0 1
9 0 obj-entry...
8 9
0000001740 00000 n
trailer
<< /Size 10 /Root 1 0 R /Prev 920 >>
startxref
1980
%%EOF

A validator reads the last trailer, sees /Prev 920, and now has the whole chain. It can verify the signature against the bytes up to the first %%EOF, which are unchanged. It can then report separately that revision 2 added an annotation. The history is in the file. Nothing was hidden by overwriting.

The trap is “incremental update means the change is small, so it is harmless.” Appending is about byte preservation, not size. An incremental update can add a great deal of content. What makes it an incremental update is that it does not touch the bytes that were already there. The corollary catches people too: a tool that “optimizes” or “linearizes” a signed PDF by rewriting it from scratch will produce a smaller, cleaner file and a broken signature, because the signed byte range no longer exists. Saving a signed PDF and re-saving it are not the same operation.

Append-only protects the bytes. It does not, by itself, tell you whether the appended changes were authorised. A second revision can legitimately add a second signature, or it can add content the first signer never intended. Deciding which is the job of signature validation and modification-detection policy (DocMDP). Appending is the substrate that makes that analysis possible, not the analysis itself.

This page also does not cover how a signature’s two byte ranges are computed and stitched, nor what a complete validation checks. Those are separate topics. And the guarantee here is about files written and updated by a conforming writer: a file whose earlier revisions were already malformed does not become well-formed by being appended to.

How do I know how many revisions a PDF has? Count the %%EOF markers and follow the /Prev chain from the last trailer. Each cross-reference section reached is one saved revision.

Does deleting an object remove it from the file? No. An incremental update marks the object deleted in its cross-reference entry, but the object’s bytes remain in earlier revisions. “Deleted” means “not referenced by the current revision,” not “erased.”

Can an incremental update change the PDF version? Yes, by setting the /Version entry in the catalog in the appended revision. The header stays as written. The catalog /Version takes precedence when it names a later version.

  • Incremental update — saving a change by appending the changed objects, a new cross-reference section, and a new trailer to the end of the file, without altering existing bytes.
  • /Prev — the trailer (or cross-reference stream) entry holding the byte offset of the previous cross-reference section. It links revisions into a backward chain.
  • Revision — the file state captured by one cross-reference section and its trailer. A file with N cross-reference sections has N revisions.
  • /ByteRange — the array in a signature dictionary giving the two byte segments the signature digest covers (everything except the signature value itself).
  • Signed byte range — the exact bytes a signature’s digest was computed over. Incremental updates exist so these bytes are never moved or overwritten.