PDF の 1 ページ目に印刷された目次を書いたのは、いま走らせたコンパイルではありません。\tableofcontents・\listoffigures・\listoftables は、一つ前の LaTeX 実行が残していった小さなファイル——.toc・.lof・.lot——を読み込みます。しかもこの三つは文字列のキャッシュではなく、1 項目 1 行の短いプログラムで、次の実行がそれを「実行」します。この一点さえ掴めば、目次まわりで人がつまずくことはほぼ説明がつきます。新しい文書で目次が空白になる理由、初回だけページ番号がずれる理由、見出しの中の脚注が二回目のコンパイルで爆発する理由、そして LaTeX が「その目次は古い」と一度も警告してくれない理由まで。
.toc・.lof・.lot には何が書かれているか
一行につき一項目、\contentsline という命令が並んでいます。三つのリストは同じ仕掛けを共有していて、\tableofcontents が .toc、\listoffigures が .lof、\listoftables が .lot を担当し、ファイル名はどれもルートファイルと同じです。\contentsline は引数を四つとります——項目の種類、印刷する文字列、ページ番号、そしてリンク先。四つ目は素の LaTeX では空で、hyperref を読み込むと section.1.1 のような PDF の飛び先が入ります。つまり .toc は「目次の下書き」ではなく、次の実行に渡す命令の列です。
% one \contentsline per entry: unit, text, page, link target
\contentsline {chapter}{\numberline {1}Body}{5}{chapter.1}%
\contentsline {section}{\numberline {1.1}Short form}{5}{section.1.1}%
% and in mydoc.lof, written by \caption inside a figure:
\addvspace {10\p@ }
\contentsline {figure}{\numberline {1.1}{\ignorespaces Short caption}}{5}{figure.1.1}%ところが、これらの行は目次ファイルへ直接書かれるのではありません。まず .aux に \@writefile{toc}{...} という形で溜められ、\end{document} の時点で LaTeX が .aux を閉じて読み直したとき、はじめて .toc に流れ込みます。この遠回りには実際の帰結が二つあります。ひとつ、書き込み用のストリームを開くのは \tableofcontents 自身なので、その命令が文書のどこにも無ければ .toc は生成されません(項目は .aux の中に残ったままです)。ふたつ、書き込みが最後にまとめて起こるので、\tableofcontents は文書のどこに置いても構いません。巻末に置いても、その前後の見出しがすべて並んだ完全な目次が出ます。
| 命令 | 書き出すファイル | 中身の出どころ |
|---|---|---|
\tableofcontents | .toc | \chapter から \subparagraph までの見出しと \addcontentsline{toc}{...} |
\listoffigures | .lof | figure 環境の中の \caption(短い任意引数があればそちら) |
\listoftables | .lot | table 環境の中の \caption。仕掛けは .lof とまったく同じ |
\addcontentsline | 指定した拡張子 | 手で書いた 1 行。ページ番号は実行時の \thepage |
\addtocontents | 指定した拡張子 | 項目ではなく素材(空き・書式命令)を差し込む |
目次が空になるのはなぜか——そして LaTeX が警告しない理由
1 回目の実行時点では読むべき .toc がまだ存在しないからです。ログには No file mydoc.toc. の一行が出て、\tableofcontents は見出しだけを組んで先へ進みます。ファイルが書かれるのはその実行の終わりなので、内容が紙に載るのは 2 回目です。しかも 2 回目には目次自身がページを占めるため、以降のページ番号がずれ、3 回目でようやく落ち着くこともあります。「情報を蓄える回」と「取り出す回」が必ず別になる——ここは \label と \ref の二段構えとまったく同じ理屈で、詳しくは相互参照のページに譲ります。
ここに、ほとんど語られない後半があります。LaTeX はこの遅れを一度も警告しません。 未定義の参照があれば LaTeX Warning: Label(s) may have changed. Rerun to get cross-references right. が出ますが、それはラベルを .aux の前回値と突き合わせているからです。目次にはその突き合わせがありません。紙に印刷された目次と、いま書き終えたばかりの .toc が食い違っていても、ログには何も出ません——実際、目次が空のまま出力された実行のログを調べても、警告は一件も見つかりません。だからこそ、必要な回数を自分で数えるのではなく latexmk のようなビルドツールに任せるのが定石です。.toc の中身が前回と変わらなくなるまで自動で繰り返してくれます。
同じ沈黙は、もっと質の悪い形でも現れます。report で書いていた原稿を article に切り替えると、古い .toc には \contentsline {chapter}{...} の行が残っています。article には \l@chapter が無く、\contentsline は \csname l@chapter\endcsname を呼ぶだけなので、未定義の名前は静かに \relax になり、題とページ番号がそのまま本文として目次に流し込まれます。エラーも警告も出ず、「1 Alpha2」のような謎の行が残るだけ。クラスやフォルダ構成を変えたあとに目次が壊れて見えたら、まず .toc(と .aux)を消してから組み直すのが最短です。
tocdepth は一つしかない——図目次を空にしてしまう設定
tocdepth は「目次に載せる最下位のレベル」を表すカウンタで、\setcounter{tocdepth}{1} なら節まで、{2} なら小節までが印刷されます。既定は article で 3、book・report で 2。ただし、これは目次だけのカウンタではありません。article.cls と book.cls を見ると \l@figure は \@dottedtocline{1}{1.5em}{2.3em}——つまり図目次の項目はレベル 1 として組まれ、\l@table はその別名です。\@dottedtocline が比べる相手は、三つのリストで共有されるただ一つの tocdepth です。
帰結は意地の悪いものです。book で「目次は章だけにしたい」と考えて \setcounter{tocdepth}{0} を書くと、図目次と表目次が空になります。.lof には項目がきちんと並んでいるのに、レベル 1 が tocdepth の 0 を超えるため、一行も印刷されないのです。エラーは出ません。対策は簡単で、\listoffigures をグループで囲み、その中だけ tocdepth を上げます。なお tocdepth が読み込み時に効くこと、だから深さを変えても .toc を作り直す必要がなく余計な実行が 1 回で済むことは、文書構造のページが扱っています。
\setcounter{tocdepth}{0} % contents: chapters only
% ... but this alone would print an EMPTY list of figures.
% Raise the depth for the float lists only:
\begingroup
\setcounter{tocdepth}{1}
\listoffigures
\listoftables
\endgroup
% Because the .toc is a program, a depth change can also be
% injected into the middle of it, taking effect from here on:
\addtocontents{toc}{\protect\setcounter{tocdepth}{1}}目次に載るのは短いほう——\section[...] の任意引数
角括弧に書いた短いほうが .toc に入り、波括弧の長いほうは本文にだけ現れます。\section[短い題]{ページの上で堂々と広がる長い題} と書けば、本文の見出しは長いまま、目次と柱(ヘッダ)には短い題が並びます。\caption[短い題]{長い説明} も同じ規則で、.lof・.lot に入るのは短いほうです(キャプション側の詳しい作法は図のキャプションのページに譲ります)。ここで大事なのは、この任意引数が「見た目を整えるための贅沢品」ではないことです。
見出しの中身は .toc へ書き出される——つまり一度ファイルへ流し込まれ、次の実行で読み直されます。だから \section{注釈つきの題\footnote{注}} のように壊れやすい命令を入れると、1 回目は何事もなく通り、2 回目にファイルを読み戻したところで崩壊します。Runaway argument? に続いて ! Paragraph ended before \contentsline was complete.、そして ! Argument of \@sect has an extra }.——いかにも見出しと無関係そうな顔をしていますが、犯人はさっき書き出された .toc の中の脚注です。エラーが一回分遅れて来るのは、目次が一回分遅れてくるのとまったく同じ理由です。処方箋は任意引数で、\section[注釈つきの題]{注釈つきの題\footnote{注}} と書けば脚注は .toc に入らず、二度と壊れません。
% the bracketed form is what lands in .toc, .lof and the running head
\section[Short form]{A long section title that would wrap in the contents}
% fragile material belongs in the braces only, never in the file
\section[Title with a note]{Title with a note\footnote{note text}}
\begin{figure}
\includegraphics{plot}
\caption[Short caption]{A long caption explaining every detail}
\end{figure}同じ「見出しは三箇所で使われる」という事情は、hyperref を読み込んだとき別の形で顔を出します。題は PDF のしおりにも流用され、しおりは純粋な文字列なので数式が入りません。\section{$\mathcal{A}$ の性質} と書くと Package hyperref Warning: Token not allowed in a PDF string (Unicode) が出て、数式が黙って落とされます。逃げ道は \texorpdfstring{$\mathcal{A}$}{A}——組版用と文字列用を別々に渡す仕組みで、hyperref のページで扱います。
星付き見出しを目次に載せる——addcontentsline と置き場所
\addcontentsline{toc}{section}{はじめに} を、見出しの直後に一行置きます。\section* や \chapter* は番号を持たず、.toc へ何も書き出さないので、載せたければ自分で一行を注入するしかありません。引数は三つとも必須です。
ext— 対象の補助ファイルの拡張子。目次ならtoc、図目次ならlof、表目次ならlot。unit— 項目の種類。tocではpart・chapter・section・subsectionなど(そのレベルの書式と字下げが使われる)、lofではfigure、lotではtable。text— 載せる文字列。\protect\numberline{}を前置すると番号付き項目と同じ位置に題がそろい、壊れやすい命令には\protectを付ける。
置き場所が結果を決めます。latex.ltx の定義を開くと、\addcontentsline は \contentsline{unit}{text}{\thepage}{} を書き出すだけ——つまりその行が実行された瞬間のページ番号をそのまま焼き付けます。\chapter* は新しいページを起こすので、うっかり \chapter* の前に置くと、目次には一つ前のページ番号が載ります。実験すると露骨で、前に置いた項目は 2 ページ、直後に置いた項目は 3 ページを指しました。読者はそのページを開き、そこに章が無いことに気づくわけです。ページ番号は LaTeX が補うので、text に自分で書く必要はありません。
% right: the line runs after the page break that \chapter* causes
\chapter*{Acknowledgements}
\addcontentsline{toc}{chapter}{Acknowledgements}
\section*{Introduction}
\addcontentsline{toc}{section}{Introduction}
% \addtocontents injects material, not an entry
\addtocontents{lof}{\protect\vspace{2ex}}ふたつ目の \addtocontents{ext}{text} は、行ではなく素材を差し込みます。引数は対象拡張子と書き込む内容の二つだけで、ページ番号は付きません。.lof を覗くと \addvspace {10\p@ } という行が見つかるはずです——章が変わるたびに LaTeX 自身が同じ手口で空きを注入しているのです。要するに、ページ番号付きの行は \addcontentsline、空きや書式は \addtocontents。どちらも書き込み先は次の実行なので、\vspace のように壊れやすい命令には \protect が要ります。
リスト自身を目次に載せる — tocbibind
\usepackage{tocbibind} の一行で、図目次・表目次・参考文献・索引が自動的に目次へ並びます。これらの見出しは番号を持たない(article では \section*、book・report では \chapter*)ため、放っておくと目次に出ません。手で \addcontentsline を並べる方法もありますが、複数ページにわたる参考文献や索引では置き場所を間違えやすく、パッケージに任せたほうが安全です。
既定では目次自身も目次に載るので、たいていの人が最初に探すのは nottoc オプションです。除外用のオプションは nottoc・notlof・notlot・notbib・notindex の五つ。逆に numbib・numindex を渡すと、参考文献と索引が番号なしの見出しではなく番号付きの章/節として組まれます。TeX Live 2024 に入っている tocbibind は 2010 年の v1.5k で、Peter Wilson の手になるもの——tocloft と同じ作者です。
% list everything except the contents itself
\usepackage[nottoc]{tocbibind}
% the manual equivalent, one line per list
\cleardoublepage
\addcontentsline{toc}{chapter}{\listfigurename}
\listoffigurestocloft で字下げ・書体・点線リーダーを作り込む
\usepackage{tocloft} を読み込むと、レベルごとに字下げ・番号幅・書体・点線リーダーを個別に設定できるようになります。命令名は規則的で、レベルを表す接頭辞(part は toc、chapter は chap、section は sec、subsection は subsec、図は fig、表は tab)に役割を組み合わせるだけです。字下げと番号幅は \cftsetindents{section}{1.5em}{2.5em} のようにまとめて指定でき、番号の桁が増えて題とぶつかるときは第 3 引数を広げます。書体は項目の題(\cftsecfont)とそのページ番号(\cftsecpagefont)で別々です。
点線リーダーには、名前から想像しにくい仕掛けがあります。点の間隔は長さ \cftdotsep(既定 4.5)で決まり、小さくすると密に、大きくすると疎になります。リーダーを消すときに使う \cftnodots はフラグではなく、tocloft.sty の中ではただの 5000 という数値——行に一つも点が入らないほど広い間隔、というだけの話です。同じ仕掛けのおかげで、\cftpartdotsep と \cftchapdotsep は既定値が \cftnodots になっており、だから標準の目次では部と章の行にだけ点線が無いわけです。
| 命令 | 役割 | 書きかた |
|---|---|---|
\cftsetindents | そのレベルの字下げと番号幅 | \cftsetindents{section}{1.5em}{2.5em} |
\cftsecfont | 節項目の題の書体 | \renewcommand{\cftsecfont}{\bfseries} |
\cftsecpagefont | 節項目のページ番号の書体 | 章なら \cftchappagefont |
\cftsecleader | 節項目の点線リーダー | \cftdotfill{\cftdotsep} を入れ替える |
\cftdotsep | 点の間隔。既定 4.5、小さいほど密 | \renewcommand{\cftdotsep}{2} |
\cftnodots | 5000 という数値。点が一つも入らない間隔 | リーダーを完全に消すときに使う |
\cftloftitlefont | 図目次の見出しそのものの書体 | 目次なら \cfttoctitlefont |
\usepackage{tocloft}
\renewcommand{\cftsecfont}{\bfseries}
\renewcommand{\cftsecpagefont}{\bfseries}
\renewcommand{\cftsecleader}{\bfseries\cftdotfill{\cftdotsep}}
\renewcommand{\cftdotsep}{2} % tighter dots
\cftsetindents{section}{1.5em}{2.5em} % indent, number width
% drop the leader on section lines altogether
\renewcommand{\cftsecleader}{\cftdotfill{\cftnodots}}tocloft で足りなくなったら — titletoc と etoc
tocloft が既存の行の寸法と書体を調整するのに対し、titletoc と etoc は行の構造そのものを書き直します。titletoc(Javier Bezos 作、titlesec と同じ束に入っています)の中心は \titlecontents で、各レベルについて「行の前に置く素材」「番号の組みかた」「題」「ページ番号までの詰め物」「行の後ろ」を順に定義します。単純な点線だけで足りるなら \dottedcontents という短縮形もあります。さらに \startcontents・\printcontents・\stopcontents・\resumecontents を使えば、章の頭にその章だけの部分目次を置けます。見出しの体裁を titlesec で作り込んでいる文書なら、目次側も同じ流儀でそろえられるのが強みです。
etoc(Jean-François Burnol 作)はさらに踏み込み、目次を「行スタイル」と「全体スタイル」という二層の枠組みで丸ごと設計し直します。目玉は \localtableofcontents で、同じ .toc から章ごとの部分目次を何度でも取り出せます。ツリー状の目次のような凝った表現もここまで来れば射程に入ります。判断の順序としては、まず tocloft で寸法と書体を合わせ、それでも行の組み立てを変えたくなったら titletoc、目次の設計そのものを引き受けたければ etoc——という三段で考えるのが実務的です。