rehype-mathml: TeX 数式を MathML にする rehype プラグイン

この記事は、[フラー株式会社 Advent Calendar 2024](https://qiita.com/advent-calendar/2024/fuller-inc) の 7 日目の記事です。6 日目は [su8](https://zenn.dev/su8) の[サポート終了間近 しがない 30 代エンジニアの本棚（2024 年版）](https://zenn.dev/su8/articles/c9a2e34d4602a0)でした。

## はじめに

[unified](https://unifiedjs.com/)（[remark](https://github.com/remarkjs/remark)、[rehype](https://github.com/rehypejs/rehype)）を使って Markdown を HTML へ変換する人は多いでしょう。unified は Gatsby や Nuxt.js、Astro など、多くのフレームワークで利用されています。

unified で数式を扱う場合、Markdown 中の `$f(x)=ax+b$` のような TeX（LaTeX）数式を `remark-math` で見つけ、`rehype-mathjax` や `rehype-katex` で MathJax や KaTeX により表示できる形式に変換するのが一般的です。

ブラウザで数式を表示するために、HTML に MathML を記述する方法があります。KaTeX は数式表示のための HTML に変換するだけでなく、MathML への変換もサポートしています。しかし、KaTeX は少し TeX 数式のカバレッジが低く、純粋な MathML 以外も出力されるなど、いくつかの不満がありました。

そんなとき、カバレッジが優れ、軽量な MathML を出力する [Temml](https://temml.org/) というライブラリを見つけました。Temml を使って MathML に変換したいと思い、[`@daiji256/rehype-mathml`](https://www.npmjs.com/package/@daiji256/rehype-mathml) を作成しました。

## `rehype-mathml` で出来ること

このような Markdown から MathML を含む HTML に変換できます。

```md
本文中や $f(x)=ax+b$、別行立てで、

$$
\begin{align}
  f(x) &= ax + b\\
  g(x) &= cx^{2} + dx + e
\end{align}
$$

MathML による数式表示が可能です。
```

<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="zxOrVZw" data-pen-title="MathML Sample" data-user="Daiji256" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/Daiji256/pen/zxOrVZw">
  MathML Sample</a> by Daiji (<a href="https://codepen.io/Daiji256">@Daiji256</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

## MathML

MathML について簡単に説明します。

[MathML（Mathematical Markup Language）](https://www.w3.org/Math/)とは XML/HTML5 で数式を記述するためのマークアップ言語です。[Can I use](https://caniuse.com/?search=MathML) によると、現在ではメジャーなブラウザが MathML をサポートしているそうです。

![Can I use MathML? Baseline 2023 Newly available across major browsers](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/699841/af09cf77-8e9e-3780-8701-bab507b8be3b.png)

ブラウザで数式を表示する方法として、MathML の他に、MathJax や KaTeX があります。これらは JavaScript の処理や画像の読み込み、HTML の肥大化などによりパフォーマンスが優れないです。一方で MathML は標準機能で数式を扱い非常に軽量です。さらに、MathML は数式の表示だけでなく、そのコードに数式としての意味を持たせるため、スクリーンリーダーなどでも扱うことができます[^tex-to-mathml-zannen]。

[^tex-to-mathml-zannen]: `rehype-mathml` では TeX 数式を MathML に変換するため、数式が持つ情報量は TeX 数式までになります。そのため、MathML ほどの情報量はありません。

しかし、MathML はコンピューターが認識しやすいように設計されたものであり、人間が直接扱うのは難しいです。そのため、MathML 専用のエディタなどを使うのが一般的です。

<details>
<summary>MathML による数式記述例</summary>

例えば二次方程式の解の公式

$$
x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
$$

は MathML で記述すると次のようになる。

```html
<math>
  <mrow>
    <mi>x</mi>
    <mo>=</mo>
    <mfrac>
      <mrow>
        <mo>−</mo>
        <mi>b</mi>
        <mo>±</mo>
        <msqrt>
          <mrow>
            <msup>
              <mi>b</mi>
              <mn>2</mn>
            </msup>
            <mo>−</mo>
            <mn>4</mn>
            <mi>a</mi>
            <mi>c</mi>
          </mrow>
        </msqrt>
      </mrow>
      <mrow>
        <mn>2</mn>
        <mi>a</mi>
      </mrow>
    </mfrac>
  </mrow>
</math>
```

</details>

## `rehype-mathml` の使い方

`rehype-mathjax` や `rehype-katex` をすでに使っている場合、その部分を `@daiji256/rehype-mathml` に置き換えるだけで完了します。詳しくは [rehype-mathml/README.md](https://github.com/Daiji256/rehype-mathml/blob/main/README.md) に書いてあります。

### インストール

npm の場合、

```sh
npm install @daiji256/rehype-mathml
```

でインストールできます。

### `remark-math` — `rehype-mathml`

`remark-math` と `rehype-mathml` を以下のようにして使うことができます。

```typescript
import { unified } from 'unified';
import rehypeParse from 'rehype-parse';
import rehypeStringify from 'rehype-stringify';
import remarkRehype from 'remark-rehype'
import remarkMath from 'remark-math';
import rehypeMathML from '@daiji256/rehype-mathml';
import { read, write } from 'to-vfile';

const file = await unified()
  .use(remarkParse)
  .use(remarkMath)
  .use(remarkRehype)
  .use(rehypeMathML)
  .use(rehypeStringify)
  .process(await read('input.html'));

file.basename = 'output.html';
await write(file);
```

## 文献

1. [`rehype-mathml`（GitHub）](https://github.com/Daiji256/rehype-mathml)
2. [`rehype-mathml`（npm）](https://www.npmjs.com/package/@daiji256/rehype-mathml)
3. [unified](https://unifiedjs.com/)
4. [remark-math](https://github.com/remarkjs/remark-math)
5. [W3C Math Home](https://www.w3.org/Math/)
6. ["MathML" | Can I use](https://caniuse.com/?search=MathML)
7. [`rehype-mathml`: TeX数式をMathMLにするrehypeプラグイン](https://daiji256.github.io/posts/rehype-mathml/)[^daiji-blog]

[^daiji-blog]: ブログにも同じ記事をあげています。ブログでは実際に `rehype-mathml` を利用しています。

---

[フラー株式会社 Advent Calendar 2024](https://qiita.com/advent-calendar/2024/fuller-inc) の 8 日目は[いのりこ](https://inoriko.hatenablog.com/about)さんの[復職記 〜第二子〜](https://inoriko.hatenablog.com/entry/2024/12/08/113627)です。