lrbox 环境如何正确地将内容放置在 TOC 中

lrbox 环境如何正确地将内容放置在 TOC 中

我检查了我写的一些旧代码,以为发现了一个拼写错误。但无论如何,一切都按我想要的方式运行。

此处 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条件中嵌入等)。

根据我对lrboxes 行为方式的理解,我原本以为在创建 时lrbox\phantomsection也应该会产生包括其实\addcontentsline,我越想这件事,心里就越复杂。

有人可以解释一下我的代码为什么有效吗?

答案1

你必须区分“代码”,但当一个盒子被建造时全部代码被执行。但是,有些代码的执行方式可能会被误认为是“未执行”。

我指的是生成“whatsits”的代码。一条\addcontentsline指令最终简化为\write(以.aux文件作为输出;稍后将重复类似的操作write以写入.toc文件)。

A\write生成一个附加到当前正在构建的列表的 whatsit;因为lrbox它是一个水平列表,而\writewhatsit 实际上并不属于水平列表:它包含一个指向内存位置的指针,该位置\write存储了完全扩展的参数,以便在操作期间执行\shipout

请注意,\immediate\write不会创建任何东西;事实上,\addcontentsline指令是“延迟的” \write

因此有三种情况:

  1. 水平列表本身被运出(不太可能)
  2. 在构建垂直列表时使用水平列表
  3. 水平列表根本没有使用

第一种情况不太可能发生,因为

\begin{lrbox}{\mytocitembox}
...
\end{lrbox}\shipout\box\mytocitembox

可能被认为是相当奢侈的。所以我们只剩下情况 2 和 3。在情况 2 中,whatsit 将迁移到封闭的垂直列表(迁移过程在 TeXbook 的第 24 章和第 25 章中描述)。在情况 3 中,它将消失得无影无踪。

几乎相同的情况也发生在\specialwhatsits 上,不同之处在于没有发生迁移。但是,同样,如果从未使用水平列表(即传递到另一个列表),则\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}

输出:

rbox1

\phantomsection确实进行了全局分配,因此在执行时,其这部分操作具有与我们的测试相同的效果。

此外额外的 \phantomsection已经由 完成\addcontentsline( 的当前版本hyperref,以及在处理chaptersectionsubsection、 ... 时,这是 已知的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
  }

相关内容