# Kerning against word space in LuaTeX

AFAIK word space in TeX work more or less like a glue instead of a real character. So it is not possible to kern a character with the space, e.g., to make the pair such as f<space>T to stay apart a little. pdfTeX used to have the ability to alter word space (though not exactly the same thing as kerning).

Is there a way to kern characters with word space in LuaTeX?

Update

I have figured out a way to do it. Please comment if there is any improvement can be made.

\documentclass{article}

\directlua{
for space in node.traverse_id(node.id('glue'), head) do
if space.subtype == 13 then % A spaceskip glue
local prev = node.prev(space)
local next = node.next(space)
% Check that the space is between two glyphs
if prev.id == 29 and next.id == 29 then
% Below we increase space by 100pt, demonstration purpose only
% In reality, we can adjust the space according to the prev and next
% In particular, e.g., adjust the skip such that its natural space
% will be increased by the kerning value between prev and next
space.width = space.width + 6553600
end
end
end
return true
end
}

\begin{document}

A V

\end{document}


Update 2 Below is a somewhat more complete, but bit tedious example, in case any one find it useful.

mclass.par.kern = {}
mclass.par.size = {}

for space in node.traverse_id(node.id('glue'), head) do
if space.subtype == 13 then -- spaceskip
local prev = node.prev(space)
local next = node.next(space)
if prev.id == 29 and next.id == 29 then -- glyphs
if prev.font == next.font then
if not mclass.par.kern[prev.font] then -- find and cache the kerning table of this font
local fnt = font.getfont(prev.font)
local seq = fnt.resources.sequences
mclass.par.size[prev.font] = fnt.size
for _,t in ipairs(seq) do
if t.features.kern then
mclass.par.kern[prev.font] = t.steps[1].coverage
end
end
end
if mclass.par.kern[prev.font][prev.char] then
local k = mclass.par.kern[prev.font][prev.char][next.char]
if k then
space.width = space.width + k / 1000 * mclass.par.size[prev.font]
end
end
print(space.width)
end
end
end
end
return true
end

"pre_linebreak_filter", mclass.par.space, "mclass_par_space");


The end result is that, as if all spaces are first removed, and glyphs properly kerned together (without ligature). And then a normal space is inserted

Below is an example of showing it works on Arno Pro

\definecolor{grey}{cmyk}{0,0,0,0.5}
\def\test#1{\leavevmode\hbox to 0pt{\color{grey}#1\hss}#1}

\begin{document}
\test{A V}\par
\test{A W}\par
\test{W A}\par
\test{V A}\par
\test{f H}\par
\end{document}


The main limitations are

1. It will not work inside boxes where no paragraph. It is added to the pre_linebreak_filter. However, it might be possible to add it as another call back to make it work in hbox, etc.
2. It will not check any additional kernings defined by users through the font loader. However, for well made fonts, I found the need for additional kerning is rare and usually are edge cases that do not involve spaces (such as a single quote followed by a double quote in Minion, which is not kerned).

The way I used to find the kerning table is tedious and look quite inefficient to me. If any one can help, many thanks in advance.

The LuaTeX document says that the font table returned by font.getfont shall have a characters table, which is there. And each character hash shall has a sub table names kerns. However, it appears not there, at least not for the OpenType fonts I tested with.

• you have tagged this in as luatex, and if you look for luatex specific solutions you could adjust that in several ways either doing a replacement of f T to f\myftspace T in the input buffer or adjusting the horizontal node list before line breaking or .. Mar 4, 2017 at 12:23
• Are you meaning f<space>T or fT? Mar 4, 2017 at 21:36
• @egreg I meant f<space>T Mar 5, 2017 at 7:35
• Please explain what you mean by "space" in f<space>T. Is it intra-word or inter-word whitespace? If it's inter-word whitespace, please explain what's unsatisfactory (in your view) about the space that gets inserted between "pdf" and "TeX" in "pdf TeX".
– Mico
Mar 5, 2017 at 7:57
• @Mico The object in a high level is to achieve visually even word space. For example, say of That will look spaced tighter than A Victor. It is similar to kerning except it applies to word space instead of tracking. Though your answer did not solve my problem, I think it is useful to may people. Examples of how to use call-back is sparse IMO. (However, for intra-word space in particular, I think an even easier way, and probably more appropriate, is to use font handler to define additional kerning table) Mar 5, 2017 at 14:07

[Caveat: I wrote this answer based on a misunderstanding of the OP's objective. I thought the objective was to insert a kern if (say) pT occur within a word (e.g., "pdfTeX"). However, this interpretation was wrong. The OP has asked me, though, not to delete this answer as it might still be of use to future readers who, in fact, do wish to influence intra-word kerning.]

Here's an implementation of David Carlisle's suggestion to set up a Lua function that provides automatic kerning of "fT" pairs and to assign the Lua function to the "process_input_buffer" callback. The code below sets up such a Lua function and provides LaTeX macros to activate and deactivate the Lua function.

\documentclass{article}
\newcommand{\fTkern}{\kern0.5pt} % choose a suitable kern
%% Lua-side code
\usepackage{luacode}
\begin{luacode}
function kernfT ( s )
return ( string.gsub ( s , "fT" , "f\\fTkern T" ) )
end
\end{luacode}
%% TeX-side code: macros to activate and deactivate the Lua function
\newcommand{\kernftOn}{\directlua{%
kernfT, "kernFT" )}}
\newcommand{\kernftOff}{\directlua{%
luatexbase.remove_from_callback ( "process_input_buffer" ,
"kernFT" )}}
\begin{document}
\kernftOn
pdfTeX
\kernftOff
pdfTeX
pdf\kern0.5ptTeX
\end{document}

• I'm not sure this is what the OP wants: note the question mentions “kerning against a space”. Mar 4, 2017 at 21:37
• @Mico is there a chance to adapt this feature and include it into selnolig to get the question tex.stackexchange.com/q/315123/112503 solved? (not every font offers for example a short armed f) Mar 4, 2017 at 21:45
• @egreg - I'm also not entirely sure what the OP wants, given that words "kern" and "space" are both used (even within a given sentence). Maybe "space" is used generically as "whitespace"? Let's see if the OP weighs in and clarifies the posting's objective.
– Mico
Mar 4, 2017 at 22:10
• @lAtExFaN - Nice to know that people care about selnolig. :-) Indeed, quite a few fonts have only a "long-armed" f glyph, which can tangle with subsequent characters. In my view, the need for a bit of whitespace (babel uses 0.03em, but that's often more than is needed) while breaking typographic ligatures arises primarily for a couple of f-ligatures -- f-l and f-i (but not for f-f; the famous "shelfful" case...) -- and not for most other ligatures (such as ct, st, sp, tt, th, as, es, is, us, et, at, ta, etc.). That's why I've so far been hesitant about inserting ... (continued)
– Mico
Mar 4, 2017 at 22:23
• @lAtExFaN - (continued) ... whitespace automatically. Assuming, though, that it had been decided that whitespace should be inserted, a second issue is: should the whitespace be breaking or nonbreaking? In the answer above, the hard kern is nonbreaking, i.e., TeX won't insert a hyphenation point at the kern. While this is quite likely OK for the case of "pdfTeX", it's not OK for German words in which an fl or an fi ligature needs to be broken up (e.g, "sträflich" and "Schilfinsel"). As you can see, though, I'm open to having a thorough discussion. Maybe send me an email with your thoughts?
– Mico
Mar 4, 2017 at 22:39

You could improve your code by using the kerning callback rather than the pre_linebreak_filter callback. As you observed, the pre_linebreak_filter callback does not run for \hboxes (which makes sense, because there are no lines to be broken). When using the kerning callback, however, you have to first call node.kerning on the node list to apply TeX's kerning.

Also I suggest that you use node.id and node.subtypes rather than hardcoding the magic numbers. In the past LuaTeX has changed those numbers around, so your code might break when you update.

test.lua

local mclass = { par = {} }

mclass.par.kern = {}
mclass.par.size = {}

local subtypes = node.subtypes("glue")

-- Apply TeX's kerning first

for space in node.traverse_id(node.id("glue"), head) do if subtypes[space.subtype] == "spaceskip" then -- spaceskip
local prv = node.prev(space)
local nxt = node.next(space)
if prv.id == node.id("glyph") and nxt.id == node.id("glyph") then -- glyphs
if prv.font == nxt.font then
if not mclass.par.kern[prv.font] then -- find and cache the kerning table of this font
local fnt = font.getfont(prv.font)
local seq = fnt.resources.sequences
mclass.par.size[prv.font] = fnt.size
for _,t in ipairs(seq) do
if t.features.kern then
mclass.par.kern[prv.font] = t.steps[1].coverage
end
end
end
if mclass.par.kern[prv.font][prv.char] then
local k = mclass.par.kern[prv.font][prv.char][nxt.char]
if k then
space.width = space.width + k / 1000 * mclass.par.size[prv.font]
end
end
print(space.width)
end
end
end
end
end


\documentclass{article}
\usepackage{xcolor}

\begin{document}
\pagestyle{empty}
\setbox0=\hbox to 0pt{\textcolor{gray}{A V}\hss}
\setbox1=\hbox to 0pt{\textcolor{gray}{A W}\hss}
\setbox2=\hbox to 0pt{\textcolor{gray}{W A}\hss}
\setbox3=\hbox to 0pt{\textcolor{gray}{V A}\hss}
\setbox4=\hbox to 0pt{\textcolor{gray}{f H}\hss}
%
\directlua{dofile("test.lua")}
%
\noindent\box0 A V\par
\noindent\box1 A W\par
\noindent\box2 W A\par
\noindent\box3 V A\par
\noindent\box4 f H\par
\end{document}