Skip to content
getnextpdf.com

stability: Experimental

PageBackfill: retained page buffer

Opt-in preview. The retained page buffer is default-off. With it off, the writer is the streaming serializer it has always been — byte-identical. Turn it on only when you genuinely need to draw onto an earlier page, and read the fail-closed list below first.

By default, the writer streams pages and flushes them in order; once a page is flushed it cannot be drawn on again. The retained page buffer is the opt-in that holds flushed pages so a previously flushed page can be back-filled — drawn onto an earlier page — before the document is serialized. The classic use is a total or a summary box that you can place only after later pages have been laid out.

Terminal window
composer require nextpdf/core:^3

The retained page buffer ships in the core package. Config::withRetainedPageBuffer() and the Document back-fill methods are @since 6.1.0. The default remains the streaming writer. ADR-037, which had previously deferred this capability, is now recorded as implemented.

Config::withRetainedPageBuffer() opts a document into retained pages. Once on, Document::setActiveBackfillPage(int $pageIndex) redirects drawing to an earlier, already-flushed page; Document::endPageBackfill() returns drawing to the normal append position. Content you write between the two calls lands on the earlier page. The buffer holds pages until save(), so the back-fill is applied before the cross-reference table and trailer are written (ISO 32000-2 §7.5).

Fail-closed boundary — refused combinations

Section titled “Fail-closed boundary — refused combinations”

Back-fill is a random-access operation, and several document features assume append-only, streamed bytes. The retained page buffer refuses to combine with any of them, order-independently and before serialization, so it can never silently break a signature or a conformance claim:

  • A digital signature.
  • Tagged PDF (structure tree).
  • PDF/A.
  • Linearization.
  • Object-stream packing.
  • Encryption.
  • Safe CSS rendering mode.

A per-document uncompressed-bytes budget caps how much the buffer may hold; a document that exceeds it hard-fails rather than consuming unbounded memory. The streaming default still fails closed the moment a caller attempts a random-access switch without the opt-in — turning the buffer on is the only way to get back-fill, and it is incompatible with the features above by construction.

SymbolLocationRole
Config::withRetainedPageBuffer(bool $enabled = true): selfsrc/Core/Config.phpOpt a document into the retained page buffer.
Document::setActiveBackfillPage(int $pageIndex): staticsrc/Core/Document.phpRedirect drawing to an earlier, already-flushed page.
Document::endPageBackfill(): staticsrc/Core/Document.phpReturn drawing to the normal append position.

A back-fill attempt that violates a refused combination raises a typed configuration exception at the boundary, not a corrupted document.

Reserve a spot on page one, fill the rest of the document, then back-fill the reserved spot with a value computed at the end.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Config;
use NextPDF\Core\Document;
$config = (new Config())->withRetainedPageBuffer();
$doc = Document::createStandalone($config);
$doc->addPage(); // page 0 — leaves room for a grand total
$doc->writeHtml('<h1>Invoice</h1>');
$doc->addPage(); // page 1 — line items
$doc->writeHtml('<p>Line items…</p>');
$total = 1234.56; // computed after laying out the items
$doc->setActiveBackfillPage(0); // draw back onto page 0
$doc->writeHtml('<p>Grand total: ' . number_format($total, 2) . '</p>');
$doc->endPageBackfill();
$doc->save(__DIR__ . '/invoice.pdf');

Keep the buffer off for any signed, tagged, PDF/A, linearized, encrypted, or object-stream document — those are exactly the combinations the buffer refuses. Choose one path explicitly.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Config;
use NextPDF\Core\Document;
function renderReport(bool $needsBackfill, bool $mustBeSigned): Document
{
if ($needsBackfill && $mustBeSigned) {
// The buffer refuses to combine with signing. Resolve the requirement
// before building: pre-compute the value, or sign a separate pass.
throw new \LogicException('Back-fill and signing are mutually exclusive.');
}
$config = new Config();
if ($needsBackfill) {
$config = $config->withRetainedPageBuffer();
}
return Document::createStandalone($config);
}
  • Off is byte-identical. With the buffer off, the writer streams as before.
  • Mutually exclusive with signing, tagging, PDF/A, linearization, object streams, encryption, and Safe CSS mode. The refusal is order-independent and fires before serialization. Plan the document for one mode or the other.
  • A bytes budget hard-fails. The retained buffer is bounded; a document that exceeds the uncompressed-bytes budget fails rather than growing without limit.
  • Pair the calls. Every setActiveBackfillPage() should be matched by an endPageBackfill() so later content appends normally.
  • The streaming default refuses random access. Without the opt-in, a random-access switch fails closed. The buffer is the only supported path.

The retained page buffer trades memory for the back-fill capability: it holds flushed pages until save(), bounded by the per-document uncompressed-bytes budget. The streaming writer’s flat memory profile applies only with the buffer off. The performance_budget (wall_ms: 1500, peak_mb: 128) reflects the higher memory ceiling of the retained path.

The retained page buffer does not widen the input surface; it changes when bytes are serialized, not what is ingested. Its refusal to combine with encryption and signing is a safety property: a back-fill can never alter signed or encrypted bytes after the fact, because the two cannot be enabled together. The bytes budget bounds memory against a hostile document.

StatementSpecClause
The writer serializes the body, cross-reference structure, and trailer at save time.ISO 32000-2§7.5

This is a preview capability. NextPDF refuses the back-fill buffer for signed, tagged, PDF/A, linearized, encrypted, and object-stream documents, so it makes no conformance claim for those profiles through this path. No standards text is reproduced.

The TCPDF compatibility adapter exposes this capability as a constructor extension. Construct the adapter with retainedPageBuffer: true, then a setPage() or lastPage() call that targets an earlier page delegates to the core back-fill instead of raising the streaming UnsupportedFeatureException. This constructor argument is a NextPDF extension, not legacy TCPDF parity — legacy TCPDF has no such flag. The same fail-closed refusals apply. See the compat adapter’s retained-page-buffer page for the adapter-side details.