Mathematics on the Web

Displaying mathematics on webpages is more difficult than one may think.

Some sites like Wikipedia use images to display mathematics. This works in all browsers, but usually the output is low-quality and requires an HTTP request for every expression. If you aren’t using a markup language that supports math it requires typesetting all the images beforehand which can be time consuming.

MathJax is the most commonly used solution for writing mathematics. It is a JavaScript library that searches HTML for different types of mathematical input and creates high-quality output using HTML and CSS or SVG. This is the most mature and well supported library bar none, but MathJax is quite heavyweight and often slow to render.

The KaTeX library used by Khan Academy rectifies these problems and additionally allows for server side parsing. This greatly improves performance especially for large amounts of math and can even work without using JavaScript at all. I would recommend this solution above the other two.

However, I found even KaTeX lacking. TeX is the standard language for typesetting mathematics, but I find AsciiMath far superior. MathJax supports AsciiMath, but KaTeX doesn’t. I would also like to use MathML as the output format instead of HTML and CSS. MathML is a format designed specifically for displaying mathematics and can be rendered more efficiently and correctly by the browser itself. Unfortunately, most browsers do not support it natively.

The solution I use is a combination of three tools: lark, amath, and mathml.css.

We’re going to construct a custom markup language using lark which implements a math element. It will pass the content of the expression in backticks to amath. The amath library will parse the expression and return MathML. Finally, mathml.css will be used to achieve proper MathML rendering in all browsers.

Let the following be input.page.

The quadratic formula is `(-b +- sqrt(b^2 - 4ac)) / (2a)`.

We’re going to use the luaffifb module to to call the amath_to_mathml function in the amath shared library. Let the following be markup.lua.

local ffi = require "ffi"
local lark = require "lark"
local amath = ffi.load("/usr/local/lib/libamath.so")
local to_mathml = (function()
    ffi.cdef "char *amath_to_mathml(char *)"
    return function(str)
        local buf = ffi.new("char[" .. (#str + 1) .. "]", str)
        local res = ffi.string(amath.amath_to_mathml(buf))
        return "<math>" .. res .. "</math>"
    end
end)()
local I = lark.Is {
    { "<math><mtext>, </mtext></math>", to_mathml }
}
local B = lark.Bs(I, {
    { nil, "<p>%1</p>" }
})
print(lark.run(B))

All that’s left is to run the input file through the markup parser.

cat input.page | lua markup.lua

This will produce the correct MathML formula:

<p>The quadratic formula is <math>...</math>.</p>

Just include mspace.js from the mathml.css project to enable lightweight MathML rendering on all browsers.

One may use the FFI to call any dynamic library or use os.execute to call the shell. This would make it easy to pass code snippets into pygments or run and embed the output of programs within documents. The possibilities here are endless.