enumitem(서식, 카운터, 인라인)

LaTeX의 목록이 지나치게 헐렁하다 — enumitem 패키지에 손을 뻗는 까닭은 대개 이것이지만, 실제 수치를 확인해 본 사람은 드뭅니다. article 클래스의 기본값에서 항목과 항목의 베이스라인 간격은 20pt로, 보통 행간 12pt에 견주면 항목 사이에만 8pt가 더 들어갑니다. 그 8pt의 정체는 \itemsep(4pt)와 \parsep(4pt)라는 두 길이이고, enumitem 패키지는 이것들을 itemsep=0pt 같은 키=값(key=value) 옵션으로 곧바로 고쳐 쓰기 위한 도구입니다. 이 페이지에서는 기본값이 실제로 얼마인지, nosepleftmargin=*이 무엇을 바꾸는지, \setlist로 문서 전체의 방침을 어떻게 정하는지를 모두 실측값과 함께 살펴봅니다.

LaTeX 목록의 기본값은 실제로 얼마인가

article 클래스(10pt, 제1계층)에서 재면 \itemsep은 4pt, \parsep은 4pt, \topsep은 8pt, \leftmargin은 25pt, \labelwidth는 20pt, \labelsep은 5pt입니다. 이 숫자를 알면 "헐렁해 보인다"는 말의 정체가 분명해집니다. 항목 사이에 들어가는 세로 여백은 \itemsep\parsep을 더한 모두 8pt이고, 행간 12pt를 더하면 20pt가 됩니다. 12pt 격자로 짜인 본문 속에서 목록만 항목당 1.67행 간격으로 조판되는 셈입니다. 조판으로 보면 의도된 설계입니다. 항목은 시각적으로 갈라져야 하니까요. 다만 짧은 항목 열 개를 늘어놓으면 지면이 성기게 보입니다. 가로도 사정은 같습니다. \leftmargin 25pt에 견주어 글머리 기호 자체는 5pt가 채 안 되니, 그 들여쓰기 가운데 20pt는 그저 빈 공간입니다.

길이(= 키 이름)article 기본값(제1계층)작용하는 곳
itemsep4pt(plus 2pt minus 1pt)항목과 항목 사이
parsep4pt(plus 2pt minus 1pt)한 항목 안의 문단과 문단 사이
topsep8pt(plus 2pt minus 4pt)목록 전체의 위와 아래
partopsep2pt(plus 1pt minus 1pt)목록이 새 문단으로 시작할 때만 더해지는 몫
leftmargin25pt항목 본문 블록 전체의 왼쪽 들여쓰기
labelwidth20pt레이블을 담는 상자의 너비. 더 넓은 레이블은 왼쪽으로 넘칩니다
labelsep5pt레이블 상자와 본문 사이
rightmargin0pt오른쪽 여백. 인용문처럼 목록 전체를 좌우에서 좁히고 싶을 때 씁니다
itemindent0pt항목의 첫 줄에만 더해지는 들여쓰기. 음수 값이면 왼쪽으로 내밉니다
labelindent—(enumitem이 추가한 키)바깥쪽에서 레이블 상자 왼쪽 끝까지의 여백

계층이 깊어지면 값은 줄어듭니다. itemize 제2계층에서는 \itemsep\parsep이 2pt, \topsep이 4pt, \leftmargin이 22pt로 내려가고, 제3・제4계층에서는 \parsep이 0pt, \topsep이 2pt까지 떨어지며 \leftmargin도 18.7pt, 17pt로 가늘어집니다. 즉 LaTeX은 애초에 "얕은 계층일수록 크게 벌린다"는 방침을 갖고 있고, 깊은 중첩은 이미 촘촘합니다. 정작 조이고 싶은 곳은 대개 제1계층뿐이라는 실감이 여기서 나옵니다.

키=값으로 쓰는 법 — 키 이름은 LaTeX의 길이 그 자체

전처리부에서 \usepackage{enumitem}으로 불러오면 itemize, enumerate, description 세 환경이 선택 인수를 받을 수 있게 되고, 대괄호 안에 키=값을 쉼표로 나열하기만 하면 그 목록의 체재가 정해집니다. 이 설계의 핵심은 키 이름이 위 표의 길이 이름과 그대로 같다는 점입니다. itemsep=1ex\itemsep을, leftmargin=1cm\leftmargin을 고쳐 씁니다. 즉 enumitem을 익히는 일이 곧 LaTeX 목록의 내부를 익히는 일이 됩니다. \renewcommand로 내부 명령을 덮어쓰던 예전 방식에 견준 가장 큰 장점이 여기에 있습니다. TeX Live 2024에 함께 실린 것은 Javier Bezos의 v3.9(2019/06/20)이며, kpsewhich enumitem.sty로 손안의 판본을 확인할 수 있습니다.

document.tex
\usepackage{enumitem}
% ...
\begin{itemize}[itemsep=1ex, leftmargin=1cm]
  \item the key names are the LaTeX length names
  \item second entry
\end{itemize}

같은 키는 문서 전체, 환경 종류별, 개별 목록이라는 세 단계로 덮어쓸 수 있습니다(뒤에 나올 \setlist). 가장 구체적으로 준 값이 이기므로, 먼저 전체 방침을 정해 두고 꼭 필요한 곳에서만 국소적으로 벗어나는 정리가 잘 듣습니다. 값이 통째로 중괄호에 싸여 있으면 바깥쪽 한 겹이 벗겨지므로(keyval의 기본 동작), 중괄호 자체를 남기고 싶을 때는 두 겹으로 쓰십시오.

목록을 조이기 — nosep과 noitemsep의 차이

nosep은 세로 여백을 넷 모두 0으로 만들고, noitemsep둘만 0으로 만듭니다. 패키지 소스를 보면 nosep\partopsep, \topsep, \itemsep, \parsep 네 개를 모두 0pt로, noitemsep\itemsep\parsep 두 개만 0pt로 둡니다. 잊히기 쉬운 것이 partopsep인데, 목록이 빈 줄 뒤에서 새 문단으로 시작할 때만 더해지는 2pt입니다. nosep을 준 itemize를 실측하면 항목 사이 베이스라인 간격이 정확히 12pt로, 본문의 보통 행과 똑같아집니다. 목록 앞뒤의 여백도 사라지므로 지문에 온전히 녹아듭니다.

document.tex
% nosep  -> partopsep = topsep = itemsep = parsep = 0pt
% noitemsep -> itemsep = parsep = 0pt, list keeps its 8pt topsep
\begin{itemize}[nosep]
  \item item baselines land exactly 12pt apart
  \item as tight as ordinary body text
\end{itemize}

무엇을 고를지는 그 목록을 지문의 일부로 읽힐 것인지에 달렸습니다. 항목 자체가 주인공인 경우 — 작업 절차서나 슬라이드 — 라면 nosep으로 충분합니다. 그러나 논문 본문에 끼어드는 짧은 목록에는 noitemsep이 더 안전합니다. 위아래 8pt가 남으므로 문단과 목록의 경계가 눈에 남기 때문입니다. 위아래까지 조이면 목록이 앞뒤 문단에 달라붙어, 열거가 어디서 시작해 어디서 끝나는지 읽어 내기 어려워집니다. 한편 nolistsep이라는 옛 키도 있는데, topsep을 완전한 0이 아니라 0pt plus .1pt로 둔다는 점만 nosep과 다릅니다. 새로 쓴다면 nosep을 쓰십시오.

label=과 ref= — 레이블과 상호 참조를 따로 정하기

목록의 레이블은 label=으로 정합니다. 값 안에서는 \arabic*, \alph*, \Alph*, \roman*, \Roman*라는 별표가 붙은 형태가 그 계층의 현재 카운터를 뜻합니다. label=(\arabic*)이면 (1) (2) (3), label=\alph*)이면 a) b) c), label=\Roman*.이면 I. II. III.가 됩니다. 별표 형태는 인수를 받지 않으며, 레이블 안에서만 뜻을 갖는 특별한 표기라고 여기면 됩니다. 또한 label*=은 부모의 레이블에 이어 붙이므로, 중첩하면 1., 1.1., 1.2.처럼 쌓입니다.

document.tex
\begin{enumerate}[label=(\arabic*), ref=\arabic*]
  \item \label{step:one} prepare the ingredients
  \item heat the pan
\end{enumerate}
Go back to step~\ref{step:one}.   % prints "step 1", not "step (1)"

기본값에서는 label=의 지정이 상호 참조의 모습에까지 미칩니다. [label=(\arabic*)]만 붙인 목록의 항목에 \label을 달아 \ref 하면 본문에는 괄호가 붙은 (1)이 나옵니다. "절차 (1)로 돌아간다"에서 괄호가 겹쳐 보이는 실무상의 성가심이 여기서 생깁니다. ref=\arabic*을 더하면 레이블은 (1) 그대로이면서 \ref의 반환값만 맨 1이 됩니다. 위 코드를 실제로 조판해 확인한 결과가 정확히 이러했습니다. 한편 labelref의 값은 이동 인수로 취급되므로, 깨지기 쉬운 명령을 넣을 때는 \protect가 필요할 수 있습니다. 별표가 붙은 카운터 표기 자체는 그대로 써도 됩니다.

레이블을 옛 enumerate 패키지식의 짧은 표기로 쓰고 싶다면 \usepackage[shortlabels]{enumitem}으로 불러옵니다. 그러면 대괄호의 첫 요소A, a, I, i, 1을 적는 것만으로 각각 \Alph*, \alph*, \Roman*, \roman*, \arabic*을 뜻하게 되어 \begin{enumerate}[(1)]처럼 쓸 수 있습니다. 옛 원고를 enumerate 패키지에서 옮겨 올 때는 편리하지만, 새로 쓴다면 label=을 분명히 적어 두는 편이 나중에 다시 읽을 때 의도가 잘 드러납니다.

leftmargin=*과 wide — 왼쪽 여백을 계산하게 하기

가로 방향 키들은 서로 독립이 아니라 \leftmargin + \itemindent = \labelindent + \labelwidth + \labelsep이라는 하나의 등식으로 묶여 있습니다. 그래서 일부 키에 값 대신 *를 건네면 enumitem이 나머지를 풀어 채웁니다. 실측하면 효과가 극적입니다. 기본 itemize\leftmargin은 25pt이지만 [leftmargin=*]을 붙이면 9.99pt까지 줄어듭니다. 글머리 기호의 실제 너비만 남긴 값입니다. 정작 중요한 것은 반대 경우입니다. label=(\roman*) 같은 넓은 레이블에서는 "(viii)"가 기본 20pt 상자에 들어가지 못해 왼쪽으로 넘치지만, leftmargin=*을 더하면 상자가 레이블에 맞춰 늘어납니다. 넓은 레이블을 쓸 때는 leftmargin=*을 함께 적는다고 기억해 두면 사고가 줄어듭니다.

또 하나 편리한 키가 wide입니다. 왼쪽 여백과 레이블 상자의 너비를 0으로 만들어 레이블이 본문의 일부처럼 읽히게 하고, 항목을 보통 문단과 같은 모양으로 짭니다. 소스를 보면 \leftmargin\labelwidth를 0으로 둔 뒤 인수(기본값 \parindent)를 \labelindent에 넣고 \listparindent를 거기에 맞추며 \itemindent를 등식으로 계산합니다. 실측에서도 \leftmargin은 0pt, \itemindent는 20pt, \listparindent는 15pt(= \parindent)로 나왔습니다. 들여쓰기 양을 바꾸려면 wide=0pt처럼 값을 줍니다. 목록이 왼쪽으로 튀어나오지 않기를 바라거나 본문 왼쪽 끝을 가지런히 맞추려는 판면 설계에서 잘 듣습니다. 함께 알아 두면 좋은 것이 align=으로, 레이블을 상자 안에서 왼쪽 맞춤(left), 오른쪽 맞춤(right, 기본), 왼쪽 맞춤 parbox(parleft) 가운데 무엇으로 둘지 정합니다.

번호를 5부터 시작하기, 앞 목록에서 이어 가기(start・resume・series)

start=은 첫 항목의 번호를 지정하고, resume은 바로 앞 enumerate의 뒤를 이어 번호를 매깁니다. 절차 중간에 설명 문단을 끼우고 그다음 "7."부터 다시 시작하고 싶은 상황은 대단히 흔한데, 이를 손수 \setcounter로 처리하면 나중에 항목을 하나 더 넣는 순간 번호가 어긋납니다. 실제로 조판해 보니 [start=5]는 5, 6을 냈고, 문단을 사이에 둔 뒤의 [resume]은 7로 이어졌습니다. resume*을 쓰면 번호뿐 아니라 앞 목록의 옵션 설정까지 물려받습니다(이 별표 형태는 환경의 대괄호 안에서만 뜻을 가집니다).

document.tex
\begin{enumerate}[start=5]
  \item numbered 5
  \item numbered 6
\end{enumerate}
An explanatory paragraph goes here.
\begin{enumerate}[resume]
  \item numbered 7 -- the count carries over
\end{enumerate}

resume은 바로 앞 목록만 보므로, 절을 넘나들며 멀리 떨어진 여러 목록에 하나의 통번호를 매기려 할 때는 부족합니다. 그럴 때는 시리즈를 씁니다. 첫 목록에 series=이름이라는 이름표를 달아 두고 이후 목록에 resume=이름 또는 resume*=이름을 지정하면, 사이에 무엇이 들어가든 번호가 이어집니다. 긴 작업 지침서를 절 단위로 나눠 쓸 때 제대로 버티는 유일한 방법입니다.

\setlist으로 문서 전체에 적용하고 \newlist로 나만의 목록 만들기

같은 옵션을 매번 쓰는 것은 현실적이지 않습니다. \setlist은 목록의 종류와 계층을 지정해 설정을 한꺼번에 입히는 명령입니다. 대괄호 없는 \setlist{...}는 모든 목록의 모든 계층에, \setlist[itemize]{...}itemize의 전 계층에, \setlist[enumerate,1]{...}enumerate의 제1계층에만 듣습니다. 별표가 붙은 \setlist*은 그때까지의 설정을 덮어쓰지 않고 덧붙입니다. 이 페이지 첫머리에서 본 헐렁함을 문서 전체에서 고치고 싶다면, 전처리부에 \setlist{noitemsep} 한 줄을 적는 것이 가장 비용 대비 효과가 큰 수입니다.

preamble
\usepackage{enumitem}
\setlist{noitemsep}                    % tighten every list in the document
\setlist[itemize]{leftmargin=*}        % let itemize size its own margin
\setlist[enumerate,1]{label=(\arabic*), ref=\arabic*}

% a list type of your own
\newlist{steps}{enumerate}{1}
\setlist[steps]{label=\textbf{Step \arabic*.}, leftmargin=*}

한 걸음 더 나아가면 자기만의 목록 유형을 정의할 수 있습니다. \newlist{이름}{유형}{최대 깊이}는 세 기본 목록(enumerateitemizedescription) 가운데 하나를 복제해 새 환경을 만들고, 이어지는 \setlist[이름]{...}이 그 체재를 줍니다(적어도 레이블은 설정해야 합니다). 위 코드를 실제로 조판하니 \begin{steps}가 "Step 1.", "Step 2."를 냈습니다. 이득은 겉모습보다 원고의 가독성에 있습니다. \begin{steps}라고 적혀 있으면 지면을 보지 않고도 그것이 절차임을 알 수 있습니다. 목록의 의미를 체재가 아니라 이름으로 나타내는 셈입니다.

문단 안에 늘어놓는 행내 목록(enumerate*)

패키지를 \usepackage[inline]{enumitem}으로 불러오면 enumerate*, itemize*, description*라는 별표가 붙은 환경이 더해지고, 항목들이 줄바꿈 없이 문단 안을 가로로 흘러갑니다. 실제로 조판하면 "We need (1) flour (2) sugar (3) salt for this."처럼 한 줄에 담깁니다. 서너 개의 짧은 어구를 헤아리기만 할 때는, 별행 목록으로 지면 네 줄을 쓰는 것보다 이 형태가 읽기 좋은 경우가 많고, 학술 문장에서 조건을 열거할 때는 오히려 이쪽이 표준적인 표기입니다.

document.tex
\usepackage[inline]{enumitem}
% ...
We need
\begin{enumerate*}[label=(\arabic*)]
  \item flour \item sugar \item salt
\end{enumerate*}
for this.

구분자는 itemjoin(항목 사이. 기본값은 공백), itemjoin*(마지막 항목 바로 앞만 다르게 하고 싶을 때 — 영어의 "a, b, and c" 같은 표기에 씁니다), afterlabel(레이블 바로 뒤. 기본값은 ~)로 다듬습니다. 다만 행내 목록의 항목은 기본적으로 상자에 담기므로 플로트, 중첩 목록, 별행 수식은 안에 둘 수 없습니다. 필요하면 mode=unboxed를 지정하십시오. 실무에서 그을 만한 선은 "행내 목록은 짧은 어구를 늘어놓는 용도에 한한다"입니다.