图不会出现在你写下 \begin{figure} 的地方。LaTeX 会把它从正文流中摘出来,先寄存在一个盒子队列里随身带着,等页面排布合适时才落下。这就是浮动体(float),而 [htbp] 那四个字母不过是一张许可证,写明 LaTeX 可以把它放在哪里。不了解这套机制就一路写 [h],图反而会跑得更远。本页讲浮动体为什么会移动、[htbp] 每个字母的真实含义、[H] 与 [h!] 的决定性差别、\FloatBarrier 和 \clearpage 如何拦截、放置参数,以及 ! LaTeX Error: Too many unprocessed floats. 出现的真正条件。
图为什么不在你写它的位置
图表若被分页从中切开就没法看了:上半截在这页、下半截在下一页是行不通的。因此 LaTeX 把每幅图或表当作一个不可分割的盒子,等排过一段正文后,再从页顶、页底或专门收容浮动体的独立页中挑一处落下。这就是浮动体,标准环境有 figure 和 table 两个。不让你手工摆位置,而是把放置当作最优化问题来解,正如它处理断行一样——这正是 TeX 一以贯之的设计思路。
这个设计有两面。一面是:图表绝不会被切开,编号(“图 1”“表 2”)以及经由 \caption、\label、\ref 的交叉引用都会自动对齐,不用你操心。另一面是:写的位置和印出来的位置会分开。第二面并非缺陷,而是前提。既然正文可以按编号写「参见图 1」,图就不必紧挨着那句话——所以原则是按编号引用,绝不按位置指示。LaTeX 还按源码出现的顺序处理浮动体,因此后面的图可能超过前面的图先出现。这正是在正文里写「上图」「下图」很危险的原因。
[htbp] 里 h、t、b、p 的真实含义
figure 与 table 接收一个可选参数,即位置指定符:\begin{figure}[htbp] 里的方括号,它交给 LaTeX 一组许可——这些位置是允许的。什么都不写时,article、book 等类会套用默认值 tbp,其中并没有 h。换句话说,不写指定符的图,一开始就没有把「原地」列为候选。
| 指定符 | 含义 | 实际行为 |
|---|---|---|
h | here | 不能单独使用;LaTeX 会补上 t 并报告 LaTeX Warning: 'h' float specifier changed to 'ht'. |
t | top | 正文页顶部;默认最多两个浮动体,高度上限为正文高度的 70% |
b | bottom | 正文页底部;默认最多一个浮动体,高度上限为正文高度的 30% |
p | page | 不含正文的浮动专用页;仅当浮动体填满至少 50% 时才生成 |
! | override | 仅对这个浮动体忽略数量与高度的内部限制;并不增加可选位置 |
H | 仅 float 宏包 | 根本不让它成为浮动体;未加载该宏包会得到 ! LaTeX Error: Unknown float option 'H'. |
多数文章把 [h] 说成「被忽略」,实际发生的却是 LaTeX 发出警告并改写它。排 \begin{figure}[h] 时,日志里会留下 LaTeX Warning: 'h' float specifier changed to 'ht'.,指定被当作 ht 处理。[h!] 同理,会得到 LaTeX Warning: '!h' float specifier changed to '!ht'.。读懂这条警告,就能当场明白图为什么跑到页顶去了。另一个误解是字母顺序:写 [bt] 和写 [tb] 毫无区别。 指定符是允许位置的集合而非序列,LaTeX 内部的尝试顺序是固定的。实践结论是别把选择面收得太窄——允许得越宽([tbp] 或 [htbp]),图落得越近;收得越窄,尤其只写一个 [h],图跑得越远。
\begin{figure}[htbp]
\centering
\includegraphics[width=0.6\textwidth]{plot}
\caption{Measured values against the model}
\label{fig:result}
\end{figure}
% refer by number, not by position; ~ keeps word and number on one line
See Figure~\ref{fig:result} for the result.[H] 与 [h!] 的区别:其中一个根本不是浮动体
[H] 并不是「比 [h!] 更强的 here」。它根本不经过浮动机制。 float 宏包替换了 LaTeX 内部的 \@xfloat,一旦在指定符开头看到 H 就跳到另一段代码。也就是说 H 从不作为位置字母被求值,图表只是作为一个大盒子直接排在那里。所以不加载 float 就写 [H],会以 ! LaTeX Error: Unknown float option 'H'. 中止。相比之下 [h!] 自始至终仍是浮动体,只是忽略内部限制;放不下时照样会往后漂。
实际编译一遍,差别立刻显现。在同一处放一幅高度为正文块一半的图,分别用 [H] 和 [!h]:[H] 的图纹丝不动,若放不下,连同周围正文一起被带到下一页。[!h] 的图先给出警告,然后越过后续正文飞到下一页顶部。所以 [H] 的意思是「绝对在这里,为此不惜分页」,[!h] 的意思是「尽量在这里,不行就走」。[H] 适合与句子顺序绑定的小图,比如步骤图、算法示意;但用在大图上,可能让前一页整个下半部分留白。也可以用 \floatplacement{figure}{H} 设为全文默认,代价几乎总是大片空白。
\usepackage{float}
% ...
\begin{figure}[H] % not a float any more: pinned, page breaks around it
\centering
\includegraphics[width=0.6\textwidth]{diagram}
\caption{Processing pipeline}
\end{figure}拦住浮动体:\FloatBarrier 与 \clearpage
最让人恼火的是某一节的图在本节里放不下,结果溢进下一节。当第 2 节的图 3 出现在第 3 节正文中间时,读者会彻底找不着北。解药是 placeins 宏包的 \FloatBarrier,由 Donald Arseneau 编写并置于公有领域。若想按节隔开,就把它写在每个 \section 之前,或者加载 \usepackage[section]{placeins},它会自动插到每个 \section 的开头。
这里有一个流行说法需要订正:「\FloatBarrier 不会分页」——这是错的。读 placeins 的源码就会发现,只要还有待处理的浮动体,\FloatBarrier 就发出 \newpage 并再次调用自身;若仍清不掉,就改用 \clearpage。实际运行中,若在有一幅大的 [b] 图待处理时经过屏障,图会落到下一页,正文则从再下一页继续——推进了两页。只有在完全没有待处理浮动体时,它才不分页。因此二者真正的区别不是「分不分页」,而是「无条件分页(\clearpage)与仅在必要时分页(\FloatBarrier)」。
\usepackage{placeins} % or [section] to barrier every \section
\usepackage{afterpage}
% ...
\section{Experiment}
% ... figures and tables ...
\FloatBarrier % nothing may cross this line
\section{Discussion}
% flush pending floats at the END of the current page, not right here
\afterpage{\clearpage}若想要 \clearpage 的效果,却又不能在此处分页,afterpage 宏包的 \afterpage{\clearpage} 正合用。它会在当前正在构建的页面自然结束之后再执行 \clearpage,因此正文中间不会开天窗。另一件更局部的工具是 \suppressfloats[t],意思是「本页顶部不要再放浮动体了」——placeins 的 [section] 选项在标题之后内部用的正是它。顺带一提,[section] 默认相当严格,连浮动体上浮到本节起始位置之前都禁止。想放宽就加 [above] 或 [below] 选项。
浮动体放置参数:LaTeX 说「放不下」的真正原因
「明明该能放到页顶,为什么没放」的答案,多半在放置参数里。LaTeX 用一组比例和个数来决定它愿意把页面的多少让给浮动体,article 类的默认值是 \topfraction = 0.7、\bottomfraction = 0.3、\textfraction = 0.2、\floatpagefraction = 0.5,以及 topnumber = 2、bottomnumber = 1、totalnumber = 3。也就是说,高度超过正文高度 70% 的图无论如何都上不了页顶。如果它的指定符只写了 [t],那它就无处可去,会被一路往后推。
| 参数 | article 类的默认值 | 含义 |
|---|---|---|
\topfraction | 0.7 | 浮动体可占据页顶的最大比例 |
\bottomfraction | 0.3 | 浮动体可占据页底的最大比例 |
\textfraction | 0.2 | 正文页中必须保留为正文的最小比例 |
\floatpagefraction | 0.5 | 生成浮动专用页所需的最小填充度 |
topnumber | 2 | 页顶最多可放的浮动体个数 |
bottomnumber | 1 | 页底最多可放的浮动体个数 |
totalnumber | 3 | 单页总共最多可放的浮动体个数 |
\dbltopfraction | 0.7 | 双栏 figure* 可占据页顶的最大比例 |
比例用 \renewcommand{\topfraction}{0.85} 修改,整数用 \setcounter{totalnumber}{5} 修改。提高 \topfraction、降低 \textfraction,页顶就能容纳更多图表,「放不下就往后推」的驱逐随之减少。其实 [htbp] 中的 ! 就是这种调节的一次性局部版本。双栏另有一套数值,figure* 遵循 \dbltopfraction。这里有个比数字更要紧的事实:双栏的 figure* 只能放在 t 或 p。 LaTeX 内部根本没有为通栏浮动体准备底部列表,其默认指定符就是 tp。写 \begin{figure*}[hb] 连一句警告都没有——它会被悄悄送到页顶。
Too many unprocessed floats:上限是 52,不是 18
队列溢出时会得到 ! LaTeX Error: Too many unprocessed floats.。而广为流传的「浮动体最多 18 个」这个数字已经过时。latex.ltx 确实分配了 \bx@A 到 \bx@R 共十八个盒子,但在有 e-TeX 的环境下(今天在用的引擎全都是),它还会继续加上 \bx@S 到 \bx@Z 以及 \bx@AA 到 \bx@ZZ,总计 52 个。这项扩展是在 LaTeX 2015/10/01 的发行版中加入的。把确实无处安放的浮动体排起来实测,报错恰好出现在第 53 个。
出了这个错,并不意味着必须删图。同一时期加入的 \extrafloats{n} 可以在导言区追加盒子;实测中,一份在 52 个时溢出的文档,只要加上 \extrafloats{40},错误立刻消失。但盒子不够通常是症状而非病因。会溢出的文档往往囤积着放不下的浮动体:某幅图高度超过正文的 70%,其指定符却只写了 [t];或者一串浮动体之间几乎没有正文,导致根本没有分页机会。把指定符放宽到 [htbp],并在适当处插入 \clearpage 把队列清空,才是治本的近路。与此相关的 LaTeX Warning: Float too large for page by ...pt 意为「这一幅放不进一页,所以我把它单独放逐到一页上」——这提示你该缩小图或更换纸张尺寸了。
% loosen the layout rules for the whole document
\renewcommand{\topfraction}{0.85}
\renewcommand{\textfraction}{0.1}
\renewcommand{\floatpagefraction}{0.75}
\setcounter{totalnumber}{5}
% allocate 40 more float boxes on top of the built-in 52
\extrafloats{40}把两幅图并排放置
浮动体不挑内容,所以并排只需在一个 figure 里做出两个盒子。用原生 LaTeX 就是 minipage:一个按指定宽度造出「小页面」的环境。把两个并排放置,中间夹一个 \hfill(可伸缩的水平空白),多余的空白就被推向外侧,两个盒子贴住左右边界。宽度取略小于正文宽度的一半,0.48\textwidth 是常见值。总宽超过 \textwidth 就会出现 Overfull \hbox,第二个盒子会掉到下一行。
\begin{figure}[htbp]
\centering
\begin{minipage}{0.48\textwidth}
\centering
\includegraphics[width=\linewidth]{left}
\caption{Left panel} % its own number
\label{fig:left}
\end{minipage}
\hfill
\begin{minipage}{0.48\textwidth}
\centering
\includegraphics[width=\linewidth]{right}
\caption{Right panel} % a second, separate number
\label{fig:right}
\end{minipage}
\end{figure}在每个 minipage 内部,\linewidth 指的是该 minipage 的宽度,所以写 \includegraphics[width=\linewidth] 正好填满盒子。这个例子里每个 minipage 都带了自己的 \caption,于是两幅图成了编号各自独立的「图 1」和「图 2」。若要做成同一幅图的 (a)(b),那就该 subcaption 宏包上场了,具体用法交给姊妹页。双栏排版中若需要跨越正文宽度、通栏铺开的图,请用 figure*(表格用 table*)——但要记住,如前所述它只能落在页顶或浮动专用页。
让正文环绕图片:wrapfig
杂志上常见的「正文沿着小图侧边流过」的版式,用 wrapfig 宏包的 wrapfigure 环境(表格用 wraptable)。它同样出自 Donald Arseneau 之手,与 placeins 是同一位作者。它有四个参数,其中两个必填:{r} 是放在哪一侧,末尾的 {0.4\textwidth} 是图的宽度。可选的 [12] 是环绕的行数,可选的 [34pt] 是伸出量,即图向版心之外突出多少。位置字母成对出现:小写固定在原处,大写允许它作为浮动体移动。
\usepackage{wrapfig}
% [lines]{side}[overhang]{width}
\begin{wrapfigure}[12]{r}[34pt]{0.4\textwidth}
\centering
\includegraphics[width=0.38\textwidth]{portrait}
\caption{Portrait}
\end{wrapfigure}| 字母 | 侧边 | 备注 |
|---|---|---|
r / R | 右 | 小写 r 固定在原处,大写 R 允许浮动 |
l / L | 左 | 小写 l 固定在原处,大写 L 允许浮动 |
i / I | 内侧(装订侧) | 在 twoside 文档中会随左右页自动切换 |
o / O | 外侧(切口侧) | 在 twoside 文档中会随左右页自动切换 |
上例中,宽 0.4\textwidth 的图放在正文右侧,随后的 12 行从它左边绕过,[34pt] 让它伸进右页边距。省略行数时 wrapfig 会依图的高度估算;估算不准环绕就会变乱,出问题时明写行数是最快的补救。不过 wrapfig 脾气不小,它自己的手册就限定了可用的场合。
- 不要在列表内部或紧邻列表前后使用。 放进或紧贴
itemize、enumerate、description都会出问题。 - 不要让它跨过分页。 放置时确保环绕不会被切成两页;远离页面边界。
- 不要让标题或行间公式环绕。 只让普通正文绕着它流动。
- 在段落边界开始,而不是段落中间。 在
minipage或parbox内使用时,要确保环绕在该组结束之前完成。
环绕出问题时,把图稍微收窄、明写行数、把起点移动一个段落——通常这三招之一就能解决。若仍旧不服帖,放弃 wrapfig、退回普通浮动体([htbp],放在正文上方或下方),最终往往能得到更好读的版面。图的落点定下之后,接着就该给它编号和说明了。