見出しの書式(titlesec)

LaTeX の見出しの体裁は、驚くほど狭い場所に書かれています。article.cls の 302 行目から始まるたった 4 行——\@startsection という一つの命令に六つの引数を渡す呼び出し——が、\section の書体も番号もページ上の空きも全部決めています。titlesec パッケージ(Javier Bezos 作)がすることは、その六つの引数をクラスから 読み戻して、人間が読める \titleformat\titlespacing として差し出すことです。だから読み込んだだけでは見た目が 1pt も変わりません。このページでは、まずクラス側の \@startsection を開いて見出しの仕組みを押さえ、そのうえで \titleformat\titlespacing の書き方、星印の本当の意味、そして KOMA-Script や memoir と衝突する理由までを実測で追います。

\@startsection ——クラスが見出しを定義している六つの引数

\section の中身をそのまま表示させると、六つの引数を並べた一行の呼び出しが出てきます。標準クラスの見出しは \part を除いてすべてこの形で、順に 見出しの名前・階層番号・字下げ量・見出しの上の空き・見出しの下の空き・書式 を渡します。article.cls の 302〜305 行がそれです。

article.cls
% 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}}

ここで面白いのは、四番目と五番目の引数の符号に意味がある ことです。四番目(上の空き)が負なら「この見出しの直後の段落を字下げしない」、五番目(下の空き)が負なら「見出しのあとで改行せず、本文を同じ行に続ける」——つまり 行内見出し(run-in) になります。\section の上の空きが -3.5ex だから、節の直後の段落は字下げされません。\paragraph の下の空きが -1em だから、\paragraph は行内見出しになります。長さの符号一つで挙動を切り替えるという、いかにも 1980 年代の TeX らしい設計です。実装の証拠もあり、titlesec がクラスの定義を読み戻すルーチンは、四番目が負なら星付きの \titlespacing* を、五番目が負なら runin 形を選んで符号を反転させています。

見出し命令article での階層book / report での階層
\part0−1
\chapter(存在しません)0
\section11
\subsection22
\subsubsection33
\paragraph4(既定で行内見出し)4
\subparagraph5(同上)5

つまり階層は article六段(0〜5)、\chapter が加わる bookreport七段(−1〜5)です。番号を振る深さは \c@secnumdepth が握っていて、実測すると article3bookreport2。だから article では \subsubsection まで番号が付き、book では \subsection までしか付きません。\setcounter{secnumdepth}{4} とすれば \paragraph にも番号が付きます。目次に載る深さは別勘定で、tocdepth が担当します。

titlesec を読み込むと何が起きるのか

見た目は何も変わりません。変わるのは \section の中身で、\@startsection の呼び出しが titlesec 自身の処理系に 置き換わります。実測すると、読み込む前の \section\@startsection {section}{1}{\z@}{-3.5ex ...}{2.3ex ...}{\normalfont \Large \bfseries}、読み込んだあとは \ttl@straightclass {section} の一語だけ。そのとき六つの引数は titlesec の内部変数へ移し替えられ、上の空きは 15.0694pt plus 4.30554pt minus 0.86108pt、下の空きは 9.90276pt plus 0.86108pt という具体的な長さになります。これは 10pt の Computer Modern で 1ex = 4.30554pt なので、3.5ex2.3ex をそのまま数値化したものです(plusminus は、ページの高さをそろえるために LaTeX が使ってよい伸縮の許容量です)。要するに 等価変換 であって、意味は一つも失われていません。

latex
% 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

\titleformat の書き方——七つの引数と六つの形

見出しの体裁を作り変える中心が \titleformat です。引数は多いものの、\@startsection の六つと対応づければ怖くありません。書体だけ差し替えたいなら星付きの \titleformat* が最短で、\titleformat*{\section}{\large\bfseries\sffamily} の二引数だけ。番号や空きはクラスのまま残ります。さらに手前には、パッケージオプションだけで済ませる簡易設定もあり、\usepackage[sf,bf]{titlesec} と読み込めば全見出しがサンセリフのボールドになります。オプションは書体・大きさ・揃えの三系統——rm sf tt md bf up it sl scbig medium small tinyraggedleft center raggedright——に、上下の空きを詰める compact が加わります。番号の出し方だけ変えたいなら \titlelabel{\thetitle.\quad} のように \titlelabel を使います(\thetitle がその見出しの番号)。

latex
\titleformat{⟨command⟩}[⟨shape⟩]{⟨format⟩}{⟨label⟩}{⟨sep⟩}{⟨before-code⟩}[⟨after-code⟩]
  • ⟨command⟩ — 作り変えたい見出し命令そのもの。\part\chapter\section\subsection\subsubsection\paragraph\subparagraph のいずれか。
  • [⟨shape⟩](省略可) — 見出しの「形」。hangblockdisplayruninleftmarginrightmarginframedropwrap から選びます。省略時は hang
  • {⟨format⟩} — 番号と題の 両方 に効く書式。\normalfont\Large\bfseries のように書体・サイズをまとめ、揃えは \filcenter\filright\filleft\fillast や、両面用の \filinner\filouter で指定します。
  • {⟨label⟩} — 番号(ラベル)の出し方。\thesection や、クラス側の書式を活かす \@seccntformat{section} を置きます。空にすると番号が消えますが、目次からも消える点に注意。
  • {⟨sep⟩} — ラベルと題のあいだの空き。必ず長さを書き、空にはできませんhangblock では水平方向、display では垂直方向、frame では題と枠の距離)。
  • {⟨before-code⟩} — 題の 直前 に実行するコード。\titlerule で罫線を引く、\titleline で任意の内容を一行分に組む、といった飾りを入れる場所です。
  • [⟨after-code⟩](省略可) — 題の 直後 に実行するコード(hangblockdisplay では垂直モード、runinleftmargin では水平モード)。
shape効果
hang既定値。番号をぶら下げ、題をその右にそろえる(標準の \section と同じ)
block番号も題も一つの段落として組む。中央揃えの見出しに向きます
display番号を別行(別段落)にして題の上に置く(標準の \chapter と同じ)
runin行内見出し。改行せず本文が同じ行に続く(標準の \paragraph と同じ)
leftmargin見出しを左余白の中に置く。rightmargin は右余白版
framedisplay に似るが、見出しを罫線で囲む
drop見出しの右下に本文を回り込ませる。wrap は見出しの幅を中身に合わせる版

\titlespacing と星の意味——なぜ \titlespacing* を使うのか

星は 見出し直後の段落の字下げを止める 印です。前の節で見た \@startsection の「上の空きが負なら字下げしない」という約束を、そのまま星に翻訳したものだと考えれば覚えやすいでしょう。標準クラスの \section は上の空きが -3.5ex ですから、同じ見た目を保ちたければ星付きを使う——それが「ほぼ常に \titlespacing*」の理由です。引数は左余白の追加分、見出しの上の空き、見出しと本文のあいだの空き、そして省略可能な右余白の追加分の四つ。leftmargin 系では第一引数が見出しの幅、runin では見出し直前の字下げを表します。なお dropwraprunin の形では字下げ抑制がそもそも意味を持たないので、星の有無は効きません。

latex
\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}

引数はすべて 長さ(寸法) でなければなりません。\stretch{1} のように命令を含む値を渡すと ! Missing number, treated as zero. に続いて ! Illegal unit of measure (pt inserted). が出ます。空の {} を渡した場合も同じ二つのエラーです。フルの skip 値を書くのが面倒なら *4 のように * と数値で略記でき、その単位は \beforetitleunit\aftertitleunit が持っています。

ここで、はまりやすい挙動を一つ実測しておきます。\titlespacing\chapter\part に対して、同時に \titleformat で体裁も定義しないかぎり何も起こしません。 report クラスで \titlespacing*{\chapter}{0pt}{0pt}{10pt} だけを書いて章のあとの本文の位置を測ったところ、指定なしの場合と 1sp も違わない 305.1077pt のまま。同じファイルに \titleformat{\chapter}[display]{...} を足すと 229.2701pt まで上がりました。警告もエラーも出ないので、効かないまま気づかないのが厄介です。\part はもともと \@startsection を使わない特殊な実装なので、簡易設定でも変わりません。どちらのレベルも \titleformat から手を付けてください。

罫線つきの見出しと行内見出しを組む

\titleformat\titlespacing* はふつう一組で書きます。次のプリアンブルは、\section を「題の上に細い罫線を渡す」体裁に、\subsection を「題のうしろにピリオドを打って本文を続ける行内見出し」に作り変える例です。罫線は [display] 形の {⟨before-code⟩}\titlerule を置くのが定石。[explicit] オプションを付けると題の文字列が自動では出てこなくなり、#1明示的に 置く必要が生まれます——題の前後に飾りを付けたいときのための仕掛けです。

document.tex
\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}

目次と柱もそろえる——titletoctitleps

見出しを作り変えたら、目次と柱もそろえたくなります。titlesec は同じ配布物に姉妹パッケージを二つ抱えていて(単独でも使えます)、目次側が titletoc、ページスタイル側が titleps です。titletoc の中心は目次項目を完全に定義する \titlecontents と、点線リーダー付きの定番を一発で作る \dottedcontents。引数の先頭 ⟨section⟩バックスラッシュなしの名前sectionchapterfigure など)である点だけ注意してください。titleps\newpagestyle でページスタイルを丸ごと定義し、\sethead\setfoot で左・中央・右の三枠を埋めます。柱に載せる見出し名の取り方は \settitlemarks で制御でき、\bottitlemarks\toptitlemarks\firsttitlemarks などで「そのページのどの見出しを使うか」まで選べます。ヘッダ・フッタそのものの作り方(fancyhdr を含む)は別ページの担当です。

latex
\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}

KOMA-Script と memoir で titlesec を使ってはいけない理由

どちらのクラス群も見出しを自前の仕組みで組み立てているので、titlesec がその上から \@startsection を横取りしようとすると噛み合いません。ただし壊れ方はまるで違います。KOMA-Script は声を上げます。 scrartcltitlesec を読み込むと、まず Class scrartcl Warning: Usage of package titlesec together with a KOMA-Script class is not recommended. が出て、続いて見出しごとに Package titlesec Warning: Non standard sectioning command \section detected. Using default spacing and no format. が並びます。この二文目が本質で、KOMA-Script の \section\@startsection ではなく \scr@startsection を呼ぶため、titlesec はクラスの体裁を一つも読み取れず、自分の既定値で組み直してしまいます。しかも警告で済むとは限りません。\documentclass[headings=big]{scrartcl} のようにクラス側の見出しオプションを併用すると、実測では ! Package titlesec Error: No format for this command. に続けて ! Missing number, treated as zero.! Illegal unit of measure (pt inserted). が出て、そこで組版が破綻します。KOMA-Script を使うなら、見出しは \setkomafont\RedeclareSectionCommand というクラス自身の仕組みで変えてください。

memoir は逆に、何も言いません。 TeX Live 2024 の組み合わせで試したところ、警告もエラーも一切なく通り、\titleformat も期待どおり効きました。memoir\section\sechook \@startsection {section}{1}{\secindent}{\beforesecskip}{\aftersecskip}{\normalfont \secheadstyle} という形をしていて、titlesec はこれを標準形と認識して読み戻せるからです。ただし読み戻した中身を見ると、\secheadstyle\secindent は名前のまま保存されている一方、上下の空きは読み込んだ瞬間の値で 15.0694pt と 9.90276pt に固定 されていました。つまり \setsecheadstyle は生き残るのに \setbeforesecskip\setaftersecskip は無視されるようになる、という 黙って半分だけ壊れる 状態です。memoir は見出しを組み立てる完全な仕組みを自前で持っているので、そちらを使うほうが安全です。

最後に、titlesec の周辺で覚えておくと便利なものを二つ。\titleclass は階層そのものを付け替える命令で、\titleclass{\subsection}{straight} のように書きます(クラス名は toppagestraight の三つ。それ以外を渡すと Package titlesec Error: Unknown sectioning class になります)。\assignpagestyle{\chapter}{plain} は特定の見出しが始まるページのページスタイルを指定します。なお、作者の Javier Bezos は多言語組版パッケージ babel の現在の保守者でもあり、箇条書きを制御する enumitem の作者でもあります——LaTeX で見出しと箇条書きと言語切り替えを触っていると、同じ人の書いたコードに三度出会うことになります。