我正在使用 luatex 并学习有关节点和回调的知识。我想要做的是能够通过特定的回调添加一些文本。我感兴趣的回调类型是输入是节点列表的回调(想想hpack_filter
),所以我想以节点列表的形式附加文本。如果我要附加的文本已经存储为节点列表(比如),这很简单stuff
:
function appendStuff(h)
local l = node.tail(h)
l.next = node.copy_list(stuff)
return h
end
困难首先在于填充该节点列表。
我想要做的(因为我很懒)是指定一个字符串,比如说hello world
,将它传递给 TeX 以将其转换为节点列表,然后保存生成的节点列表。我已经想出了如何从 TeX 端做到这一点,请参阅下面的代码。我想要做的是完全从 lua 端完成。我意识到 TeX 必须在某个时候参与,但是有没有合适的方法来做到这一点?目前,我最好的猜测是让 lua 通过发出相关的 TeX 命令tex.print()
。使用hpack_filter
作为我的回调(有没有更好的?)我会让 lua 安装回调,发出 TeX 命令来创建一个框,然后卸载回调(并且为了干净起见,销毁该框)。这感觉很笨拙。有没有更好的方法来实现这一点?如果有帮助,我可以安排它,这样我的字符串就不需要扩展(当然,如果允许的话会更好)。
下面是一些可供参考的代码:
\documentclass{article}
\usepackage{filecontents}
\usepackage{luatexbase}
\begin{filecontents*}{texttonodes.lua}
local stuff
function saveNodeList(h)
print("Saving box")
stuff = h
luatexbase.remove_from_callback('hpack_filter',"Save a box")
end
function saveNextBox()
luatexbase.add_to_callback ( 'hpack_filter', saveNodeList, "Save a box" )
end
function useLastBox()
luatexbase.add_to_callback ( 'hpack_filter', useNodeList, "Use a box" )
end
function useNodeList(h)
if stuff then
local l = node.tail(h)
l.next = node.copy_list(stuff)
end
luatexbase.remove_from_callback('hpack_filter',"Use a box")
return h
end
\end{filecontents*}
\directlua{dofile('texttonodes.lua')}
\newbox\mybox
\newcommand\savetext[1]{%
\directlua{saveNextBox()}%
\setbox\mybox=\hbox{#1}%
}
\newcommand\usetext[1]{%
\directlua{useLastBox()}%
\setbox\mybox=\hbox{#1}%
\unhbox\mybox
}
\begin{document}
\savetext{hello world}
\usetext{goodbye earth, }
\end{document}
答案1
(取自LuaTeX 维基并针对 tl2016/17/18/...? 附带的 LuaTeX 进行了更新)
这看上去很难,其实不然。好吧,我不承认。
\documentclass{article}
\usepackage{luacode}
\usepackage{libertine}
\begin{document}
\begin{luacode*}
function newglue(parameters)
local g = node.new("glue")
local tmp_spec
if node.has_field(g,"spec") then
g.spec = node.new("glue_spec")
tmp_spec = g.spec
else
tmp_spec = g
end
for k,v in pairs(parameters) do
tmp_spec[k] = v
end
return g
end
function mknodes( text )
local current_font = font.current()
local font_parameters = font.getfont(current_font).parameters
local n, head, last
-- we should insert the paragraph indentation at the beginning
head = newglue({width = 20 * 2^16})
last = head
for s in string.utfvalues( text ) do
local char = unicode.utf8.char(s)
if unicode.utf8.match(char,"%s") then
-- its a space
n = newglue({width = font_parameters.space,shrink = font_parameters.space_shrink, stretch = font_parameters.space_stretch})
else -- a glyph
n = node.new("glyph")
n.font = current_font
n.subtype = 1
n.char = s
n.lang = tex.language
n.uchyph = 1
n.left = tex.lefthyphenmin
n.right = tex.righthyphenmin
end
last.next = n
last = n
end
-- now add the final parts: a penalty and the parfillskip glue
local penalty = node.new("penalty")
penalty.penalty = 10000
local parfillskip = newglue({stretch = 2^16,stretch_order = 2})
last.next = penalty
penalty.next = parfillskip
-- just to create the prev pointers for tex.linebreak
node.slide(head)
return head
end
local txt = "A wonderful serenity has taken possession of my entire soul, like these sweet mornings of spring which I enjoy with my whole heart. I am alone, and feel the charm of existence in this spot, which was created for the bliss of souls like mine."
tex.baselineskip = newglue({width = 14 * 2^16})
local head = mknodes(txt)
lang.hyphenate(head)
head = node.kerning(head)
head = node.ligaturing(head)
local vbox = tex.linebreak(head,{ hsize = tex.sp("3in")})
node.write(vbox)
\end{luacode*}
\end{document}
我手动创建字形节点,添加一些胶水并调用内部 TeX 函数进行连字、键名和断行。
结果是一个 vbox,我将其直接写入 TeX 的当前列表,但您可以用它做其他事情。
答案2
运行luatex
。它会创建一个文件 xlist2.nodes:
0 ->nil (hlist->nil)
8,6 -> 0 (whatsit->hlist)
0 -> 37 (hlist->glyph)
37"h" -> 37 (glyph->glyph)
37"e" -> 37 (glyph->glyph)
37"l" -> 37 (glyph->glyph)
37"l" -> 37 (glyph->glyph)
37"o" -> 10 (glyph->glue)
10 -> 37 (glue->glyph)
37"w" -> 11 (glyph->kern)
11 -> 37 (kern->glyph)
37"o" -> 37 (glyph->glyph)
37"r" -> 37 (glyph->glyph)
37"l" -> 37 (glyph->glyph)
37"d" -> 12 (glyph->penalty)
12 -> 10 (penalty->glue)
10 -> 10 (glue->glue)
10 ->nil (glue->nil)
\nopagenumbers
\begingroup
\catcode`\%=12
\directlua{local out=assert(io.open("xlist2.nodes","w"))
local n=node.types()
function printnode(head)
while head do
if head.id==8 then out:write(head.id..","..head.subtype)
else out:write(string.format("%3d",head.id))
end
if head.id==37 then
out:write("\string\"",string.char(head.char),"\string\" ->")
else out:write("\space\space\space\space->")
end
if head.next==nil then out:write("nil")
else out:write(string.format("%3d",head.next.id))
end
out:write(" ("..n[head.id].."->")
if head.next==nil then out:write("nil)\string\n")
else out:write(n[head.next.id]..")\string\n")
end
if head.id==0 or head.id==1 then printnode(head.head) end
head=head.next
end
return true
end
callback.register("post_linebreak_filter",printnode,"printnode")}
\endgroup
hello world
\bye