Using KaTeX with ReStructuredText for rendering math
I’ve been using ReStructured Text to write blog articles and personal notes (in addition to markdown). The main advantage of RST is that it is a comprehensive markup language, including support for tables, LaTeX/maths, admonitions, image attributes, etc. On the other hand, the markdown ecosystem is too fragmented, with varying levels of support for such features. For example, though MultiMarkdown supports tables, it does not support multiple rows for a cell; RST does. python-markdown with extensions supports admonitions but it cannot generate PDF, manpages or other output formats; RST does. Moreover, RST is the official markup format for the Linux kernel documentation, OpenStack, Python language documentation, Nim lang, and many other projects. There is even a linter tool, rstcheck, which inspects .rst files for syntax errors.
The official tool to convert RST to HTML is via docutils script rst2html5.py. It can output mathematics formatted in MathJax or MathML or LaTeX or HTML+math.css (default).
MathML is not supported by Chromium-based browsers.
Typesetting done by HTML+math.css is very poor with limited features (e.g. no matrices); it is a fallback case, where “something is better than nothing”.
LaTeX is suitable for use with, guess what, LaTeX, for generating PDFs.
That leaves us with only MathJax for use in web browsers.
However KaTeX is a competitor to MathJax and is much faster to load and render the maths expressions. There is no official support for KaTeX in rst2html but it is very trivial to get it to work in RST.
Inline formulas
Inline formulas are entered as normally, using the :math: role:
:math:`x^2 + y^2 = z^2`
which will be rendered as x^2 + y^2 = z^2.
rst2html will wrap the math in HTML <tt class="math"> tt-tag.
Block display formulas
Normally, RST documentation says that you can enter formulas like this:
.. math:: a &= (x + y)^2 & b &= (x - y)^2 \\ &= x^2 + 2xy + y^2 & &= x^2 - 2xy + y^2
However, for KaTeX to work, multiline equations using & and \\ must be wrapped in {aligned} environment.
.. math:: \begin{aligned} a &= (x + y)^2 & b &= (x - y)^2 \\ &= x^2 + 2xy + y^2 & &= x^2 - 2xy + y^2 \end{aligned}
which will be rendered as:
\begin{aligned} a &= (x + y)^2 & b &= (x - y)^2 \\ &= x^2 + 2xy + y^2 & &= x^2 - 2xy + y^2 \end{aligned}
Another example (see html source of this file for rst code):
x_1 = \frac{1}{2}\left(2^{1/4} + \frac{1}{2^{1/4}}\right)
Invoke rst2html with latex option:
rst2html5.py --math-option=latex --template=template.txt input.rst > output.html
rst2html will wrap the math in HTML <pre class="math"> pre-block and “math” class. KaTeX will format elements under this class name (snippet below).
Script setup
Add the following towards end of the HTML, just before the closing </body> tag. Alternatively, you can include the snippet inside <head> but use defer, like <script defer src=...>. This will load the rest of the page before triggering katex.
<!-- BEGIN KaTeX math render -->
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.2/katex.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.16.2/katex.min.js">
</script>
<script type="text/javascript">
// grab all elements in DOM with the class 'math'
var tex = document.getElementsByClassName("math");
// for each element render the expression attribute
for(var i = 0; i < tex.length; i++) {
content = tex[i].textContent;
tagname = tex[i].tagName;
// inline math in <tt class="math"> and display math <pre class="math">
disp = (tagname === 'PRE');
content = content.replace("amp;", "");
katex.render(content, tex[i], {displayMode: disp});
}
</script>
<!-- END KaTeX math render -->
The trick here is to strip way the HTML entity of ampersand, & and replace it with a literal &; otherwise KaTeX renders the expression empty.
Obviously, you can replace the .js and .css URLs with other CDNs and script versions. Like:
<link href="https://cdn.jsdelivr.net/npm/katex@0.16.1/dist/katex.min.css">
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.1/dist/katex.min.js"></script>
That’s all folks!