목차/그림 목록 및 표 목록(tocloft)

PDF 첫 페이지에 인쇄된 목차를 쓴 것은 방금 돌린 그 컴파일이 아닙니다. \tableofcontents, \listoffigures, \listoftables는 각각 직전 LaTeX 실행이 남겨 둔 작은 파일——.toc, .lof, .lot——을 읽습니다. 게다가 이 파일들은 글자를 담아 둔 캐시가 아니라 항목마다 한 줄인 짧은 프로그램이며, 다음 실행이 그것을 실행합니다. 이 한 가지만 붙잡으면 목차를 둘러싼 대부분의 혼란이 설명됩니다. 새 문서에서 목차가 비어 있는 이유, 처음에만 쪽 번호가 어긋나는 이유, 제목 안의 각주가 하필 두 번째 컴파일에서 터지는 이유, 그리고 LaTeX가 “그 목차는 낡았다”고 한 번도 경고하지 않는 이유까지 말입니다.

.toc·.lof·.lot 파일에는 무엇이 들어 있나

한 항목에 한 줄, 모두 \contentsline 명령입니다. 세 목록은 같은 장치를 공유합니다. \tableofcontents.toc, \listoffigures.lof, \listoftables.lot을 맡고, 파일 이름은 모두 루트 파일과 같습니다. \contentsline은 인수를 넷 받습니다. 항목의 종류, 인쇄할 문자열, 쪽 번호, 그리고 링크 대상입니다. 네 번째는 순수 LaTeX에서는 비어 있고, hyperref를 부르면 section.1.1 같은 PDF 목적지가 채워집니다. 즉 .toc는 목차의 초안이 아니라 다음 실행에 건네는 명령의 나열입니다.

mydoc.toc
% 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}%

그런데 이 줄들은 목차 파일에 곧바로 쓰이지 않습니다. 먼저 .aux\@writefile{toc}{...} 형태로 쌓이고, \end{document} 시점에 LaTeX가 .aux를 닫고 다시 읽을 때에야 .toc로 흘러 들어갑니다. 이 우회에는 실제적인 결과가 둘 있습니다. 첫째, 쓰기 스트림을 여는 것은 \tableofcontents 자신이므로 그 명령이 문서 어디에도 없으면 .toc는 만들어지지 않습니다. 항목은 .aux 안에 그대로 남습니다. 둘째, 쓰기가 맨 마지막에 한꺼번에 일어나므로 \tableofcontents는 문서 어디에 두어도 됩니다. 맨 뒷장에 두어도 그 위쪽 제목까지 모두 실린 완전한 목차가 나옵니다.

명령쓰는 파일항목의 출처
\tableofcontents.toc\chapter부터 \subparagraph까지의 제목과 \addcontentsline{toc}{...}
\listoffigures.loffigure 환경 안의 \caption. 짧은 선택 인수를 주면 그쪽
\listoftables.lottable 환경 안의 \caption. 구조는 .lof와 완전히 같습니다
\addcontentsline지정한 확장자손으로 쓴 한 줄. 쪽 번호는 그 순간의 \thepage
\addtocontents지정한 확장자항목이 아니라 소재를 넣습니다. 여백, 서식 명령 등

목차가 비는 이유——그리고 LaTeX가 경고하지 않는 이유

첫 실행 시점에는 읽을 .toc가 아직 없기 때문입니다. 로그에는 No file mydoc.toc. 한 줄이 나오고, \tableofcontents는 제목만 조판한 뒤 지나갑니다. 파일이 쓰이는 것은 그 실행의 이므로 내용이 종이에 오르는 것은 두 번째입니다. 게다가 두 번째 실행에서는 목차 자체가 지면을 차지해 그 뒤의 쪽 번호가 밀리고, 세 번째 실행에서야 안정되기도 합니다. 한 번은 정보를 저장하고 다른 한 번은 꺼내 오는 이 이중 구조는 \label\ref의 방식과 똑같으며, 자세한 내용은 상호 참조 페이지에 맡깁니다.

그리고 좀처럼 언급되지 않는 나머지 절반이 있습니다. LaTeX는 이 지연을 한 번도 경고하지 않습니다. 정의되지 않은 참조가 있으면 LaTeX Warning: Label(s) may have changed. Rerun to get cross-references right.가 나오지만, 그것은 label을 .aux의 이전 값과 대조하기 때문입니다. 목차에는 그런 대조가 없습니다. 지면에 인쇄된 목차와 방금 쓴 .toc가 어긋나도 로그는 아무 말도 하지 않습니다. 실제로 목차가 빈 채 출력된 실행의 로그를 뒤져 봐도 경고는 한 건도 없습니다. 그래서 실행 횟수를 직접 세는 대신 latexmk 같은 빌드 도구에 맡기는 것이 정석입니다. .toc의 내용이 더 이상 바뀌지 않을 때까지 알아서 반복합니다.

같은 침묵이 더 고약한 형태로도 나타납니다. report로 쓰던 원고를 article로 바꾸면 옛 .toc에는 \contentsline {chapter}{...} 줄이 그대로 남아 있습니다. article에는 \l@chapter가 없고, \contentsline\csname l@chapter\endcsname을 부를 뿐이라 정의되지 않은 이름은 조용히 \relax가 됩니다. 그래서 제목과 쪽 번호가 그대로 본문처럼 목차에 쏟아집니다. 오류도 경고도 없이 “1 Alpha2” 같은 알 수 없는 줄만 남습니다. 문서 클래스나 디렉터리 구성을 바꾼 뒤 목차가 깨져 보이면, .toc(와 .aux)를 지우고 다시 조판하는 것이 가장 빠릅니다.

tocdepth는 하나뿐——그림 목록을 비워 버리는 설정

tocdepth는 목차에 인쇄할 가장 깊은 단계를 가리키는 카운터입니다. \setcounter{tocdepth}{1}이면 절까지, {2}이면 소절까지 인쇄됩니다. 기본값은 article에서 3, bookreport에서 2입니다. 그런데 이것은 목차만의 카운터가 아닙니다. article.clsbook.cls를 보면 \l@figure\@dottedtocline{1}{1.5em}{2.3em}입니다. 즉 그림 목록의 각 항목은 레벨 1로 조판되며, \l@table은 그 별칭입니다. 그리고 \@dottedtocline이 비교하는 대상은 세 목록이 함께 쓰는 단 하나의 tocdepth입니다.

결과는 짓궂습니다. book에서 “목차는 장만 싣자”고 생각해 \setcounter{tocdepth}{0}을 쓰면 그림 목록과 표 목록이 비어 버립니다. .lof에는 항목이 빠짐없이 들어 있는데도 레벨 1이 0인 tocdepth를 넘어서기 때문에 한 줄도 인쇄되지 않습니다. 오류는 나지 않습니다. 해결은 간단합니다. \listoffigures를 그룹으로 감싸고 그 안에서만 tocdepth를 올리면 됩니다. 한편 tocdepth파일을 다시 읽을 때 걸러 낸다는 점, 그래서 깊이를 바꿔도 .toc를 다시 만들 필요가 없고 실행 한 번만 더 하면 된다는 점은 문서 구조 페이지가 다룹니다.

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

목차에 실리는 것은 짧은 쪽——\section[...]의 선택 인수

대괄호에 쓴 짧은 쪽이 .toc에 들어가고, 중괄호의 긴 쪽은 본문에만 나타납니다. \section[짧은 제목]{지면 위에 길게 펼쳐지는 긴 제목}처럼 쓰면 본문 제목은 긴 채로 두고 목차와 쪽머리에는 짧은 제목이 실립니다. \caption[짧은 캡션]{긴 설명}도 같은 규칙이며, .lof.lot에 들어가는 것은 짧은 쪽입니다. 캡션 쪽의 자세한 작법은 그림 캡션 페이지에 맡깁니다. 여기서 중요한 것은 이 선택 인수가 겉모습을 다듬기 위한 사치품이 아니라는 점입니다.

제목의 내용은 .toc써 나갑니다. 곧 파일에 부어 넣었다가 다음 실행에서 다시 읽습니다. 그래서 \section{주가 달린 제목\footnote{주}}처럼 깨지기 쉬운 명령을 넣으면 첫 번째 컴파일은 아무 말 없이 지나가고, 두 번째에 파일을 되읽는 순간 무너집니다. Runaway argument?에 이어 ! Paragraph ended before \contentsline was complete., 그리고 ! Argument of \@sect has an extra }.——제목과는 아무 상관도 없어 보이는 얼굴을 하고 있지만, 범인은 방금 쓰인 .toc 안의 각주입니다. 오류가 한 번 늦게 오는 이유는 목차가 한 번 늦게 오는 이유와 똑같습니다. 처방은 선택 인수입니다. \section[주가 달린 제목]{주가 달린 제목\footnote{주}}라고 쓰면 각주가 .toc에 들어가지 않아 다시는 깨지지 않습니다.

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

“제목은 세 곳에서 쓰인다”는 같은 사정이 hyperref를 불러오면 다른 모습으로 다시 나타납니다. 제목은 PDF 책갈피에도 전용되는데, 책갈피는 순수한 문자열이라 수식이 들어가지 못합니다. \section{$\mathcal{A}$의 성질}이라고 쓰면 Package hyperref Warning: Token not allowed in a PDF string (Unicode)가 나오고 수식은 조용히 떨어져 나갑니다. 탈출구는 \texorpdfstring{$\mathcal{A}$}{A}로, 조판용과 문자열용을 따로 건네는 방식이며 hyperref 페이지에서 다룹니다.

별표 제목을 목차에 싣기——addcontentsline과 그 자리

\addcontentsline{toc}{section}{들어가며}를 제목 바로 뒤 줄에 둡니다. \section*이나 \chapter*는 번호가 없고 .toc에 아무것도 쓰지 않으므로, 목차에 넣고 싶다면 그 한 줄을 직접 넣는 수밖에 없습니다. 인수 셋은 모두 필수입니다.

  • ext — 대상 보조 파일의 확장자입니다. 목차는 toc, 그림 목록은 lof, 표 목록은 lot입니다.
  • unit — 항목의 종류입니다. toc에서는 part, chapter, section, subsection 등이며 그 단계의 서식과 들여쓰기가 쓰입니다. lof에서는 figure, lot에서는 table입니다.
  • text — 목록에 실을 문자열입니다. 앞에 \protect\numberline{}을 붙이면 번호 붙은 항목과 제목 위치가 맞고, 깨지기 쉬운 명령 앞에는 \protect를 붙입니다.

어디에 두느냐가 결과를 정합니다. latex.ltx의 정의를 열어 보면 \addcontentsline\contentsline{unit}{text}{\thepage}{}을 써 낼 뿐입니다. 곧 그 줄이 실행되는 순간의 쪽 번호를 그대로 새겨 넣습니다. \chapter*는 새 쪽을 시작하므로 무심코 \chapter* 앞에 두면 목차에는 한 쪽 전의 번호가 실립니다. 실험 결과는 노골적입니다. 앞에 둔 항목은 2쪽을, 바로 뒤에 둔 항목은 3쪽을 가리켰습니다. 독자는 그 쪽을 펼치고 거기에 장이 없다는 것을 알게 됩니다. 쪽 번호는 LaTeX가 채워 주므로 text에 직접 쓸 필요가 없습니다.

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

짝이 되는 \addtocontents{ext}{text}는 행이 아니라 소재를 끼워 넣습니다. 인수는 대상 확장자와 쓸 내용 둘뿐이고 쪽 번호는 붙지 않습니다. .lof를 들여다보면 \addvspace {10\p@ }라는 줄이 보일 것입니다. 장이 바뀔 때마다 LaTeX 자신이 바로 이 방식으로 여백을 넣고 있는 것입니다. 요컨대 쪽 번호가 붙는 행은 \addcontentsline, 여백과 서식은 \addtocontents 입니다. 둘 다 다음 실행을 위해 쓰는 것이므로 \vspace 같은 깨지기 쉬운 명령에는 \protect가 필요합니다.

목록 자신을 목차에 싣기 — tocbibind

\usepackage{tocbibind} 한 줄이면 그림 목록, 표 목록, 참고문헌, 색인이 저절로 목차에 실립니다. 이 제목들은 번호가 없어서——article에서는 \section*, bookreport에서는 \chapter*——그냥 두면 목차에 나오지 않습니다. 손으로 \addcontentsline을 늘어놓는 방법도 있지만, 여러 쪽에 걸치는 참고문헌이나 색인에서는 놓을 자리를 틀리기 쉬워 패키지에 맡기는 편이 안전합니다.

기본값에서는 목차 자신도 목차에 실리므로, 대부분의 사람이 가장 먼저 찾는 것이 nottoc 옵션입니다. 제외용 옵션은 nottoc, notlof, notlot, notbib, notindex 다섯입니다. 반대로 numbib, numindex를 주면 참고문헌과 색인이 번호 없는 제목이 아니라 번호 붙은 장이나 절로 조판됩니다. TeX Live 2024에 들어 있는 tocbibind는 2010년의 v1.5k이며, Peter Wilson이 만들었습니다. tocloft와 같은 저자입니다.

latex
% list everything except the contents itself
\usepackage[nottoc]{tocbibind}

% the manual equivalent, one line per list
\cleardoublepage
\addcontentsline{toc}{chapter}{\listfigurename}
\listoffigures

tocloft로 들여쓰기·글꼴·점선 리더 다듬기

\usepackage{tocloft}를 불러오면 단계마다 들여쓰기, 번호 폭, 글꼴, 점선 리더를 따로 설정할 수 있습니다. 명령 이름은 규칙적입니다. 단계를 나타내는 접두사(parttoc, chapterchap, sectionsec, subsectionsubsec, 그림은 fig, 표는 tab)에 역할을 붙이면 됩니다. 들여쓰기와 번호 폭은 \cftsetindents{section}{1.5em}{2.5em}처럼 함께 지정하며, 번호 자릿수가 늘어 제목과 부딪히면 세 번째 인수를 넓힙니다. 글꼴은 항목 제목(\cftsecfont)과 그 쪽 번호(\cftsecpagefont)가 별개입니다.

점선 리더에는 이름만 보고는 짐작하기 어려운 장치가 있습니다. 점의 간격은 길이 \cftdotsep(기본 4.5)이 정하며, 작게 하면 촘촘해지고 크게 하면 성겨집니다. 그리고 리더를 없앨 때 쓰는 \cftnodots는 플래그가 아닙니다. tocloft.sty 안에서 그것은 그저 5000이라는 수입니다. 한 줄에 점이 하나도 들어가지 못할 만큼 넓은 간격일 뿐입니다. 같은 장치가, 수없이 보고도 눈치채지 못한 사실 하나를 설명합니다. \cftpartdotsep\cftchapdotsep의 기본값이 \cftnodots이며, 그래서 표준 목차에서는 부와 장의 줄에만 점선이 없습니다.

명령역할쓰는 법
\cftsetindents해당 단계의 들여쓰기와 번호 폭\cftsetindents{section}{1.5em}{2.5em}
\cftsecfont절 항목 제목의 글꼴\renewcommand{\cftsecfont}{\bfseries}
\cftsecpagefont절 항목 쪽 번호의 글꼴장이라면 \cftchappagefont
\cftsecleader절 항목의 점선 리더안의 \cftdotfill{\cftdotsep}을 바꿉니다
\cftdotsep점 간격. 기본 4.5, 작을수록 촘촘함\renewcommand{\cftdotsep}{2}
\cftnodots5000이라는 수. 점이 하나도 들어가지 못하는 간격리더를 완전히 없앨 때 씁니다
\cftloftitlefont그림 목록 제목 자체의 글꼴목차라면 \cfttoctitlefont
latex
\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}}

tocloft로 부족해지면 — titletoc과 etoc

tocloft가 기존 행의 치수와 글꼴을 손보는 데 비해, titletocetoc은 행의 구조 자체를 다시 씁니다. titletoc(Javier Bezos 작성, titlesec과 같은 묶음에 들어 있습니다)의 핵심은 \titlecontents로, 단계마다 행 앞에 놓을 소재, 번호를 조판하는 방식, 제목, 쪽 번호까지 이어지는 채움, 그리고 행 뒤를 차례로 정의합니다. 단순한 점선이면 충분할 때는 \dottedcontents라는 축약형도 있습니다. 나아가 \startcontents, \printcontents, \stopcontents, \resumecontents를 쓰면 장 머리에 그 장만의 부분 목차를 둘 수 있습니다. 제목 모양을 이미 titlesec으로 다듬은 문서라면 목차도 같은 어법으로 맞출 수 있다는 것이 강점입니다.

etoc(Jean-François Burnol 작성)은 한 걸음 더 나아가, “행 스타일”과 “전체 스타일”이라는 두 층의 틀로 목차를 통째로 다시 설계합니다. 간판 기능은 \localtableofcontents로, 같은 .toc에서 장별 부분 목차를 몇 번이든 꺼낼 수 있습니다. 여기까지 오면 트리 모양 목차 같은 표현도 사정권에 들어옵니다. 판단 순서로는 세 단계가 실무에서 잘 통합니다. 먼저 tocloft로 치수와 글꼴을 맞추고, 행의 조립 방식을 바꿔야 하면 titletoc으로, 목차 설계 자체를 떠맡고 싶다면 etoc으로 넘어가는 것입니다.