PDF/A、PDF/X 和 PDF/UA 是三种不同的标准,解决三个不同的问题:长期归档、印刷交换和无障碍访问。它们不是同一张合规表上的三个勾选框,而最常见的错误就是把它们当成那样。一份文件可以是完美的 PDF/A,对印刷厂却毫无用处;一份完美的印刷母版对屏幕阅读器可能完全不可读。更麻烦的是,这三者约束的都是文件的内部结构,而不是它看上去如何。一份在你手上每个查看器里都能干净打开的文档,第一次送检时照样可能不合格,而且通常就是不合格
HotPDF 是 losLab 的原生 VCL PDF 库,它把合规当成第一页尚未存在时就要声明的事。你设置一个合规属性,附上标准所要求的结构,库则会在保存时拒绝与该配置文件相抵触的配置。这比先生成文件再指望某个后处理器把它改造合规要好,因为这些标准要求的绝大部分内容都无法事后补上
三项 ISO 标准,三种不同的承诺
PDF/A(ISO 19005)关乎时间。它承诺一份文件几十年后仍能一模一样地呈现,因此要求彻底自包含:字体全部嵌入,每种颜色都通过 OutputIntent 获得与设备无关的含义,XMP 元数据完整,并禁止任何行为取决于环境的东西。加密和 JavaScript 被排除在外,因为没人能保证解密器或脚本引擎在 2050 年还存在
PDF/X(ISO 15930)关乎纸面上的颜色。它的存在,是为了让设计师把文件交给印刷厂时双方无需再讨论,这意味着要有特性化的印刷条件、一个必备的 /Trapped 键、明确的裁切与出血几何,而在 X-1a 这一档里,还不允许留下 RIP 只能靠猜的实时透明。PDF/UA(ISO 14289)关乎谁能读到成果。辅助技术需要一棵完整的标签树、合理的阅读顺序、一个声明过的文档语言,以及任何非文本内容的文字替代
由于这三者用力的方向各不相同,正确做法是按输出通道各选一项主导标准,而不是追求一份让它们统统满意的文件。给一位从不看颜色的屏幕阅读器用户递一份纯 CMYK 的印刷母版,恰恰是最不该做的事;而归档配置文件对动态行为的封锁,也会跟任何交互内容撞车。从同一份源数据按通道分别生成,你就绕开了整个冲突
PDF/A:OutputIntent 是人人都会忘掉的那一环
如果一份 PDF/A 文件验证不过,第一个该查的就是 OutputIntent。它是生成器最常跳过的结构,恰恰因为没有任何可见的东西依赖它。ISO 19005 要求必须有一个:一份嵌入的 ICC 配置文件,把文档的设备颜色究竟意味着什么钉死。HotPDF 把这份配置文件做成显式输入,而不是事后想起的补丁:
var
Pdf: THotPDF;
ICC: TFileStream;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'invoice-archival.pdf';
Pdf.PDFACompliance := 'B'; // 级别 B:视觉保真
Pdf.Lang := 'en-US';
Pdf.StandardFontEmulation := False; // 嵌入真实字体,不做 Base-14 模拟
ICC := TFileStream.Create('sRGB.icc', fmOpenRead);
try
Pdf.AddPDFAOutputIntent('sRGB IEC61966-2.1', '', ICC, 3, 'DeviceRGB');
finally
ICC.Free;
end;
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Archival invoice body');
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
这里有几处细节决定通过与否。StandardFontEmulation 必须关闭:被模拟的 Base-14 字体不会嵌入,而在 ISO 19005 之下嵌入没有商量余地。加密必须保持禁用,所以永远不要把 PDFACompliance 与 ActivateProtection 组合起来;一份加密的归档文件本身就是矛盾,校验器一眼就能抓到。AddPDFAOutputIntent 里的分量数必须与配置文件匹配,sRGB IEC61966-2.1 这类 RGB 配置文件是 3,CMYK 则是 4。HotPDF 在写出过程中会对照声明的意图跟踪 DeviceRGB 和 DeviceCMYK 的使用情况,因此 RGB 意图文档里混进的一处 CMYK 填充会变成一个被报告出来的问题,而不是静悄悄溜过去
关于 ICC 配置文件还有一点值得说:把它当作有版本的部署产物,而不是某人某天丢在构建服务器上的一个文件。它的字节会嵌入你生成的每一份文档,所以一份被截断或损坏的配置文件会悄无声息地毒化一整批,而你要到验证时才发现。让它随安装程序一起发布,把校验和记进运行日志,并按上面示范的 TFileStream 模式加载它,这样文件缺失会在生成阶段大声报错,而不是在归档关口悄然失手
面向印刷的 PDF/X:Trapped、CMYK 与印刷特性文件
印刷母版把颜色的故事整个反了过来。印刷机要的是特性化的 CMYK,而标准逼你声明是否做过陷印,哪怕诚实的答案是你根本不知道。/Trapped 键无论如何都是必备的:
Pdf.PDFXCompliance := 'X-1a';
Pdf.Trapped := 'Unknown'; // ISO 15930 下的必备键
ICC := TFileStream.Create('FOGRA39.icc', fmOpenRead);
try
Pdf.AddPDFXOutputIntent('FOGRA39 (ISO 12647-2:2004)', '', ICC, 4, 'DeviceCMYK');
finally
ICC.Free;
end;
Pdf.BeginDoc;
// 用 CMYK 安全色绘制,不用透明,不加密
Pdf.EndDoc;
现在分量数变成了 4,对应 CMYK 印刷特性文件。X-1a 还禁止实时透明,因此要审查任何叠放半透明元素的绘图代码;查看器在屏幕上合成出来的东西,正是 RIP 会拒绝解释的东西。当你的印刷厂换了一套特性化数据,替换配置文件字节和标识字符串即可,周围的结构不必动
PDF/UA:结构是生成出来的,绝不能事后补装
无障碍是团队最常想在最后阶段临时加装的标准,而它对这种做法的惩罚比另外两项都重。标签树必须映射内容在逻辑上被创建的顺序,而文件一旦写出,这份信息你就再也拿不回来了。设置 PDFUACompliance 会打开带标记输出,结构 API 则在你逐步绘制时把每次绘图调用绑定到它的语义角色上:
Pdf.PDFUACompliance := True; // 自动启用带标记 PDF
Pdf.Lang := 'en-US'; // 显式设置;留空则回落到 'en'
Pdf.BeginDoc;
Root := Pdf.AddStructureElement(sstDocument, nil);
H1 := Pdf.EmitTaggedHeading(1, Root, 50, 700, 'Quarterly Report');
Para := Pdf.BeginTaggedContent('P', Root);
Pdf.CurrentPage.TextOut(50, 650, 0, 'Revenue grew in all regions.');
Pdf.EndTaggedContent;
Pdf.EndDoc;
要提防的失败是画在任何 BeginTaggedContent/EndTaggedContent 配对之外的文本。它渲染得完美无缺,却对屏幕阅读器隐身,因此没有哪个视力正常的测试者能发现它;这个缺陷就此发布出去,只有当真正使用辅助技术的用户撞上这处空档时才浮现。当你的模板带有自定义结构角色名时,用 AddStructRoleMap('MyHead', 'H1') 把它们映射到标准集合上,合规的阅读器才知道它们是什么意思。ISO 14289 同样要求声明语言。Lang 为空时 HotPDF 会回落到 'en',但那是一张安全网,不是把真实文档语言留空的理由
验证:信校验器,别信查看器
一个能打开你文件的查看器,对合规性什么都证明不了,所以验证应当放进发布流程,交给检查结构而非检查渲染的工具。对 PDF/A 和 PDF/UA,veraPDF 是参考级的开源校验器;它按 ISO 条款报告失败项,可以直接对回上面的配置。对 PDF/X,Adobe Acrobat 的印前检查配置文件仍是现实中的检查手段,因为印刷合规既关乎语法,也同样关乎色彩意图
生成器也承担了自己那一份。保存时 HotPDF 会把特性开关与所配置的 PDF 版本对账,把该版本表达不了的东西静默降级,比如在 PDF 1.7 以下把 AES-256 降为 AES-128。EndDoc 里的合规关卡走得更远,遇到硬性矛盾会直接抛出,比如同时要求 PDFACompliance 和加密。这两者都不能替代外部校验器。它们只是让不可能的配置根本走不到校验器面前
有一个习惯会反复带来回报:把整套合规配置作为一个整体来做版本管理。HotPDF 的发布版本、模板修订号、ICC 配置文件的校验和、签署通过的校验器构建号。只要其中任何一项在其他项之下悄悄变动,合规就开始漂移,而最难看的审计,正是那种没人能重建出五年前那份归档文件由哪套组合产出的情形。每批一条配置记录,就能一劳永逸地了结这个问题
最后,要在真实的生产输出上跑校验器,绝不能只跑手工拼出来的干净样本。真正咬人的失败来自没人预料到的数据:一个到手时是 CMYK 的客户徽标,而意图声明的是 RGB;一次模板微调塞进了一款未嵌入的字体;一条新代码路径把文本画到了标签树之外。把过往每次事故留下的一份已知坏文件当作回归输入,合规关卡才能长期保持诚实。这些流水线的渲染一侧,见我们关于用 HotPDF 输出报表、字体与图像的文章;要把校验器接进构建流程,还有一篇自动化 PDF 印前检查的姊妹篇
这些示例中用到的合规属性、输出意图和标记 API,随面向 Delphi 与 C++Builder 的 HotPDF Delphi Component 一同提供;产品页链接了此处每个调用的完整参考