상자

TeX에서 가장 근본적인 발상이 무엇이냐고 물으면, 답은 글꼴도 수식도 줄 나누기 알고리즘도 아닌 박스입니다. LaTeX가 페이지에 올리는 모든 것은 박스이며, 너비・높이・깊이라는 세 치수를 가지고 접착제(glue)로 이웃과 이어지며, 한번 조립되고 나면 그 안을 다시는 들여다보지 않습니다. \mbox가 단어의 하이픈 분할을 막는 것도, 넘친 줄이 Overfull \hbox로 보고되는 것도, \parboxminipage가 갈라지는 지점도 모두 여기서 나옵니다. 이 페이지에서는 실제로 입력하게 되는 박스 명령——\mbox, \makebox, \framebox, \raisebox, \parbox, minipage, \newsavebox——을 그 하나의 발상에서부터 따라갑니다.

너비・높이・깊이: 박스에 치수가 셋인 이유

박스의 치수는 둘이 아니라 셋입니다. 너비, 높이, 깊이입니다. 높이는 기준선에서 위로, 깊이는 기준선에서 아래로 잽니다. 즉 박스는 잉크를 둘러싼 직사각형이 아니라 글자가 올라선 선에 매달린 직사각형입니다. 세 값은 \the\wd, \the\ht, \the\dp로 읽어낼 수 있습니다. 10pt Computer Modern에서 Hx를 박스로 만들면 깊이는 0.0pt이지만, Hxg로 하면 깊이가 1.94444pt가 됩니다. g가 기준선 아래로 내려가기 때문입니다. 물론 너비도 늘었지만, 깊이가 늘어난 이유는 전혀 다른 데 있습니다.

이 한 가지 사실이, 박스가 무엇인지 알기 전에 거의 모두가 부딪히는 수수께끼를 풀어 줍니다. \fbox{Hx}의 테두리는 글자에 딱 붙는데 \fbox{Hxg}의 테두리는 아래로 처져 보일까요? \fbox가 감싸는 것은 글자가 아니라 박스이고, Hxg의 박스가 1.94pt 더 깊기 때문입니다. 테두리를 두른 낱말을 표의 한 행에 늘어놓으면 테두리가 내림 획(디센더)의 유무에 따라 위아래로 출렁입니다. 해법은 스트럿(strut) 입니다. \fbox{\strut Hx}\fbox{\strut Hxg}는 높이와 깊이가 완전히 같습니다. \strut이 현재 \baselineskip의 0.7배를 기준선 위로, 0.3배를 아래로 주장하는 너비 0의 보이지 않는 박스이기 때문입니다. 스트럿은 이 페이지 뒷부분에서 따로 다룹니다.

latex
\newsavebox{\probe}
\newcommand{\dims}[1]{\sbox{\probe}{#1}%
  \typeout{[#1] w=\the\wd\probe\space h=\the\ht\probe\space d=\the\dp\probe}}

\dims{Hx}    % w=12.77782pt  h=6.83331pt  d=0.0pt      <- no descender
\dims{Hxg}   % w=17.77783pt  h=6.83331pt  d=1.94444pt  <- the g adds depth

% same words framed: only the second frame sinks below the baseline
\fbox{Hx}\quad\fbox{Hxg}\quad \fbox{\strut Hx}\quad\fbox{\strut Hxg}

\mbox로 줄바꿈과 하이픈 분할 막기

\mbox{...}는 내용을 한 줄에 붙들어 두고 그 안의 하이픈 분할을 끕니다. 이유는 정의 자체에 있습니다. LaTeX는 \mbox\leavevmode\hbox{#1} 이상도 이하도 아닌 것으로 선언합니다. \hbox이미 조립이 끝난 박스이고, TeX는 완성된 박스 안으로 되돌아가 줄바꿈 지점이나 하이픈 자리를 찾지 않습니다. 그래서 \mbox{antidisestablishmentarianism}은 “끊지 말아 달라”는 부탁을 받은 것이 아니라 아예 끊을 수 없습니다. 내용을 왼쪽에서 오른쪽으로 흘려보낼 뿐 결코 줄을 바꾸지 않는 이런 박스를 LaTeX 용어로 LR 박스(내부는 LR 모드)라고 합니다. \mbox, \makebox, \fbox, \framebox가 그 계열이며, 너비를 정해 두고 줄바꿈하는 단락 박스 \parboxminipage와 짝을 이룹니다. 조판이란 박스를 접착제로 잇는 일이라는 TeX의 세계관이 이 명령 하나에 응축되어 있습니다.

대가는 끊을 수 없는 박스가 줄에 들어가지 못할 수 있다는 점이고, 그럴 때 TeX가 불평을 합니다. 그 메시지는 글자 그대로 읽을 값어치가 있습니다. Overfull \hbox (29.62221pt too wide) in paragraph at lines 5--5. “줄”이 아니라 \hbox라고 적혀 있는 것은 TeX가 만들어 내는 줄 자체가 hbox이기 때문입니다. 단락 분할기는 낱말과 늘어나는 접착제를 한 줄에 하나씩의 hbox에 채워 넣습니다. 도널드 커누스와 마이클 플라스가 1981년에 「Breaking paragraphs into lines」로 발표한 모델입니다. 문서 클래스에 draft 옵션을 붙이면 LaTeX가 \overfullrule을 5pt로 설정해 문제가 된 줄 옆 여백에 검은 막대를 찍는데, 그 막대 자체가 꽉 채워진 박스입니다. \mbox는 정말로 쪼개지면 안 되는 것——제품명, 전화번호, 수치와 단위——에 쓰고, 보기 싫은 줄바꿈의 만병통치약으로 쓰지는 마세요.

latex
% never split, never hyphenated
Released for \mbox{Mac OS X} and \mbox{Windows 11}; call \mbox{+81 3-1234-5678}.

% too wide for the measure -> Overfull \hbox (...pt too wide) in the log
\parbox{3.4cm}{aaa \mbox{antidisestablishmentarianism}}

% \documentclass[draft]{article} sets \overfullrule=5pt and marks the line

\makebox로 너비와 정렬 정하기: 너비 0 박스, \rlap\llap

\makebox[width][position]{text}는 너비를 정할 권한을 TeX에게서 가져와 사용자에게 넘긴 \mbox입니다. [position]은 요구한 너비 안에서 내용이 놓일 자리를 정합니다. l은 왼쪽, r은 오른쪽, c는 가운데(기본값), 그리고 s는 낱말 사이의 접착제를 늘려 내용이 너비를 정확히 채우게 합니다. \makebox[3cm][s]{a b c d}는 실제로 꼭 3cm 너비로 나옵니다. 다른 글자도 받아들이기는 하지만 LaTeX가 Unexpected alignment 경고를 내고 c로 되돌립니다.

너비 인수 안에서는 네 개의 이름이 “지금 박스에 담으려는 것의 자연스러운 크기”를 가리킵니다. \width, \height, \depth, 그리고 \totalheight(높이와 깊이의 합)입니다. 이들은 너비가 평가되기 전에 내용으로부터 설정되므로, \makebox[2\width]{word}word를 21.4167pt로 재고 나서 42.8334pt짜리 박스를 만듭니다. “이것의 두 배 너비”라는 말을, 이것이 무엇이 될지 정하지 않은 채 쓸 수 있는 깔끔한 방법입니다. 같은 네 이름은 \framebox, \savebox, \raisebox의 인수에서도 쓸 수 있습니다.

그리고 가장 쓸모 있는 너비는 0pt입니다. 너비 0 박스는 조판은 되지만 현재 위치를 전진시키지 않으므로, 뒤따라오는 것 위에 겹쳐 찍힙니다. \makebox[0pt][l]{...}은 현재 지점의 오른쪽으로, \makebox[0pt][r]{...}은 왼쪽으로 내용을 드리우고, [c]는 양쪽에 걸칩니다. 이 둘에는 짧은 이름이 있는데——\rlap\llap——LaTeX는 정확히 그대로 \hbox to 0pt{#1\hss}\hbox to 0pt{\hss#1}로 정의합니다. 겹쳐 찍기, 매달린 구두점, 여백의 표시, 그리고 직접 만든 취소선 매크로의 대부분이 모두 이 하나의 요령입니다.

latex
\makebox[3cm][l]{left}|\makebox[3cm][r]{right}|
\makebox[3cm][s]{a b c d}|   % interword glue stretched to exactly 3cm
\makebox[2\width]{word}|      % twice the natural width of "word"

% zero width: the slash overprints the space that follows the X
X\rlap{/}\quad Y              % identical to X\makebox[0pt][l]{/}\quad Y

\fbox\framebox의 차이, 그리고 \fboxsep\fboxrule

\fbox{text}\framebox[width][position]{text}의 차이는 \mbox\makebox의 차이와 정확히 같습니다. \fbox는 내용을 자연스러운 너비 그대로 감싸고, \framebox는 지정한 너비 안에 감싸며, 위치 지정도 lcrs로 같습니다. 둘 다 박스의 네 변에 선을 긋고, 모양은 두 길이가 정합니다. 선 두께인 \fboxrule0.4pt, 선과 내용 사이 여백인 \fboxsep3pt입니다. 계산이 맞아떨어집니다. Hx의 너비는 12.77782pt이고 \fbox{Hx}는 19.5778pt——차이가 정확히 3.4pt의 두 배입니다. \setlength{\fboxsep}{0pt}라고 쓰면 테두리가 글자에 딱 붙습니다.

\fbox는 LR 박스이므로 감쌀 수 있는 것은 한 줄분입니다. 단락을 감싸고 싶다면 단락 박스를 감쌉니다——\fbox{\parbox{6cm}{...}}. 바깥 박스는 안에 무엇이 들었는지가 아니라 그것이 얼마나 큰지만 봅니다. 이 조립 가능성이야말로 박스 모델의 핵심이며, 동시에 \parbox를 두른 테두리가 앞 절의 내림 획 문제를 그대로 물려받는 이유이기도 합니다. 테두리 박스를 여러 개 쌓는다면 안쪽 글의 처음과 끝에 \strut을 넣어 두면 높이가 맞습니다. 둥근 모서리, 배경색, 페이지를 넘어 이어지는 테두리가 필요해지면 framedtcolorbox 같은 전용 패키지의 영역이며, 그쪽은 “테두리” 페이지에서 다룹니다.

latex
\setlength{\fboxrule}{1pt}   % default 0.4pt
\setlength{\fboxsep}{6pt}    % default 3pt
\fbox{one line}\quad\framebox[4cm][r]{4cm wide, flush right}

% frame a whole paragraph: put a paragraph box inside the frame
\fbox{\parbox{6cm}{\strut A framed paragraph. The frame follows the box,
and the struts keep its height independent of ascenders and descenders.\strut}}

\parboxminipage의 차이

둘 다 너비를 고정하고 그 안에서 글이 줄바꿈되게 합니다. 차이는 흔히 되풀이되는 “\parbox에는 단락을 하나만 넣을 수 있다”가 아닙니다. 얼마든지 넣을 수 있고 목록도 들어갑니다——\parbox{4cm}{First.\par Second.}는 실제로 두 단락을 출력합니다. 진짜 차이는 minipage환경이고 \parbox명령이라는 점이며, 여기서 “안에 무엇을 쓸 수 있는가”가 갈립니다. 그 밖의 형식은 같습니다. \parbox[position][height][inner-pos]{width}{text}\begin{minipage}[position][height][inner-pos]{width}입니다. 둘 다 \parindent\parskip을 0으로 되돌리고 \linewidth를 박스 너비로 설정합니다. 그래서 그 안에서 \includegraphics[width=\linewidth]{...}라고 쓰면 기대한 대로 동작합니다.

[position]은 박스가 주변 줄과 어떻게 만나는지를 정하는 인수이며, 가장 많이 오해받습니다. [t]박스의 위쪽을 맞추는 것이 아닙니다. 박스의 첫 번째 줄 기준선을 주변 기준선에 올려놓습니다. 세 줄짜리 \parbox[t]를 재 보면 높이는 4.30554pt——소문자 o의 높이이지 박스의 높이가 아닙니다——이고, 깊이는 24pt, 즉 기준선 아래로 두 줄이 통째로 매달려 있습니다. [b]는 마지막 줄로 같은 일을 거울처럼 하고, 기본값 [c]는 박스를 줄 가운데에 맞춥니다. 그래서 [t]를 붙인 두 minipage는 첫 기준선으로 정렬되며, 한쪽이 그림이나 더 큰 글씨로 시작하면 위쪽은 여전히 어긋납니다. 각각의 첫머리에 \strut을 넣거나, 한쪽을 \raisebox로 옮기면 해결됩니다. 인수가 넷인 형태 \parbox[position][height][inner-pos]{width}{text}를 쓰면 박스의 높이도 강제할 수 있고, [inner-pos]는 그 높이 안에서 내용을 위(t)・가운데(c)・아래(b)에 둘지, 아니면 s로 위아래 가득 늘릴지를 정합니다.

둘이 정말로 갈라지는 지점은 안에 무엇을 넣을 수 있는가입니다. \parbox의 본문은 카테고리 코드가 이미 확정된 상태에서 매크로 인수로 읽힙니다. 그래서 \parbox{5cm}{\begin{verbatim}...\end{verbatim}}! Argument of \@xverbatim has an extra }.로 곧바로 실패합니다——같은 verbatimminipage 안에서는 잘 동작합니다. 각주는 오류보다 더 고약합니다. \parbox 안의 \footnote는 표식만 찍고 각주 본문을 통째로 잃어버립니다. 삽입물이 박스에 갇혀 페이지까지 도달하지 못하기 때문입니다. minipage는 이를 의도적으로 처리하여, 전용 mpfootnote 카운터가 만드는 이탤릭 소문자를 표식으로 삼아 각주를 minipage 바로 아래에 놓습니다. 플로트는 둘 다 금지여서, minipage 안에 figure를 쓰면 ! LaTeX Error: Not in outer par mode.로 멈춥니다. 그리고 어느 쪽도 페이지를 넘어 나뉘지 않습니다. 박스는 정의상 통째로 놓이는 것이기 때문입니다.

넣는 것\parboxminipage
\par여러 단락 가능여러 단락 가능
itemize가능가능(목록 깊이도 초기화됨)
verbatim! Argument of \@xverbatim has an extra }.가능
\footnote표식은 나오지만 본문이 사라짐박스 바로 아래에 이탤릭 소문자로 표시
figure사용 불가! LaTeX Error: Not in outer par mode.
\parindent0으로 초기화0으로 초기화

minipage의 가장 흔한 쓰임새는 나란한 두 단입니다. \textwidth의 0.48배쯤 되는 minipage 둘을 놓고 사이에 \hfill을 넣습니다. 합이 1 미만이어야 두 번째가 들어갑니다. 그리고 누구나 한 번은 반나절을 날리는 함정이 있습니다. \end{minipage}\begin{minipage} 사이에 있는 원본의 줄바꿈이 보통 공백이 되어, 입력한 적 없는 낱말 사이 간격만큼 두 박스가 벌어집니다. 줄 끝을 %로 닫으면 그 공백이 사라집니다. 두 단의 높이를 맞춰야 하거나 글이 박스를 감싸며 흐르게 하고 싶다면 “플로트와 배치” 페이지로 넘어가세요.

latex
\noindent
\begin{minipage}[t]{0.48\textwidth}
  \strut Left column. Several paragraphs, lists and a local footnote
  are all fine here.\footnote{Printed under this box, marked a, b, \dots}
\end{minipage}\hfill%   <- the % kills the newline; without it the boxes drift apart
\begin{minipage}[t]{0.48\textwidth}
  \strut Right column. [t] aligns the FIRST BASELINES, not the tops --
  the struts are what make the tops line up as well.
\end{minipage}

\raisebox로 위아래로 옮기기: 치수를 거짓으로 신고하는 인수

\raisebox{lift}{text}는 내용을 지정한 만큼 올리고, 값이 음수이면 내립니다. 크기가 다른 두 요소를 기준선에 맞추는 표준 도구입니다. 흥미로운 것은 두 개의 선택 인수입니다. \raisebox{lift}[height][depth]{text}는 실제로 무엇이 그려지든 상관없이 LaTeX가 그 높이와 깊이를 주위에 신고하게 만듭니다. \raisebox{0pt}[0pt][0pt]{\Huge Ag}는 너비 28.66008pt를 온전히 차지하면서도 높이 0pt, 깊이 0pt로 측정됩니다(실제 값은 16.99782pt와 4.83937pt입니다). 그래서 주변 줄 간격은 거기에 아무것도 없는 것처럼 계산됩니다. 장식 문자나 큰 글리프를 본문에 떨어뜨려도 줄 사이가 벌어지지 않는 이유가 이것이며, \width, \height, \depth, \totalheight 네 가지도 여기서 쓸 수 있습니다. 물론 신고와 실물이 어긋나면 이웃과 겹치므로, 겹침 자체가 목적일 때만 치수를 속이세요.

\rule와 스트럿(strut): 보이지 않는 박스로 줄 높이 확보하기

\rule[lift]{width}{height}는 꽉 채운 검은 직사각형을 그립니다. 박스 그 자체의 모양으로 페이지에 잉크를 올리는 거의 유일한 LaTeX 명령입니다. \rule{0.5\linewidth}{0.4pt}는 줄 너비의 절반인 가는 가로선이 됩니다. 선택 인수 [lift]는 직사각형의 크기를 유지한 채 기준선에 대한 위치를 옮깁니다. \rule[-2pt]{10pt}{8pt}는 너비 10pt, 높이 6pt, 깊이 2pt로 측정됩니다. 즉 8pt짜리 직사각형 중 2pt가 줄 아래로 매달립니다.

너비를 0으로 하면 선은 인쇄를 멈추면서도 자리 확보는 계속합니다. 그것이 스트럿(strut) 입니다. \rule{0pt}{2.6ex}는 눈에 보이지 않고 너비 0pt, 높이 11.19443pt입니다. 표 행 앞에 놓으면 그 행은 반드시 그 높이 이상이 됩니다. LaTeX 자체의 \strut은 현재 줄 간격에 맞춘 기성품으로, \baselineskip이 12pt일 때 높이 8.39996pt, 깊이 3.60004pt——줄을 0.7과 0.3으로 나눈 값입니다. \fbox{\strut Hx}\fbox{\strut Hxg}가 완전히 같게 나온 이유가 바로 이것입니다. 스트럿이 두 낱말보다 더 높고 깊으므로 내림 획이 더 이상 박스의 치수를 결정하지 못합니다. 글자가 \hline에 달라붙을 때의 표준 해법이자, 박스를 늘어놓을 때 세로 리듬을 맞추는 도구입니다.

latex
% a centred horizontal rule
\noindent\hfil\rule{0.5\linewidth}{0.4pt}\hfil

% a strut forces a taller, roomier table row
% (\strut is the ready-made one, sized to the current \baselineskip)
\begin{tabular}{|l|}
  \hline
  \rule{0pt}{2.6ex}Tall, uncramped row \\
  \hline
  \strut Same idea, sized to the current leading \\
  \hline
\end{tabular}

% put two differently sized boxes on one baseline
big \raisebox{-0.4ex}{\Huge A} small

\newsavebox\sbox\usebox로 박스 저장해 재사용하기

두 번 이상 나오는 재료는 한 번만 조판해 두고 재생할 수 있습니다. \newsavebox{\name}으로 박스 레지스터를 하나 확보하고, \sbox{\name}{text}로 채우며——\makebox 방식의 형태가 필요하면 \savebox{\name}[width][position]{text}를 씁니다——\usebox{\name}으로 꺼냅니다. 내용이 실제로 조판되는 것은 \sbox의 그 순간뿐이고 그 뒤로는 없습니다. 긴 표나 복잡한 그림을 세 군데에서 쓴다면 조판 비용은 한 번이면 되고, 세 사본은 점 하나까지 똑같습니다.

박스 레지스터는 유한한 자원이며, 스무 해쯤은 희소한 자원이었습니다. 최초의 TeX이 가진 박스 레지스터는 정확히 256개, 번호 0부터 255까지였고, 너무 많이 할당한 문서는 ! No room for a new \box로 멈췄습니다. eTeX이 상한을 32768로, LuaTeX이 65536으로 올린 덕분에 이제는 거의 부딪히지 않습니다. LaTeX는 포맷을 만들 때 엔진을 보고 상한을 정하며, 내부의 \e@alloc@top은 각각 255・32767・65535가 됩니다. 인터페이스에 관한 주의가 둘 있습니다. 저장 박스는 LR 박스이므로 여러 줄 내용은 먼저 \parboxminipage로 감싸야 합니다. 그리고 verbatim이 든 내용을 저장할 때는 환경 형태인 \begin{lrbox}{\name} … \end{lrbox}를 씁니다. 한편 오래된 자료는 “\savebox는 취약하다(fragile)”고 경고하지만, 현재 LaTeX에서는 그렇지 않습니다. \mbox, \makebox, \fbox, \parbox, \raisebox, \sbox, \savebox 모두 견고한(robust) 명령으로 선언되어 있습니다.

\usebox의 짝은 \settowidth{\len}{text}, \settoheight, \settodepth입니다. 내용을 보이지 않게 조판한 뒤 결과의 치수 하나를 길이 변수에 저장합니다. “다른 무언가와 정확히 같은 크기로 그린다”를 위한 도구로, 서명란의 너비를 재고 나서 \rule로 밑줄을 긋는 식으로 씁니다.

latex
\newsavebox{\mylogo}
\sbox{\mylogo}{\fbox{\textbf{Draft}}}   % typeset once, here

Header: \usebox{\mylogo} \dots\ Footer: \usebox{\mylogo}

% verbatim inside a save box needs the environment form
\newsavebox{\snippet}
\begin{lrbox}{\snippet}
  \begin{minipage}{6cm}\begin{verbatim}int main(void) { }\end{verbatim}\end{minipage}
\end{lrbox}
\usebox{\snippet}

% measure a box into a length, then rule exactly that wide
\newlength{\siglen}
\settowidth{\siglen}{Signature}
Signature\par\rule{\siglen}{0.4pt}

어떤 박스 명령을 언제 쓸까

명령종류이럴 때 사용
\mboxLR이 구절이 절대 끊기면 안 될 때
\makeboxLR너비를 직접 정하거나 [0pt]로 겹쳐 찍고 싶을 때
\rlapLR\makebox[0pt][l]의 축약형(\llap은 왼쪽으로)
\fboxLR자연스러운 너비 그대로 테두리를 두를 때
\frameboxLR너비와 위치를 정한 테두리(\fboxsep\fboxrule로 조정)
\parbox단락고정 너비로 줄바꿈할 때. 표 셀 안 등
minipage단락verbatim・각주・목록이 필요하거나 나란한 단을 만들 때
\raisebox변형위아래로 옮기거나 높이・깊이를 0으로 신고할 때
\rule채움 박스선을 그을 때. 너비 0이면 보이지 않는 스트럿
\strut스트럿줄이나 표 셀의 높이를 고르게 맞출 때
\usebox저장\newsavebox + \sbox로 만든 박스를 꺼낼 때
\settowidth측정너비(그리고 \settoheight\settodepth)를 길이에 저장