我检查了我写的一些旧代码,以为发现了一个拼写错误。但无论如何,一切都按我想要的方式运行。
此处 MWE 的基本思想是,我将内容存储在lrbox
我可能选择使用或不选择使用的 中。如果我使用它,我希望目录中有一个条目。如果我不使用lrbox
,那么我不想要任何这样的条目。
以下是 MWE:
\documentclass{article}
\usepackage{lipsum}
\usepackage{hyperref}
\newif\ifusemybox
\usemyboxtrue
\newsavebox{\mytocitembox}
\newenvironment{mytocitem}[1]{%
\begin{lrbox}{\mytocitembox}
\phantomsection
\addcontentsline{toc}{subsection}{#1}
\begin{minipage}[t]{3in}
}
{%%
\end{minipage}%%
\end{lrbox}%%
\ifusemybox
\usebox{\mytocitembox}
\fi
}
\begin{document}
\tableofcontents
\section{A}
\lipsum[1-5]
\begin{mytocitem}{test for section A}
\lipsum[5]
\end{mytocitem}
\lipsum[6-14]
\section{B}
\usemyboxfalse
\begin{mytocitem}{test for section B}
\lipsum[10]
\end{mytocitem}
\lipsum[5-10]
\end{document}
我感到困惑的是它\phantomsection
是如何工作的。
在我看来,正确的写法应该是使用
\ifusemybox
\phantomsection
\addcontentsline{toc}{subsection}{#1}
\fi
在环境的定义中(而不是我实际做的,即不在\phantomsection
条件中嵌入等)。
根据我对lrbox
es 行为方式的理解,我原本以为在创建 时lrbox
,\phantomsection
也应该会产生包括其实\addcontentsline
,我越想这件事,心里就越复杂。
有人可以解释一下我的代码为什么有效吗?
答案1
你必须区分“代码”,但当一个盒子被建造时全部代码被执行。但是,有些代码的执行方式可能会被误认为是“未执行”。
我指的是生成“whatsits”的代码。一条\addcontentsline
指令最终简化为\write
(以.aux
文件作为输出;稍后将重复类似的操作write
以写入.toc
文件)。
A\write
生成一个附加到当前正在构建的列表的 whatsit;因为lrbox
它是一个水平列表,而\write
whatsit 实际上并不属于水平列表:它包含一个指向内存位置的指针,该位置\write
存储了完全扩展的参数,以便在操作期间执行\shipout
。
请注意,\immediate\write
不会创建任何东西;事实上,\addcontentsline
指令是“延迟的” \write
。
因此有三种情况:
- 水平列表本身被运出(不太可能)
- 在构建垂直列表时使用水平列表
- 水平列表根本没有使用
第一种情况不太可能发生,因为
\begin{lrbox}{\mytocitembox}
...
\end{lrbox}\shipout\box\mytocitembox
可能被认为是相当奢侈的。所以我们只剩下情况 2 和 3。在情况 2 中,whatsit 将迁移到封闭的垂直列表(迁移过程在 TeXbook 的第 24 章和第 25 章中描述)。在情况 3 中,它将消失得无影无踪。
几乎相同的情况也发生在\special
whatsits 上,不同之处在于没有发生迁移。但是,同样,如果从未使用水平列表(即传递到另一个列表),则\special
在输出文件(DVI 或 PDF)中没有位置。指令\phantomsection
执行\special
命令(在 的情况下pdflatex
,它是\pdfdest
,这非常相似)。
如果你添加代码
{\tracingonline=1 \showboxdepth=1000 \showboxbreadth=1000 \showbox\mytocitembox}
并\ifusemybox
从终端进行编译,TeX 将停止显示
> \box26=
\hbox(6.94444+0.0)x220.14333
.\penalty 10000
.\hbox(0.0+0.0)x0.0
..\hbox(0.0+0.0)x0.0, shifted -12.0
...\pdfdest name{section*.2} xyz
...\penalty 10000
.\write3{\protect \BOOKMARK [2][-]{section*.2}{test for section A}{section.1}%\ETC.}
.\write1{\@writefile{toc}{\protect \contentsline {subsection}{test for section\ETC.}
.\glue 3.33333 plus 1.66666 minus 1.11111
.\vbox(6.94444+0.0)x216.81
..\hbox(6.94444+0.0)x216.81, glue set 194.30995fil
...\hbox(0.0+0.0)x0.0
...\OT1/cmr/m/n/10 H
...\OT1/cmr/m/n/10 e
...\OT1/cmr/m/n/10 l
...\OT1/cmr/m/n/10 l
...\OT1/cmr/m/n/10 o
...\penalty 10000
...\glue(\parfillskip) 0.0 plus 1.0fil
...\glue(\rightskip) 0.0
总之,这样的命令是执行,但执行结果可能不会在输出中体现出来。因此,是否将\phantomsection
或\addcontentsline
指令放在\ifusemybox
条件语句中并不重要。
相反,诸如的命令\stepcounter
将在构建框时立即执行;使用框是无关紧要的:如果您不使用用构建的框\sbox
,\savebox
或者lrbox
或多次使用它,\stepcounter
那么其中的命令将只执行一次。
答案2
当定义时lrbox
,它会在定义时执行里面的代码。我们可以从下面看到这一点,其中计数器test
从定义框的唯一动作开始递增:
\documentclass{article}
\usepackage{hyperref}
\newif\ifusemybox
\usemyboxtrue
\newcounter{test}
\def\TESTINLRBOX{\stepcounter{test}}
\newsavebox{\mytocitembox}
\newenvironment{mytocitem}[1]{%
\begin{lrbox}{\mytocitembox}
\TESTINLRBOX
\phantomsection
\addcontentsline{toc}{subsection}{#1}
\begin{minipage}[t]{3in}
}
{%%
\end{minipage}%%
\end{lrbox}%%
\ifusemybox
\usebox{\mytocitembox}
\fi
}
\begin{document}\thispagestyle{empty}
\tableofcontents
\section{A}
\begin{mytocitem}{test for section A}
Hello
\end{mytocitem}
\arabic{test}
\section{B}
\usemyboxfalse
\begin{mytocitem}{test for section B}
World
\end{mytocitem}
\arabic{test}
\end{document}
输出:
\phantomsection
确实进行了全局分配,因此在执行时,其这部分操作具有与我们的测试相同的效果。
此外额外的 \phantomsection
已经由 完成\addcontentsline
( 的当前版本hyperref
,以及在处理chapter
、section
、subsection
、 ... 时,这是 已知的hyperref
)。您的代码没有任何\phantomsection
作用!
\documentclass{article}
\usepackage{hyperref}
\newif\ifusemybox
\let\oldphantomsection\phantomsection
\def\phantomsection{\stepcounter{test}\oldphantomsection}
\newcounter{test}
\newsavebox{\mytocitembox}
\newenvironment{mytocitem}[1]{%
\begin{lrbox}{\mytocitembox}
% \phantomsection
\addcontentsline{toc}{subsection}{#1}
\begin{minipage}[t]{3in}
}
{%%
\end{minipage}%%
\end{lrbox}%%
\ifusemybox
% \phantomsection
\usebox{\mytocitembox}
\fi
}
\begin{document}
\tableofcontents
\section{A}
phantomsection has been done \arabic{test} times
\usemyboxfalse
\begin{mytocitem}{test for section A}
Hello
\end{mytocitem}
phantomsection has been done \arabic{test} times
% testing:
% @currentHref: \csname @currentHref\endcsname
% Hy@linkcounter: \the\csname Hy@linkcounter\endcsname
\section{B}
\usemyboxtrue
phantomsection has been done \arabic{test} times
\begin{mytocitem}{test for section B}
World
\end{mytocitem}
phantomsection has been done \arabic{test} times
% @currentHref: \csname @currentHref\endcsname
% Hy@linkcounter: \the\csname Hy@linkcounter\endcsname
\end{document}
此代码在日志文件中包含与添加相关的以下内容\phantomsection
:
Package hyperref Warning: The anchor of a bookmark and its parent's must not
(hyperref) be the same. Added a new anchor on line 38.
Package hyperref Warning: The anchor of a bookmark and its parent's must not
(hyperref) be the same. Added a new anchor on input line 56.
答案3
框中的内容是用 执行的\usebox
,而不是之前执行的。所以您的示例有效。
不过,我会这样使用它:
\newenvironment{mytocitem}[1]
{%
\def\mytocentry{#1}%
\begin{lrbox}{\mytocitembox}
\begin{minipage}[t]{3in}
}{%%
\end{minipage}%%
\end{lrbox}%%
\ifusemybox
\phantomsection
\addcontentsline{toc}{subsection}{\mytocentry}%
\usebox{\mytocitembox}%
\fi
}