Ir al contenido
getnextpdf.com

estabilidad: Experimental

Extensión del búfer de páginas retenidas

Extensión opcional, no paridad heredada. Este argumento del constructor no existe en el TCPDF heredado 6.x. Es una extensión de NextPDF. Está desactivado por defecto; con él desactivado, el adaptador se comporta exactamente como antes, y setPage() a una página anterior lanza UnsupportedFeatureException como siempre ha hecho.

El TCPDF heredado le permite llamar a setPage() para volver a una página anterior y seguir dibujando. El adaptador por streaming no puede hacer eso por defecto —una vez que una página se vuelca, desaparece—, por lo que setPage() o lastPage() a una página anterior lanza UnsupportedFeatureException. El búfer de páginas retenidas es la opción que restaura este comportamiento de relleno a posteriori sobre el búfer de páginas retenidas del core de NextPDF.

Pase retainedPageBuffer: true al constructor del adaptador. Con el búfer activado, una llamada setPage() o lastPage() que apunte a una página anterior delega en el relleno a posteriori del core en lugar de lanzar:

<?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');

Ejemplo de producción: rellenar a posteriori una página de portada reservada

Sección titulada «Ejemplo de producción: rellenar a posteriori una página de portada reservada»

Un motivo común para recurrir al búfer es una página de portada o de resumen cuyos números solo se conocen después de maquetar el cuerpo: un recuento total de páginas, un total general, un recuento de registros. Reserve la página 1 por adelantado, represente el cuerpo, luego rellene a posteriori la portada con setPage(1) y reanude al final con lastPage(). Este ejemplo también muestra los dos límites de cierre seguro que debe manejar: la UnsupportedFeatureException del adaptador para un número de página fuera de rango, y la RetainedPageBufferIncompatibleException del core si el documento también habilita una característica incompatible.

<?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,
);
}
}

Distinga las dos superficies de fallo deliberadamente:

  • UnsupportedFeatureException (adaptador): un objetivo de setPage() / lastPage() fuera de rango, o cualquier cambio a una página anterior cuando el búfer está desactivado.
  • RetainedPageBufferIncompatibleException (core, NextPDF\Exception\Strict): el búfer está activado pero combinado con una característica cuyos metadatos a nivel de página no pueden volver a derivarse tras un relleno a posteriori. No existe ningún tipo RetainedPageBufferInconsistency; esta es la única excepción de incompatibilidad, y una vulneración del presupuesto aflora como \OverflowException.

El adaptador delega en el búfer de páginas retenidas del core, por lo que se aplican los mismos rechazos. El relleno a posteriori se rechaza —de forma independiente del orden y antes de la serialización— cuando el documento también usa cualquiera de:

  • Una firma digital.
  • PDF etiquetado (árbol de estructura).
  • PDF/A.
  • Linealización.
  • Empaquetado en flujos de objetos.
  • Cifrado.
  • Modo de representación Safe de CSS.

Cada rechazo es una excepción tipada y de cierre seguro, nunca un descarte silencioso:

  • Combinar el búfer con cualquier característica de arriba lanza la RetainedPageBufferIncompatibleException del core (espacio de nombres NextPDF\Exception\Strict, @since core 6.1.0). La comprobación es independiente del orden: se dispara tanto si la característica incompatible se configuró antes como después de optar por el búfer.
  • Un presupuesto de bytes sin comprimir de 16 MiB por documento acota el búfer; excederlo lanza \OverflowException en el momento de la compilación en lugar de descartar una página rellenada a posteriori.

El objetivo del rechazo es que un relleno a posteriori nunca pueda alterar silenciosamente un documento firmado o cifrado: ambos no pueden habilitarse a la vez.

  • Desactivado por defecto. Construya sin el indicador y el adaptador queda sin cambios; setPage() a una página anterior aún lanza UnsupportedFeatureException. Esto preserva el contrato de streaming para cada llamador existente.
  • No es paridad heredada. El TCPDF heredado no tiene ningún indicador de constructor retainedPageBuffer. Documente esto como una extensión de NextPDF cuando migre, para que un lector futuro no lo confunda con una característica de TCPDF.
  • lastPage() vuelve al final. Tras un relleno a posteriori, llame a lastPage() para reanudar el anexado en la página final.
  • Planee un modo. Si el documento debe estar firmado, etiquetado, en PDF/A, linealizado, cifrado o con flujos de objetos, no habilite el búfer; en su lugar, calcule de antemano el valor que habría rellenado a posteriori.