PDF の生成(pdfTeX / dvipdfmx)

「Hello」だけを書いた LaTeX 文書は、DVI なら 252 バイトです。同じ 1 ページを pdflatex で PDF にすると 11,287 バイトになります。増えた 11 キロバイトのほとんどは本文ではなく フォント本体 で、その組版に使った 5 文字ぶんだけを切り出した Type 1 プログラムが 9,002 バイトを占めています。LaTeX の PDF 生成とは、突き詰めればこの「何をファイルに同梱するか」の決定の連続です。このページでは、PDF へ至る 2 つの経路、PDF バージョンと圧縮の指定、用紙サイズが実際にどう記録されるか、そしてフォント埋め込みを pdffonts でどう確認するかを、すべて実測値つきで見ていきます。

PDF への 2 つの経路——直接出力と DVI 経由

PDF を書き出すのは、エンジン自身か、あるいは DVI を変換する別のプログラムかのどちらかです。直接経路では pdflatex(pdfTeX)と lualatex(LuaTeX)がページを組みながら PDF 演算子を直接吐きます。DVI 経由では latex や日本語の (u)platex がまず DVI を書き、dvipdfmx がそれを PDF に翻訳します。おもしろいのは xelatex で、これは見た目には 1 コマンドですが実質は後者です——XeTeX は .xdv という拡張 DVI を書き、それを xdvipdfmx に渡します。TeX Live 2024 の実体を見るとこの事情がはっきりします。dvipdfmxxdvipdfmx へのシンボリックリンクであり、日本語の DVI 経路と XeTeX の .xdv 経路は同じ 1 つのバイナリが処理しているのです。証拠は出力側に残っていて、xelatex で作った PDF の Producer は xdvipdfmx (20240305) になります。

terminal
# TeX Live 2024: one binary serves both DVI routes
$ ls -l $(which dvipdfmx)
lrwxr-xr-x  1 root  wheel  9 May  4  2024 .../dvipdfmx -> xdvipdfmx

# direct
$ pdflatex paper.tex
# via DVI
$ latex paper.tex && dvipdfmx paper.dvi
# via DVI, the Japanese way
$ uplatex paper.tex && dvipdfmx paper.dvi

同じ原稿を 5 通りに通すと、どれも「A4 の 1 ページ」なのに、PDF の中身は少しずつ違います。Producer 文字列が違うのは当然として、注目すべきは 既定の PDF バージョンMediaBox の数値 です。A4 の幅 210 mm は正確には 595.2755905… ポイントで、これをどう丸めるかは変換器ごとに違います。pdfTeX と LuaTeX は \pdfdecimaldigits=3 に従って 595.276、dvipdfmx は 595.28、Ghostscript(ps2pdf)にいたっては 595 と整数まで丸めます。この差が実務で問題になることはまずありませんが、「同じ A4 なのに差分が出る」現象の正体はこれです。

コマンドPDF を書くプログラム既定の PDF バージョンA4 の MediaBox 幅
pdflatexpdfTeX 自身1.5595.276
lualatexLuaTeX 自身1.5595.276
xelatex.xdv を経て xdvipdfmx1.5595.28
latex + dvipdfmxdvipdfmx1.5595.28
uplatex + dvipdfmxdvipdfmx。日本語の定番経路1.5595.28
latex + dvips + ps2pdfGhostscript1.4595

pdflatex で EPS が使えないというのは、もう古い話

TeX Live 2024 では、\usepackage{graphicx}\includegraphics{fig.eps} と書いて pdflatex を回せば、追加の指定なしに図が入ります。pdfTeX が EPS を読めるようになったわけではありません。ドライバファイル pdftex.def が、LaTeX が動いていて シェルエスケープが有効(TeX Live 既定の restricted で十分)で \DoNotLoadEpstopdf が定義されていないなら、epstopdf-base を自動で読み込むのです。実際に走らせると fig-eps-converted-to.pdf という中間ファイルが原稿の横にでき、それが取り込まれます。\usepackage{epstopdf} を明示的に書く必要はもうありません。逆に、-no-shell-escape を付けると変換が走らず、エラーも警告も出ないまま図だけが消えます。この静かな失敗のほうが厄介です。

この自動変換には落とし穴もあり、pdftex.def のコメント自身が 「これは間違いになりうる」 と警告しています。fig.pdffig.eps が両方あり、本物の原図が PDF のほうである場合、EPS から作り直された PDF に上書きされてしまうからです。そのときは \documentclass よりさらに前に \newcommand{\DoNotLoadEpstopdf}{} を置きます。なお DVI 経由(dvipdfmx)は EPS を昔から扱えます——裏で Ghostscript を呼んで PDF に変換して取り込むためで、この経路では変換の中間ファイルは残りません。EPS 資産の多い旧来のワークフローが DVI 経由に馴染むのはこのためです。

latex
% keep a hand-made fig.pdf from being overwritten by fig.eps
\newcommand{\DoNotLoadEpstopdf}{}
\documentclass{article}
\usepackage{graphicx}
\begin{document}
\includegraphics{fig}   % extension omitted: pdf, png, jpg are tried first
\end{document}

出力ドライバは本当に自動判定されるのか

graphicxcolor については、答えは「はい」です。両者は低レベルの命令を出すために 出力ドライバpdftexluatexxetexdvipdfmxdvipsdvisvgm)を知る必要がありますが、設定ファイル graphics.cfg\pdfoutput\XeTeXversion\luatexversion の有無を調べてエンジンを言い当て、pdftex.defluatex.defxetex.defdvips.def のどれを読むかを決めます。だから ドライバオプションを手で書かないでください——手書きの指定は、コンパイル方法を変えた瞬間に嘘になります。

ところが hyperref だけは別扱いが要ります(u)platex で DVI を作っているのに \usepackage{hyperref} とだけ書くと、ログには Package hyperref Info: Driver (default): hdvips. と出ます——dvips 向けの \special を書いてしまうのです。その DVI を dvipdfmx に渡すと dvipdfmx:warning: Unknown token "SDict" が並び、リンクもしおりも消えた PDF ができます。この経路では \usepackage[dvipdfmx]{hyperref} と明示してください。「ドライバは指定しない」は graphicx の話であって、DVI 経由の hyperref には当てはまりません(詳しくは「しおりとメタデータ」を参照)。

PDF のバージョンを指定する——\pdfminorversion はどのエンジンで効くか

\pdfminorversionpdfTeX 専用です。LuaTeX で書くと ! Undefined control sequence. になり、XeTeX でも同じエラーが出ます。LuaTeX は primitive を名前空間にまとめ直したので \pdfvariable minorversion=4 と書きます。XeTeX にはそもそも対応する primitive がなく、PDF を書いているのは xdvipdfmx のほうなので、-output-driver="xdvipdfmx -V 4" のようにドライバへ渡します。DVI 経由なら話は簡単で、dvipdfmx -V 4 paper.dvi です。既定値がどこから来るかも辿れます——TeX Live の pdftexconfig.tex\pdfminorversion = 5 を設定しており、それが「既定は PDF 1.5」の正体です。

ただし 2024 年時点で、この 3 通りを覚え分ける必要はだいぶ薄れました。LaTeX カーネルの新しい入口 \DocumentMetadata{pdfversion=2.0}\documentclass の前に置くと、pdflatex・lualatex・xelatex のいずれでも PDF 2.0 が出ます(実測で 3 エンジンすべて PDF version: 2.0)。\pdfminorversion が「マイナー番号だけ」を触るのに対し、pdfversion はメジャー番号ごと指定できます——\pdfminorversion=0 は PDF 1.0 になるだけで、2.0 には決して届きません。

エンジン・経路PDF バージョンの指定方法備考
\pdfminorversion=5pdfTeX(pdflatexLuaTeX/XeTeX では ! Undefined control sequence.
\pdfvariable minorversion=5LuaTeX(lualatex同じ綴りで compresslevel なども指定できます
dvipdfmx -V 4DVI 経由・XeTeXXeTeX では -output-driver="xdvipdfmx -V 4"
\DocumentMetadata{pdfversion=2.0}3 エンジン共通メジャー番号も指定可。\documentclass の前に置きます

PDF の圧縮——\pdfcompresslevel\pdfobjcompresslevel の実測

圧縮レベルを 9 まで上げても、6 との差は 2 バイトです。\lipsum[1-40] の 8 ページ文書で測ると、\pdfcompresslevel=0 が 79,508 バイト、=1 で 45,730、=6 で 43,323、=9 で 43,321。効くのは 0 → 1 の一歩だけで、そこから先は誤差の世界です。もう一つの \pdfobjcompresslevel はまったく別の仕組みで、ページ内容ではなく PDF のオブジェクト定義そのものをまとめて圧縮します(オブジェクトストリーム)。同じ文書で 43,321 → 41,019 バイト、約 5% がここから出ます。

ここに、この 2 つを繋ぐ罠があります。オブジェクトストリームは PDF 1.5 で導入された機能なので、古いビューア向けに \pdfminorversion=4 へ下げると、\pdfobjcompresslevel が黙って無効になります。正確には黙ってはおらず、ログにこう出ます——pdfTeX warning (Object streams): \pdfobjcompresslevel > 0 requires PDF-1.5 or greater. Object streams disabled now. 実測でも、\pdfminorversion=4 の出力は \pdfobjcompresslevel=0 の出力とバイト数まで一致します(どちらも 43,321 バイト)。「バージョンを下げたらファイルが少し太った」ときの原因はほぼこれです。dvipdfmx 側にも同じ関係があり、-V 4 を付けると 8,310 バイトの PDF が 10,055 バイトになります。圧縮の強さそのものは -z 0-z 9 で指定しますが、こちらも -z 6-z 9 の差は 6 バイトでした。

latex
% pdfTeX defaults, as set by TeX Live in pdftexconfig.tex
\pdfminorversion     = 5
\pdfcompresslevel    = 9   % 0 = off; 1 already captures most of the gain
\pdfobjcompresslevel = 2   % object streams; needs PDF 1.5 or later

% LuaTeX spells the same knobs differently
\pdfvariable minorversion    = 5
\pdfvariable compresslevel   = 9
\pdfvariable objcompresslevel = 2

出力された PDF の中身を目で読みたいときは、圧縮を全部止めるのが早道です。エンジンごとに primitive を書き分ける代わりに、\DocumentMetadata{uncompress} の 1 行で済みます——実測で 52,622 バイトの PDF が 91,347 バイトの、テキストエディタで開ける PDF になります。生成した PDF の演算子を確認したいときや、どのパッケージがどんなオブジェクトを差し込んでいるか追いたいときに使ってください。

[letterpaper] と書いたのに A4 で出る——用紙サイズはどこで決まるか

クラスオプションは PDF の用紙サイズを決めません。TeX Live 2024 で \documentclass[letterpaper]{article}pdflatex に通し、pdfinfo で見ると Page size: 595.276 x 841.89 pts (A4) と出ます。理由は素朴です——PDF の MediaBox を書くのは \pdfpagewidth\pdfpageheight であり、letterpaper はそこには触れず、版面(\textwidth など)だけを設定するからです。そして TeX Live の pdftexconfig.tex は起動時に \pdfpageheight = 297 true mm\pdfpagewidth = 210 true mm を設定済みです。つまりクラスは「文字を A4 の紙に載せるつもりで letter の版面を組んだ」状態になります。

確実に効かせる方法は 3 つあります。geometry を読み込む\usepackage[letterpaper]{geometry}\pdfpagewidth まで面倒を見ます)、primitive を直接書く\pdfpagewidth=8.5in — これは XeTeX でも受け付けます)、あるいは DVI 経由なら変換時に指定するdvipdfmx -p letter paper.dvi)。実測ではこの 3 つとも 612 x 792 pts (letter) になりました。そして 4 つめの、いちばん現代的な答えがあります——\DocumentMetadata{} を置くと、MediaBox は \pdfpagewidth ではなく \paperwidth\paperheight から書かれるようになります。つまりクラスオプションがようやく勝ちます。同じソースが、この 1 行の有無だけで A4 と letter に分かれるということです。移行時に気づかないと、ページ番号の位置がずれた程度では済みません。

terminal
# TeX Live 2024, same source, four ways of asking for US letter
$ pdflatex letter.tex     && pdfinfo letter.pdf  | grep "Page size"
Page size:       595.276 x 841.89 pts (A4)      # [letterpaper] alone: ignored

$ pdflatex geom.tex       && pdfinfo geom.pdf    | grep "Page size"
Page size:       612 x 792 pts (letter)         # \usepackage[letterpaper]{geometry}

$ latex letter.tex && dvipdfmx -p letter letter.dvi
Page size:       612 x 792 pts (letter)         # decided by the converter

$ pdflatex dm.tex         && pdfinfo dm.pdf      | grep "Page size"
Page size:       612 x 792 pts (letter)         # \DocumentMetadata{} present

フォントは埋め込まれているか——pdffonts の読み方

pdffonts paper.pdfemb 列がすべて yes なら、フォントは埋め込まれています。もう一つの見分け方が字面に出ていて、埋め込まれたフォントの名前には OREBYP+CMR10 のように 6 文字の大文字++ という接頭辞が付きます。これはサブセット化された——つまり文書で実際に使った字だけを切り出した——ことを示す印で、逆に CMR10 と裸の名前が並んでいたら、そのフォントは埋め込まれていない可能性が高い。冒頭の「Hello」の PDF を覗くと、埋め込まれたフォント記述子に /CharSet (/H/e/l/o/one) と書いてあります。H・e・l・o と、ページ番号の 1 だけ。サブセット化とはこういうことです。

terminal
$ pdffonts paper.pdf
name                          type       encoding  emb sub uni object ID
----------------------------- ---------- --------- --- --- --- ---------
OREBYP+CMR10                  Type 1     Builtin   yes yes yes      4  0
PTKKKD+CMTI10                 Type 1     Builtin   yes yes yes      5  0

# the same source through latex + dvipdfmx: Type 1C instead of Type 1
$ pdffonts paper-dvipdfmx.pdf
FUYUFD+CMR10                  Type 1C    Builtin   yes yes yes      4  0

# a font that was NOT embedded: bare name, emb = no
Helvetica                     Type 1     Standard  no  no  no       7  0

type 列にも情報があります。同じ「Hello」の 1 ページを比べると、pdflatex の出力は Type 1dvipdfmx の出力は Type 1C です。両者はまったく同じ Computer Modern の、まったく同じ 5 文字のサブセットですが、埋め込まれたフォントプログラムの大きさは 9,002 バイト対 720 バイト——12 倍以上の開きがあります。dvipdfmx は Type 1 を CFF(Compact Font Format、PDF では Type 1C)に再符号化してから入れるからで、冒頭で見た「DVI 版 2,145 バイト対 pdflatex 版 11,287 バイト」という総サイズの差も、ほぼこの一点で説明できます。LuaTeX と XeTeX は既定で Latin Modern の OpenType 版を使うので、こちらは CID Type 0C として入ります。どれも埋め込みは埋め込みで、印刷にも投稿にも問題ありません。

印刷所・投稿先に送る前に確認すること

確認は 2 コマンドで終わります。pdffonts でフォントが全部埋め込まれていることを見て、pdfinfo でページ寸法と PDF バージョンを見る。それだけです。よくある事故は決まっていて、embno が混じっている(外部の PDF 図版を取り込んだときに紛れ込みやすい)、A4 のつもりが letter になっている、投稿規定より新しい PDF バージョンになっている、の 3 つ。加えて pdfinfoPage size は各ページを見ているわけではないので、pdfinfo -f 1 -l 99 のようにページを指定して、途中で寸法が変わっていないかも確かめられます。

  • pdffonts paper.pdfemb 列がすべて yes か。名前に ABCDEF+ の接頭辞が付いているか。
  • pdfinfo paper.pdfPage size が意図どおりか、PDF version が投稿規定の範囲か。
  • 英文中心で手早く → 直接経路(pdflatex)。既定のまま何も考えなくて済みます。
  • OS のフォントを使いたい・Unicode 中心lualatex / xelatex(直接経路)。
  • 日本語で (u)platex → DVI 経由(dvipdfmx)。用紙は -p、PDF バージョンは -V で指定します。
  • EPS 資産が多い → DVI 経由なら中間ファイルを残さず取り込めます。直接経路でも自動変換は効きますが *-eps-converted-to.pdf が散らかります。