LuaTeX:标记数学边界而不影响排版输出(形式和空格)

LuaTeX:标记数学边界而不影响排版输出(形式和空格)

从我之前的回答可以看出问题,数学边界节点不是标记数学边界的可靠方法。有两种可能的解决方法可用于标记边界:1) 在数学列表周围添加用户定义的 whatsit 节点,2) 为数学列表内的节点定义并设置“数学”属性。在 TeX 完成分行后,这种边界标记在很多方面都很有用,可以方便地发布处理节点列表:a) 在尝试提取文本时添加 <beginmath> <endmath> 类型的分隔符 b) 选择性地对文本和数学着色,c) 等等。a) 还可以与添加到 beginmath whatsit 值的“替代文本”一起使用,以提供方程的替代/文本描述。

关于方法 1),LuaTeX 文档指出“LuaTEX 引擎会直接跳过这些内容,而不去查看其中的内容。”虽然有些帖子表明,whatsits 会影响微排版、换行和分页输出:链接到邮政显示受影响的分页,并链接到邮政显示受影响的微排版(这也会影响 TeX 中的换行)。

鉴于方法 2) 需要遍历所有数学列表内容,如果使用 mlist_to_hlist 过滤器调用,它会比 1) 慢吗?(除非$<tex_equation>$可以安全地重新定义以在分隔符之前和之后设置和取消设置属性$,尽管有更多这样的分隔符......)。所以问题是,如何使用方法 1) 标记数学边界,使其真正不影响微排版、换行和分页?或者方法 2) 是唯一安全的选择?

这是我第一次尝试内联数学,正在寻找专家评审和显示数学的扩展(对齐等环境)。

代码:

% >>> lualatex mathmode.tex
\documentclass{article}
\usepackage[callback={}]{nodetree}

\begin{document}

\directlua{
local function mathboundary(h,d,p)
    local beginmath = node.new("whatsit","user_defined")
    beginmath.type = string.byte("s")
    beginmath.value = "beginmath"
    beginmath.attr = h.attr
    beginmath.user_id = luatexbase.new_whatsit'beginmath'
    local endmath = node.new("whatsit","user_defined")
    endmath.type = string.byte("s")
    endmath.value = "endmath"
    endmath.attr = h.attr
    endmath.user_id = luatexbase.new_whatsit'endmath'
    h = node.insert_after(h,node.tail(h),endmath)
    h = node.insert_before(h,h,beginmath)
    return node.mlist_to_hlist(h,d,p)
end

luatexbase.add_to_callback('mlist_to_hlist', mathboundary, 'Mark math')
}

\setbox0=\vbox{{\noindent Hello\\ $x=a+b^2$}}

\directlua{
    local nodetree = require('nodetree')
    nodetree.print(tex.box[0])
}

\box0

\end{document}

控制台(寻找WHATSIT subtype: user_defined):

└─VLIST width: 345pt, depth: 0.83pt, height: 18.94pt
  ╚═head:
    ├─HLIST subtype: line, width: 345pt, depth: 0.11pt, height: 6.94pt
    │ ╚═head:
    │   ├─LOCAL_PAR 
    │   ├─GLYPH subtype: 256, char: H, width: 7.5pt, height: 6.83pt
    │   ├─GLYPH subtype: 256, char: e, width: 4.44pt, height: 4.48pt, depth: 0.11pt
    │   ├─GLYPH subtype: 256, char: l, width: 2.78pt, height: 6.94pt
    │   ├─GLYPH subtype: 256, char: l, width: 2.78pt, height: 6.94pt
    │   ├─GLYPH subtype: 256, char: o, width: 5pt, height: 4.48pt, depth: 0.11pt
    │   ├─PENALTY penalty: 10000
    │   ├─GLUE stretch: +1fil
    │   ├─PENALTY penalty: -10000
    │   └─GLUE subtype: rightskip
    ├─PENALTY subtype: linebreakpenalty, penalty: 300
    ├─GLUE subtype: baselineskip, width: 3.75pt
    └─HLIST subtype: line, width: 345pt, depth: 0.83pt, height: 8.14pt
      ╚═head:
        ├─WHATSIT subtype: user_defined, user_id: 1, type: 115, value: beginmath
        ├─GLYPH subtype: 256, char: x, width: 5.72pt, height: 4.31pt
        ├─GLUE subtype: thickmuskip, width: 2.78pt, stretch: 2.78pt
        ├─GLYPH subtype: 256, char: =, width: 7.78pt, height: 3.67pt, depth: -1.33pt
        ├─PENALTY subtype: noadpenalty, penalty: 500
        ├─GLUE subtype: thickmuskip, width: 2.78pt, stretch: 2.78pt
        ├─GLYPH subtype: 256, char: a, width: 5.29pt, height: 4.31pt
        ├─GLUE subtype: medmuskip, width: 2.22pt, stretch: 1.11pt, shrink: 2.22pt
        ├─GLYPH subtype: 256, char: +, width: 7.78pt, height: 5.83pt, depth: 0.83pt
        ├─PENALTY subtype: noadpenalty, penalty: 700
        ├─GLUE subtype: medmuskip, width: 2.22pt, stretch: 1.11pt, shrink: 2.22pt
        ├─GLYPH subtype: 256, char: b, width: 4.29pt, height: 6.94pt
        ├─HLIST subtype: sup, width: 4.49pt, height: 4.51pt, shift: -3.63pt
        │ ╚═head:
        │   └─GLYPH char: 2, width: 3.99pt, height: 4.51pt
        ├─WHATSIT subtype: user_defined, user_id: 2, type: 115, value: endmath
        ├─MATH subtype: endmath
        ├─PENALTY subtype: linepenalty, penalty: 10000
        ├─GLUE subtype: parfillskip, stretch: +1fil
        └─GLUE subtype: rightskip

答案1

在换行后查看节点列表时,边界节点不是一个可靠的解决方案。这至少有两个原因:

  1. 每个节点要么可丢弃,要么不可丢弃。如果您的边界节点是可丢弃的,那么您会遇到与数学开启/数学关闭节点相同的问题:它们会在行首消失。因此,您想要的节点不能是可丢弃的,但请记住行首可丢弃节点的规则:换行后,所有可丢弃节点都将被丢弃,直到遇到第一个不可丢弃节点。特别是,这意味着您的新不可丢弃边界节点将抑制可能跟随的可丢弃节点的删除,从而影响输出。(还有其他原因可以解释为什么在这里添加不可丢弃节点(例如 whatsit)总是会影响换行,但这个是最容易描述的)

  2. 一旦出现换行,您可以让 hlist 包含数学但不包含任何数学边界。一个相当极端的例子是:

    \documentclass{article}
    \begin{document}
    \showoutput
    \setbox0\vbox{
      \hsize=1cm
      \noindent
      Hi $1+2+3+4$
    }
    \setbox1\vsplit0 to\baselineskip 
    \setbox2\vsplit0 to\baselineskip
    \showbox0
    \showbox1
    \showbox2
    \end{document}
    

    这里,框 1 最终只包含数学公式的中间部分,因此它没有理由包含任何类型的边界节点。但它仍然是数学。

    (实践中更可能发生的这种情况是数学公式跨页。第二页将没有起始数学节点,因为没有数学从那里开始。)

那么我们能做些什么呢?有两种选择:

  1. 虽然您的方法 1) 无法可靠地工作,但您可以实现 2) 并添加一个属性。基本上,您只需在post_mlist_to_hlist_filter¹ 中迭代节点列表即可添加它,然后内联和显示数学都将被覆盖。
  2. 做任何你想做的事换行前并只使用现有的数学边界节点。在大多数情况下,你无论如何都希望在换行之前做一些工作,所以这是很自然的。

根据具体用例,其中一种解决方案通常会非常自然。例如,文本提取几乎总是基于预换行(甚至预整形)文本,因此它适合 2,而为数学节点添加不同的颜色实际上只是意味着添加一个属性,因此它自然适合 1。

¹:在现代 LuaLaTeX 版本中,mlist_to_hlist几乎从不直接设置,而应使用pre_mlist_to_hlist_filter或。它们具有与基本相同的接口,但您不必自己调用,并且您的代码与使用这些回调的其他包保持兼容。例如,您问题中的代码可以通过替换进行调整post_mlist_to_hlist_filtermlist_to_hlistnode.mlist_to_hlist

luatexbase.add_to_callback('mlist_to_hlist', mathboundary, 'Mark math')

luatexbase.add_to_callback('pre_mlist_to_hlist_filter', mathboundary, 'Mark math')

并更换

return node.mlist_to_hlist(h,d,p)

只需

return h

相关内容