제목 서식(titlesec)

LaTeX 제목의 체재는 놀랄 만큼 좁은 자리에서 정해집니다. article.cls 302행부터 시작하는 단 네 줄, 즉 \@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이므로 행내 제목이 됩니다. 길이의 부호 하나로 동작을 바꾸는, 지극히 1980년대 TeX다운 설계입니다. 구현에도 증거가 있습니다. titlesec이 클래스 정의를 읽어 되돌리는 루틴은 네 번째가 음수면 별표 붙은 \titlespacing*을, 다섯 번째가 음수면 runin 형태를 고르고 각각 부호를 뒤집습니다.

제목 명령article에서의 계층book / report에서의 계층
\part0−1
\chapter(존재하지 않습니다)0
\section11
\subsection22
\subsubsection33
\paragraph4(기본적으로 행내 제목)4
\subparagraph5(위와 같음)5

즉 계층은 article에서 여섯 단(0〜5), \chapter가 더해지는 book·report에서 일곱 단(−1〜5)입니다. 번호를 매기는 깊이는 \c@secnumdepth가 쥐고 있으며, 실측하면 article3, book·report2입니다. 그래서 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를 그대로 수치화한 값입니다(plus·minus는 페이지 아래끝을 고르게 맞추기 위해 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 sc / big medium small tiny / raggedleft 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⟩](생략 가능) — 제목의 “형태”입니다. hang, block, display, runin, leftmargin, rightmargin, frame, drop, wrap 중에서 고르며, 생략하면 hang입니다.
  • {⟨format⟩} — 번호와 제목 양쪽 에 적용되는 서식입니다. 글꼴과 크기를 여기서 함께 지정하고, 정렬은 \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은 오른쪽 여백 버전입니다
framedisplay와 비슷하지만 제목을 테두리로 감쌉니다
drop제목 주위로 본문이 감싸 흐르게 합니다. wrap은 제목 상자를 내용 너비에 맞추는 변형입니다

\titlespacing과 별표의 의미 — 왜 \titlespacing*를 쓰는가

별표는 제목 바로 뒤 단락의 들여쓰기를 막는 표시입니다. 앞 절에서 본 \@startsection의 “위 간격이 음수면 들여쓰지 않는다”는 약속을 그대로 별표로 옮긴 것이라고 생각하면 기억하기 쉽습니다. 표준 클래스의 \section은 위 간격이 -3.5ex이므로, 같은 모습을 유지하려면 별표 붙은 형태를 씁니다. “거의 언제나 \titlespacing*”이라는 조언은 그래서 나옵니다. 인수는 왼쪽 여백 추가분, 제목 위 간격, 제목과 본문 사이 간격, 그리고 생략 가능한 오른쪽 여백 추가분 넷입니다. leftmargin 계열에서는 첫 인수가 제목의 폭, runin에서는 제목 바로 앞의 들여쓰기를 뜻합니다. 한편 drop·wrap·runin 형태에서는 억제할 들여쓰기 자체가 없으므로 별표의 유무가 효과를 내지 않습니다.

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}만 쓰고 장 뒤 본문의 위치를 재니 305.1077pt로, 아예 쓰지 않았을 때와 1sp도 다르지 않았습니다. 같은 파일에 \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⟩백슬래시 없는 이름(section, chapter, figure 등)이라는 점입니다. 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}처럼 씁니다(클래스 이름은 top, page, straight 셋뿐이며 그 밖의 값을 넘기면 Package titlesec Error: Unknown sectioning class가 납니다). \assignpagestyle{\chapter}{plain}은 특정 제목이 시작되는 페이지의 페이지 스타일을 지정합니다. 그리고 작자에 관한 한마디. Javier Bezos는 다국어 패키지 babel 의 현재 유지 관리자이자, 목록 체재를 제어하는 enumitem 의 작자이기도 합니다. LaTeX에서 제목과 목록과 언어 전환을 만지다 보면 같은 사람이 쓴 코드를 세 번 만나게 되는 셈입니다.