# Metalsmith Plugins for Server-side KaTeX Processing

In a prior post, I briefly described the Metalsmith tool-chain I use to render my blog as a collection of static pages (no repeated server fetches, and minimal client-side Javascript). The tool-chain continues to work just fine, though my postings are still very sporadic. Recently, I spent some time trying to improve the Markdown support for formatting of LaTeX math expressions. The implementation I had running relied on two separate Markdown schemes, one supported in IPython files and the other in regular Markdown files. Since this was becoming a burden for me to remember which to use, I decided to clean up the KaTeX processing to only use one Markdown style involving ‘$’ tokens.

# IPython Processing

The Jupyter framework supports the presence of LaTeX commands surrounded by `$`

or `$$`

tokens. The text wrapped by a single-`$`

will appear inline with the rest of the text in the current paragraph, while text wrapped with a double-`$`

will appear on its own in a bigger format and centered on the page. This makes it very easy to enter math equations such as $E = mc^2$ and symbols such as $\lambda$.

Unfortunately, the notebookjs code I rely on to render the IPython files into HTML does not support such extension to Markdown, and so I worked out a scheme where I changed the delimiters and then relied on in-browser Javascript to ultimately do the rendering using the KaTeX package. In order to bring the processing back into the tool-chain, I needed to write something that would perform the KaTeX expansion without harming the normal Markdown processing done by `notebookjs`

.

The solution I came up with was a pre-processor called notebookjs-katex. It only works on IPython Markdown cells, transforming any embedded `$...$`

or `$$...$$`

spans into HTML generated by the KaTeX library. Since `notebookjs`

will accept and ignore embedded HTML, this seems to be doing just fine. Here is how I use it in my Metalsmith build script:

```
var KatexFilter = require("notebookjs-katex");
var kf = new KatexFilter();
...
var ipynb = JSON.parse(fs.readFileSync('/path/to/notebook.ipynb'));
kf.expandKatexInNotebook(ipynb); // (1)
...
var notebook = notebookjs.parse(ipynb); // (2)
var html = notebook.render().outerHTML;
```

You can see an example of the new KaTeX plugin at work here.

# Markdown Processing

The Markdown processor I use in my tool-chain is the Remarkable package. It is fast and supports a variety of useful Markdown extensions. Although it does not natively support math expression tagging, it does support expansion via plugins. Since I found no existing plugins for KaTeX rendering, I decided to roll my own and call it — surprise! — remarkable-katex.

To use the plugin is incredibly easy since there are no customization options yet:

```
var Remarkable = require('remarkable');
var plugin = require('remarkable-katex');
var md = new Remarkable();
md.use(plugin);
```

# End Result

Removing the in-browser rendering proved to be less work than I had originally thought. Now all KaTeX processing takes place in the site build script, page load time is reduced and there is no more *flashing* due to the in-browser KaTeX processing. Segregating the code into separate NPM modules makes the build script code a bit cleaner — and perhaps someone else will be able to use it as well.

Finally, some math formatting examples (taken from TiddlyWiki KaTeX Demo page.

## Example 1

$f(x) = \int_{-\infty}^\infty \hat f(\xi)\,e^{2 \pi i \xi x} \,d\xi$

## Example 2

$\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+\cdots} } } }$

## Example 3

$1 + \frac{q^2}{(1-q)}+\frac{q^6}{(1-q)(1-q^2)}+\cdots = \prod_{j=0}^{\infty}\frac{1}{(1-q^{5j+2})(1-q^{5j+3})}, \quad\quad \text{for }\lvert q\rvert<1.$

## Example 4

$I_n = \frac{-ln U}{\lambda}$

## Inline Examples

The equation $\color{#9c0}c = \pm\sqrt{a^2 + \color{#F44}{b^2}}$ looks familiar. Since $a\equiv b \mod{2}$, we are done. Unfortunately, in the interval $T_0$ to $T_1$ we have no data.