L’apparence d’un titre LaTeX se décide dans un espace étonnamment restreint. Quatre lignes à partir de la ligne 302 d’article.cls — un seul appel passant six arguments à une commande nommée \@startsection — fixent la police, la numérotation et les espacements de chaque \section. Ce que fait le package titlesec (de Javier Bezos), c’est relire ces six arguments dans la classe et les resservir sous la forme lisible de \titleformat et \titlespacing. Voilà pourquoi le simple fait de le charger ne change pas un point sur la page. Cette page ouvre d’abord le \@startsection de la classe pour comprendre le fonctionnement réel d’un titre, puis traite l’écriture de \titleformat et \titlespacing, le sens véritable de l’étoile, et la raison des conflits avec KOMA-Script et memoir — mesures à l’appui.
\@startsection : les six arguments par lesquels une classe définit un titre
Affichez la définition de \section et il en sort un unique appel à six arguments. Tous les titres des classes standard, sauf \part, ont cette forme, et les arguments sont dans l’ordre le nom du titre, son numéro de niveau, le retrait, l’espace au-dessus, l’espace au-dessous et la mise en forme. Les lignes 302 à 305 d’article.cls sont exactement cela.
% article.cls, lines 302-305 (TeX Live 2024)
\newcommand\section{\@startsection {section}{1}{\z@}%
{-3.5ex \@plus -1ex \@minus -.2ex}%
{2.3ex \@plus.2ex}%
{\normalfont\Large\bfseries}}
% \paragraph, from the same file - note the NEGATIVE after-skip
\newcommand\paragraph{\@startsection{paragraph}{4}{\z@}%
{3.25ex \@plus1ex \@minus.2ex}%
{-1em}%
{\normalfont\normalsize\bfseries}}Le point intéressant est que le signe des quatrième et cinquième arguments porte du sens. Un quatrième argument négatif (l’espace au-dessus) signifie « ne pas indenter le paragraphe qui suit ce titre » ; un cinquième négatif (l’espace au-dessous) signifie « ne pas couper la ligne après le titre, le texte enchaîne » — autrement dit un titre run-in. \section a -3.5ex au-dessus : le paragraphe qui suit une section n’est donc pas indenté. \paragraph a -1em au-dessous : \paragraph est donc un titre run-in. Commuter un comportement par le signe d’une longueur est du pur design TeX des années 1980. La preuve est dans le code : la routine par laquelle titlesec relit une définition de classe choisit le \titlespacing* étoilé quand le quatrième est négatif et la forme runin quand le cinquième l’est, en inversant chaque fois le signe.
| Commande de titre | Niveau dans article | Niveau dans book / report |
|---|---|---|
\part | 0 | −1 |
\chapter | (n’existe pas) | 0 |
\section | 1 | 1 |
\subsection | 2 | 2 |
\subsubsection | 3 | 3 |
\paragraph | 4 (run-in par défaut) | 4 |
\subparagraph | 5 (idem) | 5 |
Il y a donc six niveaux dans article (de 0 à 5) et sept dans book et report (de −1 à 5), le supplémentaire étant \chapter. La profondeur de numérotation est tenue par \c@secnumdepth : à la mesure, 3 dans article et 2 dans book et report. C’est pourquoi article numérote jusqu’à \subsubsection alors que book s’arrête à \subsection. Avec \setcounter{secnumdepth}{4}, \paragraph reçoit aussi un numéro. La profondeur de la table des matières est un compte distinct, tenu par tocdepth.
Ce qui se passe réellement au chargement de titlesec
Rien ne change sur la page. Ce qui change, c’est la définition de \section : l’appel à \@startsection est remplacé par la mécanique propre de titlesec. À la mesure, \section vaut avant chargement \@startsection {section}{1}{\z@}{-3.5ex ...}{2.3ex ...}{\normalfont \Large \bfseries} et après chargement le seul jeton \ttl@straightclass {section}. Au passage, les six arguments migrent vers le stockage interne de titlesec, où l’espace au-dessus devient la longueur concrète 15.0694pt plus 4.30554pt minus 0.86108pt et celui au-dessous 9.90276pt plus 0.86108pt. Ce sont simplement 3.5ex et 2.3ex évalués, puisque 1ex = 4.30554pt en Computer Modern 10pt (les parts plus et minus sont l’étirement et le retrait que LaTeX peut employer pour égaliser le bas d’une page). Bref, une transformation équivalente : pas une miette de sens n’est perdue.
% Measured in a 10pt article. These two lines reproduce exactly what the
% class already does - they are what titlesec extracted on load.
\titleformat{\section}[hang]
{\normalfont\Large\bfseries}{\thesection}{1em}{}
\titlespacing*{\section}{0pt}
{15.0694pt plus 4.30554pt minus 0.86108pt} % = 3.5ex plus 1ex minus .2ex
{9.90276pt plus 0.86108pt} % = 2.3ex plus .2exÉcrire \titleformat : sept arguments et six formes
\titleformat est le cœur de la refonte. Il prend beaucoup d’arguments, mais ils correspondent aux six de \@startsection : rien d’effrayant. Pour changer seulement la police, la forme étoilée \titleformat* est la plus courte — deux arguments, comme \titleformat*{\section}{\large\bfseries\sffamily} — et la numérotation comme les espacements restent ceux de la classe. Plus court encore, un réglage simple ne passe que par les options du package : \usepackage[sf,bf]{titlesec} met tous les titres en sans sérif gras. Les options forment trois familles — rm sf tt md bf up it sl sc ; big medium small tiny ; raggedleft center raggedright — auxquelles s’ajoute compact, qui resserre l’espace au-dessus et au-dessous. Pour ne changer que l’affichage du numéro, employez \titlelabel, par exemple \titlelabel{\thetitle.\quad}, où \thetitle désigne le numéro du titre.
\titleformat{⟨command⟩}[⟨shape⟩]{⟨format⟩}{⟨label⟩}{⟨sep⟩}{⟨before-code⟩}[⟨after-code⟩]⟨command⟩— la commande de sectionnement à redessiner :\part,\chapter,\section,\subsection,\subsubsection,\paragraphou\subparagraph.[⟨shape⟩](facultatif) — la « forme » du titre :hang,block,display,runin,leftmargin,rightmargin,frame,drop,wrap. Par défaut :hang.{⟨format⟩}— mise en forme appliquée au label comme au titre. Police et taille se règlent ici, l’alignement avec\filcenter,\filright,\filleft,\fillast, ou\filinner/\filouteren recto-verso.{⟨label⟩}— la façon d’imprimer le numéro ; on y met\thesection, ou\@seccntformat{section}pour conserver la mise en forme de la classe. Laisser vide supprime aussi le numéro dans la table des matières.{⟨sep⟩}— espace entre le label et le titre. Il doit être une longueur et ne peut pas être vide (horizontal avechang/block, vertical avecdisplay, distance titre-cadre avecframe).{⟨before-code⟩}— code exécuté juste avant le titre. C’est là que va la décoration :\titlerulepour un filet,\titlelinepour composer un contenu quelconque sur une ligne à part.[⟨after-code⟩](facultatif) — code exécuté juste après le titre (mode vertical avechang/block/display, horizontal avecrunin/leftmargin).
| shape | Effet |
|---|---|
hang | Défaut : label suspendu, titre aligné à côté (le \section standard) |
block | Compose label et titre en un seul bloc (paragraphe) ; le bon choix pour des titres centrés |
display | Place le label sur sa propre ligne au-dessus du titre (le \chapter standard) |
runin | Titre run-in : pas de coupure, le texte enchaîne sur la même ligne (le \paragraph standard) |
leftmargin | Place le titre dans la marge gauche ; rightmargin en est la version droite |
frame | Comme display, mais le titre est encadré |
drop | Fait habiller le texte autour du titre ; wrap ajuste la boîte du titre à son contenu |
\titlespacing et le sens de l’étoile : pourquoi on veut presque toujours \titlespacing*
L’étoile signifie supprimer le retrait du paragraphe qui suit le titre. Le plus simple est d’y voir la traduction directe de la convention vue plus haut dans \@startsection : un espace supérieur négatif veut dire « pas de retrait ». Le \section standard a -3.5ex au-dessus ; pour conserver l’aspect de la classe, on emploie donc la forme étoilée — d’où le conseil « presque toujours \titlespacing* ». Ses arguments sont la marge gauche supplémentaire, l’espace au-dessus du titre, la séparation entre titre et texte, et une marge droite supplémentaire facultative. Dans les formes leftmargin, le premier argument est la largeur du titre ; dans runin, le retrait qui le précède. Avec drop, wrap et runin, il n’y a de toute façon aucun retrait à supprimer : l’étoile y reste sans effet.
\titlespacing*{⟨command⟩}{⟨left⟩}{⟨before-sep⟩}{⟨after-sep⟩}[⟨right⟩]
% All arguments must be LENGTHS. A value containing a command such as
% \stretch, or an empty {}, produces:
% ! Missing number, treated as zero.
% ! Illegal unit of measure (pt inserted).
%
% Shorthand: *4 means 4 units of \beforetitleunit (or \aftertitleunit)
\titlespacing*{\section}{0pt}{*4}{*2}Tous les arguments doivent être des longueurs (dimensions). Passez une valeur contenant une commande, comme \stretch{1}, et vous obtenez ! Missing number, treated as zero. puis ! Illegal unit of measure (pt inserted). Un {} vide produit la même paire d’erreurs. Si écrire les valeurs de skip complètes est pénible, abrégez avec * et un nombre, comme *4 ; les unités de cette notation résident dans \beforetitleunit et \aftertitleunit.
Un comportement qui mérite une mesure, parce que beaucoup s’y font prendre. \titlespacing ne fait rien pour \chapter ni \part tant qu’on ne définit pas aussi leur format avec \titleformat. Dans la classe report, en écrivant seulement \titlespacing*{\chapter}{0pt}{0pt}{10pt} puis en mesurant où commence le texte après un chapitre, on obtient 305,1077pt — pas un point échelonné d’écart avec l’absence de réglage. Ajoutez \titleformat{\chapter}[display]{...} dans le même fichier et la valeur remonte à 229,2701pt. Aucun avertissement, aucune erreur : le désagrément, c’est de ne rien remarquer. \part n’est de toute façon pas implémenté via \@startsection, si bien que même le réglage simple le laisse intact. Pour ces deux niveaux, commencez par \titleformat.
Construire un titre à filet et un titre run-in
\titleformat et \titlespacing* s’écrivent normalement ensemble. Le préambule ci-dessous transforme \section en un titre surmonté d’un filet fin, et \subsection en un titre run-in terminé par un point que le texte poursuit. Pour le filet, l’usage est de placer \titlerule dans le {⟨before-code⟩} de la forme [display]. L’option [explicit] empêche le texte du titre d’apparaître tout seul : il faut alors le poser explicitement avec #1 — le mécanisme requis dès qu’on veut de la décoration des deux côtés du titre.
\documentclass{article}
\usepackage[explicit]{titlesec}
% A rule above the title, sans-serif bold below it
\titleformat{\section}[display]
{\normalfont\Large\bfseries\sffamily} % applies to label and title
{\thesection} % the number
{0pt} % label-to-title gap (vertical here)
{\titlerule\vspace{2pt}#1} % before-code, then the title itself
\titlespacing*{\section}{0pt}
{3.5ex plus 1ex minus .2ex}{2.3ex plus .2ex}
% A run-in subsection: "1.1 Beta. Text continues here."
\titleformat{\subsection}[runin]
{\normalfont\bfseries}{\thesubsection}{.5em}{#1.}
\titlespacing*{\subsection}{0pt}{2ex plus .5ex}{.5em}
\begin{document}
\section{Alpha}
The paragraph after a starred \titlespacing is not indented.
\subsection{Beta}
This text runs on from the heading, on the same line.
\end{document}Accorder la table et le titre courant : titletoc et titleps
Une fois les titres redessinés, on veut en général que la table des matières et le titre courant s’accordent. titlesec livre dans le même ensemble deux packages compagnons, utilisables seuls : titletoc côté table et titleps côté styles de page. Le cœur de titletoc, ce sont \titlecontents, qui définit intégralement une entrée de table, et \dottedcontents, un préréglage à points de conduite. Seul piège : le premier argument ⟨section⟩ est le nom sans contre-oblique (section, chapter, figure, …). titleps définit un style de page complet avec \newpagestyle et en remplit les trois cases gauche, centre et droite via \sethead et \setfoot. Le nom de titre qui atteint le titre courant se règle par \settitlemarks, et \bottitlemarks, \toptitlemarks, \firsttitlemarks permettent de choisir quel titre de la page utiliser. La fabrication des en-têtes et pieds eux-mêmes — fancyhdr compris — relève d’une autre page.
\usepackage{titletoc}
% Dotted leaders for section entries:
% indent 1.5em, no numbered-entry format, label width 2.3em, leader 1pc
\dottedcontents{section}[1.5em]{}{2.3em}{1pc}
\usepackage{titleps}
\newpagestyle{main}{\sethead{\thesection}{}{\thepage}}
\pagestyle{main}Pourquoi ne pas employer titlesec avec KOMA-Script ni memoir
Les deux familles construisent leurs titres avec une mécanique propre : un titlesec qui tente d’intercepter \@startsection par-dessus ne s’y emboîte pas. Elles échouent toutefois de façon très différente. KOMA-Script proteste. Chargez titlesec dans scrartcl et vous obtenez d’abord Class scrartcl Warning: Usage of package titlesec together with a KOMA-Script class is not recommended., puis un message par titre : Package titlesec Warning: Non standard sectioning command \section detected. Using default spacing and no format. Ce second message est le fond du problème : le \section de KOMA-Script appelle \scr@startsection et non \@startsection, si bien que titlesec ne peut rien relire du dessin de la classe et recompose le titre avec ses propres valeurs par défaut. Et cela ne s’arrête pas toujours à un avertissement. Combinez-le à une option de titre côté classe, comme \documentclass[headings=big]{scrartcl}, et le résultat mesuré est ! Package titlesec Error: No format for this command. suivi de ! Missing number, treated as zero. et ! Illegal unit of measure (pt inserted). : la composition s’effondre là. Avec KOMA-Script, retravaillez les titres via l’interface de la classe : \setkomafont et \RedeclareSectionCommand.
memoir, à l’inverse, ne dit rien du tout. Testée sur TeX Live 2024, la combinaison compile sans le moindre avertissement ni erreur, et \titleformat agit comme prévu. C’est que le \section de memoir a la forme \sechook \@startsection {section}{1}{\secindent}{\beforesecskip}{\aftersecskip}{\normalfont \secheadstyle}, que titlesec reconnaît comme standard et sait relire. Mais si l’on regarde ce qui a été relu, \secheadstyle et \secindent sont conservés sous forme de noms, tandis que les espaces au-dessus et au-dessous ont été figés aux valeurs en vigueur au chargement, 15.0694pt et 9.90276pt. Autrement dit, \setsecheadstyle survit mais \setbeforesecskip et \setaftersecskip cessent d’agir : le package casse la moitié de l’interface, en silence. memoir possède un mécanisme complet de conception des titres ; l’employer est la voie la plus sûre.
Deux dernières choses utiles autour de titlesec. \titleclass réattribue la classe d’un niveau, comme dans \titleclass{\subsection}{straight} ; les noms de classe valides sont top, page et straight, tout autre valeur provoquant Package titlesec Error: Unknown sectioning class. \assignpagestyle{\chapter}{plain} fixe le style de la page sur laquelle démarre un titre donné. Une note sur l’auteur, enfin : Javier Bezos est aussi le mainteneur actuel du package multilingue babel et l’auteur d’enumitem, qui contrôle la mise en forme des listes — passer une journée à régler titres, listes et langues sous LaTeX, c’est croiser trois fois le code de la même personne.