MathJaxのLaTeXから画像ファイルを錬成する

# MathJaxのLaTeXから画像ファイルを錬成する

この記事は[アドカレ](https://qiita.com/advent-calendar/2023/hachinohe_kosen)に参加しています。

## LaTeXってなに？
平方根や、行列式などを綺麗に表示したいときがあります。そんなときに役に立つのが、LaTeXという記法です。例えば、Qiitaで$a = \sqrt{10}$と表示するには以下のように書きます。
```TeX
a = \sqrt{10}
```

LaTeXで表わせる数式記号はたくさんあります。
・[Qiitaでの様々な数式の書き方 ～ TeX記法を使ったサンプルコード付き](https://qiita.com/ttabata/items/92a4a72b042e6a2dbbad)
・[よく忘れるので数学のTeX記法をまとめ](https://qiita.com/shepabashi/items/27b7284d1f0007af533b)
・[場合分けをきれいに書く](https://qiita.com/Yarakashi_Kikohshi/items/26cbc47ea0b18906d1ff)
・[27.7 数式の書き方(1)](https://hwb.ecc.u-tokyo.ac.jp/wp/applications-2/latex/math/)

## MathJax
web上のLaTeXで書かれたコードを実際の数式に変換してくれるライブラリに[MathJax](https://www.npmjs.com/package/mathjax)があります。MathJaxはQiitaでも採用されていて、Qiitaの多くの記事ではMathJaxによって数式が描かれています。

## LaTeXから任意の画像ファイルへ
LaTeXから画像ファイルを作成するには、
LaTeX→SVG→DataURL(svg)→Image→canvas→DataURL(任意の画像形式)
のようにする必要があります。

### 1. LaTeXからSVG
MathJaxの`MathJax.tex2svgPromise`を用いることで、LaTeXコードからsvg要素を作成することができます。
```javascript
let str = "\\frac{1}{2}"
MathJax.tex2svgPromise(str, {display: true}).then((obj) => {
  let svg_ele = obj.childNodes[0]
})
```
`obj`はsvg文字列ではなく、nodeであることに注意する必要があります。`obj.childNodes[0]`に`svg`要素が、`obj.childNodes[1]`の方には`MathML`要素があります。

### 2. SVGをURL化
svg要素を`XMLSerializer().serializeToString`でXML形式の文字列にします。さらに、その文字列から[DataURL](https://developer.mozilla.org/ja/docs/Web/HTTP/Basics_of_HTTP/Data_URLs)を作成します。`encodeURIComponent`関数で文字列をエスケープする必要があります。
```javascript
let svg = new XMLSerializer().serializeToString(obj.childNodes[0])
let svg_url = "data:image/svg+xml;charset=utf-8;," + encodeURIComponent(svg)
```
DataURLは`data:(MIMEタイプ);(文字コード);,(データ)`のようにします。

### 3. URLからcanvasに描画
`Image()`コンストラクタでDataURLを読み込み、canvas要素にsvgを描画します。
```javascript
let cvs = document.createElement("canvas")
let img = new Image()
img.src = svg_url
img.onload = () => {
  cvs.width = img.width
  cvs.height = img.height
  cvs.getContext("2d").drawImage(img, 0, 0)
  let url = cvs.toDataURL("image/png")
}
```
canvasの`toDataURL`メソッドでcanvasの内容を画像データを表すDataURLに変換することができます。ユーザーがこのDataURLを踏むことで、画像ファイルをダウンロードすることができます。

:::note warn
`toDataURL`の引数ではMIMEタイプで画像形式を指定しますが、ブラウザが必ずしも指定した画像形式のエンコードに対応している訳ではありません。もしもブラウザがエンコードをサポートしていないMIMEタイプであった場合は、戻り値のDataURLがpng形式になります。(戻り値の最初が`data:image/png`で始まっていれば、引数で指定されたMIMEタイプをブラウザはサポートしていません。)
:::

### 4. URLから自動ダウンロード処理
URLからファイルを自動ダウンロードするような処理を書くには、aタグを使用します。`download`プロパティにファイル名、`href`プロパティにファイル先を示すURLがある状態でaタグがクリックされると、ファイルがダウンロードされます。
```javascript
let ele = document.createElement("a")
ele.download = "filename.png"
ele.href = url
ele.click()
```

## プログラム例
`download`をクリックすると、指定されたファイル名でファイルをダウンロードします。
```html
<span>file output: </span>
<input type="text" value="sample.png" placeholder="filename.filetype" spellcheck="false" id="text"/>
<input type="button" value="download" id="button" />

<script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"></script>
<script>
  let str = "\\frac{1}{2}"
  let text = document.getElementById("text")
  let button = document.getElementById("button")
  button.addEventListener("click", () => {
    let ele = document.createElement("a")
    ele.download = text.value
    let type = ele.download.replace(/[^\.]*\.(.*)/gu, "$1")
    if (type === "txt") {
      ele.href = "data:text/plain;charset=utf-8;," + encodeURIComponent(str)
      ele.click()
    }
    else {
      MathJax.tex2svgPromise(str, {display: true}).then((obj) => {
        let svg = new XMLSerializer().serializeToString(obj.childNodes[0])
        let svg_url = "data:image/svg+xml;charset=utf-8;," + encodeURIComponent(svg)
        if(type === "svg") {
          ele.href = svg_url
          ele.click()
        }
        else {
          let cvs = document.createElement("canvas")
          let img = new Image()
          img.src = svg_url
          img.onload = () => {
            cvs.width = img.width
            cvs.height = img.height
            cvs.getContext("2d").drawImage(img, 0, 0)
            ele.href = cvs.toDataURL("image/" + type)
            ele.click()
          }
        }
      })
    }
  })
</script>
```


## 参考文献
・[MathJaxでLaTeXをsvgに変換する](https://zenn.dev/maemon/articles/6cf4fb1675f6e3)
・[JavaScriptで動的に作成したテキストファイルをダウンロード](https://qiita.com/ledsun/items/93b0965c9720e0baf81c)
・[SVG イメージを JavaScript から保存する方法](https://qiita.com/aster-mnch/items/d89f1d1be43d89a6fe70)
・[[HTML/js] SVGファイルをPNGに変換するツール](https://qiita.com/tenmyo/items/00fb6548231b2da7ab1a)
・[Rendering MathJax output on <canvas>](https://stackoverflow.com/questions/20852529/rendering-mathjax-output-on-canvas)

## むすび
LaTeXコードからファイルを錬成する話でした。

宣伝((小声
[こんなかんじ](https://github.com/metaphysical-bard/mathjax-editor)のエディタを作っていたりするので、よければ。
　
　
[bsky](https://bsky.app/profile/drywaterfall.bsky.social)