跳转到内容

将 HTML 渲染为 PDF 页面

这则 recipe(示例)使用 writeHtml() 将一段 HTML 与 CSS 片段转换为 PDF 页面内容。你提供 markup(标记语言),它就会绘制出排好版的页面。完整、可直接运行的代码版本在 examples/08-html-basic.php。你可以按下面的步骤操作,也可以直接复制这个示例。

NextPDF 会一趟读完你的 HTML,并把结果直接以流式方式写入页面。这是一条单趟流式(streaming)流水线。你不必理解这套模型也能使用这则示例,但它值得记住,因为它影响了本页后面的几条规则。

Terminal window
composer require nextpdf/core:^3

这条命令会安装 nextpdf/core 包。本页示例在 PHP 8.4 上运行,支持的运行环境为 >=8.4 <9.0

writeHtml() 接收一段 HTML 字符串,从当前光标位置开始,将其绘制到当前页面。以下逐步说明内部发生了什么。首先,引擎会扫描一次你的 HTML,并将它拆成一串 token(HtmlTokenizer)。接着,它从左到右遍历这份列表(HtmlParser)。对于每个元素,它会把对应的 PDF 绘图指令(也就是内容流运算符)写入缓冲区。在两次调用之间,引擎不会在内存中构建或保留元素树。这是刻意的设计,也就是 ADR-001 记录的单趟流式模型。

每个受支持的块级元素会变成一个布局框,每段文本会变成一个显示文本(text-show)运算符。来自行内 style 属性与 <style> 块的样式,会通过 CSS 层叠(cascade)完成 resolve(解析):当多条规则同时适用时,决定哪个样式胜出的那套标准规则。文本换行、对齐与间距遵循 CSS Text 模型;该模型规范原始文本如何转换为排版后、已换行的文本(W3C CSS Text Level 3)。

如果你不选择字体,正文会使用一套默认字体。该默认字体是标准 Type 1 字体,也就是 ISO 32000-2 列出的 14 个标准字体之一。只有在你注册并选用自己的字体,或某个一致性配置文件要求 NextPDF 嵌入替代字体时,这套默认才会改变。

先明确一项预期:NextPDF 支持的是 HTML 与 CSS 的一个子集,而不是两者的全部。这则示例涵盖的就是该受支持子集,并不声称支持完整的 HTML 或完整的 CSS。每个模块经过验证的确切状态,请参见 CSS 支持矩阵一节。

这个方法的签名是 writeHtml(string $html): static。它声明于 NextPDF\Contracts\PdfDocumentInterface 接口,并实现于 NextPDF\Core\Concerns\HasTextOutput。它会绘制到当前页面;如果还没有任何页面,它会自动创建一页。这个方法的完整 PHPDoc 表格由源代码生成。

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('HTML Basic');
$doc->addPage();
$doc->writeHtml('<h1>HTML Rendering in NextPDF</h1><p>Rendered with <strong>writeHtml()</strong>.</p>');
$doc->save(__DIR__ . '/out.pdf');

这是一个完整、自包含的示例,也是测试套件(harness)实际运行的版本。它对应 examples/08-html-basic.php。它不会写死输出路径,而是写入测试套件提供的位置,这样可重现性套件才能将脚本运行两次并比对结果。

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
$doc->setTitle('HTML Basic');
$doc->addPage();
$html = <<<'HTML'
<h1 style="color: #1E3A8A;">HTML Rendering in NextPDF</h1>
<p>NextPDF renders <strong>HTML content</strong> directly into PDF pages.
This is the recommended approach for <em>mixed formatting</em>.</p>
<h2>Supported elements</h2>
<ul>
<li>Headings (h1-h6)</li>
<li>Paragraphs with <strong>bold</strong> and <em>italic</em></li>
<li>Ordered and unordered lists</li>
<li>Tables with borders and alignment</li>
<li>Inline styles (color, font-size, margin)</li>
</ul>
<h2>Ordered list</h2>
<ol>
<li>Create a Document instance</li>
<li>Add pages and content</li>
<li>Call save() or output()</li>
</ol>
HTML;
$doc->writeHtml($html);
// The harness sets NEXTPDF_COOKBOOK_OUTPUT and runs this script twice.
// Honour it: do not hard-code a path, do not echo the PDF to STDOUT.
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT');
$doc->save($out !== false ? $out : __DIR__ . '/render-html-to-pdf.pdf');
echo "Wrote render-html-to-pdf.pdf\n";

预期的 STDOUT:

Wrote render-html-to-pdf.pdf
  • 光标交接。 writeHtml() 会将光标推进到已绘制内容的结尾。后续的 cell() 或第二次 writeHtml() 会从该位置继续,而不是从页面顶端开始。
  • 还没有页面。 如果不存在任何页面,writeHtml() 会在绘制前先添加一页。当你需要先设定特定页面尺寸时,请明确调用 addPage()
  • 元素数与嵌套深度上限。 这个流式引擎施加 50,000 个元素与 100 层嵌套的上限(ADR-001)。超过上限的文档会被拒绝,而不会被无声截断。
  • 不支持的标记。 落在受支持子集之外的元素与属性会被忽略或回退(fall back);它们不会抛出异常。在依赖某个属性之前,请对照 CSS 支持矩阵确认其覆盖范围。
  • 外部资源。 远程图像与样式表受外部资源策略管理;默认策略不会抓取任意远程 URL。

由于分词(tokenization)与绘制会对输入进行单趟处理,成本会随 token 数量线性增长,也就是 O(n)。这则示例的默认预算是 wall_ms: 1500, peak_mb: 96。由于引擎以流式方式输出、不会在内存中保留 DOM,峰值内存取决于内容流缓冲区与活跃样式栈的大小,而不是整份文档的大小。

CSS 支持矩阵摘录(仅列 Verified 的行)

标题为“CSS 支持矩阵摘录(仅列 Verified 的行)”的章节

这里只列出评为 Verified、且出现在经过真实性审核的 CSS 支持矩阵中的行。「Verified」表示存在一份 src/Html/ 实现,以及一组实质性的专属 fixture 测试,并且能在结构配置文件下确定性地通过。

W3C 模块级别状态佐证
CSS 弹性盒布局(css_flexbox_11已验证(Verified)src/Html/Flex/tests/Unit/Html/Flex/
CSS 网格布局(css_grid_11已验证(Verified)src/Html/Grid/、WPT 语料
CSS Cascading and Inheritance 层叠与继承(css_cascade_33已验证(Verified)src/Html/Cascade/tests/Unit/Html/Cascade/
CSS 表格(css_tables_33已验证(Verified)src/Html/Table/、表格 fixture 与黄金 PDF
CSS 字体(css_fonts_44已验证(Verified)src/Html/FontFace/tests/Unit/Html/FontFace/

text-aligntext-indentcolor 这类属性,在矩阵中评为「Claimed」(已实现,但没有专属模块 fixture),因此这里刻意不列为 Verified。

这个 HTML 引擎不保留任何 DOM。它的状态由一个标量光标和一个 push/pop 样式栈组成;只包含空白的文本节点会在分词阶段被丢弃。一个后果是:较晚出现的元素无法重新设置较早元素的样式,而需要完整树状上下文的选择器(例如复杂的 :has() 情况)会依 ADR-006 受到约束。规划布局时,请只依赖文档顺序。

解析、布局与绘制是彼此分离的层。解析器不发出原始绘制运算符,布局分派也不解析 CSS;跨越这些边界正是 ADR-010 所禁止的耦合债。对示例作者而言,这意味着公开入口点是 writeHtml(),不要直接操作解析器内部。

依 ADR-020,以容器为范围的格式化上下文(flex、表格)可能会构建一棵短暂的子树,但每个上下文受限于 5,000 个节点、20 层深度,并且所有活跃上下文合计有 50 MB 的活跃内存上限与 10 层嵌套限制。在这些上下文之外,流式模型不保留任何树。让单个表格与 flex 容器保持在节点上限之内,内存用量才会可预期。

把 HTML 输入视为不可信。NextPDF 不执行任何脚本,默认的外部资源策略也不会抓取任意远程 URL,因此引擎本身采取保守做法。即便如此,对于任何由用户输入组装出的 HTML,在绘制之前都要验证或清理。元素与嵌套上限也会保护你:它们限制了一份恶意或格式错误的文档能够要求的工作量。

陈述规范条款参考 ID
CSS Text 规范定义了原始文本转换为排版后、已换行文本的过程。W3C CSS Text Level 3(文本模块第 3 级)条款 css_text_3#x1.x2.p4
默认正文字体解析为一个标准 Type 1 字体。ISO 32000-2条款 iso32000_2_sec9#x1.x29

这则示例演示 NextPDF 如何绘制一个受支持的 HTML 与 CSS 子集。它并不声称支持完整的 HTML 或完整的 CSS;每个模块经过验证的状态都列在 CSS 支持矩阵中。

不适用。