# LuaTeX node library examples: like turn raggedright lines to raggedleft, and more

In addition to posting the right answer for the specific example in my question, please feel free to leave short snippets of code that display capabilities of node library

Am new to LuaTeX node library and learning how it can be utilized to post-process lines using post_linebreak_filter. So far I am able to do very basic maneuvering, like in the answer to my own question here. Next I tried to traverse over hlist nodes with the hope to change their leftskip/rightskip and to convert raggedright text to raggedleft. My trail-and-error didn't work, am posting my incorrect attempts below (Caution: Attempt-1 seems to enter infinite loop, so don't try it in your main terminal). Can someone please explain what is wrong with these, and show how the correct code would look like (with some explanation)?

% Attempt-1: Set leftskip/rightskip outside, and rebox the contents using hpack?
\directlua{
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)
node.write(b)
end
end
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{
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
end
luatexbase.add_to_callback('post_linebreak_filter', my_post_lb_filter, 'Play with luatex node library')
}


A comment about Attempt-2: In my observation from internals of hlist nodes, raggedright lines lack 'leftskip' subnode inside them. So how could one add leftskip subnode to such a line? While raggedleft lines have both 'leftskip' & 'rightskip' subnodes within them, so it is just a matter of updating them I guess.

• In the post_linebreak_filter the glue has already been set. You have to remove it from the end of the hlist and put it in the beginning. Apr 16, 2020 at 11:19
• The node.write in your first example will result in an infinite loop. Never use node.write unless you know exactly that the generated node is going to end up in the correct place. Apr 16, 2020 at 11:21

First let's look at your first attempt:

You used

            tex.setglue("rightskip",0,0,0,2,2)
tex.setglue("leftskip",0,65536,0,2,2)
local b = node.copy(n)


Here node.hpack has been used to repack in order to pick up the changed rightskip/leftskip settings, but that is not how TeX works: rightskip and leftskip are not applied as part of packing the hbox, they are applied during linebreaking. So for this approach you would have to do use the linebreak callback and change the parameter there.

Also using node.write is almost always going to lead to problems in a callback. That function is adding a node to the current list TeX is working on, this will sometimes not be the list you think TeX is working on. Instead, try to use the list passed as head. In this case this happens to be the same list, but you are copying hlist nodes and append them at the end. So after the regular hbox nodes are processed, the former end of the list is no longer the end but followed by your copied nodes. Then these copied nodes are processed, creating even more copies. This is causing the infinite loop.

So the second approach is more promising. Your code already is quite close, the remaining steps are:

• node.subtype only works for whatsit nodes, for other nodes you either have to hardcode the values or analyze the table returned by node.subtypes.
• We have to traverse n.head instead of n, otherwise you never look into the actual content of the lists.
• We have to ensure that a leftskip glue actually exists. We could use the guarantee that a leftskip glue will always be the first node in the hlist, but it is a bit safer to just remember while iterating if we already had a leftskip. (This way we don't get problems if another package adds some earlier node).

To actually create the node, we can use node.new and then insert it into the list with node.insert_before.

• A little simplification is using node.traverse_id instead of node.traverse here. Then you need less if statements and it is also a little bit faster:
\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
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
end
end
end
luatexbase.add_to_callback('post_linebreak_filter', my_post_lb_filter, 'Play with luatex node library')
}
\begin{document}
\showoutput
\raggedright

\blindtext
\end{document}


You might notice a little exercise left for the reader: The last line is currently centered. Explain why this happens and figure out how to fix it.

• local gluetypes = table.swapped(node.subtypes("glue")); local RKSIP, LSKIP = gluetypes.rightskip, gluetypes.leftskip Apr 17, 2020 at 3:54
• @MarcelKrüger Thanks a lot. \showoutput shows that there is a \glue 0.0 plus 1.0fil at the end of last line. It doesn't say what type of glue it is though? I don't see it in the hlist of last line visualized using topskip's viznodelist (gist.github.com/pgundlach/556247/…) If it not a bug there, then I would think this unnamed glue is being placed outside the last hlist? My question is what is it? I did nullify its effect by setting leftskip to node.setglue(g,0,2*65536,0,2,0) only for last line, though still don't know what's going on there. Apr 17, 2020 at 3:55
• @reportaman It's \parfillskip (sorry for spoiling, Marcel). Apr 17, 2020 at 3:58
• @reportaman No, I mean the \glue 0.0 plus 1.0fil is vertical glue in the sense that it creates a vertical distance. This is unrelated to horizontal positioning. Apr 17, 2020 at 4:30
• That the box is still centered after you removed the parfillskip is because the box was not packed again, so the glue hasn't been reset. The leftskip doesn't "know" yet that it has to stretch more. Apr 17, 2020 at 4:32