我感兴趣的是使用 LaTeX 的方式,这样当我编译两次时,我会得到完全相同的结果文件。
我的测试.tex:
\documentclass{article}
\begin{document}
Hello, World!
\end{document}
编译产生不同的哈希值:
8493b40b225993d01d46ed7479725d8b4e9f6efbfddcc8d6d657f00084d41cdb test.pdf
05f2a3cd3780df33470a4363da18b008595e42acd9a085d76c83b6c83dc71c41 test.pdf
等等。(这也适用于至少间隔一分钟编译为 DVI 的情况。)
我猜测这至少是由于 PDF 的创建和修改元数据造成的。我跟着这个答案来修复这些问题,但我仍然得到不同的哈希值。
我发现使用faketime '2008-12-24 08:15:42' pdflatex test
该文件进行编译时可以重现。我得出结论,其中不涉及随机数据,但它仅取决于时间。
我的问题是,我可以从我的 TeX 文档中影响 pdflatex 的时间吗?
答案1
自从TeX Live 2016,有几个选项可以实现可重现的构建:
pdfTeX
对于 pdfTeX (版本≥1.40.17),有三个新的原语:
\pdfinfoomitdate
,这将删除文档信息字典中的/CreationDate
和/ModDate
条目。默认情况下,这些条目将设置为文档编译的日期。它们可能已经使用在旧版本的 pdfTeX 中进行了修改\pdfinfo{/CreationDate (...)} /ModDate (...)}
,但使用\pdfinfoomitdate=1
,它们可以从生成的 PDF 文件中完全删除。\pdftrailerid
,它在文档信息条目中设置 PDF 文档的文件标识符,/ID
如PDF 规范,第 10.3 节默认情况下,它是通过散列当前日期和时间(即使\pdfinfoomitdate=1
使用)和输出文件的完整路径来计算的。通过包含固定\pdftrailerid{string}
细绳在您的文档中,此字符串的哈希值将用作标识符。将其留空会\pdftrailerid{}
完全删除该/ID
条目。\pdfsuppressptexinfo
控制写入文档的一些额外元数据:首先,pdfTeX 通常会创建一个PTEX.Fullbanner
包含完整版本字符串的条目,如 的输出所示pdftex --version
。此外,对于文档中包含的每个 PDF 图像,写入一些额外的元数据。对于可重现的构建来说,抑制这些条目并不是绝对必要的,但如果您想在不同的系统上编译相同的文档,这可能会有所帮助。可以通过发出 来完成\pdfsuppressptexinfo=-1
。
TL; DR:因此,获得可重现的 PDF 输出的最简单方法是使用
\documentclass{article}
\pdfinfoomitdate=1
\pdftrailerid{}
\begin{document}
Hello, World!
\end{document}
LuaTeX
自 TeX Live 2017 起,LuaTeX(版本≥1.0.4)也支持这些功能,尽管语法略有不同:
\pdfvariable suppressoptionalinfo
防止某些元数据包含在生成的 PDF 文件中,类似于\pdfsuppressptexinfo
pdfTeX,但具有更多选项:\pdfvariable suppressoptionalinfo \numexpr 0 + 1 % PTEX.FullBanner + 2 % PTEX.FileName + 4 % PTEX.PageNumber + 8 % PTEX.InfoDict + 16 % Creator + 32 % CreationDate + 64 % ModDate + 128 % Producer + 256 % Trapped + 512 % ID \relax
\pdfvariable trailerid
允许您指定自己的文件标识符\pdftrailerid
,就像你必须自己掌握正确的语法,因此我建议直接使用上述命令来抑制 ID。
TL; DR:对于 LuaLaTeX 中的可重现构建,请使用
\documentclass{article}
\pdfvariable suppressoptionalinfo \numexpr32+64+512\relax
\begin{document}
Hello, World!
\end{document}
特克斯
自 TeX Live 2019 起,XeTeX 支持指定文件标识符:
pdf:trailerid
是\special
识别的命令dvipdfmx
,XeTeX 使用它来生成 PDF 文件。值格式与\pdfvariable trailerid
LuaTeX 中的 相同:两个 PDF 字符串的原始 PDF 数组。两个字符串都必须是 16 个字节。dvipdfmx 文档提供了一个带有文字字符串(在括号之间指定)的示例。另一个示例是带有 16 字节十六进制字符串(在括号之间指定<[…]>
)的 MD5 哈希值,它可以是标识文档的 MD5 哈希值:\special{pdf:trailerid [ <00112233445566778899aabbccddeeff> <00112233445566778899aabbccddeeff> ]}
所有主流引擎(pdfTeX、LuaTeX、XeTeX)
作为替代方案,pdfTeX、LuaTeX 和 XeTeX 支持SOURCE_DATE_EPOCH
:
如果将环境变量设置SOURCE_DATE_EPOCH
为某个日期(以 Unix 时间戳的形式,例如由 的输出生成date +%s
),则将使用此日期而不是当前日期。因此,将其设置为固定日期可让您创建可重现的 PDF 文件,而无需对 LaTeX 源代码进行任何更改。但请记住,输出文件名(对于 pdfTeX 和 LuaTeX 包括完整路径,对于 XeTeX 仅包括名称本身)仍用于计算上述文件标识符:因此,如果您移动或重命名 LaTeX 文档并进行编译,则生成的 PDF 文档将发生变化。
答案2
更新pdftex 中现在有(texlive 2016)SOURCE_DATE_EPOCH 支持来解决这个问题(参见其他答案)。
如果你将源代码修改为
\pdfcompresslevel=0
\pdfobjcompresslevel=0
\documentclass{article}
\begin{document}
Hello, World!
\end{document}
运行两次,你会发现有三行发生了变化
/CreationDate (D:20150222233514Z)
/ModDate (D:20150222233514Z)
/ID [<84943B8BBB033F5EF8FAE4B3E350E35C> <84943B8BBB033F5EF8FAE4B3E350E35C>] >>
因此,一种可能性是使用运行 pdflatex 的包装脚本,然后清空这些字段,保持字节数保持不变。
答案3
pdfprivacy
您可以使用带有以下选项的包nopdftrailerid
:
pdfprivacy 还可以删除文档元数据:作者、标题、主题和关键字;以及允许您创建可复制 pdf 的 pdftrailerid。
https://ctan.gutenberg.eu.org/macros/latex/contrib/pdfprivacy/pdfprivacy.pdf
\usepackage[nopdftrailerid=true]{pdfprivacy}
也可以看看:在不同安装上可重现的构建