Generating Images From MathML

I recently found myself needing to generate images of a couple dozen economics formulas for a mobile application. Hopefully this walk-through of how I did it will help someone else avoid a couple of my missteps.

The formulas were already written out in MathML, a mark-up language for specifying math content. Ultimately each formula needed to be a separate image file so they could be embedded in the mobile application. As an added wrinkle, I needed to be able to generate multiple resolutions because this was a Xamarin.Forms application targeting multiple platforms and multiple display densities on those platforms.

The obvious path to accomplish this was MathML -> SVG -> PNG. There are multiple options for getting MathML to render as an SVG (as a fall-back for web browsers which can't handle MathML natively), and having the formulas rendered as SVG files solved my scaling issues. I could generate an SVG for each formula, and from there I could render them at whatever resolutions I needed for the various platforms and densities, then convert the rendered images to PNG for embedding.

MathJax is an excellent JavaScript library for handling mathematics rendering in browsers, and one of the input formats it supports is MathML. It also supports SVG as an output format. And, most lucky of all for me, someone has already gone to the trouble of creating a Node.js version of the library called MathJax-node. So the MathML -> SVG portion of this journey could be easily scripted from the command line. Or so I thought.

I work on a Windows machine by default, and I already had Node.js installed. I installed MathJax-node with no errors and wrote up a quick script to run the MathML files through it with SVG output. I ran it and nothing happened. I then spent an inordinate amount of laboriously debugging my way through the MathJax-node code, desperately trying to figure out why I neither got any SVG output or any error messages. Eventually I hit a dead end, because the MathJax-node library was loading the MathJax library into the DOM and after that ... nothing.

Since MathJax is heavily dependent on the DOM to operate, the Node version fakes it with jsdom, which relies on Contextify to give it a V8 execution context. Unfortunately, Contextify has some issues with Windows, and at that point in the chain everything stopped working and swallowed any useful error messages. I spent some time trying to debug this to no avail, so I installed all the tools on my Mac and gave it a shot there; worked like a charm. Here's the script, in case you're interested:

var mjAPI = require("../node_modules/MathJax-node/lib/mj-single.js");
var fs = require('fs');

mjAPI.config({
    MathJax : {
        SVG : {
            font : "STIX-Web"
        }
    },
    displayErrors : true,
    displayMessages : false
});

mjAPI.start();

fs.readFile(process.argv[2], 'utf8', function (err, formula) {
    if (err) {
        return console.log(err);
    }

    mjAPI.typeset({
        math : formula,
        format : "MathML",
        svg : true,
        width: 1,
        linebreaks: true
    }, function (results) {
        if (!results.errors) {
            console.log(results.svg)
        }
    });
});

I named it mathmlToSVG.js and called it like this: node mathmlToSVG "eq1.html" > "eq1.svg"

With that hurdle cleared, I just needed to convert the SVG files to PNG files at the right resolutions. I've used ImageMagick for this kind of thing before, so I installed that on the Mac and gave it a try. But for some reason I'm not entirely clear on, ImageMagick just likes to shove all the symbols into a big pile in the bottom left corner of the resulting PNG. I was about to dive into debugging that problem when I came across a StackExchange post suggesting Inkscape for this type of job. The installation process is a little rougher than ImageMagick's, but the results were fantastic. Here's a sample formula in MathML:

<math>
<mrow>
    <msub><mi>&#x03B5</mi> <mi>D</mi></msub>
    <mo>=</mo>
    <mi>&#x2223</mi>
    <mfrac>
        <mi>%&#x00394 Qd</mi>
        <mi>%&#x00394 P</mi>
    </mfrac>
    <mi>&#x2223</mi>
</mrow>
</math>

And the resulting PNG:

Price Elasticity of Demand

A bit of shell scripting later, I was able to convert a directory full of MathML files to PNGs at arbitrary resolutions. Here's the shell script with the Inkscape commands:

echo Generating PNG from MathML files

for mlfile in *.html; do
  echo "Processing $mlfile"
  svg=${mlfile%.html}.svg
  png=${mlfile%.html}.png

  node mathmlToSVG "${mlfile}" > "${svg}"
  /Applications/Inkscape.app/Contents/Resources/bin/inkscape --export-png "${png}" "${svg}"
done

It definitely all works on a Mac running Yosemite. On other platforms, YMMV. Oh, and if you're targeting mobile OSes which support both light and dark themes, you might need an inverse version of all your images. ImageMagick has no trouble handling this - you can convert a whole folder of images from black to white with a PowerShell one-liner on Windows:

dir *.png | % { convert $_.Name -negate $_.Name }

This will convert them in place, and it assumes you've got ImageMagick in your path.