Math in reStructuredText

Table of Contents

Math in Browser

There are many ways to embed math formulas into a web page.

Solution 1: MathML

MathML is a W3C Recommendation [1], so you might guess it is the best choice, but it is not, because it is not supported well by the major web browsers [2] (see [3]).

If you want to use MathML, you still have a decision to make: do you want to write the MathML code by yourself?

If you decide to write your formulas in MathML directly, it's OK. However, you don't have to write it if you don't want to. Instead, you can write in \(\rm \LaTeX\) and convert it into MathML using tools such as itex2MML [4].

[1]http://www.w3.org/Math/
[2]http://en.wikipedia.org/wiki/MathML#Web_browsers
[3]http://www.w3.org/Math/testsuite/
[4]http://golem.ph.utexas.edu/~distler/blog/itex2MML.html

Solution 2: JavaScript + CSS

The fact that HTML is not \(\rm \LaTeX\)-friendly does not mean that web browsers are not \(\rm \LaTeX\)-friendly, because browsers are powered by JavaScript and CSS. JavaScript can parse \(\rm \LaTeX\) codes and render them with the help of CSS, which can display really fancy math in a browser. MathJax [5] is such a tool. All you have to do is to insert a line in your HTML:

<script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"> </script>

Then you can write Math in \(\rm \LaTeX\) now:

\[ \frac{1}{\Bigl(\sqrt{\phi \sqrt{5}}-\phi\Bigr) e^{\frac25 \pi}} =
1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}}
{1+\frac{e^{-8\pi}} {1+\ldots} } } } \]

This will be rendered as

\[\frac{1}{\Bigl(\sqrt{\phi \sqrt{5}}-\phi\Bigr) e^{\frac25 \pi}} = 1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}} {1+\frac{e^{-8\pi}} {1+\ldots} } } }\]

More demos can be found on the official MathJax website [6].

Please note that MathJax can convert \(\rm \LaTeX\) into MathML too, so choosing MathJax means you get the flexibility to switch between CSS and MathML.

In fact MathJax is not the only JS+CSS solution. There are other similar tools, such as jsMath [7] [8] and jqMath [9].

jsMath seems the predecessor of MathJax (from Wikipedia [10]):

The MathJax project started in 2009 as the successor to an earlier JavaScript mathematics formatting library, jsMath, and is managed by Design Science. The project is sponsored by the American Mathematical Society, Design Science, and the Society for Industrial and Applied Mathematics and is supported by the American Physical Society, Elsevier, and Project Euclid.

MathJax is used by web sites including MathSciNet, GitHub, n-category cafe, Math Overflow, Project Euclid journals, and the All-Russian Mathematical Portal.

[5]http://www.mathjax.org
[6]http://www.mathjax.org/demos/tex-samples/
[7]http://www.math.union.edu/~dpvc/jsmath/
[8]http://en.wikipedia.org/wiki/JsMath
[9]http://mathscribe.com/author/jqmath.html
[10]http://en.wikipedia.org/wiki/Mathjax

Solution 3: Image

Almost all browsers support images, so if you don't trust anything (like MathML, MathJax, etc.), use images.

There are many tools that can convert \(\rm \LaTeX\) into images, such as Mathhack [11], LatexFormulaMacro [12], django_mathlatex [13] (if you use Django), etc.

[11]http://docutils.sourceforge.net/sandbox/cben/rolehack/
[12]http://trac-hacks.org/wiki/LatexFormulaMacro
[13]https://github.com/emesik/django_mathlatex

Comparison of solutions

option web browser support look the same in browsers? usage users
MathML poor no embed MathML into HTML currently no website really uses it
Image perfect yes make images and embed them in HTML
  • Wikipedia
MathJax (JS+CSS) very good almost yes embed \(\rm \LaTeX\) in HTML
[14](1, 2) http://math.stackexchange.com/
[15](1, 2) http://cdsweb.cern.ch/
[16]http://cdsweb.cern.ch/record/1330928
[17](1, 2) http://annals.math.princeton.edu/
[18]http://www.mathjax.org/community/mathjax-in-use/

My Choice

So far MathML is obviously not a good choice. Between JS+CSS and image, I choose the former, because I want the formulas to be rendered at runtime. If I use image, I would have to convert the math into images, store it in some place and embed them in HTML, which would be less convenient.

MathJax and jsMath are very similar. Both MathJax and jsMath are used by many websites [19], but I prefer MathJax because it's newer and is supported by many famous commercial and academic sites such as [14], [15], [17].

jqMath is another option. It is said that it is much faster than MathJax [20], but I didn't try it. It seems fewer sites are using it. Maybe I should give it a try later.

[19]http://www.math.union.edu/~dpvc/jsmath/gallery.html
[20]http://mathscribe.com/author/jqmath-mathjax-perf.html

Use MathJax in reStructuredText

No hacking is needed to use MathJax in reStructuredText. We can just use the raw directive:

math.rst::

        .. role:: raw-latex(raw)
            :format: latex html

        .. raw:: html

           <script type="text/javascript" src="http://localhost/mathjax/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>

        This: :raw-latex:`\((x+a)^3\)`

        this: :raw-latex:`\(W \approx \sum{f(x_k) \Delta x}\)`

        this: :raw-latex:`\(W = \int_{a}^{b}{f(x) dx}\)`

        and this:

        .. raw:: latex html

           \[ \frac{1}{\Bigl(\sqrt{\phi \sqrt{5}}-\phi\Bigr) e^{\frac25 \pi}} =
           1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}}
           {1+\frac{e^{-8\pi}} {1+\ldots} } } } \]

        When :raw-latex:`\(a \ne 0\)`, there are two solutions to :raw-latex:`\(ax^2 + bx + c = 0\)` and they are
        :raw-latex:`\(x = {-b \pm \sqrt{b^2-4ac} \over 2a}.\)`

You will get this:

This: \((x+a)^3\)

this: \(W \approx \sum{f(x_k) \Delta x}\)

this: \(W = \int_{a}^{b}{f(x) dx}\)

and this:

\[\frac{1}{\Bigl(\sqrt{\phi \sqrt{5}}-\phi\Bigr) e^{\frac25 \pi}} = 1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}} {1+\frac{e^{-8\pi}} {1+\ldots} } } }\]

When \(a \ne 0\), there are two solutions to \(ax^2 + bx + c = 0\) and they are \(x = {-b \pm \sqrt{b^2-4ac} \over 2a}.\)

Use MathJax in a Django project

I mentioned in my home page that all articles in this site are stored as reStructuredText format. Some of the articles contains math formula. Here's what I did in my Django project.

Use a custom module for our custom stuff

We will use some custom stuff, so let's create a custom module:

$ mkdir rsthack
$ cd rsthack
$ touch __init__.py
$ ln -s /path/to/rsthack /path/to/a/module/search/path/

The code for new roles and new directive will be put in the __init__.py.

Add a new role mil

mil stands for math inline.

class_name = 'mathjaxlatex'  # appears in <span class="mathjaxlatex"> and <div class="mathjaxlatex">

def MRole(role, rawtext, text, lineno, inliner, options={}, content=[]):
    t = re.sub(':%s:' % role, '', rawtext)
    assert t[:1] == '`' and t[-1:] == '`'
    t = r'\(%s\)' % t[1:-1]
    options={'format': 'html', 'classes': [class_name]}
    node = nodes.raw(rawtext, t, **options)
    return [node], []

roles.register_canonical_role('mil', MRole)

Then we can use :mil:`x = {-b \pm \sqrt{b^2-4ac} \over 2a}` to get \(x = {-b \pm \sqrt{b^2-4ac} \over 2a}\).

Add new directive math and new role eq

The code for role eq is simple, but the code for directive math is a little more complicated. Most lines below are cloned from Sphinx source.

######################################################################
# role eq
######################################################################
# see: /usr/share/pyshared/sphinx/ext/mathbase.py

class eqref(nodes.Inline, nodes.TextElement):
    pass

def html_visit_eqref(self, node):
    self.body.append('<a href="#equation-%s">' % node['target'])

def html_depart_eqref(self, node):
    self.body.append('</a>')

def eq_role(role, rawtext, text, lineno, inliner, options={}, content=[]):
    text = utils.unescape(text)
    node = eqref('(?)', '(?)', target=text)
    return [node], []

roles.register_canonical_role('eq', eq_role)

######################################################################
# directive math
######################################################################
# see:
#     /usr/share/pyshared/sphinx/application.py
#     /usr/share/pyshared/sphinx/ext/mathbase.py
#     /usr/share/pyshared/sphinx/ext/jsmath.py
#     /usr/share/doc/python-sphinx/html/ext/math.html

def massage_equations(doctree):
    num = 0
    numbers = {}

    # generate the "label->number" map from directive ``math``
    for node in doctree.traverse(displaymath):
        if node['number'] is None:
            continue
        numbers[node['label']] = node['number']

    # set eqno in references
    for node in doctree.traverse(eqref):
        if node['target'] not in numbers:
            continue
        num = '(%d)' % numbers[node['target']]
        node[0] = nodes.Text(num, num)

def html_visit_displaymath(self, node):
    if node['nowrap']:
        self.body.append(self.starttag(node, 'div', CLASS=class_name))
        self.body.append(node['latex'])
        self.body.append('</div>')
        raise nodes.SkipNode
    for i, part in enumerate(node['latex'].split('\n\n')):
        part = self.encode(part)
        if i == 0:
            # necessary to e.g. set the id property correctly
            if node['number'] is not None:
                self.body.append('<span class="eqno">(%s)</span>' %
                                 node['number'])
            self.body.append(self.starttag(node, 'div', CLASS=class_name))
        else:
            # but only once!
            self.body.append('<div class="%s">' % class_name)
        if '&' in part or '\\\\' in part:
            self.body.append('\\[\\begin{align}\n' + part + '\n\\end{align}\\]')
        else:
            self.body.append('\\[' + part + '\\]')
        self.body.append('</div>\n')
    raise nodes.SkipNode

def add_node(node, **kwds):
    nodes._add_node_class_names([node.__name__])
    for key, val in kwds.iteritems():
        try:
            visit, depart = val
        except ValueError:
            raise ExtensionError('Value for key %r must be a '
                                 '(visit, depart) function tuple' % key)

        assert key == 'html', 'accept html only'

        setattr(translator, 'visit_'+node.__name__, visit)
        if depart:
            setattr(translator, 'depart_'+node.__name__, depart)

class displaymath(nodes.Part, nodes.Element):
    pass

# custom directive ``math``
class MathDisplay(Directive):
    has_content = True
    required_arguments = 0
    optional_arguments = 1
    final_argument_whitespace = True
    option_spec = {
        'label':  directives.unchanged,
        'nowrap': directives.flag,
        'number': directives.nonnegative_int,
    }

    def run(self):
        latex = '\n'.join(self.content)
        if self.arguments and self.arguments[0]:
            latex = self.arguments[0] + '\n\n' + latex
        node = displaymath()
        node['latex']  = latex
        node['number'] = self.options.get('number', None)
        node['label']  = self.options.get('label', node['number'])
        node['nowrap'] = 'nowrap' in self.options

        ret = [node]

        if node['label']:
            tnode = nodes.target('', '', ids=['equation-' + node['label']])
            self.state.document.note_explicit_target(tnode)
            ret.insert(0, tnode)
        return ret

add_node(eqref, html=(html_visit_eqref, html_depart_eqref))
add_node(displaymath, html=(html_visit_displaymath, None))

directives.register_directive('math', MathDisplay)

Write a custom Django filter

The code above are in our custom module rsthack, so everytime we import rsthack, the code will run and we are ready to use the new roles, new directive and stuff.

One trick in the following filter is that we call rsthack.massage_equations to set the correct equation numbers in references.

##############################
# filter enhancedrst
##############################

from django.conf import settings
from django.utils.encoding import smart_str, force_unicode
from django.utils.safestring import mark_safe

import rsthack  # run rsthack/__init__.py

def enhancedrst(value):
    try:
        from docutils import io
        from docutils.core import publish_doctree, Publisher
        from docutils.readers.doctree import Reader
    except ImportError:
        if settings.DEBUG:
            raise template.TemplateSyntaxError("Error in {% restructuredtext %} filter: The Python docutils library isn't installed.")
        return force_unicode(value)
    else:
        docutils_settings = getattr(settings, "RESTRUCTUREDTEXT_FILTER_SETTINGS", {})

        # generate the doctree
        document = publish_doctree(source=smart_str(value), source_class=io.StringInput)

        # connect eqno and the reference
        rsthack.massage_equations(document)

        # apply the massaged doctree
        # (this is almost the same as docutils.core.publish_from_doctree)
        pub = Publisher(Reader(parser_name='null'),
                        None, None,
                        source=io.DocTreeInput(document),
                        destination_class=io.StringOutput)
        pub.set_writer('html4css1')
        pub.process_programmatic_settings(None, docutils_settings, None)
        pub.set_destination(None, None)
        pub.publish()
        parts = pub.writer.parts

        return mark_safe(force_unicode(parts["fragment"]))

enhancedrst.is_safe = True

register.filter(enhancedrst)

Use the new filter

A Django template sample:

{% load rst %}
<link rel="stylesheet" type="text/css" href="{{ mediaurl }}css/rst.css" />
<script type="text/javascript" src="{{ mathjax }}"></script>
{% for art in articles %}
    <blockquote>
    {{ art.content|enhancedrst }}
    </blockquote>
{% endfor %}

in which

  • the filter source is part of file rst.py, so we use load rst to load it

  • in rst.css we mush have
    span.eqno {
            float: right;
    }
    
  • the URL mathjax can be either the MathJax CDN or your own MathJax URL [21]

  • art.content should be in reStructuredText format

[21]http://www.mathjax.org/docs/1.1/configuration.html

Write math in Django

Now everything's ready. Let's write math in our article:

When :mil:`a \ne 0`, there are two solutions to :mil:`ax^2 + bx + c = 0` and they are
:mil:`x = {-b \pm \sqrt{b^2-4ac} \over 2a}.`

Some *big* math:
.. math::
    :number: 123
    :label: blah

    \frac{1}{\Bigl(\sqrt{\phi \sqrt{5}}-\phi\Bigr) e^{\frac25 \pi}} =
    1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}}
    {1+\frac{e^{-8\pi}} {1+\ldots} } } }

Did you know equation :eq:`blah`?

We will get:

When \(a \ne 0\), there are two solutions to \(ax^2 + bx + c = 0\) and they are \(x = {-b \pm \sqrt{b^2-4ac} \over 2a}.\)

Some big math:

(123)
\[\frac{1}{\Bigl(\sqrt{\phi \sqrt{5}}-\phi\Bigr) e^{\frac25 \pi}} = 1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} {1+\frac{e^{-6\pi}} {1+\frac{e^{-8\pi}} {1+\ldots} } } }\]

Did you know equation (123)?

Get the code

Most of the code above is available in Google Code. You can either read online or check it out from the repository.

Extra readings