盒子

LaTeX 会先把文字、数学公式和图形都装进 盒子,再把这些盒子排到页面上。盒子是带有宽度、高度和深度这三个尺寸的刚性矩形;断行器不会查看盒子内部,而是把它当作一个不可分割的整体来处理。理解了这种“先装进盒子”的思路后,移动文字、重叠内容、加框以及制作分栏,都会显现为同一机制的不同用法。

盒子模型

在 TeX 内部,所有已经排好的素材都是带尺寸的盒子。一个字符是盒子,一个单词是盒子,一行是盒子,整页也是盒子。LaTeX 生成行和段落时,会用可伸缩的空白把这些盒子粘接起来,并在最佳位置断行、分页。关键在于:一旦素材被打包成一个盒子,它的内容就不会再被拆开。 盒子内部不会发生断行;整个盒子会作为一个单位移动到下一行。

LaTeX 的盒子大致分为两类。第一类是 LR 盒子(left-to-right box):它只把内容排成一行,绝不会在中途断行;如果横向太长,就会伸出边界。\mbox\makebox 以及带框的 \fbox 都属于 LR 盒子。第二类是 段落盒子:它会在预先指定的宽度内折行,因此可以容纳多行,甚至多个段落。\parboxminipage 环境就是段落盒子。

这种区别对应于 TeX 的 模式。LR 盒子的内部处于 LR 模式,只把输入从左到右排下去,不会断行。段落盒子的内部会切换到 (内部)段落模式,断行机制就像普通正文一样工作。只要留意哪个命令会进入哪种模式,就能清楚看出为什么某些内容不会换行,或者为什么某个盒子会向下增长。

LR 盒子:打包成一行

最基本的 LR 盒子是 \mbox{...}。它把内容封进一个盒子,并且 禁止断行和断词。如果有不希望被拆开的短语,比如产品名 Mac OS X、电话号码或很长的化合物名称,用 \mbox 包起来就会始终留在同一行。它也适合做细小调整,例如让数值和单位保持在一起。

想自己指定宽度时,使用 \makebox[width][position]{text}[width] 设定盒子的宽度,[position] 选择内容在盒子中的位置:l 为左对齐,c 为居中(默认),r 为右对齐,s 会拉伸词间空白,使这一行在整个宽度内两端对齐。除了 \textwidth 这样的普通长度,宽度还可以使用 表示内容自然尺寸的特殊长度\width(内容宽度)、\height\depth,以及 \totalheight(高度加深度)。因此,\makebox[2\width]{word} 会生成一个宽度为该词两倍的盒子。

特别有用的是 零宽盒子 \makebox[0pt][position]{text}。因为盒子的宽度为 0,LaTeX 会排出内容,但 不会推进当前的水平位置,所以内容可以叠印到后续材料上。使用 [c] 时内容会以当前位置为中心向左右伸出,使用 [r] 时向左伸出,使用 [l] 时向右伸出。删除线、重叠印字和边注都利用这一机制;简写命令 \rlap\llap(分别是向右、向左伸出的零宽盒子)也是同样的原理。

要画边框,可以使用 \fbox{text}\framebox[width][position]{text},它们会在盒子的四边画线。\framebox[width][position]\makebox 完全相同。边框的外观由两个长度控制:线条粗细 \fboxrule(默认 0.4pt),以及边框和内容之间的间距 \fboxsep(默认 3pt)。可以用 \setlength 修改;例如 \setlength{\fboxsep}{0pt} 会让边框紧贴内容。这些都是单行 LR 盒子,因此若要给多行内容加框,需要与下面的段落盒子组合使用(更复杂的边框请参见“框线”)。

document.tex
Do not break \mbox{Mac OS X} here.

% a 4cm box, contents flush right, then framed
\framebox[4cm][r]{right} \\[1ex]

% zero-width box: the word overprints what follows
X\makebox[0pt][l]{\,/} Y   % prints an X/ over the gap before Y

在这个例子中,\mbox 包住的短语始终保持在一行,\framebox[4cm][r] 把内容右对齐放在 4 cm 宽的边框中,最后的零宽盒子则在 X 后面叠印一条斜线。

段落盒子:parbox 与 minipage

当你想固定宽度并让内容自动换行时,就要使用段落盒子。较轻量的是 \parbox[position]{width}{text},它会在给定的 {width} 内把内容作为一个段落来折行。[position] 指定 这个盒子相对于周围行的对齐位置c(默认,盒子中心对齐到该行中心)、t(盒子首行的基线对齐到周围基线)或 b(盒子末行的基线对齐)。还可以写成 \parbox[position][height][inner-pos]{width}{text},进一步指定高度和内部垂直对齐(t/c/b/s),但 \parbox 只能处理 单个段落,不能放多个段落或列表。

功能更强的段落盒子是 minipage 环境:写作 \begin{minipage}[position][height][inner-pos]{width}\end{minipage},参数含义与 \parbox 相同,但其内容会像 一小页独立页面 一样处理。它可以包含多个段落、itemize 等列表,甚至 verbatim;在段落盒子中它是真正的主力。需要注意的是,内部的 段落缩进(\parindent)会重置为 0;如果需要,可以用 \setlength{\parindent}{1em} 自行设置。

minipage 还有自己独特的 脚注处理。在其中使用 \footnote 时,脚注不会出现在页面底部,而是出现在 minipage 正下方,并以 a, b, … 这样的小写字母标记(使用专门的 mpfootnote 计数器)。当你希望表格或框内说明的注释自成一体时,这很方便。另一方面,minipage 不能在内部放置浮动体(figure/table),也不会跨页拆分,它必须作为一个盒子完整放入页面。

minipage 最常见的用法是 并排分栏。把两个 minipage 连续放置,并在中间加入 \hfill 这样的空白,就能得到简单的两栏布局,适合把图和说明文字左右并排,或把两张图片并排放置。把两者都设为 [t],即使两个盒子的高度不同,顶端也能整齐对齐(更正式的放置方法请参见“浮动体与放置”)。

document.tex
\noindent
\begin{minipage}[t]{0.48\textwidth}
  Left column. This minipage wraps text within 48\%
  of the text width, and can hold several paragraphs,
  lists, and even its own footnote.\footnote{Local note.}
\end{minipage}\hfill
\begin{minipage}[t]{0.48\textwidth}
  Right column, top-aligned with the left one because
  both use the optional \verb|[t]| argument.
\end{minipage}

两个 minipage 各占正文宽度的 48%,中间的 \hfill 会吸收剩余空间,使它们横跨整行。因为两者都带有 [t],即使行数不同,顶端也会对齐。左侧盒子中的 \footnote 会带着小写标记出现在 minipage 下方,而不是页面底部。

移动与画线:raisebox 和 rule

要在垂直方向移动盒子,可以使用 \raisebox{lift}[height][depth]{text}{lift} 为正时内容上移,为负时内容下移。巧妙之处在于可选的 [height][depth]:它们会 告诉 LaTeX 假装这个盒子具有指定的高度和深度,因此周围行距会根据声明值而不是实际绘制的外观来计算。例如,\raisebox{0pt}[0pt][0pt]{a big symbol} 会实际画出较大的内容,却声明高度和深度都为零,从而可以插入装饰而不扰乱行距。这里同样可以使用 \height\depth\totalheight 等特殊长度。

\rule[lift]{width}{height} 会画出一个实心填充矩形(黑盒)。{width}{height} 指定它的尺寸,可选的 [lift] 调整它相对于基线的位置(为负时向下)。它非常适合画水平线,例如 \rule{0.5\linewidth}{0.4pt} 会得到一条宽度为行宽一半的细线。

\rule 的另一面是 支柱(strut)。零宽的 \rule{0pt}{height} 是一个不可见盒子,不打印任何内容,却会 预留相应高度,适合强制最小行高,或给表格单元格留出空间。在一行开头放入 \rule{0pt}{2.6ex},这一行就保证至少有 2.6 ex 高。LaTeX 自带的 \strut 实际上正是 \rule[-0.3\baselineskip]{0pt}{\baselineskip},也就是一个零宽支柱,覆盖当前行距在基线上下的完整高度和深度。这是防止表格线和文字挤在一起的经典技巧。

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

% a strut forces a taller line / roomier table cell
\begin{tabular}{|l|}
  \hline
  \rule{0pt}{2.6ex}Tall, uncramped row \\
  \hline
\end{tabular}

% align two differently sized boxes on a common baseline
big \raisebox{-0.4ex}{\Huge A} small

保存并重复使用盒子

如果要反复使用同一份材料,可以 只排版一次,把它保存到盒子里,之后再取出来使用。首先用 \newsavebox{\boxname} 声明一个保存用盒子(寄存器)。然后用 \sbox{\boxname}{text}\savebox{\boxname}[width][position]{text} 填入内容;内容真正被排版只发生在这一刻一次。在需要的地方写 \usebox{\boxname},就会原样输出已保存的盒子。对于长表格或复杂图形这类要在多处使用、排版成本较高的材料,这既能加快处理,也能保证外观完全一致。

保存用盒子本身是 LR 盒子,因此如果要保存多行内容,需要先用 \parboxminipage 包起来。也可以使用环境形式 \begin{lrbox}{\boxname} … \end{lrbox},它更适合保存包含 verbatim 的内容。注意,\sbox 是稳健的(robust),而 \savebox 是脆弱的(fragile),所以在章节标题等“移动参数”中使用后者时要小心。

要测量盒子的尺寸,可以使用 \settowidth{\len}{text}\settoheight{\len}{text}\settodepth{\len}{text}。它们会把内容排版后的宽度、高度或深度存入指定的长度寄存器。需要按照盒子尺寸精确画边框或下划线时,这些命令很有用。

document.tex
\newsavebox{\mylogo}
\sbox{\mylogo}{\fbox{\textbf{Draft}}}   % typeset once

% reuse the identical box as many times as you like
Header: \usebox{\mylogo} \dots\ Footer: \usebox{\mylogo}

% measure a box into a length, then rule under it
\newlength{\w}
\settowidth{\w}{Signature}
Signature\par\rule{\w}{0.4pt}

\sbox 那一行把 “Draft” 加框并只排版一次,两个 \usebox 调用取出的都是同一个已保存盒子。下半部分用 \settowidth 测量 “Signature” 的宽度,再用 \rule 画出正好同宽的下划线。

盒子命令速览

命令类型作用
\mboxLR 盒子将内容固定在一行;不允许断行或断词
\makeboxLR 盒子指定宽度和位置;[0pt] 以零宽叠印
\frameboxLR 盒子带框的 \makebox\fbox 是不指定宽度的形式)
\parbox段落盒子在固定宽度内折行的单个段落盒子
minipage段落盒子可容纳段落、列表和脚注的小页面
\raisebox变换上下移动内容;可声明虚假的高度和深度
\rule填充盒子实心矩形;零宽时成为支柱(strut)
\usebox存储输出通过 \newsavebox + \sbox 保存的盒子