超高速な数式表示（KaTeX より高速！）

この記事は静的サイトジェネレータ向けの内容です。もちろん動的なサイトでも使えますが，おすすめしません。

## はじめに

僕のブログは静的サイトジェネレータの HUGO で作成しています。ここでは HUGO 用の説明をしますが，他の静的サイトジェネレータ（Hexo，Jekyll など）でも使える内容だと思います。

静的サイトジェネレータはその名の通り，静的なサイトを生成します。WordPress のようにユーザがサイトにアクセスするたびに，ページを作成する動的なものと比べ，読み込み速度などのパフォーマンスが高いメリットがあります。

科学技術に関する内容の記事を書く場合，サイト上に数式を表示したくなります。数式表示には MathJax や KaTeX がよく利用されています[^katex]。これらはユーザがサイトにアクセスすると，LaTeX 記述の数式を JavaScript を用いてブラウザ上で表示させます。この処理のせいで，サイトにアクセスした直後少しの間，数式がちゃんと表示されません。

[^katex]: KaTeX は MathJax より早いです。僕のブログでは KaTeX を利用していました。

当たり前ですが，数式の見た目は何度読み込んでも同じです。せっかく HUGO を使っているのに，これでは何となく嫌な感じです。最初から HUGO で HTML を生成するときに数式を埋め込んでしまえば，ユーザがアクセスするたびに，MathJax や KaTeX の処理をしなくていいです。

**こんなこといいな できたらいいな...**

できます！

## 目標

このコードのように LaTeX 数式コードを SVG へ変換し挿入する Shortcode を作成することを目標にします。作り方なんて興味ないから使い方教えろって人は[使い方](#使い方)を見てください。

```
{{< tex2svg >}}E=mc^2{{< /tex2svg >}}
```
```svg
<svg> ... </svg>
```

## いざ作成

アイデアは文献 4 の [tex2svg (GitHub)](https://github.com/atishay/tex2svg/) と同じです。**個人的に**使いやすくするようにカスタマイズします。

### LaTeX から SVG に変換

LaTeX を SVG に変換するツールは色々あります。しかし，HUGO から外部コマンドは実行できないので，簡単ではなさそうです。

HUGO は `getJSON` でインターネット上の JSON を取得することができます。LaTeX を SVG の情報を含む JSON を返す API を探したところ，[tex2svg](http://tex2svg.herokuapp.com/) と [CODECOGS](https://codecogs.com/) が見つかりました。

- [tex2svg](http://tex2svg.herokuapp.com/)：MathJax によって数式を生成します。Heroku で API が公開されていてのレスポンスは少し遅いです。
- [CODECOGS](https://codecogs.com/)：昔からある有名な奴です。SVG 以外に PNG や GMP とかも生成できます。ベースラインが無く，インラインで表示すると微妙になる。

インライン表示が綺麗に見えないと嫌なので，tex2svg を利用します。

tex2svg は [`https://tex2svg.herokuapp.com/?`+`q=E=m^2`+`&inline=true`](https://tex2svg.herokuapp.com/?&q=E=m^2&inline=true) のように叩きます。`q=` に表示したい数式の LaTeX コードを書きます。`&inline=true` を書くとインライン表示になります。また，ディスプレイ表示のとき `class=tex`，インライン表示のとき `class=i-tex` にしてくれます。

API は次のような JSON を返します。`svg` に `vertical-align` などのパラメータもすでに含まれているので，`svg` だけを利用します。

```json
{
  "speakText": "equation",
  "svg": "<svg ...",
  "width":"...ex",
  "height":"....ex",
  "style": "vertical-align: ...ex;"
}
```

### Shortcodes

`getJSON` は URLが `str1str2str3` のとき `"str1" "str2" "str3"` のように分割して指定できます。`(querify "q" .Inner)` は `{{< tex2svg >}}` と `{{< /tex2svg >}}` の間のテキストを `q=%5Cfrac%7B1%7D%7Bx%7D` みたいにします。インライン表示するときは `&inline=true` を加えます。`svg` には `\n` などのコードを含むので，それを `safeHTML` でいい感じにして表示させます。

```html
{{- $json := getJSON "https://tex2svg.herokuapp.com/" "?" (querify "q" .Inner) "&inline=true" -}}
{{- $json.svg | safeHTML -}}
```

Shortcode の引数を指定して表示方法の選択ができたほうが便利です。`{{- if eq (.Get 0) "display" -}}` で引数に `display` の有無に応じて，`<span>` と `<div>` を分岐し，`&inline=true` を適宜付けます。具体例は次節の[使い方](#使い方)で紹介します。

## 使い方

この節では使い方だけを説明します。コピペで簡単に利用できるので，「中身とか知らねーけど使わせろ」って人はここだけ見てください。

`shortcodes` に次のコードを `tex2svg.html`[^tex2svg.html] として保存してください。

[^tex2svg.html]: 好きな名前で構いません。僕は短いほうが楽なので `eq.html` にしています。

```html
{{- if eq (.Get 0) "display" -}}
<div class="displaystyle">
  {{- $json := getJSON "https://tex2svg.herokuapp.com/" "?" (querify "q" (replace .Inner "\\bm" "\\boldsymbol")) -}}
  {{- $json.svg | safeHTML -}}
</div>
{{- else -}}
<span class="inline">
  {{- $json := getJSON "https://tex2svg.herokuapp.com/" "?" (querify "q" (replace .Inner "\\bm" "\\boldsymbol")) "&inline=true" -}}
  {{- $json.svg | safeHTML -}}
</span>
{{- end -}}
```

次のように Markdown で記事を書いて見てください。

```
ディスプレイ表示の数式は
{{< tex2svg display >}}
  (f \ast g)(t)=\int_{-\infty}^{\infty} f(\tau) g(t - \tau) \, \mathrm{d}\tau
{{< /tex2svg >}}
となる。

インライン表示の数式は
{{< tex2svg >}}
  (f \ast g)(t)=\int_{-\infty}^{\infty} f(\tau) g(t - \tau) \, \mathrm{d}\tau
{{< /tex2svg >}}
となる。
```

次のような表示（出力）が確認できます。

---

ディスプレイ表示の数式は

```math
(f \ast g)(t)=\int_{-\infty}^{\infty} f(\tau) g(t - \tau) \, \mathrm{d}\tau
```

となる。

インライン表示の数式は $(f \ast g)(t)=\int_{-\infty}^{\infty} f(\tau) g(t - \tau) \, \mathrm{d}\tau$ となる。

---

もちろん `align` 環境も使えます。

```math
\begin{align}
  f(x) &= x+1\\
  g(x) &= x^2\\
  h(x) &= \frac{1}{\sqrt{x}}
\end{align}
```

インライン表示のときは `<span class="inline"> ... </span>` で囲み，ディスプレイ表示のときは `<div class="display"> ... </div>` で囲みます。CSS で設定することで，表示方法を簡単に設定できます。

## おまけ

### カスタマイズ

#### ディスプレイ表示を中央へ

ディスプレイ表示の時は TeX みたいに中央に置きたい場合は CSS で左右にマージンを与えます。

```css
.tex {
  display: block;
  margin-left: auto;
  margin-right: auto;
}
```

#### フォントサイズを変更したい

ちょっと本文より大きくて嫌だな～と思ったので，フォントサイズを $0.9$ 倍させました。

```css
.tex {font-size: 0.9em;}
.i-tex {font-size: 0.9em;}
```

#### `\bm` を使いたい

MathJax はデフォルトでは `\bm` を使えないので，代わりに `\boldsymbol` で太字にします。`\bm` を `\boldsymbol` に置換すればもっと便利です。

```html
replace .Inner "\\bm" "\\boldsymbol"
```

#### テキスト選択したい

単なる画像としての SVG なので，数式をコピペできません。SVG 内に見えないテキストを用意してコピペできるようにしたいと思います。`replace` を使って `</svg>` を `<text> ... </text></svg>` に置換して，無理やりテキスト情報を入れます。CSS テキストのスタイルを指定します。色を無色 `rgba(0,0,0,0)` にします。SVG 内のフォントサイズがはちょっと**アレ**ですごく小さいみたいなので，適当に超大きくなるように設定します。

```html
{{- (replace $json.svg "</svg>" (printf "<text x=\"0\" y=\"0\" class=\"eq-text\">%s</text></svg>" $.Inner)) | safeHTML -}}
```
```css
.eq-text {
  fill: rgba(0, 0, 0, 0);
  font-size: 100ex;
}
```

### tex2svg

MathJax にはパッケージを追加したり，フォントを変更する機能があります。tex2svg ではデフォルト状態になっています。MathJax の設定をやりたい場合は自分で API を用意してください。ソースコードは [tex2svg (GitHub)](https://github.com/atishay/tex2svg/) を参考にすれば良いと思います。ローカルで HUGO を使う場合は `localhost:3000` に API をたてたとしたら，`https://tex2svg.herokuapp.com/` を `http://localhost:3030/` に置きかえらば良いです。僕は GitHub Actions で HUGO しているので，tex2svg を直接利用しています[^myapi]。

[^myapi]: まあ，自分で Heroku とか Repl.it とかで API を公開すればいいんだけどね...

## 文献

1. [超高速な数式表示（KaTeX より高速！）](https://daiji256.github.io/posts/tex2svg/)
2. [MathJax](https://www.mathjax.org/)
3. [KaTeX](https://katex.org/)
4. [tex2svg (API)](http://tex2svg.herokuapp.com/)
5. [tex2svg (GitHub)](https://github.com/atishay/tex2svg/)
6. [CODECOGS](https://codecogs.com/)