自定义标签

多数人记得 LaTeX 的 itemize 在各层给出的四个行首标签(标记)是"•、-、、·"。可一旦翻开 article.cls,后三个竟无一与设想相符。第 2 层不是两个连字符,而是 粗体的 en 破折号\labelitemfont \bfseries \textendash);第 3 层不是寻常的星号,而是 上下居中的星号* \textasteriskcentered;第 4 层则是 \textperiodcentered。自定义标签,须从弄清这些宏究竟装着什么开始。本页依次考察 itemize 的四个标记、enumerate\theenumi\labelenumi 的分工、从 \arabic\fnsymbol 的计数器表示命令及各自的极限,以及嵌套的真正上限——也就是 ! LaTeX Error: Too deeply nested. 究竟何时才会出现。

itemize 的四个默认标记究竟是什么

itemize 的行首符号分装在四个宏里,每层一个:\labelitemi\labelitemii\labelitemiii\labelitemiv(词尾的 i/ii/iii/iv 是罗马数字,代表第 1 至第 4 层)。它们在标准文档类中的定义见下表,四者开头都带 \labelitemfont——那是 article.cls 中定义为 \normalfont 的一个小钩子,它保证即便周围文字是斜体或粗体,标记仍以正文字体排出。唯有第 2 层在 \labelitemfont 之后还接了 \bfseries,故其 en 破折号排成粗体。它看上去比邻居更浓,原因就在这里。

标准文档类中的定义实际印出的字
\labelitemi\labelitemfont \textbullet• 实心圆点
\labelitemii\labelitemfont \bfseries \textendash– 粗体 en 破折号,并非两个连字符
\labelitemiii\labelitemfont \textasteriskcentered∗ 上下居中的星号,与 * 并非同一字形
\labelitemiv\labelitemfont \textperiodcentered· 间隔号

要更换 整层 的符号,就用 \renewcommand 重新定义对应的宏,置于导言区或所需作用范围之前。有一点须留意:这些宏 不会 自行进入数学模式。若不自己写上美元符号,如 $\diamond$,则 \diamond 在正文中无法使用,会报错。常用的替代符号有 $\diamond$(◇)、$\ast$(∗)、$\cdot$(·)、$\triangleright$(▷);若载入 pifont 宏包,还可使用 \ding{"6C} 之类的装饰符号。不过切莫过分讲究:符号越花哨,层级越难分辨,因此通常只改第 1 层最为稳妥。

preamble
% the whole of level 1 becomes a diamond -- note the explicit math shift
\renewcommand{\labelitemi}{$\diamond$}

% or, with enumitem, the same thing scoped to one list
\begin{itemize}[label=$\diamond$]
  \item this list only
\end{itemize}

只替换单个条目的标签:\item[...]

若只想改 某一个条目 的标记,就在 \item 之后用方括号给出标签:\item[$\star$] 使该条目行首成为星号,\item[--] 则为破折号。这里须纠正一个流传甚广的误解:方括号给出的标签并不会变成粗体。itemize 中写 \item[Word],所生成的 PDF 只嵌入正文用的罗马体,全然不见粗体。itemizeenumerate\makelabel 定义为 \hss\llap{...},因此标签不过是 在盒内右对齐(超出宽度的部分向左溢出),字体则丝毫未动。唯有 description 才会加粗,而那是因为 \descriptionlabel 里带着 \bfseries

\theenumi 与 \labelenumi 之别——数的表现与标签的体例

\theenumi 决定 数如何表现——是 1、是 I 还是 a;\labelenumi 则决定 该数在行首如何装点,前后是否加括号或句点。enumerate 每层使用一个计数器:enumienumiienumiiienumiv\theenumi 把计数器的值化为字符,\labelenumi 再调用它并添上标点。这是两级的构造。标准文档类的默认值见下表,且并不对称:唯有第 2 层用圆括号,其余都用句点。

计数器\the… 的默认标签的默认行首印出的形式
enumi\arabic{enumi}\theenumi.1. 2. 3. …
enumii\alph{enumii}(\theenumii)(a) (b) (c) …
enumiii\roman{enumiii}\theenumiii.i. ii. iii. …
enumiv\Alph{enumiv}\theenumiv.A. B. C. …

这两级构造的价值在交叉引用上显现。\ref 返回的是剥去 \labelenumi 装饰后的裸表现,也就是 \theenumi。所以,即便你把 \labelenumi 改成 (\theenumi) 使行首印出 (1),\ref 仍返回不带括号的 1;若要写"见步骤 (1)",就得在引用处补上括号,或改用 enumitemref=。更确切地说,LaTeX 是在 \theenumi 之前加上 \p@enumi 来构造引用字符串的,而标准文档类把 \p@enumii 定义为 \theenumi\p@enumiii 定义为 \theenumi(\theenumii)。因此对第 2 层条目的 \ref 会返回"1a",连父级编号一并带上。至于计数器本身如何创建与递增,那是计数器与长度那一页的职责。

preamble
% change only the representation: the head reads I. II. III. and \ref gives I
\renewcommand{\theenumi}{\Roman{enumi}}

% change only the dressing: the head reads (1) but \ref still gives a bare 1
\renewcommand{\labelenumi}{(\theenumi)}

计数器表示命令及其极限

要在 \theenumi\labelenumi 内选定数的外观,可用 LaTeX 标准的六个 计数器表示命令。它们都以计数器名为参数(如 \Roman{enumi}),并按指定体例印出该计数器的值。要紧的是,其中三个 有上限\alph\Alph 一旦超过 26,\fnsymbol 一旦越出 1–9,都会以 ! LaTeX Error: Counter too large. 中止。一个有 27 项、标签为 label=\alph* 的列表,必定在第 27 项上出错。

命令印出的形式范围与极限
\arabic1, 2, 3 …(阿拉伯数字)无上限;负数照原样印出(−3 印作 −3)
\alpha, b, c …(小写)1–26;27 及以上报 Counter too large。0 则悄然为空
\AlphA, B, C …(大写)1–26;上限与 \alph 相同
\romani, ii, iii …(小写罗马数字)不会报错,但 0 及以下悄然为空
\RomanI, II, III …(大写罗马数字)无上限;4000 印作 MMMM,只是把 M 排开
\fnsymbol* † ‡ § ¶ ‖ ** †† ‡‡(脚注符号)仅 1–9;10 及以上、0 及以下均报 Counter too large

棘手之处在于,有的失败会喊出声,有的却悄无声息\alph{0}\roman{0} 会一声不吭地返回空串,于是引用一个仍停在 0 的计数器,便造出一份"标签凭空消失"的列表,而线索全无。相比之下 \alph{27} 好歹会以 Counter too large 停下来。\fnsymbol 的九个符号——正是 \@fnsymbol 定义中的那一列——本是脚注用的序列,挪来充作列表标签,到第 10 项就会崩坏。请只把它们留给简短的注记式列表。

用 enumitem 的 label= 来写——由计数器组装标签

重新定义宏固然有力,但当版式需因列表而异时便显笨重。多数情况下 enumitemlabel= 读起来更清楚,不妨先试它。在 label= 之内,带星号\arabic*\alph*\Alph*\roman*\Roman* 代表该层当前的计数器。不带星的 \arabic{...} 仍可并用,两者混合便能 由别的计数器组装标签:写 label=\thesection.\arabic*,条目就编成"3.1""3.2",前面冠以节号,且移动该节时会自动跟随。

preamble
\usepackage{enumitem}
\setlist[enumerate,1]{label=\Roman*.}    % level 1 becomes I. II. III.
\setlist[enumerate,2]{label=(\alph*)}    % level 2 becomes (a) (b)
\setlist[itemize,1]{label=$\diamond$}    % itemize level 1 becomes a diamond

% a label built from another counter
\begin{enumerate}[label=\thesection.\arabic*, leftmargin=*]
  \item numbered 3.1 inside section 3
\end{enumerate}

% label*= accumulates onto the parent label: 1. then 1.1. 1.2.
\begin{enumerate}[label*=\arabic*.]
  \item one
  \begin{enumerate}[label*=\arabic*.]
    \item one-point-one
  \end{enumerate}
\end{enumerate}

label*= 会接在父级标签之后,嵌套时便累积为 1.、1.1.、1.2.(已实际排版验证)。但要注意,这种串接只作用于行首的显示,交叉引用并不会自动带上父级编号。若想得到"1.a"这样的引用,须用 ref= 明写,如 ref=\theenumi.\alph*。另外,凡是给出较宽的标签,切记补上 leftmargin=*:默认的标签盒只有 20pt,像 \thesection.\arabic* 这样的长标签会向左溢出。若要在全篇生效,则指明种类与层级,如 \setlist[enumerate,1]{...}

此外,以 \usepackage[shortlabels]{enumitem} 载入宏包,还可像旧的 enumerate 宏包那样简写为 \begin{enumerate}[(1)]。只需把 AaIi1 置于方括号的首位,便分别表示 \Alph*\alph*\Roman*\roman*\arabic*

Too deeply nested——嵌套的真正上限

常听人说 LaTeX 的列表最多只能嵌套四层。这话 只对了一半。上限其实有两个,读一读 latex.ltx 便能看清其构造。itemize 把深度记在 \@itemdepthenumerate 记在 \@enumdepth——这是 两个各自独立的计数器——各自在超过 3 时调用 \@toodeep。可见四层的限制 只在层叠同一种环境时 才成立。此外,\list 本身还设有一道全局关卡 \ifnum \@listdepth >5,这一道则不问列表是何种类。

实际编译,结果正是如此。层叠五个 itemize,第五个便报 ! LaTeX Error: Too deeply nested.;五个 enumerate 亦然。可若把 itemizeenumerate 交替 层叠,六层竟毫无错误,直到第七层才报同样的错——这一次拦下它的是 \@listdepth。可见实用上的上限是"同种四层,交替六层"。不过这只宜当作知识,而非许可:六层深的嵌套几乎不可能读得下去。若确实需要那样的深度,请转向 \subsubsection 之类的标题,或考虑 easylist 宏包,而不是列表。

这个错误的麻烦之处在于,报了错却不会中止排版\itemize 定义中的深度检查形如 \ifnum ... \@toodeep \else ... \fi,一旦超限,LaTeX 便干脆不开始列表,任正文径直流过。于是造成一种令人费解的损坏:错误只记录一次,PDF 照样生成,而 该层的条目却化作普通正文流出。若在日志里看到 Too deeply nested,纵然 PDF 已经产出,也务必修正相应段落。还有一个后果:为第 5 层定义 \labelitemv 毫无意义,因为检查在抵达该层之前就已把列表挡下。