LaTeX Kerning Issue: Inline Math Vs. Displayed Formulas

by Lucas 56 views

Hey guys! Have you ever run into a weird issue in LaTeX where a font feature, like kerning, works perfectly in inline math but mysteriously vanishes in displayed formulas? It's a head-scratcher, right? Let's dive into this specific problem, explore why it happens, and check out a workaround to get things looking consistent.

The Curious Case of Disappearing Kerning

So, the core issue is that when you add a kerning font feature to a math font in LaTeX, it seems to apply only to inline math and not to displayed math. To illustrate, consider this example. In this section, let's explore in detail why this happens and what's going on behind the scenes. This will help us understand the core of the problem. It’s crucial to understand this, especially when dealing with complex documents where consistency in mathematical notation is paramount. Kerning, which is the adjustment of spacing between characters, is a subtle but essential aspect of typography. When kerning is inconsistent, it can make a document look unprofessional or even lead to misinterpretations of the mathematical expressions. So, understanding why kerning behaves differently in inline versus displayed math modes is very important. Now, let's consider that LaTeX handles inline and displayed math differently. Inline math is treated more like regular text, flowing within the paragraph. Displayed math, on the other hand, is set apart on its own line, often centered, and treated as a distinct block element. This difference in handling is key to why the kerning issue occurs. When LaTeX processes inline math, it applies font features, such as kerning, in a way that’s integrated with the surrounding text. However, for displayed math, the processing pipeline might not always apply these features in the same way. This can be due to various factors, including how LaTeX optimizes the layout of displayed equations and the specific way math fonts are handled in this mode. One of the main reasons for this discrepancy lies in the internal workings of the unicode-math package and the LuaTeX engine, which are commonly used for advanced math typesetting in LaTeX. These tools provide powerful features for handling Unicode math fonts but also introduce complexities in how font features are applied. When a font feature like kerning is added using Lua code, as in the provided example, it might not be automatically applied in all contexts. The default behavior of LaTeX might be to skip certain font features in displayed math mode for performance reasons or due to assumptions about how math should be typeset. This is where the workaround, which we’ll discuss later, comes into play. By manually re-registering the node processor and ensuring it runs on display math as well, we can force LaTeX to apply the kerning feature consistently across both inline and displayed math. This workaround highlights the underlying issue: the default processing pipeline for displayed math in LaTeX doesn’t always include the application of custom font features defined in Lua. Understanding this distinction is crucial for anyone who needs to fine-tune the typography of their mathematical documents. So, the next time you notice that your kerning looks off in displayed math, remember that you're not alone, and there are ways to address this issue.

\documentclass{article}

\usepackage{unicode-math}

\directlua{
  fonts.handlers.otf.addfeature {
      name = "mygaramond",
      type = "kern",
      data = {
          [0x0028] = { [0x1D453] = 100 ,
                       [0x1D712] = 100 },
      }
  }
}

\setmathfont[RawFeature=mygaramond]{Garamond-Math.otf}

\begin{document}

${(f) + (\chi)}$

${
  (f) + (\chi)
}$

\end{document}

In this example, a kerning feature named "mygaramond" is added using Lua to adjust the spacing between the parenthesis and the letters f and χ\chi. However, you'll notice that this kerning is applied only in the inline math (f)+(χ){(f) + (\chi)} but not in the displayed math block.

The Visual Proof

Here’s what the output looks like:

Image

See the difference? In the inline equation, the spacing between the parenthesis and the letters is adjusted, but in the displayed equation, it's not. This inconsistency can be frustrating, especially when aiming for a polished and professional look.

The Workaround: A Lua-Powered Fix

Thankfully, there's a workaround suggested by Max on the StackExchange chat that seems to do the trick. Let's break it down. The workaround involves manipulating the luaotfload.node_processor callback in LuaTeX. This callback is responsible for processing font features, and by default, it might not be applied to displayed math. This section is really important, guys, because it's where we get into the nitty-gritty of fixing the problem. The workaround involves a bit of Lua magic, but don't worry, we'll walk through it step by step. The key to the solution lies in how LaTeX processes math formulas. As we discussed earlier, inline and displayed math are handled differently. By default, the luaotfload.node_processor, which is responsible for applying font features, might not be invoked for displayed math. This is why the kerning feature, which we defined using Lua, only works in inline math. To fix this, we need to ensure that the luaotfload.node_processor is also run for displayed math. This involves a few steps. First, we need to remove the existing luaotfload.node_processor from the pre_linebreak_filter callback. This is done using the luatexbase.remove_from_callback function. This might seem counterintuitive, but it’s necessary to ensure that we can re-register the callback with the desired behavior. Next, we save the function that was removed so we can add it back later. This is important because we still want the node processor to run for inline math. Then, we re-register the node processor with luatexbase.add_to_callback, ensuring it's still applied during the pre_linebreak_filter. This makes sure inline math continues to be processed correctly. Finally, and most importantly, we add the luaotfload.node_processor to the post_mlist_to_hlist_filter callback. This is the step that makes the magic happen for displayed math. The post_mlist_to_hlist_filter is invoked after the math list is converted to a horizontal list, which is how displayed math is processed. By adding the node processor to this callback, we ensure that our font features, including kerning, are applied to displayed equations as well. The conditional if true then block is a way to easily enable or disable this part of the workaround. If you set it to false, the node processor won’t be added to the post_mlist_to_hlist_filter, effectively disabling the fix for displayed math. This can be useful for testing or debugging. By understanding these steps, you can see how the workaround effectively extends the application of font features to displayed math, ensuring consistency in your LaTeX documents. This is a powerful technique for anyone who wants to have fine-grained control over the typography of their mathematical expressions.

\documentclass{article}

\directlua{
    fonts.handlers.otf.addfeature {
        name = "mygaramond",
        type = "kern",
        data = {
            [0x0028] = { [0x1D453] = 1000 ,
                        [0x1D712] = 1000 },
        }
    }

    --[[
         luaotfload doesn't publicly expose the `luaotfload.node_processor`
         function, and `luatexbase` doesn't expose the functions currently
         registered to a callback. However, `remove_from_callback` returns the
         function currently registered, so we can "pop" and save the function
         with `remove_from_callback`, and then re-register it with
         `add_to_callback`.
      ]]
    local node_processor = luatexbase.remove_from_callback(
        "pre_linebreak_filter", "luaotfload.node_processor"
    )
    luatexbase.add_to_callback(
        "pre_linebreak_filter", node_processor, "luaotfload.node_processor"
    )

    --[[
         Also run the `luaotfload.node_processor` callback on display math.
      ]]
    if true then --[[ Set to `false` to disable. ]]
        luatexbase.add_to_callback(
            "post_mlist_to_hlist_filter", node_processor, "luaotfload.node_processor"
        )
    end
}

\usepackage{unicode-math}
\setmathfont[RawFeature=mygaramond]{Garamond-Math.otf}

\pagestyle{empty}
\setlength{\parindent}{0pt}

\begin{document}
    \centering
    ${(f) + (\chi)}$
    ${(f) + (\chi)}$
\end{document}

Let's break down what's happening here:

  1. Adding the Kerning Feature: The code first adds the kerning feature "mygaramond" using Lua, just like in the initial example. This is crucial for setting up the desired kerning adjustments. Remember, kerning is all about fine-tuning the spacing between specific characters to improve readability and visual appeal. In this case, we're focusing on the spacing between parentheses and certain math symbols. Getting this right can make a big difference in how professional and polished your document looks. By defining the kerning feature in Lua, we're leveraging the flexibility and power of LuaTeX, which allows us to make very specific adjustments to font rendering. This is particularly useful when dealing with complex mathematical typography where standard LaTeX commands might not give you the level of control you need. So, the initial step of adding the kerning feature sets the stage for the rest of the workaround, ensuring that we have a custom kerning rule that we want to apply consistently across our document. Without this step, the rest of the code wouldn't have any kerning adjustments to apply, so it's a foundational part of the solution.
  2. Detaching and Reattaching the Node Processor: This is the clever bit. The code removes the luaotfload.node_processor from the pre_linebreak_filter callback and then immediately re-registers it. It might sound a bit like unnecessary shuffling, but it's a trick to ensure the processor is run in the right context. Think of it like this: sometimes you need to unplug and replug a device for it to work correctly. In this case, we're doing the same thing with the node processor. The pre_linebreak_filter is a key part of LaTeX's processing pipeline, and by manipulating the node processor in this way, we're ensuring that our font features are applied at the right stage. This is important because LaTeX processes documents in a specific order, and if we try to apply font features at the wrong time, they might not have the desired effect. So, by carefully detaching and reattaching the node processor, we're making sure it's ready to do its job when it's needed.
  3. Applying to Displayed Math: The key line here is the luatexbase.add_to_callback that adds the node_processor to the post_mlist_to_hlist_filter. This ensures that the kerning feature is also applied to displayed math, which is processed differently from inline math. This is the heart of the workaround, guys. As we've discussed, displayed math is handled in a separate way by LaTeX, and by default, the font features might not be applied consistently. By adding the node processor to the post_mlist_to_hlist_filter, we're explicitly telling LaTeX to apply our kerning adjustments to displayed equations as well. This ensures that the spacing in our displayed math is consistent with the inline math, giving our document a more polished and professional look. The if true then conditional block is a nice touch because it allows us to easily enable or disable this part of the workaround. This can be useful for debugging or for situations where we might not want the kerning adjustments to be applied to displayed math. So, this step is crucial for achieving consistency in our document's typography.

With this workaround, the kerning should now be applied consistently to both inline and displayed math.

Why This Happens: A Deeper Dive

To really understand why this issue occurs, we need to delve into how LaTeX processes math. Inline and displayed math are treated differently, and the luaotfload package, which handles OpenType font features in LuaTeX, might not apply features uniformly across both modes. This is a bit of a rabbit hole, but it's worth exploring if you're a LaTeX enthusiast or someone who needs to troubleshoot complex typesetting issues. The key takeaway here is that LaTeX's internal workings can sometimes lead to unexpected behavior, and understanding these nuances is essential for advanced users. Knowing how LaTeX handles different types of math and how packages like luaotfload interact with these processes can empower you to solve a wide range of typesetting problems.

Conclusion

This kerning issue is a prime example of the quirks you can encounter when working with advanced typesetting systems like LaTeX. While it might seem like a small detail, consistent kerning is crucial for professional-looking documents. By understanding the problem and applying the workaround, you can ensure that your math formulas look their best, whether inline or displayed. And that's what we're all striving for, right? A beautifully typeset document that communicates our ideas clearly and effectively. Keep experimenting, keep learning, and don't be afraid to dive into the details – that's where the real LaTeX magic happens!