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, 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 での階層 |
|---|---|---|
\part | 0 | −1 |
\chapter | (存在しません) | 0 |
\section | 1 | 1 |
\subsection | 2 | 2 |
\subsubsection | 3 | 3 |
\paragraph | 4(既定で行内見出し) | 4 |
\subparagraph | 5(同上) | 5 |
つまり階層は article で 六段(0〜5)、\chapter が加わる book/report で 七段(−1〜5)です。番号を振る深さは \c@secnumdepth が握っていて、実測すると article は 3、book/report は 2。だから 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.5ex と 2.3ex をそのまま数値化したものです(plus/minus は、ページの高さをそろえるために 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 sc/big medium small tiny/raggedleft center raggedright——に、上下の空きを詰める compact が加わります。番号の出し方だけ変えたいなら \titlelabel{\thetitle.\quad} のように \titlelabel を使います(\thetitle がその見出しの番号)。
\titleformat{⟨command⟩}[⟨shape⟩]{⟨format⟩}{⟨label⟩}{⟨sep⟩}{⟨before-code⟩}[⟨after-code⟩]⟨command⟩— 作り変えたい見出し命令そのもの。\part・\chapter・\section・\subsection・\subsubsection・\paragraph・\subparagraphのいずれか。[⟨shape⟩](省略可) — 見出しの「形」。hang・block・display・runin・leftmargin・rightmargin・frame・drop・wrapから選びます。省略時はhang。{⟨format⟩}— 番号と題の 両方 に効く書式。\normalfont\Large\bfseriesのように書体・サイズをまとめ、揃えは\filcenter・\filright・\filleft・\fillastや、両面用の\filinner・\filouterで指定します。{⟨label⟩}— 番号(ラベル)の出し方。\thesectionや、クラス側の書式を活かす\@seccntformat{section}を置きます。空にすると番号が消えますが、目次からも消える点に注意。{⟨sep⟩}— ラベルと題のあいだの空き。必ず長さを書き、空にはできません(hang/blockでは水平方向、displayでは垂直方向、frameでは題と枠の距離)。{⟨before-code⟩}— 題の 直前 に実行するコード。\titleruleで罫線を引く、\titlelineで任意の内容を一行分に組む、といった飾りを入れる場所です。[⟨after-code⟩](省略可) — 題の 直後 に実行するコード(hang/block/displayでは垂直モード、runin/leftmarginでは水平モード)。
| shape | 効果 |
|---|---|
hang | 既定値。番号をぶら下げ、題をその右にそろえる(標準の \section と同じ) |
block | 番号も題も一つの段落として組む。中央揃えの見出しに向きます |
display | 番号を別行(別段落)にして題の上に置く(標準の \chapter と同じ) |
runin | 行内見出し。改行せず本文が同じ行に続く(標準の \paragraph と同じ) |
leftmargin | 見出しを左余白の中に置く。rightmargin は右余白版 |
frame | display に似るが、見出しを罫線で囲む |
drop | 見出しの右下に本文を回り込ませる。wrap は見出しの幅を中身に合わせる版 |
\titlespacing と星の意味——なぜ \titlespacing* を使うのか
星は 見出し直後の段落の字下げを止める 印です。前の節で見た \@startsection の「上の空きが負なら字下げしない」という約束を、そのまま星に翻訳したものだと考えれば覚えやすいでしょう。標準クラスの \section は上の空きが -3.5ex ですから、同じ見た目を保ちたければ星付きを使う——それが「ほぼ常に \titlespacing*」の理由です。引数は左余白の追加分、見出しの上の空き、見出しと本文のあいだの空き、そして省略可能な右余白の追加分の四つ。leftmargin 系では第一引数が見出しの幅、runin では見出し直前の字下げを表します。なお drop・wrap・runin の形では字下げ抑制がそもそも意味を持たないので、星の有無は効きません。
\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 で 明示的に 置く必要が生まれます——題の前後に飾りを付けたいときのための仕掛けです。
\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}目次と柱もそろえる——titletoc と titleps
見出しを作り変えたら、目次と柱もそろえたくなります。titlesec は同じ配布物に姉妹パッケージを二つ抱えていて(単独でも使えます)、目次側が titletoc、ページスタイル側が titleps です。titletoc の中心は目次項目を完全に定義する \titlecontents と、点線リーダー付きの定番を一発で作る \dottedcontents。引数の先頭 ⟨section⟩ は バックスラッシュなしの名前(section・chapter・figure など)である点だけ注意してください。titleps は \newpagestyle でページスタイルを丸ごと定義し、\sethead/\setfoot で左・中央・右の三枠を埋めます。柱に載せる見出し名の取り方は \settitlemarks で制御でき、\bottitlemarks・\toptitlemarks・\firsttitlemarks などで「そのページのどの見出しを使うか」まで選べます。ヘッダ・フッタそのものの作り方(fancyhdr を含む)は別ページの担当です。
\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 は声を上げます。 scrartcl に titlesec を読み込むと、まず 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} のように書きます(クラス名は top・page・straight の三つ。それ以外を渡すと Package titlesec Error: Unknown sectioning class になります)。\assignpagestyle{\chapter}{plain} は特定の見出しが始まるページのページスタイルを指定します。なお、作者の Javier Bezos は多言語組版パッケージ babel の現在の保守者でもあり、箇条書きを制御する enumitem の作者でもあります——LaTeX で見出しと箇条書きと言語切り替えを触っていると、同じ人の書いたコードに三度出会うことになります。