内容管理

数学公式

介绍在内容中排布数学公式的常见方案、配置与注意事项。

概览

用 LaTeX 标记书写的数学公式在学术与科技内容中很常见,浏览器通常借助 MathJax 或 KaTeX 这类开源 JavaScript 排版引擎把它渲染出来。例如下面这段 LaTeX 标记:

\[
\begin{aligned}
KL(\hat{y} || y) &= \sum_{c=1}^{M}\hat{y}_c \log{\frac{\hat{y}_c}{y_c}} \\
JS(\hat{y} || y) &= \frac{1}{2}(KL(y||\frac{y+\hat{y}}{2}) + KL(\hat{y}||\frac{y+\hat{y}}{2}))
\end{aligned}
\]

公式可以与正文同行显示(行内公式),也可以独占一块(块级公式,即 display 模式)。究竟按哪种方式呈现,取决于包裹标记的定界符(delimiter)。定界符成对出现,一对由开定界符与闭定界符组成,两者可以相同,也可以不同。

除了在客户端渲染,也可以选择在构建站点时用 transform.ToMath 函数把标记渲染成数学标记;本文介绍的是前一种做法。

配置 passthrough(原样透传)

第一步,在项目配置中启用并配置 Goldmark 的原样透传(passthrough)扩展。该扩展会在被定界符包住的片段中保留原始 Markdown,连定界符本身也一并保留:

[markup.goldmark.extensions.passthrough]
  enable = true
  [markup.goldmark.extensions.passthrough.delimiters]
    block = [['\[', '\]'], ['$$', '$$']]
    inline = [['\(', '\)']]

[params]
  math = true

上面的配置会让每个页面都渲染数学标记,除非在某页的前置元数据(front matter)中把 math 参数显式设为 false。若希望按页启用,则把项目配置里的 math 设为 false,只在需要公式的页面把前置元数据中的 math 设为 true,模板中的用法见下一步。

只保留块级公式、不要行内透传时,把 inline 一行删掉即可;也可以自定义成对的定界符,前提是与下一步 JavaScript 中的设置保持一致,例如块级用 @@、行内用 @。

需要注意:上面的配置排除了 $...$ 这对行内定界符。虽然可以把它加进配置与 JavaScript,但此后在数学环境之外使用 $ 符号时必须双重转义,否则会产生意料之外的排版,详见行内定界符。

引入渲染脚本

第二步,创建一个局部模板(partial)来加载 MathJax 或 KaTeX。下面的例子加载 MathJax,保存为 layouts/partials/math.html:

<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@4/tex-mml-chtml.js"></script>

<script>
  MathJax = {
    tex: {
      displayMath: [['\\[', '\\]'], ['$$', '$$']],  // 块级
      inlineMath: [['\\(', '\\)']]                  // 行内
    },
    loader: {
      load: ['ui/safe']
    },
  };
</script>

这里的定界符必须与项目配置中的一致。

按页加载脚本

第三步,在基础模板(base template)中按条件调用该局部模板:

<head>
  {{ if .Param "math" }}
    {{ partialCached "math.html" . }}
  {{ end }}
</head>

页面在前置元数据中把 math 设为 true 时脚本才会加载;没有设置时,条件会回退到项目配置中的 math 参数。因此第四步是:若项目配置里把 math 设为 false,就必须在需要公式的页面前置元数据中把它设回 true:

title = '数学示例'
date = 2024-01-24T18:09:49-08:00

[params]
  math = true

书写公式

第五步,按声明的定界符书写公式。行内公式与块级公式分别写作:

这是一个行内公式 \(a^*=x-b^*\)。

下面是块级公式:

\[a^*=x-b^*\]

$$a^*=x-b^*$$

块级公式独占一行,前后留出空行更稳妥。定界符之间不要混入其他 Markdown 结构。

行内定界符

上面的配置、JavaScript 与示例都用 \(...\) 作为行内定界符。$...$ 是常见的替代写法,但在数学环境之外使用 $ 符号时容易产生意料之外的排版。若确实要加入 $...$,在数学环境之外使用 $ 时必须双重转义:

只要你能解出 $y = x^2$,我就给你 \\$2。

此外,若使用了 $...$ 又偶尔在数学环境之外写 $,必须选 MathJax 而不是 KaTeX,以避开 KaTeX 的这一限制导致的错误排版。

排版引擎

MathJax 与 KaTeX 都是开源的 JavaScript 排版引擎,两者都能渲染本文的公式。要改用 KaTeX,把第二步的局部模板换成加载 KaTeX 的样式表与脚本(可以从库的 CDN 或发行包取得),并在 DOM 就绪后调用 renderMathInElement 渲染正文:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex/dist/katex.min.css">

<script defer src="https://cdn.jsdelivr.net/npm/katex/dist/katex.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/katex/dist/contrib/auto-render.min.js"
        onload="renderMathInElement(document.body);"></script>

自动渲染时传入的 delimiters 列表要与项目配置一致,并保留 throwOnError: false 之类的容错设置。若要使用 Markdown 写就的化学公式,MathJax 无需额外配置即可支持;KaTeX 需要按官方文档启用 mhchem 扩展:

$$C_p[\ce{H2O(l)}] = \pu{75.3 J // mol K}$$

常见问题

  • 开启透传后仍要确认前端库确实被加载,否则页面上只会显示公式源码。
  • 使用 $...$ 行内定界符时,数学环境之外的 $ 必须双重转义;$...$ 与 KaTeX 同时使用时排版容易出错,建议改用 MathJax。
  • 项目配置与 JavaScript 中的定界符必须完全一致,改了一处就要同步另一处。
  • 公式中出现 {{ 之类的双花括号序列时,可能会与短代码(shortcode)定界符冲突,需要改写或在短代码中包裹处理,写法见短代码。
  • 若公式在列表、表格或引用块中显示异常,先检查该处的定界符是否被 Markdown 结构拆开。

相关主题