你写下「这里空 2cm」,LaTeX 就真的空出 2cm。可是同一句 \vspace{2cm} 一旦落在页首,这个 长度 便消失得无影无踪。这不是 bug。LaTeX 里的空白不是固定的尺寸,而是 TeX 所说的 glue(弹性空白):会伸、会缩,不再需要时会被直接丢掉。本页沿着这一条线索,梳理长度的单位(pt、bp、em、ex)、真正产生空白的命令(\hspace、\vspace 及其星号形式)以及弹性长度——包括 pt 与 bp 为何相差 0.375%、em 为何在同为 10pt 的两种字体里取值不同、\hspace* 的星号究竟在守护什么。
pt 与 bp 的区别:LaTeX 的长度单位
长度必须写成 数字 + 单位。小数和负值都可以,例如 12pt、2.5cm、-3mm;但如果漏掉单位,排版就会停在 ! Illegal unit of measure (pt inserted).——写成 \hspace{2} 或 \setlength{\mylen}{5} 时就会看到,这是 LaTeX 中最常见的错误之一。单位本身分成两类:取值与当前字体无关的 绝对单位(pt、bp、in、cm、mm、pc),以及从当前所用字体里读取数值的 字体相对单位(em、ex)。
绝对单位的基准是 pt,即 TeX 点。Knuth 原封不动地沿用了英美活字业的定义——1 英寸 = 72.27pt。与之并列,TeX 还带着第二种点:定义为恰好 1/72 英寸的 bp(big point,大点),也就是 PostScript、以及后来的 PDF 采用的那个整数。两者并不相等:1bp = 1.00375pt,big point 恰好大 0.375%,于是 100bp 等于 100.375pt。由于 PDF 自身的坐标网格就以 1/72 英寸为刻度,EPS 或 PDF 边界框(BoundingBox)里的数字全都是 bp;把插图从 \includegraphics[width=100pt] 改成 [width=100bp],尺寸就恰好差出这 0.375%。用毫米给出图片尺寸之所以更保险,正是因为根本不会发生这种混淆。
| 单位 | 含义 / 换算 | 使用场合 |
|---|---|---|
pt | TeX 点。1in = 72.27pt | 所有长度的基准;字号也用它 |
bp | 大点。1bp = 1.00375pt(72bp = 1in) | PostScript/PDF 的点;BoundingBox 的单位 |
in | 英寸。1in = 72.27pt | 美制纸张尺寸 |
cm | 厘米。1cm = 28.45274pt | 页边距与图片尺寸的实用标准 |
mm | 毫米。1mm = 2.84526pt | 较精细的尺寸 |
pc | 派卡。1pc = 12pt | 西文排版传统单位;常用于栏宽 |
dd | Didot 点。1157dd = 1238pt(≈ 1.07pt) | 欧洲大陆的活字传统 |
cc | 西塞罗。1cc = 12dd = 12.8401pt | Didot 体系中相当于派卡的单位 |
sp | 缩放点。1pt = 65536sp | TeX 内部保存长度的最小刻度;一般不手写 |
px | 像素。默认 1px = 1bp = 1.00375pt | 仅限 pdfTeX 一系;由 \pdfpxdimen 设定 |
em | 当前字体自己声明的 quad(\fontdimen6) | 字体相对;用于缩进与水平间距 |
ex | 当前字体的 x 高度(\fontdimen5) | 字体相对;与小写字母高度对齐的竖向微调 |
mu | 数学单位。18mu = 1em | 仅限数学模式;\, 为 3mu,\; 为 5mu |
表格靠下的 sp(scaled point)是 TeX 内部保存长度的最小刻度,1pt = 65536sp。你几乎不会亲手写它,但正是这一刻度同时决定了 TeX 的精度与上限。1sp 约为 5.4 纳米——远细于可见光的波长(400〜700nm);而另一端,TeX 能保存的最大长度 \maxdimen 是 16383.99998pt,折合 约 5.76 米 而已。这两个数字来自同一条约束:长度必须是 sp 的整数倍,且小于 2^30。源自欧洲大陆活字传统的 dd(Didot 点,1157dd = 1238pt)与 cc(西塞罗,1cc = 12dd)仍然可用,但在现代文档中很少出现。
em 与 ex 会随字体改变
em 不是「字号」。 它是当前生效的字体自己声明的 quad(全身宽),也就是 TeX 内部的 \fontdimen6。所以同样标称 10pt,答案也会不同:10pt 的 Computer Modern Roman 里 1em 恰好是 10.00002pt,换成同为 10pt 的等宽体 Computer Modern Typewriter,1em 就变成 10.4999pt,斜体则是 10.22217pt。顺带一提,常见的说法「em 等于大写 M 的宽度」同样经不起实测:cmr10 中 M 宽 9.16669pt,比 1em 还窄。这个名字只是金属活字时代的遗留——那时 em 空铅与字身同宽、恰成正方形;至于具体数值,交由字体自己决定。
ex 的机制相同,保存的是字体声明的 x 高度(\fontdimen5)。10pt 的 Computer Modern Roman 报出 4.30554pt,同为 10pt 的无衬线体 Computer Modern Sans 则报出 4.44444pt。这两者的取舍并不是「横向用 em、纵向用 ex」的方向问题,而是 希望空白跟随什么 的问题:要跟随字符推进量就用 em,要跟随小写字母高度做微调就用 ex。数学模式另有第三种单位 mu(math unit),18mu = 1em。\quad 就是 \hskip1em,\qquad 是它的两倍 2em;细空白 \, 在数学中为 3mu,在正文中则是 0.16667em(六分之一 em)的 kern。较宽的 \; 是 5mu,中等的 \:(4mu)要加载 amsmath 之后才可用。
% absolute units: the same length whatever font is active
\hspace{12pt} \hspace{2.5cm} \hspace{-3mm} \hspace{100bp}
% font-relative units: the font decides the value
\hspace{1em} \vspace{2ex}
% proof that em follows the font, not the point size
\rmfamily \the\dimexpr 1em\relax % 10.00002pt at 10pt
\ttfamily \the\dimexpr 1em\relax % 10.4999pt at 10pt弹性长度:plus 与 minus 到底做了什么
plus 给出可伸长量,minus 给出可缩短量。像 12pt 这样纹丝不动的长度称为 刚性长度(rigid length),像 1cm plus 2mm minus 1mm 这样留有余地的称为 弹性长度(rubber length),其含义是「通常 1cm,需要时可伸到 1.2cm,受挤压时可缩到 0.9cm」。对 TeX 来说这并非特殊功能:无论在行内、段落还是整页,几乎所有空白本质上都是可伸缩的 glue。一行之所以能严丝合缝地顶到左右两边,正是因为词间空白是弹性的。反过来说,排下一串毫无余地的 \vspace{12pt},等于夺走了 TeX 用来周旋的空间。
% rigid: exactly 1cm, always
\vspace{1cm}
% rubber: 1cm normally, up to 1.2cm, down to 0.9cm
\vspace{1cm plus 2mm minus 1mm}
% infinite stretch: 0pt at rest, grows without limit
\vspace{\fill}LaTeX 的 \fill 是一个 skip 寄存器,内容为 0pt plus 1fill——自然长度为 0,伸长量却是 无限。无限必胜有限,因此同一行或同一栏里只要出现一个 \fill,周围那些只有有限 plus 的普通空白就完全不再伸长。TeX 的无限分三级:fil < fill < filll。\hfil 带的是 plus 1fil,\hfill 带的是 plus 1fill,两者同处一行时,\hfill 会把剩余空间全部拿走。想按比例分配就用 \stretch{n}(即 0pt plus n fill):\hspace{\stretch{2}} 与 \hspace{\stretch{1}} 并列,剩余空间便按 2:1 分开;两个同级的 \fill 则是平分。
那么这份伸缩在竖直方向上究竟起不起作用?有时起,有时不起。 在把每页底部对齐的 \flushbottom 下,TeX 会把该页上已有的可伸缩空白——段间的 \parskip(默认 0pt plus 1pt)、标题前后的空白、偶尔的一个 \bigskip——一点点拉开,直到该栏恰好等于版心高度。而在 \raggedbottom 下就不会,原因只是 LaTeX 内核里的一行:\raggedbottom 只在正文末尾追加一个 \vskip 0pt plus.0001fil。这一丝 无限 伸长量压过页面上所有有限的 plus,把全部余量独吞,于是正文中的空白保持自然长度,剩下的统统堆到页底。标准文档类则替你做出选择:article、report、book 在 单面 时调用 \raggedbottom,在 twoside 与 twocolumn 时调用 \flushbottom。由于 article 默认 oneside,在默认的 article 里,你写在 \vspace 上的 plus/minus 几乎不会被用于页面调整——想让它生效,就声明 \flushbottom。
\hspace 与 \hspace* 的区别,以及 \hfill、\dotfill
在行内插入水平空白的是 \hspace{长度}:写 \hspace{2cm} 就得到 2cm——前提是这段空白不落在行的边缘。TeX 每次把段落切成行,都会丢弃紧邻断点的空白,好让右边不挂着多余的白,也让下一行不会莫名其妙地缩进。星号形式 \hspace*{长度} 绕开了这一点。翻开它的定义会发现,它不过是在空白之前放了一条宽度为 0 的 \vrule 和一个 \nobreak:线条是 盒子 而不是 glue,无法被丢弃,于是它后面的空白也就不再「紧邻断点」了。需要给首行强制缩进时,用的正是它。
\hfill 与 \hspace{\fill} 是同一回事:把两侧的内容在一行允许的范围内尽量推开,这是把一个词顶到行首、另一个顶到行尾的标准做法。它同样是 glue,因此在行边会被丢弃;必须保留时就写 \hspace*{\fill}。若希望这段空隙被 填满 而不是留白,可用画横线的 \hrulefill 与铺点的 \dotfill,两者本质上都是 \hfill 配合 TeX 的 leaders 机制(\leaders/\cleaders)。目录条目与页码之间的引导线是它,填写栏下方的横线也是它。至于较小的固定空隙,跟随字体的 \quad(1em)、\qquad(2em)与 \, 在字号变化时仍能保持视觉平衡——写死的 6pt 就做不到。
Name:\hspace{2cm}Date:\par
\noindent Chapter 1\quad Introduction\dotfill 1\par
\noindent left\hfill centre\hfill right\par
\noindent Signature:\hrulefill\par
% 2:1 split of the leftover space
\noindent A\hspace{\stretch{2}}B\hspace{\stretch{1}}C\par
% survives even at the start of a line
\noindent\hspace*{2cm}forced indent为什么 \vspace 会在分页处消失
因为同一条规则在竖直方向上照样成立。无论断点落在哪里,TeX 都会丢弃紧邻断点的空白,所以落在页首的 \vspace{3cm} 会整整齐齐地消失;实测表明,紧跟在 \newpage 之后的 \vspace{3cm} 贡献恰好为零——「别处都好用,唯独这里不生效」多半就是它。用意与 \hspace 一致:不让页首留下一条无从解释的白带。星号形式 \vspace*{长度} 用同样的手法绕开:在空白之前放一条高度为 0 的 \hrule 和一个 \nobreak。凡是无论分页落在哪里都必须保留的空白——例如章首页上方固定的一段留白——就用 \vspace*。
日常的竖直留白有三档现成的尺寸:\smallskip、\medskip、\bigskip,标准值分别是 3pt plus 1pt minus 1pt、6pt plus 2pt minus 2pt、12pt plus 4pt minus 4pt。10pt 文档中 \baselineskip 为 12pt,因此 \bigskip 大致相当于一行。三者都是弹性长度,会随页面情况略微伸缩。这时上一段的结论就发作了:它们都是以不带星号的 \vspace 定义的(如 \vspace\smallskipamount),因而在分页处会像其他 glue 一样被毫不留情地丢弃。你在标题上方留的空白,一旦该标题落到页首就消失不见,原因正在于此;必须保留就写 \vspace*{\bigskipamount}。竖直方向的 \fill 是 \vfill(即 \vspace{\fill}),可用来把只有一行的页面垂直居中,或把签名栏一直压到页面最底部。
| 命令 | 插入的空白 | 断点处会被丢弃吗 |
|---|---|---|
\hspace{len} | 指定宽度的水平 glue | 是,在行边会消失 |
\hspace*{len} | 同样的宽度,前面加一条宽 0 的 \vrule | 否 |
\hfill | 等同于 \hspace{\fill}:0pt plus 1fill | 是 |
\hfil | 0pt plus 1fil;比 \hfill 弱一级 | 是 |
\stretch{n} | 0pt plus n fill;按比例分配空间 | 是 |
\hrulefill | 用横线填满空隙的 \hfill | 是 |
\dotfill | 用点填满空隙的 \hfill;目录引导线 | 是 |
\quad | \hskip1em;随字体变化的固定空白 | 是 |
\qquad | \hskip2em;\quad 的两倍 | 是 |
\, | 细空白:数学中 3mu,正文中 0.16667em | 否,正文中是 kern |
\vspace{len} | 指定高度的垂直 glue | 是,在分页处会消失 |
\vspace*{len} | 同样的高度,前面加一条高 0 的 \hrule | 否 |
\vfill | 等同于 \vspace{\fill};在竖直方向撑开剩余空间 | 是 |
\smallskip | 值为 3pt plus 1pt minus 1pt 的 \vspace | 是 |
\medskip | 值为 6pt plus 2pt minus 2pt 的 \vspace | 是 |
\bigskip | 值为 12pt plus 4pt minus 4pt 的 \vspace;10pt 下约一行 | 是 |
用 \newlength、\setlength、\addtolength 把长度当变量
如果同一个长度在文档各处反复出现,声明一个 长度寄存器 并按名引用会更好维护。\newlength{\mylen} 声明新长度,\setlength{\mylen}{2em} 赋值,\addtolength{\mylen}{-1pt} 增减;只需在一处改动,所有引用都会跟着变。声明只能做一次——对同一个名字再次 \newlength 会得到 ! LaTeX Error: Command \mylen already defined.。若要量取实际排出来的尺寸,\settowidth{\mylen}{Total:}(以及 \settoheight、\settodepth)会把那段文字真正占据的宽度存进去。想核对数值时,写 \the\mylen 就会把当前值输出到 PDF 里。
已有的长度可以直接参与计算:\textwidth(正文块宽度)、\linewidth(当前正在排的行宽——在 minipage 或列表内部会比 \textwidth 窄)、\baselineskip(行距)、\parindent(缩进量)、\parskip(段间空白,默认 0pt plus 1pt)。在前面加系数即可缩放,如 0.8\textwidth,且完全不需要任何宏包。但这里有一个因为不报错而更棘手的陷阱:不加载 calc 时,写 \setlength{\mylen}{\textwidth - 2em},LaTeX 一声不吭。 可发生的并不是减法:\mylen 拿到的是原样的 \textwidth(默认 article 下为 345.0pt),而剩下的 - 2em 会 被当作正文文字排出来。往往要等到 PDF 里冒出「- 2em」这串字符才发现。加上 \usepackage{calc},同一行就会正确地给出 324.99997pt,并且可以写 (\textwidth - 2em)/2 这类带括号的表达式。
\usepackage{calc} % needed for + - * / inside \setlength
% ...
\newlength{\gutter}
\setlength{\gutter}{2em}
\addtolength{\gutter}{-1pt}
\newlength{\halfcol}
\setlength{\halfcol}{(\textwidth - \gutter) / 2}
\newlength{\labelw}
\settowidth{\labelw}{Total:} % width of that text once typeset
\the\halfcol % prints the value, e.g. 162.49998pt
\noindent\hspace*{0.8\textwidth}right-aligned heading归结到实务,大致就是三条习惯。写死的固定值是最后的手段:一旦写下 8cm,只要纸张尺寸或页边距一变,比例就崩了;而基于已有长度的表达式——0.8\textwidth、2\baselineskip——则经得起改动。竖直留白优先使用现成的 \smallskip/\medskip/\bigskip,把生硬的 \vspace{12pt} 留给真正必要的场合,整篇文档的呼吸才会一致。以及 只给那些确实必须挺过断点的空白加星号——没加星号的空白,就等于告诉 TeX 它可以丢掉。守住这三条,「唯独这里空白莫名消失」这类不明原因的事故基本就不会再发生。