除了发布我的问题中具体示例的正确答案外,请随意留下展示节点库功能的简短代码片段
我是 LuaTeX 节点库的新手,正在学习如何利用它通过 post_linebreak_filter 对行进行后处理。到目前为止,我能够进行非常基本的操作,就像我自己的问题的答案一样这里。接下来,我尝试遍历 hlist 节点,希望改变它们的 leftskip/rightskip 并将 raggedright 文本转换为 raggedleft。我的尝试和错误没有奏效,我在下面发布了我的错误尝试(警告:尝试 1 似乎进入了无限循环,因此不要在主终端中尝试)。有人可以解释一下这些有什么问题吗,并展示正确的代码是什么样子(并附上一些解释)?
% Attempt-1: Set leftskip/rightskip outside, and rebox the contents using hpack?
\directlua{
function my_post_lb_filter(head,groupcode)
local HLIST = node.id("hlist") % node.id for a line of text in vertical list
for n in node.traverse(head) do % For every subnode within paragraph
if n.id==HLIST then % If its a line of text
tex.setglue("rightskip",0,0,0,2,2)
tex.setglue("leftskip",0,65536,0,2,2)
local b = node.copy(n)
b = node.hpack(b.head)
node.write(b)
end
end
return head
end
luatexbase.add_to_callback('post_linebreak_filter', my_post_lb_filter, 'Play with luatex node library')
}
% Attempt-2 Traverse glue nodes within lines, and surgically update them
\directlua{
function my_post_lb_filter(head,groupcode)
local HLIST = node.id("hlist") % node.id for a line of text in vertical list
local GLUE = node.id("glue")
local RSKIP = node.subtype("rightskip")
local LSKIP = node.subtype("leftskip")
for n in node.traverse(head) do % For every subnode within paragraph
if n.id==HLIST then % If its a line of text
for g in node.traverse(n) do % For every subnode within line
if g.id==GLUE then % If its a glue
if g.subtype == RSKIP then
node.setglue(g,0,0,0,2,2)
end
if g.subtype == LSKIP then
node.setglue(g,0,65536,0,2,2)
end
end
end
end
end
return head
end
luatexbase.add_to_callback('post_linebreak_filter', my_post_lb_filter, 'Play with luatex node library')
}
关于尝试 2 的评论:根据我对 hlist 节点内部的观察,raggedright 行中缺少 'leftskip' 子节点。那么如何才能将 leftskip 子节点添加到这样的行中?而 raggedleft 行中同时包含 'leftskip' 和 'rightskip' 子节点,所以我猜这只是更新它们的问题。
答案1
首先让我们看看您的第一次尝试:
你使用了
tex.setglue("rightskip",0,0,0,2,2)
tex.setglue("leftskip",0,65536,0,2,2)
local b = node.copy(n)
b = node.hpack(b.head)
这里node.hpack
已用于重新打包,以便获取更改的rightskip
/leftskip
设置,但 TeX 的工作方式并非如此:rightskip
和leftskip
不是作为打包 hbox 的一部分应用的,而是在换行期间应用的。因此,对于这种方法,您必须使用回调linebreak
并在那里更改参数。
另外,使用node.write
几乎总是会导致回调出现问题。该函数会将一个节点添加到 TeX 正在处理的当前列表中,这有时不是您认为 TeX 正在处理的列表。相反,尝试使用作为传递的列表head
。在这种情况下,这恰好是同一个列表,但您正在复制hlist
节点并将它们附加在末尾。因此,在处理常规 hbox 节点后,列表的前端不再是末尾,而是后面跟着您复制的节点。然后处理这些复制的节点,从而创建更多副本。这导致了无限循环。
因此第二种方法更有希望。您的代码已经非常接近了,剩下的步骤是:
node.subtype
仅适用于 whatsit 节点,对于其他节点,您要么对值进行硬编码,要么分析返回的表node.subtypes
。- 我们必须遍历
n.head
而不是n
,否则您永远不会查看列表的实际内容。 我们必须确保左跳过粘合确实存在。我们可以保证左跳过粘合始终是 hlist 中的第一个节点,但如果我们已经有一个左跳过,则在迭代时记住这一点会更安全一些。(这样,如果另一个包添加了某个较早的节点,我们就不会遇到问题)。
要实际创建节点,我们可以使用
node.new
,然后将其插入到列表中node.insert_before
。- 这里使用 node.traverse_id 代替 node.traverse 可以简化一些。这样就不需要那么多 if 语句,而且速度也更快一些:
\documentclass{article}
\usepackage{blindtext}
\directlua{
local HLIST = node.id("hlist") % node.id for a line of text in vertical list
local GLUE = node.id("glue")
local RSKIP, LSKIP do
local gluetypes = node.subtypes("glue")
for i, n in pairs(gluetypes) do
if n == "leftskip" then LSKIP = i end
if n == "rightskip" then RSKIP = i end
end
end
function my_post_lb_filter(head,groupcode)
for n in node.traverse_id(HLIST, head) do % For every subnode within paragraph
local leftskip_found
for g, s in node.traverse_id(GLUE, n.head) do % For every subnode within line
if s == RSKIP then
node.setglue(g)
end
if s == LSKIP then
node.setglue(g,0,65536,0,2,0)
leftskip_found = true
end
end
if not leftskip_found then % We have to add a glue node
local g = node.new(GLUE, LSKIP)
g.subtype = LSKIP
node.setglue(g, 0, 65536, 0, 2, 0)
g.attr = n.attr % Ensure that attributes have some reasonable value
n.head = node.insert_before(n.head, n.head, g)
end
end
return head
end
luatexbase.add_to_callback('post_linebreak_filter', my_post_lb_filter, 'Play with luatex node library')
}
\begin{document}
\showoutput
\raggedright
\blindtext
\end{document}
你可能会注意到给读者留了一点练习:最后一行目前处于居中状态。解释为什么会发生这种情况并找出解决方法。