Перейти к содержимому

Разбиение длинного HTML на страницы

Используйте автоматические разрывы страниц, чтобы размещать длинный контент на нескольких страницах. Добавьте структуру закладок, чтобы читатели могли переходить между разделами. Этот рецепт соответствует examples/12-bookmarks-and-toc.php.

Окно терминала
composer require nextpdf/core:^3

Используйте это ограничение версии для пакета nextpdf/core. Пример работает с PHP 8.4.

setAutoPageBreak(true, $margin) указывает движку создавать новую страницу до того, как контент пересечёт порог нижнего поля. Движок разбивает длинный текст, выведенный через multiCell() или writeHtml(), по этой границе. Модуль фрагментации CSS (css_break_3) имеет в матрице поддержки оценку Verified. Он отвечает за поведение разрывов в HTML-конвейере.

bookmark($title, $level) добавляет элемент структуры закладок в текущей позиции. Элемент структуры закладок PDF связывается с пунктом назначения, чтобы читатели могли перейти прямо к нужной странице (ISO 32000-2). Движок записывает этот пункт назначения как запись Dest элемента (ISO 32000-2). Используйте аргумент level, чтобы вкладывать элементы в иерархическое оглавление на боковой панели программы для чтения.

Конвейер остаётся однопроходным (ADR-001). Движок принимает решения о разбиении на страницы по мере вывода потока, не сохраняя дерево макета.

  • setAutoPageBreak(bool $enabled, float $margin = 20): staticNextPDF\Core\Concerns\HasPages.
  • bookmark(string $title, int $level = 0, float $y = -1): staticNextPDF\Core\Concerns\HasNavigation.
  • multiCell(...) / writeHtml(string $html): staticNextPDF\Core\Concerns\HasTextOutput.

Полная таблица PHPDoc формируется из исходного кода.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setAutoPageBreak(true, margin: 25);
$doc->addPage();
$doc->bookmark('Section 1', level: 0);
$doc->setFont('helvetica', '', 11);
for ($i = 1; $i <= 80; $i++) {
$doc->multiCell(0, 7, "Paragraph {$i} of a long flowing document.");
}
$doc->save(__DIR__ . '/out.pdf');

Этот автономный пример запускается в тестовой среде. Он создаёт документ из нескольких глав с вложенной структурой закладок и автоматическими разрывами страниц и соответствует examples/12-bookmarks-and-toc.php.

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Bookmarks and Navigation');
$doc->setPrintHeader(false);
$doc->setPrintFooter(false);
$doc->setAutoPageBreak(true, margin: 25);
$chapters = [
'Chapter 1: Introduction' => ['What is NextPDF?', 'Key Features'],
'Chapter 2: Getting Started' => ['Installation', 'Your First PDF'],
'Chapter 3: Advanced Topics' => ['Worker-safe Architecture', 'Streaming Output'],
];
$body = 'NextPDF is a modern PDF 2.0 library for PHP. This paragraph is '
. 'repeated so the content overflows the page and the engine inserts '
. 'an automatic page break at the bottom-margin threshold.';
foreach ($chapters as $chapter => $sections) {
$doc->addPage();
$doc->bookmark($chapter, level: 0);
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, $chapter, newLine: true);
$doc->ln(3);
foreach ($sections as $section) {
$doc->bookmark($section, level: 1);
$doc->setFont('helvetica', 'B', 14);
$doc->cell(0, 10, $section, newLine: true);
$doc->setFont('helvetica', '', 11);
for ($i = 0; $i < 12; $i++) {
$doc->multiCell(0, 7, $body);
}
$doc->ln(4);
}
}
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
$doc->save($out !== false ? $out : __DIR__ . '/paginate-long-html.pdf');
echo "Wrote paginate-long-html.pdf\n";

Ожидаемый стандартный вывод (STDOUT):

Wrote paginate-long-html.pdf
  • Отключили и забыли. Когда автоматический разрыв страниц отключён, движок обрезает контент за нижним полем вместо переноса. Снова включите его перед длинным контентом.
  • Неразрывный контент. Отдельный блок, высота которого превышает полезную высоту страницы, может вызвать UnsplittableContentException. Причиной может быть очень высокая строка таблицы или большое изображение. Разделите исходный контент.
  • Закладка перед контентом. Вызовите bookmark() в позиции, на которую должен указывать пункт назначения. Размещайте её непосредственно перед следующим заголовком, на нужной странице.
  • Верхний и нижний колонтитулы резервируют место. Печатный верхний или нижний колонтитул уменьшает полезную высоту контента, и порог разрыва это учитывает. Отключение обоих, как в этом примере, даёт полную высоту основного контента.
  • Вложенность структуры закладок. level — это глубина вложенности. Дочерний элемент с level: 1 должен следовать за родительским элементом с level: 0. Иначе программа для чтения выровняет дерево структуры закладок.

Движок принимает решения о разбиении на страницы за один проход вывода. Затраты линейно зависят от длины контента, O(n). Бюджет составляет wall_ms: 2000, peak_mb: 96. Реальное время работы немного выше, чем у рецептов с одной страницей, потому что многостраничная таблица перекрёстных ссылок (xref) и сборка структуры закладок добавляют работу. Потоковая модель ограничивает потребление памяти, а структура закладок остаётся небольшим плоским списком.

Выдержка из матрицы поддержки CSS (только строки Verified)

Заголовок раздела «Выдержка из матрицы поддержки CSS (только строки Verified)»

Эта таблица воспроизводит только строки Verified из проверенной матрицы поддержки CSS.

Модуль W3CУровеньСтатусПодтверждение
Фрагментация CSS (css_break_3)3Verified (проверено)src/Html/Fragmentation/, tests/Unit/Html/PagedMedia/
Таблицы CSS (css_tables_3)3Verified (проверено)src/Html/Table/ + эталонные PDF
CSS Cascading and Inheritance — каскад и наследование (css_cascade_3)3Verified (проверено)src/Html/Cascade/

@page — это селекторы именованных страниц из модуля CSS Paged Media. Прежде чем полагаться на них, проверьте в матрице текущую оценку этого модуля.

Ограничения однопроходной потоковой обработки (ADR-001)

Заголовок раздела «Ограничения однопроходной потоковой обработки (ADR-001)»

Движок вставляет разрывы страниц по мере прохождения потока. Поскольку сохранённого дерева для повторного размещения нет, каждое решение о разрыве окончательное. Некоторому контенту, например перекрёстной ссылке, после размещения нужен окончательный номер страницы. Такой контент ограничен, поэтому формируйте его с учётом этого ограничения.

Разбиение на страницы относится к контроллеру разрывов страниц, а не к парсеру. Парсер не выдаёт низкоуровневые операторы перехода между страницами; он запрашивает разрыв через контракт контроллера.

Потоковая модель хранит буфер текущей страницы и плоский список структуры закладок, а не все страницы сразу. Очень длинный документ остаётся в пределах потолка ADR-020, потому что движок сбрасывает завершённые страницы. Таблицы и flex-контейнеры по-прежнему подчиняются ограничению в 5,000 узлов на контекст.

Вредоносный документ не может вызвать неограниченное потребление памяти. Ограничения на количество элементов и глубину вложенности (ADR-001), а также бюджет узлов на контекст (ADR-020) ограничивают объём работы. Проверяйте длину и структуру длинного контента, предоставленного пользователем. Движок отображает заголовок структуры закладок, контролируемый злоумышленником, как текст и никогда не интерпретирует его.

УтверждениеСпецификацияПунктreference_id (идентификатор ссылки)
Каждый элемент структуры закладок может быть связан с пунктом назначения, чтобы пользователь переходил прямо к нему.ISO 32000-2iso32000_2_sec12#x1.x5.p4 (раздел 12)
Запись Dest элемента структуры закладок задаёт пункт назначения, который отображается при активации элемента.ISO 32000-2iso32000_2_sec12#x1.x11.p30 (раздел 12)

Этот рецепт показывает, как NextPDF размещает длинный контент и строит структуру закладок. В матрице поддержки фрагментация CSS имеет оценку Verified.

Не применимо.