我目前正在试验linebreak_filter
luatex,当使用它来更改/替换换行算法时,\lastnodetype
命令停止工作。
MWE 如下:
\def\partest{%
\directlua{%
function hpackparagraph (head)
return node.hpack(head)
end
callback.register("linebreak_filter", hpackparagraph)
}%
\par
}
\setbox0\vbox{ABC\par
{\tracingonline1\showboxbreadth\maxdimen\showboxdepth\maxdimen\showlists}%
\showthe\lastnodetype
}
\setbox0\vbox{ABC\partest
{\tracingonline1\showboxbreadth\maxdimen\showboxdepth\maxdimen\showlists}%
\showthe\lastnodetype
}
简而言之,该\partest
命令改变了段落分隔算法,简单地将未分隔的段落打包到水平盒子中,并将该水平盒子不加改变地返回到垂直列表(独立使用时没用,但这是能显示我的问题的最短版本)。
因此如果你运行这个你会得到:
This is LuaTeX, Version beta-0.70.1-2011061421 (rev 4277)
restricted \write18 enabled.
(./lua-bug.tex
### internal vertical mode entered at line 13
\hbox(6.83331+0.0)x469.75499, glue set 427.9494fil, direction TLT
.\whatsit
..\localinterlinepenalty=0
..\localbrokenpenalty=0
..\localleftbox=null
..\localrightbox=null
.\hbox(0.0+0.0)x20.0, direction TLT
.\tenrm A
.\tenrm B
.\tenrm C
.\penalty 10000
.\glue(\parfillskip) 0.0 plus 1.0fil
.\glue(\rightskip) 0.0
prevdepth 0.0, prevgraf 1 line
### vertical mode entered at line 0
prevdepth ignored
! OK.
l.14 ...h\maxdimen\showboxdepth\maxdimen\showlists
}%
?
> 1.
l.15 \showthe\lastnodetype
?
### internal vertical mode entered at line 18
\hbox(6.83331+0.0)x41.8056, direction TLT
.\whatsit
..\localinterlinepenalty=0
..\localbrokenpenalty=0
..\localleftbox=null
..\localrightbox=null
.\hbox(0.0+0.0)x20.0, direction TLT
.\tenrm A
.\tenrm B
.\tenrm C
.\penalty 10000
.\glue(\parfillskip) 0.0 plus 1.0fil
prevdepth ignored
### vertical mode entered at line 0
prevdepth ignored
! OK.
l.19 ...h\maxdimen\showboxdepth\maxdimen\showlists
}%
?
> -1.
l.20 \showthe\lastnodetype
如您所见,\lastnodetype
第一个测试的结果1
与预期一致 (hbox)。但第二个测试的结果为 (hbox),这-1
意味着当前列表没有节点。
这当然是不正确的,因为它确实在我的原语返回的 hbox 里面有一个 hbox linebreak_filter
。我们可以观察到的两个列表之间的唯一区别是,在第一个列表中,hbox 的宽度为 469pt,而在第二个列表中,它的宽度是其自然宽度,但这是预料之中的,因为它是这样打包的。
我猜想标准段落生成器放置的节点列表正在更新数据结构,\lastnodetype
但如果应用了这样的过滤器,则不会发生这种情况,这感觉像是一个错误。还是我在这里遗漏了一些基本的东西?
更新
与问题一致,\lastbox
在应用的情况下linebreak_filter
,该命令也会返回一个空框,即\setbox0\lastbox \showbox0
无法返回当前列表中的最后一个对象。
答案1
我现在相当肯定这是 当前实现中的一个错误linebreak_filter
。似乎发生了以下情况:当 TeX 构建列表(在本例中为垂直列表)时,它会通过指向该列表的头部和尾部的指针来跟踪它。现在,当段落被分成几行时,这些行会通过换行算法附加到当前垂直列表中,并且指针 tail
会更新为指向此列表的新末尾。但是,如果linebreak_filter
取代 TeX 的算法,则该过滤器的材料会附加到列表中,但tail
指针不会改变。因此在我的示例中,它仍然指向段落之前的节点(当段落开始 vbox 时,该节点恰好为 nil)。
幸运的是,这个tail
指针可以从 Lua 代码中访问和修改,所以有一种方法可以解决这个问题。但是,我们无法在 中执行此操作linebreak_filter
,因为调用过滤器的当前 luatex 代码恰好需要这个尾部错误的因为它做了一些最后的检查和改变。
但是我们可以做的是使用后续文件post_linebreak_filter
来为我们完成工作,因为到那时所有这些事情都已经发生了。因此解决方法如下:
function hpack_paragraph (head)
h = node.hpack(head)
return h
end
function fix_nest_tail (head)
tex.nest[tex.nest.ptr].tail = node.tail(head)
return true
end
callback.register("linebreak_filter", hpack_paragraph)
callback.register("post_linebreak_filter",fix_nest_tail)
第二个过滤器只是更新 tail
当前语义嵌套中的 TeX 指针,使其成为换行产生的结果的尾部。有点讨厌,但至少它允许linebreak_filter
在当前实现中使用。
如果您想知道这段代码的作用:它无需进行任何进一步的操作即可更改输出,以便每个段落都作为一行返回(并删除诸如 fromitemize
或类似环境的缩进),并且脚注或\vadjust
s 之类的插入内容保留在这些水平盒子内。因此,它还需要更多功能才能变得有用,但请发挥您的想象力。
更新
Hans Hagen 向我指出,正确的方法linebreak_filter
通常需要设置更多变量,即诸如prevdepth
和之类的变量prevgraf
,并可能添加表示基线跳过的粘合节点。与指针问题相比,tail
这些可以直接在过滤器中设置,因此更完整的解决方案将如下所示:
function oneliner(head)
local h = node.hpack(head)
local d = tex.baselineskip.width - tex.nest[tex.nest.ptr].prevdepth - h.height
tex.nest[tex.nest.ptr].prevdepth = h.depth
tex.nest[tex.nest.ptr].prevgraf = 1
local n
if d < tex.lineskiplimit then
n = 1
d = tex.lineskip
else
n = 2
end
local s = node.new("glue_spec")
local n = node.new("glue",n)
s.width = d
n.spec = s
return node.insert_before(h,h,n)
end
callback.register("linebreak_filter",oneliner)
-- fix the bug with the tail pointer not being updated:
function fix_nest_tail (head)
tex.nest[tex.nest.ptr].tail = node.tail(head)
return true
end
callback.register("post_linebreak_filter",fix_nest_tail)
对于我所考虑的用例,这种额外的编码是不必要的,因为无论如何我都想存储这些框以供日后使用,但如果有人想编写一个真正的换行算法,这种额外的工作肯定是需要的,所以在这里评论一下是有意义的。