在Hugo中使用MathJax

有时候,会有需要在网页中展示数学公式。 与代码高亮不同,Hugo本身是不支持渲染数学公式的。 因此,只能用前端的方式实现。

在已经生成好的HTML页面中,使用JavaScript来渲染LaTeX形式的数学公式,是一个可行思路。 MathJax是这方面最流行的JavaScript库。

官方文档的MathJax使用方案有一些问题,本文提供的方案胜于官方。

MathJax简介

MathJax是一个开源的JavaScript数学公式渲染引擎,支持LaTeX、MathML、AsciiMath等形式的数学公式,并且适配所有现代的浏览器。

A JavaScript display engine for mathematics that works in all browsers.

No more setup for readers. It just works.

测试样例

与代码类似,数学公式也有有行内(inline)公式和区块(block)公式。 前者需要与同行的其它文字混排,而后者需要独占一行,居中显示。 以下展示了两个行内公式和一个区块公式的测试代码,可以放到Markdown中测试MathJax的渲染效果。

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}$$

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}$$

方案

这里给出两个方案,分别是官方方案与本站实际使用的自定义方案。

官方方案与改进

官方方案有三处修改,其实添加第一处,引入MathJax就已经可以支持区块公式了。

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
</script>

但是,这样对行内公式仍然无法支持。 而除了支持行内公式,还有Markdown特殊字符的转义问题,如下划线_。 为了支持无需转义地写公式,行内公式推荐写成行内代码,用``来包含,而区块公式则推荐用<div></div>来包含。

例如:

When `$a \ne 0$`, there are two solutions to `\(ax^2 + bx + c = 0\)` and they are:

<div>$$
x = {-b \pm \sqrt{b^2-4ac} \over 2a}
$$</div>

而为了解决样式问题,把普通的codeMathJax分开,官网使用了加后缀的方式。 根据《MathJax in Markdown - Doswa》提供的方法,把MathJaxcode样式自动改成code has-jax

<script type="text/x-mathjax-config">
MathJax.Hub.Config({
  tex2jax: {
    inlineMath: [['$','$'], ['\\(','\\)']],
    displayMath: [['$$','$$'], ['\[','\]']],
    processEscapes: true,
    processEnvironments: true,
    skipTags: ['script', 'noscript', 'style', 'textarea', 'pre'],
    TeX: { equationNumbers: { autoNumber: "AMS" },
         extensions: ["AMSmath.js", "AMSsymbols.js"] }
  }
});
</script>

<script type="text/x-mathjax-config">
  MathJax.Hub.Queue(function() {
    // Fix <code> tags after MathJax finishes running. This is a
    // hack to overcome a shortcoming of Markdown. Discussion at
    // https://github.com/mojombo/jekyll/issues/199
    var all = MathJax.Hub.getAllJax(), i;
    for(i = 0; i < all.length; i += 1) {
        all[i].SourceElement().parentNode.className += ' has-jax';
    }
});
</script>

上面两段JavaScript就是官网第二处修改。 前者做了一些配置,而后者就是自动给className has-jax后缀。 而后,下面的第三处修改,则是在CSS中对这种特殊的MathJax进行样式处理,否则行内公式的显示会有些奇怪。

code.has-jax {
    font: inherit;
    font-size: 100%;
    background: inherit;
    border: inherit;
    color: #515151;
}

然而,这样做亲测无效! 祝你好运!

本站方案(note.qidong.name)

本站在添加MathJax时,把所有修改写成了一个layouts/partials/mathjax.html文件:


<script type="text/javascript"
        async
        src="https://cdn.bootcss.com/mathjax/2.7.3/MathJax.js?config=TeX-AMS-MML_HTMLorMML">
MathJax.Hub.Config({
  tex2jax: {
    inlineMath: [['$','$'], ['\\(','\\)']],
    displayMath: [['$$','$$'], ['\[\[','\]\]']],
    processEscapes: true,
    processEnvironments: true,
    skipTags: ['script', 'noscript', 'style', 'textarea', 'pre'],
    TeX: { equationNumbers: { autoNumber: "AMS" },
         extensions: ["AMSmath.js", "AMSsymbols.js"] }
  }
});

MathJax.Hub.Queue(function() {
    // Fix <code> tags after MathJax finishes running. This is a
    // hack to overcome a shortcoming of Markdown. Discussion at
    // https://github.com/mojombo/jekyll/issues/199
    var all = MathJax.Hub.getAllJax(), i;
    for(i = 0; i < all.length; i += 1) {
        all[i].SourceElement().parentNode.className += ' has-jax';
    }
});
</script>

<style>
code.has-jax {
    font: inherit;
    font-size: 100%;
    background: inherit;
    border: inherit;
    color: #515151;
}
</style>

这里,把官网的三处修改合并成一个partial。 此外,还把MathJax的CDN从cdnjs.cloudflare.com替换成了cdn.bootcss.com,更好地支持国内。 因为个人的一些写作习惯,还替换了区块公式的第二标志,从中括号[]换成双中括号。

把这个partial模板添加到<head>中,即可正常工作。

{{ partial "mathjax.html" . }}

本站新方案(2020年更新)

为了与时俱进,本站新方案使用v3版本。

{{ if .Params.math }}
<script>
  MathJax = {
    tex: {
      inlineMath: [["$", "$"]],
    },
    displayMath: [
      ["$$", "$$"],
      ["\[\[", "\]\]"],
    ],
    svg: {
      fontCache: "global",
    },
  };
</script>
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
<script
  id="MathJax-script"
  async
  src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"
></script>
{{ end }}

以上是新的mathjax.html方案,使用的是最新的v3版本,保持更新。 还额外加上了.Params.math的检查,避免所有页面都需要加载它。 在需要使用的Hugo页面,可以在Front Matter中添加一个配置,显式指定使用Mathjax。 以下为TOML形式的示例:

+++
...
math = true
+++

(c)(r)等转义问题

在Hugo使用blackfriday的情况下,(c)可转换为©,(r)可转换为®。

$$
AveP = \int_0^1 p(r) dr
$$

于是,以上公式会转义,发生显示问题。

以上提到的<div>方案,是可以解决这个问题的。 此外,要禁用全局的这种转义,也可以在config.toml中添加以下配置,禁用该功能。

[blackfriday]
smartypants = false

效果如下:

$$ AveP = \int_0^1 p(r) dr $$

如果是使用goldmark,则无上述问题。 Hugo的v0.60.0版本后,默认使用goldmark

参考

官网的《Enable MathJax》,是绝佳的参考,但有上述问题。 此外还有中文版的《MathJax Support - Hugo中文文档》,基本是官网内容的翻译,不过内容略有滞后,比如CDN代码仍然是cdn.mathjax.org的,不推荐使用。

另外还有一篇《Setting MathJax with Hugo | Hi, I am David》,其中解决了MathJax.Hub.Queue那段JavaScript的问题。 本站的方案,受益于此。


相关笔记