Перейти к содержимому
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(). Этот пример также показывает две границы с отказом закрытием, которые вы должны обработать: 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.

Каждый отказ — это типизированное, отказывающее закрытием исключение, а не молчаливый сброс:

  • Сочетание буфера с любой функцией выше вызывает у ядра RetainedPageBufferIncompatibleException (пространство имён NextPDF\Exception\Strict, @since ядра 6.1.0). Проверка независима от порядка: она срабатывает независимо от того, была ли несовместимая функция настроена до или после подключения буфера.
  • Подокументный бюджет несжатых байтов в 16 MiB ограничивает буфер; превышение его вызывает \OverflowException во время сборки, а не сбрасывает дозаполненную страницу.

Смысл отказа в том, что дозаполнение никогда не может молча изменить подписанный или зашифрованный документ — эти два режима нельзя включить вместе.

  • По умолчанию выключено. Сконструируйте без флага, и адаптер не изменён; setPage() на более раннюю страницу по-прежнему вызывает UnsupportedFeatureException. Это сохраняет потоковый контракт для каждого существующего вызывающего.
  • Не устаревший паритет. В устаревшем TCPDF нет флага конструктора retainedPageBuffer. Документируйте это как расширение NextPDF при миграции, чтобы будущий читатель не принял его за функцию TCPDF.
  • lastPage() возвращает к концу. После дозаполнения вызовите lastPage(), чтобы возобновить добавление на последней странице.
  • Планируйте один режим. Если документ должен быть подписан, тегирован, PDF/A, линеаризован, зашифрован или с потоками объектов, не включайте буфер; вместо этого предвычислите значение, которое вы бы дозаполнили.