跳转到内容

构建支持自动分页的多页文档

构建一份内容横跨多页的文档。在生成过程中逐步加入内容。启用 setAutoPageBreak() 后,当光标抵达底部边距时,排版引擎会自动为你开启新页。调用 save() 后,可用 getNumPages() 读取最终页数。本示例基于 examples/05-multi-page.php

save() 期间,引擎会把每一页的绘制指令写入一个内容流。ISO 32000-2 §7.7.3.3 将一页的 Contents 定义为单一流,或按顺序串联的流数组。因此,多页输出是一连串页对象,而不是单一缓冲区。

Terminal window
composer require nextpdf/core:^3

无需任何可选扩展。本示例可在 PHP 8.1–8.4 的 backport 兼容矩阵上运行。getNumPages()setAutoPageBreak() 自 1.0.0 起即为稳定 API。

每份 NextPDF 文档都是一棵页面树(page tree)。随着你添加内容,内部光标(getY())会继续推进。启用自动分页后,引擎会在每个内容块之前检查剩余的垂直空间。如果该块无法放入底部边距上方的空间,引擎会刷写当前页,并为你调用 addPage()。传给 setAutoPageBreak() 的底部边距就是触发阈值。

页级属性(例如 media box)可以继承。ISO 32000-2 §7.7.3.4 规定,页对象中省略的属性会从上层页面树节点解析(resolve)取得。NextPDF 会在整份文档中应用一致的页面尺寸,因此每一页生成时都共享相同几何,你不必逐页重复设置。

API 接口由 PHPDoc 自动生成。本示例会用到下列方法:

  • Document::createStandalone(): self — 创建一份独立的文档。
  • setAutoPageBreak(bool $enabled, float $margin = 20): static — 启用自动分页。 $margin 是以毫米为单位的底部边距触发值。
  • addPage(?PageSize $size = null, Orientation $orientation = Orientation::Portrait): static — 创建第一页,以及任何显式新增的页面。
  • multiCell(...): static / cell(...): static — 输出可流动或固定的文本块。分页检查会测量这些块。
  • getNumPages(): int — 排版后的页数。
<?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->setFont('helvetica', '', 11);
for ($i = 1; $i <= 60; $i++) {
$doc->multiCell(0, 7, "Line {$i}: content flows until the page is full, "
. 'then the engine starts a new page automatically.');
}
$doc->save(getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/multi-page.pdf');
echo 'Pages: ' . $doc->getNumPages() . "\n";

这是完整示例,可直接配合 harness(测试装置)运行。它会使用 harness 设置的 NEXTPDF_COOKBOOK_OUTPUT,所以不要把 PDF 打印到 STDOUT。它本身不会固定任何熵源(entropy)。当 harness 运行它时,DeterministicMode 会固定时钟、/ID 与品牌信息。

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('Multi-Page Document');
// Enable automatic page breaks. The 25 mm bottom margin is the trigger:
// when the cursor would cross it, the engine flushes the page and adds
// a new one before the next block is drawn.
$doc->setAutoPageBreak(true, margin: 25);
$doc->addPage();
$doc->setFont('helvetica', 'B', 18);
$doc->cell(0, 12, 'Multi-Page Document Example', newLine: true);
$doc->ln(5);
for ($chapter = 1; $chapter <= 3; $chapter++) {
$doc->setFont('helvetica', 'B', 14);
$doc->cell(0, 10, "Chapter {$chapter}: Lorem Ipsum", newLine: true);
$doc->setFont('helvetica', '', 11);
for ($para = 1; $para <= 5; $para++) {
$text = "Paragraph {$para} of Chapter {$chapter}. "
. 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. '
. 'Sed do eiusmod tempor incididunt ut labore et dolore magna '
. 'aliqua. Ut enim ad minim veniam, quis nostrud exercitation '
. 'ullamco laboris nisi ut aliquip ex ea commodo consequat.';
$doc->multiCell(0, 7, $text);
$doc->ln(3);
}
$doc->ln(5);
}
// The harness sets NEXTPDF_COOKBOOK_OUTPUT; honour it. STDOUT stays free
// for progress text only.
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: __DIR__ . '/multi-page.pdf';
$doc->save($out);
echo 'Created multi-page.pdf with ' . $doc->getNumPages() . " pages\n";
  • 禁用自动分页。 使用 setAutoPageBreak(false, …) 时,超过底部边距的内容会被裁剪在该页上,而不会继续流动,文档也会保持为单页。若内容需要流动,请启用它。
  • 单一块高于整页。 引擎会在内部拆分文本超过可打印高度的 multiCell。但如果是一个高于可用区域且不可分割的单一块(例如一张很高的图像),它只会被放置一次并发生溢出。此时请自行手动分页。
  • 首次调用 addPage() 仍然必要。 当还没有任何页面时,cell() 会按需调用 addPage()。即便如此,仍请显式调用 addPage(),以确保第一页的尺寸和方向是确定的。
  • 边距单位。 在默认单位系统下,setAutoPageBreak() 的边距以毫米为单位,而不是以点(point)为单位。

getNumPages() 是 O(1) 操作。它只读取一个计数器,不会重新排版。内存用量随保留的内容量而增长,而不是随页数增长。引擎会在每一页完成时,将已完成页面刷写到输出缓冲区 — 这就是单趟(single-pass)流式模型(ADR-001)。2000 ms / 64 MB 的预算足以在参考主机上处理包含数百页文本的文档。

本示例只会写入你代码提供的文本。它不会进行任何输入解析、网络访问或反序列化。请将任何来自外部来源的文本都视为不受信任,并在渲染之前先限制其长度。引擎不会代你施加应用级别的内容大小上限。

陈述规范条款参考 ID
一页的 Contents 是单一流,或按顺序串联的流数组。ISO 32000-2§7.7.3.3
页对象中省略的可继承页面属性,会从上层页面树节点解析取得。ISO 32000-2§7.7.3.4
trailer 的 /ID 是由两个字节字符串组成的文件标识符(在 PDF 2.0 中为必填)。ISO 32000-2§7.5.5

可复现性配置 — 结构级别(为什么不是逐字节)。 每一份保存的文档都带有一个 trailer /ID,其两个字节字符串即为文件标识符(ISO 32000-2 §7.5.5,如上)。第二个元素并非跨运行稳定,因此即使内容完全相同,每次运行的原始字节仍会不同。harness 会比对经过 qpdf 规范化的结构;该流程会剥除 /ID/CreationDate/ModDate。本示例描述的是 NextPDF 如何生成这个结构。它并未笼统宣称符合 ISO 32000-2。

不适用。支持自动分页的多页排版属于 Core 能力,不受 Premium 限制。