With package semantic, one can easily produce special math symbols via ligature (e.g. -> becomes \rightarrow). In OpenType, however, there are similar things. For example, the Fira Code font provides ligatures for programming. How to implement such feature in TeX equations?

Actually, this feature may require the support of OpenType fonts. But the existing OpenType math fonts don't seem to have this feature, so I hope to create one if possible.

  • 1
    Since with Unicode you can directly type , I'm not sure about the need for such ligatures. Moreover, I'd like to be able to type $x<-2$ without worrying about font properties kicking in. ;-) More relevant: why is this on topic for TeX.SX?
    – egreg
    Apr 19, 2018 at 7:29
  • You can do this with luatex, see e.g. here about how context does it: tex.stackexchange.com/questions/426356/…. But I would do it, this automatic replacements are quite a pain, you always have to take care that they don't kick in in places where you don't want them. Apr 19, 2018 at 8:34

3 Answers 3


Since you're willing to use OpenType fonts, I would assume that you're also willing to consider a LuaLaTeX-based solution. The following code sets up a Lua function that (a) scans all input lines and (b) automatically replaces all instances of -> and <- with \rightarrow and \leftarrow, respectively, "on the fly".

I trust it's straightforward to figure out how to define additional "smart ligatures".

The operation of the Lua function can be disabled and subsequently re-enabled with the LaTeX macros \SmartligOff and \SmartligOn, respectively. This might come in handy if your bibliography contains bib entries which might feature URL strings that contain the substrings -> and <-: Obviously, one wouldn't want to replace these substrings with \rightarrow and \leftarrow, right?

enter image description here

% !TEX TS-program = lualatex
\usepackage{unicode-math} % optional
\setmathfont{Asana Math}  % optional

function smartlig ( s )
    -- Use '%-' to search for '-'
    -- Write '\\' to output a (single) backslash character
    s = s:gsub ( "%->", "\\rightarrow " )
    s = s:gsub ( "<%-", "\\leftarrow " )
  return s
\newcommand\SmartligOn{\directlua{luatexbase.add_to_callback (
     "process_input_buffer", smartlig, "smartlig" )}}
\newcommand\SmartligOff{\directlua{luatexbase.remove_from_callback (
     "process_input_buffer", "smartlig" )}}
\AtBeginDocument{\SmartligOn} % enabled by default

$a->b$, $c<-d$

$a->b$, $c<-d$

$a->b$, $c<-d$
  • I really dislike the process_input_buffer callback because it is completely agnostic to modes and catcodes. Couldn't this be done using mlist_to_hlist? Apr 19, 2018 at 9:52
  • I'm very sorry. My comment was not intended to be offensive. Admittedly, the problem with process_input_buffer is not as severe with combinations like -> but the fact that there is no way to limit it to math mode or tell it to not garble control sequences is quite a strong limitation in my opinion. In light of this it might be better to do \everymath{\SmartligOn\aftergroup\SmartligOff} rather than \AtBeginDocument{\SmartligOn}. Apr 19, 2018 at 21:13
  • @HenriMenke - Thanks for this. I've deleted my earlier comment, and I'll incorporate the jist of your comment in the body of my answer.
    – Mico
    Apr 19, 2018 at 21:19
  • @HenriMenke - Actually, is \everymath{\SmartligOn\aftergroup\SmartligOff} correct? If I add this instruction in the preamble and delete \AtBeginDocument{\SmartligOn}, the Lua function smartlig doesn't seem to do any work.
    – Mico
    Apr 19, 2018 at 21:29
  • 1
    Reported: tug.org/pipermail/luatex/2018-April/006799.html Apr 19, 2018 at 21:49

ConTeXt has a feature which is called collapsing where a number of predefined combinations of glyphs are collapsed into one, either via font ligatures (internally called specials), such as

′′′ (3 times ‎0x2032 PRIME) to (0x2034 TRIPLE PRIME)

or via a lookup table called mathlist, where things like the arrows are defined. There was a related question recently one how turn these off

$a->b$ $b<-a$

enter image description here


It's a nice challange. This solution works only in text and displaystyle so far and it only replaces the rightarrow.

  local rightarrow = node.new("noad",5) % subtype 5 = "rel"
  rightarrow.nucleus = node.new("math_char")
  rightarrow.nucleus.fam = 2
  rightarrow.nucleus.char = 33
  local function is_minus(v)
     return v and v.nucleus and v.nucleus.fam == 2 and v.nucleus.char == 0
  local function is_gt(v)
     return v and v.nucleus and v.nucleus.fam == 1 and v.nucleus.char == 62
        local v = head
        local delete_next = false
        while v do
           if delete_next then
              node.insert_after(head, v, node.copy(rightarrow))
              head = node.remove(head, v)
              delete_next = false
           if is_minus(v) and is_gt(v.next) then
              head = node.remove(head, v)
              delete_next = true
           v = v.next
        return node.mlist_to_hlist(head,style,penalties)

You must log in to answer this question.

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