1

This is a LuaTeX node-library/ node-list question.

In the following code, I am trying to typeset a manually crafted nodelist. To add a line break, I add a penalty node, and that works fine (i.e. the line breaks at that point). But when I add a vertical glue/kern after the linebreak, it doesn't really add any vertical space. Where am I going wrong? What nodes are needed to tell the linebreak routine to add vertical space? Am expecting behavior similar to LaTeX's macros \vspace{25pt} or \\[25pt].

Sidenote lua questions: 1) Is the require way of loading the lua file ok? I mean, what if I have a local variable name clash with another lua file that I load with require, for instance variable local head? 2) Instead of making the functions global, is there a way to call them with something like linebreak. prefix to avoid name clash with other lua files that have functions with same name?

Update: The "Sidenote" questions above are now discussed as a separate question here, please ignore above "Sidenote lua questions" while reading this question as they are not relevant to it.

Screenshot (I expect additional 25pt or 50pt vertical space after first line): Screenshot of output

linebreak.tex:

>>> lualatex linebreak.tex
\documentclass{article}
\directlua{require"linebreak.lua"}
\begin{document}
\raggedright
\directlua{
    initialize()
    addchars('H')
    addchars('e')
    addchars('l')
    addchars('l')
    addchars('o')
    addchars(' ')
    addchars('W')
    addchars('o')
    addchars('r')
    addchars('l')
    addchars('d')
    addpenalty(1,-10000) % Add line break, works fine!
    addglue(1,25*65536) % XXX: Add vertical glue, doesn't work!
    addkern(1,25*65536) % XXX: Add vertical kern, doesn't work!
    addchars(' ')
    addchars('H')
    addchars('e')
    addchars('y')
    writeparagraph()
}
\end{document}

linebreak.lua:

local glyph_id = node.id("glyph")
local glue_id  = node.id("glue")
local kern_id  = node.id("kern")
local penalty_id = node.id("penalty")
local current_font = font.current()
local font_parameters = font.getfont(current_font).parameters
local n, head, last

function addchars(c)
  for s in string.utfvalues(c) do
    local char = unicode.utf8.char(s)
    if unicode.utf8.match(char,"%s") then
      -- its a space
      n = node.new(glue_id)
      n.width   = font_parameters.space
      n.shrink  = font_parameters.space_shrink
      n.stretch = font_parameters.space_stretch
    else -- a glyph
      n = node.new(glyph_id)
      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
  return last
end

function addpenalty(s,p)
    n = node.new(penalty_id)
    n.subtype = s or 0
    n.penalty = p or 0
    last.next = n
    last = n
    return n
end

function addkern(s,p)
    n = node.new(kern_id)
    n.subtype = s or 0
    n.kern = p or 0
    last.next = n
    last = n
    return n
end

function addglue(s,a,b,c,d,e)
    n = node.new(glue_id)
    n.subtype = s
    n.width = a or 0
    n.stretch = b or 0
    n.shrink = c or 0
    n.stretch_order = d or 0
    n.shrink_order = e or 0
    last.next = n
    last = n
    return n
end

function initialize()
    -- add parindent glue in the beginning, create head node
    head = node.new("glue")
    head.width = tex.parindent
    last = head
end

function writeparagraph()
    -- append a penalty and parfillskip glue at the end of list
    addpenalty(0,10000)
    addglue(15,0,tex.parfillskip.stretch,tex.parfillskip.stretch_order)
    -- create prev pointers
    node.slide(head)
    -- hyphenate
    lang.hyphenate(head)
    -- take care of fonts, kerning, ligature, etc
    head = nodes.simple_font_handler(head)
    -- call tex linebreaking, write to pdf
    tex.write(tex.linebreak(head))
    -- initize pointers
    head = nil
    last = nil
end
6
  • 1
    you can't add vertical glue in a horizontal list you need to put it in a vadjust node see the definition of \vspace Commented Nov 16, 2020 at 22:38
  • @DavidCarlisle Thanks, it worked! I posted the modified code based on your recommendation as answer below. Do you have any inputs on my lua sidenote questions?
    – codepoet
    Commented Nov 17, 2020 at 4:02
  • @reportaman I find it confusing to post a follow-up question as an answer. Could you post a new question or make it clear which part of the question have been resolved?
    – topskip
    Commented Nov 17, 2020 at 8:03
  • 1
    @reportaman Nice to see more people playing with node lists! Let's establish a "node list manipulator's club"
    – topskip
    Commented Nov 17, 2020 at 8:04
  • @reportaman the site format really doesn't support multiple questions in the same post: it's much better to ask new questions as new questions. Basically you should structure your file as a module, that is do not define lots of global names just return a table containing all your functions, then the caller can choose the top level name foo=require('yourfile') ... foo.myfun() ... Commented Nov 17, 2020 at 8:05

1 Answer 1

0

Based on David Carlisle's comment to my question, I added an adjust node with vertical glue in it. Now the output is as desired. Here's the screenshot and code:

Screenshot showing the output of modified code

modified linebreak.tex:

\documentclass{article}
\directlua{require"linebreak.lua"}
\begin{document}
\raggedright
\directlua{
    initialize()
    addchars('H')
    addchars('e')
    addchars('l')
    addchars('l')
    addchars('o')
    addchars(' ')
    addchars('W')
    addchars('o')
    addchars('r')
    addchars('l')
    addchars('d')
    addpenalty(1,-10000) % Add line break
    vspace(25*65536) % Add vspace
    addchars('H')
    addchars('e')
    addchars('y')
    writeparagraph()
}
\end{document}

modified linebreak.lua:

local glyph_id = node.id("glyph")
local glue_id  = node.id("glue")
local kern_id  = node.id("kern")
local penalty_id = node.id("penalty")
local adjust_id  = node.id("adjust")
local current_font = font.current()
local font_parameters = font.getfont(current_font).parameters
local n, head, last

function addchars(c)
  for s in string.utfvalues(c) do
    local char = unicode.utf8.char(s)
    if unicode.utf8.match(char,"%s") then
      -- its a space
      n = node.new(glue_id)
      n.width   = font_parameters.space
      n.shrink  = font_parameters.space_shrink
      n.stretch = font_parameters.space_stretch
    else -- a glyph
      n = node.new(glyph_id)
      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
  return last
end

function addpenalty(s,p)
    n = node.new(penalty_id)
    n.subtype = s or 0
    n.penalty = p or 0
    last.next = n
    last = n
    return n
end

function addkern(s,p)
    n = node.new(kern_id)
    n.subtype = s or 0
    n.kern = p or 0
    last.next = n
    last = n
    return n
end

function addadjust(s,h)
    n = node.new(adjust_id)
    n.subtype = s or 0
    n.head = h
    last.next = n
    last = n
    return n
end

function vspace(s)
    g1 = node.new(glue_id)
    g1.width = s
    g1.subtype = 0
    addadjust(1,g1)
end

function addglue(s,a,b,c,d,e)
    n = node.new(glue_id)
    n.subtype = s
    n.width = a or 0
    n.stretch = b or 0
    n.shrink = c or 0
    n.stretch_order = d or 0
    n.shrink_order = e or 0
    last.next = n
    last = n
    return n
end

function initialize()
    -- add parindent glue in the beginning, create head node
    head = node.new("glue")
    head.width = tex.parindent
    last = head
end

function writeparagraph()
    -- append a penalty and the parfillskip glue at the end of list
    addpenalty(0,10000)
    addglue(15,0,tex.parfillskip.stretch,tex.parfillskip.stretch_order)
    -- create prev pointers
    node.slide(head)
    -- hyphenate
    lang.hyphenate(head)
    -- take care of fonts, kerning, ligature, etc
    head = nodes.simple_font_handler(head)
    -- call linebreaking
    tex.write(tex.linebreak(head))
    -- initize pointers
    head = nil
    last = nil
end
2
  • the site format would be clearer if you removed the extra question (post as a new question) then self-accepted this answer to mark this question as done Commented Nov 17, 2020 at 8:40
  • @DavidCarlisle Done, here's the new question: tex.stackexchange.com/q/571204/192717
    – codepoet
    Commented Nov 17, 2020 at 8:51

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .