LaTeXで大規模な文書も幸せに作りたい

2023-12-23 (updated: 2023-12-27)  #LaTeX 

本稿はTeX & LaTeX Advent Calendar 2023の23日目の記事です。22日目はKosei Watanabeさんでした。24日目はk16shikanoさんです。

皆さんは、LaTeXで大規模な文書を作成したことがありますか。ここで大規模な文書とは、A4用紙に常識的な基本版面設定(天地左右の余白や文字サイズ、字間と行間など)で数百ページにおよぶような文書を想定しています。このような文書の作成は大変です。なにもLaTeXに限った話ではありません。使う道具はConTeXtだろうがZzTeXだろうがTypstだろうがSATySFiだろうがMS WordだろうがMarkdownだろうがプレーンテキストだろうが紙とペンだろうが、純粋に中身に意味のある文章を書くのが大変です。もちろん、出版や学位審査等のために、品質も求められるケースがほとんどでしょうからなおさらです1

本文を書く大変さについてはそれぞれの著者自身で頑張ってもらうほかありませんが2、それ以外の部分ではLaTeXのようなツールの使い方によって強力な支援を受けることが可能です。大規模な文書の作成時には、数ページから十数ページ程度の小さな文書の作成ではほとんど気にならないようなところでも問題が発生します。いくつか具体的に挙げてみます。

  • 一貫性の維持が難しい。例えば表記ゆれの問題
  • エラー発生時のデバッグが難しい。純粋にソースの量が多いので問題の発生箇所の探索範囲が広い。また、LaTeXでのタイプセットに時間がかかる上、エラー探しの重要なヒントであるログも巨大になりやすい
  • バージョンや差分の管理が難しい。数百ページの原稿の一段落が変わったぐらいだと、一目ではどこが変わったのか気付くのも困難

本稿では、こうした大規模文書の作成に特有の問題に対処する上で役立つLaTeXパッケージや関連ツールを紹介したいと思います。もちろん、これらは大規模文書の作成専門というわけではないので、一部は小さな文書の作成にも役立つでしょう。ただ、その真価を発揮するのが大規模な文書の作成時だと思われるものを集めています。

なお、索引や参考文献の作成や管理も大規模文書ならではのトピックですが、こうした内容はさまざまなLaTeXの参考書が扱っているので、本稿では省略します。本稿は「何かを網羅しよう」という意図の記事ではないのでご容赦ください。また対象文書の言語には特に依存しないツールが多いと思いますが、筆者が直近で作成したのは英文の大規模文書3なので、少し英文作成に寄った視点での紹介になっているかもしれません。

目次

紹介するパッケージを先に示すため、当ブログには珍しく目次を設置しておきます4

texfot: LaTeXエラーのフィルタリング

文書が大きくなると、LaTeXがターミナルに書き出すログ情報も巨大になります。これはTeX/LaTeXがデフォルトで、読み込んだ外部ファイルやLaTeXのフォントに関するあまり重要でない情報などが逐一書き出されてしまうためです。しかし、こうした情報のほとんどは、特別な問題の調査をする際以外には必要がありません。むしろ、より重要な警告やエラーが発生している場合に、それを見落とす原因になりかねません。

こうした問題を解決するために開発されたのがtexfotです5。このツールを用いると、文書の作者が注意を払うべき警告やエラー以外はターミナル出力から除去されるようになります(ログファイルには相変わらず膨大な量の情報が出力されます)。

使い方は簡単で、通常の各種latexコマンドの前にtexfotを付けるだけです。latexコマンドより後ろに与えるコマンドラインオプションもそのままで大丈夫です。

texfot lualatex -interaction=nonstopmode -file-line-error -synctex=1 thesis.tex

コマンドラインオプションの形式が変わらないので、直接ターミナルでコマンドを叩かないエディタ設定などのユースケースでも便利です。筆者も、llmkに解釈させるマジックコメントにtexfotと使用するLaTeXエンジンを併記する運用をしています6

%#!texfot lualatex

発展的な情報として、texfotはPerlで実装されています。フィルタする内容もPerlの正規表現によって追加したり、逆にフィルタしない許可リストに加えたりすることも可能です。texfotの使用法に関しては、manpageが一番くわしいドキュメントになっています。texdocを使うより、manコマンドでドキュメントを開く方が早いでしょう。

man texfot

ちなみに、このツールの作者はTeX Live主宰者のKarl Berry氏です。ドキュメントによれば、TeX Users Group(TUG)の会誌TUGboatの編集時に、大量のエラーが出て作業が非効率なのでフィルタ用のツールを作ったというのが開発の経緯のようです。

acro: 頭字語の管理

少し長い英語の単語やフレーズの頭文字から成る頭字語を、英語ではacronymと言いますね。本稿でも、いましがたTUGという頭字語が出てきました。こういった頭字語は、各文書で最初に使用したときに「TUG(TeX Users Group)」のように本来の長い表記をカッコ書き等で示す(ここでは、これをフル表記と呼びます)のが作法とされています。文脈上、頭字語の意味がはっきりしている場合(例えば、TeXを話題にしている文書でPDFやHTMLといった頭字語を用いるときなど)は省略されることもありますが、書籍や学術論文の場合は原則としてすべての頭字語について初出時にはフル表記をするのが望ましいとされているはずです。

さて、短い文書であったり、出てくる頭字語の数が少ない場合はこのルールに則った初出表記の管理を手動でやってもそれほど大変ではありません。しかし、これがいくつもの章から構成される大規模な文書になると話が変わってきます。文書内のある地点で、どの頭字語が導入済みでどの頭字語が未導入かを人間がすべて記憶しておくのは困難です。また、こういった文書は必ずしも頭から順序通りに執筆するものではありませんし、後から前の方に追記を行った結果、初出位置が変化することもあるでしょう。acroパッケージは、こうした頭字語の管理を自動化してくれるLaTeXパッケージです。

このパッケージでは、事前に頭字語とその長い表記を宣言する必要があります。この宣言には\DeclareAcronymコマンドを使用します。

\DeclareAcronym{〈ID〉}{〈設定項目〉}

第1引数の〈ID〉は頭字語のIDです。このIDを使用して、本文で実際に頭字語を使用します。第2引数の〈設定項目〉にはkey-value形式でその略語の内容を記述します。具体例を見た方が早いでしょう。

\DeclareAcronym{tug}{
  long = TeX Users Group,
  short = TUG,
}

第2引数はkey-value形式なので順不同ですが、longキーには長い表記、shortキーに頭字語を与えます。他にも設定できる項目はありますが、基本はこの2つです。

その上で、実際に頭字語を使用する場合には\ac{〈ID〉}コマンドを使用します。このようにしておくと、次のような動作になります:

  • その位置がIDに対応する頭字語の初出であれば、「頭字語(長い表記)」形式のフル表記で印字
  • その位置でIDの頭字語が既出である場合には、短い頭字語のみを印字

察しのよい方はすでに気づいているかもしれませんが、正しく初出位置を判定するには複数回のLaTeX実行が必要な場合があります。

\acコマンドにはさまざまなバリエーションがあります。あえて初出・既出の位置によるルールを無視して必ずフル表記をする\acfや長い表記・頭字語での印字を指定する\acl\acs、英語での複数形で印字する\acpなども用意されています。これ以上に細かいことはtexdoc等でパッケージ文書を参照してください。

大規模な文書の作成において、このパッケージを利用するメリットは2つあると思っています。一つはすでに説明したように初出位置を自分で管理しなくてよくなることです。もう一つは頭字語の一貫性の維持に役立つということです。acroパッケージを利用した頭字語の管理には\DeclareAcronymによる事前の宣言が必須です。IDは重複が許されないので、同じ頭字語を2つ定義しようとするとエラーになります。これにより、1つの文書で意図せずに頭字語が衝突してしまう事態を防ぐことができます。一人の著者で書いているのに、頭字語が衝突することなどあるのかと思われるかもしれませんが、意外と普通に起こります。2-3文字程度のアルファベットの頭字語は、とても容易に衝突しますから7

showkeysとgentombow: デバッグ情報の出力

これら2つのパッケージの間に直接の関係はありませんが、筆者がLaTeX文書についてのデバッグ情報の出力という共通の目的のために利用しているのでまとめて紹介します。まずshowkeysは相互参照系のコマンド(\label, \ref, \citeなど)の内部キーを、紙面に印字するためのパッケージです。現在はLaTeXチームが管理するlatex-toolsバンドルに含まれており、実は数少ない “公式” パッケージの1つです。

大規模な文書になってくると、定義するラベルの数もかなり増えるので人間が覚えておくのは困難です。出来上がりのPDF(DVIでもよいですが)を見ながら相互参照したい箇所を見つけたときに、SyncTeX等でソースにジャンプする一手間を省略するのに一役買います。ただ、参照箇所で本文と同じ黒で内部キーが表示されていると鬱陶しいので、筆者はshowkeysをcolorオプション付きで読み込んだ上で少し色を薄めるように設定しています。

\definecolor{refkey}{gray}{.6}

showkeysを利用した文書の例

なお、このパッケージは\label\refといったLaTeX標準コマンドの定義を上書きします。そのため、同様にこれらの標準コマンドの定義を上書きするパッケージとの組み合わせには注意が必要です。代表的なところではhyperrefパッケージです。showkeysをhyperrefより後に読み込むとうまく動作しないので、showkeysは必ずhyperrefより前に読み込む必要があります。

gentombowは本来の紙面領域の外側にトンボ8と呼ばれる裁断位置等を表示するためのパッケージです。日本語用の有名な文書クラスの一部(主にjsclasses)はクラスオプションtombowをサポートしており、これを用いるとトンボ付きの文書を作成できます。gentombowパッケージは、こうしたクラスオプションを一般化したパッケージで、日本語用でない文書クラスや、特に日本語専用でないLaTeXエンジンでも利用することが可能です。このパッケージの作者は、scsnowmanでおなじみのaminophenさんです。

このパッケージを読み込むと、本来の出力紙面サイズが自動的に特定され、それより一回り大きな紙面での出力に切り替わります。例えば仕上がりがA4サイズの文書であれば、gentombowの読み込み時の出力はB4サイズとなります。これにより、本来の仕上がりサイズの領域の外側にトンボ情報が出力されることになります。

本来、トンボは印刷用のデータを作成する際にその裁断位置を確認するのが主な用途のようです。今日日筆者は自分で作成する文書を印刷することはほとんどなく、電子データ(PDF)を然るべきところに提出することで仕事が終わるのですが、他の2つの目的でトンボを使用しています。

第一の目的はバージョン情報代わりのタイムスタンプを全ページに印字することです。gentombowパッケージを用いると、デフォルトでトンボ領域の左上に「文書名 (日付 時刻)」の形式でバナーが印字されます。これが案外便利です。大規模な文書の作成には何ヶ月もかかるわけですが、その間ほぼ毎日異なるバージョンの原稿を扱います。もちろん文書ソースはGitで管理していますが、PDFだけを別の場所に移した際にそのPDFがいつのどのバージョンだかわからなくなるのは困ります。表紙に印刷する手もありますが、長い文書をいちいちスクロールして表紙を見に行くのも手間なので、全ページにタイプセット日時が印字されていると安心です。

第二の目的は「草稿」であることをひと目でわかるようにすることです。最終的に完成したPDFではトンボを取り払って提出します。そのためトンボ付きのPDFかどうかで草稿版か完成版かをひと目で見分けることができます。適当な草稿版を取り違えて提出してしまうと大変なので、こうしておくと個人的には取り違いの心配が減って嬉しいというわけです。

草稿版と完成版の切替

このセクションで紹介したshowkeysやgentombowは、いずれもデバッグ情報の出力を目的としており、草稿版の作成時のみ使用します。逆に完成版で使ってしまったら、それは事故です。筆者が大規模な文書を作成するときは、草稿版と完成版は自動的に切り替えられるようにしています。すなわちクラスオプションのdraftを指定しているときはshowkeysとgentombowを読み込み、finalを指定しているときは読み込まないようにしています。こうした切替はLaTeXの\ifdraftスイッチを利用すれば簡単に実現できます。

それ以外には、自作の\TODOコマンドも草稿版か完成版かで異なる振る舞いをするようにしています。草稿版では赤字で本文に印刷して未解決の作業があることを目立たせつつターミナル・ログにもメッセージを出す一方、完成版ではターミナル・ログへの警告出力のみを行います9。TODOのような著者向けノートを管理するためのLaTeXパッケージもいくつかあるようですが、筆者はこうした仕組みを自分で制御したいのでコマンドを自作しています。\ifdraftスイッチがわかっていればそれほど難しくないので、皆さんもまだ作ったことがなければ実装してみてはいかがでしょうか。

wtref: 相互参照キーの名前空間とスコープ(2023-12-24追記)

ここぞとばかりに自作パッケージを紹介しようと思っていたのに忘れていたので追記します。showkeysを利用するモチベーションのところでも言及しましたが、大規模な文書の作成時に相互参照用のキーをすべて記憶しておくのは困難です。また、参照キーも数が増えてくると衝突することがあります。なるべく説明的な参照キーを与えておくことももちろん重要ですが、キーの名前空間とスコープを適切に分離するという工夫の仕方もあります。wtrefパッケージはこのアイデアを実装したものです。

このパッケージを利用すると、相互参照用の\label\refの目的別のバリエーションを作成することができます。よくある利用例としては見出し、図、表、数式のそれぞれについて、独立のコマンドを用意することです。

\newref{sec, fig, tab, eq}

これにより見出し用には\seclabel\secref、図用には\figlabel\figrefといった相互参照コマンドの変種が利用できるようになります。それぞれの見出しコマンドは、それぞれの項目別に分離された名前空間を持ちます。例えば\seclabel{foo}のように見出し用のラベルを作成した場合には内部的にsec:labelがキーとして用いられます。もちろん、\secref{foo}とすれば自動的に対応する名前空間を利用するので、この内部キーについては直接意識する必要はありません。

また、大規模な文書の作成時にはスコープの切り分けも役立ちます。見出し以外の、図表等の参照は原則として章をまたぐべきではないので、スコープを各章に閉じておいても問題はありません。

\newref[scope=chapter]{fig, tab}

こうすることで、だいぶ離れた位置にある図表同士で参照キーが衝突してしまう事故を防ぐことができます。wtrefは、これ以外にも参照項目別に決まったプレフィックスを付ける機能(たとえば\secref{foo}で「第2章」と印字するなど)も提供しています。詳しいことは大昔にブログ記事を執筆しているので、そちらを参照してください。

latexdiff: 差分のハイライト

文書の編集をした後で、差分を確認したいということがあります。文書ソースをGit等で管理していれば、ソースの差分を表示するのは簡単ですね。ただ、そもそも何のために(プレーンテキストではなく)LaTeXで文書を作成しているかといえば、より読むのに適した紙面を作るためなので、大規模な文書の差分を相対的に読みにくいソースの状態で比較するのはあまり幸せなことではありません。差分もLaTeXの出力側で確認したいというわけです。これは特に、自分以外の誰か(共著者や指導教員など)に読んでもらうときには考慮した方がよいポイントです10

そのような場合にはlatexdiffが役立ちます。これを利用すると、差分を強調表示したPDFを比較的簡単に作成することができます。その見た目は、例えば次のような具合です。

latexdiffの出力例

基本的な使い方としては、編集前のファイル(仮にold.texとしましょう)と編集後のファイル(new.tex)を用意して、以下のようにlatexdiffコマンドで差分表示用のLaTeXソースを作ります。

latexdiff old.tex new.tex > diff.tex

あとはdiff.texを普通のファイルと同じ手順でLaTeX(および必要ならBibTeX等の外部ツールも)で処理します。ただこの「普通のファイルと同じ手順で」というのがちょっと曲者です。latexdiffが加える差分ハイライト用の置換のために、厳密に言うと同じ手順だとしばしばLaTeX文書の処理に失敗します。そういった場合はセーフコマンドを指定したり、あるいは図表の部分はそもそも差分表示を諦めて差分表示の対象から除外したりといった設定が必要になります。このあたりはあべのりさんのブログ記事が簡潔かつ要点をうまく拾っている気がするので、そちらを読んでいただくとよいでしょう。

ファイル分割とlatexdiff

個人的にはまた、大規模な文書の場合はdocmuteを利用して章ごとにファイルを分割しており、差分も章ごとに作成しています。そうするとメインのソースファイル(全体を1つのPDFにまとめるためのもの)、章ごとのソースファイル、章ごとの差分がそれぞれ階層的に配置された状態で有効なソースファイルになっている必要があります。これの実現が若干厄介なので、短いシェルスクリプトを書いてlatexdiffが出力するソース内のllmk設定のTEXINPUTSsedコマンドで置換するような荒業を使っています。全部専用のビルドツールで解決しないのかという声も聞こえてきそうですが、数行程度ならあれこれ考える前にシェルスクリプトを書いた方が早いということもあります😇

おわりに

今回は雑多でまとまりがない感じですが、大規模な文書作成に役立つツールやアイデアを書き出してみました。皆さんのLaTeXでの大規模な文書作成が、ちょっとでも幸せになることを願っています11


  1. こうした苦行から逃れるため、中身のコンテンツを本質化する画期的な手段をさまざま提唱している方もいらっしゃいますが、今日はそのムーブメントについては措いておきます。 ↩︎

  2. それも最近ではChatGPT等のいわゆる生成系AIの発展により、いろいろと技術的なサポートが可能になってはきましたね。 ↩︎

  3. お察しの通り、博士論文とかいうものです。 ↩︎

  4. このブログの管理システムには目次の生成機能は(たぶん)ないので手動で書いています。 ↩︎

  5. 実は改訂第8版以降の『美文書』(注意:アフィリエイトリンクです)では付録でtexfotが紹介されています(付録C.7)。 ↩︎

  6. 余談の余談ですが、美文書の改訂第9版では拙作llmkもコラム「LaTeXソースに記述したタイプセット方法を実行できるllmk」でとりあげていただきました。ありがとうございます。 ↩︎

  7. 筆者の狭義研究分野でも、MLPはMathematical Language ProcessingだったりMulti-Layer Perceptronだったりします。 ↩︎

  8. 筆者は印刷業界の事情や歴史に詳しくないのですが、どうも日本固有のものなのかもしれません。 ↩︎

  9. 提出前にすべてのTODOを片付けるのが望ましいですが、締切もあるので優先度の低いTODOは残ってしまうこともあります。仕方がありません。 ↩︎

  10. OverleafはTeXシステムのインストールいらずかつ共同編集用の機能もあるのでエントリーポイントとしては素晴らしいのですが、ソース側にしかコメント機能がないのが不満です。Overleafで原稿を共有されると強制的にソースを読む羽目になるのが個人的にかなり嫌です。他人のLaTeXソースはできれば読みたくありません🙃 ↩︎

  11. そもそも大規模な文書(特に学位論文とか)を書かなければならない状況の時点で不幸せかもしれませんが。「痛みを軽減する」ぐらいの意味で…… ↩︎