为长篇 HTML 内容分页
使用自动分页,让长篇内容跨多页排布。再加上大纲,读者就能在各章节之间跳转。本示例(recipe)遵循 examples/12-bookmarks-and-toc.php。
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)输出过程中决定的,而不是依赖保留下来的布局树。
API 接口
标题为“API 接口”的章节setAutoPageBreak(bool $enabled, float $margin = 20): static—NextPDF\Core\Concerns\HasPages。bookmark(string $title, int $level = 0, float $y = -1): static—NextPDF\Core\Concerns\HasNavigation。multiCell(...)/writeHtml(string $html): static—NextPDF\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) | 3 | 已验证 | src/Html/Fragmentation/、tests/Unit/Html/PagedMedia/ |
CSS 表格(css_tables_3) | 3 | 已验证 | src/Html/Table/ 以及 golden PDF |
CSS Cascading and Inheritance(层叠与继承)(css_cascade_3) | 3 | 已验证 | src/Html/Cascade/ |
@page 具名页面选择器属于 CSS Paged Media。在依赖它之前,请先查阅矩阵,确认该模块的当前评级。
单遍流式处理的限制(ADR-001)
标题为“单遍流式处理的限制(ADR-001)”的章节引擎会在流式处理内容的同时写出断页。没有保留下来的树可供重新排版,因此断页决定一旦做出就是最终结果。有些内容(例如交叉引用)需要在排版后才能得到最终页码。这类内容会受到限制,因此编写时要把这项限制纳入考量。
层级契约(ADR-010)
标题为“层级契约(ADR-010)”的章节分页由断页控制器(controller)负责,而不是由 parser(解析器)负责。parser 不会输出原始的换页运算符,而是通过控制器契约来请求断页。
大型文档的内存预算
标题为“大型文档的内存预算”的章节流式模型只持有当前页面缓冲区加上扁平的大纲列表,而不是一次持有所有页面。很长的文档也会保持在 ADR-020 的上限内,因为引擎会把已完成的页面刷出。表格与 flex 容器仍受 5,000 个节点/每个上下文的上限约束。
安全注意事项
标题为“安全注意事项”的章节恶意文档无法强迫内存无界增长。元素与嵌套上限(ADR-001)以及每个上下文的节点预算(ADR-020)会限制其工作量。请验证用户提供的长篇内容的长度与结构。引擎会把攻击者可控的大纲标题当作文本绘制,绝不会对其加以解读。
符合性
标题为“符合性”的章节| 陈述 | 规范 | 条款 | 参考 ID |
|---|---|---|---|
| 每个大纲项目都可关联一个目标位置,让用户直接跳转到该位置。 | ISO 32000-2 | iso32000_2_sec12#x1.x5.p4(第 12 节) | |
| 大纲项目的 Dest 条目会指定该项目被激活时所显示的目标位置。 | ISO 32000-2 | iso32000_2_sec12#x1.x11.p30(第 12 节) |
本示例演示 NextPDF 如何让长篇内容跨页排布并建立大纲。CSS 分段在支持矩阵中的评级为 Verified。
商业情境
标题为“商业情境”的章节不适用。