La table des matières imprimée en page un de votre PDF n’a pas été écrite par la compilation qui a produit ce PDF. \tableofcontents, \listoffigures et \listoftables lisent chacune un petit fichier laissé par la compilation LaTeX précédente — .toc, .lof et .lot — et ces fichiers ne sont pas des caches de texte mais de courts programmes, une ligne par entrée, que la compilation suivante exécute. Ce seul fait explique presque tout ce qui déroute chez ces trois commandes : pourquoi un document neuf n’affiche rien, pourquoi les numéros de page sont faux la première fois, pourquoi une note de bas de page dans un titre de section explose à la deuxième compilation et non à la première, et pourquoi LaTeX ne signale jamais que la table imprimée est périmée.
Ce que contient réellement un fichier .toc, .lof ou .lot
Une commande par entrée, toujours \contentsline. Les trois listes partagent un même mécanisme : \tableofcontents gouverne le .toc, \listoffigures le .lof, \listoftables le .lot, et chacun de ces fichiers porte le nom du fichier racine. \contentsline prend quatre arguments : le type d’entrée, le texte à imprimer, le numéro de page et une cible de lien. Le quatrième reste vide en LaTeX pur ; avec hyperref, il se remplit d’une destination PDF telle que section.1.1. Un .toc n’est donc pas un brouillon de la table, mais une suite d’instructions transmise à la compilation suivante.
% 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}%Ces lignes ne sont pourtant pas écrites directement dans le fichier de table. Elles s’accumulent d’abord dans le .aux sous la forme \@writefile{toc}{...}, et ce n’est qu’au moment où LaTeX ferme puis relit le .aux à \end{document} qu’elles passent dans le .toc. Ce détour a deux conséquences pratiques. D’abord, le canal d’écriture est ouvert par \tableofcontents lui-même : si cette commande n’apparaît nulle part, aucun fichier .toc n’est produit — les entrées restent simplement dans le .aux. Ensuite, comme l’écriture se fait d’un seul coup tout à la fin, \tableofcontents peut se trouver n’importe où. Placée en dernière page, elle donne encore une table complète, y compris les titres qui la précèdent.
| Commande | Fichier écrit | Origine des entrées |
|---|---|---|
\tableofcontents | .toc | les titres de \chapter à \subparagraph, plus \addcontentsline{toc}{...} |
\listoffigures | .lof | \caption dans une figure ; l’argument optionnel court s’il est fourni |
\listoftables | .lot | \caption dans une table ; mécanisme identique à celui du .lof |
\addcontentsline | l’extension indiquée | une ligne écrite à la main ; le numéro de page est le \thepage du moment |
\addtocontents | l’extension indiquée | du matériau plutôt qu’une entrée : espacements, commandes de mise en forme |
Pourquoi la table sort vide — et pourquoi LaTeX ne prévient jamais
Parce qu’à la première compilation il n’y a pas encore de .toc à lire. Le journal affiche la seule ligne No file mydoc.toc., et \tableofcontents compose son titre puis passe à la suite. Le fichier est écrit à la fin de cette compilation : les entrées n’arrivent donc sur le papier qu’à la deuxième. Et à la deuxième, la table occupe elle-même des pages, ce qui décale tous les numéros suivants ; une troisième compilation est parfois nécessaire avant stabilisation. Une passe enregistre l’information, une autre la récupère : c’est exactement le dispositif en deux temps de \label et \ref, détaillé sur la page des références croisées.
Et voici la moitié dont on parle rarement : LaTeX ne signale jamais ce décalage. Une référence indéfinie produit LaTeX Warning: Label(s) may have changed. Rerun to get cross-references right., mais seulement parce que les étiquettes sont comparées à leur valeur précédente dans le .aux. La table, elle, ne fait l’objet d’aucune comparaison. Lorsque la table imprimée contredit le .toc qui vient d’être écrit, le journal reste muet : examinez le journal d’une compilation qui a produit une table vide, vous n’y trouverez pas un seul avertissement. C’est l’argument décisif pour confier le comptage des passes à un outil comme latexmk plutôt que de le faire soi-même : il recommence jusqu’à ce que le .toc cesse de changer.
Le même silence se manifeste sous une forme plus désagréable. Passez un manuscrit de report à article et l’ancien .toc contient encore des lignes du type \contentsline {chapter}{...}. article ne définit aucun \l@chapter, et comme \contentsline se contente d’appeler \csname l@chapter\endcsname, un nom indéfini devient discrètement \relax : le titre et le numéro de page sont donc déversés dans la table comme du texte courant. Ni erreur ni avertissement, juste une ligne énigmatique du genre « 1 Alpha2 ». Chaque fois que la table paraît corrompue après un changement de classe ou d’arborescence, le plus rapide est de supprimer le .toc (et le .aux) puis de recompiler.
Il n’y a qu’un seul tocdepth — le réglage qui vide la liste des figures
tocdepth est un compteur qui désigne le niveau le plus profond imprimé dans la table : \setcounter{tocdepth}{1} s’arrête aux sections, {2} va jusqu’aux sous-sections. Les valeurs par défaut sont 3 dans article et 2 dans book et report. Mais ce n’est pas un compteur réservé à la table. Dans article.cls et book.cls, \l@figure vaut \@dottedtocline{1}{1.5em}{2.3em} : chaque entrée de la liste des figures est composée au niveau 1, et \l@table en est un alias. Or \@dottedtocline se compare à l’unique tocdepth, partagé par les trois listes.
La conséquence est cruelle. Dans un book, décider que la table ne doit lister que les chapitres et écrire \setcounter{tocdepth}{0} vide la liste des figures et celle des tableaux. Le .lof contient pourtant toutes les entrées, mais le niveau 1 dépasse un tocdepth de 0 : pas une ligne n’est imprimée. Aucune erreur n’est émise. Le remède est léger : entourer \listoffigures d’un groupe et y relever tocdepth. Que tocdepth filtre au moment de la relecture du fichier — raison pour laquelle changer la profondeur n’oblige jamais à régénérer le .toc et ne coûte qu’une compilation de plus — est traité sur la page consacrée à la structure du document.
\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}}C’est le titre court qui est enregistré : l’argument optionnel de \section[...]
C’est le titre court, entre crochets, qui entre dans le .toc ; le long, entre accolades, n’apparaît que dans le corps. Écrivez \section[Forme courte]{Un long titre qui s’étale sur la page} et le titre reste long sur la page tandis que la table et le titre courant prennent la forme courte. \caption[Légende courte]{Une longue explication} suit la même règle, et c’est la version courte qui atteint le .lof et le .lot — la page consacrée aux légendes traite ce versant en détail. L’essentiel ici est que cet argument optionnel n’est pas un luxe destiné à soigner l’apparence.
Un titre est écrit vers le .toc : versé dans un fichier, puis relu à la compilation suivante. Glissez-y une commande fragile, comme \section{Titre avec une note\footnote{note}}, et la première compilation passe sans broncher tandis que la seconde s’effondre au moment précis de la relecture. Runaway argument?, puis ! Paragraph ended before \contentsline was complete., puis ! Argument of \@sect has an extra }. — des messages qui semblent étrangers à tout titre, alors que le coupable est la note logée dans le .toc écrit un instant plus tôt. L’erreur arrive avec une compilation de retard pour exactement la raison qui fait que la table arrive en retard. L’ordonnance tient dans l’argument optionnel : \section[Titre avec une note]{Titre avec une note\footnote{note}} garde la note hors du .toc, et rien ne casse plus.
% 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}Le même fait — un titre sert à trois endroits — réapparaît sous une autre forme dès que hyperref est chargé. Le titre est réemployé pour les signets du PDF, et un signet est du texte pur : il n’admet aucune formule. Écrivez \section{Propriétés de $\mathcal{A}$} et vous obtenez Package hyperref Warning: Token not allowed in a PDF string (Unicode), la formule étant silencieusement supprimée. L’issue est \texorpdfstring{$\mathcal{A}$}{A}, qui livre une version au compositeur et une autre à la chaîne de caractères ; la page consacrée à hyperref s’en charge.
Faire entrer un titre étoilé dans la table : addcontentsline et son emplacement
Placez \addcontentsline{toc}{section}{Introduction} sur la ligne immédiatement après le titre. Un \section* ou un \chapter* n’a pas de numéro et n’écrit rien dans le .toc ; pour qu’il y figure, il faut injecter la ligne soi-même. Les trois arguments sont obligatoires.
ext— l’extension du fichier auxiliaire visé :tocpour la table,lofpour les figures,lotpour les tableaux.unit— le type d’entrée. Pourtoc:part,chapter,section,subsection, etc., dont le format et le retrait sont repris ; pourlof,figure; pourlot,table.text— le texte à lister. Préfixer par\protect\numberline{}aligne le titre sur les entrées numérotées ; toute commande fragile doit être précédée de\protect.
L’emplacement décide du résultat. Ouvrez la définition dans latex.ltx : \addcontentsline se borne à écrire \contentsline{unit}{text}{\thepage}{} — il grave donc le numéro de page en vigueur à l’instant où cette ligne s’exécute. Comme \chapter* ouvre une page neuve, placer la ligne étourdiment avant le \chapter* enregistre la page précédente. L’expérience est sans appel : l’entrée placée avant pointait sur la page 2, celle placée juste après sur la page 3. Le lecteur ouvre la page et n’y trouve aucun chapitre. LaTeX fournit le numéro lui-même ; il ne s’écrit jamais dans 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}}Son compagnon, \addtocontents{ext}{text}, injecte du matériau plutôt qu’une ligne. Il ne prend que l’extension cible et le contenu à écrire, sans numéro de page. Jetez un œil dans un .lof : vous y trouverez une ligne \addvspace {10\p@ } — c’est exactement par cette voie que LaTeX insère lui-même de l’espace à chaque changement de chapitre. En bref : une ligne avec numéro de page passe par \addcontentsline ; l’espacement et la mise en forme passent par \addtocontents. Tous deux écrivent à l’intention de la compilation suivante, d’où le \protect requis devant une commande fragile comme \vspace.
Faire figurer les listes elles-mêmes : tocbibind
Une seule ligne, \usepackage{tocbibind}, et la liste des figures, celle des tableaux, la bibliographie et l’index figurent d’eux-mêmes dans la table. Ces titres ne portent pas de numéro — \section* dans article, \chapter* dans book et report — et n’apparaissent donc jamais si l’on n’y touche pas. On peut aligner des appels à \addcontentsline à la main, mais pour une bibliographie ou un index qui court sur plusieurs pages l’emplacement se trompe facilement : le package est le choix le plus sûr.
Par défaut, il fait aussi figurer la table dans la table, ce qui explique que nottoc soit l’option que l’on cherche en premier. Les exclusions sont nottoc, notlof, notlot, notbib et notindex. À l’inverse, numbib et numindex composent la bibliographie et l’index en chapitres ou sections numérotés plutôt qu’en titres non numérotés. Le tocbibind livré dans TeX Live 2024 est la v1.5k de 2010, de Peter Wilson — le même auteur que tocloft.
% list everything except the contents itself
\usepackage[nottoc]{tocbibind}
% the manual equivalent, one line per list
\cleardoublepage
\addcontentsline{toc}{chapter}{\listfigurename}
\listoffiguresRemodeler retraits, polices et points de conduite avec tocloft
Chargez \usepackage{tocloft} et chaque niveau obtient son propre retrait, sa largeur de numéro, sa police et ses points de conduite. Les noms de commandes sont systématiques : un préfixe de niveau (toc pour part, chap pour chapter, sec pour section, subsec pour subsection, fig pour les figures, tab pour les tableaux) combiné à un rôle. Retrait et largeur du numéro se règlent ensemble, comme dans \cftsetindents{section}{1.5em}{2.5em} ; si des numéros plus larges heurtent le titre, on augmente le troisième argument. Les polices sont distinctes pour le titre de l’entrée (\cftsecfont) et pour son numéro de page (\cftsecpagefont).
Les points de conduite cachent une astuce que les noms ne trahissent pas. L’espacement des points est la longueur \cftdotsep (4.5 par défaut) : plus petite, les points se resserrent ; plus grande, ils s’écartent. Et \cftnodots, dont on se sert pour supprimer une conduite, n’est pas un drapeau : dans tocloft.sty, c’est tout simplement le nombre 5000, un écart si large qu’aucun point ne tient sur la ligne. La même astuce explique un détail vu mille fois sans y prendre garde : \cftpartdotsep et \cftchapdotsep valent par défaut \cftnodots, et c’est pourquoi, dans une table standard, seules les lignes de partie et de chapitre sont dépourvues de points.
| Commande | Ce qu’elle contrôle | Comment la régler |
|---|---|---|
\cftsetindents | Retrait et largeur du numéro d’un niveau | \cftsetindents{section}{1.5em}{2.5em} |
\cftsecfont | Police du titre d’une entrée de section | \renewcommand{\cftsecfont}{\bfseries} |
\cftsecpagefont | Police du numéro de page d’une entrée de section | pour les chapitres, \cftchappagefont |
\cftsecleader | Points de conduite d’une entrée de section | remplacer le \cftdotfill{\cftdotsep} qu’elle contient |
\cftdotsep | Espacement des points ; 4.5 par défaut, plus petit = plus dense | \renewcommand{\cftdotsep}{2} |
\cftnodots | Le nombre 5000 — un espacement où aucun point ne tient | pour supprimer entièrement une conduite |
\cftloftitlefont | Police du titre de la liste des figures | pour la table, \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}}Quand tocloft ne suffit plus : titletoc et etoc
Là où tocloft ajuste les dimensions et les polices des lignes existantes, titletoc et etoc réécrivent la structure de la ligne. Le cœur de titletoc (de Javier Bezos, dans le même ensemble que titlesec) est \titlecontents, qui définit niveau par niveau le matériau précédant la ligne, la composition du numéro, le titre, le remplissage menant au numéro de page et ce qui suit la ligne. Si de simples points suffisent, il existe le raccourci \dottedcontents. Au-delà, \startcontents, \printcontents, \stopcontents et \resumecontents permettent de placer une table partielle propre à un chapitre en tête de celui-ci. Dans un document dont les titres sont déjà façonnés par titlesec, la table peut l’être dans le même idiome.
etoc (de Jean-François Burnol) va plus loin encore et refond la table dans son ensemble, au moyen d’un cadre à deux couches de « styles de ligne » et de « styles globaux ». Sa pièce maîtresse est \localtableofcontents, qui extrait autant de fois qu’on le souhaite une table partielle par chapitre du même .toc ; à ce niveau, même une table arborescente devient accessible. Comme démarche de décision, trois étapes fonctionnent bien : régler dimensions et polices avec tocloft ; passer à titletoc dès qu’il faut modifier l’assemblage d’une ligne ; recourir à etoc pour reprendre la conception de la table elle-même.