我可以在 lua 中完全根据某些文本创建节点列表吗?

我可以在 lua 中完全根据某些文本创建节点列表吗?

我正在使用 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

相关内容