DVI / PS / PDF ワークフロー

同じ 1 ページの LaTeX 文書をコンパイルして、DVI は 956 バイト、dvips に通した PostScript は 169,769 バイトになりました。178 倍です。増えた分に新しい情報は一つもありません。違いは、DVI が 9 つのフォントを 名前で呼んでいる だけなのに対し、PostScript はその 9 つを 丸ごと抱えている ことだけです。この一点が、DVI という形式の性格も、PostScript が長らく中継地点だった理由も、そして PDF への三つの経路が今どう違うのかも、ほとんど説明してしまいます。

DVI ファイルの中身——箱と、フォントの名前だけ

DVI に入っているのは 位置とフォント参照だけ で、文字の形は一つも入っていません。TeX Live に付属する dvitype にかけると、その様子がそのまま読めます。fntdef1 27: cmr10 は「27 番として cmr10 というフォントを使う」という 宣言 にすぎず、setchar49 は「いま選ばれているフォントの 49 番の文字をここに置け」という 命令 にすぎません。49 は文字コードで、1 のことです。あいだにある pushdown4right3 は座標を動かす操作です。つまり DVI は「どこに、どのフォントの、何番の文字を置くか」を並べた指示書であり、その文字が実際にどんな形をしているかは一切知りません。

terminal
dvitype -output-level=4 -page-start='*' doc.dvi

# numerator/denominator=25400000/473628672
# magnification=1000;       0.00006334 pixels per DVI unit
# Postamble starts at byte 723.
# Font 27: cmr10---loaded at size 655360 DVI units
# Font 37: cmbx12 scaled 1200---loaded at size 943718 DVI units
#  (this font is magnified 120%)
# 145: fntdef1 37: cmbx12
# 167: fntnum37 current font is cmbx12
# 168: setchar49 h:=4063232+530841=4594073, hh:=291

これは「DVI が小さい理由」であると同時に「DVI が単体では見られない理由」でもあります。strings を 956 バイトのファイルにかけると、読めるものは本文の単語と、cmr10cmmi7cmsy10cmex10 といったフォント名の羅列だけです。ですから DVI を渡された側は、同じ名前のフォントを自分の環境で見つけられなければ、そのページを再現できません。TeX が 1980 年代に狙ったのはまさにこの分業でした——組版の結果は 装置に依存しない(device-independent) 中間形式にとどめ、実際の紙やスクリーンに合わせる作業は後段のドライバに任せる。DVI という名前そのものがその設計方針です。

DVI が持つもの/持たないもの内容
setchar / put文字番号の指定。字形そのものは入らない
fntdef / fntnumフォントを名前・設計サイズ・チェックサムで参照するだけ
push / pop / down / right箱を積む座標操作。TeX の組版結果そのもの
xxx (\special)DVI が理解しない指示の逃がし口。中身はドライバ任せ
glyph outlines入らない。 だからフォントが無いと表示できない
color, paper sizeDVI 本体には無い。すべて \special 経由で運ばれる

なぜ DVI はすべてを 1/65536 ポイントで測るのか

答えは dvitype の冒頭にそのまま書かれています。numerator/denominator=25400000/473628672 の分母 473628672 は 65536 × 7227 で、7227 ポイントはちょうど 100 インチです。つまり DVI の 1 単位 = 1/65536 ポイント で、これは TeX が内部で使う整数単位「スケールドポイント(sp)」と同じものです。だから先ほどの cmr10---loaded at size 655360 DVI units は 10 × 65536、すなわち 10pt をそう書いただけですし、cmbx12 scaled 1200 の 943718 は 12pt × 1.2 = 14.4pt に 65536 を掛けた値です。TeX は浮動小数点を一切使わず、この整数だけで版面を組みます——同じソースがどの機械でも 1 sp 単位まで同じ結果になるのは、そのためです。

この形式を設計したのは、クヌースの学生だった David R. Fuchs で、1979 年のことです。仕様は 1982 年 10 月の TUGboat 誌(第 3 巻第 2 号)に「The format of TeX’s DVI files」として発表されました。40 年以上たったいまも、doc.dvi の先頭バイトは 247(pre 命令)、次のバイトは 2(DVI の識別番号)のままです。互換性が壊れなかったのは、形式が装置について何も仮定しなかったからです——想定していた出力機はとうに消えましたが、何も想定していない部分だけが残りました。

PostScript が長らく中継地点だった理由——\special の中身

DVI は装置に依存しない代わりに、色も、画像も、回転も表現できません。そこで Fuchs は逃がし口を一つだけ用意しました。\special{...} です——中身は解釈せず、ドライバにそのまま渡す。そしてこの逃がし口を最初に埋めたのが PostScript でした。実際、\usepackage{graphicx} を読み込んで \rotatebox{30}{rotated} と書き、latex で DVI を作って中を覗くと、生の PostScript がそのまま入っています。DVI 側は「これは PostScript だ」ということすら知りません。ただ文字列を運んでいるだけです。

terminal
# what latex actually wrote into the DVI for \rotatebox and \textcolor
dvitype -output-level=4 -page-start='*' spec.dvi | grep xxx

#  88: xxx 'header=l3backend-dvips.pro'
# 116: xxx 'papersize=614.295pt,794.96999pt'
# 247: xxx 'color push rgb 1 0 0'
# 347: xxx 'ps: gsave currentpoint currentpoint translate
#            30 neg rotate neg exch neg exch translate'
# 449: xxx 'ps: currentpoint grestore moveto'

ここから先はもう一本道でした。DVI の逃がし口が PostScript で埋まっている以上、その DVI を最後まで解釈できるのは PostScript を理解する装置だけです。1980 年代半ばに Adobe の PostScript を積んだレーザープリンタが事実上の標準になると、dvips が DVI を PostScript に翻訳する経路が定着します。翻訳のときに dvips はフォントを 埋め込みます——生成された .ps の先頭には %%DocumentFonts: CMBX12 CMR10 CMEX10 CMSY7 CMR7 CMMI10 CMMI7 CMR5 CMSY10 という一行があり、本文にはその 9 つぶんの %%BeginFont: ブロックが続きます。冒頭の 956 バイトと 169,769 バイトの差は、この 9 つです。

PostScript は表舞台からは退きましたが、指紋はいまも残っています。DVI から直接 PDF を作る dvipdfmx の実行ファイルには、インラインの PostScript を実行するための小さなインタプリタが入っていて、Stack not empty after execution of inline PostScript code. という診断メッセージまで持っています。先ほどの ps: 特殊命令は、だからこの経路でも回転として正しく反映されます。--help を見れば -D template PS->PDF conversion command line template [none] という項目もあり、手に負えない PostScript は外部(Ghostscript)に投げる設計です。さらに、画像の大きさを記録する .xbb ファイルの中身は %%BoundingBox: 0 0 8 8 ——PostScript のコメント記法そのものです。

今の三つの経路——どれを選ぶと何が変わるか

欧文なら pdflatex などで直接 PDF、日本語なら (u)platexdvipdfmx、PSTricks や古い EPS 一式を抱えているなら latexdvipsps2pdf —— これが実務上の分岐のすべてです。ただし「同じ文書だから同じ PDF が出る」わけではありません。冒頭の文書を三つの経路で PDF にすると、direct.pdf が 85,509 バイト、viadvi.pdf が 14,693 バイト、viaps.pdf が 17,851 バイトになりました。pdffonts で覗くと理由はすぐわかります——pdfTeX は Type 1 のまま埋め込み、dvipdfmx と Ghostscript は圧縮形式の Type 1C に変換して埋め込むからです。中身の見た目は同じで、容量だけが 6 倍違います。

経路通る形式向いている場面
pdflatex / lualatex.tex → PDF一段で済む。欧文の既定
xelatex.tex → XDV → PDFシステムフォント。XDV は隠れているだけ
dvipdfmx.tex → DVI → PDF日本語((u)pLaTeX)の定番。PNG・JPEG も直接置ける
dvips + ps2pdf.tex → DVI → PS → PDFPSTricks、EPS 一式、PostScript 前提の入稿
terminal
# 1. straight to PDF
pdflatex doc.tex

# 2. via DVI (the Japanese route)
uplatex doc.tex && dvipdfmx doc
#   doc.dvi -> doc.pdf
#   [1]
#   14692 bytes written

# 3. via PostScript
latex doc.tex && dvips doc -o doc.ps && ps2pdf doc.ps

# in practice latexmk drives all three for you

XeLaTeX は本当に DVI を飛ばしているのか

飛ばしていません。xelatex -no-pdf doc.tex を走らせると Output written on doc.xdv (1 page, 2476 bytes). と出て、doc.xdv が残ります。中を見ると先頭バイトは 247 ——doc.dvi とまったく同じ pre 命令です。違うのは次の 1 バイトだけで、DVI が 2 なのに対し XDV は 7。つまり XeTeX が書いているのは 拡張 DVI(XDV) であって、そこから PDF を作っているのは xdvipdfmx です。TeX Live 付属の README にも「In the installation, dvipdfmx is a symlink to xdvipdfmx.」とあり、実際 dvipdfmxextractbb も同じ実行ファイルを指しています。「直接 PDF を出す」という言い方は、DVI 段を隠したという意味であって、無くしたという意味ではありません。

terminal
xelatex -no-pdf doc.tex
# Output written on doc.xdv (1 page, 2476 bytes).

# same container, different id byte:
#   doc.dvi   byte 0 = 247 (pre)   byte 1 = 2
#   doc.xdv   byte 0 = 247 (pre)   byte 1 = 7

xdvipdfmx doc.xdv          # this is what xelatex runs for you

Cannot determine size of graphic ... (no BoundingBox) が出たら

DVI 経路で PNG や JPEG を貼ろうとした人がまず当たるのがこれです。実際のメッセージは ! LaTeX Error: Cannot determine size of graphic in sample.png (no BoundingBox). で、原因は画像でも dvipdfmx でもなく、graphicx のドライバ設定 にあります。latex は PDF を作らないので、画像の寸法を自分で読む能力を持ちません。既定ドライバの dvips は PostScript の %%BoundingBox 行しか読めず、PNG のヘッダは読めないのです。解決は二つ。\usepackage[dvipdfmx]{graphicx} とドライバを明示するか、extractbb sample.png を先に走らせて %%BoundingBox: 0 0 8 8 を書いた .xbb を作っておくかです。pdflatex でこのエラーが出ないのは、pdfTeX 自身が PNG のヘッダを読めるからにすぎません。

terminal
# ! LaTeX Error: Cannot determine size of graphic in sample.png (no BoundingBox).

# fix 1 - name the driver in the preamble:
#   \usepackage[dvipdfmx]{graphicx}

# fix 2 - write the bounding box out first:
extractbb sample.png
cat sample.xbb
#   %%Title: sample.png
#   %%Creator: extractbb 20240305
#   %%BoundingBox: 0 0 8 8
#   %%HiResBoundingBox: 0.000000 0.000000 8.000000 8.000000

まとめると、判断の順序はこうです。まず出力形式ではなく言語とパッケージで決める ——日本語なら (u)platexdvipdfmx、PSTricks があるなら PostScript 経由、それ以外なら直接 PDF。次に画像形式をそろえる ——DVI 経路なら PDF か EPS、あるいは extractbb を通した PNG/JPEG。最後にドライバ名を明示する ——graphicx にも hyperref にも [dvipdfmx] を書いておけば、後日その文書を別の経路でビルドした人が同じ罠を踏まずに済みます。DVI 側は依然として、自分が何色でどんな図を運んでいるのかを知らないままです。