LaTeX 的分页和断行的机制毫无相似之处。段落会一次性权衡所有可能的断法、挑出整体最好的一种;而页面一旦断开就不会回头重算。所以在没弄清「这一页为什么在这里结束」之前就敲下 \newpage,往往会把后面四十页一起拖下水。本页实测文档节奏背后的机制——widow 与 orphan(\widowpenalty 和 \clubpenalty,默认值都是 150)、\raggedbottom 与 \flushbottom 的区别,以及浮动体如何把正文挤走——并给出一套诊断糟糕分页的顺序,而不是硬来。
这一页为什么在这里结束——分页与断行的根本差别
一页在哪里结束,只取决于排这一页时已经掌握的信息。TeX 自上而下地灌入正文,盯住行与行之间出现的合法断点;一旦累积高度超过目标,它就退回到目前为止代价最小的候选点并在那里切断。段落内部的断行是对所有可能排布的全局最优化,而分页是一边前进一边做出的顺序决策。这个差别在实务上最要紧,也正是修改第 40 页的分页会让第 41 页之后全部错位的原因。
顺序决策也意味着事后没有讨价还价的余地。TeX 剩下的手段,只有用一个数字声明「有多不想在这里断」——那就是惩罚值(penalty)。惩罚值是附加在断点上的整数,值越大越不愿意在此断开。10000 是「绝不在此断开」的特殊值,-10000 则相反,表示「必须在此断开」。避免段落只剩一行被孤立的机制、安排浮动体位置的机制、禁止在标题之后立刻断开的机制,全都用这一种数字来表达。所以调整分页,实质上就是调整惩罚值。
widow 与 orphan——\widowpenalty 和 \clubpenalty 的默认值都是 150
在 article 类里实测,\widowpenalty 和 \clubpenalty 都是 150,\displaywidowpenalty 是 50。widow 指段落的最后一行被单独留在页面开头,orphan 指段落的第一行被单独留在页面末尾。德语印刷界把二者分别叫作 Hurenkind 和 Schusterjunge。第一个绊住人的是命名:压制 orphan 的参数并不是 \orphanpenalty——TeX 和 LaTeX 里根本没有这个变量——而是 \clubpenalty,这是旧日排版行话把段首孤行称作 club line 留下的痕迹。
% Print the defaults into the .log yourself:
% pdflatex penalties.tex -> grep MEASURE penalties.log
\documentclass{article}
\begin{document}
\typeout{MEASURE widowpenalty=\the\widowpenalty}
\typeout{MEASURE clubpenalty=\the\clubpenalty}
\typeout{MEASURE displaywidowpenalty=\the\displaywidowpenalty}
\typeout{MEASURE brokenpenalty=\the\brokenpenalty}
x
\end{document}150 这个数字有来历。在 TeX Live 2024 的 latex.ltx 第 514–516 行可以看到这三条赋值,而完全相同的三行也出现在 Knuth 的 plain.tex 第 290–292 行。也就是说,LaTeX 只是原封不动继承了 plain TeX 的默认值,四十年来没有重新审视过。而 150 在实务上几乎不起作用:在垂直方向几乎没有可伸缩空白的正文里,把一页缩短一行所带来的丑陋程度(badness)轻易就会超过 1000。150 的惩罚值在这场较量里基本赢不了。
| 参数 | 默认值 | 阻止什么 |
|---|---|---|
\widowpenalty | 150 | 段落最后一行单独落在页首 |
\clubpenalty | 150 | 段落第一行单独留在页尾 |
\displaywidowpenalty | 50 | 在行间公式之前只剩一行 |
\brokenpenalty | 100 | 在带连字符的行之后分页 |
\interlinepenalty | 0 | 在段落中间任意处分页(默认不加阻止) |
\predisplaypenalty | 10000 | 在行间公式之前分页(实际上被禁止) |
只有一个地方,LaTeX 自己放弃了这些默认值。article.cls 的 thebibliography 环境一进入就调用 \sloppy,并设置 \clubpenalty4000 和 \widowpenalty4000(TeX Live 2024 所带 article.cls 的第 576–579 行)。参考文献表是一长串短条目,容易出现 widow,而一个条目被拆到两页上确实难读——标准类唯独在这里认真起来,原因就在于此。反过来说也成立:在正文里,标准类什么忙也帮不上。
把 \widowpenalty 设为 10000 会发生什么——实测这笔交易
widow 消失了,取而代之的是一页变短。 用 \flushbottom 排一份每页 20 行、每段 6 行的文档实测:保持默认时,第 1 页排入 23 行,第四段的最后一行被单独留在第 2 页开头。加上 \widowpenalty=10000 之后,第 1 页减到 22 行,最后两行一起挪到下一页,widow 不见了。但日志里多出一行:
Underfull \vbox (badness 10000) has occurred while \output is active []这条警告的意思是「没能把页面的纵向空隙填满」,而且只在 \flushbottom 下出现。同样的实验换成 \raggedbottom 就一次也不会报——页面允许不排到底,也就没有需要填的东西。也就是说,\widowpenalty=10000 的代价表现为:在 \flushbottom 下行距被拉开而参差不齐,在 \raggedbottom 下页脚的空白伸缩不定。无论哪种,代价都不为零。即便如此,对于以正文为主的稿件,10000 仍是站得住脚的设置:读者一定会注意到孤零零的一行,却几乎没人会注意到某页少排了一行。
% A defensible pair of settings for a text-heavy manuscript.
\widowpenalty=10000
\clubpenalty=10000
% Also stop a two-line gap opening before a displayed formula:
\displaywidowpenalty=10000
% Prefer this over sprinkling \newpage through the source.\raggedbottom 与 \flushbottom 的区别,以及你现在用的是哪一个
\flushbottom 会伸缩纵向空白,让所有页面的底边对齐;\raggedbottom 则允许底边参差。很多资料在这里说错了:单面排版的 article,默认是 \raggedbottom 而不是 \flushbottom。 article.cls、report.cls、book.cls 末尾都会执行 \if@twoside \else \raggedbottom \fi,所以只要没指定 twoside,生效的就是 \raggedbottom。只有默认就是 twoside 的 book 留在 flush-bottom 那一侧。而一旦指定双栏,这几个类都会显式调用 \flushbottom。
| 类选项 | 实际生效的是 | 页脚的样子 |
|---|---|---|
\documentclass{article} | \raggedbottom | 每页底边高度不同 |
\documentclass[twoside]{article} | \flushbottom | 底边对齐;行距可能被拉开 |
\documentclass{book} | \flushbottom | book 默认就是 twoside,所以落在这一侧 |
\documentclass[twocolumn]{article} | \flushbottom | 由类显式调用;双栏不齐会很显眼 |
机制简单得几乎让人失望。在 latex.ltx 里,\raggedbottom 只是被定义为 \def\@textbottom{\vskip \z@ \@plus.0001fil},\flushbottom 则是 \let\@textbottom\relax。前者在页面最下方放了一段「长度为零但可无限伸展」的胶水,这个 fil 会压倒正文中那点有限的伸缩量,于是所有余量都被页脚吸收。让 TeX 输出 \meaning\@textbottom,article 会回答 macro:->\vskip \z@ \@plus .0001fil,而 book 回答 \relax——在导言区写一行就能当场确认自己的文档处于哪种模式。
% Which bottom mode am I in? Put this in the preamble and read the .log.
\makeatletter
\AtBeginDocument{\typeout{MEASURE textbottom=\meaning\@textbottom}}
\makeatother
% article -> macro:->\vskip \z@ \@plus .0001fil (\raggedbottom)
% book -> \relax (\flushbottom)当浮动体把正文挤走——\topfraction 与 \textfraction
当分页看起来莫名其妙时,罪魁祸首通常是浮动体。放置图表之前,LaTeX 会检查它在这一页上允许占多大比例、已经放了几个。标准类的实测值是:顶部浮动体最多占正文高度的 \topfraction = .7,底部最多 \bottomfraction = .3,正文至少要留下 \textfraction = .2,一页上的浮动体总数不超过 totalnumber = 3(顶部 topnumber = 2,底部 bottomnumber = 1)。只要有一条不满足,这张图就不会放在本页,而是推迟到后面的页面。被推迟的浮动体会排在其后正文之前,于是就出现了「文字在第 12 页、图在第 14 页」这种错位。
| 参数 | 默认值 | 含义 |
|---|---|---|
\topfraction | .7 | 顶部浮动体可占正文高度的比例 |
\bottomfraction | .3 | 底部浮动体可占的比例 |
\textfraction | .2 | 带浮动体的页面上必须留给正文的最小比例 |
\floatpagefraction | .5 | 允许生成纯浮动体页面所需的最小占满率 |
totalnumber | 3 | 一页上允许的浮动体总数 |
topnumber | 2 | 页面顶部允许的浮动体数量 |
bottomnumber | 1 | 页面底部允许的浮动体数量 |
浮动体作祟时,日志会先告诉你。若只写了 [h] 的图放不下,LaTeX 会提示 LaTeX Warning: 'h' float specifier changed to 'ht'. 并自作主张把它挪到顶部。若积压的浮动体太多,就会以 ! LaTeX Error: Too many unprocessed floats.(latex.ltx 中的 \@fltovf)停下来。实务上的常规做法是:在章节交界处放上 placeins 的 \FloatBarrier,把图限制在所属章节之内;或使用 afterpage 宏包的 \afterpage{\clearpage},在当前页排完之后再清空队列。直接就地写 \clearpage,会在正文中间留下一大片空白。
\usepackage{placeins} % \FloatBarrier
\usepackage{afterpage} % \afterpage{\clearpage}
% Keep every figure inside the section that discusses it:
\let\oldsection\section
\renewcommand\section{\FloatBarrier\oldsection}
% Or loosen the rules instead of forcing breaks:
\renewcommand{\topfraction}{0.85}
\renewcommand{\textfraction}{0.1}
\renewcommand{\floatpagefraction}{0.75}糟糕的分页怎么修——动手硬来之前先做什么
这是有先后顺序的:先定位原因,再修改全局设置,最后才动手处理那唯一还不对的一页。 顺序反过来,上游只要多加一句话,所有手工调整就前功尽弃。定位从 .log 开始:出现 Underfull \vbox 说明 \flushbottom 和惩罚值在互相拉扯;出现 float specifier changed 说明是浮动体;成串的 Overfull \hbox 则说明问题在断行那一侧,那是另一页的地盘。弄清原因之后,再决定该动惩罚值、浮动体比例,还是段落本身的长度。
- 先读
.log。出现的是Underfull \vbox、float specifier changed还是Overfull \hbox,决定了谁是元凶。 - 在导言区提高
\widowpenalty与\clubpenalty。因为作用于整篇文档,修好的地方不会复发。 - 若是浮动体的问题,用
\FloatBarrier或调整\topfraction。把位置说明放宽为[htbp]同样有效。 - 把段落本身缩短或加长一行。改动一处措辞,往往才是最耐久的修法。
\enlargethispage{\baselineskip}只让这一页多容纳一行。到这一步才算动手。\newpage和\pagebreak是最后手段。一旦插入,每次改稿都得重新确认位置。
要请 TeX「把段落缩短一行」,工具是 \looseness。在段落内部写 \looseness=-1 表示「若可能就少排一行」,+1 则是多排一行。不过实测的结果发人深省:在设置了 \tolerance=9999 与 \emergencystretch=3em 的段落里,\looseness=1 确实把 6 行拉成了 7 行,代价是冒出 6 条 Underfull \hbox。而 \looseness=-1——恰恰是大家真正想要的方向——完全没能改变行数,因为在 \tolerance 允许的范围内根本不存在更短的解。\looseness 是一步值得一试的棋,而不是一定奏效的杠杆。它不起作用时,老老实实从稿子里删掉一个词反而更快。
% Ask for one line fewer inside the paragraph itself.
% \looseness is reset by every \par, so it must sit inside the paragraph.
\tolerance=9999 \emergencystretch=3em
Some paragraph text ... \looseness=-1
% Only after the above has failed: one page, one line taller.
\enlargethispage{\baselineskip}\pagebreak、\linebreak、\- 在哪里讲
本页处理的是整篇文档的节奏,具体的断开命令由别的页面负责。页面一侧的 \pagebreak[n] / \nopagebreak[n](n 为 0–4)、无条件的 \newpage、连同浮动体一起清出的 \clearpage,以及双面排版时补空白页的 \cleardoublepage,请看「分页微调」。行的一侧 \\、\newline、\linebreak[n]、\nolinebreak[n]、任意连字符 \-、导言区的 \hyphenation{man-u-script} 和 hyphenat 宏包,请看「断行微调」。段落本身的结构与 \par 归「段落与换行」所有。而 Overfull \hbox、\sloppy、\emergencystretch 有专门的错误页面,那里也实测出 \sloppy 不过是四条赋值(\tolerance 9999、\emergencystretch 3em、\hfuzz .5\p@、\vfuzz\hfuzz)。
最后,这一族里有一个成员,LaTeX 早已不声不响地替你用上了。\section 之类的标题命令,会把紧随其后的那一段的 \clubpenalty 临时提到 10000,让紧接标题之后不可能分页——这正是 latex.ltx 里 \@clubpenalty 那套机制的用途。所以「标题孤零零留在页尾」这种事故,在原装 LaTeX 里基本不会发生。真发生了,那多半是有人用自制宏替换了标题命令。