description

LaTeX 的 description 环境是与 itemizeenumerate 并列的第三种列表,但设计思路与它们截然不同。翻开标准文档类中的定义就会看到,description\labelwidth 设为 0pt,把 \itemindent 设为 负的 \leftmargin。换言之,"安放术语的那一栏"根本没有宽度——这是刻意的设计。这一行同时解释了 description 的长处(术语再长也排得下)与它最著名的麻烦(长术语会把说明文字挤开)。本页将考察这个环境究竟为何而设、粗体从何而来,以及 enumitemstyle= 如何驾驭那一挤。

description 究竟为何而设

description 的存在,是为了排布 "词"与"该词的解释"成对出现 的内容。itemizeenumerate 会替你补上标记——圆点或编号——而 description 则要求 由你用文字给出标签:写在 \item 可选参数 [...] 里的内容,就是那个术语。在 itemizeenumerate 中,[...] 只是"替某一项换个标记"的附带功能;在 description 里,它才是全部要点。术语表、选项清单、参数说明、人物表——只要结构是"一个名称,底下挂着解释",这就是正确的环境。

latex
\begin{description}
  \item[TeX] The typesetting system Knuth wrote.
  \item[LaTeX] A document language built on top of TeX.
  \item[CTAN] The worldwide archive network that distributes packages.
\end{description}

这一结构恰好对应 HTML 的 <dl><dt><dd>,即定义列表。因此选用 description 具有超出外观的意义:你是在稿件中声明"这是一条定义",转换工具与索引生成器都能读出这份意图。反之,若用 \textbf{术语}\quad 说明 之类手工排出同样的东西,输出或许相似,结构信息却已荡然无存。与 itemizeenumerate 一样,至少需要一个条目;若空着就关闭环境,会报 ! LaTeX Error: Something's wrong--perhaps a missing \item.

粗体从何而来(\descriptionlabel)

术语之所以排成粗体,并非因为 \item[...],而是因为一条专属于 description 的命令:\descriptionlabel。它在标准文档类中的定义只有一行 \hspace\labelsep \normalfont\bfseries #1\bfseries 正写在其中。这一区别可以验证:在 itemize 里写 \item[Word],生成的 PDF 只嵌入正文用的罗马体,全然不见粗体。可见 \item[...] 只是"替换标记",并不具备加粗的能力。description 中的粗体,唯有当该环境把 \descriptionlabel 交给 \makelabel 时才会出现。

因此,若不想动用 enumitem 而只想换个字体,正道是用 \renewcommand 重新定义 \descriptionlabel;其参数 #1 就是术语文本。下面这个版本把所有术语排成小型大写字母,开头的 \hspace{\labelsep} 予以保留,以维持与原定义相同的间距。把 \textsc 换成 \texttt(等宽)或 \textit(斜体),即可得到别的样貌。另需留意:itemize 会随层级更换标记,enumerate 会随层级更换编号形式,而 description 无论嵌套多深,术语的体例都不变\descriptionlabel 只有一个,所以这次重定义对文档中所有层级一视同仁。

preamble
% the standard-class definition is:
%   \newcommand*\descriptionlabel[1]{\hspace\labelsep \normalfont\bfseries #1}
\renewcommand{\descriptionlabel}[1]{%
  {\hspace{\labelsep}\textsc{#1}}}

长术语为何会挤开说明文字——因为 labelwidth 是 0

长术语 不会换行,而是把说明文字的第一行按自身宽度向右推开。根源在于 latex.ltx 里的一处分支:若标签盒宽于 \labelwidth\ifdim \wd\@tempboxa >\labelwidth),LaTeX 便放弃固定宽度的盒子,改以标签的 自然宽度 排出。而 description\labelwidth 设为 0pt,于是 哪怕再短的词也必定走进这条分支。换言之,description 的术语根本就不在什么固定宽度的栏里。在 article 中实测:\labelwidth 为 0pt,\itemindent 为 −25.00003pt(即 −\leftmargin),\labelsep 为 5pt。

实际排版并测量可知,术语之后总有 5pt 的空白——恰是 \labelsep——而且无论源文件里写成紧挨的 \item[Term]body 还是隔开的 \item[Term] body,落点完全相同。这空白由 \labelsep 造出,并非源文件里的空格,所以写紧了也救不回来。换行后的第二行起会退回到 \leftmargin(25pt),因此术语与说明的第二行左缘并不对齐,整条看去呈阶梯状。若再走极端,放一个宽过文本块的词,它便会连同 Overfull \hbox 警告一起溢出到右边距。实务上的判断是:一旦术语超过三四个词,就该用下一节的 style= 改变排布方式本身。

用 enumitem 的 font= 与 style= 改变排布

载入 enumitem 之后,便可直接向 description 传递选项:font= 设定术语的字体,style= 则决定术语与正文的排布方式本身。写 font=\sffamily\bfseries 得到无衬线粗体,写 font=\ttfamily 得到等宽体——比重写 \descriptionlabel 更简短,而且可以逐个列表变化,这才是真正的长处。与其他列表类型共用的键,如 leftmargin=labelsep=itemsep=,在此照常可用。真正解决上一节挤压问题的,是 style=

style 的取值术语较长时的行为
standard与标准文档类相同:标签装入盒中,溢出多少就把正文向右推多少
unboxed接近 standard,但标签不装盒,长术语不至局促,也可以断行
nextline若标签容不下于边距之内,正文便另起一行;正文绝不侵入左边距
sameline与 nextline 类似,但即便标签容不下,正文仍在同一行继续
multiline把术语折行于 labelwidth 之内,必要时断词,正文则在右侧对齐

把同一个长术语在五种 style= 下分别排出,差别便一目了然。nextline 会让正文在长术语之后落到下一行,sameline 则让它在同一行继续。术语较短时两者都把正文留在同一行,可见 差异只在"容不下的时候"才显现multiline 最为醒目:它把长术语折行于 labelwidth 宽度之内,必要时甚至断词,纵向叠起——在术语冗长的技术文档词汇表中,若想让正文左缘保持笔直,这一招很管用。不过此时必须明确给出 labelwidth=,否则没有折行的宽度可依;请与 leftmargin= 成对指定。

document.tex
\usepackage{enumitem}
% one list only
\begin{description}[font=\bfseries\sffamily, style=nextline, leftmargin=1.5cm]
  \item[A term long enough to overrun its line]
    the explanation begins on the next line instead of being shoved sideways
  \item[Short] the explanation stays on this line
\end{description}

% or once in the preamble, for every description in the document
\setlist[description]{font=\sffamily\bfseries, style=nextline}

方括号的陷阱与省略标签

由于 [] 用作可选参数的分隔符,想让方括号 作为字符 出现时,须用花括号把它藏起来。若要把正则表达式的字符类 [a-z] 作为术语,写作 \item[\texttt{[a-z]}];含右括号时更需小心,应写成 {]},如 \item[右括号 {]}]。反过来同样危险:当说明文字本身以 [ 开头时,须包成 \item {[},否则 LaTeX 会误读为标签的开始。在讲解编程语言语法的文档里,这两种情形层出不穷。

latex
\begin{description}
  \item[\texttt{[a-z]}] a character class; brackets in a label need braces
  \item {[}this is how a body starting with a bracket is written
\end{description}

省略标签,该条目便真的没有标签——你会得到一个形如悬挂段落的条目,既无标记,也无缩进的提示。既然标签正是 description 的要害,除非有意为之,请避免这样做。既无合用的默认值可退,就该把给出标签当作自己分内的事。还有一点:在标签内以 声明形式 书写的字体切换命令会覆盖默认的粗体,所以像 \item[{\ttfamily label}] 那样把命令连同内容一并用花括号包起来,是稳妥的习惯。

description 与两列表格,该用哪一个

实务上的分界是:说明若超出一行,用 description;取值若简短齐整、意在纵向扫读,则用表格。tabular 的两列看似正适合排"术语与说明",却有三处弱点。其一,它跨不了页——非得搬出 longtable 之类不可。其二,列宽须由你自己决定,而 p{5cm} 这样的固定值,一旦正文宽度改变便立刻崩坏。其三,说明越长,越是只有右列向下拉伸,行与行的界限终至难辨。description 这三样都不会发生:它像段落一样自然流动,自行跨页断开,宽度则随 \textwidth

反过来说,一旦某个取值带有 多项属性——类型、默认值、范围——那它就不再是定义列表,而是表格了。description 只适合"名称对说明"这种严格的一对一映射。当你开始想要第三列的时候,转投 tabular 才是正确的判断;硬塞成 \item[名称(类型、默认值)] 只会让术语变长,径直退回本页前面所说的挤压问题。