无障碍:标记原语与 PDF/UA-2 结构模型
NextPDF Core 提供用于无障碍创作的原语:逻辑结构树、标准角色映射、标记内容标记,以及与 ISO 14289-2 (PDF/UA-2) 和 ISO 32000-2 §14.7 所定义结构树模型一致的 BCP-47 语言属性。所生成文件的合规性取决于最终文档、作者的内容选择以及外部检查器。这并不是本库替你作出的保证。
composer require nextpdf/core概念概述
标题为“概念概述”的章节带标记的 PDF 包含一棵逻辑结构树,其根节点包含一个 Document 结构元素。辅助技术读取该树,从而推导出不依赖视觉布局的有意义阅读顺序 (ISO 32000-2 §14.7.2;ISO 14289-2 §8.2.5.2)。NextPDF 在 NextPDF\Accessibility 命名空间中通过三个相互协作的类型对此建模。
StructureTree 负责维护这一层级结构。它按页分配标记内容标识符,跟踪父子嵌套关系,并按 ISO 32000-2 §14.7 序列化结构树根、结构元素、父树、角色映射以及 PDF 2.0 标准结构命名空间。createRoot() 创建带有语言属性、必需且唯一的 Document 元素。addElement() 挂接带类型的子元素。hasRoot() 和 rootHasChildren() 报告该树是否存在以及是否有后代。
StructureElement 是单个结构元素字典的值对象。它记录标准结构类型(表 368 中的名称,如 H1 到 H6、P、L、LI、Table、Figure、Link)、标记内容标识符条目,以及用于替代文本、替换文本、标题和语言的可选无障碍属性。单个元素可能跨越多个页面,每页累积一个标识符条目,使 kids 数组能够跨页边界引用标记内容。
TaggedContentEmitter 在 HTML 管线与结构树之间充当桥梁。当 Document::enableTaggedPdf() 启用时,HTML renderer(渲染器)会接入发射器,为块级元素生成成对的标记内容运算符以及相应的结构元素节点。HtmlToStructureMap 提供从 HTML 标签到 PDF 结构类型的表驱动映射 (ISO 14289-2 §8)。发射器会将页眉、页脚等装饰性连续内容(如 HTML 页眉和页脚区域)路由到工件 (artifact),从而将其排除在阅读顺序之外。
语言标记由 Bcp47Validator (RFC 5646) 验证。它提供格式良好的语法检查以及基于注册表的有效性检查。严格模式 (ConformancePolicy::strictUa2()) 会在 API 边界拒绝格式错误的标记,而不是在写入时静默丢弃它们。这符合 ISO 14289-2 §8.4.4 中目录语言条目须解析为特定语言的要求。
API 表面
标题为“API 表面”的章节| 符号 | 类别 | 摘要 |
|---|---|---|
Document::enableTaggedPdf(string $lang = 'en', ?ConformancePolicy $policy = null): static | 方法 | 启用结构树和 HTML 桥接;设置 mark-info 和目录语言条目。 |
Document::setLanguage(string $lang): static | 方法 | 设置文档级自然语言 (BCP-47)。 |
Document::isTaggedPdfEnabled(): bool | 方法 | 检查当前活动的合规模式是否强制要求结构标记。 |
StructureTree::createRoot(string $lang = 'en'): int | 方法 | 创建强制性的单个 Document 根元素。 |
StructureTree::addElement(int $parentIndex, string $type, int $pageIndex, ...): int | 方法 | 挂接一个带类型的子结构元素。 |
StructureTree::hasRoot(): bool 和 rootHasChildren(): bool | 方法 | 检查该树是否存在以及是否有后代。 |
StructureElement | final 类 | 单个结构元素的值对象(替代文本、替换文本、标题、语言、标识符)。 |
RoleMap::standard(): array<string,string> | 静态 | 标准结构类型词汇表 (ISO 32000-2 表 368 加上 PDF 2.0 类型)。 |
Bcp47Validator::isWellFormed/isValid/validate/normalise | 方法 | RFC 5646 语法及基于注册表的语言标记验证。 |
AccessibilityAutoFixerRegistry | final 类 | 面向启发式结构修复器、可选用的 PSR-11 风格注册表。 |
代码示例 — 快速开始
标题为“代码示例 — 快速开始”的章节<?php
declare(strict_types=1);
use NextPDF\Core\Document;
$doc = Document::createStandalone();
// The BCP 47 tag drives the catalog language entry and the// structure-tree root language attribute.$doc->enableTaggedPdf(lang: 'en');$doc->setTitle('Tagged accessibility demo');$doc->addPage();
// Semantic HTML maps to structure elements: h1 to /H1, p to /P,// ul and li to /L plus /LI. Text runs are wrapped in// marked-content operators with stable identifiers.$doc->writeHtml('<h1>Document title</h1><p>Body paragraph.</p>');
$doc->save(__DIR__ . '/output/tagged.pdf');代码示例 — 生产环境
标题为“代码示例 — 生产环境”的章节<?php
declare(strict_types=1);
use NextPDF\Conformance\ConformancePolicy;use NextPDF\Core\Document;use NextPDF\Exception\InvalidConfigException;use Psr\Log\LoggerInterface;
final class AccessibleReportWriter{ public function __construct(private readonly LoggerInterface $logger) { }
public function render(string $html, string $bcp47Lang, string $outPath): void { $doc = Document::createStandalone();
try { // strictUa2() rejects malformed BCP 47 tags at the API // boundary (ISO 14289-2 §8.4.4) instead of dropping silently. $doc->enableTaggedPdf($bcp47Lang, ConformancePolicy::strictUa2()); } catch (InvalidConfigException $e) { $this->logger->error('Rejected language tag for tagged PDF', [ 'lang' => $bcp47Lang, 'reason' => $e->getMessage(), ]);
throw $e; }
$doc->setTitle('Quarterly accessibility report') ->setLanguage($bcp47Lang) ->addPage();
$doc->writeHtml($html);
// The engine emits a Degraded / ComplianceRisk advisory directing // the caller to validate externally; surface it to operators // rather than treating tagged output as certified. foreach ($doc->getWarnings() as $warning) { $this->logger->warning('Tagged-PDF advisory', [ 'code' => $warning->code->value, 'message' => $warning->message, ]); }
$doc->save($outPath); }}边界情况与陷阱
标题为“边界情况与陷阱”的章节- 调用顺序。 先调用
enableTaggedPdf(),再调用writeHtml()。HTML 管线在解析器构造时检查合规模式,不会对已渲染的内容回溯接入发射器。 - 空结构树。 调用了
enableTaggedPdf()但未挂接任何结构后代的文档,不会在其元数据中声明 PDF/UA-2。发布门控条件是rootHasChildren(),而不是hasRoot(),因为以空结构树声称符合 PDF/UA-2 的文件会被验证器拒绝 (ISO 14289-2 §5;由EmptyTaggedPdfDoesNotAdvertisePdfUa2Test验证)。 - 合规模式坍缩。 在同一文档上同时调用
enablePdfA()和enableTaggedPdf()会使单值合规判别符坍缩为以后一次调用为准。副作用(结构树、mark-info)仍会累加,并会发出CONFORMANCE_MODE_CLOBBERED警告,使该坍缩可被观察到。 - 自动修复器并非自动启用。 内置修复器 (
EmptyTagStripper、LegacyLangNormaliser、RootLangFallback) 随NextPDF\Accessibility\AutoFixer\*一同提供,但绝不会自动注册。使用方必须在AccessibilityAutoFixerRegistry上显式注册它们。
已知限制
标题为“已知限制”的章节NextPDF 会发出与 PDF/UA-2 结构树模型一致的结构,但不会自动创建它无法推断的语义。以下内容需要作者提供标记或属性,NextPDF 不会为你自动生成:
- 图像及其他非文本内容的替代文本;
- 超出 HTML 标记所表达范围的表头作用域,以及表头到单元格的关联;
- 当可见链接文本不能自描述时的链接用途文本;
- 在视觉上以列表形式排布但未使用列表标记的内容的列表语义;
- 当源顺序与预期阅读顺序不一致时,经过校正的阅读顺序;
- 对含义不明的内容判断其属于装饰性内容还是有意义内容。
本库不执行端到端的 PDF/UA-2 验证。运行时会发出 Degraded / ComplianceRisk 通告 (PDFUA2_FOUNDATIONAL),提示调用方使用外部检查器验证输出,以用于生产环境签核。请使用 PDF/UA 检查器(例如 veraPDF)进行验证。NextPDF 不会代你声明合规性。最终文档的合规性取决于创作选择以及验证器,而非取决于是否调用该 API。
结构树构建的复杂度与结构元素数量呈线性关系。每个标记内容序列的标识符分配为均摊常数时间。序列化是对元素集合的单次线性遍历。HTML 驱动标记的主要开销在于 HTML 管线本身,而非标记发射。performance_budget 中声明的每个配方上限(1500 ms 实际耗时、64 MB 峰值)适用于典型的多页语义文档。大型文档的扩展与元素数量呈线性关系,而不是与页数呈线性关系。
安全说明
标题为“安全说明”的章节语言标记和无障碍属性会流入 PDF 名称对象和字符串对象。NextPDF 通过 PdfStringEscaper 对它们进行转义,防止格式错误或恶意的语言、替代文本、替换文本和标题值突破其 PDF 对象上下文。严格模式还会在 API 边界拒绝未注册的 BCP-47 标记,在其到达写入器之前收窄输入面。无障碍属性可能携带作者提供的自由文本。请将它们视为不可信输出,并像审查其他文档内容一样进行审查。有关配置文件检查器的行为,请参阅合规模块。
合规性
标题为“合规性”的章节本页将库行为映射到条款标识符。它并不声明你的输出符合规范。所引用的条款均为转述,绝不直接引用原文。有关条款级表格和明确列出的未覆盖范围,请参阅PDF/UA-2 规范映射。引用块哈希记录在 docs/public/modules/core/_normative-evidence-a11y.md 中。