stability: Experimental
Retained page buffer extension
At a glance
Section titled “At a glance”Opt-in extension, not legacy parity. This constructor argument does not exist in legacy TCPDF 6.x. It is a NextPDF extension. It is default-off; with it off, the adapter behaves exactly as before, and
setPage()to an earlier page raisesUnsupportedFeatureExceptionas it always has.
Legacy TCPDF lets you call setPage() to move back to an earlier page and keep
drawing. The streaming adapter cannot do that by default — once a page is
flushed, it is gone — so setPage() or lastPage() to an earlier page raises
UnsupportedFeatureException. The retained page buffer is the opt-in that
restores this back-fill behavior on top of the NextPDF core retained page
buffer.
Enable the buffer
Section titled “Enable the buffer”Pass retainedPageBuffer: true to the adapter constructor. With the buffer on, a
setPage() or lastPage() call that targets an earlier page delegates to the
core back-fill instead of raising:
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Compat\Tcpdf\TCPDF;
$pdf = new TCPDF(retainedPageBuffer: true);
$pdf->AddPage(); // page 1 — reserve room for a running total$pdf->Cell(0, 10, 'Invoice', ln: 1);
$pdf->AddPage(); // page 2 — line items$pdf->Cell(0, 10, 'Line items…', ln: 1);$total = 1234.56; // known only after the items are laid out
$pdf->setPage(1); // delegates to the core back-fill$pdf->Cell(0, 10, 'Grand total: ' . number_format($total, 2), ln: 1);$pdf->lastPage(); // return to the final page
$pdf->Output(__DIR__ . '/invoice.pdf', 'F');Production example: back-fill a reserved cover page
Section titled “Production example: back-fill a reserved cover page”A common reason to reach for the buffer is a cover or summary page whose
numbers are only known after the body is laid out — a total page count, a
grand total, a record count. Reserve page 1 up front, render the body, then
back-fill the cover with setPage(1), and resume at the end with lastPage().
This example also shows the two fail-closed boundaries you must handle: the
adapter’s UnsupportedFeatureException for an out-of-range page number, and the
core RetainedPageBufferIncompatibleException if the document also enables an
incompatible feature.
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Compat\Tcpdf\Exception\UnsupportedFeatureException;use NextPDF\Compat\Tcpdf\TCPDF;use NextPDF\Exception\Strict\RetainedPageBufferIncompatibleException;
/** * Render a multi-page report whose cover page summarises figures that are * only known once every body page has been laid out. * * @param list<array{label: string, amount: float}> $lineItems */function renderReport(array $lineItems, string $destination): void{ // Opt in to the back-fill buffer. Default-off; this is a NextPDF // extension, not legacy TCPDF parity. (Underlying core feature: 6.1.0.) $pdf = new TCPDF(retainedPageBuffer: true);
// Page 1 — the cover. Reserve it now; the summary is filled in last. $pdf->AddPage(); $pdf->Cell(0, 10, 'Quarterly report', ln: 1);
// Body pages — lay out the line items, accumulating the running total. $pdf->AddPage(); $total = 0.0; foreach ($lineItems as $item) { $total += $item['amount']; $pdf->Cell(0, 8, $item['label'] . ': ' . number_format($item['amount'], 2), ln: 1); }
// Back-fill the cover with figures known only now. setPage() delegates to // the core back-fill in retained mode; an out-of-range page number still // fails closed with UnsupportedFeatureException in BOTH modes. try { $pdf->setPage(1); } catch (UnsupportedFeatureException $e) { throw new RuntimeException('Cover page was not reserved: ' . $e->getMessage(), previous: $e); } $pdf->Cell(0, 10, 'Total: ' . number_format($total, 2), ln: 1); $pdf->Cell(0, 10, 'Line items: ' . count($lineItems), ln: 1);
// Resume appending at the final page before output. $pdf->lastPage();
// Output() drives the core build. If the document had also enabled a // back-fill-incompatible feature (signing, tagging, PDF/A, linearization, // object streams, encryption, Safe CSS mode), the core refuses here, // order-independently, with RetainedPageBufferIncompatibleException — the // back-fill can never silently corrupt such a document. try { $pdf->Output($destination, 'F'); } catch (RetainedPageBufferIncompatibleException $e) { // $e->feature names the incompatible feature, e.g. 'signature'. throw new RuntimeException( 'Back-fill is incompatible with ' . $e->feature . '; render pages in order instead.', previous: $e, ); }}Distinguish the two failure surfaces deliberately:
UnsupportedFeatureException(adapter) — an out-of-rangesetPage()/lastPage()target, or any earlier-page switch when the buffer is off.RetainedPageBufferIncompatibleException(core,NextPDF\Exception\Strict) — the buffer is on but combined with a feature whose page-level metadata cannot be re-derived after a back-fill. There is noRetainedPageBufferInconsistencytype; this is the only incompatibility exception, and a budget breach surfaces as\OverflowException.
Fail-closed boundary
Section titled “Fail-closed boundary”The adapter delegates to the core retained page buffer, so the same refusals apply. Back-fill is refused — order-independently and before serialization — when the document also uses any of:
- A digital signature.
- Tagged PDF (structure tree).
- PDF/A.
- Linearization.
- Object-stream packing.
- Encryption.
- Safe CSS rendering mode.
Each refusal is a typed, fail-closed exception — never a silent drop:
- Combining the buffer with any feature above raises the core
RetainedPageBufferIncompatibleException(namespaceNextPDF\Exception\Strict,@sincecore 6.1.0). The check is order-independent: it fires whether the incompatible feature was configured before or after the buffer was opted in. - A per-document 16 MiB uncompressed-bytes budget bounds the buffer;
exceeding it raises
\OverflowExceptionat build time rather than dropping a back-filled page.
The point of the refusal is that a back-fill can never silently alter a signed or encrypted document — the two cannot be enabled together.
Behavioral notes
Section titled “Behavioral notes”- Default-off. Construct without the flag and the adapter is unchanged;
setPage()to an earlier page still raisesUnsupportedFeatureException. This preserves the streaming contract for every existing caller. - Not legacy parity. Legacy TCPDF has no
retainedPageBufferconstructor flag. Document this as a NextPDF extension when you migrate, so a future reader does not mistake it for a TCPDF feature. lastPage()returns to the end. After a back-fill, calllastPage()to resume appending at the final page.- Plan one mode. If the document must be signed, tagged, PDF/A, linearized, encrypted, or object-streamed, do not enable the buffer; pre-compute the value you would have back-filled instead.