본문 너비를 넘는 표

표가 본문 밖으로 삐져나왔습니다. LaTeX을 쓰다 보면 누구나 한 번은 만나는 Overfull \hbox입니다. 실측해 봅시다. article의 12열 booktabs 표는 404.83pt, 이에 맞선 \textwidth345pt 이고 로그에는 Overfull \hbox (74.83432pt too wide)가 찍힙니다. 여기서 많은 사람이 \small을 시도하지만 그것으로는 부족합니다. \scriptsize(7pt)까지 줄여도 표는 351.90pt 로 여전히 넘칩니다. 글자를 줄여도 열 사이 여백은 줄지 않기 때문 입니다. 이 페이지에서는 이 표를 지면에 올리는 방법을 모두 늘어놓습니다. 폭을 선언하는 tabularxtabulary, 키=값으로 다시 짜는 tabularray(tblr 환경), 확대·축소하는 \resizeboxadjustbox, 페이지째 가로로 돌리는 pdflscaperotating, 그리고 여백으로 흘려보내는 \makebox입니다.

글자를 줄여도 표가 넘치는 이유

열 사이 여백 \tabcolsep이 절대 길이라서 글꼴 크기를 바꿔도 줄지 않기 때문 입니다. 12열 표라면 \tabcolsep(한쪽 6pt)이 스물네 군데에 들어갑니다. 모든 열의 양쪽에, 표의 바깥 양끝까지 포함해서입니다. 같은 표를 \tabcolsep=0pt로 다시 조판하면 404.83pt가 260.83pt 가 됩니다. 차이는 정확히 144pt, 즉 표 폭의 35%가 여백이었던 셈입니다. 이 144pt는 글자를 줄여도 1pt도 줄지 않습니다. 실제로 \scriptsize로 조판해도 여백 합계는 144pt 그대로였습니다. 그래서 351.90pt까지밖에 내려가지 않습니다.

대처실측 표 폭(textwidth = 345pt)결과
(none)404.83ptOverfull \hbox (74.83432pt too wide)
\small385.27pt여전히 넘침
\footnotesize365.70pt여전히 넘침
\scriptsize351.90pt7pt까지 줄여도 여전히 넘침
\tabcolsep=3pt332.83pt들어감. 글자는 10pt 그대로
\resizebox345.01pt들어가지만 실효 8.52pt로 줄어듦

이 표에서 실무의 순서가 읽힙니다. 먼저 여백을 깎고, 다음에 열 설계를 다시 보고, 그래도 안 되면 축소하거나 회전 입니다. \tabcolsep을 3pt로 두는 것만으로 332.83pt에 들어가고 본문과 같은 읽기 좋은 10pt를 유지하므로 여기서 시작해야 합니다(\tabcolsep@{}의 자세한 내용은 “열 지정, 정렬 및 너비”가 담당합니다). 다만 깎을 수 있는 여백에는 한계가 있습니다. 원인이 숫자가 아니라 긴 문장이 들어간 열 이라면 여백을 아무리 깎아도 소용없고, 열 자체가 줄바꿈해야 합니다. 다음 절이 그 이야기입니다.

tabularx — 폭을 먼저 정하고 X 열이 늘어나게 한다

tabularx표의 목표 폭을 첫 인수로 받고 X 열이 남은 폭을 채우게 합니다. 형식은 \begin{tabularx}{width}{column spec}입니다. X는 남은 폭을 자동으로 흡수하는 p 열이고, 여러 개면 균등하게 나눕니다. 넘침의 원인이 긴 설명 문장이라면 이것이 가장 짧은 해결책입니다. 폭을 \textwidth로 선언해 버리면 구조상 넘칠 수가 없습니다. 저자는 longtable과 같은 David Carlisle이고 tools 번들에 들어 있어 따로 설치할 것이 없습니다.

작동 방식은 조금 특이합니다. tabularx는 표를 여러 번 시험 조판 해서 폭을 구합니다. 먼저 각 X를 목표 폭 전체로 잡아 조판하고(그래서 표가 너무 넓어집니다), 초과분을 X 열의 개수로 나눠 각각을 그만큼 줄인 뒤 다시 조판합니다. 수렴할 때까지 반복합니다. 최종적으로 X\tabularxcolumn이 정하는 열이 되며 기본값은 \newcommand{\tabularxcolumn}[1]{p{#1}}, 곧 위쪽 정렬 단락 열입니다. 세로 가운데로 맞추려면 m{#1}로 재정의합니다. X 셀은 기본이 양쪽 정렬이므로 왼쪽 정렬로 하려면 열 지정에 >{\raggedright\arraybackslash}X 를 붙입니다. 핵심은 \arraybackslash 로, \raggedright 등이 \\의 의미를 바꿔 놓는 것을 되돌립니다. 같은 전처리를 여러 번 쓴다면 \newcolumntype으로 한 글자 별명을 만들면 열 지정이 다시 읽기 쉬워집니다.

document.tex
\usepackage{tabularx}
\newcolumntype{Y}{>{\raggedright\arraybackslash}X}
% ...
\begin{tabularx}{\textwidth}{l Y Y}
  \hline
  Package & Strength & Weakness \\
  \hline
  tabular  & simple and fast              & you compute the widths yourself \\
  tabularx & the table hits a fixed width & slower: the body is set several times \\
  \hline
\end{tabularx}

주의할 점이 둘 있습니다. 첫째, 본문이 여러 번 펼쳐지는 성질 때문에 tabularx 안에서는 부작용이 있는 명령 — 카운터 증가, 파일 쓰기, \footnote, \verb 등 — 이 잘못 동작할 수 있습니다. 그런 셀은 상자나 minipage로 감싸는 것이 정석입니다. 둘째, 기본 tabularx에는 비율 지정이 없습니다. X X라고 쓰면 반드시 균등 분할이고, 2:1로 하려면 아래의 tabularrayX[2]를 쓰거나 \newcolumntype으로 p{}를 손으로 계산해야 합니다.

tabulary — 내용의 양에 따라 폭을 배분한다

tabularxX 열을 균등하게 나누는 데 비해, tabulary각 열 내용의 자연 너비에 비례 해 폭을 나눠 줍니다. 한쪽은 짧은 표제어, 다른 쪽은 긴 설명인 표에서는 균등 분할이 오히려 어색합니다. 문법은 같아서 \begin{tabulary}{width}{column spec}이고, 열 타입은 네 가지 — L 왼쪽, C 가운데, R 오른쪽, J 양쪽 정렬 — 입니다. 일반 l, c, r, p{width}도 섞을 수 있지만 이들은 측정 대상에서 빠져 자연 너비를 유지합니다. 측정되는 각 열은 그 열에서 가장 넓은 셀의 자연 너비로 가중되고 전체가 지정 폭이 되도록 조정되지만, 이미 자기 몫보다 좁은 열은 그대로 둡니다. 배분이 극단으로 가지 않도록 측정되는 열은 \tymin(기본 10pt)과 \tymax(기본 2\textwidth)로 제한됩니다. 유난히 긴 항목 하나가 열 폭을 독점하는 것을 막는 안전판입니다.

document.tex
\usepackage{tabulary}
% ...
\begin{tabulary}{\textwidth}{L L}
  \hline
  Term & Description \\
  \hline
  X column & Shares the width left over after the fixed columns, equally with the other X columns. \\
  \hline
\end{tabulary}

여기서는 두 열 모두 L이지만, 내용이 짧은 “Term”은 좁게, 긴 글이 든 “Description”은 넓게 배분됩니다. tabularxX X라고 쓰면 반드시 같은 폭이 되므로, 표제어 열과 본문 열로 된 표에서는 tabulary 쪽이 더 자연스러워 보입니다. 다만 tabulary도 본문을 여러 번 처리하므로 각주와 카운터에 대한 주의는 똑같이 필요합니다.

tabularray — tblr 환경으로 폭도 선도 하나의 문법에

tabularray 는 예전에 array, booktabs, multirow, colortbl, diagbox를 함께 써야 했던 일을 하나의 키=값 인터페이스 로 통합한 패키지입니다. 저자는 Jianrui Lyu(lvjr) 이며, 오래된 \halign 기반 구현을 버리고 LaTeX3(expl3)로 표를 직접 해석하고 조판합니다. TeX Live 2024 동봉판은 2024-02-16 릴리스, 내부 버전 2024A입니다. 주 환경은 tblr 이고 \usepackage{tabularray}로 불러옵니다.

tblr의 구조는 tabular와 조금 다릅니다. 본문 앞에 키=값을 쉼표로 나열한 하나의 인수, 곧 내부 지정 을 두고 \begin{tblr}{ … inner spec … }처럼 씁니다. 열 정렬은 colspec 키로 선언합니다. 익숙한 한 글자 지정 colspec={lcr}도 여전히 되지만, 진짜 힘은 Q[…](정렬·폭 등의 옵션을 대괄호로 넘기는 범용 열)와 X[…](tabularx와 같은 신축 열)에 있습니다. X[c]는 가운데 정렬 신축 열이고, X[2,l]계수 2(다른 X의 두 배 폭)의 왼쪽 정렬 신축 열입니다. 넘침이라는 맥락에서 반가운 점은 width=colspec=을 같은 괄호 안에 쓸 수 있다 는 것, 그리고 이 계수로 비율을 지정할 수 있다 는 것입니다. 실측하면 width=\textwidthcolspec={X[2,l] X[1,r] X[1,r]}을 지정한 tblr의 출력은 정확히 345.0pt 로 목표 폭과 딱 맞았습니다.

X 열이 넓어질 대상 폭은 width 키로 지정합니다(지정하지 않으면 \linewidth). 행간과 열간 여백은 rowsepcolsep 키로 제어하며 tblr의 기본값은 rowsep=2pt, colsep=6pt입니다. 선은 선언적으로 지정합니다. hlines / vlines 라고 쓰면 모든 가로줄 / 세로줄을 그리고, hline{2}={1pt,solid}처럼 hline{n} / vline{n} 으로 특정 선의 두께와 종류를 지정합니다(Z는 마지막 행을 나타내는 특별한 번호). 본문에 \hline을 쓸 필요가 없습니다. 자주 쓰는 설정은 \SetTblrInner{rowsep=2pt}로 묶어 둘 수 있습니다.

셀 결합은 본문 쪽에서 \SetCell 을 사용합니다. \SetCell[c=2]{c}2열을 가로지르는 가운데 정렬 셀(\multicolumn에 해당), \SetCell[r=2]{c}2행을 가로지르는 셀(\multirow에 해당)이며, c=r=는 열・행 결합 수, 중괄호는 그 셀의 정렬입니다. 행이나 열 전체를 꾸미려면 \SetRow\SetColumn 을 쓰며, 예를 들어 \SetRow{cyan8}은 행을 옅은 청록색으로 칠합니다(색 이름은 패키지 내장). 같은 내용을 내부 지정 쪽에서 row{1}={font=\bfseries}, column{2}={halign=r}, cell{2}{3}={c=2}처럼 선언할 수도 있어 본문을 어지럽히지 않습니다.

document.tex
\usepackage{tabularray}
% ...
\begin{tblr}{
    width        = \textwidth,
    colspec      = {Q[l] X[c] X[2,l]},
    rowsep       = 3pt,
    hline{1,Z}   = {1pt,solid},
    hline{2}     = {0.5pt,solid},
    row{1}       = {font=\bfseries},
  }
  Item & Kind & Description \\
  \SetCell[r=2]{l} Table environment & fixed width   & The table width is fixed; X columns share the rest. \\
                                     & content width & Width is apportioned by how much each column holds. \\
  Note & --- & Rules, colour and spans are all set through keys. \\
\end{tblr}

tblrwidth=\textwidth로 표 폭을 본문 폭에 고정합니다. 세 열은 Q[l](왼쪽 정렬 기본 열), X[c](가운데 정렬 신축 열), X[2,l](계수 2의 왼쪽 정렬 신축 열)이며, 두 X 열은 남은 폭을 1 : 2 로 나누므로 3열 “Description”이 가장 넓어집니다. 본문에는 \hline이 나오지 않고 선은 내부 지정의 hline{…} 키만으로 그립니다. 첫 행은 row{1}={font=\bfseries}로 굵게, 1열의 “Table environment”는 \SetCell[r=2]{l}2행을 관통 하게 했습니다. booktabs, multirow, array를 함께 쓰지 않고도 여기까지 되는 것이 tabularray의 강점입니다.

표가 가로뿐 아니라 세로로도 길다면 두 파생 환경이 넘겨받습니다. longtblr 은 여러 페이지에 걸칠 수 있는 tblr로 머리글 행 반복, 캡션, 각주를 지원합니다. talltblrthreeparttable처럼 표 아래에 주석을 두는 한 장짜리 표입니다. 둘 다 캡션, 레이블, note{…} 등을 본문 앞의 선택 인수 […](외부 지정) 에 씁니다. longtblr의 머리글 반복은 rowhead=1처럼 행 수를 숫자로 주기만 하면 되어 longtable의 네 마커보다 간결합니다.

resizebox는 글자 크기에 대해 거짓말을 한다

graphicx\resizebox{\textwidth}{!}{...} 는 내용을 통째로 확대·축소해 지정한 폭에 맞춥니다. 표를 지면에 올리는 가장 빠른 방법이지만, 소스 어디에도 적히지 않는 축소율로 글자를 작게 만듭니다. 실측해 봅시다. 404.83pt짜리 표를 \resizebox{\textwidth}{!}에 넣으면 폭 345.01pt의 상자가 되고 높이는 26.07pt에서 22.22pt로 줄어듭니다. 두 비율 모두 0.8522. 즉 10pt로 쓴 줄 알았던 글자가 종이 위에서는 8.52pt 입니다. 문서에는 \documentclass[10pt]라고 적혀 있는데 실물은 8.52pt. 심사자나 투고 규정이 글꼴 크기를 보는 자리에서는 사고가 됩니다.

그렇다면 왜 \resizebox는 들어가고 \scriptsize(7pt)는 들어가지 않을까요. 이 페이지 첫머리와 같은 이유로, \resizebox는 열 사이 여백까지 함께 줄이기 때문 입니다. 글꼴 크기 변경은 그 144pt에서 1pt도 덜어 내지 못하지만, 0.8522배 축소는 그 144pt를 122.7pt로 줄입니다. 그래서 겉보기 8.52pt인 \resizebox가 실제 7pt인 \scriptsize를 이겨 버립니다. 뒤집어 말하면 \resizebox여백까지 눌러 붙인 표 를 만드는 것이어서 읽기 쉬움의 관점에서는 이중으로 불리합니다.

게다가 \resizebox에는 훨씬 눈에 띄는 함정이 있습니다. 작은 표를 주면 확대해 버린다 는 것입니다. 실측에서 폭 4.44pt의 작은 표를 \resizebox{\textwidth}{!}에 넣었더니 345.46pt 가 나왔습니다. 약 78배로 늘어난 괴물입니다. \includegraphics에 하듯 반사적으로 모든 표에 붙이면 좁은 표만 부풀어 오릅니다. 여기서 adjustboxmax width=\textwidth 가 값을 합니다. 같은 4.44pt 표는 4.44pt 그대로 였고 404.83pt짜리만 345.01pt로 줄었습니다. 필요할 때만 줄이고 결코 키우지 않는 것 — 실제 문서에서는 이쪽을 기본으로 삼는 편이 안전합니다.

latex
\usepackage{graphicx}   % \resizebox
\usepackage{adjustbox}  % \adjustbox and the adjustbox environment
% ...

% scales in BOTH directions -- a narrow table gets blown up
\resizebox{\textwidth}{!}{\begin{tabular}{...}...\end{tabular}}

% only ever shrinks; a narrow table is left alone
\begin{adjustbox}{max width=\textwidth}
  \begin{tabular}{...}...\end{tabular}
\end{adjustbox}

페이지째 가로로 돌리기 — lscape, pdflscape, rotating의 차이

PDF 뷰어에서 페이지째 돌아가 보이는 것은 pdflscape 뿐입니다. 셋 다 \begin{landscape}sidewaystable로 내용을 90도 돌리지만, PDF에 “이 페이지는 돌려서 보여라”라는 지시, 곧 /Rotate 90 항목을 써 넣는 것은 pdflscape 뿐입니다. 같은 문서를 세 가지 방식으로 조판하고 pdfinfo로 확인한 결과가 이것입니다.

패키지사용하는 환경pdfinfo가 보고하는 rot과 용지 크기
lscapelandscape모든 페이지 rot: 0, 612 x 792 그대로. 내용만 회전
pdflscapelandscape해당 페이지만 rot: 90, 612 x 792. 뷰어가 돌려서 표시
rotatingsidewaystablerot: 0, 612 x 792. 번호가 붙은 부동체로서 회전

셋 모두 용지 크기는 612 x 792 그대로 라는 점에 유의하십시오. 종이를 실제로 가로로 만든 것이 아니라 내용만 돌아간 것입니다. 여기서 판단이 정해집니다. 화면으로 읽는 PDF라면 pdflscape 를 써서 독자가 고개를 기울이지 않게 합니다. pdflscapelscape를 읽어 들인 뒤 /Rotate를 더하므로 lscape의 문법이 그대로 통합니다. 인쇄해서 제본한다면 lscape로도 충분 합니다. 종이를 돌리면 되니까요. 표를 번호와 캡션이 붙은 부동체로 두고 싶다면 rotatingsidewaystable(그림이면 sidewaysfigure)입니다. rotatingfiguresrightfiguresleft 옵션으로 회전 방향도 고를 수 있습니다.

document.tex
\usepackage{pdflscape}   % loads lscape and adds /Rotate 90 to the page
\usepackage{rotating}    % sidewaystable / sidewaysfigure
% ...

% a whole page turned sideways in the viewer
\begin{landscape}
  \begin{tabular}{...}...\end{tabular}
\end{landscape}

% a rotated float that still says "Table N"
\begin{sidewaystable}
  \centering
  \begin{tabular}{...}...\end{tabular}
  \caption{A very wide table}
\end{sidewaystable}

여백으로 흘려보내기 — makebox로 본문 폭을 넘기

본문 폭은 345pt이지만 종이에는 더 여유가 있습니다. article 기본값에서 왼쪽 여백은 134.27pt, 오른쪽은 135.03pt 로, 본문 좌우에 합쳐 269pt의 흰 공간이 비어 있습니다. 여기로 뻗어 나가는 정석이 \noindent\makebox[\textwidth]{...} 입니다. \makebox는 내용을 폭 \textwidth인 상자에 넣는 명령이고, 내용이 더 넓으면 좌우로 균등하게 넘칩니다. 404.83pt짜리 표라면 한쪽에 29.92pt 씩 여백으로 나가는 셈이어서, 판면의 중심은 유지된 채 표만 넓어집니다.

한 가지는 각오해야 합니다. 상자보다 내용이 넓으므로 TeX은 여전히 Overfull \hbox를 보고합니다. 이번만큼은 무시해도 되는 경고 입니다. 일부러 넘치게 한 것이니까요. 한쪽으로만 내보내고 싶다면 \makebox[\textwidth][l]{...}로 왼쪽 끝을 맞추고 오른쪽으로만 넘치게 합니다. 더 정교하게 다루고 싶다면 changepage 패키지의 adjustwidth 환경으로 그 구간만 본문 폭을 넓히는 쪽이 예의 바르고, 각주와 머리글과의 정합도 지켜집니다. 여백의 실제 치수는 용지와 geometry 설정에 따라 달라지므로, 여백으로 내보내는 설계를 할 때는 반드시 실제 PDF로 확인하십시오(여백 자체를 정하는 법은 “여백과 판면”이 담당합니다).

표를 나누기 — 세로로 자르거나 행과 열을 바꾸거나

패키지에 기대지 않는 해결책도 있습니다. 열을 두 표로 나누고 핵심 열만 양쪽에 반복하는 것 입니다. 12열짜리 표라면 지역 이름을 양쪽 표의 첫 열에 두고 연도를 2–6과 7–12로 나눕니다. 독자의 시선이 좌우를 오갈 필요가 없고 글자 크기도 본문과 같습니다. 축소도 회전도 하지 않으므로 결과적으로 가장 읽기 좋은 경우가 많습니다. 또 하나는 행과 열을 바꾸는 것 입니다. “12년 × 5지역” 표는 가로로 길어지지만 “5지역 × 12년”으로 바꾸면 세로로 길어지고, 그러면 longtable의 담당 범위로 넘어갑니다.

결국 무엇을 쓸 것인가

판단은 원인이 정합니다. 긴 문장 열이 넘친다면 tabularx(비율이 필요하면 tabularrayX[2], 분량에 따른 배분을 원하면 tabulary)입니다. 폭을 아예 선언해 버리는 것이 가장 깔끔합니다. 숫자 열이 많아서 넘친다면 먼저 \tabcolsep 을 손보고, 그 다음에 그 많은 열이 한꺼번에 필요한지 물어봅니다. 모든 열을 꼭 한 장에 실어야 한다면 \resizebox가 아니라 adjustboxmax width 입니다. 그래도 안 들어갈 만큼 넓다면 pdflscape로 페이지째 돌립니다. 새로 쓰기 시작하는 표라면 tabularray 가 순리로, 폭·선·색·병합을 하나의 문법으로 씁니다. 표가 가로뿐 아니라 세로로도 길다면 longtblr이나 longtable로 넘어갑니다. 어느 길을 택하든 마지막에는 반드시 완성된 PDF를 눈으로 보고 8.5pt 숫자가 읽히는지 확인하십시오.

패키지폭 결정 방식주요 용도
tabularx전체 폭 고정, X 열이 균등 분배본문 폭에 맞춘 같은 폭의 글 열
tabulary전체 폭 고정, 내용량에 따라 배분분량이 다른 레이블과 본문의 표
tabularraywidth 키와 X 열(계수 포함)선, 색, 병합을 통합한 현대적 표
adjustbox넘칠 때만 축소(max width)기존 표에 안전한 상한을 두기
pdflscape폭은 그대로 두고 페이지를 90도 회전세로로는 도저히 들어가지 않는 표

열 지정 자체의 세부 사항(\multicolumn, array 패키지의 >{…} 등)은 “열 지정, 정렬 및 너비”가, 셀 병합의 기본은 “셀 병합”이, 페이지를 넘어가는 매우 긴 표는 “longtable” 페이지가 담당합니다.