TeX Live 2024 里带着一个叫 VPlayer.swf 的文件:483,412 字节的 Flash,修改日期是 2018 年 1 月 19 日。用 LaTeX 的 media9 调 \includemedia,这个文件就会被整个嵌进 PDF——为了一段视频塞进近半兆字节的播放器,而且它已经在任何地方都跑不起来了。Flash 于 2020 年 12 月 31 日终止支持,自 2021 年 1 月 12 日起 Adobe 自己开始阻止 Flash 内容播放。PDF 能承载的远不止静态页面:附件、图层、注释、动画、视频——但它们在真实世界里的退化程度差别极大。本页用 pdfdetach 与 pdftotext 逐一验证,把这件事理清楚。
把文件嵌进 PDF:attachfile2 与 embedfile 的区别
区别在于文件被放进 PDF 的哪个位置。attachfile2 的 \attachfile{data.csv} 会在页面上放一个 /FileAttachment 注释,也就是那个回形针图标。embedfile 的 \embedfile{data.csv} 则不在页面上放任何东西,而是把文件登记到全文档的 /Names 下的 /EmbeddedFiles 树里。实测的分工非常干净:attachfile2 的输出里有两个 /FileAttachment 注释(分别来自 \attachfile 与 \textattachfile),却没有 /EmbeddedFiles;embedfile 的输出恰好相反。查看器的「附件」面板读的是后面那棵名字树,所以只用 \attachfile 做出来的 PDF,面板里什么也不显示,读者必须在页面上找到图标才能打开。两个都用才最稳妥。
\usepackage{attachfile2}
\usepackage{embedfile}
% visible on the page: a clickable paperclip, and custom link text
\attachfile[icon=Paperclip,author={Ada}]{data.csv}
\textattachfile{data.csv}{download the data}
% invisible, but listed in the viewer's Attachments panel
\embedfile[desc={Source data},mimetype=text/csv]{data.csv}该选哪个,宏包自己给了答案。attachfile2 是 Heiko Oberdiek 在 Scott Pakin 的 attachfile 基础上发展来的(TeX Live 2024 随附 v2.12,2024-01-18),增加了 dvips 支持,并会自动写入附件的元信息。实测也确实如此:attachfile2 生成的 PDF 里带着 /Size 12(原文件字节数)和 /CheckSum <178ba857…>,而旧的 attachfile 输出里根本没有 /CheckSum。而且它自己的 README 明写着:「关于这项功能,请参见更新的 embedfile 宏包。」新写文档时,按需不需要可见图标来选;若不需要,embedfile 更直截了当。
附件到底进没进去,用 poppler 的 pdfdetach 一验便知。pdfdetach -list paper.pdf 会列出嵌入的文件,pdfdetach -saveall -o out paper.pdf 则把它们取出来——实测取出的 data.csv 与原文件一字节不差。这里有一点值得留意:同一个文件附两次,就会被嵌入两份。只要把 \attachfile 与 \textattachfile 并排写上,pdfdetach -list 就会回答 2 embedded files。如果想从多处引用一份大数据集,明智的做法是用 embedfile 只嵌一次,正文里再指向它。
$ pdfdetach -list paper.pdf
2 embedded files
1: data.csv
2: data.csv
$ pdfdetach -saveall -o out paper.pdf && diff out/data.csv data.csv && echo identical
identical图层(OCG)——ocgx2,以及「隐藏的内容并没有藏住」
PDF 的 OCG(Optional Content Groups,可选内容组) 是可以显示或隐藏的层。Alexander Grahn 的 ocgx2 用 \begin{ocg}{name}{id}{initial visibility}…\end{ocg} 包住内容,并提供 \switchocg、\toggleocgs、\showocgs、\hideocgs 作为可点击的开关。生成的 PDF 里确实带有 /OCProperties 字典,这套做法适合逐步揭示,或把同一张图的不同版本叠在一起。不过切换主要在 Acrobat 里才真正生效,其他查看器可能只显示初始状态。
\usepackage{ocgx2}
Question: what is $2+2$?
\begin{ocg}{Answer}{ans}{0} % 0 = hidden when the file opens
The answer is 4.
\end{ocg}
\switchocg{ans}{Show or hide the answer}还有一个更要命的陷阱:隐藏图层上的文字并没有被藏住。把上面的例子编译出来再跑 pdftotext,抽取到的文本是 Question: what is 2 + 2? The answer is 4.——初始状态明明是隐藏,答案却原样出来了。OCG 规定的是「画不画」,既不是加密也不是删除。所以用 OCG 藏起不想被看到的内容毫无意义:习题答案、给审稿人的遮蔽内容、另一版价目表——任何人只要复制文本或跑一次抽取工具,就全都能读到。不该被看到的东西,一开始就别放进 PDF。
注释与批注——用 pdfcomment 做便签、高亮与提示框
Josef Kleber 的 pdfcomment(TeX Live 2024 随附 v2.4a)能从 LaTeX 源码生成 PDF 注释。\pdfcomment{…} 生成便签(/Subtype /Text),\pdfmarkupcomment[markup=Highlight]{正文}{批注} 会给正文涂上荧光笔并挂上批注(/Subtype /Highlight),\pdftooltip{文字}{说明} 则是鼠标悬停时出现的提示框。author={Reviewer} 会写进 PDF 的 /T,在查看器的批注列表里显示为作者名。于是可以把审稿意见写在 LaTeX 源码里,在分发的 PDF 中以注释形式呈现。
实务上有两点提醒。其一是颜色:不加载 xcolor 就写 color=yellow,编译会以 ! Package pdfcomment Error: This color specification is not supported 中断;先放 \usepackage{xcolor} 就通过了。其二是编译轮次:标记类注释第一次不会出现,要到第二次才会写进 /Subtype /Highlight(日志里甚至会写 Rerun to get outlines right or use package bookmark.)。不过 pdfcomment 属于比较能活下来的那一类——用 poppler 把页面渲染成图片再数颜色,高亮的黄色(RGB 255,242,0)确实被画了出来。另外注释正文不会出现在 pdftotext 的抽取结果里,所以不必担心批注混进正文。
\usepackage{xcolor} % must come before pdfcomment for named colours
\usepackage{pdfcomment}
A sentence.\pdfcomment[author={Reviewer},icon=Note,color=yellow]{Check this reference.}
A \pdfmarkupcomment[markup=Highlight,color=yellow,author={Reviewer}]%
{highlighted phrase}{needs a citation}.
A \pdftooltip{term}{shown when the pointer rests on it}.动画——animate 靠 JavaScript 驱动
Alexander Grahn 的 animate 是唯一不依赖 Flash 的动态方案。写下 \animategraphics[controls,autoplay]{12}{frame-}{0}{59},就得到一个以每秒 12 帧播放 frame-0.png 到 frame-59.png 的图。若要直接让 TikZ 或 PSTricks 画出的帧动起来,用 animateinline 环境配合 \multiframe。它的内核是嵌进 PDF 的 JavaScript——pdfinfo 的 JavaScript 一栏确实会变成 yes。只要查看器会执行 JavaScript 就能播放,可移植性远胜嵌入视频。代价是所有帧都要嵌进去,帧数与分辨率会直接反映到文件体积上。
\usepackage{animate}
% a numbered image series: frame-0.png ... frame-59.png at 12 fps
\animategraphics[controls,autoplay,width=6cm]{12}{frame-}{0}{59}
% or animate TikZ frames drawn on the fly
\begin{animateinline}[controls,loop]{6}
\multiframe{12}{i=0+30}{%
\begin{tikzpicture}\draw[->,thick] (0,0)--(\i:1.5);\end{tikzpicture}}
\end{animateinline}多媒体——media9 与 Flash 的遗产
media9 的 \includemedia 能把视频、音频和 3D 嵌进 PDF,movie15 是它已被废弃的前身。技术上它至今仍能跑——实测确实生成了 /Subtype /RichMedia 注释和 /Subtype /Video 对象。问题出在播放这一侧。\includemedia 第二个参数里写的 VPlayer.swf 是 Flash 文件,至今仍随 TeX Live 2024 一起分发,而 Flash 本身已于 2020 年底终结。用一个 14 字节的假视频试了一下,输出 PDF 达到 493,989 字节——里面绝大部分是一个再也不会运行的播放器。新文档请改为链接外部媒体;若只是逐帧运动,用 animate。把 media9 当作遗留方案,才是正确的判断。
哪些功能能活下来,活到什么程度
先给结论:附件和注释可以放心用,图层和视频不行。下表记录的是用 TeX Live 2024 生成的 PDF 在 poppler 系工具(pdfinfo、pdfdetach、pdftotext、pdftoppm)眼中的样子。这能确认的只是「PDF 里有没有正确的对象」以及「poppler 如何对待它们」;Adobe Acrobat 中的行为本文并未验证。对于公认高度依赖 Acrobat 的功能——OCG 切换、\includemedia 播放——请务必先在读者实际使用的环境里自行测试,再决定是否采用。
| 宏包 | 在 PDF 里生成什么 | 本地确认到的结果 |
|---|---|---|
attachfile2 | 页面上的 /FileAttachment 注释 | pdfdetach -list 能检出;还记录 /Size 与 /CheckSum |
embedfile | 文档 /Names 下的 /EmbeddedFiles 树条目 | pdfdetach -saveall 取回的字节与原文件完全一致 |
ocgx2 | /OCProperties 字典与图层 | 隐藏图层上的文字仍会被 pdftotext 抽出 |
pdfcomment | /Text 与 /Highlight 注释以及 /T(作者名) | poppler 渲染时确实画出了荧光色;需要编译两次 |
animate | 嵌入的 JavaScript 与全部帧的图像 | pdfinfo 报 JavaScript: yes |
media9 | /RichMedia 注释与 VPlayer.swf(Flash) | 14 字节的视频让输出膨胀到 493,989 字节 |