2

I have been using LuaTeX recently and have thoroughly enjoyed typesetting my documents with it. There are still a few issues though I am trying to solve though. One of the issues is introducing page references so that a document can reference a page number and so forth.

I am already very familiar with the 2-pass concept in which on one scan of the document, you have TeX write to an auxiliary file or table of contents file, then on the next scan it can actually use the page numbers from said file to add references to the doc. I still intend to do this, but now I want to accomplish it with Lua as part of LuaTex.

Attempt 1

As a first pass I thought I could just reference the 0 counter since TeX holds the page number in that. Something like

\def\MakePageReference#1{ % #1 is the name of the reference we wish to track \directlua{ tex.count[0] -- use this value to get the page number and write it to a file (aux/toc file) } } \def\PlaceReference#1{ % #1 the name of the page reference we created % do something in here to get the page number of said reference } 

However this doesn't work too well after learning about how LuaTex executes its code alongside the TeX engine. For starters, it appears that LuaTeX will start expanding the directlua calls immediately which means in the same Lua body, the page number will never be updated. I have to do some kind of switching back and forth between TeX and Lua and that led me down this coroutine rabbit hole: Concurrently interleaving execution of Lua and TeX in LuaTeX.

Attempt 2

Since TeX only knows the page number for some material during the shipout process, I thought I could maybe take advantage of some of LuaTeX's callbacks.

I could create a whatsit Lua node every time I introduce a page reference and then hook into the pre_output_filter that LuaTeX provided. Something similar to the following (some of this will be pseudo code since I am forgetting the syntax):

\def\MakePageRef#1{ #1 % Output the material as normal, such as word \directlua{ new_node = node.new("whatsit", "user_defined") new_node.value = "#1" -- give the node the value of the text node.write(new_node) } } \directlua{ function page_output_callback(head, ...other params...) -- loop through each node of head and if the type is whatsit -- get the page number with tex.count[0] and write this to a file end tex.register_callback("pre_output_filter", page_output_callback) } 

However I still get stumped on this since as I iterate through the nodes of head in my callback function, my whatsit nodes do not appear in the list.

If anyone has more experience on this or could mention a better way of handling pagerefs in LuaTeX, I would greatly appreciate it.

Edit 1

Added a MWE for the pre_output_filter attempt I described

-- helper.lua function create_node_reference(name) whatsit_id = node.id("whatsit") user_define_id = node.subtype("user_defined") local new_node = node.new(whatsit_id, user_define_id) new_node.type = 115 -- internal code for a string value new_node.value = name node.write(new_node) end function print_whatsit_nodes_page(head, groupcode, size, packtype, maxdepth) -- traverse_id will only iterate over nodes with the specified id, in this case "whatsit" -- There seems to be no whatsit nodes though in this list for n in node.traverse_id(node.id("whatsit"), head) do print(n) end end callback.register("pre_output_filter", print_whatsit_nodes_page) 

Main tex file

\directlua{ dofile("helper.lua") } \def\CreateRef#1{ \directlua{ create_node_reference("#1") } } \CreateRef{Test here on page 1} \vfil \eject \CreateRef{Something on page 2} \vfil \eject 

Edit 2

After a discovery it appears that if there is text (or horizontal material?) right before the directlua call, the node.write appears to not work.

\def\CreateRef#1{ Some text here \directlua{ create_node_reference("#1") } } 
16
  • 2
    if you are just doing this as a learning exercise and don't plan to use it in real documents, well and good. if you want to actually use this in real documents, I think 'letting TeX handle outputting page numbers is the best way'. not because it is necessarily better but because TeX is already doing it anyway, so you are basically setting up a parallel system for cross-referencing. could you say what the motivation for this is? what advantage are you hoping such an alternative system will provide? Commented Feb 1 at 4:27
  • 1
    @cfr Thanks for your response! As you guessed, this is mainly for a learning exercise. Well mostly, I do enjoy trying to build my tools from the basics out of enjoyment/foolishness. My first motivation was to understand, how does LaTeX's pageref, ref, and label actually work. The next thought was, how can I accomplish this in LuaTeX? I have seen how to do it in just "plain" TeX by seeing how the eplain tex package works. For my own curiosity, I wanted to see if Lua could handle the responsibility/logic for creating page references. It seems rather difficult to do so from my findings. Commented Feb 1 at 5:06
  • 1
    pre_output_filter is the wrong place for this since LaTeX calls the output routine every time that you add a footnote or figure. Instead, you probably want to use pre_shipout_filter. You should probably also look into \latelua, which is like \directlua except that it isn't executed until shipout. Commented Feb 1 at 5:20
  • 1
    And the whatsit nodes should be appearing somewhere inside head, although you'll likely need to recursively traverse the list (function f(n) for m in node.traverse(n) do if m.head then f(m) end end f(head)) to find them. Commented Feb 1 at 5:25
  • 1
    @Mico three back ticks followed by lua and three to close (on their own lines) also works. Commented Feb 1 at 7:32

1 Answer 1

2

Nodes in LuaTeX can be deeply nested within each other, but node.traverse only iterates through the uppermost level. So in the example below:

\directlua{ function create_node_reference(name) whatsit_id = node.id("whatsit") user_define_id = node.subtype("user_defined") local new_node = node.new(whatsit_id, user_define_id) new_node.type = 115 --[[ internal code for a string value ]] new_node.value = name node.write(new_node) end local function traverse_whatsits(head) for n in node.traverse(head) do if n.id == node.id("whatsit") then print(n) elseif n.head then traverse_whatsits(n.head) end end end callback.register("pre_output_filter", traverse_whatsits) } \def\CreateRef#1{ \directlua{ create_node_reference("#1") } } \CreateRef{Page 1} \vfil \eject line before\par \CreateRef{Page 2} \par line after\par \vfil \eject preceding text \CreateRef{Page 3} \vfil \eject \bye 

page 2 looks like

hlist -------------------------------- whatsit --- hlist --- ... | | local_par --- glyph --- glyph --- ... local_par --- ... 

while page 3 looks like

hlist --------------------------------- hlist --- ... | | local_par --- glyph --- whatsit --- ... local_par --- glyph --- whatsit --- ... 

In your original code, you were only traversing through the topmost level of the main vertical list, so it worked for the case where the whatsits were inserted between paragraphs, but not for the case where the whatsits were inserted inside of a paragraph. To fix this, you need to recursively iterate through all of the possible (h|v)lists, as shown in my code above.

3
  • Excellent answer! Your diagram made this idea crystal clear. Thanks for all the help. As one final question, do you know of any good sources (or any LuaTeX Manual references) I can use to learn more about this? I want to understand better the node structures, and where nodes get placed (i.e. at the top level or some nested layer). Commented Feb 4 at 8:25
  • 1
    @nick2225 The LuaTeX manual is okay (but not great as you've already seen). The cld manual is also pretty helpful; despite only mentioning ConTeXt, about 90% of it is applicable to Plain TeX/LaTeX, as long as you load luaotfload first. There are also a few good TUGboat articles; searching the list of titles for “LuaTeX” should find most of the relevant ones. Otherwise, there really isn't much good documentation for LuaTeX, unfortunately. Commented Feb 4 at 8:52
  • 1
    I'll have to give those TUGBoat articles a chance. There seems to be a lot of neat and "hidden" features in LuaTeX that are not documented super well. I anticipate that learning about these esoteric capabilities will be like pulling teeth. I appreciate your help, and I'm sure you will see more LuaTeX/TeX related questions from me in the future. Commented Feb 4 at 9:14

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.