LuaTeX: visualizing interword glue calculations

Introduction

In this example we will use the “post_linebreak_filter” to iterate over all the lines in a typeset paragraph and use the data provided by LuaTeX to insert pdf_literal nodes which draw a box to show the size of the glue between words. It is based on a nice piece of code on the LuaTeX wiki by (I believe) Patrick Gundlach.

Note: The code does not recurse into nested lists; so if you have further boxes within the lines of your paragraph you should note that this code is not designed to handle them. An exercise for the reader ;-).

If you extend the code to deal with nested boxes, other glue types, such as \baselineskip etc, and account for glyph widths, heights and depths, what this leads to is the ability to calculate precise (x,y) positions within many types of node lists.

Code and explanations

Here, we are placing a paragraph into a \vbox{There are many variations of ….} and hooking into the post_linebreak_filter to access the node list after TeX has broken the paragraph into lines. Each line in the paragraph is accessible from LuaTeX as an hlist which is traversed using the LuaTeX API call node.traverse_id(…). We then scan through the nodes contained in each hlist, looking for glue. As mentioned, the code does not recurse so we are just choosing the individual paragraph lines and not exploring anything below that level.

Each hlist contains its glue setting parameters which let us calculate by how much the individual glue items are stretched or shrunk within each line of the paragraph. From those values you can calculate the size of the set glue and draw an appropriately sized box with a pdf_literal, whose zero size does not affect the positioning of the text. The pdf_literal is inserted into the hlist node tree just before each glue node.

For further reading on TeX’s glue calculations see page 77 of The TeXBook.

\pdfoutput=1
\hoffset-1in
\voffset-1in
\nopagenumbers
\directlua{

function scanskips(hlist)

local size=0
% Some debug output
print(string.char(10), "glue_order = "..hlist.glue_order, "glue_set = "..hlist.glue_set, "glue_sign = "..hlist.glue_sign)

id = node.id("glue")

for n in node.traverse_id(id, hlist.head) do 
% Some more debug output
          print("subtype="..n.subtype, "width: ", n.spec.width,  "stretch: ", n.spec.stretch,  "shrink: ", 
                  n.spec.shrink, "stretch order: ", n.spec.stretch_order, 
                  "shrink order: ", n.spec.shrink_order, string.char(10)) 

          order=hlist.glue_order
          set=hlist.glue_set
          sign=hlist.glue_sign

          if sign ==2 then 
                size=n.spec.width - n.spec.shrink*set
          else
                 size=n.spec.width + n.spec.stretch*set
          end

          bp=(size/65536)*(72.0/72.27)

          local pdf = node.new("whatsit","pdf_literal")
          pdf.mode = 0
          pdf.data = "q 0 0 "..bp.." 12 re 0.3 w S Q"
          local prev = n.prev
          prev.next=pdf
          pdf.next = n

      end %for loop
end % function
}

\directlua{
          linelist=function(head)
                    for line in node.traverse_id('hlist',head) do
                              scanskips(line)
                    end
          return head
          end

          callback.register("post_linebreak_filter",linelist)
}
 
\setbox1000=\vbox{\hsize=50mm There are many variations of passages of Lorem Ipsum available, but the majority have suffered alteration in some form, by injected humour, or randomised words which don't look even slightly believable. If you are going to use a passage of Lorem Ipsum, you need to be sure there isn't anything embarrassing hidden in the middle of text. All the Lorem Ipsum generators on the Internet tend to repeat predefined chunks as necessary, making this the first true generator on the Internet. It uses a dictionary of over 200 Latin words, combined with a handful of model sentence structures, to generate Lorem Ipsum which looks reasonable. The generated Lorem Ipsum is therefore always free from repetition, injected humour, or non-characteristic words etc.}
\pdfpagewidth=\wd1000
\vsize=\ht1000
\advance\vsize by 65536 sp
\pdfpageheight=\vsize
\box1000
\bye

Resulting PDF

As usual, through the Google Docs viewer or download here.