Contents
Update (2020-11-29): Added information for kramdown v2.2 and GitHub Pages v207.
Writing Math in Markdown
Jekyll kramdown
Jekyll uses the kramdown converter to compile Markdown into HTML. As part of the conversion process, kramdown looks for “math blocks” that start and end with $$
. The double-dollar-sign is the only math block delimiter that kramdown supports. A math block that is both preceded and follwed by a newline is considered “display math.” Any non-display math block is called “inline math.”
There is one caveat: $$
-enclosed text that appears between an explicit pair of opening and closing HTML tags in the Markdown file might need special treatment. See the kramdown documentation on markdown="span"
and markdown="block"
attributes.
The math_engine
option in Jekyll’s _config.yml
determines what kramdown does with each math block. The following table shows how kramdown converts math blocks in Markdown into HTML with the default math_engine: mathjax
option.
Type | Markdown syntax | HTML output (v2.2+) | HTML output (v1 - v2.1) |
---|---|---|---|
display math | [newline] $$...$$ [newline] |
\[...\] |
<script type="math/tex; mode=display">...</script> |
inline math | [text] $$...$$ [text] |
\(...\) |
<script type="math/tex">...</script> |
The HTML output <script>
tags used by kramdown v1 through v2.1 are appropriate for MathJax 2.7, which renders them into math notation. However, MathJax 3 no longer uses the <script>
tags. This is explained in more detail below.
Another important note is that kramdown treats the \
character as an escape character. In order to have \(
or \[
appear in the generated HTML output, the Markdown file must have \\(
or \\[
.
MathJax
MathJax is a JavaScript library that parses HTML documents, identifies math expressions, and renders them in proper math notation. In both versions 2.7 and 3, MathJax looks for certain math delimiters:
The default math delimiters are
$$...$$
and\[...\]
for displayed mathematics, and\(...\)
for in-line mathematics. Note in particular that the$...$
in-line delimiters are not used by default.
See MathJax 2.7 and 3 documentation
Note that these delimiters are customizable. See MathJax 2.7 and 3.
In version 2.7, MathJax ran a preprocessor on HTML files that converted math delimiters into <script>
tags, similar to the process described for kramdown above. Then, a MathJax “input jax” would translate each <script>
tag into the internal MathJax format which is then rendered by an “output jax” based on the output format chosen by the user (e.g., HTML-CSS, SVG, or NativeMML).
However, MathJax 3 changes the paradigm by skipping <script>
tags altogether. Instead, it directly parses math delimiters in HTML documents and renders math expressions. As a result, MathJax 3 by default no longer accepts the <script>
tags that kramdown (before v2.2) outputs using math_engine: mathjax
option. As a workaround, MathJax 3 can also be extended to work with HTML documents that already have <script>
tags by adding an action to its renderActions
option. The first element in the list corresponding to the findScript
key is a number indicating the priority that the action runs. For reference, MathJax 3’s standard parsing is done using the find
action with a priority of 10.
window.MathJax = {
options: {
renderActions: {
findScript: [9, function (doc) {
for (const node of document.querySelectorAll('script[type^="math/tex"]')) {
const display = !!node.type.match(/; *mode=display/);
const math = new doc.options.MathItem(node.textContent, doc.inputJax[0], display);
const text = document.createTextNode('');
node.parentNode.replaceChild(text, node);
math.start = {node: text, delim: '', n: 0};
math.end = {node: text, delim: '', n: 0};
doc.math.push(math);
}
doc.findMath();
}, '']
}
}
};
Other sources
- MathJax 3 in Jekyll and Kramdown: describes the kramdown-MathJax 3 integration problem and proposes a different JavaScript solution to deal with the
<script>
tags that kramdown generates.
GitHub Pages
GitHub Pages currently always sets math_engine: mathjax
in _config.yml
, overriding any user-specified value (GitHub Help). This is a GitHub Pages limitation, and not a Jekyll limitation. However, there is a possibility that GitHub removes its math_engine: mathjax
requirement.
GitHub Pages also sporadically updates the version of kramdown that it uses. As of August 2020, GitHub Pages v207 now uses kramdown version 2.3+.
Setup
I have described how kramdown, MathJax, and GitHub Pages work individually. Now I describe what it means when these moving pieces all come together.
November 2020 onwards
Because I am using GitHub Pages to automatically generate my static site, I am stuck with Jekyll’s math_engine: mathjax
option in _config.yml
. Since GitHub Pages now uses kramdown v2.3, math blocks $$
in Markdown are converted to either inline \(...\)
or display \[...\]
markers. This means that I can directly use Mathjax 3 without resulting to the findScript
workaround.
I could have tried to avoid kramdown’s math blocks altogether by switching to single-dollar-sign $
delimiters for inline math and using \[...\]
delimiters for display math. However, this strategy does not always work because kramdown does not know to treat the math expression as a math block and may instead interpret LaTeX underscores as italics. (This is also why setting markdown: GFM
in _config.yml
is a bad idea, because GFM does not know to parse math separately from normal Markdown.)
Thus, my current setup is as follows:
Setup
- Set
use_math: true
in the preamble of the page. Write$
for an actual dollar-sign in the page; no need for any escaping.
Inline Math: [text] $$...$$ [text]
(note the double dollar signs)
- converted by kramdown into
[text] \(...\) [text]
in the HTML - MathJax 3 directly parses and renders the HTML as inline math
Display Math: [newline] $$...$$ [newline]
- converted by kramdown into
[newline] \[...\] [newline]
in the HTML - MathJax 3 directly parses and renders the HTML as display math
Wishful / possible future directions
- If kramdown supports custom math delimiters for either the
math_engine: mathjax
ormath_engine: null
options, then I could directly use\[...\]
and\(...\)
delimiters. - If GFM adds support for math blocks, then I could bypass kramdown altogether and specify
markdown: GFM
in_config.yml
. - Now that kramdown v2.2+ directly outputs LaTeX into the HTML, I may consider using KaTeX instead of MathJax.
pre-November 2020
Because I am choosing to upgrade to MathJax 3 and I am using GitHub Pages to automatically generate my static site, I am stuck with Jekyll’s math_engine: mathjax
option in _config.yml
. Therefore, math blocks $$
in Markdown are converted to <script>
tags, which I parse with MathJax by adding in the findScript
action to its set of renderActions
.
I could have tried to avoid kramdown’s math blocks altogether by switching to single-dollar-sign $
delimiters for inline math and using \[...\]
delimiters for display math. However, this strategy does not always work because kramdown does not know to treat the math expression as a math block and may instead interpret LaTeX underscores as italics. Furthermore, I am also holding out for the possibility that GitHub removes its math_engine: mathjax
requirement.
Thus, my current setup is as follows:
Setup
- Set
use_math: true
in the preamble of the page. To write an actual dollar-sign in the page, escape it as\$
. Such escaping is only necessary whenuse_math: true
is set in the preamble.
Inline Math
- legacy (but current) option:
[text] $$...$$ [text]
(note the double dollar signs)- converted by
kramdown
into[text] <script type="math/tex">...</script> [text]
- MathJax 3 uses the
findScript
action inrenderActions
of the MathJax configuration to identify these script tags
- converted by
- future (hopefully):
[text] $...$ [text]
or[text] \\(...\\) [text]
(double-backslash is necessary becausekramdown
treats\
as an escape character)- not converted by
kramdown
duringjekyll
Markdown-to-HTML compilation - rendered directly from LaTeX by MathJax 3 (after enabling
$
inline delimiter)
- not converted by
Display Math: [newline] $$...$$ [newline]
- legacy (but current) option
- converted by
kramdown
into<script type="math/tex; mode=display">...</script>
- MathJax 3 uses the
findScript
action inrenderActions
of the MathJax configuration to identify these script tags
- converted by
- future (hopefully)
- not converted by
kramdown
duringjekyll
Markdown-to-HTML compilation - rendered directly from LaTeX by MathJax 3
- not converted by
Known issue: Because the findScript
action is called separately from find
, different math expressions are parsed at different times. Suppose the findScript
action is called before find
, as illustrated in the example above. As a result, if there are new commands or operators (e.g., \newcommand{\bx}{\mathbf{x}}
) that are used by a math expression parsed by findScript
but defined within an expression parsed by find
, then the expression will fail to display properly.