PDFium Component 校验 ISO 19005-1 Annex C 的实现限制——127 字节的名称记号、8191 个数组元素、4095 个字典条目和 28 层容器嵌套——并报告携带 /Encoding 条目的符号化 TrueType 字体。两项检查都在字节扫描路径上跑,所以 Delphi 或 Lazarus 应用完全不加载 PDFium DLL 就能拿到判定结论
这些是让人最困惑的失败,因为文档看起来好好的。它能渲染、能打印、每个字体都嵌入了、输出意图也在。然后校验器因为一个有 4096 个条目的字典拒绝它,而可见文档里没有任何东西解释原因
Annex C 这些限制到底在保护什么
与早于你生成器的那些实现之间的互操作性。Annex C 把 PDF Reference 的实现限制延续到每一个 PDF/A 子集,而这些数字不是任意的——它们描述了一个合规阅读器在历史上被要求必须能处理什么。一份超出它们的文件,可能在一个现代阅读器里完美打开,却在一台记录系统十五年前标准化的归档阅读器里失败,而这恰恰是 PDF/A 存在所要防止的场景
四个限制都是含端点的。一个恰好 127 字节的名称记号通过校验;128 不通过。一个恰好 8191 个元素的数组通过;8192 不通过。PDFium Component 正因如此在它的测试套件里把每一条边界的两侧都钉死,因为一处限制检查里的 off-by-one 会产生最糟糕的那种校验器:拒绝合规文件,却仍然被信任
uses FPdfPdfa;
var
Src: TFileStream;
Res: TPdfAValidationResult;
begin
Src := TFileStream.Create('archive.pdf', fmOpenRead or fmShareDenyWrite);
try
Res := ValidatePdfACompliance(Src);
if pvaiArrayOverLimit in Res.Issues then
Memo1.Lines.Add('An array carries more than 8191 elements');
if pvaiDictOverLimit in Res.Issues then
Memo1.Lines.Add('A dictionary carries more than 4095 entries');
if pvaiNestingOverLimit in Res.Issues then
Memo1.Lines.Add('Containers nest deeper than 28 levels');
if pvaiNameOverLimit in Res.Issues then
Memo1.Lines.Add('A name token is longer than 127 bytes');
finally
Src.Free;
end;
end;
哪些生成器真的会撞上这些限制
程序化构建结构的那些,也就是大多数业务线输出。一张带几千个字段的表单会产生超出 8191 的 /Annots 数组或 AcroForm /Fields 数组。一张其资源字典为每张生成的图片或字体实例累积一条条目的页面,会越过 4095。深度生成的结构树——对嵌套数据模型递归构建的标签化文档——会在没人留意的情况下越过 28 层,因为没人会去看嵌套深度
超长的名称来自另一种习惯:把数据编码进名称记号。一个用客户标识符拼出来的色料名、一个用完整文件路径命名的可选内容组、一个把六级层级拼到一起的表单字段全名。名称生成起来廉价、做长也容易,而一旦涉及 UTF-8 编码的标签,127 字节消失得比你以为的快
每一种情况下的修复都是结构性的。拆分数组、拆分字典、压平嵌套、缩短名称——每条问题的预检建议都点出具体的限制,而不是告诉你文件不合规。打标识注入(marker injection)在这里帮不上忙:这些不是元数据声称,它们就是对象图的形状
为什么符号化 TrueType 字体不能携带 /Encoding
因为 ISO 19005-1 §6.3.7 对符号化 TrueType 字体只承认字体自带的 cmap,而一个 /Encoding 条目会与之矛盾。一个符号化字体按自己的规则把代码映射到字形——这正是"符号化"的含义。加一张编码表,于是"字节 0x41 选哪个字形"就有了两个答案,文件里却没有规则说明哪个胜出。不同的阅读器以不同方式解决,一份在这个阅读器里显示为文本的文档,在另一个里就显示为 dingbat
无论描述符是内联写在字体字典里、还是被间接引用,PDFium Component 都从 /FontDescriptor 读取符号化标志。一个非符号化的 TrueType 字体保留它所需的 /WinAnsiEncoding 或 /MacRomanEncoding 而不会被标记,因为对非符号化字体而言,编码正是标准所要的。检查触发在矛盾上,而不是触发在出现了编码这件事上
if pvaiSymbolicTrueTypeEncoding in Res.Issues then
Memo1.Lines.Add(
'A symbolic TrueType font carries /Encoding; PDF/A admits only its ' +
'built-in cmap (ISO 19005-1 6.3.7)');
这种缺陷的实际来源,是把每一个 TrueType 字体都一视同仁地做子集化的某个生产者。Symbol、Wingdings、条码字体和图标字体是常见的携带者——恰好是商务文档用来做复选框、标志和条码的那些字体,也恰恰是文档因为"字体"通不过校验时没人再去复核的那些
问题如何出现在预检报告里
四条容器限制归到结构(structure)之下;符号化 TrueType 编码问题归到内容(content)之下。当一份报告要发给两个不同的人时,这种划分有意义:结构发现通常属于写生成器的那个人,内容发现通常属于提供素材的那个人
每条问题都带一条以具体语言点出补救措施的建议——把名称记号缩短到 127 字节或更短、拆分数组使其中任何一个都不超过 8191 个元素、从符号化 TrueType 字体里移除 /Encoding。一份说"不符合 PDF/A"的报告开启一次调查。一份说哪条限制被超出了、被什么超出了的报告终结一次调查
不带 DLL 校验,以及为什么这件事在这里要紧
上述所有检查都对着文件字节跑,所以它们能在没有部署 PDFium 二进制的服务里、在某个构建步骤里、或在加载原生 DLL 是一项策略问题的机器上工作。这是 PDFium Component 里一条刻意的设计线:能从结构回答的检查就从结构回答,DLL 留给那些确实需要渲染引擎的检查
围绕它的工作流——对一个文件夹跑校验、产出报告、决定如何处置发现——参见 Delphi 中的 PDF/A 预检校验和 批量预检报告 CLI的详解。至于位于所有这些检查之上的归档子集选择,PDF/A 归档合规的笔记覆盖了在你开始修问题之前应当瞄准哪个子集、哪个级别
PDFium Component 为 Delphi、C++Builder 和 Lazarus 包装 PDFium 引擎,提供高层 VCL API 和一组带不带 DLL 都能跑的合规校验器——支持的标准与平台见 PDFium Component 产品页