# Underline part of a word while preserving kerning

The usual ways of underlining text (or at least the ones I know of: be it with \underline or e.g. the soul package) break kerning when used with part of a word. My current solution consists of a two-step process

1. I type the letters to be underlined (this takes care of the kerning with the preceding letter)
2. I type the underline
3. I grab the following letter and add the kerning manually

MWE with picture of the result

\documentclass{article}

\makeatletter
\def\foo#1{%
#1%
\setbox0=\hbox{#1}%
\hb@xt@\z@{\hss\vrule height -1.2pt depth 1.6pt width\wd0}%
}

\setbox0=\hbox{{#1}{#2}}%
\setbox1=\hbox{#1#2}%
\dimen@=\wd1
\kern\dimen@ #2%
}
\makeatother

\begin{document}

V\foo{A}V

VAV

V\underline{A}V

\end{document}


The problem is that this kind of fails if the underlined part is the last piece of a word. Of course I can correct this with an empty group but I'd like to avoid that. I have a working trick (which I refuse to post here :-)) based on \new@ifnextchar from amsgen to test for a following space character but I'm starting to grow uncomfortable with the level of hacking. Furthermore, when utf8 encoding is used it breaks down for stuff like l\foo{ie}ß, which should be thus input as l\foo{ie}\ss.

So the question is: Is there a (better) way to underline part of a word while preserving the correct kerning and not swallowing spaces?

For those who might think this is an XY problem: this is about typesetting psalms in the different tones, like

• luatex an option? – David Carlisle Aug 17 '18 at 15:28
• @DavidCarlisle If I have to :-) Jokes aside, yes, of course. My desire of having this working is stronger than my distaste for stuff I don't understand... :-) – campa Aug 17 '18 at 15:32
• for classic tex I'm pretty sure i'd use a syntax \foo{Kr}{a}{ft} with the pre, underlined, and post groups separate and possibly empty, then you don't have to mess with ifnextchar tests, for luatex you could perhaps use a callback to add the lines after typesetting – David Carlisle Aug 17 '18 at 15:35
• Point is, I'm working for someone else. The idea would be to have an environment which makes _ active, and use Kr_a_ft. With my current solution (plus the bit I haven't shown) spaces are not a problem, but ß are. But I start to think that the question is ill-posed :-( – campa Aug 17 '18 at 15:38
• ß should't be a problem I'd have thought, if they are your example should include that case:-) – David Carlisle Aug 17 '18 at 15:42

Using LuaTeX, you can use an attribute to mark the characters you want to underline. This does not interfere with kerning/ligaturing/line breaking because it only acts after all this is finished.

I added some extra flexibility for customized line thickness, placing, duck underlines, ... but the callback basically just iterates over the node tree and draws leaders whenever it finds a marked node:

\documentclass{article}
\usepackage{luacode}
\usepackage{tikzducks,pict2e}

\newattribute\underlineattr
\begin{luacode*}
local underlineattr = token.create'underlineattr'.index
local underline_types = {}
function new_underline_type()
tex.sprint(#underline_types)
end
end
end
end
local last_value
local first
local new_value = node.has_attribute(n, underlineattr)
if n.id == node.id'hlist' then
new_value = nil
elseif n.id == node.id'vlist' then
new_value = nil
elseif n.id == node.id'kern' and n.subtype == 0 then
if n.next and not node.has_attribute(n.next, underlineattr) then
new_value = nil
else
new_value = last_value
end
elseif n.id == node.id'glue' and (
n.subtype == 8 or
n.subtype == 9 or
n.subtype == 15 or
false) then
new_value = nil
end
if last_value ~= new_value then
if last_value then
local width = node.rangedimensions(head, first, n)
local kern = node.new'kern'
kern.kern = -width
kern.next = node.copy(underline_types[last_value])
kern.next.width = width
kern.next.next = n
n.prev.next = kern
end
if new_value then
first = n
end
last_value = new_value
end
end
if last_value then
local kern = node.new'kern'
kern.kern = -width
kern.next = node.copy(underline_types[last_value])
kern.next.width = width
end
end
local function filter(b, loc, prev, mirror)
local new_prev = mirror and b.height or b.depth
if prev > -65536000 then
local lineglue = tex.baselineskip.width - prev - (mirror and b.depth or b.height)
local skip
if lineglue < tex.lineskiplimit then
skip = node.new('glue', 1)
node.setglue(skip, node.getglue(tex.lineskip))
else
skip = node.new('glue', 2)
node.setglue(skip, node.getglue(tex.baselineskip))
skip.width = lineglue
end
skip.next = b
b = skip
end
return b, new_prev
-- return node.prepend_prevdepth(b)
end
luatexbase.callbacktypes.append_to_vlist_filter = 3 -- This should not be necessary
\end{luacode*}

\newcommand\newunderlinetype[2]{%
\setbox0\hbox{#2\hskip0pt}%
\chardef#1=\directlua{new_underline_type()}\relax
}
\begin{tikzpicture}[baseline=3,scale=0.05]%
\duck
\end{tikzpicture}%
}}
\setlength\unitlength{.3mm}%
\begin{picture}(4,0)(0,1)
\thicklines
\color{red}%
\qbezier(0,0)(1,1)(2,0)
\qbezier(2,0)(3,-1)(4,0)
\end{picture}%
}}
\newcommand\underLine[1]{{\underlineattr=\myunderline#1}}
\newcommand\underDuck[1]{{\underlineattr=\myunderduck#1}}
\newcommand\underWavy[1]{{\underlineattr=\myunderwavy#1}}
\newcommand\strikeThrough[1]{{\underlineattr=\mystrikethrough#1}}
\begin{document}
V\underLine{A}V

\underDuck{VAV}

\underDuck{V}\underLine{AV}

\underDuck{These are \underWavy{ucks}}


• +1 but give me some more time to digest this before I accept :-) One thing (besides the whole lua code) is not clear to me: what's the purpose of \newcount? – campa Aug 18 '18 at 11:02
• @campa A "underlinetype" is basically just a number identifying the stored leader specification. This leads to the question how to store this number under a nice name. There are three options: Creating a macro with the written out number, using a TeX count register or a (math)chardef. Using a macro can lead to parsing problems related to trialing spaces, so I avoided them. chardefs are nice but hard to set from Lua (This will probably change in the next LuaTeX version). So a \count register is used to store the number. Before setting it, it has to be allocated by \newcount. – Marcel Krüger Aug 18 '18 at 11:20
• I just edited the code, now the \newcount is gone and a \chardef is used instead. – Marcel Krüger Aug 18 '18 at 11:28
• Is there a simple way to use this luacode for a new command like \strikeThrough. I tried to adapt the measurments but didn't succeed. {\leaders\vrule height+4ptdepth1.5pt} is obviously not correct. – LeO Jun 3 at 10:37