The issue is that I have a font that has 9 different glyphs per characters, glyphs called e.g. a, a.2, … a.8 for the lowercase a; and the glyphs look similar per character and have the identical metrics. Those glyphs are rotated cyclically via the GSUB font table and there is a large kerning table for pairs for all glyphs. This works fine in most places today, including in LuaLaTeX with HarfBuzz renderer and in XeLaTeX, but it is not fully supported with LuaLaTeX with the default Node renderer, there kerning is only correct in part of the cases.
Since it looks like it would not be practical to fix this in the source codes, see this luaoftload github issue of mine from 2021, I was wondering if I could maybe find a workaround using Lua hooks, but have not found out so far if there would be such a hook.
Specifically, I know that kerning works fine if I reduce the font to just use one glyph per character, let me call that font "A". The idea would now be to use that font via fontspec and use a Lua hook late in the process, when "typesetting" has already happened (kerning, hyphenation, etc.), i.e. when it has been determined where each glyph will be placed on the output and then replace the glyphs with pseudo-randomly one from the full font "B", e.g. replace glyph a from font A with a.5 from font B.
Is there such a hook, or would anybody know of a different way achieving this in LuaLaTeX (with Node renderer)?
I guess, if not possible, it would in principle be possible to do this afterwards in the resulting pdf, and I could probably figure out how to do this (or ask elsewhere), but that would not be ideal, also because it would be two steps, e.g. not visible in TexShop while writing.
Added same day:
Here is a specific example.
The font with the GSUB rotation is Jackwrite.ttf and I have created a variant with just a single glyph per character, see links in the example. While I am at it, I am also showing an example with the opposite, a font Stoicheoin that has two chars per glyph, uppercase A and lowercase a for glyph A and also there I created a variant with one glyph (duplicated) for each char.
I have also added the output of the nodetree package for the Jackwrite case...
% !TEX TS-program = lualatex
\documentclass{article}
\usepackage{fontspec}
\usepackage{nodetree}
% https://jack-daw.com/fonts/stoicheion.zip => Stoicheion.ttf
\newfontfamily\stoicheion{Stoicheion.ttf}
% % https://jack-daw.com/fonts/StoicheionSingleCharPerGlyphDoNotDistribute.ttf
\newfontfamily\stoicheionsimple{StoicheionSingleCharPerGlyphDoNotDistribute.ttf}
% https://jack-daw.com/fonts/jackwrite.zip => Jackwrite.ttf
\newfontfamily\jackwrite{Jackwrite.ttf}
% https://jack-daw.com/fonts/JackwriteSingleGlyphPerCharDoNotDistribute.ttf
\newfontfamily\jackwritesimple{JackwriteSingleGlyphPerCharDoNotDistribute.ttf}
\begin{document}
\Large
\section*{\stoicheion{Full Featured Fonts Yes}}
\jackwrite{iiiiiiiii}
\section*{\stoicheionsimple{Full Featured Fonts No}}
\jackwritesimple{iiiiiiiii}
\end{document}
And here the output of fontree, first for the simpler font because there the result seems to be simpler (Jackwrite case, the 9 i's):
├─GLUE (baselineskip) wd 9.26pt
└─HLIST (line) wd 345pt, dp 0.33pt, ht 8.41pt
╚═head
├─LOCAL_PAR
├─GLYPH (glyph) 'i', font 30, wd 7.88pt, ht 8.41pt, dp 0.33pt
├─KERN (fontkern) -2.88pt
├─GLYPH (glyph) 'i', font 30, wd 7.88pt, ht 8.41pt, dp 0.33pt
│ ╚═ props {['injections'] = {['leftkern'] = -188743.6}}
├─DISC (regular) penalty 50
│ ╠═replace
│ ║ └─KERN (fontkern) -2.88pt
│ ╚═pre
│ ├─KERN (fontkern) -1.44pt
│ └─GLYPH (glyph) '-', font 30, wd 7.88pt, ht 3.61pt
│ props {['preinjections'] = {['leftkern'] = -94371.8}}
├─GLYPH (glyph) 'i', font 30, wd 7.88pt, ht 8.41pt, dp 0.33pt
│ ╚═ props {['emptyinjections'] = {['leftkern'] = -188743.6}}
├─DISC (regular) penalty 50
│ ╠═replace
│ ║ └─KERN (fontkern) -2.88pt
│ ╚═pre
│ ├─KERN (fontkern) -1.44pt
│ └─GLYPH (glyph) '-', font 30, wd 7.88pt, ht 3.61pt
│ props {['preinjections'] = {['leftkern'] = -94371.8}}
├─GLYPH (glyph) 'i', font 30, wd 7.88pt, ht 8.41pt, dp 0.33pt
│ ╚═ props {['emptyinjections'] = {['leftkern'] = -188743.6}}
├─DISC (regular) penalty 50
│ ╠═replace
│ ║ └─KERN (fontkern) -2.88pt
│ ╚═pre
│ ├─KERN (fontkern) -1.44pt
│ └─GLYPH (glyph) '-', font 30, wd 7.88pt, ht 3.61pt
│ props {['preinjections'] = {['leftkern'] = -94371.8}}
├─GLYPH (glyph) 'i', font 30, wd 7.88pt, ht 8.41pt, dp 0.33pt
│ ╚═ props {['emptyinjections'] = {['leftkern'] = -188743.6}}
├─DISC (regular) penalty 50
│ ╠═replace
│ ║ └─KERN (fontkern) -2.88pt
│ ╚═pre
│ ├─KERN (fontkern) -1.44pt
│ └─GLYPH (glyph) '-', font 30, wd 7.88pt, ht 3.61pt
│ props {['preinjections'] = {['leftkern'] = -94371.8}}
├─GLYPH (glyph) 'i', font 30, wd 7.88pt, ht 8.41pt, dp 0.33pt
│ ╚═ props {['emptyinjections'] = {['leftkern'] = -188743.6}}
├─DISC (regular) penalty 50
│ ╠═replace
│ ║ └─KERN (fontkern) -2.88pt
│ ╚═pre
│ ├─KERN (fontkern) -1.44pt
│ └─GLYPH (glyph) '-', font 30, wd 7.88pt, ht 3.61pt
│ props {['preinjections'] = {['leftkern'] = -94371.8}}
├─GLYPH (glyph) 'i', font 30, wd 7.88pt, ht 8.41pt, dp 0.33pt
│ ╚═ props {['emptyinjections'] = {['leftkern'] = -188743.6}}
├─KERN (fontkern) -2.88pt
├─GLYPH (glyph) 'i', font 30, wd 7.88pt, ht 8.41pt, dp 0.33pt
│ ╚═ props {['injections'] = {['leftkern'] = -188743.6}}
├─KERN (fontkern) -2.88pt
├─GLYPH (glyph) 'i', font 30, wd 7.88pt, ht 8.41pt, dp 0.33pt
│ ╚═ props {['injections'] = {['leftkern'] = -188743.6}}
├─PENALTY (linepenalty) 10000
├─GLUE (parfillskip) plus +1fil
└─GLUE (rightskip)
Looks like it would simply allow to hyphenate anywhere except between first and last pairs of "ii", which makes sense.
And here the output with the regular font with GSUB rotation (again Jackwrite case, the 9 i's):
├─GLUE (baselineskip) wd 9.27pt
└─HLIST (line) wd 345pt, dp 0.42pt, ht 8.42pt
╚═head
├─LOCAL_PAR
├─GLYPH (glyph) 'i', font 28, wd 7.88pt, ht 8.41pt, dp 0.33pt
├─KERN (fontkern) -2.88pt
├─GLYPH (glyph) '', font 28, wd 7.88pt, ht 8.32pt, dp 0.42pt
│ ╚═ props {['injections'] = {['leftkern'] = -188743.6}}
├─KERN (fontkern) -2.88pt
├─GLYPH (glyph) '', font 28, wd 7.88pt, ht 8.37pt, dp 0.35pt
│ ╚═ props {['injections'] = {['leftkern'] = -188743.6}}
├─KERN (fontkern) -2.88pt
├─GLYPH (glyph) '', font 28, wd 7.88pt, ht 8.32pt, dp 0.33pt
│ ╚═ props {['injections'] = {['leftkern'] = -188743.6}}
├─KERN (fontkern) -2.88pt
├─GLYPH (glyph) '', font 28, wd 7.88pt, ht 8.41pt, dp 0.36pt
│ ╚═ props {['injections'] = {['leftkern'] = -188743.6}}
├─KERN (fontkern) -2.88pt
├─GLYPH (glyph) '', font 28, wd 7.88pt, ht 8.32pt, dp 0.35pt
│ ╚═ props {['injections'] = {['leftkern'] = -188743.6}}
├─DISC (regular) penalty 50
│ ╠═replace
│ ║ ├─KERN (fontkern) -2.88pt
│ ║ ├─GLYPH (glyph) '', font 28, wd 7.88pt, ht 8.42pt, dp 0.39pt
│ ║ │ props {['replaceinjections'] = {['leftkern'] = -188743.6}}
│ ║ ├─GLYPH (glyph) 'i', font 28, wd 7.88pt, ht 8.41pt, dp 0.33pt
│ ║ ├─KERN (fontkern) -2.88pt
│ ║ └─GLYPH (glyph) '', font 28, wd 7.88pt, ht 8.32pt, dp 0.42pt
│ ║ props {['injections'] = {['leftkern'] = -188743.6}}
│ ╠═post
│ ║ ├─GLYPH (glyph) 'i', font 28, wd 7.88pt, ht 8.41pt, dp 0.33pt
│ ║ ├─KERN (fontkern) -2.88pt
│ ║ ├─GLYPH (glyph) '', font 28, wd 7.88pt, ht 8.32pt, dp 0.42pt
│ ║ │ props {['injections'] = {['leftkern'] = -188743.6}}
│ ║ └─GLYPH (glyph) 'i', font 28, wd 7.88pt, ht 8.41pt, dp 0.33pt
│ ╚═pre
│ └─GLYPH (glyph) '', font 28, wd 7.88pt, ht 3.6pt
├─PENALTY (linepenalty) 10000
├─GLUE (parfillskip) plus +1fil
└─GLUE (rightskip)
I do not really understand what it does, but looks like I if I just used the simpler font and found out how to replace the GLYPH settings with the desired glyph (identify it how exactly?) and font 28 instead of 30 (get these numbers from where?), this could yield the desired result…
Added same day as asked:
Based on Max Cherkoff's answer I managed to make a quick demo that his answer would also work in my case. I am just replacing one of the rs in the example with one with a more prominent "typewriter" glitch, using for the demo just the numbers of the fonts and the glyphs, but would certainly also work along what Max did:
% !TEX TS-program = lualatex
\documentclass{article}
\usepackage{fontspec}
\usepackage{luacode}
\begin{luacode}
local function recurse(head, indent)
for n in node.traverse(head) do
print(string.rep("-", indent) .. ">>")
if n.id == node.id("glyph") then
print(string.rep("-", indent) .. "char " .. n.char .. " of font " .. n.font)
if n.char == 114 and n.font == 23 then
n.char = 983520
n.font = 22
end
elseif n.head or n.replace then
if n.head then
n.head = recurse(n.head, indent+2)
end
if n.replace then
if n.pre then
n.pre = recurse(n.pre, indent+2)
end
if n.post then
n.post = recurse(n.post, indent+2)
end
n.replace = recurse(n.replace, indent+2)
end
end
print(string.rep("-", indent) .. "<<")
end
return head
end
local function show_fonts_and_chars(head)
print()
print("show fonts and chars...")
return recurse(head, 0)
end
luatexbase.add_to_callback("post_linebreak_filter", show_fonts_and_chars, "demo")
\end{luacode}
% https://jack-daw.com/fonts/jackwrite.zip => Jackwrite.ttf
\newfontfamily\jackwrite{Jackwrite.ttf}
% https://jack-daw.com/fonts/JackwriteSingleGlyphPerCharDoNotDistribute.ttf
\newfontfamily\jackwritesimple{JackwriteSingleGlyphPerCharDoNotDistribute.ttf}
\begin{document}
\Huge
\jackwrite{iiiiiiiiii rrrrrrrrrr}
\jackwritesimple{iiiiiiiiii rrrrrrrrrr}
\end{document}
Output:







post_par_filter, while it seems to bepost_linebreak_filter, but seems to have gotten the basic code structure otherwise right and helpful, will see...