你可能会认为我不会惹恼egreg 解决方案,但为了辩护,我使用了 DavidCarlisle 的解决方案\setbox 与 \sbox 和 \savebox - 我需要了解哪些区别?使 egreg 的解决方案适应检测空文本的正确方法到目前为止,这对我来说一直很好,但是当我尝试在enumerate
环境中使用它时,它会导致它吞噬产品编号:
请注意,如果没有可用的标题(第三项),则不会吞噬项目编号。
如果我\sbox0{\begingroup\vbox{#1}\endgroup}
按照原始解决方案替换:
\sbox0{#1}
这个特定的 MWE 起作用,但在其他地方引起了问题(此处未显示)。
问题:
那么,我可以对现有解决方案做出哪些最小改变,\sbox0
以使产品编号不被吞噬?
我正在禁用其中的某些宏\begingroup
以消除任何副作用,因此需要分组。
代码:
\documentclass{article}
\usepackage{enumitem}
\makeatletter
\newcommand{\DoIfNonEmptyText}[1]{% Actually takes two parameters
%\sbox0{#1}%
\sbox0{\begingroup\vbox{#1}\endgroup}%
\ifdim\wd0=\z@\relax%
\expandafter\@gobble%
\else%
\expandafter\@firstofone%
\fi%
}%
\makeatother
\newcommand{\QuotableText}[3][]{%
% [#1] = optional title
% #2 = name for this (not relevant for this example)
% #3 = the content
\DoIfNonEmptyText{#1}{\textit{#1}:~}%
#3%
}%
\begin{document}
\begin{enumerate}
\item First Item
\item \QuotableText[Sub title]{Text One}{Second Item}
\item \QuotableText{Text Two}{Third Item}
\item Fourth Item
\end{enumerate}
\end{document}
更新:
根据 Frank Mittelbach 的回答,我尝试使用我自己的\savebox
,但它仍然显示与上面相同的输出:
\makeatletter
\newsavebox{\@DoIfNonEmptyTextBox}%
\newcommand{\DoIfNonEmptyText}[1]{% Actually takes two parameters
\begingroup
\savebox{\@DoIfNonEmptyTextBox}{\vbox{#1}}% -- This still gobbles the item number!
%\savebox{\@DoIfNonEmptyTextBox}{#1}% -- This works
\ifdim\wd\@DoIfNonEmptyTextBox=\z@\relax%
\endgroup\expandafter\@gobble%
\else%
\endgroup\expandafter\@firstofone%
\fi%
}%
\makeatother
但如果我删除\vbox{}
,那么它就可以正常工作。那么,如何使用\vbox
(注意:这里不使用\sbox0
)导致枚举项消失?
答案1
当项目标签消失时,一个好的经验法则是添加\leavevmode
;但在这种情况下(感谢 Frank 指出这一点)设置\everypar
为空应该可以解决问题。
\makeatletter
\newcommand{\DoIfNonEmptyText}[1]{% Actually takes two parameters
\begingroup\sbox0{\vbox{\everypar{}#1}}%
\ifdim\wd0=\z@\relax
\endgroup
\expandafter\@gobble
\else
\endgroup
\expandafter\@firstofone
\fi
}
\makeatother
内容\everypar
将在结束时恢复\vbox
并且这将保持enumerate
快乐。
答案2
错误的答案(尽管它偶然解决了这个问题)
答案很简单,不要使用框 0,或者确保使用它不会造成任何伤害。这是一个临时框,但\item
LaTeX 的命令也会使用它来存储其项目一小段时间(在遇到命令\item
和段落开始后排版标签之间),\QuotableText
在此期间会执行您的命令。
现在您对 box0 的更改不是本地的,因此您会覆盖内容。
解决方案 1:使用\newbox
获取您的私人盒子注册您的测试
解决方案 2:对 box0 进行本地更改。启动一个组,然后在进行测量后立即将其关闭(这意味着在两个分支中都将其关闭):
\makeatletter
\newcommand{\DoIfNonEmptyText}[1]{% Actually takes two parameters
\begingroup
\sbox0{#1}%
\ifdim\wd0=\z@\relax%
\endgroup
\expandafter\@gobble%
\else%
\endgroup
\expandafter\@firstofone%
\fi%
}%
\makeatother
你有过
\sbox0{\begingroup\vbox{#1}\endgroup}%
但那里的组设置错了。框 0 的设置仍然在组之外。组内只有正在排版的内容,因此组没有任何效果。
更新(为什么\vbox
物品标签会消失)
这是个棘手的问题。我们必须仔细查看列表环境如何将项目标签附加到项目段落。
基本上,内部发生的事情是标签被构造并存储在一个盒子里(它不是我之前声称的盒子 0,而实际上是一个私人盒子(\@labels
)所以感谢你的信任投票,但我的答案完全是错误的。
现在,通过使用,框已附加到\everypar
,实际使用的定义是
\everypar={\@minipagefalse \global \@newlistfalse \if@inlabel \global \@inlabel
false {\setbox \z@ \lastbox \ifvoid \z@ \kern -\itemindent \fi }\box \@labels
\penalty \z@ \fi \if@nobreak \@nobreakfalse \clubpenalty \@M \else \clubpenalty
\@clubpenalty \everypar {}\fi }
因此,当从垂直模式切换到水平模式时,就会执行上述操作。
现在,如果我们只是使用测试,\sbox
那么这将构建一个 h-box,因此\everypar
不会执行。但是,如果我们\vbox
在内部使用,那么任何文本#1
都会触发\everypar
。如果你仔细观察,标签只会在@inlabel
为真时设置,并且此开关是全球在真实情况下设置为 false。
因此基本上标签放入此处\vbox
进行测试,之后就不会再进行排版。
因此,总结一下上述最初的论点错误的(我没有仔细看,列表环境确实使用了框 0,但这是无害的)并且我的重新定义只起作用,因为我\vbox
同时取出了。