跳转到内容

为长篇 HTML 内容分页

使用自动分页,让长篇内容跨多页排布。再加上大纲,读者就能在各章节之间跳转。本示例(recipe)遵循 examples/12-bookmarks-and-toc.php

Terminal window
composer require nextpdf/core:^3

该版本约束对应 nextpdf/core 软件包。本示例在 PHP 8.4 上运行。

setAutoPageBreak(true, $margin) 会让引擎在内容即将超出下边距阈值时另起新页。引擎会在该边界处切分通过 multiCell()writeHtml() 写入的长文本。CSS 分段(Fragmentation)模块(css_break_3)在支持矩阵中的评级为 Verified,为 HTML 管线的断页行为提供支持。

bookmark($title, $level) 会新增一个指向当前位置的大纲项目。PDF 大纲项目会关联一个目标位置,让用户能直接跳到某一页(ISO 32000-2)。引擎会把该目标位置写入项目的 Dest 条目(ISO 32000-2)。level 参数会按嵌套层级,将项目排入阅读器侧边栏中的分层目录。

管线保持单遍处理(ADR-001)。分页是在流式(stream)输出过程中决定的,而不是依赖保留下来的布局树。

  • 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_33已验证src/Html/Fragmentation/tests/Unit/Html/PagedMedia/
CSS 表格(css_tables_33已验证src/Html/Table/ 以及 golden PDF
CSS Cascading and Inheritance(层叠与继承)(css_cascade_33已验证src/Html/Cascade/

@page 具名页面选择器属于 CSS Paged Media。在依赖它之前,请先查阅矩阵,确认该模块的当前评级。

引擎会在流式处理内容的同时写出断页。没有保留下来的树可供重新排版,因此断页决定一旦做出就是最终结果。有些内容(例如交叉引用)需要在排版后才能得到最终页码。这类内容会受到限制,因此编写时要把这项限制纳入考量。

分页由断页控制器(controller)负责,而不是由 parser(解析器)负责。parser 不会输出原始的换页运算符,而是通过控制器契约来请求断页。

流式模型只持有当前页面缓冲区加上扁平的大纲列表,而不是一次持有所有页面。很长的文档也会保持在 ADR-020 的上限内,因为引擎会把已完成的页面刷出。表格与 flex 容器仍受 5,000 个节点/每个上下文的上限约束。

恶意文档无法强迫内存无界增长。元素与嵌套上限(ADR-001)以及每个上下文的节点预算(ADR-020)会限制其工作量。请验证用户提供的长篇内容的长度与结构。引擎会把攻击者可控的大纲标题当作文本绘制,绝不会对其加以解读。

陈述规范条款参考 ID
每个大纲项目都可关联一个目标位置,让用户直接跳转到该位置。ISO 32000-2iso32000_2_sec12#x1.x5.p4(第 12 节)
大纲项目的 Dest 条目会指定该项目被激活时所显示的目标位置。ISO 32000-2iso32000_2_sec12#x1.x11.p30(第 12 节)

本示例演示 NextPDF 如何让长篇内容跨页排布并建立大纲。CSS 分段在支持矩阵中的评级为 Verified。

不适用。