Pro edition
Writer
At a glance
Section titled “At a glance”The Writer module appends incremental-update revisions to a PDF and packs small objects into Object Streams. The incremental writer enforces an append-only rule: bytes that existed before the revision must not change.
Availability & licensing
Section titled “Availability & licensing”This capability ships in NextPDF Pro (nextpdf/pro) and activates with a Pro-tier license envelope. A deployment without that entitlement does not load the capability’s classes. Compare editions and get a license. There is no separate per-feature license flag; the code ships with the Pro edition.
Install
Section titled “Install”composer require nextpdf/pro:^3The code lives under the NextPDF\Pro\Writer namespace.
Conceptual overview
Section titled “Conceptual overview”Two capabilities are provided:
IncrementalUpdateWriterwrites a new revision. It re-writes the catalog with merged entries, appends a traditional cross-reference table for the new and modified objects, and writes a trailer that links to the previous revision. It enforces a fail-closed append-only rule.ObjectStreamWritergroups small objects into a single compressed Object Stream. This reduces cross-reference table size and improves compression. It rejects objects that would exceed the maximum stream size and rejects an empty stream.
The append-only rule protects existing signatures. Every byte the buffer held before the revision must appear unchanged at the same position after the revision. If any earlier byte changes, the writer raises an error and does not produce output.
Why it works this way
Section titled “Why it works this way”The load-bearing choice is where the append-only gate lives. It sits at writer scope, not only in higher-level orchestrators, so every present and future caller inherits fail-closed coverage. The check is a pure prefix-equality test: the writer snapshots the buffer prefix before appending, then confirms every earlier byte is unchanged afterward. This protects any signature whose /ByteRange covered the prefix, since one altered byte would silently invalidate it. Traditional cross-reference tables and a /Prev pointer carry the new revision, because incremental updates must append rather than rewrite. The verification cost is linear in the existing prefix, and that cost is accepted deliberately: signed-byte integrity outranks a second copy.
Design background: Incremental updates and why they matter.
Behavior contract
Section titled “Behavior contract”IncrementalUpdateWriter::writeRevision(...)returns the byte offset of the new cross-reference table, so you can chain further revisions.- The writer verifies the original prefix is byte-equal before and after writing. A divergence raises a writer exception that carries an append-only-violation state.
- The new revision uses a traditional cross-reference table and a trailer with a
/Prevpointer; mixing tables and streams across revisions is permitted. ObjectStreamWriter::addObject()raises an overflow error when adding an object would exceed the maximum stream size (65,536 bytes uncompressed for index plus body).ObjectStreamWriter::build()raises an error when no objects were added; otherwise it returns the compressed Object Stream content.
Code sample — Quick start
Section titled “Code sample — Quick start”The following reflects the documented public API. The repository does not ship a runnable example for this module.
use NextPDF\Pro\Writer\ObjectStreamWriter;
$writer = new ObjectStreamWriter();$writer->addObject(10, $serializedObjectBody);$objStm = $writer->build();Code sample — Production
Section titled “Code sample — Production”use NextPDF\Pro\Writer\IncrementalUpdateWriter;
$newXrefOffset = IncrementalUpdateWriter::writeRevision( $buffer, $registry, $prevXrefOffset, $catalogObject, $catalogEntries, $catalogUpdates, $newObjectNumbers, $fileId,);// A WriterException here means the append-only rule was violated.// Treat it as a hard failure; do not emit the output.Edge cases & gotchas
Section titled “Edge cases & gotchas”- The append-only check copies the existing prefix. The cost grows with the size of the already-written document. This cost is intentional and protects signed bytes.
- The Object Stream size limit is on the combined index and body before compression. Group objects accordingly.
- Object Streams must not contain certain object types (for example, the encryption dictionary). Place those as direct indirect objects.
Performance
Section titled “Performance”The append-only verification is linear in the size of the existing document prefix. Object Stream packing reduces cross-reference size and improves compression at the cost of one extra compression pass. There is no published throughput figure. Measure with representative documents.
Security notes
Section titled “Security notes”The incremental writer is fail-closed. If a code path would change a byte that an earlier signature covered, the writer raises an error instead of producing a document. This protects signature integrity for revision-chained workflows. No document content is logged.
Conformance
Section titled “Conformance”The source annotates the incremental-update grammar and Object Stream model in ISO 32000-2 and the revision-chaining requirements in the ETSI EN 319 142-1 PAdES profile.
Enterprise boundary note
Section titled “Enterprise boundary note”Enterprise adds higher-tier signature lifecycle features (long-term validation and renewal) that build on incremental updates at a behavior level. The Writer module provides the revision primitive only; those higher-tier features are documented separately and are not required to write a revision.
Core fallback / alternative
Section titled “Core fallback / alternative”Without Pro, use NextPDF Core’s base writer; incremental-update revisions with the append-only gate and Object Stream packing are Pro additions. See /modules/writer/.
Publication boundary
Section titled “Publication boundary”This page documents externally observable behavior and the supported public API surface only. Internal namespace paths, helper classes, mechanism tables, runbook filenames, and ticket prefixes are out of scope.