Aller au contenu
getnextpdf.com

stabilité: Expérimental

Extension du tampon de page conservé

Extension opt-in, pas une parité historique. Cet argument de constructeur n’existe pas dans le TCPDF 6.x historique. C’est une extension NextPDF. Elle est désactivée par défaut ; lorsqu’elle est désactivée, l’adaptateur se comporte exactement comme avant, et setPage() vers une page antérieure lève UnsupportedFeatureException comme il l’a toujours fait.

Le TCPDF historique vous permet d’appeler setPage() pour revenir à une page antérieure et continuer à dessiner. L’adaptateur en streaming ne peut pas faire cela par défaut — une fois qu’une page est vidée, elle a disparu — de sorte que setPage() ou lastPage() vers une page antérieure lève UnsupportedFeatureException. Le tampon de page conservé est l’opt-in qui restaure ce comportement de remplissage a posteriori par-dessus le tampon de page conservé du core NextPDF.

Passez retainedPageBuffer: true au constructeur de l’adaptateur. Avec le tampon activé, un appel setPage() ou lastPage() qui cible une page antérieure délègue au remplissage a posteriori du core au lieu de lever :

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

Exemple de production : remplir a posteriori une page de couverture réservée

Section intitulée « Exemple de production : remplir a posteriori une page de couverture réservée »

Une raison courante de recourir au tampon est une page de couverture ou de synthèse dont les chiffres ne sont connus qu’une fois le corps mis en page — un nombre total de pages, un total général, un nombre d’enregistrements. Réservez la page 1 dès le départ, effectuez le rendu du corps, puis remplissez a posteriori la couverture avec setPage(1), et reprenez à la fin avec lastPage(). Cet exemple montre aussi les deux frontières fail-closed que vous devez gérer : l’UnsupportedFeatureException de l’adaptateur pour un numéro de page hors plage, et la RetainedPageBufferIncompatibleException du core si le document active aussi une fonctionnalité 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,
);
}
}

Distinguez délibérément les deux surfaces de défaillance :

  • UnsupportedFeatureException (adaptateur) — une cible setPage() / lastPage() hors plage, ou tout basculement vers une page antérieure lorsque le tampon est désactivé.
  • RetainedPageBufferIncompatibleException (core, NextPDF\Exception\Strict) — le tampon est activé mais combiné à une fonctionnalité dont les métadonnées au niveau page ne peuvent pas être redérivées après un remplissage a posteriori. Il n’existe pas de type RetainedPageBufferInconsistency ; c’est la seule exception d’incompatibilité, et un dépassement de budget remonte sous la forme \OverflowException.

L’adaptateur délègue au tampon de page conservé du core, de sorte que les mêmes refus s’appliquent. Le remplissage a posteriori est refusé — indépendamment de l’ordre et avant la sérialisation — lorsque le document utilise aussi l’une des fonctionnalités suivantes :

  • Une signature numérique.
  • Le PDF balisé (arbre de structure).
  • PDF/A.
  • La linéarisation.
  • L’empaquetage en flux d’objets.
  • Le chiffrement.
  • Le mode de rendu CSS Safe.

Chaque refus est une exception typée et fail-closed — jamais un abandon silencieux :

  • Combiner le tampon avec n’importe quelle fonctionnalité ci-dessus lève la RetainedPageBufferIncompatibleException du core (espace de noms NextPDF\Exception\Strict, @since core 6.1.0). Le contrôle est indépendant de l’ordre : il se déclenche que la fonctionnalité incompatible ait été configurée avant ou après l’opt-in du tampon.
  • Un budget d’octets non compressés de 16 Mio par document borne le tampon ; son dépassement lève \OverflowException au moment de la build plutôt que d’abandonner une page remplie a posteriori.

L’intérêt du refus est qu’un remplissage a posteriori ne peut jamais altérer silencieusement un document signé ou chiffré — les deux ne peuvent pas être activés ensemble.

  • Désactivé par défaut. Construisez sans l’indicateur et l’adaptateur est inchangé ; setPage() vers une page antérieure lève toujours UnsupportedFeatureException. Cela préserve le contrat de streaming pour chaque appelant existant.
  • Pas une parité historique. Le TCPDF historique n’a pas d’indicateur de constructeur retainedPageBuffer. Documentez ceci comme une extension NextPDF lorsque vous migrez, afin qu’un futur lecteur ne le prenne pas pour une fonctionnalité TCPDF.
  • lastPage() revient à la fin. Après un remplissage a posteriori, appelez lastPage() pour reprendre l’ajout à la page finale.
  • Planifiez un seul mode. Si le document doit être signé, balisé, PDF/A, linéarisé, chiffré ou en flux d’objets, n’activez pas le tampon ; pré-calculez à la place la valeur que vous auriez remplie a posteriori.