画像化(TeX2img / dvisvgm / ImageMagick)

LaTeX で描いた図は、書き出す前の時点ですでにベクタ画像です——軸は曲線として、ラベルはフォントのグリフとして存在しています。ところが 画像として書き出す段になると、その情報はあっけなく捨てられます。しかも捨てるかどうかを決めているのは、多くの人が思うよりずっと手前——DVI を経由するか PDF を経由するかという一点です。dvisvgm のマニュアルにはこう書かれています。PDF や PostScript に含まれる文字は、常にパス要素に変換される。 つまり PDF を経由した瞬間、図の中の文字は文字であることをやめます。このページでは standalonepdfcropdvisvgm、そしてラスタ化までを、その一点を軸に並べます。

図だけを1ページにする——standalone クラス

書き出しの第一歩は、用紙を図の大きさに合わせてしまうことです。Martin Scharrer の standalone クラス(TeX Live 2024 収録は v1.3b)は、中身の寸法をそのまま紙面の寸法にします。手元で \documentclass[border=2pt,preview]{standalone}$\displaystyle \int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi}$ だけを入れて pdflatex に通したところ、できあがった PDF のページサイズは 85.779 × 27.993 pt——数式の外形そのものでした。border を書かないときの既定は上下左右 0pt です。クラス自身は 0.50001bp(丸め誤差でグリフの端が切れるのを防ぐ値)を持っていますが、同梱の standalone.cfg がオプション処理の直前に \standaloneconfig{border=0pt} で上書きします。TikZ の絵なら \documentclass[tikz,border=2pt]{standalone}tikzpicture\begin{document} 直下に置くだけ。文章を含む断片を折り返して切り出したいときは varwidth=10cm のように幅を与えます。standalone が内部で使っている preview パッケージは、もとは Emacs の AUCTeX に付属する preview-latex のためのもので、「文書の一部だけを切り出して組む」という発想はそこから来ています。

latex
% fig.tex — a figure that is its own page
\documentclass[tikz,border=2pt]{standalone}
\begin{document}
\begin{tikzpicture}
  \draw[thick,->] (0,0) -- (3,0) node[right] {$x$};
  \draw[thick,->] (0,0) -- (0,2) node[above] {$y$};
  \draw[blue,thick] (0,0) .. controls (1,2) .. (3,1);
\end{tikzpicture}
\end{document}

既にある PDF の余白を切る——pdfcrop

図が普通の文書の中にあって作り直せないときは、pdfcrop が使えます。Heiko Oberdiek の Perl スクリプト(TeX Live 2024 収録は v1.42)で、ページの中身の外接矩形を求めてトリムボックスを縮めます。実測では、レターサイズ 612 × 792 pt の 1 ページ文書が pdfcrop doc.pdf doc-crop.pdf345 × 539 pt になりました。余白をゼロにしたくないときは --margins 10 のように指定でき、これで各辺に 10 pt ずつ戻って 365 × 559 pt になります。注意点がひとつ。pdfcrop は Ghostscript を呼びます——スクリプト冒頭にも「Requirements: Perl5, Ghostscript」と書かれています。gs が入っていない環境では動きません。なお、書き出す図そのものを standalone で作れるなら pdfcrop は要りません。切るより、最初から余白を作らないほうが速く、精度も上です。

terminal
pdfcrop doc.pdf doc-crop.pdf              # 612x792 pt becomes 345x539 pt
pdfcrop --margins 10 doc.pdf doc-crop.pdf # keep 10 pt on every side
pdfcrop --hires doc.pdf doc-crop.pdf      # use the high-resolution bounding box

SVG にする——dvisvgm と、DVI 経路が効く理由

Web に出す図なら SVG が第一候補です。Martin Gieseking の dvisvgm(TeX Live 2024 収録は 3.2.2)は DVI・EPS・PDF を SVG に変換しますが、入口によって出てくるものが変わります\def\pgfsysdriver{pgfsys-dvisvgm.def}\documentclass の前に置いて latex で DVI を作り、dvisvgm fig.dvi を走らせたところ、出てきた SVG には軸と曲線が <path> として、$x$ $y$ $y=f(x)$ のラベルが <text> 要素として入っていました——文字は文字のまま、<defs> に埋め込まれたベクタフォントを参照しています。ところが PDF から入るとこうはなりません。dvisvgm のマニュアルは「PDF や PostScript に含まれる文字は常にパス要素に変換される」と明言しています。実際、poppler の pdftocairo -svg で同じ図の PDF を SVG にしたら、<text> 要素はゼロでした。

latex
% figdvi.tex — the DVI route, which keeps the labels as real text
\def\pgfsysdriver{pgfsys-dvisvgm.def}
\documentclass[tikz,border=2pt]{standalone}
\begin{document}
\begin{tikzpicture}
  \draw[thick,->] (0,0) -- (3,0) node[right] {$x$};
  \draw[blue,thick] (0,0) .. controls (1,2) .. (3,1);
\end{tikzpicture}
\end{document}
terminal
latex figdvi.tex        # produce a DVI, not a PDF
dvisvgm figdvi.dvi      # SVG with <text> elements and vector paths

# the PDF route needs a helper and outlines all text:
dvisvgm --pdf fig.pdf   # requires mutool with Ghostscript 10.01 or newer

PDF から SVG を作りたい場合には、もうひとつ現実的な障害があります。dvisvgm --pdf は昔 Ghostscript に PDF を読ませていましたが、マニュアルによれば Ghostscript 10.01.0 で PDF インタプリタが刷新されたためこの方法は使えなくなり、代わりに MuPDF の mutool を探すようになりました。この記事の環境(Ghostscript 10.03.0、mutool なし)で dvisvgm --pdf fig.pdf を実行すると ERROR: can't retrieve number of pages from file fig.pdf で止まります。つまり PDF 経路は追加の依存を要求します。しかも Ghostscript が処理する場合はマニュアルの言うとおり文字が必ずアウトライン化されるので、DVI 経路を選ぶ理由は二重にあるわけです。もうひとつ覚えておくと得なのが -e--exact-bbox)で、既定では TFM の寸法から外接矩形を決めるため、字形が実際にはみ出していると端が切れることがあります。図の縁で文字が欠けたら、まずこれを付けてください。

SVG のフォントをどうするか——--font-format--no-fonts

<text> を残せるのは良いことですが、そのままではもうひと工夫が要ります。dvisvgm の既定は SVG フォント<font><glyph><defs> に埋め込む形式)で、これは仕様としては存在するものの、対応していない環境が多いのが実情です——dvisvgm 自身のマニュアルも --no-fonts の説明で「SVG フォントに対応しない多くのアプリケーションと、より互換性が高くなる」と書いています。同じ TikZ の図で3通りを比べると、既定の SVG フォントが 5,790 バイト--font-format=woff24,815 バイト@font-facesrc にデータ URI で WOFF2 を埋め込む形)、--no-fonts8,290 バイト<text> 要素はゼロ、グリフはすべて <path> になりました。実務の判断は単純です。Web に置くなら --font-format=woff2——文字は選択でき、ブラウザは確実に描けます。Illustrator・Inkscape・PowerPoint など他のソフトに渡すなら --no-fonts——サイズは増えますが、見た目が崩れる余地がありません。

オプションSVG 内の文字同じ図での大きさ向いている用途
--font-format=svg<text> + SVG フォント(既定)5,790 バイト対応ソフトが限られるため実務では非推奨
--font-format=woff2<text>@font-face に WOFF2 を埋め込み4,815 バイトWeb 公開。文字が選択でき最も小さい
--no-fonts<text> は無し。全グリフが <path>8,290 バイト他の描画ソフトへ渡すとき。表示が崩れない

PNG にする——スライドや古い環境のために

SVG を受け付けない相手はまだ多く、そのときは PNG に落とします。解像度は -r で指定し、300 dpi を下限、印刷や高精細ディスプレイ向けなら 600 dpi が目安です。上で作った 85.779 × 27.993 pt の数式 PDF を 600 dpi でラスタ化したところ、Ghostscript(gs -sDEVICE=pngalpha -r600)でも poppler の pdftocairo -png -r 600 -transp でも 715 × 233 ピクセル前後の透過 PNG が得られました。72 pt = 1 インチなので、85.779 ÷ 72 × 600 ≒ 715。この計算が合っているかどうかが、解像度指定が効いているかの確認になります。透過が要らなければ -sDEVICE=png16mpdftocairo なら -transp を外す)で白背景になります。多くの解説は ImageMagick の magick -density 300 in.pdf out.png を勧めますが、ImageMagick は PDF の描画を Ghostscript に委譲します。TeX Live にも含まれていませんし、この記事の環境にも入っていなかったので動作は確認していません。実際に描くのが Ghostscript なら、直接呼ぶほうが依存が一段少なくて済みます。

terminal
# Ghostscript, transparent background, 600 dpi
gs -q -dNOPAUSE -dBATCH -sDEVICE=pngalpha -r600 \
   -dTextAlphaBits=4 -dGraphicsAlphaBits=4 \
   -sOutputFile=eq.png eq.pdf

# poppler, same result, shorter to type
pdftocairo -png -r 600 -transp -singlefile eq.pdf eq

1コマンドで PNG まで——standalone の convert=--shell-escape

毎回3コマンド打つのが面倒なら、standalone のクラスオプション convert= に任せられます。組版が終わった直後に、クラス自身が変換コマンドをシェルに投げます。バックエンドは imagemagick(既定)・ghostscriptpdf2svg から選べ、解像度の既定値は density=300 です。ImageMagick が無い環境でも ghostscript を指定すれば動きます——実際に \documentclass[border=2pt,convert={ghostscript,density=600,outext=.png}]{standalone} として pdflatex --shell-escape eqc.tex を走らせたところ、ログに gs -dSAFER -dBATCH -dNOPAUSE -sDEVICE=png16m -r600 -sOutputFile=eqc.png eqc.pdf と出て、715 × 233 の PNG が 1 コマンドで出てきました。ただし外部コマンドを起動するので、制限付きシェルエスケープでは足りず --shell-escape が必要です。図が多いプロジェクトでは、変換をビルドスクリプト側に置くほうが、--shell-escape を常用しなくて済むぶん安全です。

latex
% eqc.tex — build the PNG in the same run
\documentclass[border=2pt,convert={ghostscript,density=600,outext=.png}]{standalone}
\usepackage{amsmath}
\begin{document}
$\displaystyle \int_{-\infty}^{\infty} e^{-x^2}\,dx = \sqrt{\pi}$
\end{document}

% then: pdflatex --shell-escape eqc.tex

ここまでの流れ——断片だけ組んで、切り詰めて、ベクタかラスタに落とす——を GUI にまとめたのが TeX2img です。数式を貼り付けて形式と解像度を選ぶだけで、透過とアンチエイリアスまで面倒を見てくれます。単発の書き出しには圧倒的に速い一方、図が何十枚もあって版を重ねる文書では、この節までのコマンドをビルドに組み込むほうが結局は楽です。なお TeX2img は独立したアプリケーションで TeX Live には含まれず、この記事の環境にも入っていないため、挙動の確認はしていません。

どの形式で書き出すか

  • Web に載せる図 → SVG。latex で DVI を作り dvisvgm --font-format=woff2 を通す。文字が <text> のまま残り、拡大しても劣化しません。
  • LaTeX 文書に取り込み直す図 → PDF のまま。standalone で作れば余白ゼロで、\includegraphics にそのまま渡せます。
  • スライド・Word・SNS → PNG。gs -sDEVICE=pngalpha -r600pdftocairo -png -r 600 -transp。背景を白にしたいときだけ透過を外します。
  • Illustrator や Inkscape で手を入れたい図 → SVG を --no-fonts で。フォントが無い環境でも形が変わりません。
  • 写真を含む図 → 写真部分だけ JPEG、線画とラベルは SVG か PNG。全体を JPEG にすると文字の縁が汚れます。
  • dvisvgm --pdf が動かないとき → まず DVI 経路に切り替える。それでも PDF から作りたいなら MuPDF の mutool を導入します。