如何创建一个描述列表,其中项目文本自动缩进到最宽标签的宽度?
这个问题与具有对齐描述的描述列表但我希望自动计算最宽标签的宽度。
例如,我想要以下输出:
由以下输入产生:
\documentclass{article}
\usepackage{calc}
\usepackage{enumitem}
\usepackage{lipsum}
\usepackage[margin=1em,papersize={4in,2.2in}]{geometry}
\newenvironment{mydescription}{%
\begin{description}[
leftmargin=!,
labelwidth=\magicgoeshere,
]%
}{%
\end{description}%
}
\newcommand{\text}{long long long long long long long long long long
long long long long long long long long long long long long long
long long long long long long long text}
\begin{document}
\begin{mydescription}
\item[The longest label] text
\item[Short] \text
\end{mydescription}
\hrule
\begin{mydescription}
\item[Medium label] text
\item[Short] \text
\end{mydescription}
\end{document}
答案1
解决方案
这是一个使用的解决方案eqparbox
包来测量项目标签宽度。它避免了在Gonzalo Medina 的回答(参见本文末尾)因为它不会对整个列表进行两次排版。缺点是它需要额外的编译运行,因为在一次运行期间测量的宽度只能在下一次运行中使用。
您可以通过在序言中添加以下几行来声明一个可以执行您想要的操作的描述环境版本。 (仅适用于 enumitem v3.7+,见下文。)
%% Requires enumitem v3.7+
\newlist{mydescription}{description}{1} %% <- pick a larger number if you want to nest these
\setlist[mydescription]{
labelwidth=\eqboxwidth{listlabel@\EnumitemId},
leftmargin=!,
format=\mydescriptionlabel,
}
\newcommand\mydescriptionlabel[2][l]{\eqmakebox[listlabel@\EnumitemId][#1]{#2}}
这样做的目的是将描述项标签放入\eqmakebox
es 中,以便将其宽度写入辅助文件。从第二次运行开始,同一列表中的所有此类框将被赋予相同的宽度(其中最大),并且此宽度将用作列表的labelwidth
。
如果您使用的版本enumitem
早于 v3.7(于 2019-01-04 发布),您仍然可以通过添加以下几行来完成此工作:
%% Only required for enumitem v3.6-
\usepackage{etoolbox} %% <- for \AtBeginEnvironment
\newcounter{mydescription}
\AtBeginEnvironment{mydescription}{%
\stepcounter{mydescription}%
\edef\EnumitemId{\arabic{mydescription}}%
}
(注意:使用before
密钥不起作用,因为代码运行得太晚了。)
向您的 MWE 提出申请
这是我应用于您的 MWE(enumitem
v3.7+ 版本)的解决方案。
\documentclass{article}
\usepackage{eqparbox}
\usepackage{enumitem}
\usepackage[margin=1em,papersize={4in,2.2in}]{geometry}
\newlist{mydescription}{description}{1} %% <- pick a larger number if you want to nest these
\setlist[mydescription]{
labelwidth=\eqboxwidth{listlabel@\EnumitemId},
leftmargin=!,
format=\mydescriptionlabel,
}
\newcommand\mydescriptionlabel[2][l]{\eqmakebox[listlabel@\EnumitemId][#1]{#2}}
\newcommand{\text}{long long long long long long long long long long
long long long long long long long long long long long long long
long long long long long long long text}
\begin{document}
\begin{mydescription}
\item[The longest label] text
\item[Short] \text
\end{mydescription}
\hrule
\begin{mydescription}
\item[Medium label] text
\item[Short] \text
\end{mydescription}
\end{document}
工作原理
- 我使用
\newlist
来\newenvironment
定义mydescription
环境并\setlist
对其进行自定义。当然,您也可以放弃\newlist
并修改正常description
环境。 enumitem
sformat
键用于将mydescription
环境中的每个项目标签放入 中\eqmakebox
。所有\eqmakebox
具有相同标签的 es 将被赋予相同的尺寸(第二次运行后)。它们默认居中,但我提供了[l]
一个选项使它们左对齐。- 这使用的标签
\eqmakebox
包括\EnumitemId
,它是当前列表环境的唯一标识符,由 提供enumitem
。 - 使用 检索给定列表的最宽框的宽度,并将其
\eqboxwidth
用作labelwidth
该列表的宽度。 - 计算出的水平长度为
leftmargin
,与您的 MWE 一样。
enumitem
和的文档eqparbox
可以找到这里和这里分别
笔记
- 如果您希望文档中所有此类列表环境都使用相同的环境,那么
labelwidth
您可以简单地删除\EnumitemId
我的代码片段中的两个实例。 - 如果使用大于 1 的
\newlist{mydescription}{description}{n}
for进行声明,则可以安全地嵌套它们。n
- 要使标签右对齐、居中或拉伸(仅限多词),可以分别用、或
\mydescriptionlabel
替换。\mydescriptionlabel[r]
\mydescriptionlabel[c]
\mydescriptionlabel[s]
- 您可以
\mydescriptionlabel
与其他文本格式命令结合使用,但它们应该优先于其他命令\mydescriptionlabel
,并且它们都不应该带有参数(所以\itshape
是可以的,但\textit
不是)。 项目标签本身仍会排版两次,因此其中的分配仍会执行两次,我认为这不太可能成为问题。这可以避免,但前提是前言会变得很长。
标签本身仍会排版两次,因此标签中的定义也会应用两次。这可以避免,但与排版整个列表两次相比,这不太可能成为问题。
- 标签不允许包含任何不允许放入环境中的东西
tabular
,但我不认为这是一个很大的限制,因为我想不出任何不允许放入的东西,而这些东西却tabular
可以在物品标签中放进去。
与其他解决方案的比较
Gonzalo Medina 的回答在大多数情况下都能很好地工作,但它有点过于繁琐并且有一些限制:
\NewEnviron
有一些微妙的 问题和限制;- 显示方程式在以此方式定义的列表中不起作用;
- 从列表中任何地方增加的计数器将增加两次,并且列表环境中进行的其他全局分配也是如此;
- 如果标签不使用粗体格式,则它将不起作用;
- 多个这样的环境不能嵌套。
我的答案的缺点:
- 我的解决方案需要额外的编译运行,因为第一次运行期间测量的标签宽度仅在第二次运行期间可用。
答案2
\documentclass{article}
\usepackage{enumitem}
\usepackage{environ}
\newlength\widest
\makeatletter
\NewEnviron{ldescription}{%
\vbox{%
\global\setlength\widest{0pt}%
\def\item[##1]{%
\settowidth\@tempdima{\textbf{##1}}%
\ifdim\@tempdima>\widest\global\setlength\widest{\@tempdima}\fi%
}%
\setbox0=\hbox{\BODY}%
}
\begin{description}[
leftmargin=\dimexpr\widest+0.5em\relax,
labelindent=0pt,
labelwidth=\widest]
\BODY
\end{description}%
}
\makeatother
\begin{document}
\begin{ldescription}
\item[Short] text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text
\item[A really really long label] text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text
\end{ldescription}
\begin{ldescription}
\item[Short] text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text
\item[A medium label] text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text
\end{ldescription}
\end{document}
在第一部分中,计算最长标签的宽度:最初,\widest
设置为0pt
;对于每个\item
,测量其可选参数的长度并将长度存储在中\@tempdima
;如果\@tempdima
大于\widest
,(对于第一个,这始终为真\item
)则\widest
更新为\@tempdima
。对所有 s 都执行此操作\item
;\BODY
设置在从未使用过的框内。然后,description
使用环境,将设置\labelwidth
为先前计算的值\widest
。
后续问题的答案:
- 问: 有什么意义
\setbox0=\hbox{\BODY}
?
A:这只是装箱\BODY
,没有排版。 - 问: 由于
\BODY
使用了两次,如果\BODY
包含具有副作用的内容(写入文件.aux
、包含\newcommand
、包含\footnote
等)会发生什么?
A:包含的内容没有问题\BODY
(只要对于标准描述是合理的,例如,不允许使用分段单位命令,但无论如何它们在描述中没有意义)。 - 问: 为什么那些东西被包裹在里面
\vbox{}
?
A:用于\vbox
防止在列表开头出现可怕的“出现问题 - 可能缺少 \item。”错误(\hbox
或者\mbox
也可以改用)。