mirror of
https://github.com/jgm/pandoc.git
synced 2026-09-19 02:22:09 +08:00
LaTeX writer: set pdf-trailer-id if SOURCE_DATE_EPOCH envvar is set
The `SOURCE_DATE_EPOCH` environment variable is used to trigger reproducible PDF compilation, i.e., PDFs that are identical down to the byte level for repeated runs. Closes: #6539
This commit is contained in:
committed by
John MacFarlane
parent
48e59436ec
commit
3a185fb5d0
+15
@@ -3209,6 +3209,16 @@ These variables function when using BibLaTeX for [citation rendering].
|
||||
`natbiboptions`
|
||||
: list of options for natbib
|
||||
|
||||
#### Other
|
||||
|
||||
`pdf-trailer-id`
|
||||
: the PDF trailer ID; must be two PDF byte strings if set,
|
||||
conventionally with 16 bytes each. E.g.,
|
||||
`<00112233445566778899aabbccddeeff>
|
||||
<00112233445566778899aabbccddeeff>`.
|
||||
|
||||
See the section on [reproducible builds].
|
||||
|
||||
[KOMA-Script]: https://ctan.org/pkg/koma-script
|
||||
[LaTeX Font Catalogue]: https://tug.org/FontCatalogue/
|
||||
[LaTeX font encodings guide]: https://ctan.org/pkg/encguide
|
||||
@@ -7541,6 +7551,11 @@ be taken from it instead of the current time.
|
||||
`SOURCE_DATE_EPOCH` should contain an integer unix timestamp
|
||||
(specifying the number of seconds since midnight UTC January 1, 1970).
|
||||
|
||||
For reproducible builds with LaTeX, you can either specify the
|
||||
`pdf-trailer-id` in the metadata or leave it undefined, in which
|
||||
case pandoc will create a trailer-id based on a hash of the
|
||||
`SOURCE_DATE_EPOCH` and the document's contents.
|
||||
|
||||
Some document formats also include a unique identifier. For
|
||||
EPUB, this can be set explicitly by setting the `identifier`
|
||||
metadata field (see [EPUB Metadata], above).
|
||||
|
||||
@@ -40,6 +40,19 @@ $header-includes$
|
||||
$endfor$
|
||||
$after-header-includes.latex()$
|
||||
$hypersetup.latex()$
|
||||
$if(pdf-trailer-id)$
|
||||
|
||||
\ifXeTeX
|
||||
\special{pdf:trailerid [ $pdf-trailer-id$ ]}
|
||||
\fi
|
||||
\ifPDFTeX
|
||||
\pdftrailerid{}
|
||||
\pdftrailer{/ID [ $pdf-trailer-id$ ]}
|
||||
\fi
|
||||
\ifLuaTeX
|
||||
\pdfvariable trailerid {[ $pdf-trailer-id$ ]}
|
||||
\fi
|
||||
$endif$
|
||||
|
||||
$if(title)$
|
||||
\title{$title$$if(thanks)$\thanks{$thanks$}$endif$}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
{-# LANGUAGE ViewPatterns #-}
|
||||
{- |
|
||||
Module : Text.Pandoc.Writers.LaTeX
|
||||
Copyright : Copyright (C) 2006-2024 John MacFarlane
|
||||
Copyright : Copyright (C) 2006-2025 John MacFarlane
|
||||
License : GNU GPL, version 2 or above
|
||||
|
||||
Maintainer : John MacFarlane <jgm@berkeley.edu>
|
||||
@@ -30,6 +30,7 @@ import Control.Monad
|
||||
liftM,
|
||||
when,
|
||||
unless )
|
||||
import Crypto.Hash (hashWith, MD5(MD5))
|
||||
import Data.Containers.ListUtils (nubOrd)
|
||||
import Data.Char (isDigit, isAscii)
|
||||
import Data.List (intersperse, (\\))
|
||||
@@ -40,7 +41,8 @@ import qualified Data.Text as T
|
||||
import Network.URI (unEscapeString)
|
||||
import Text.DocTemplates (FromContext(lookupContext), Val(..), renderTemplate)
|
||||
import Text.Collate.Lang (renderLang)
|
||||
import Text.Pandoc.Class.PandocMonad (PandocMonad, report, toLang)
|
||||
import Text.Pandoc.Class.PandocMonad (PandocMonad, getPOSIXTime, lookupEnv,
|
||||
report, toLang)
|
||||
import Text.Pandoc.Definition
|
||||
import Text.Pandoc.Highlighting (formatLaTeXBlock, formatLaTeXInline, highlight,
|
||||
styleToLaTeX)
|
||||
@@ -63,6 +65,7 @@ import Text.Pandoc.Writers.LaTeX.Util (stringToLaTeX, StringContext(..),
|
||||
wrapDiv, hypertarget, labelFor,
|
||||
getListingsLanguage, mbBraced)
|
||||
import Text.Pandoc.Writers.Shared
|
||||
import qualified Text.Pandoc.UTF8 as UTF8
|
||||
import qualified Text.Pandoc.Writers.AnnotatedTable as Ann
|
||||
|
||||
-- Work around problems with notes inside emphasis (see #8982)
|
||||
@@ -178,6 +181,19 @@ pandocToLaTeX options (Pandoc meta blocks) = do
|
||||
st <- get
|
||||
titleMeta <- stringToLaTeX TextString $ stringify $ docTitle meta
|
||||
authorsMeta <- mapM (stringToLaTeX TextString . stringify) $ docAuthors meta
|
||||
-- The trailer ID is as hash used to identify the PDF. Taking control of its
|
||||
-- value is important when aiming for reproducible PDF generation. Setting
|
||||
-- `SOURCE_DATE_EPOCH` is the traditional method used to control
|
||||
-- reproducible builds. There are no cryptographic requirements for the ID,
|
||||
-- so the 128bits (16 bytes) of MD5 are appropriate.
|
||||
reproduciblePDF <- isJust <$> lookupEnv "SOURCE_DATE_EPOCH"
|
||||
trailerID <- do
|
||||
time <- getPOSIXTime
|
||||
let hash = T.pack . show . hashWith MD5 $ mconcat
|
||||
[ UTF8.fromString $ show time
|
||||
, UTF8.fromText $ render Nothing main
|
||||
]
|
||||
pure $ mconcat [ "<", hash, "> <", hash, ">" ]
|
||||
-- we need a default here since lang is used in template conditionals
|
||||
let hasStringValue x = isJust (getField x metadata :: Maybe (Doc Text))
|
||||
let geometryFromMargins = mconcat $ intersperse ("," :: Doc Text) $
|
||||
@@ -254,7 +270,10 @@ pandocToLaTeX options (Pandoc meta blocks) = do
|
||||
Just (Just ('A', ds))
|
||||
| not (T.null ds) && T.all isDigit ds
|
||||
-> resetField "papersize" ("a" <> ds)
|
||||
_ -> id)
|
||||
_ -> id) .
|
||||
(if reproduciblePDF
|
||||
then defField "pdf-trailer-id" trailerID
|
||||
else id) $
|
||||
metadata
|
||||
let babelLang = mblang >>= toBabel
|
||||
let context' =
|
||||
|
||||
Reference in New Issue
Block a user