把列表嵌套起来,LaTeX 会按层级切换每行开头的标签。可是放进 itemize 条目里的 enumerate,版面上明明是第二层,却从「1.」而不是「(a)」开始。因为 LaTeX 数的不是纸面上的深度,而是有多少个同类列表把它包在里面。不知道这一点就去写嵌套列表,看到输出时只会一头雾水。本页要讲的是:标签如何被选中、名为 Too deeply nested 的那堵墙为什么立在第四层、所有列表环境共用的另一道上限,以及每加深一层就确实失去的那部分行宽。
决定标签的是同类嵌套的层数,不是版面上的深度
LaTeX 用两个各自独立的计数器来数深度。在 latex.ltx 中,itemize 推进 \@itemdepth,enumerate 推进 \@enumdepth;两者各自用自己的值拼出 \labelitem 或 \labelenum 加罗马数字的命令名再调用。二者互不通气,所以 itemize 里的 enumerate 在 \@enumdepth 上就是第 1 层——于是用 \labelenumi,编号从「1.」开始。常有人说「标签一律由嵌套深度决定,与类型无关」,这是错的。实际排一次就一目了然。
\begin{enumerate}
\item Make the dough
\begin{itemize}
\item Flour
\item Water
\begin{enumerate}
\item Add half
\item Add the rest
\end{enumerate}
\end{itemize}
\item Let it rest
\end{enumerate}这三层嵌套实际排出来是这样:外层 enumerate 给出「1.」「2.」;它里面的 itemize 拿到的是第一层的圆点 •,而不是第二层的破折号 –;最里层的 enumerate 拿到的是第二层的 (a),而不是第三层的罗马数字 i.。因为在 itemize 看来这是第一个 itemize,而在内层 enumerate 看来外面只有一个 enumerate。交替地一层层套下去,每一种类型都会各自按顺序用掉自己那套标签。
| 深度 | 环境 | 该类型内的层数 | 得到的标签 |
|---|---|---|---|
1 | itemize | 1 | • (\labelitemi) |
2 | enumerate | 1 | 1. (\labelenumi) |
3 | itemize | 2 | – (\labelitemii) |
4 | enumerate | 2 | (a) (\labelenumii) |
5 | itemize | 3 | * (\labelitemiii) |
6 | enumerate | 3 | i. (\labelenumiii) |
第四层的那堵墙:! LaTeX Error: Too deeply nested.
把同一类型的列表套到第五层,编译就会停在 ! LaTeX Error: Too deeply nested.。这条消息不会告诉你深度是多少——只有这三个词,究竟哪个 \begin 是第五层,得自己去数;报出的行号就是开启第五层的那行 \begin{itemize}。原因并非排版上的禁令,而只是名字不够用:latex.ltx 里的判断不过是 \ifnum \@itemdepth >\thr@@,即「是否大于 3」。标签命令只定义到第四层,第五层拿不到名字,于是就停。这也意味着光加载 enumitem 并不能拆掉这堵墙:不加任何配置就打开第五层,同样的报错会以同样的方式出现。
% raising the ceiling really does take all four lines
\usepackage{enumitem}
\setlistdepth{9}
\renewlist{itemize}{itemize}{9}
\setlist[itemize]{label=$\cdot$}
\setlist[itemize,1]{label=$\bullet$}真要拆掉这堵墙,光靠 enumitem 的 \setlistdepth 是不够的。\setlistdepth{9} 只是抬高了列表总体的上限,itemize 本身仍旧停在第四层。必须用 \renewlist{itemize}{itemize}{9} 按九层重新造出这个环境,再用 \setlist[itemize]{label=…} 给新增的层配上标签,第五层往下才排得出来。不给标签就打开第五层,报错只会换成 ! Package enumitem Error: Undefined label.。如果真正想要的是带编号的深层结构,那么像 easylist 这样从一开始就为任意深度设计的宏包更为直白。不过在动手之前请先停一停:几乎没有读者能跟得住五层嵌套。嵌套到这个深度,通常意味着这段结构应该改写成章节标题。
另一道上限:所有列表环境共享的六层预算
在「每种四层」之上,还压着一道所有列表共享的六层上限。itemize 和 enumerate 内部都建立在同一个 list 环境之上,而 list 自带一条判断:\ifnum \@listdepth >5。所以把 itemize 与 enumerate 交替叠到第六层还能通过,第七层照样会得到 Too deeply nested。麻烦之处在于,花掉这份预算的并不只有列表。翻一遍 article.cls 就会看到,quote、quotation、verse、description,乃至 thebibliography,全都建立在 \list 之上。在很深的项目符号列表里放上两层嵌套的引用块,光这两层就能把剩余预算耗光。由于罪魁看上去根本不像列表,这类故障是最难排查的一种。
缩进是累加的:到第四层,行宽已少掉四分之一
嵌套真正的代价不体现在标签上,而体现在行的宽度上。list 环境每打开一次,就把自己的 \leftmargin 累加到左侧的总和里,于是缩进一层层堆起来。在单栏的 10pt article 中,\leftmargini 是 25.0pt,\leftmarginii 是 22.0pt,\leftmarginiii 是 18.7pt,\leftmarginiv 是 17.0pt。到第四层时左边距合计 82.7pt,相对 345pt 的正文宽度已经去掉了大约四分之一,剩下可写的行宽为 262.3pt。这些数值逐层略微减小,正是为了减缓这种累加,但即便如此,四层也要削去这么多。
| 长度 | 取值 | 累计 | 剩余行宽(textwidth 345pt) |
|---|---|---|---|
\leftmargini | 25.0pt(2.5em) | 25.0pt | 320.0pt |
\leftmarginii | 22.0pt(2.2em) | 47.0pt | 298.0pt |
\leftmarginiii | 18.7pt(1.87em) | 65.7pt | 279.3pt |
\leftmarginiv | 17.0pt(1.7em) | 82.7pt | 262.3pt |
换成数字,深层嵌套为什么难读就变得具体了。262pt 的行宽已接近双栏排版中的一栏;把长句灌进去,断行算法的回旋余地变小,词间距会不自然地撑开,Overfull \hbox 警告也随之增多。何况在双栏排版中 \leftmargini 本来就被压到 2em,版面越窄,嵌套的余地越小。要收紧左边距,用 enumitem 的 leftmargin= 逐层指定是实用的做法。不过多数情况下,回头调整结构比这些都快——比第三层更深的条目,通常都能作为散文并回父条目里去。
嵌套该在哪里停下
LaTeX 允许四层,但那并不等于说四层就读得下来。深层嵌套传达的正是层级结构,而读者只能凭行首符号的形状与左边缘的位置这两样东西去还原它。第三层的星号与第四层的居中点形状相近,而两者的缩进差只有 17pt。也就是说,到了第四层,读者手上几乎没剩下什么线索。下面这份清单可用于写完嵌套之后的复查。
- 先确认两层是否已经够用。 第三层以后的内容,通常都能作为散文并回父条目。
- 如果确实需要那样的深度,就改用章节标题而不是列表。
\subsection与\subsubsection会顺带带来目录与交叉引用。 - 混用类型时务必看一眼输出。 标签取决于有多少个同类列表把它包住,所以拿到的符号可能与预期不同。
- 别把
quote、verse放进深层嵌套。 它们花的是同一份六层预算,引发的报错来源难以察觉。 - 出现
Too deeply nested就重新数一遍深度。 消息从不告诉你层数,只能从报出的那行往回追\begin。