10

The Alegreya font has a pretty clever feature by which it substitutes the regular f glyph with a variant that has a longer cross stroke when the f is followed by t. The result is that the two cross strokes in f and t join, so that the sequence ft looks like a ligature. I like this feature.

BUT, the glyph variant of f with a longer cross stroke is also selected when an end-of-the-line hyphen removes the context by splitting ft into f-t. Now the f variant looks just plain wrong and weird.

\documentclass{article}
\usepackage{fontspec}
\setmainfont{Alegreya}%
    [Path = /usr/local/texlive/2015/texmf-dist/fonts/truetype/huerta/alegreya/,
    UprightFont = Alegreya-Regular.ttf]
\begin{document}
\parbox{0pt}{\hspace{0pt}for after}
\end{document}

enter image description here

The first word for illustrates how the f should look when not followed by t. Clearly what's happened here is that the substitution of f with the font's f.t variant occurs first, and then later LaTeX breaks the sequence by introducing a hyphen, and now it's apparently "too late" for the f character to revert to its regular glyph variant.

So now the question is: Is there a way of telling LaTeX to choose the regular f glyph variant whenever it is followed by a line-breaking hyphen?

I've used lualatex to compile the example above, and I intend to stick to that engine if possible.

3
8

I don't know how is this feature implemented in the font, but I guess that Luaotfload is missing support for it. The following code is just quick fix for this issue, it should be fixed in Luaotfload ideally.

The full code follows, some explanation is provided bellow:

\documentclass{article}
\usepackage{fontspec}
\usepackage{luacode}
\begin{luacode*}
local hlist_id = node.id "hlist"
local glyph_id = node.id "glyph"
local utfchar = unicode.utf8.char
local hyphen_char = 45
local break_subtype = 0

charreplaces = {}

local function get_char(c)
  local x = tonumber(c)
  if not x then
    return string.byte(c)
  end
  return x
end

function char_replace(old, new)
  local old = get_char(old)
  local new = get_char(new)
  print(old, new)
  charreplaces[old] = new
end

local function get_next(n)
  if n and n.id == glyph_id then 
    return n
  elseif n then 
    return get_next(n.prev)
  end
end

local function get_last_glyph(head)
  local last = node.tail(head)
  return get_next(last)
end

local function fix_end_f(head)
  for n in node.traverse(head) do
    if n.id == hlist_id then
      local last = get_last_glyph(n.head)
      if last and last.subtype == break_subtype and last.char == hyphen_char then
        local prev = get_next(last.prev)
        local c = prev.char
        print("Last char: ", c)
        if charreplaces[c] then
          print("Replacing", c)
          prev.char = charreplaces[c]
        end
      end
    end
  end
  return head
end

luatexbase.add_to_callback("post_linebreak_filter", fix_end_f, "fix_end_f")

\end{luacode*}

\newcommand\CharReplaceAtEnd[2]{
  \luaexec{char_replace(\luastring{#1},\luastring{#2})}
}

\CharReplaceAtEnd{983056}{f}
\setmainfont{Alegreya}%
    [Path = /usr/local/texlive/2015/texmf-dist/fonts/truetype/huerta/alegreya/,
    UprightFont = Alegreya-Regular.ttf]
\begin{document}
\parbox{0pt}{\hspace{0pt}for after}


for after
\end{document}

enter image description here

Node processing callback is used to fix the characters. Because we need to apply the fix after linebreak, we need to use post_linebreak_filter.

local hlist_id = node.id "hlist"
local glyph_id = node.id "glyph"
local utfchar = unicode.utf8.char
local hyphen_char = 45
local break_subtype = 0

there we define some constants, in the callback, we need to process node list. In post_linebreak_filter this list contains hlist nodes, each containing one line. We need to get the last glyph from the line, to test whether it is linebreak hyphen, if it is, then we get the previous glyph, we print it to the standard output and test it whether it is in the table with replacements. If it is, we can replace the bad character with the right one.

local function fix_end_f(head)
  for n in node.traverse(head) do
    if n.id == hlist_id then
      local last = get_last_glyph(n.head)
      if last and last.subtype == break_subtype and last.char == hyphen_char then
        local prev = get_next(last.prev)
        local c = prev.char
        print("Last char: ", c)
        if charreplaces[c] then
          print("Replacing", c)
          prev.char = charreplaces[c]
        end
      end
    end
  end
  return head
end

In order to make the interface more user friendly, one LaTeX macro is provided:

\newcommand\CharReplaceAtEnd[2]{
  \luaexec{char_replace(\luastring{#1},\luastring{#2})}
}

it can be used in this way:

\CharReplaceAtEnd{983056}{f}

how we found the value 983056? Search the LaTeX output for Last char. It will print all characters preceding linebreak hyphen.

4
  • So would you say this qualifies as a luaotfload bug/issue that I should report?
    – Sverre
    Aug 28 '15 at 12:24
  • @Sverre probably yes. I've tried to Algreya in LibreOffice and it handles this feature correctly.
    – michal.h21
    Aug 28 '15 at 14:47
  • Bug report here. Is this something you perhaps would be able to help them with? I believe this is essentially the same bug as one reported three years ago, and which still hasn't been fixed.
    – Sverre
    Sep 22 '15 at 13:35
  • 1
    @Sverre I've posted some comment on luaotfload tracker. I think I've found the culprit, but I don't know a solution
    – michal.h21
    Sep 24 '15 at 22:37
0

Another solution of sorts is to use Renderer = Basic:

\documentclass{article}
\usepackage{fontspec}
\setmainfont{Alegreya}[Renderer = Basic]
\begin{document}
\parbox{0pt}{\hspace{0pt}for after}
\end{document}

enter image description here

Renderer = Basic will, however, lead to incorrect kernings in the font, as this option prevents luaotfload from interpreting class based kernings: https://github.com/khaledhosny/libertine/issues/3#issuecomment-133849174

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.