`\discretionary` 如何工作?

`\discretionary` 如何工作?

在查看其他人的包时,我发现这个\discretionary命令被使用得相当频繁。我想挂接到其中一个参数中使用的代码中,但发现我的代码执行得比我预期的要多得多。我猜想它的\discretionary行为有点像\mathchoice:每个参数都被处理成一个框,其中一个框被排版到文档中,其他的被丢弃。

是吗?

(我想做的是当使用某个特定参数时,计数器会移动。由于这是一个分配,所以每次\discretionary使用时,计数器似乎都会移动。在类似情况下,我使用了一个技巧,使用文件aux来查看实际处理了哪个框。我想知道我是否可以在这里避免这种情况。)

答案1

(对您最终想要完成的目标没有帮助,只是按照要求逐字回答问题。)

回想一下,这\discretionary应该被用作

\discretionary{⟨pre-break text⟩}{⟨post-break text⟩}{⟨no-break text⟩}

其中 是⟨no-break text⟩默认打印的内容,但如果需要换行,则当前行将以 结束⟨pre-break text⟩,下一行将以 开始⟨post-break text⟩。这在 TeXbook 第 95 页(第 14 章:TeX 如何将段落分成行)中有解释。

其内部工作原理是 TeX 会将“圆盘”节点插入到当前列表中。例如,如果你输入

在这里,您可以找到一个 st\discretionary{ra-}{ng}{rang}e 词。

那么 TeX 的内部列表将如下所示(点击图片以获得适当的分辨率):

列表

(还要注意字距和“fi”连字符——上面的代码是使用 LuaTeX 的pre_linebreak_filteras这里)。更准确地说,disc节点指向的当然是列表本身:

奇怪的.png

一般而言,这三个列表将仅包含char_node、、、、和项目。kern_nodehlist_nodevlist_noderule_nodeligature_node

当 TeX 执行\discretionary命令时,会构建这些列表;稍后在断行期间,TeX 将首先在其第一次(link无连字符)传递中使用节点的 (无断行文本)disc,然后(如果在 中未找到任何令人满意的内容)在第二次断行传递中\pretolerance使用节点的pre和链接。postdisc


进一步深入到更低层次的细节:

当 TeX 遇到 时\discretionary,如果它处于垂直模式,那么(就像遇到字母 或\hskip等时一样)它首先后退并开始一个新段落(从而切换到水平模式)。(参见 TeXbook 第 283 页“第 24 章:垂直模式总结”,或该计划的第 1090 条)因此我们可以假设当 TeX 遇到 时,它处于水平模式或数学模式\discretionary

因此,当它遇到时\discretionary,它会执行以下操作:

  1. 将新disc节点附加到当前列表。
  2. 推送到保存堆栈,并开始新的保存级别,跟踪当前级别是否为可自由选择的类型。(而不是 hbox、align 等——有 16 种组;该计划的第 269 条
  3. 扫描左边的括号。
  4. 进入受限水平模式(空间系数为 1000,默认值)。
  5. 然后,直到遇到右括号,继续照常处理(就像在受限水平模式下一样)。
  6. 当遇到右括号时,
    • 修剪目前已建立的列表,使其仅包含 char、kern、hlist、vlist、rule 和 ligature 节点。(否则说“不适当的自由列表”。)
    • 如果正在扫描休息前的文本,则让disc节点的pre_break字段指向它,然后再次开始扫描(从上面的步骤 3 开始)。
    • 如果正在扫描休息后的文本,则使disc节点的post_break字段指向它,然后再次开始扫描(从上面的步骤 3 开始)。
    • 如果正在扫描不间断文本,则使disc节点的link字段指向它。(此处对于数学模式,该字段应为空。)

这一切都在该计划的第 1114 至 1121 条,或者 The TeXbook 的第 287 页(第 25 章:水平模式摘要):

\discretionary⟨general text⟩⟨general text⟩⟨general text⟩

这三个通用文本以受限水平模式处理。它们应该只包含固定宽度的内容;因此在这种情况下它们实际上并不是很通用。更准确地说,每个自由选择通用文本形成的水平列表必须仅由字符、连字、字距、方框和规则组成;不应有粘连或惩罚项等。此命令将自由选择项附加到当前列表;有关自由选择项的含义,请参阅第 14 章。空间因子不变。

答案2

如果您使用 luatex,您可以在回调中遍历节点post_linebreak_filter,但看看经典的 TeX......

你提到\mathchoice,但情况更\mathchoice简单,语法\mathchoice

\mathchoice{a}{b}{c}{d}

并且 a、b、c、d 中的每一个都设置在一个框中,但只有其中一个框被复制到最终页面,因此通过在每个框中放置非立即写入,您可以从输出中看到使用了哪个框。

 \mathchoice{\write20{A}a}{\write20{B}b}{\write20{C}c}{\write20{D}d}

只会写出 ABCD 中的一个来显示使用了哪个盒子。

情况discretionary表面上类似

 \discretionary{pre-break text}{post-break text}{no-break text}

但参数通常不被框起来。使用一个盒子,但通常它们只是字符,并且它们仅限于字符,盒子和字距,你不能有写节点。

因此您可以立即打印而不生成节点:

aaa \discretionary{\message{1}a}{\message{2}b}{\message{3}c} xxx

aaa \discretionary{\immediate\write20{a}a}{\immediate\write20{b}a}{\immediate\write20{c}a} xxx

但是这两种形式都不会给你任何信息,因为所有三个参数都会被评估,所以你分别得到1 2 3a b c

你可以尝试非立即行动\write,但你会得到

! Improper discretionary list.
l.9 aaa \discretionary{\write20{a}a}

所以你可以把写入节点放在一个盒子里

aaa \discretionary{\hbox{\write20{A}a}}{\hbox{\write20{B}b}}{\hbox{\write20{C}c}} xxx

没有错误,只是输出C没有换行的情况,但这意味着水平列表\hbox{c}不包含c任何会干扰连字或字母间字距的字符。

相关内容