分栏不是排版风格上的偏好,而是为阅读服务的装置。10pt 的 article 每行宽 345pt,英文约 78 个字符;而字体排印学者 Robert Bringhurst 认为舒适的行长是 45 到 75 个字符,理想值是 66。把页面切成两栏,每一行就正好回到这个区间。学术期刊、会议论文集和报纸之所以分栏,原因就在这里。LaTeX 为此准备了两件性格迥异的工具:类选项 twocolumn,以及 multicol 宏包的 multicols 环境。它们看着像是一回事,其实不是。本页要追的是真正会让人栽跟头的差异——最多能排几栏、最后一页会不会自动排齐,以及图表悄悄消失的两种不同原因。
twocolumn 和 multicol 该用哪一个
整篇文档双栏且含图表,用 twocolumn;只想让局部分栏、需要三栏以上、或要求最后一页排齐,用 multicol。分界线在浮动体上。twocolumn 原封不动地保留了 LaTeX 的浮动机制,因此 figure 仍会在栏内浮动,figure* 则横跨整页。而 multicol 是整套替换输出例程的实现,它换来了栏高平衡,代价是彻底放弃栏内浮动体。本页几乎所有讨论都由这一次交换派生而来。
| 写法 | 栏数 | 效果 |
|---|---|---|
twocolumn | 2 | 类选项;整个正文变双栏,最后一页不排齐 |
\twocolumn[⟨text⟩] | 2 | 在正文中使用;先强制分页,再把 ⟨text⟩ 以全宽排在两栏之上 |
\onecolumn | 1 | 在正文中使用;强制分页并回到单栏 |
multicols | 2 到 10 | 环境;可在页面中途开始与结束,并把最后几栏排齐 |
multicols* | 2 到 10 | 同上,但最后几栏从左往右依次填满,不做排齐 |
把整篇文档排成双栏 —— twocolumn 选项
在类选项里加上 twocolumn 就够了:\documentclass[twocolumn]{article} 会把整个正文排成双栏(默认是 onecolumn)。许多学会论文类内部都以这个选项为前提来设计。若要在文档中途切换,可在正文里调用 \twocolumn 与 \onecolumn,但有两处限制:这两个命令都会开始新的一页,所以栏数不会在页面中途改变;而 \twocolumn 只能排出 恰好两栏。一旦需要第三栏,这件工具就到头了。
\documentclass[twocolumn]{article}\twocolumn 还带一个可选参数,论文的“脸面”通常正是由它造出来的。写成 \twocolumn[⟨全宽内容⟩],这部分内容会单独以 单栏(版心全宽) 排在双栏正文之上。标题、作者、单位与摘要横跨整页,正文从下方开始分成两栏——每篇会议论文都穿着的这身外衣,就是这一行写出来的。
\documentclass[twocolumn]{article}
\begin{document}
\twocolumn[%
\begin{center}
{\LARGE A Study of Narrow Measures}\\[4pt]
A. Author \quad Institute of Typography
\end{center}
\vspace{1em}
]
The body starts here and is set in two columns.
\end{document}栏与栏之间的间距是长度 \columnsep,其中竖线的粗细是 \columnseprule。标准类的默认值是 \columnsep 为 10pt、\columnseprule 为 0pt(不画线)。代入数字,开头那番话就具体了:10pt 的 article 的 \textwidth 是 345pt,因此单栏宽 (345 − 10) ÷ 2 = 167.5pt,英文约 38 个字符,正好落在 Bringhurst 为多栏推荐的 40 到 50 之下——也就是说,\columnsep 每加一点,都是从本已不宽裕的行长里扣。更划算的交换往往是画一条竖线:靠它在视觉上分开两栏,间距本身就能保持窄。
\setlength{\columnsep}{20pt} % wider gap between the columns
\setlength{\columnseprule}{0.4pt} % thin vertical rule inside the gap让图表跨栏占满全宽 —— figure* 与 table*
用带星号的环境。\begin{figure*} 与 \begin{table*} 会生成跨越两栏、占满页宽的浮动体。里面的宽度设定记住一条就能避开大部分事故:\columnwidth 是单栏的宽度,\textwidth 是两栏加栏间距的整个版心宽度。放进单栏的图用 width=\columnwidth,跨栏的图用 width=\textwidth。写反了,不是溢出版心,就是排得莫名其妙地小。
\documentclass[twocolumn]{article}
\usepackage{graphicx}
\begin{document}
% one column wide: an ordinary float
\begin{figure}
\centering
\includegraphics[width=\columnwidth]{plot}
\caption{Fits inside a single column.}
\end{figure}
% spans both columns: starred float, default placement is [tp]
\begin{figure*}[t]
\centering
\includegraphics[width=\textwidth]{wide-plot}
\caption{Spans the full text width.}
\end{figure*}
\end{document}“figure* 没有出现在我写它的地方”“往后跳了三页”这类抱怨,一旦知道它的默认位置就说得通了。LaTeX 内核源码明确写着:所有跨栏浮动体的默认位置指定都是 tp。也就是说 \begin{figure*} 会被悄悄当作 [tp] 处理——页面顶部,或者一整页的浮动页——而不是 htbp。而且正如 LaTeX2e 参考手册指出的,双栏输出中的全宽浮动体不允许使用 b(页底)。如果下一页的顶部已被占用,它就会继续往后滑。确实需要放在页底时,加载 stfloats 或 dblfloatfix 就能让 b 生效。
这条队列里曾长期藏着一个颇有意思的缺陷。2015 年那次发布之前,LaTeX 用 两条彼此独立的延迟队列 管理待放的浮动体:一条给单栏浮动体,一条给跨栏浮动体。两条队列就会有插队——写在后面的单栏图 3,可能越过写在前面的跨栏图 2 先印出来。修正最初以 fixltx2e 宏包的形式发布,把两条队列合并成一条。2015 年,fixltx2e 的全部修正被并入内核本身,因此在任何现行 TeX 环境中都不必再额外加载什么。若读到旧帖抱怨图序错乱,先看看它的日期。
三栏以上,以及能排齐的栏 —— multicol 宏包
加载 \usepackage{multicol},把要分栏的区域放进 multicols 环境,并把栏数作为参数传入。仅此一步就能获得 2 到 10 栏(十栏上限自 1.5 版起);环境可在页面中途开始、中途结束,结束时会自行把各栏高度排齐。multicols 甚至可以嵌套。由于它不强制分页,在单栏正文中间插入一段三栏的术语表或索引,是件很自然的事。
\usepackage{multicol}
% ... later, in the body
\begin{multicols}{3}
This text is broken into three balanced columns.
\end{multicols}multicol 的诞生,起因是一份索引。1989 年,Frank Mittelbach 在为 doc 宏包写宏时,希望文档文件的索引和参考文献能挤在同一页上。标准双栏做不到这件事:\twocolumn 必定另起一页,而最后一页会以半空的右栏收尾。他在宏包文档里记下,第一版花掉了他 整整一个周末,起点是《The TeXbook》第 417 页的算法——并补了一句:这句话写在初版文档里,此后又投入了好几百个小时。也就是说,排齐栏高从一开始就不是为了好看,而是为了 省纸。
multicols 在栏数之后接受两个可选参数。第一个 [⟨前置文字⟩] 是紧接在分栏之上、以全宽排版的标题或引言。第二个 [⟨长度⟩] 是分栏能在此页开始所需的页底最小剩余空间,它会就此一次覆盖默认值 \premulticols(50pt)。若剩余不足,环境就从新的一页开始。前置内容较长时若省略这个参数,宏包可能以 Error saving partial page 报错,后面跟着 Some text may be lost!——它给出的处方正是加大 \premulticols。
\begin{multicols}{3}
[\section{Glossary}
This heading and this sentence run the full text width.]
[6cm]
Everything after the optional arguments is split into three columns.
\end{multicols}为什么 twocolumn 的最后一页排不齐
因为 LaTeX 在知道还有下一栏之前,就已经把上一栏做完了。双栏模式下的输出例程 \@outputdblcol 每栏调用一次:左栏填满时,其内容被收进名为 \@leftcolumn 的盒子,排版随即继续;只有当右栏也填满,整页才被组装并输出。也就是说,LaTeX 从来没有过“手里握着剩余正文、决定如何一分为二”的时刻。于是 \end{document} 时剩下的内容径直流进左栏就停住,右栏短了一截。这不是缺陷,而是逐段推进的建页方式必然的结果。
multicol 把这个顺序倒了过来,谜底全在这里。它的文档把工作分成两步:先收集环境中的材料,到达末尾时再做平衡。正因为“先收集”,宏包才能用 \vsplit 切分得到的盒子,并不断改变高度试排,直到各栏高度接近相等。1.5 版还加了质量判定:用 TeX 的 badness(0 为最优,10000 为无限差)度量每个候选解,凡差于计数器 columnbadness(默认 10000)的一律弃用。另有 finalcolumnbadness(默认 9999)负责判断:内容稀薄的末栏与其硬拉伸成难看的样子,不如让它短一截收尾。
\raggedcolumns— 不对齐各栏底部;默认是会对齐的\flushcolumns- 计数器
unbalance— 让除最右栏之外的各栏加长指定行数;每个环境结束后重置 - 计数器
minrows(默认 1)— 第一栏至少放多少行,用来避免只有一行的栏 - 计数器
columnbadness(默认 10000)— badness 超过该值的方案一律弃用 - 计数器
finalcolumnbadness(默认 9999)— 超过该值时,末栏宁可留短也不拉伸 \maxbalancingoverflow(默认 12pt)— 排齐后若超出超过此值,就放弃平衡,改出一个普通页面
落到实处,办法有三。若想继续用 twocolumn 而只把最后一页排齐,balance 与 flushend 两个宏包会给输出例程打补丁来实现。但 TeX FAQ 提醒:两者在有浮动体时都容易出错,届时可能需要手工调整图表的位置。更稳妥的做法,是只把文末那一段用 multicols 环境包起来,或者干脆接受右栏短一截——多数期刊样式都容许这一点。
自己决定断栏位置 —— \columnbreak 与栏间竖线
\columnbreak 会在你放置它的位置结束当前栏,把后续内容送到下一栏(1.5u 版加入)。自 1.9 版起,它接受与 \pagebreak 同形的可选参数,可从 \columnbreak[0](较弱的意愿)指定到 \columnbreak[4](强制)。同样在 1.9 版还加入了 \newcolumn:断栏,并让当前栏保持较短。至于此处为何不能改用 \pagebreak,理由同样明白——在 multicols 内部,\pagebreak 会结束 收集阶段本身,于是该页所有栏会被一起关掉。
\setlength{\columnsep}{1.5em} % gap between columns
\setlength{\columnseprule}{0.4pt} % show a rule in the gap
\renewcommand{\columnseprulecolor}{\color{gray}}
\begin{multicols}{2}
Text of the left column.
\columnbreak % push the rest into the next column
Text of the right column.
\end{multicols}栏间距与竖线仍由与 twocolumn 相同的 \columnsep、\columnseprule 控制,multicol 在此之上补了一个 \columnseprulecolor(默认 \normalcolor,即正文颜色)。这些都应在进入环境之前设定。此外,宏包还悄悄改了一项设置:断行的容忍度。LaTeX 平常的 \tolerance 是 200,也就是 \fussy 背后的数值,对 38 个字符的行长而言过于苛刻。于是在环境内部,multicol 换成 \multicoltolerance(默认 9999)——距离“无所谓”的 10000 只差一步,是为窄栏准备的务实折中。若两端对齐后空隙仍然刺眼,惯用的对策是在环境内改用 \raggedright,或检查所用语言的连字符断词规则。
为什么图表在 multicols 里消失了
因为普通浮动体在那里根本没有实现,放进去的东西会被丢掉。在 multicols 里写 figure 或 table 环境,或者 \marginpar,日志中就会出现警告 Floats and marginpars not allowed inside 'multicols' environment!。宏包文档对后果说得很直白:这类浮动体会消失。它也交代了原因——要实现浮动体,就得把 LaTeX 的输出例程整个重写一遍,这远远超出了第一版所花的那个周末。由于这只是警告而非错误,文档照样能编译通过。如果图不见了,日志里又看不出别的毛病,先去找这一行。
| 写法 | 在 twocolumn 下 | 在 multicols 内 |
|---|---|---|
figure / table | 在栏内正常浮动 | 不可用;只出警告,图表消失 |
figure* / table* | 横跨整页;默认 [tp],不能用 b | 可用;t、b、p 都有效,但绝不出现在书写它的那一页 |
\marginpar | 可用 | 不可用;同样的警告,同样消失 |
\columnbreak | 没有;只能靠分页代替 | 在该处结束当前栏(1.5u 起) |
\pagebreak | 结束当前页 | 会结束收集阶段,因此该页所有栏一起关闭 |
\columnseprule | 栏间竖线;默认 0pt | 相同,另可用 \columnseprulecolor 改颜色 |
带星号的浮动体在 multicols 里确实可用,但行为与 twocolumn 下不同。据文档所述,multicols 内的 figure* 更接近“单栏页面上的普通浮动体”:t、b、p 都可以指定,且它参照的是 \topfraction 而非 \dbltopfraction。不过有一条绝对限制——它绝不会出现在书写它的那一页。最早也要等到下一页的顶部,因此写 h 毫无意义。twocolumn 下不能用 b、multicols 内反而能用,这个颠倒就源于此。
那么,想在栏内放一张小图该怎么办?答案是:别把它做成浮动体。把 \includegraphics 直接放进 center 环境,它就会老老实实待在你写的位置(环境内 \linewidth 就是栏宽,因此 width=\linewidth 是顺手的写法)。若需要编号题注,用 caption 宏包的 \captionof{figure}{…} 即可,编号与交叉引用都不受影响。想让正文环绕,还可以用 wrapfig。不过,一旦发现 整篇文档都需要栏内浮动体,那就是该改用 twocolumn 而非 multicol 的信号。
\usepackage{multicol}
\usepackage{graphicx}
\usepackage{caption} % provides \captionof
% ... in the body
\begin{multicols}{2}
Text before the picture.
\begin{center}
\includegraphics[width=\linewidth]{plot}
\captionof{figure}{Not a float, so it stays where it is written.}
\end{center}
Text after the picture.
\end{multicols}