Overfull / Underfull \hbox

Overfull \hbox오류가 아니라 경고입니다. LaTeX 실행은 끝까지 완주하고 PDF도 멀쩡히 만들어집니다. 다만 어느 한 줄이 오른쪽 여백을 넘어 튀어나와 있을 뿐입니다. 그래서 가장 먼저 할 일은 로그를 노려보는 것이 아니라 PDF를 열어 그 줄을 보는 것입니다. 그 위에서 이 페이지는 (71.93637pt too wide)의 숫자가 무엇을 재는지, in paragraph at lines 4--9detected at line 3이 어떻게 다른지, \hbadness\tolerance\emergencystretch 중 무엇을 건드려야 하는지, 그리고 정말로 고치는 것경고만 입막음하는 것이 전혀 다른 조작이라는 점을 차례로 다룹니다. 뒤집힌 경고인 Underfull \hbox와 세로 방향의 Overfull \vbox도 이 페이지의 몫입니다.

(71.93637pt too wide)의 숫자는 무엇을 재는가

그 줄이 \hsize(단 너비)를 넘어선 양이며, 단위는 포인트입니다. 1pt는 약 0.35mm이므로 0.5pt는 육안으로 보이지 않고, 70pt면 엄지손가락 너비만큼 지면 밖으로 나와 있는 셈입니다. 이 숫자 하나로 대응 우선순위가 정해집니다. 몇 pt 이하라면 아무도 알아채지 못하며, 실제로 LaTeX은 기본값으로 0.1pt까지는 잠자코 있습니다(\hfuzz=0.1pt). TeX은 “상자” 단위로 조판하며, 한 줄분의 글자를 가로로 늘어놓은 상자가 수평 상자, 곧 \hbox입니다. Overfull \hbox는 문단을 줄로 나눌 때 어떤 상자의 내용이 단 너비보다 넓어졌고 더는 좁힐 수 없었다는 뜻입니다. 줄이 좁혀지지 않는 이유는 거의 언제나 하나, 그 줄 안에 끊을 자리가 없었다는 것입니다.

경고의 뒷부분, 이를테면 in paragraph at lines 4--9 같은 대목은 어디서 일어났는지를 알려 줍니다. 이런 표현은 네 가지가 있고, 어느 것이 나왔느냐에 따라 찾아야 할 곳이 달라지므로 아래 표로 구별하십시오. 각 항목은 TeX Live 2024의 pdfLaTeX에서 최소 파일로 직접 재현한 것입니다. 그중 가장 성가신 것은 has occurred while \output is active로, “페이지를 조립하는 중”이라는 뜻이라 원인이 되는 내용이 본문의 어느 줄에도 대응하지 않을 수 있습니다. 반대 방향의 함정도 있습니다. 맨 \mbox{...}\makebox[2cm]{...}는 내용이 아무리 튀어나와도 경고를 전혀 내지 않습니다. 이들이 채우개로 쓰는 \hss는 무한히 줄어들 수 있어서, TeX이 보기에 상자는 항상 꼭 맞게 들어맞습니다. 여백을 넘어선 글자가 로그에 아무 흔적도 남기지 않고 인쇄됩니다.

보고 형태의미어디를 볼지
in paragraph at lines A--B보통 문단의 줄 나누기에서 발생A행부터 B행까지의 문단. 가장 흔함
detected at line N문단이 아니라 너비를 고정한 상자에서 발생\hbox to, \makebox[…][s], 패키지가 만든 상자. N행
in alignment at lines A--B표 정렬에서 발생tabular*, tabularx처럼 너비를 지정한 표
while \output is active페이지를 조립하는 중에 발생머리말・꼬리말・각주・플로트. 줄 번호는 믿을 수 없음

로그는 범인인 줄을 보여 준다 — 하이픈 자리까지

경고 바로 아래에 튀어나온 줄의 내용이 그대로 인쇄됩니다. 게다가 TeX이 찾아낸 하이픈 자리마다 -가 들어간 상태로 말입니다. 여기가 진단의 핵심입니다. 아래는 폭 4cm 단에 영문을 흘려 넣었을 때의 실제 로그인데, Wis-senschaft-skol-leg에는 하이픈이 들어 있지만 electroencephalography-에는 하나도 없습니다. TeX은 명시적 하이픈이 들어 있는 낱말을 자동 하이픈 처리 대상에서 제외합니다(탐색이 명시적 하이픈에서 멈추기 때문입니다). 즉 이 줄은 정말로 끊을 자리가 없어서, \tolerance를 아무리 올려도 해결되지 않습니다. 로그의 하이픈을 보면 하이픈 처리가 시도되고 있는지, 아니면 아예 포기된 상태인지가 한눈에 보입니다. 어디에도 하이픈이 없다면 언어 설정이 잘못됐을 가능성이 큽니다.

log
Overfull \hbox (13.35585pt too wide) in paragraph at lines 4--9
\OT1/cmr/m/n/10 The Wis-senschaft-skol-leg pub-
 []

Overfull \hbox (80.27266pt too wide) in paragraph at lines 4--9
\OT1/cmr/m/n/10 li-ca-tion de-scribes an electroencephalography-
 []

PDF에서 해당 위치를 눈으로 확인하고 싶으면 클래스 옵션에 draft를 붙입니다. article.cls에는 \DeclareOption{draft}{\setlength\overfullrule{5pt}}라고 적혀 있어, 튀어나온 줄 오른쪽에 폭 5pt짜리 검은 괘선이 나옵니다. 평소 이 검은 막대가 보이지 않는 이유는 latex.ltx가 마지막에 \overfullrule 0pt를 실행해 INITEX의 기본값 5pt를 지워 버리기 때문입니다. 즉 검은 막대는 꺼져 있을 뿐 언제든 되살릴 수 있습니다. 클래스 옵션을 건드리기 싫다면 프리앰블에 \overfullrule=5pt라고 직접 써도 같습니다. 제출 전에 되돌리는 것만 잊지 마십시오.

2단·표·코드 — 좁은 단이 overfull을 양산하는 이유

단이 좁을수록 끊을 수 없는 낱말의 상대적 길이가 치명적이 되기 때문입니다. 폭 10cm 단이라면 3cm짜리 낱말은 줄의 3분의 1이지만, 폭 4cm 단에서는 줄보다 길어서 어떻게 끊어도 반드시 튀어나옵니다. 같은 원고를 1단에서 2단으로 바꾸자마자 경고가 수십 건으로 늘어나는 것은 글이 나빠져서가 아니라 단이 절반이 되었기 때문입니다. 아래는 overfull이 몰려서 발생하는 자리이니, 짚이는 데가 있으면 거기부터 보십시오. 좁은 단이 원인임을 알면 대처도 달라집니다. 문단마다 \sloppy를 뿌리는 것보다 그 표의 p{} 열 너비를 다시 보는 편이 빠를 때가 많습니다.

  • 2단 조판(\documentclass[twocolumn], multicol). 단 너비가 대략 절반이 되므로 1단에서는 멀쩡하던 낱말이 한꺼번에 튀어나옵니다.
  • 표의 p{}m{}b{}tabularx. 열 너비는 저자가 정한 숫자라서, 담을 내용에 비해 좁은 경우가 잦습니다.
  • 축자 환경(verbatim, \verb, listings). 공백도 줄바꿈도 원고 그대로이므로 TeX에게 끊을 여지가 전혀 없습니다.
  • URL, DOI, 파일 경로, 형번, 화학명, 유전자명. 공백 없는 긴 문자열은 그 자체가 낱말 하나로 취급됩니다.
  • 행 안 수식. 기본적으로 수식은 이항 연산자나 관계 기호 뒤에서만 끊기고, \frac 같은 구조 안에서는 전혀 끊기지 않습니다.
  • 각주와 그림·표 캡션. 본문보다 좁은 너비로 조판되기도 하고, \output is active 형태로 보고되기 쉽습니다.

overfull hbox 없애기 — 우선순위가 높은 것부터

한 낱말을 고쳐 쓰는 것이 최선의 대처입니다. 밋밋한 답이지만 어떤 소프트웨어도 대신해 줄 수 없는 유일한 해결이며, 보통 5초면 끝납니다. 긴 낱말을 짧은 동의어로 바꾸고, 어순을 바꾸고, 긴 형번을 각주로 옮기면 상자는 들어맞습니다. 이 순서에는 이유가 있습니다. TeX의 줄 나누기는 문단 전체를 한 번에 보는 알고리즘이기 때문입니다(커누스와 마이클 플라스가 1981년 논문 Breaking Paragraphs into Lines에서 발표했습니다). 문단 마지막 낱말을 바꾸면 첫 줄의 끊김까지 달라질 수 있습니다. 뒤집어 말하면, 저자가 낱말 하나만 움직여도 조판 쪽의 자유도는 크게 넓어집니다. 아래는 효과와 부작용의 균형 순으로 정리한 대처입니다.

  • 바꿔 쓰기. 대개 낱말 하나면 되고 부작용이 전혀 없습니다. 긴 형번과 제품명은 각주나 표로 보냅니다.
  • 언어 설정 확인. 독일어나 프랑스어 본문에 영어 하이픈 패턴이 적용되면 끊을 자리를 찾지 못합니다. babel이면 \usepackage[ngerman]{babel}, XeLaTeX/LuaLaTeX이면 polyglossia\setmainlanguage{german}.
  • 그 낱말만 끊기게 하기. 낱말 안에 \-를 두면 임의 하이픈이 되고, \hyphenation{Wis-sen-schafts-kol-leg}는 프리앰블에 예외를 등록합니다. 명시적 하이픈이 든 복합어는 하이픈 뒤에 \hspace{0pt}를 끼우면 끊을 여지가 생깁니다.
  • \sloppy 또는 sloppypar 환경. 문단 단위로 낱말 간격을 느슨하게 합니다. sloppypar\end에서 닫히므로 \sloppy보다 안전합니다.
  • \emergencystretch 올리기. 실측에서 \emergencystretch=3em만으로 overfull 5건이 0건이 되었습니다(대신 underfull이 7건 나왔습니다). 부작용은 낱말 간격이 늘어지는 것으로, 지면 밖으로 튀어나온 글자보다 훨씬 덜 눈에 띕니다.
  • microtype 불러오기. 미세한 문자 돌출과 글꼴 확장으로 같은 시험에서 초과량이 13.4pt→8.9pt, 80.3pt→74.9pt로 줄었습니다. 다만 줄이는 것이지 없애는 것은 아닙니다.
  • 마지막 수단으로 \linebreak\mbox. 손으로 끊을 자리를 지정하는 방법입니다. 나중에 문장을 고치면 위치가 어긋나므로 제출 직전 마무리에만 씁니다.

\tolerance\emergencystretch\hbadness\hfuzz의 역할 분담

앞의 둘은 조판 자체를 바꾸고, 뒤의 둘은 경고가 나오는 방식만 바꿉니다. 이 구별을 혼동하면 “고쳤다고 생각했지만 사실은 감췄을 뿐”인 사고가 납니다. \hfuzz를 키우면 초과 보고는 사라지지만 지면의 글자는 1mm도 움직이지 않습니다. TeX의 줄 나누기는 최대 3단계입니다. 먼저 \pretolerance 범위에서 하이픈 없이 시도하고, 안 되면 \tolerance 범위에서 하이픈을 넣어 시도하고, 그래도 안 되면 모든 줄에 \emergencystretch만큼의 여분 신축을 더해 세 번째로 시도합니다. \emergencystretch는 오직 이 세 번째 주사에서만 작동하며, 이것이 곧 그 성격입니다. 다른 수가 없을 때 꺼내는 마지막 패입니다. LaTeX의 기본값은 latex.ltx\tolerance=200, \hbadness=1000, \hfuzz=0.1pt로 적혀 있습니다. 많이 쓰는 \sloppy도 같은 파일에 있는데, 그 실체는 대입 네 개뿐입니다. \tolerance 9999, \emergencystretch 3em, \hfuzz .5pt, \vfuzz\hfuzz. 즉 \sloppy는 “허용도를 올린다”에 그치지 않고 \emergencystretch 설정을 포함하며, 위 실측에서도 \emergencystretch=3em 단독과 \sloppy의 결과가 같았습니다.

파라미터LaTeX 기본값하는 일
\tolerance200한 줄이 가질 수 있는 나쁨의 상한. 올리면 조판이 바뀜
\emergencystretch0pt세 번째 주사에서만 더해지는 여분 신축. 조판이 바뀜
\hbadness1000이 값을 넘는 줄만 underfull로 보고. 조판은 그대로
\hfuzz0.1pt이 폭까지의 초과는 보고하지 않음. 조판은 그대로
\overfullrule0pt초과한 줄 옆에 긋는 검은 괘선의 폭. draft에서 5pt가 됨
preamble
% changes the typesetting -- use these to actually fix boxes
\emergencystretch=3em     % last-resort stretch on the third pass
\usepackage{microtype}    % protrusion and font expansion

% changes only the log -- the page looks exactly the same
\hbadness=10000           % never report Underfull \hbox
\hfuzz=1pt                % ignore overhangs below 1pt

긴 URL과 코드 문자열이 끊기지 않을 때

\url{}로 감싸는 것만으로는 부족할 때가 많습니다. 폭 5cm 단에서 실측했을 때, 맨 문자열 상태의 긴 URL은 79.0pt 튀어나왔고, url 패키지의 \url{}에 넣어도 42.4pt와 10.8pt짜리 두 건이 남았습니다. \url{}/. 같은 문자 뒤에서만 끊기 때문에 호스트명이나 긴 경로 조각의 중간에서는 끊지 못합니다. 여기서 듣는 방법이 둘 있습니다. 하나는 \sloppy를 함께 쓰는 것으로, 실측에서 overfull이 0건이 되고 대신 underfull이 나왔습니다. \sloppy에 늘 따라붙는 낱말 간격 늘어짐이 그 대가입니다. 다른 하나는 xurl 패키지로, \usepackage{xurl} 한 줄만 더했을 뿐인데 overfull도 underfull도 0건이 되었습니다. xurl은 URL 안의 임의 위치에서 끊는 것을 허용합니다. hyperref는 내부에서 url을 불러오므로 끊는 방식이 같고, 링크 자체를 쪼개려면 breaklinks 옵션이 필요합니다. 코드 리스팅이라면 listingsbreaklines=true가 대응하는 설정입니다.

preamble
\usepackage{hyperref}   % loads url internally; add breaklinks for split links
\usepackage{xurl}       % allows a break at any point inside a URL
...
\url{https://example.org/proceedings/vol12/paper-0037.pdf}

Underfull \hbox (badness N)는 반대 방향의 증상

줄이 너비에 못 미쳐 TeX이 낱말 간격을 늘여 채웠다——그것이 Underfull \hbox입니다. 둘 중 덜 급한 쪽이고, badness 값이 판단 재료가 됩니다. badness는 0(완벽)부터 10000(상한, “더 나빠질 수 없음”)까지의 척도입니다. 실측에서는 1590, 2050, 7667 같은 중간값도 나오지만, badness 10000이 줄줄이 나온다면 낱말 간격이 눈에 띄게 벌어졌다는 뜻이니 PDF를 확인하십시오. 가장 흔한 원인은 overfull을 잡으려던 조작의 부작용입니다. \sloppy\emergencystretch를 넣으면 overfull이 underfull로 바뀝니다(위 실측에서 5건→0건, 대신 underfull 0건→7건). 다른 원인은 문단 도중에 \linebreak\\로 짧은 줄을 강제한 경우입니다. \linebreak는 “여기서 끊되 줄 너비는 채워라”라는 뜻이라 간격이 늘어나고, \newline\\는 “여기서 줄을 끝내라”라서 늘어나지 않습니다. 일부러 짧은 줄을 만들고 싶다면 \\를 쓰는 것이 정답입니다. 경고만 없애고 싶다면 \hbadness=10000으로 사라지지만, 지면은 전혀 달라지지 않습니다.

Overfull \vbox (… too high)Underfull \vbox

같은 이야기가 세로 방향에도 있으며, 단위가 “너비”가 아니라 “높이”가 됩니다. Overfull \vbox (56.90552pt too high)는 정해진 높이의 세로 상자에 내용이 다 들어가지 않았다는 뜻입니다. 실측에서 \vbox to 1cm{\hrule height 3cm} 같은 명시적 상자는 detected at line 3 형태로 나옵니다. 실제 문서에서 자주 만나는 것은 페이지를 조립할 때로, \textheight보다 키가 큰 그림이나 \rule을 두면 Overfull \vbox (85.35828pt too high) has occurred while \output is active가 나왔습니다. 이 형태가 보이면 가장 먼저 의심할 것은 한 페이지에 들어가지 않는 크기의 그림입니다. Underfull \vbox (badness 10000)는 그 반대로, 세로 여백을 늘여 높이를 채웠다는 뜻입니다. book 클래스의 기본값인 \flushbottom에서는 각 페이지 아래끝을 맞추려고 세로 여백을 늘이므로, 내용이 적은 페이지에서 잘 나옵니다. 신경 쓰지 않겠다면 \raggedbottom으로 바꾸거나 \vbadness\vfuzz로 보고 임계값을 올립니다. 여기서도 임계값을 올려도 지면은 바뀌지 않는다는 원칙은 같습니다. 그림이 정말 안 들어갈 때는 \enlargethispage로 그 페이지만 본문 영역을 늘리는 방법도 있습니다.