从语义内容输出带标记的 PDF/UA-2 结构树
本 recipe(示例)会生成一份以 ISO 14289-2(PDF/UA-2)为目标的带标记 PDF。NextPDF 会输出逻辑结构树、标记内容序列、目录语言,以及文件级识别元数据。这套结构支持无障碍内容创作,但符合性由独立检查工具判定。本示例对应 examples/31-pdfua2-tagged.php。
composer require nextpdf/core:^3验证步骤需要在 PATH 中提供 PDF/UA-2 检查工具。本文示例均使用带 ua2 配置文件的 veraPDF。输出带标记的结构不需要 Pro 或 Enterprise 套件。
观念概述
标题为“观念概述”的章节带标记的 PDF 会在视觉内容流之外,并行携带一棵 逻辑结构树。辅助技术读取的是这棵树,而不是像素布局,因此结构决定了对外暴露的阅读顺序。ISO 14289-2 在这里规定了四项要求。真实(非 artifact)内容必须能通过这棵树访问(§8.2.2)。结构元素必须按已定义的顺序嵌套(§8.2.3)。每个元素都必须 resolve(解析)到已知的结构命名空间,可以直接对应,也可以通过角色映射实现(§8.2.4)。此外,内容的自然语言要在文件级声明,并在语言不同的结构元素上逐一细化(§8.4.4)。
NextPDF 使用带类型的 ConformanceMode 来建模这件事。enableTaggedPdf() 会设置 ConformanceMode::PdfUa2,它会(a)让 HTML 流水线在 parser(解析器)构造时接入 TaggedContentEmitter、(b)设置表示带标记 PDF 的目录 MarkInfoMarked 标志(ISO 32000-2 §14.7),以及(c)为目录 Lang 项目记录 BCP-47 语言。writer 还会输出每页的 Tabs 项目,让 Tab 顺序遵循结构顺序(ISO 32000-2 §14.8)。
严格的 UA-2 不变量只适用于 ConformanceMode::PdfUa2。在任何其他模式下构造严格的 ConformancePolicy,都会按设计抛出 InvalidConfigException。
API 接口
标题为“API 接口”的章节API 接口由 PHPDoc 生成。核心入口点如下:
\NextPDF\Core\Document::createStandalone(): DocumentDocument::enableTaggedPdf(string $lang = 'en', ?ConformancePolicy $policy = null): staticDocument::setLanguage(string $lang): static\NextPDF\Conformance\ConformancePolicy::strictUa2(): self\NextPDF\Conformance\ConformanceMode::PdfUa2(由enableTaggedPdf()设定的模式)Document::beginTag(string $type): static/Document::endTag(): static(为非 HTML 内容手动加标记)
代码范例 — 快速上手
标题为“代码范例 — 快速上手”的章节<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone();
// Enable tagged mode BEFORE writeHtml(). The HTML pipeline detects the// mode at parser construction time and wires the tagged-content emitter.$doc->enableTaggedPdf(lang: 'en');
$doc->setTitle('Quarterly Accessibility Report');$doc->setLanguage('en');$doc->addPage();
$doc->writeHtml(<<<'HTML'<h1>Quarterly Accessibility Report</h1><p>This document opts into tagged PDF so assistive technology can exposea meaningful reading order.</p><ul> <li>Headings carry semantic roles.</li> <li>Lists keep their item structure.</li></ul>HTML);
$doc->save(__DIR__ . '/output/31-pdfua2-tagged.pdf');
echo "Created: output/31-pdfua2-tagged.pdf\n";代码范例 — 正式环境
标题为“代码范例 — 正式环境”的章节这是一个自包含、可由测试框架执行的程序。生产环境调用方会在语言标签格式不正确时快速失败,而不是等到外部检查工具运行时才发现。传入 ConformancePolicy::strictUa2(),即可在 API 边界拒绝无效的 BCP-47 标签,再用检查工具的判定为构建把关。
<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Conformance\ConformancePolicy;use NextPDF\Core\Document;use NextPDF\Exception\InvalidConfigException;
$out = getenv('NEXTPDF_COOKBOOK_OUTPUT') ?: (__DIR__ . '/accessible.pdf');
try { $doc = Document::createStandalone();
// Strict UA-2: a malformed BCP 47 tag throws here, not silently at // write time. strictUa2() also forces the §8.4.4 Lang validation. $doc->enableTaggedPdf(lang: 'en-GB', policy: ConformancePolicy::strictUa2());
$doc->setTitle('Accessible Annual Report 2026'); $doc->setLanguage('en-GB'); $doc->addPage();
$doc->writeHtml(<<<'HTML'<h1>Annual Report 2026</h1><p>Audited results for the financial year ending March 2026.</p><h2>Segment performance</h2><table> <tr><th>Segment</th><th>Revenue</th></tr> <tr><td>Cloud</td><td>42.1</td></tr> <tr><td>Services</td><td>18.7</td></tr></table>HTML);
$doc->save($out);} catch (InvalidConfigException $e) { fwrite(STDERR, "Tagged PDF/UA-2 setup rejected: {$e->getMessage()}\n"); exit(1);}
// The gate is the checker, not the library.$exitCode = 0;$report = [];exec('verapdf --flavour ua2 ' . escapeshellarg($out), $report, $exitCode);
if ($exitCode !== 0) { fwrite(STDERR, "veraPDF FAILED — output is not PDF/UA-2 conforming\n"); fwrite(STDERR, implode("\n", $report) . "\n"); exit(1);}
echo "veraPDF PASS — accessible.pdf carries a conforming UA-2 structure\n";在 verapdf --flavour ua2 报告文件符合要求的主机上,预期 STDOUT 为:
veraPDF PASS — accessible.pdf carries a conforming UA-2 structure如果 enableTaggedPdf() 拒绝该语言标签,程序会向 STDERR 输出 Tagged PDF/UA-2 setup rejected: …,然后以非零退出码结束。如果检查工具报告问题,程序会输出 veraPDF FAILED — output is not PDF/UA-2 conforming,然后以非零退出码结束。符合性由检查工具判定:NextPDF 只输出结构,并不声明符合性。
边界情况与陷阱
标题为“边界情况与陷阱”的章节- 调用顺序。 如果在
writeHtml()之后才调用enableTaggedPdf(),它不会追溯为已写入内容加标记。请先启用带标记模式。 - 严格语言把关。 没有策略时,无法解析的 BCP-47 标签会被静默丢弃,只在检查工具运行时才暴露出来。加上
ConformancePolicy::strictUa2()后,同一个标签会在enableTaggedPdf()边界抛出InvalidConfigException(ISO 14289-2 §8.4.4 严格路径)。 - 可重复启用。 调用
enableTaggedPdf()两次会更新语言,但不会重建已经填充的结构树。 - 手动加标记。 对于非 HTML 内容,请用
beginTag()/endTag()包住项目。容器角色(Table、TR、L、LI)会变成没有标记内容的分组元素。叶节点角色(P、H1–H6、TD)则会获得 MCID。 - 模式互斥。 严格的
ConformancePolicy只在ConformanceMode::PdfUa2下才有效。将严格 UA-2 标志和 PDF/A 模式组合在一起会抛出InvalidConfigException。如果要生成带标记的 PDF/A 交付物,请分别启用带标记模式与 PDF/A 配置文件。
结构树会额外增加一棵由轻量字典组成的并行树,并为每段文字执行 BDC/EMC 运算符。对于典型报告,这部分额外开销只占输出大小的几个百分点,并且稳定落在 2000 ms / 128 MB 预算之内。语义可重现性配置文件在这里适用,因为面向检查工具的交付物使用结构抽象语法树(AST)和元数据进行比对,而不是比对原始字节。请参阅「符合性」一节。
安全性注意事项
标题为“安全性注意事项”的章节数据落地与 PII 缓解措施
标题为“数据落地与 PII 缓解措施”的章节结构树携带的文字与可见内容相同。如果来源 HTML 含有个人数据,这些数据也能通过这棵树以及 ActualText/Alt 属性访问。请在写入前套用与可见内容相同的遮蔽与最小化处理。加标记不会新增任何泄露通道,但它确实会按设计让文字可被程序提取。
安全遥测与日志清洗
标题为“安全遥测与日志清洗”的章节本示例只会向 STDOUT 写入一行固定的进度消息。它会把 PDF 输出到测试框架的旁路通道(NEXTPDF_COOKBOOK_OUTPUT),或某个调用方路径。文件文字绝不会写入日志。请不要将可能返回内容片段的检查工具输出写入共享日志。
威胁模型
标题为“威胁模型”的章节带标记的 PDF 并不是信任边界。依赖结构树进行自动化处理的消费方仍必须验证该文件,因为恶意生成方可以输出一棵结构上格式正确、却会误导人的树。请把这套结构视为一种无障碍辅助手段,而不是完整性或真实性信号。
FIPS 模式行为
标题为“FIPS 模式行为”的章节本示例不执行任何密码学运算。FIPS 模式不会改变它的行为。不涉及任何签名或加密。
PDF/UA-2 映射
标题为“PDF/UA-2 映射”的章节| PDF/UA-2 要求 | NextPDF 输出的内容 | 条款 |
|---|---|---|
| 真实内容位于结构树中 | StructTreeRoot,搭配各区块的 StructElem 以及通过 MCID 关联的标记内容 | ISO 14289-2 §8.2.2 |
| 已定义的嵌套与阅读顺序 | 区块元素按文件顺序映射到 grouping/leaf 角色 | ISO 14289-2 §8.2.3 |
| 已知的结构命名空间 | 角色位于 PDF 2.0 命名空间;HTML 标签在需要时会做角色映射 | ISO 14289-2 §8.2.4 |
| 文件与元素语言 | 目录 Lang 来自 BCP-47 标签;每个元素的 Lang 会在语言不同时分别带上 | ISO 14289-2 §8.4.4 |
| 非文本内容带有文本替代 | Alt/ActualText 带在 figure/non-text 结构元素上 | ISO 14289-2 §8.5.1 |
| 表格关系 | Table/TR/TH/TD 角色,并带表头关联 | ISO 14289-2 §8.2.5.26 |
| 分卷识别元数据 | 文件级识别信息安排在保存时写入 | ISO 14289-2 §引言(pdfua2#p17) |
标签 → ISO 32000-2 §14 交叉引用
标题为“标签 → ISO 32000-2 §14 交叉引用”的章节PDF/UA-2 将无障碍要求叠加在 ISO 32000-2 的带标记 PDF 机制之上。NextPDF 依赖的映射如下:
| NextPDF 输出 | ISO 32000-2 §14 机制 | 条款 |
|---|---|---|
逻辑结构树(StructTreeRoot) | 带标记 PDF 的逻辑结构 | §14.7 (iso32000_2_sec14#x1.x38.p13) |
目录 MarkInfo << /Marked true >> | 带标记 PDF 的标记标志 | §14.7 (iso32000_2_sec14#x1.x40.p3) |
每页的 Tabs 项目,遵循结构顺序 | 结构导航/Tab 顺序 | §14.8 (iso32000_2_sec14#x1.x50) |
WCAG 2.2 映射
标题为“WCAG 2.2 映射”的章节PDF/UA-2 是 WCAG 2.2 以格式无关方式陈述的结构要求在 PDF 格式中的具体表达。相关对应如下:
| WCAG 2.2 成功准则 | 这则范例产生的 PDF/UA-2 机制 |
|---|---|
| 1.3.1 信息与关系(Level A) | 结构树让标题、列表与表格关系可通过程序判定(wcag_2_2#x2.x3.x3.x1.p3)。 |
| 1.3.2 有意义的顺序(Level A) | 结构顺序定义了不受视觉布局影响的阅读顺序。 |
| 3.1.1 页面语言(Level A) | 取自 BCP-47 标签的目录 Lang 项目。 |
| 1.1.1 非文本内容(Level A) | Alt/ActualText 带在非文本结构元素上(ISO 14289-2 §8.5.1)。 |
这份映射说明了输出结构在哪些位置支持相应的 WCAG 2.2 准则。它不是一项 WCAG 符合性声明。WCAG 符合性涵盖整体用户体验,由无障碍评估判定,而不是由生成方决定。
符合性
标题为“符合性”的章节| 陈述 | 规范 | 条款 | 参考 ID |
|---|---|---|---|
| 真实内容需要逻辑结构。 | ISO 14289-2 | §8.2.2 | |
| 结构元素遵循已定义的嵌套与阅读顺序。 | ISO 14289-2 | §8.2.3 | |
| 每个结构元素都会解析到一个已知的命名空间,可直接对应,也可通过角色映射。 | ISO 14289-2 | §8.2.4 | |
| 自然语言会在文件级与结构元素级声明。 | ISO 14289-2 | §8.4.4 | |
| 非文本内容带有文本替代。 | ISO 14289-2 | §8.5.1 | |
| 表格单元格带有 row/header/数据关系。 | ISO 14289-2 | §8.2.5.26 | |
带标记 PDF 的标记是目录 MarkInfoMarked 标志。 | ISO 32000-2 | §14.7 | |
| 符合性依据该部分标准判定,而非由生成方主张。 | ISO 14289-2 | §8.14.2 |
NextPDF 输出支持无障碍内容创作的带标记结构。支持不等于符合。 本示例并不声明 PDF/UA-2 符合性。这项判定由独立检查工具(例如 veraPDF)完成。在宣称某个文件符合之前,请先执行检查工具。