페이지를 넘기는 표(longtable)

table 환경에 넣은 표는 행이 몇 개든 페이지를 넘어 나뉘지 않습니다. 게다가 넘쳐도 오류가 나지 않습니다. TeX Live 2024에서 119행짜리 표를 부동체로 감싸 보니 출력은 세 페이지가 아니라 한 페이지였고, 남은 행들은 종이 아래로 조용히 사라졌습니다. 이 벽을 부수는 것이 LaTeX의 longtable 패키지입니다. 다만 longtable의 진짜 일은 페이지를 나누는 것이 아닙니다. 모든 페이지에서 열 너비를 맞추는 것이며, 그러기 위해 실측값을 .aux 파일에 적어 두고 Table widths have changed. Rerun LaTeX. 라는 경고로 두 번째 컴파일을 요구합니다. 이 페이지에서는 그 두 번째 컴파일이 무엇을 사 주는지를, 그것을 생략하는 supertabular와 실측으로 비교하며 살펴봅니다.

table 환경의 표가 페이지를 나누지 못하는 이유

tablefigure와 같은 부동체(float) 이고, LaTeX는 부동체를 “한 페이지에 들어가는 분할 불가능한 상자”로 다루기 때문입니다. 상자는 통째로 페이지 위나 아래로 옮겨지고, 통째로 옮기는 이상 중간에서 자를 수 없습니다. 그래서 안쪽 tabular가 아무리 길어져도 나뉘지 않고, 들어가지 않으면 종이 밖으로 나가 버립니다. 고약한 점은 그것이 경고로 드러나지 않는다 는 것입니다. 같은 119행 데이터로 실험하면 부동체로 감싼 쪽은 1페이지, 감싸지 않은 쪽은 3페이지였고 오류 메시지는 한 줄도 없었습니다. PDF를 끝까지 봐야 알아차릴 수 있는 종류의 사고입니다.

즉 “페이지를 넘는 표”는 부동체라는 구조 자체와 맞지 않습니다. 필요한 것은 표를 본문과 같은 흐름에 두고, 문단과 똑같이 TeX의 페이지 나눔 알고리즘에 맡기는 장치입니다. 그것이 longtable입니다. 저자는 array, tabularx, dcolumn을 만든 David Carlisle 이고, 모든 LaTeX 배포에 딸려 오는 tools 번들의 일원이므로 따로 설치할 것이 없습니다.

longtable 쓰는 법 — tabular와 무엇이 다른가

쓰는 법은 거의 tabular와 같습니다. \usepackage{longtable}을 읽어 들이고 \begin{longtable}{l l r}처럼 시작하며 — 열 지정은 필수 인수 입니다 — &로 열을 나누고 \\로 행을 닫습니다. l, c, r, p{width} 같은 열 타입도, \hline\multicolumn도 그대로 통합니다. 다른 점은 세 가지뿐 입니다. 첫째, table 환경으로 감싸면 안 됩니다. 감싸면 다시 부동체가 되어 앞의 한 페이지 문제가 되살아납니다. 둘째, 열 지정 에 배치용 선택 인수 [c], [r], [l]을 쓸 수 있습니다. 셋째, longtable은 자체 캡션을 가집니다(아래 참조). 페이지 나눔은 행과 행 사이 또는 \hline 자리에서만 일어나며, p 열의 한 셀 중간에서는 일어나지 않습니다.

위치와 앞뒤 여백은 \begin{document} 뒤에서 언제든 바꿀 수 있는 길이로 정해집니다. TeX Live 2024에서 실측한 기본값은 다음과 같습니다. \LTleft\LTright는 둘 다 0pt plus 1fill, 즉 양쪽에서 같은 양으로 밀어내므로 표가 가운데 에 옵니다. 왼쪽 맞춤으로 하려면 \setlength{\LTright}{0pt plus 1fill}을 남기고 \setlength{\LTleft}{0pt}로 둡니다. 표 위아래 여백은 \LTpre\LTpost이며 기본값은 둘 다 12pt plus 4pt minus 4pt입니다.

머리와 꼬리를 매 페이지 반복하기 — endhead와 endfoot

표 앞부분에 네 개의 마커 를 늘어놓고, 각각 “여기까지의 행을 어느 페이지에 반복할지”를 선언합니다. 마커는 해당 행의 끝에서 \\ 대신 놓습니다. 네 가지 모두 생략할 수 있고, 모든 페이지에 같은 머리글이면 \endhead 하나로 충분합니다.

마커앞의 행이 나타나는 곳전형적인 내용
\endfirsthead첫 페이지의 표 머리에만\caption{…}과 열 머리글
\endhead첫 페이지를 제외한 모든 페이지의 표 머리“(continued)”와 열 머리글
\endfoot다음으로 이어지는 페이지의 꼬리(마지막 제외)“Continued on next page” 행
\endlastfoot표의 맨 끝, 마지막 페이지에만마무리 선이나 주석
\kill어디에도 나오지 않음 — 폭 측정용가장 넓을 것으로 예상되는 더미 행
document.tex
\documentclass{article}
\usepackage{longtable}
\begin{document}

\begin{longtable}{l l r}
  % head of the first page only
  \caption{Annual sales log}\\
  \hline
  Date & Item & Amount \\
  \hline
  \endfirsthead

  % head of every later page
  \multicolumn{3}{l}{\small (continued)}\\
  \hline
  Date & Item & Amount \\
  \hline
  \endhead

  % foot of every page that continues
  \hline
  \multicolumn{3}{r}{\small Continued on next page}\\
  \endfoot

  % foot of the last page
  \hline
  \endlastfoot

  % everything below the markers is real data
  2026-01-05 & Apple  & 380 \\
  2026-01-06 & Orange & 120 \\
  % ...rows continue; pages are broken automatically...
\end{longtable}

\end{document}

순서가 중요합니다. 네 마커는 반드시 이 순서로 표 맨 위에 놓이고, 그 아래에 쓴 행이 진짜 데이터 로서 각 페이지에 흘러 들어갑니다. 뒤집어 말하면 표의 내용은 \endlastfoot 다음의 첫 행부터 시작합니다. 이 순서를 무너뜨리면 머리글로 삼으려던 행이 데이터 첫 행으로 나옵니다. \kill은 조금 다릅니다. 측정만 하고 인쇄하지 않는 행 을 만듭니다. 머리글은 짧은데 100쪽에서 한 번 아주 긴 값이 나오는 경우, 맨 앞에 최대 폭 견본을 담은 \kill 행을 두면 처음부터 넉넉한 열로 조판됩니다.

Table widths have changed. Rerun LaTeX. — 왜 두 번 컴파일하는가

각 페이지의 열 너비를 맞추기 위해서입니다. longtable은 표를 페이지 단위로 조판하는데, 어떤 페이지에서 가장 넓은 셀이 무엇인지는 그 페이지를 다 조판한 뒤에야 알 수 있습니다. 페이지마다 따로 조판하면 페이지마다 열 너비가 달라집니다. 그래서 longtable은 측정한 폭을 .aux 파일에 적어 두고, 다음 실행에서 그것을 읽어 들여 전 페이지 공통의 폭으로 사용합니다. 그래서 첫 컴파일에서 이런 경고가 나옵니다.

log
Package longtable Warning: Column widths have changed
(longtable)                in table 1 on input line 24.


Package longtable Warning: Table widths have changed. Rerun LaTeX.

경고를 무시하면 어떻게 되는지 실측해 봅시다. 세 페이지로 나뉜 표의 오른쪽 열 머리글 “Amount”의 위치를 PDF에서 읽어 보면 첫 실행에서는 x = 320.41pt이고 그 아래 데이터는 x = 488.69pt 로, 머리글과 숫자가 서로 다른 자리에 서 있습니다. 두 번째 실행 뒤에는 머리글이 x = 471.53pt 로 옮겨 가 세 페이지 모두 같은 자리에 놓입니다. 즉 첫 실행의 출력은 열이 너무 좁고 머리글만 왼쪽으로 밀린 표입니다. “한 번 컴파일했더니 표가 이상하다”의 정체가 바로 이것입니다.

.aux 파일은 들여다볼 수 있습니다. 세 열짜리 longtable이 남긴 내용이 이것입니다. \LT@i는 “이 문서의 첫 번째 longtable”이라는 뜻이고 두 번째는 \LT@ii가 됩니다. 각 항목의 두 번째 인수가 열별 실측 폭이며, 다음 실행은 이 숫자를 그대로 사용합니다.

document.aux
\gdef \LT@i {\LT@entry
    {2}{58.6668pt}\LT@entry
    {2}{274.36153pt}\LT@entry
    {1}{47.55566pt}}

측정은 한 번에 이루어지지 않고 덩어리 단위 로 진행되며, 그 크기를 정하는 것이 카운터 LTchunksize(기본 200행)입니다. 행이 아주 많은 표가 TeX의 메모리를 다 써 버린다면 \setcounter{LTchunksize}{10}처럼 줄입니다. 반대로 키우면 .aux 왕복이 줄어 더 빨리 수렴하기도 합니다. 실무에서는 latexmk를 쓰면 이 왕복을 신경 쓸 필요가 없습니다. 경고가 사라질 때까지 알아서 돌려 줍니다.

캡션과 번호 — LTcapwidth의 기본값은 4인치

longtable은 부동체가 아닌데도 자체 \caption{…} 을 가집니다. 표준 table 카운터를 증가시키므로 “표 N”으로 자동 번호가 붙고 \listoftables에도 실립니다. 캡션은 표 레이아웃의 일부이므로 \caption{…}\\처럼 머리글 블록, 즉 \endfirsthead 앞에 두고 \\로 행을 닫는 것이 약속입니다. 여기서 뜻밖인 것이 폭입니다. 캡션 폭 \LTcapwidth의 기본값은 4인치, 곧 289.08pt 입니다. article\textwidth가 345pt이므로 기본 상태에서는 캡션만 본문보다 56pt 좁습니다. 본문 폭에 맞추려면 \setlength{\LTcapwidth}{\textwidth}라고 씁니다.

나머지는 자잘한 약속입니다. 번호를 붙이고 싶지 않으면 \caption*{…}을 씁니다(번호도 표 목록 항목도 없습니다). 두 번째 페이지 이후의 “(continued)” 같은 부속 캡션 은 표 목록에 두 번 실리지 않도록 선택 인수를 비운 \caption[]{…} 로 씁니다. 그리고 상호 참조용 \label{…}반복되는 \endhead 안에 절대 두면 안 됩니다. 페이지 수만큼 같은 레이블이 정의되기 때문입니다. \endfirsthead 안이나 표 본문 첫 행에 두십시오.

booktabs 선은 페이지가 나뉠 때 어떻게 되는가

\toprule, \midrule, \bottomrulelongtable 안에서 그대로 쓸 수 있습니다. booktabs는 2000년 판에서 longtable 호환을 넣었고, booktabs.sty에는 지금도 \ifx\longtable\undefined 분기가 남아 있습니다. 다만 동작에 비대칭이 하나 있습니다. longtable은 페이지 나눔을 위해 \hline을 자동으로 이중으로 그리지만(이음매 위아래에 한 줄씩), booktabs의 선은 이중이 되지 않습니다. 그래서 선이 마침 페이지 나눔 자리에 떨어지면 booktabs 설명서 자신이 말하듯 손으로 조정 하는 수밖에 없습니다. 대개는 그 선을 지웁니다. 실무적으로는 선을 \endfoot\endhead에 담아, 이음매에 선이 오지 않도록 설계하는 것이 정답입니다.

덤이 있습니다. 원래 \cmidrule에만 쓸 수 있는 좌우 잘라내기 인수longtable 안에서는 \toprule, \midrule, \bottomrule에도 듣습니다. \toprule(r)라고 쓰면 오른쪽 끝이 안쪽으로 들어오고, \midrule(lr)이면 양끝이 들어옵니다. TeX Live 2024에서 longtablebooktabs를 함께 읽어 들여 실제로 통과했습니다. 뒤집어 말하면 일반 tabular 안에서는 같은 것을 쓸 수 없습니다. booktabs의 저자는 설명서에서 반쯤 농담으로, 잘라낸 선이 꼭 필요하면 표를 전부 longtable로 만들어 버리라고 적어 두었습니다.

supertabular와 xtab — 한 번이면 되지만 열이 어긋난다

페이지를 넘는 표를 만드는 패키지는 longtable만이 아닙니다. supertabular(Johannes Braams, Theo Jurriens)와 그 파생인 xtab(Peter Wilson)은 전혀 다른 작전을 씁니다. longtable이 “표 전체를 조판하고 TeX의 페이지 나눔에 맡긴다”면, 이쪽은 “페이지에 남은 높이를 재고 거기 들어갈 만큼의 tabular를 새로 만들어 놓는다” 입니다. 즉 페이지마다 독립된 tabular를 늘어놓는 셈입니다. 그래서 .aux 왕복이 필요 없고 한 번의 컴파일로 끝납니다.

다만 그 대가는 그대로 지면에 드러납니다. 같은 119행 데이터에서 90행에만 유난히 긴 항목을 섞어 비교해 보았습니다. supertabular에서는 열 머리글 “Amount”가 1·2페이지에서 x = 249.16pt, 긴 항목이 오는 3페이지에서 x = 471.53pt 였습니다. 페이지를 넘기는 순간 표의 폭이 달라집니다. 같은 데이터를 longtable로 조판하면 두 번째 컴파일 뒤 세 페이지 모두 x = 471.53pt 였습니다. 두 번 컴파일한다는 것은 이 일관성을 산다는 뜻입니다. 문법도 달라서 supertabular는 머리와 꼬리를 환경 바깥에서 \tablefirsthead, \tablehead, \tabletail, \tablelasttail로 선언하고 캡션에는 \topcaption\bottomcaption을 씁니다. xtab은 여기에 끊는 자리가 어색해지지 않도록 최소 행 수를 지정하는 기능 등을 더한 것입니다.

줄바꿈하는 X 열도 페이지를 넘기려면 — xltabular와 ltxtable

longtable의 열 지정은 tabular와 같으므로 긴 글은 고정 폭 p{width} 열에 넣어 줄바꿈하게 됩니다. 남은 폭 전체로 자동 확장되는 열을 원한다면 tabularxX 이지만, tabularx 환경 자체는 페이지를 넘길 수 없습니다. 이 둘을 잇는 다리가 둘 있습니다. xltabular(Rolf Niepraschk, Herbert Voß)는 “X 열을 가진 longtable”로, \begin{xltabular}{\linewidth}{l X r}처럼 총폭과 열 지정 을 받고 \endfirsthead 계열 마커도 그대로 쓸 수 있습니다. 내부에서 ltablex를 불러오지만 기존 tabularx 환경의 동작은 바꾸지 않습니다.

document.tex
\documentclass{article}
\usepackage{xltabular}
\begin{document}

\begin{xltabular}{\linewidth}{l X r}
  \caption{Glossary}\\
  \hline
  Term & Description & Page \\
  \hline
  \endfirsthead
  \hline
  Term & Description & Page \\
  \hline
  \endhead

  longtable  & Breaks across pages; sits in the text flow, not in a float. & 12 \\
  xltabular  & A longtable whose X column takes the leftover width.       & 34 \\
\end{xltabular}

\end{document}

다른 다리는 역시 David Carlisle이 만든 ltxtable 입니다. 여기서는 표 본체를 별도 파일에 적어 두고 \LTXtable{\textwidth}{파일명}으로 읽어 들입니다. 파일 안에는 X 열을 가진 longtable이 들어 있고, ltxtabletabularx의 폭 계산을 한 번 돌린 뒤 그 결과를 longtable에 넘깁니다. 파일을 나누는 수고는 있지만, tabularx의 반복 전개와 longtable.aux 왕복이 서로 간섭하지 않아 긴 표에서 안정적입니다. 선택은 단순합니다. 간편함이면 xltabular, 크고 복잡한 표에서 동작이 수상하면 ltxtable 입니다. 한 가지 세부 차이로, xltabular는 실제로 \caption을 쓰지 않는 한 table 번호를 소비하지 않습니다.