# Can I create a node list from some text entirely within lua?

I'm playing around with luatex and learning about nodes and callbacks. What I'd like to do is be able to add some text via a particular callback. The type of callback that I'm interested in is one where the input is a node list (think hpack_filter) so I want to append the text in the form of a node list. This is simple enough if I have my text to append already stored as a node list (say stuff):

function appendStuff(h)
local l = node.tail(h)
l.next = node.copy_list(stuff)
return h
end


The difficulty is in populating that node list in the first place.

What I'd like to do (because I'm lazy) is to specify a string, say hello world, pass it to TeX to convert it to a node list, and then save the resulting node list. I've figured out how to do this from the TeX side, see code below. What I'd like to do is do it entirely from the lua side. I realise that TeX will have to be involved at some point, but is there a proper way to do this? At the moment, my best guess is to have lua issue the relevant TeX commands via a tex.print(). Using hpack_filter as my callback (is there a better one?) I would have lua install the callback, issue the TeX command to create a box, then uninstall the callback (and, for cleanliness, destroy the box). That just feels cludgey. Is there a better way to accomplish this? If it helps, I can arrange it so that my strings don't need expansion (though, of course, it would be better if that was allowed).

Here's some code to play with:

\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}


(This is taken from the LuaTeX wiki and updated for LuaTeX shipped with tl2016/17/18/...?)

This look difficult, but it isn't. Well, it is, I don't admit it.

\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
-- we should insert the paragraph indentation at the beginning
head = newglue({width = 20 * 2^16})

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
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 vbox = tex.linebreak(head,{ hsize = tex.sp("3in")})
node.write(vbox)
\end{luacode*}
\end{document}


I create glyph nodes manually, add some glue and call the internal TeX functions for ligaturing, keming and linebreaking.

The result is a vbox which I write directly into TeX's current list, but you could do other things with it.

run with luatex. It creates a file 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()
end
else out:write("\space\space\space\space->")
end
end

• no, it is only an example how one can get the nodes. Instead of writing it into an external file you can use tex.print` to write it into TeX's source. – user2478 May 16 '13 at 15:17