解决方案

解决方案

如何创建一个描述列表,其中项目文本自动缩进到最宽标签的宽度?

这个问题与具有对齐描述的描述列表但我希望自动计算最宽标签的宽度。

例如,我想要以下输出:

示例输出

由以下输入产生:

\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}}

这样做的目的是将描述项标签放入\eqmakeboxes 中,以便将其宽度写入辅助文件。从第二次运行开始,同一列表中的所有此类框将被赋予相同的宽度(其中最大),并且此宽度将用作列表的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(enumitemv3.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环境。
  • enumitemsformat键用于将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

变体egreg 的回答使用边距

\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也可以改用)。

相关内容