跳转到内容
getnextpdf.com

稳定性: 实验性

保留页面缓冲扩展

可选启用的扩展,而非旧版对等。 这个构造器参数在旧版 TCPDF 6.x 中不存在。它是一项 NextPDF 扩展。它默认关闭;在它关闭时,适配器的行为与之前完全一样,而对一个较早页面的 setPage() 会像它一向那样抛出 UnsupportedFeatureException

旧版 TCPDF 允许你调用 setPage() 移回一个较早的页面并继续绘制。流式适配器默认无法做到这一点——一个页面一旦被刷出,它就消失了——因此对一个较早页面的 setPage()lastPage() 会抛出 UnsupportedFeatureException。保留页面缓冲是那个可选开关,它在 NextPDF 核心保留页面缓冲之上恢复这种回填行为。

向适配器构造器传入 retainedPageBuffer: true。在缓冲开启的情况下,一个瞄准较早页面的 setPage()lastPage() 调用会委托给核心回填,而不是抛出:

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

正式环境示例:回填一个预留的封面页

标题为“正式环境示例:回填一个预留的封面页”的章节

伸手去用缓冲的一个常见原因,是一个封面或汇总页,其数字只有在正文排布完成后才得知——总页数、一个合计、一个记录数。提前预留第 1 页,渲染正文,然后用 setPage(1) 回填封面,并在最后用 lastPage() 恢复。本示例还展示了你必须处理的两个 fail-closed 边界:适配器针对越界页码的 UnsupportedFeatureException,以及当文档同时启用了一个不兼容特性时的核心 RetainedPageBufferIncompatibleException

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

请刻意区分这两个失败面:

  • UnsupportedFeatureException(适配器)——一个越界setPage() / lastPage() 目标,或在缓冲关闭任何切回较早页面的操作。
  • RetainedPageBufferIncompatibleException(核心,NextPDF\Exception\Strict)——缓冲开启,但与一个其页面级元数据在回填后无法重新导出的特性组合。不存在 RetainedPageBufferInconsistency 类型;这是唯一的不兼容异常,而一次预算突破会作为 \OverflowException 浮现。

适配器委托给核心保留页面缓冲,因此适用相同的拒绝。当文档同时使用以下任一项时,回填会被拒绝——与顺序无关且在序列化之前:

  • 一个数字签章。
  • 标签化 PDF(结构树)。
  • PDF/A。
  • 线性化。
  • 对象流打包。
  • 加密。
  • Safe CSS 渲染模式。

每一次拒绝都是一个带类型、fail-closed 的异常——绝不是一次沉默的丢弃:

  • 把缓冲与上述任一特性组合,会抛出核心的 RetainedPageBufferIncompatibleException(命名空间 NextPDF\Exception\Strict@since 核心 6.1.0)。该检查与顺序无关:无论不兼容特性是在缓冲被选择启用之前还是之后配置,它都会触发。
  • 一个逐文档 16 MiB 的未压缩字节预算为缓冲设界;超出它会在构建时抛出 \OverflowException,而不是丢弃一个被回填的页面。

该拒绝的要点在于,一次回填永远不能沉默地改变一份已签章或已加密的文档——两者不能被同时启用。

  • 默认关闭。 不带该标志构造,则适配器不变;对一个较早页面的 setPage() 仍会抛出 UnsupportedFeatureException。这为每一个既有调用方保留了流式契约。
  • 非旧版对等。 旧版 TCPDF 没有 retainedPageBuffer 构造器标志。在你迁移时把它记录为一项 NextPDF 扩展,以免未来的读者把它误认为一项 TCPDF 特性。
  • lastPage() 返回到末尾。 在一次回填之后,调用 lastPage() 以在最后一页恢复追加。
  • 规划一种模式。 如果文档必须被签章、标签化、PDF/A、线性化、加密或对象流化,请不要启用缓冲;改为预先计算你本会回填的那个值。