TeX Alchemist Online

TeX のこと,フォントのこと,Mac のこと

非Illustratorで作ったPDF図版のサイズ自動取得

この記事は TeX & LaTeX Advent Calendar 2025 の14日目の記事です。13日目はBeige a.k.a MaruzenLemonさんでした。 15日目はabenoriさんです。


【目次】


TeX 文書に貼り込む図版作成はどうする?

例えば数学教材を作成していると,放物線や楕円,三角形などの図を文書中に貼り込みたいシーンが多々あります。そういうとき,図版の作成法としては,次のような選択肢があります。

1. TikZ / MetaPost / Asymptote / PSTricks 等で頑張る

  • メリット:無償,後からのパラメータ調整が楽
  • デメリット:凝った図版を座標指定で作っていくのは大変

2. Adobe Illustrator を使う

  • メリット:商用製品ならではのGUIの使いやすさ
  • デメリット:高額のサブスク商品

3. その他のドローツール(Inkscape等)を使う

  • メリット:無償または安価,GUIで作図できる
  • デメリット:Adobe Illustrator に比べると機能面や使い勝手で劣る点が多い

新星:無償化された Affinity

かねてより「Illustratorの対抗馬」として知られていた Affinity Designer という商用ドローツールが,今年,Affinity Photo(画像編集ツール),Affinity Publisher(DTPツール)と合体した上で基本機能が無償提供(Windows版,macOS版)され,大きなニュースとなりました。

news.mynavi.jp

ascii.jp

これにより,Adobe Illustratorなしでカンタン(←今年の TeX & LaTeX Advent Calendar のテーマ)に凝った図版が作れる新たな選択肢が生まれたことになりました。

実際にやってみた

Affinity も,Illustrator 同様に複数ページからなる図版が作成できます。

普通に保存すると,独自形式の *.af というファイル形式で保存されるため,ファイル→エクスポート→書き出し から,PDF形式で書き出します。

これをLaTeX文書中から \includegrapihcs で読み込みます。

\includegraphics{fig.pdf}% page 1
\includegraphics[page=2]{fig.pdf}% page 2

ところが,このように読み込むと,図版そのものではなく,図版の周囲の余白部分を含む,ページ全体がバウンディングボックスとして読み込まれてしまいます。見やすいように

\setlength\fboxsep{0pt}
\fbox{\includegraphics{fig.pdf}}

として,\includegraphics で読み込まれた図版部分を枠線で囲ってみると,次のように余白も含めたページ全体が図版として貼り込まれてしまっていることが分かります。

これは,PDFに埋め込まれた5つのナントカBoxに起因します。

PDFの5つのナントカBox

PDFの各ページには,次の5つのボックスの数値が(明示的に or 暗黙に)与えられています。

  • MediaBox: 用紙(等の媒体)に実際に印刷される領域。一般の人が行う(非専門的な)印刷行程ではこのボックスのみが指定(および使用)されることが多く,その場合は「用紙サイズ」を表す。
  • CropBox: 最も一般的な意味での,「そのページの(外形を込めた)内容」を表す領域。
  • BleedBox: 専門的な印刷工程における,「断ち落としサイズ」に相当する領域。
  • TrimBox: 専門的な印刷工程における,「仕上がりサイズ」に相当する領域。
  • ArtBox: PDF 文書を画像として用いる場合の,「その画像の(外形を込めた)内容」を表す領域。

また,これらが省略された場合は,次のような規則で決まります。

  • MediaBox の値は必ず明示される。
  • CropBox の値が明示されていない場合は,MediaBox と同じと解釈される。
  • BleedBox,TrimBox,ArtBox の値が明示されていない場合は,CropBox と同じと解釈される。

通常,ビューアアプリで開いたときには CropBox (非明示の場合はMediaBox)の領域がページとして表示されるようになっています。

\includegraphics とナントカBox

\includegraphics においては,

\includegraphics[pagebox=artbox]{fig.pdf}

のようにして,どのボックスをバウンディングボックスとして扱うかを指定できます。省略時は CropBox が用いられます。

※かつてのdvipdfmxでは,省略時の複雑な選択律がありましたが,現在では LuaTeX などと同様に「デフォルトは常にCropBox」という単純な選択律になりました。

qiita.com

Illustrator 図版と5つのBox

Illustrator で作成したPDF図版のBoxの数値を確認してみましょう。Poppler に付属する pdfinfo コマンドで確認できます。

$ pdfinfo fig_illustrator.pdf
Page    1 MediaBox:     0.00     0.00   532.92   745.51
Page    1 CropBox:      0.00     0.00   532.92   745.51 
Page    1 BleedBox:     0.00     0.00   532.92   745.51
Page    1 TrimBox:      8.50     8.50   524.41   737.00
Page    1 ArtBox:      58.26   537.85   235.92   672.80

Page    2 MediaBox:     0.00     0.00   532.92   745.51
Page    2 CropBox:      0.00     0.00   532.92   745.51
Page    2 BleedBox:     0.00     0.00   532.92   745.51
Page    2 TrimBox:      8.50     8.50   524.41   737.00
Page    2 ArtBox:      43.26   552.13   182.57   688.73

この結果を見ると,ArtBoxの値として,MediaBoxよりも内側のボックスが指定されていることが分かります(実際に調べてみると,この5つのボックスはすべて明示指定されています)。これは,「ページ内の図版が存在する部分のみ」の外接長方形の座標(左下x座標,左下y座標,右上x座標,右上y座標)を示しています。よって,図版部分だけを LaTeX 文書に取り込みたいときは,

\includegraphics[pagebox=artbox]{fig.pdf}

として,ArtBoxをバウンディングボックスとして指定すればよいわけです。

Affinity 図版と5つのBox

一方,Affinity で生成したPDF図版についてもボックスを確認してみます。

$ pdfinfo fig_affinity.pdf
Page    1 MediaBox:     0.00     0.00   595.28   841.89
Page    1 CropBox:      0.00     0.00   595.28   841.89
Page    1 BleedBox:     0.00     0.00   595.28   841.89
Page    1 TrimBox:      0.00     0.00   595.28   841.89
Page    1 ArtBox:       0.00     0.00   595.28   841.89

Page    2 MediaBox:     0.00     0.00   595.28   841.89
Page    2 CropBox:      0.00     0.00   595.28   841.89
Page    2 BleedBox:     0.00     0.00   595.28   841.89
Page    2 TrimBox:      0.00     0.00   595.28   841.89
Page    2 ArtBox:       0.00     0.00   595.28   841.89

となり,すべてのボックス値が一致しています。実際に調べてみると,MediaBoxのみが明示指定されており,他の4つのボックスはMediaBoxの値へとフォールバックされています。

Illustrator図版のようにArtBoxが適切に明示指定されていないため,Affinityで作成した図版をLaTeX文書に貼り込む際,「余白を除く図版の中身」のみを貼り込むことができず,困ってしまいます。

解決策

解決策①:pdfcrop

最も簡単な方法は,TeX Live に付属する pdfcrop を使うことでしょう。

$ pdfcrop fig.pdf
PDFCROP 1.42, 2023/04/15 - Copyright (c) 2002-2023 by Heiko Oberdiek, Oberdiek Package Support Group.
==> 2 pages written on `fig-crop.pdf'.

のように余白を切り落としたPDFを生成してくれます。これを \includegraphics で読み込めばよいわけです。

ただし,欠点としては,図版を更新するたびに毎回 pdfcrop をかけ直さなければならない という点です。これを忘れると,古い図版が更新されずにLaTeX文書に取り込まれてしまいます。また,

  • Affinityの元データ fig.af
  • Affinityからエクスポートしたfig.pdf
  • それを pdfcrop にかけたfig-crop.pdf

という3ファイルが存在する状態になるというのも,ファイル管理上ちょっと面倒です。

解決策②:bbオプションの手動指定

pdfcrop は,内部的に Ghostscript と pdfTeX を呼び出しています。まず Ghostscript を使って「図版のギリギリのサイズ」を取得し,次に pdfTeX を使ってページ位置をずらした新たなPDF文書を生成する,という手順で fig-crop.pdf を生成しています。

いちいちcropされたPDFを生成しなくても,バウンディングボックスの数値のみ取得すれば,\includegraphics にその値を伝えられます。

$ gs -sDEVIEC=bbox -dNOPAUSE -dBATCH -c '<< /WhiteIsOpaque true >> setpagedevice' -dFirstPage=2 -dLastPage=2 -f fig.pdf 2>&1 >/dev/null
%%BoundingBox: 38 675 178 812
%%HiResBoundingBox: 38.155921 675.053979 177.479995 811.655975

-dFirstPage=2 -dLastPage=2 の部分は取得したいページ番号に呼応して変化させます。)

なお,この -c '<< /WhiteIsOpaque true >> setpagedevice' というオプションは,白い部分も図版の一部と見なすという意味です。図版にマージンを設けたいとき,図版の白長方形を最下位レイヤーに敷くことがあり,これを有効に機能させるために指定しています。

こうして得られた HiResBoundingBox の値を \includegraphicsbb オプションに指定します。

\includegraphics[page=2,bb=38.155921 675.053979 177.479995 811.655975]{fig.pdf}

こうすれば,pdfcrop を使わずに適切なバウンディングボックスを指定できます。

ただし,この方法もやはり,図版を更新するたびに毎回バウンディングボックスの数値を手動更新せねばならないというのが面倒です。

解決策③:xbbファイル(dvipdfmxの場合)

fig.pdf に対して

$ gs -sDEVIEC=bbox -dNOPAUSE -dBATCH -c '<< /WhiteIsOpaque true >> setpagedevice' -dFirstPage=1 -dLastPage=1 -f fig.pdf 2>fig.xbb >/dev/null

のようにして fig.xbb を生成しておくと,\includegraphics{fig.pdf} において,そのバウンディングボックス情報が読み取られて使用されます。ただし,この方法は,

  • (u)pLaTeX + dvipdfmx のワークフローならば使えるが,LuaLaTeX だと使えない(graphicx パッケージの LuaTeX 用ドライバは xbb ファイルを参照しないため)
  • 図版更新のたびに xbb ファイルの更新が必要で,面倒で忘れやすい
  • xbb ファイルが邪魔
  • PDF図版の2ページ目以降に対しては使えない

などの欠点があり,あまりお勧めできません。

解決策④:自動的にGhostscriptが呼び出されるコマンドを作る

そこで,これらの欠点を,

コンパイル時に Ghostscript を自動的に呼び出してバウンディングボックスを動的に取得する\includegraphics のラッパーコマンドを作る

という方針で解決しようと考えます。

dvipdfmx における実装方針

TeX Live 2025 の dvipdfmx.def を見てみると,次のように書かれています。

\def\Gread@extractbb@aux#1{%
  \ifeof\@inputcheck
    \immediate\openin\@inputcheck=%
      "|extractbb %
      \ifx\Gin@page\@empty\else -p \Gin@page\GPT@space\fi
      \ifx\Gin@pagebox\@empty\else -B \Gin@pagebox\GPT@space\fi
      -O \Gin@base\Gin@ext"%
  \fi
  \Gread@true
  ...(後略)
}

ここで extractbb コマンドを呼び出すことで,PDFの指定されたボックスをその場で動的に取得しています。Affinityで作成された図版の場合,どのボックスでもMediaBoxと同じなので,これでは \Gin@pagebox をどのように指定しても,図版のギリギリサイズを切り出すことはできません。そこで,この部分を Ghostscript を呼び出すように書き換えたバージョンを別に定義し,それを呼び出すような専用のラッパーコマンド \includegraphicsGS を定義します。

LuaLaTeX における実装方針

LuaLaTeXの場合,\directlua の中で io.popen で Ghostscript を呼び出し,その出力を Lua でパースして,取得した値を bb= オプションとして付与して\includegraphics の呼び出すという方針で \includegraphicsGS を実装します。

実装結果

dvipdfmx, LuaLaTeX のそれぞれについて,上記方針で実装したものがこちらです。

github.com

セキュリティ上の注意

TeX Live のデフォルトの texmf.cnf では,shell_escape_commands のところに extractbb は指定されていても gs は指定されていないため,通常の -shell-restricted モードでは,\openinio.popen() で Ghostscript を起動できません。texmf.cnfshell_escape_commands に gs を追記するか,LaTeXエンジンの起動オプションに -shell-escape を加える必要があります。

テストソース

\setlength\fboxsep{0pt}
あいうえお\par\fbox{\includegraphicsGS{fig.pdf}}\par\fbox{\includegraphicsGS[page=2]{fig.pdf}}\par
かきくけこ

出力結果

余白をカットし図版をギリギリのサイズで切り出した,所望のバウンディングボックスが正しく取得できています。これで教材作成がカンタンになりますね!