目次・図目次・表目次(tocloft)

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 は「目次の下書き」ではなく、次の実行に渡す命令の列です。

mydoc.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.loffigure 環境の中の \caption(短い任意引数があればそちら)
\listoftables.lottable 環境の中の \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、bookreport で 2。ただし、これは目次だけのカウンタではありません。article.clsbook.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 回で済むことは、文書構造のページが扱っています。

latex
\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 に入らず、二度と壊れません。

latex
% 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 では partchaptersectionsubsection など(そのレベルの書式と字下げが使われる)、lof では figurelot では table
  • text — 載せる文字列。\protect\numberline{} を前置すると番号付き項目と同じ位置に題がそろい、壊れやすい命令には \protect を付ける。

置き場所が結果を決めます。latex.ltx の定義を開くと、\addcontentsline\contentsline{unit}{text}{\thepage}{} を書き出すだけ——つまりその行が実行された瞬間のページ番号をそのまま焼き付けます。\chapter* は新しいページを起こすので、うっかり \chapter* の前に置くと、目次には一つ前のページ番号が載ります。実験すると露骨で、前に置いた項目は 2 ページ、直後に置いた項目は 3 ページを指しました。読者はそのページを開き、そこに章が無いことに気づくわけです。ページ番号は LaTeX が補うので、text に自分で書く必要はありません。

latex
% 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*bookreport では \chapter*)ため、放っておくと目次に出ません。手で \addcontentsline を並べる方法もありますが、複数ページにわたる参考文献や索引では置き場所を間違えやすく、パッケージに任せたほうが安全です。

既定では目次自身も目次に載るので、たいていの人が最初に探すのは nottoc オプションです。除外用のオプションは nottocnotlofnotlotnotbibnotindex の五つ。逆に numbibnumindex を渡すと、参考文献と索引が番号なしの見出しではなく番号付きの章/節として組まれます。TeX Live 2024 に入っている tocbibind は 2010 年の v1.5k で、Peter Wilson の手になるもの——tocloft と同じ作者です。

latex
% list everything except the contents itself
\usepackage[nottoc]{tocbibind}

% the manual equivalent, one line per list
\cleardoublepage
\addcontentsline{toc}{chapter}{\listfigurename}
\listoffigures

tocloft で字下げ・書体・点線リーダーを作り込む

\usepackage{tocloft} を読み込むと、レベルごとに字下げ・番号幅・書体・点線リーダーを個別に設定できるようになります。命令名は規則的で、レベルを表す接頭辞(parttocchapterchapsectionsecsubsectionsubsec、図は 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}
\cftnodots5000 という数値。点が一つも入らない間隔リーダーを完全に消すときに使う
\cftloftitlefont図目次の見出しそのものの書体目次なら \cfttoctitlefont
latex
\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 が既存の行の寸法と書体を調整するのに対し、titletocetoc は行の構造そのものを書き直します。titletoc(Javier Bezos 作、titlesec と同じ束に入っています)の中心は \titlecontents で、各レベルについて「行の前に置く素材」「番号の組みかた」「題」「ページ番号までの詰め物」「行の後ろ」を順に定義します。単純な点線だけで足りるなら \dottedcontents という短縮形もあります。さらに \startcontents\printcontents\stopcontents\resumecontents を使えば、章の頭にその章だけの部分目次を置けます。見出しの体裁を titlesec で作り込んでいる文書なら、目次側も同じ流儀でそろえられるのが強みです。

etoc(Jean-François Burnol 作)はさらに踏み込み、目次を「行スタイル」と「全体スタイル」という二層の枠組みで丸ごと設計し直します。目玉は \localtableofcontents で、同じ .toc から章ごとの部分目次を何度でも取り出せます。ツリー状の目次のような凝った表現もここまで来れば射程に入ります。判断の順序としては、まず tocloft で寸法と書体を合わせ、それでも行の組み立てを変えたくなったら titletoc、目次の設計そのものを引き受けたければ etoc——という三段で考えるのが実務的です。