PDF/A 与无障碍

PDF/A 麻烦的地方在于:是否合规完全看不出来。同一份 LaTeX 文档生成的、外观完全相同的 PDF,按 A-2b 检验通过,按 A-1b 检验却不通过——真正跑一遍检验,验证器会用一行话点明原因:某个 ExtGState 字典里的 ca 键是 0.5 而不是 1.0。PDF/A 谈的不是「好看的 PDF」,而是 ISO 对文件内部结构提出的要求。本页讲清这个标准到底要求什么、从 LaTeX 生成它的两条路径(pdfx\DocumentMetadata),以及通常被略过的一步:如何确认生成的文件真的合规

PDF/A 到底要求什么

PDF/A(ISO 19005)是长期保存标准,其核心要求只有一条:文件必须完全自包含。十年后打开它的人没有你的字体、没有你的色彩管理设置、也没有你的任何其他文件,而它仍须正确呈现。所有具体条件都由此推出。字体必须完整嵌入(绝不指望读者机器上恰好装了什么);不得加密(密钥一丢,文档即失);须有 XMP 元数据(标题与作者以标准 XML 形式存放在 PDF 内部);须嵌入输出意图,即 ICC 配置文件(让「这个红」自带色彩空间的定义)。凡是要伸向文件之外的东西——比如表单的提交地址——之所以不受欢迎,也是同一个道理。

pdfx 生成 PDF/A — ICC 配置文件已随附

一行 \usepackage[a-2b]{pdfx},再加一个写着标题和作者的 \jobname.xmpdata 文件,PDF/A 就出来了。你不需要自己去找 ICC 配置文件。 早年确实有让你从 Adobe 网站下载 sRGB 配置文件的说法,但在 TeX Live 2024 中 colorprofiles 宏包已随附配置文件,日志里会留下 <<sRGB.icc>> 的嵌入记录。同一份日志中的 ** pdfx: Metadata file document.xmpdata read successfully. 表示元数据已读入;若忘了放这个文件,则会看到 ** pdfx: No file document.xmpdata . Metadata will be incomplete!——不过后文会看到,即使无视这条警告,文件照样能通过检验。除 a-1ba-2ba-3b 外,还有含无障碍要求的 a-1aa-2aa-3a 和支持 Unicode 的 a-2ua-3u。面向印刷的各种 PDF/X(如 x-1a)也由同一个宏包负责。

latex
% document.tex
\documentclass{article}
\usepackage[a-2b]{pdfx}   % reads document.xmpdata, embeds sRGB.icc
\begin{document}
Hello PDF/A.
\end{document}

% document.xmpdata -- a separate file, same base name
\Title{A Test Document}
\Author{Test Author}
\Keywords{LaTeX\sep PDF/A}

A-1b、A-2b 与 A-4 的区别:透明度能不能过

数字表示底层的 PDF 版本,末尾的字母表示严格程度。A-1 基于 PDF 1.4,A-2 基于 PDF 1.7,A-3 是 A-2 再允许附件,A-4 则基于 PDF 2.0。末尾的 b 是 basic,只要求外观可复现a 是 accessible,还要求标签结构(逻辑结构)u 要求正文文本可映射到 Unicode。实务中最容易踩到的差别是透明度。用 TikZ 叠两个 opacity=0.5 的矩形,以 [a-1b]{pdfx} 编译后交给 veraPDF,会在 ISO 19005-1:20056.4 条落败,原因是「An ExtGState dictionary contains the ca key (fill alpha) with value 0.5 other than 1.0」。同一份原稿只把选项改成 [a-2b] 就通过了。二十年前的一个事实——PDF 1.4 没有透明度——至今仍决定着仓储会不会收你的文件。

符合级别底层 PDF要点
A-1bPDF 1.4最窄。不能用透明度——凡用到 opacity 的图都会落败。对象流同样不允许
A-2bPDF 1.7透明度可以通过。从 LaTeX 生成时的默认选择。也允许 JPEG 2000
A-3bPDF 1.7在 A-2b 基础上允许附加任意文件。需要把原始数据随文档一同提交时会指定它
A-4PDF 2.0最新。文件头必须是 %PDF-2.0,因此要指定 pdfversion=2.0pdfx 不提供,需用 \DocumentMetadata

\DocumentMetadata — 不对齐 pdfversion 就会落败

另一条路径是把 \DocumentMetadata{pdfstandard=A-2b} 放在 \documentclass 之前。它不需要加载宏包,从 A-1B 到 A-4 都接受——但照原样写下去,只有 A-2b 真的能通过。pdfTeX 默认写出的是 PDF 1.5:对 A-1b 而言,交叉引用表成了流,因而被判「The document uses xref streams」(clause 6.1.4);对 A-4 而言,则被判「File header %PDF-1.5 ... does not match the pattern %PDF-2.n」(clause 6.1.2)。解法就在同一对花括号里:加上 pdfversion=1.4,A-1b 就通过;加上 pdfversion=2.0,A-4 就通过。反过来,给 A-2b 加 pdfversion=2.0,它又会以「%PDF-2.0 ... does not match the pattern %PDF-1.n」落败——因为 A-2 的底层最高只到 PDF 1.7。每个标准的正确答案都不一样,这正是最容易搞错的地方。

所写的指定保持默认(PDF 1.5)加上 pdfversion 后
pdfstandard=A-1b不合格 — clause 6.1.4「uses xref streams」pdfversion=1.4合格
pdfstandard=A-2b合格 — 唯一无需额外设置的组合pdfversion=2.0 反而不合格(clause 6.1.2)
pdfstandard=A-4不合格 — clause 6.1.2「header %PDF-1.5」pdfversion=2.0合格

那么,为什么 pdfxa-1b 仍写着 PDF 1.5 的文件头却能通过?答案在宏包源码里。pdfx.sty 第 515 行有一句注释 「PDF/A-1b doesn't allow object compression」,紧接着就设置了 \pdfobjcompresslevel=0。它不去纠结文件头上的数字,而是干脆不使用标准所禁止的功能。这正是两条路径的性格差异。pdfx 是一个把输出调整到符合标准的宏包\DocumentMetadata 是一个声明「我遵循哪个标准」的内核机制——至于内容能否配得上这个声明,得你自己负责。另一个差异是元数据:pdfx 会自动读取 .xmpdata,而 \DocumentMetadata 既不会自动填标题也不会自动填作者,需要你用 \hypersetup{pdftitle=...} 之类自己写入(见书签与元数据一页)。

latex
% before \documentclass -- the version must match the standard
\DocumentMetadata{
  pdfstandard = A-4,
  pdfversion  = 2.0,   % A-4 needs a %PDF-2.0 header
  lang        = en
}
\documentclass{article}

% for A-1b it is pdfversion = 1.4 instead;
% for A-2b, leave pdfversion out entirely.

检验产出:veraPDF,以及「通过」并不意味着什么

从 LaTeX 这一侧无法得知文件是否合规。 日志只会写「按 PDF/A 输出」,至于结果是否真的满足标准,那是另一回事。作出判定的是 PDF/A 验证器,事实标准是 veraPDF,即开源的官方检验工具。它不包含在 TeX Live 中,需要另行安装。用法很简单:verapdf file.pdf 会读取 PDF 内部的 XMP 标识符,自行判断该按哪个级别检验,并用一行给出 PASS ... 2bFAIL ... 1b。也可以用 -f 1b 强制指定级别。落败时要看的是条款号:6.4(透明度)、6.1.2(文件头版本)、6.3.3(注释外观)这些编号直接指向该改的地方。

shell
# auto-detect the level from the file itself
verapdf document.pdf
# -> PASS /path/document.pdf 2b

# force a level (useful to see what a stricter one would say)
verapdf -f 1b --format text document.pdf
# -> FAIL /path/document.pdf 1b

# the XML report names the clause and the exact reason
verapdf -f 1b document.pdf > report.xml

这里有一个事实,知道了将来就不会后悔:通过检验并不代表元数据可以是空的。 忘了放 .xmpdata、任由 ** pdfx: No file ... Metadata will be incomplete! 的警告留着,再把结果交给 veraPDF,它照样回答 PASS ... 1b——尽管文件里既没有标题也没有作者。因为符合级别 b 要求的是「XMP 元数据在结构上正确地存在」,而不是「内容被填满」。而让图书馆或仓储管理员为难的恰恰是那些空栏,所以请把验证器的通过与提交物的完成视为两回事。另外,养成用 pdffonts 确认字体嵌入的习惯也很稳妥(PDF 的生成一页有详述)。

交互表单与 PDF/A 无法共存

只要有一个用 hyperrefForm 环境做出的输入框,PDF/A 检验就会落败。 在文档里只放一个 \TextField,以 [a-2b]{pdfx} 编译后交给 veraPDF,会在 clause 6.3.3「An annotation does not contain an appearance dictionary」上不合格。因为 hyperref 把输入框的外观交给查看器去画(这正是 NeedAppearances 的含义),而 PDF/A 坚持外观必须固化进文件。再加上提交按钮,失格项还会增加,包括 clause 6.4.1「A Widget annotation dictionary shall not contain the A or AA keys」——也就是说,给表单部件挂动作这件事本身就被禁止。细想也理所当然:长期保存标准不可能认可一个专门向外通信的部件。如果收件方同时要求 PDF/A 和可填写表单,那是需求本身自相矛盾,动手之前先去确认。

带标签 PDF 与 PDF/UA:无障碍仍在发展中

PDF/A 的 a 级所要求的标签结构,就是把标题、段落、列表、阅读顺序、替代文本等逻辑结构嵌入 PDF,使屏幕阅读器能够遍历文档。单独的无障碍标准是 PDF/UA(ISO 14289),其第二部分即 PDF/UA-2。LaTeX 侧的实现是 LaTeX Project 的 tagpdf 宏包(Ulrike Fischer),TeX Live 2024 随附的是 0.98x 版(2024-02-29)——版本号以 0 开头,本身就说明了现状。像 \tagstructbegin 这样手工放置标签的低层命令,正是由该宏包提供。事实上,在 TeX Live 2024 的内核(LaTeX2e 2023-11-01)上写 \DocumentMetadata{tagging=on} 会报错:! LaTeX Error: The key 'document/metadata/tagging' is unknown and is being ignored. 本版中启用标签的入口是 testphase=phase-III,这样 pdfinfoTagged: 才会变成 yes。正如名称所示,这是处于测试阶段的功能,写法还会变。使用前请务必查阅当前文档。

还有一点应当如实写出:要同时满足这两者,目前还不容易。 把一份仅用 pdfstandard=A-2b 就能通过的文档加上 testphase=phase-III 后,veraPDF 在 clause 6.2.11.5 上判定不合格:「Glyph width 333 in the embedded font program is not consistent with the Widths entry of the font dictionary (value 334.2)」。问题不出在标签结构本身,而是嵌入字体的字宽不一致。如果对方要求提交「带标签的 PDF/A」,务必先过一遍 veraPDF 再交——假定它会通过,结果往往是在截稿前一夜卡住。

提交前该做的事

  • 先问清是哪一级。 如果对方只说「PDF/A」,务必确认是 A-1b 还是 A-2b。只要图中含透明度,这个差别就决定收不收。
  • 拿不准就用 pdfxa-2b \usepackage[a-2b]{pdfx} 加上 \jobname.xmpdata 里的标题与作者。这是唯一无需额外设置就能通过的组合。
  • 若必须 A-4,用 \DocumentMetadata{pdfstandard=A-4,pdfversion=2.0} pdfx 最高只到 A-3,做不出 A-4。
  • 别忘了写 .xmpdata 少了它检验照样通过,所以要自己去日志里找 Metadata will be incomplete! 这条警告。
  • 每次提交前都跑一遍 verapdf document.pdf 它不在 TeX Live 里,需另行安装。落败就读条款号(6.46.1.26.3.3)。
  • 放弃表单。 带可填写字段的文档无法符合 PDF/A。