Some time ago I tried to implement the \pdfadjustinterwordglue from pdfTeX in LuaTeX using a callback. Getting the callback to work is pretty simple, but there is another major problem in the way which I was so far not able to solve. The culprit is in the way parameters for spacing are set, which is for example
\knbscode\font`r=1100
This is a regular TeX assignment and this can be implemented in Lua using
\def\knbscode{\directlua{
local fid = token.scan_int()
local chr = token.scan_int()
local str = token.scan_string()
knbscode[fid] = knbscode[fid] or {}
knbscode[fid][chr] = knbscode[fid][chr] or {}
local int = string.gsub(str,"=","")
knbscode[fid][chr][field] = tonumber(int)
}\fontid}
That is to say, I look ahead for an integer, which is the font ID, another integer which is a character, and “string”, which according the LuaTeX manual is
returns a string given between
{}, as\macroor as sequence of characters with catcode 11 or 12
The mindful reader might already notice a problem here because \knbscode\font`r=1100abc should parse by the normal TeX rules but will fail in this case. Luckily the usage of register assignment is sort of regular in TeX and such a situation rarely occurs.
The next big problem is that registers are not write-only. They can also be read using:
\the\knbscode\font`r
Of course, in this case, \knbscode should still scan for the font ID and the character but not for the assignment. My idea to solve this was to overload \the to peek at the next token and if it is \knbscode, delegate to another function. On the Lua end this looks like this
local t = lua.get_functions_table()
t[1] = function()
local next = token.get_next()
if next.csname == "knbscode" then
token.put_next(token.create("theknbscode"))
elseif next.csname == "stbscode" then
token.put_next(token.create("thestbscode"))
elseif next.csname == "shbscode" then
token.put_next(token.create("theshbscode"))
else
token.put_next{ token.create("normalthe"), next }
end
end
and on the TeX end I define \the to refer to this Lua function
\luadef\the1
such that it can expand within a single step. Except that it doesn't. My new \the does not expand within a single step because instead of putting the number from the register in the input stream I put a token which has to be expanded again.
Maybe this approach is just wrong altogether, so the question is relatively general:
How do I define a new TeX register with arguments in LuaTeX?
I actually have asked the same question some time ago on the LuaTeX mailing list with a simplified example, so I just got the answer: “well, use a normal register, duh” (https://tug.org/pipermail/luatex/2019-January/007040.html).
Below I have a full MWE with which you can play around.
\documentclass{article}
\usepackage{luacode}
\newcount\pdfadjustinterwordglue
\begin{luacode}
local subtypes = node.subtypes("glue")
local knbscode = {}
local t = lua.get_functions_table()
t[1] = function()
local next = token.get_next()
if next.csname == "knbscode" then
token.put_next(token.create("theknbscode"))
elseif next.csname == "stbscode" then
token.put_next(token.create("thestbscode"))
elseif next.csname == "shbscode" then
token.put_next(token.create("theshbscode"))
else
token.put_next{ token.create("normalthe"), next }
end
end
function set_knbscode(field)
local fid = token.scan_int()
local chr = token.scan_int()
local str = token.scan_string()
knbscode[fid] = knbscode[fid] or {}
knbscode[fid][chr] = knbscode[fid][chr] or {}
local int = string.gsub(str,"=","")
knbscode[fid][chr][field] = tonumber(int)
end
function get_knbscode(field)
local fid = token.scan_int()
local chr = token.scan_int()
knbscode[fid] = knbscode[fid] or {}
knbscode[fid][chr] = knbscode[fid][chr] or {}
tex.sprint([[\numexpr]] .. (knbscode[fid][chr][field] or 0) .. [[\relax]])
end
local microtype_spacing = function(head, tail)
head, tail, success = node.kerning(head, tail)
if tex.count.pdfadjustinterwordglue > 0 then
for space in node.traverse_id(node.id("glue"), head) do
if subtypes[space.subtype] == "spaceskip" then
local prev = node.prev(space)
if prev.id == node.id("glyph") then
local knbs = knbscode[prev.font]
if knbs and knbs[prev.char] then
local f = font.getfont(prev.font)
local em = f.parameters.quad
local width = knbs[prev.char].width or 0
local stretch = knbs[prev.char].stretch or 0
local shrink = knbs[prev.char].shrink or 0
local glue = node.new(node.id("glue"))
glue.width = width*em/1000
glue.stretch = stretch*em/1000
glue.shrink = shrink*em/1000
head = node.insert_before(head, space, glue)
end
end
end
end
end
end
luatexbase.add_to_callback("kerning", microtype_spacing, "microtype_spacing")
\end{luacode}
\protected\def\knbscode{\directlua{set_knbscode("width")}\fontid}
\protected\def\stbscode{\directlua{set_knbscode("stretch")}\fontid}
\protected\def\shbscode{\directlua{set_knbscode("shrink")}\fontid}
\let\normalthe\the
\def\theknbscode{\normalthe\directlua{get_knbscode("width")}\fontid}
\def\thestbscode{\normalthe\directlua{get_knbscode("stretch")}\fontid}
\def\theshbscode{\normalthe\directlua{get_knbscode("shrink")}\fontid}
\luadef\the1
\pdfadjustinterwordglue=1
% Bogus values, just for demonstration
\knbscode\font`r=1100
\stbscode\font`r=10
\shbscode\font`r=10
\knbscode\font`r 1100
\stbscode\font`r 10
\shbscode\font`r 10
\begin{document}
\the\knbscode\font`r
\the\stbscode\font`r
\the\shbscode\font`r
\input lorem
\end{document}