4

Lualatex + unicode-math is awesome, but I'd like to be able to type $Â$ (in vscode) to get the same as if I were to type $\hat A$. So I wrote the clunky code below which does exactly what I want it to do, but there has to be a cleaner / less costly way of achieving the same thing? When I say costly, I mean that it searches the line for each of the characters and then just steps through, changing the input.

Pointers on how my LuaLaTeX code can be improved? I'm looking for a lualatex only solution.

% !TeX program = lualatex
\documentclass{article}

\usepackage{unicode-math}
\usepackage{fontspec}
\setmathfont{STIX Two Math}  % for the weird accents


\directlua{
    
    function fixline( line, nm, str, ch )
        beg,fin = unicode.utf8.find( line, ch )
        if beg == nil then return line end
        lastp = 0
        lastc = 0
        nline = ""
        for p,c in utf8.codes( line ) do
            if c == nm then
                if lastc == 0 then
                else
                    nline = nline..str..utf8.char( lastc )
                end
                lastc = 0
            else
                if lastc == 0 then
                else
                    nline = nline..utf8.char( lastc )
                end
                lastc = c
            end
            lastp = p
        end
        if lastc == 0 then
            else
            nline = nline..utf8.char( lastc )
        end
        return nline
    end

    function ParseMe( line )
        line = fixline( line, 770, "\\hat ", "̂" )
        line = fixline( line, 771, "\\tilde ", "̃" )
        line = fixline( line, 772, "\\bar ", "̄" )
        line = fixline( line, 773, "\\tilde ", "̃" )
        line = fixline( line, 774, "\\breve ", "̆" )
        line = fixline( line, 775, "\\dot ", "̇" )
        line = fixline( line, 776, "\\ddot ", "̈" )
        line = fixline( line, 778, "\\ocirc ", "̊" )
        line = fixline( line, 780, "\\check ", "̌" )
        line = fixline( line, 768, "\\grave ", "̀" )
        line = fixline( line, 769, "\\acute ", "́" )
        line = fixline( line, 8407, "\\vec ", "⃗" )
        line = fixline( line, 8411, "\\dddot ", "⃛" )
        line = fixline( line, 777, "\\ovhook ", "̉" )
        line = fixline( line, 786, "\\oturnedcomma ", "̒" )
        line = fixline( line, 784, "\\candra ", "̐" )
        line = fixline( line, 8406, "\\overleftarrow ", "⃖" )
        line = fixline( line, 8432, "\\asteraccent ", "⃰" )
        line = fixline( line, 8423, "\\annuity ", "⃧" )
        line = fixline( line, 8417, "\\overleftrightarrow ", "⃡" )
        line = fixline( line, 8401, "\\rightharpoonaccent ", "⃑" )
        line = fixline( line, 8400, "\\leftharpoonaccent ", "⃐" )
        line = fixline( line, 794, "\\droang ", "̚" )
        line = fixline( line, 789, "\\ocommatopright ", "̕" )
        line = fixline( line, 8425, "\\widebridgeabove ", "⃩" )
        return line
    end

    id,error = luatexbase.add_to_callback("process_input_buffer", ParseMe, "Beautiful Accents" )
}



\begin{document}

\[ 
  Ã,Â,Ă,Ā,Ȧ,Ä,Å,Ǎ,A⃗,À,Á,A⃛,Ả,A⃖,A̐,A⃰,A̒,A̕,A̚,A⃐,A⃑,A⃡,A⃧,A⃩,Â
\]


\end{document}

enter image description here

4
  • You may not need lua for this: see egreg’s answer to this question. See also this question on combining accents and math mode.
    – renkema
    Commented Aug 24, 2023 at 15:13
  • Thank you @renkema. No, my code above gives me the behavior that I want. I'm just looking for a way to streamline it. The post in your link does more or less the same thing without using lualatex, which I suppose is an advantage, but using tex macros makes it even clunkier than what I have. I was hoping for a method that streamlined the lua code that I already have.
    – JPi
    Commented Aug 24, 2023 at 16:31
  • 1
    You might want to look at the code at the end of my answer at tex.stackexchange.com/a/456225/80496 Commented Aug 24, 2023 at 17:01
  • Thanks @MarcelKrüger. I'll have a look.
    – JPi
    Commented Aug 24, 2023 at 17:09

1 Answer 1

4

I'd say that using the pre_mlist_to_hlist_filter callback to operate on the nodes directly is much cleaner than doing textual replacements inside process_input_buffer, but it's also much more complicated:

\documentclass[12pt,fleqn]{article}
\pagestyle{empty}
\setlength{\parindent}{0pt}


\usepackage{unicode-math}
\setmathfont{STIX Two Math}
\csname@mathmargin\endcsname=0pt


\usepackage{luacode}
\begin{luacode*}
    -- Save local versions of some frequently-used globals
    local combining_classes = require("luaotfload-unicode").ccc
    local fields = node.fields
    local get_font = font.getfont
    local ipairs = ipairs
    local is_node = node.is_node
    local new_node = node.new
    local slide = node.slide


    -- Define some constants
    local ACCENT = 200 -- Something with a “Canonical Combining Class Value”
                       -- ≥ 200 is probably an accent
    local accent_id = node.id("accent")
    local BAD_FIELDS = {
        id = true,
        next = true,
        prev = true,
        subtype = true,
    }
    local mathchar_id = node.id("math_char")
    local noad_id = node.id("noad")


    -- The main function
    local function fix_accents(head)
        -- Loop through all the nodes
        local n = head
        repeat
            -- Recurse
            for _, field in ipairs(fields(n.id)) do
                if is_node(n[field]) and field ~= "attr" then
                    n[field] = fix_accents(n[field])
                end
            end

            -- See if the current node is an accent character typeset as a
            -- regular math character
            if n.id == noad_id and
               n.nucleus and
               n.nucleus.id == mathchar_id and
               (combining_classes[n.nucleus.char] or 0) >= ACCENT
            then
                -- Get the accent and the preceding character (its base)
                local accent = n
                local base = n.prev

                -- Create a new “accent” node
                local new = new_node(accent_id)

                -- Copy over all the fields
                for _, field in ipairs(fields(accent.id)) do
                    if not BAD_FIELDS[field] and accent[field] then
                        new[field] = accent[field]
                    end
                end
                for _, field in ipairs(fields(base.id)) do
                    if not BAD_FIELDS[field] and base[field] then
                        new[field] = base[field]
                    end
                end

                -- Set the new nucleus and accent fields
                new.nucleus = base.nucleus
                new.accent = accent.nucleus

                -- Get the fonts used for the accent and the base
                local accent_font = get_font(accent.nucleus.font)
                local base_font = get_font(base.nucleus.font)

                -- If the accent isn't available in its original font, check
                -- if it's in the base glyph's font
                if not accent_font.characters[accent.nucleus.char] and
                   base_font.characters[accent.nucleus.char]
                then
                    -- Use the base glyph's font
                    new.accent.fam = new.nucleus.fam
                end

                -- Replace the accent and base nodes with the new “accent” node
                if base.prev then
                    base.prev.next = new
                else
                    head = new
                end
                new.next = accent.next
                n = new
                slide(head)
            end

            n = n.next
        until not n

        return head
    end


    -- Define the callback
    luatexbase.add_to_callback(
        "pre_mlist_to_hlist_filter",
        fix_accents,
        "fix_accents"
    )
\end{luacode*}


\begin{document}
    \begin{tabular}{p{8em}p{6em}}
        All the accents:
        \begin{equation*}
            \begin{array}{*5c}
                Ã  &  Â  &  Ă  &  Ā  &  Ȧ  \\
                Ä  &  Å  &  Ǎ  &  A⃗  &  À  \\
                Á  &  A⃛  &  Ả  &  A⃖  &  A̐  \\
                A⃰  &  A̒  &  A̕  &  A̚  &  A⃐  \\
                A⃑  &  A⃡  &  A⃧  &  A⃩  &  Ẫ
            \end{array}
        \end{equation*}

        &

        Nested:
        \begin{equation*}
            \sqrt[A̚]{Â^{\frac{Ã}{Å_{A⃰^{Ä}}}}}
        \end{equation*}
    \end{tabular}
\end{document}

output

1
  • Awesome, thanks!
    – JPi
    Commented Aug 25, 2023 at 13:01

Not the answer you're looking for? Browse other questions tagged .