MathJax v3.x メモ-1 MathJaxの導入

現在の最新版は8/25リリースのv3.1らしい

MathJax公式ドキュメント

何かあったらこ↑こ↓を読んでくれよな〜頼むよ〜

注意

ちょっと頑張って和訳したり実験したりしているだけです,あんまり本気でアテにするなよ…?

前提

(特には)ないです.

MathJaxの導入

最も基本の型

こ↑こ↓にある通りだと

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

<head></head>にぶち込めばおk. ちなこのcdn.jsdelivrはこのままだと強制的に最新版を読み込ませてくるので,それが嫌なら

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

のように,指定したいバージョンを@3.x.yみたいにフル記述してやればおk. これでもうバージョン管理は…完璧やな!

概念として

「html本文を読み取った内容を読める数式として出力する」という性質上,html本文に記述する入力とページに表示される出力のそれぞれに,方式が2つずつ存在する. 入力ならば

  • TeX
    • みなさんお馴染みのまさしくアレ
  • MML
    • XMLとしてXHTMLにぶち込まれる

,出力ならば

  • chtml
    • どうやらこれはCSS+HTMLを意味するらしい,決してCompact HTMLではない
  • svg
    • どんだけ拡大しても品質が劣化しないヤツ,これを生成して直に埋め込むらしい

,これをどう設定するかによって最初の話からがらりと変わってくる…のだが,俺はTeXsvgが好きだからよ,折角だしTeX入力・svg出力を選ぶぜ!

用途ごとの使い分け

じゃあさっきの例だとtex-mml-chtml.jsを呼び出すということだ,そしてこれは「tex型の入力もmml型の入力も受け入れてchtmlで出力する」ということ,他のパターンならこ↑こ↓にある通り

なんか-fullって付いてあるのが強そうだな!じゃあ俺は

<script type="text/javascript" id="MathJax-script" async
  src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg-full.js">
</script>

<head></head>にぶち込んでやればいいんだな!

☝️-fullってなんだよ

こ↑こ↓によると,TeXの入力の処理に使うライブラリを事前に全てロードさせるオプションらしい.

更なるオプションについて

実は最初のロードの時点で使うライブラリやその動作の設定自由自在にできたりする,TeXのオプションだったりMathMLのオプションだったりAsciiMathのオプションがある,その上でTeXにはこれだけのライブラリを個別に使い分けられるだからこれほどまでにTeXを勧めているという節もある. じゃあ試しに書いてみるか,さっきのinput/tex-fullのとこには

This is the most complete TeX input component. It includes the main TeX/LaTeX input parser, along with all the TeX extensions, and is configured to enable all of them other than physics and colorv2. You can add these two to the packages array in the tex section of your MathJax configuration, though you should remove the color extension if you add the colorv2 extension, and should remove the braket extension if you enable the physics package. これは最も完全なTeX入力コンポーネントです。 これには、メインのTeX / LaTeX入力パーサーとすべてのTeX拡張機能が含まれており、物理学とcolorv2以外のすべてを有効にするように構成されています。 これら2つをMathJax構成のtexセクションのpackages配列に追加できますが、colorv2拡張機能を追加する場合はcolor拡張機能を削除し、physicsパッケージを有効にする場合はbracket拡張機能を削除する必要があります。

って書いてある,つまり-fullした上で更にcolorbracketを減らしてcolorv2physicsを入れることができる(そしたらコマンドの重複がなく安全に動作する)らしいな? こ↑こ↓の全体像とこ↑こ↓の説明

This array lists the names of the packages that should be initialized by the TeX input processor. The input/tex and input/tex-full components automatically add to this list the packages that they load. If you explicitly load addition tex extensions, you should add them to this list. For example: この配列は、TeX入力プロセッサによって初期化される必要があるパッケージの名前をリストします。 input / texおよびinput / tex-fullコンポーネントは、ロードするパッケージをこのリストに自動的に追加します。 追加のtex拡張機能を明示的にロードする場合は、それらをこのリストに追加する必要があります。

そしてこ↑こ↓の例文を見る限りだと,なんだか

MathJax = {
  loader: {load: ['[tex]/colorv2', '[tex]/physics']},
  tex: {
    packages: {
      '[+]': ['colorv2', 'physics'],
      '[-]': ['color', 'bracket']
    }
  }
};

というような形が推奨されているように思うなぁ…

urlとライブラリの対応関係

  • input/tex-base
    • baseのみ
      • requireautoloadも入ってないので,本文中のTeX記法からの拡張が不可能になる
  • input/tex
    • base
    • ams
    • newcommand
    • require
    • autoload
    • configmacros
    • noundefined
    • 管轄外のコマンドを呼び出すと,physicscolorv2以外なら勝手にrequireする
      • ちなみに自分でrequireで明示的に呼び出した方が整理しやすいらしいよ
  • input/tex-full
    • physicscolorv2以外の全て
      • その2つはbracketcolorと差し替えできる

以上からわかる通り,別にTeX専用のライブラリに限らずとも,そもそもInputのコンポーネントOutputのコンポーネントも同じくライブラリの扱いであり,単に指定しなければsrcの先のurlに-fullがあるか否かでロードする中身を勝手に決めているだけである.

requireautoload

requireの出来る仕事は本家TeX並みに強く,説明ページには

The require extension defines the non-standard \require macro that allows you to load extensions from within a math expression in a web page. require拡張機能は、Webページの数式内から拡張機能をロードできるようにする非標準の\ requireマクロを定義します。

とあり,つまりコイツさえ入れれば各ページでrequireすることで逐次にロードできる.

他方でautoloadTeXコマンドとロードするライブラリとの対応関係を事前に定義するものであり,そのTeXコマンドをWebページの本文に打っておくと最初にロードされる. 説明ページを見てもrequireとは準備や挙動が全く異なることがわかるが,TeX本家にはなさげな見慣れない機能なので,試しに動作する例を示すと

MathJax = {
  loader: {
    load: ['input/tex-base', 'output/svg', '[tex]/autoload']
  },
  tex: {
    packages: {
                    '[+]': ['base', 'autoload']
    },
    autoload: {
      boldsymbol: ['boldsymbol']
    }
  }
}

スクリーンショット 2020-11-30 0.13.31.png

後述するloader: {load: []}tex: {packages: {}}とは無関係にロードを行うことができる,という点はrequireと共通している

最小構成のやり方について

ん?先に挙げた最小構成オプションの選択肢に対応するようなURLがこ↑こ↓のリストに載ってないやん!と思う方も中にはいるかもしれない. だがしかし,そもそもこのMathJaxオブジェクトの中のloader: {}(の中のload: [])こそが,ページがロードする中身を事前に決め打ちできるレシピのような存在なのである.早い話が例えば

MathJax = {
  loader: {load: ['input/tex-base', 'output/svg']},
  tex: {
    packages: {
      '[+]': ['base']
    }
  }
};

とすれば,取り合えずは(使えるかどうかは置いといて)最小構成となる. 更にrequireautoloadを含めて柔軟性を高めた最小構成であれば

MathJax = {
  loader: {load: ['input/tex-base', 'output/svg', '[tex]/require', '[tex]/autoload']},
  tex: {
    packages: {
      '[+]': ['base', 'require', 'autoload']
    }
  }
};

と設定してやるのが妥当であろう.

しかしそう設定したところで,srcの先のurlを

<script type="text/javascript" id="MathJax-script" async
  src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js">
</script>

やら

<script type="text/javascript" id="MathJax-script" async
  src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg-full.js">
</script>

やらとやってしまって良いのだろうか?いや,やっぱりそれはよくない(反語). 後述するstartup用いなければMathJaxの指定通りのロードは完璧にはできない. そしてドキュメントには仕様が載っていなかったので,自分でやって比較してみた…

fullを選ぶ

startupを選ぶ

下の方が指定通りのロードをしていそうなことがわかる. よって突き詰めるのであれば

<script type="text/javascript" id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/startup.js">
</script>

を選択することが正しいと考えられる.でもurlを打てば一括でロードできちゃうのは楽なんよ

まだまだあるよ!ライブラリ

別にライブラリがあるのはTeXに限った話ではないし,面白い機能もあったりする.

アクセシビリティ

さっきのload: []に入れると使えるらしい.

  • a11y/semantic-enrich
    • 数式に読み仮名を振る,読み上げ機能がある端末に対応できる
  • a11y/complexity
    • 複雑すぎる式を折り畳んでくれる,クリックすれば元に戻る
  • a11y/explorer
    • Tab(↹)とかを押してWebページの中で数式を移動できる
      • a11y/complexityと組み合わせられる
  • a11y/assistive-mml
    • MathMLを利用する読み上げ機能がある端末に対応できる
      • a11y/semantic-enrichが必要

ここまでを全部載せすると…?

<script type="text/javascript" id="MathJax-script" async
  src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg-full.js">
</script>
MathJax = {
  loader: {load: ['[tex]/colorv2', '[tex]/physics', 'a11y/semantic-enrich', 'a11y/complexity', 'a11y/explorer', 'a11y/assistive-mml']},
  tex: {
    packages: {
      '[+]': ['colorv2', 'physics'],
      '[-]': ['color', 'bracket']
    }
  }
};

tex: {packages: {}}が設定するのはTeXのタイプセット処理にどのライブラリを用いるかのみであり,アクセシビリティのライブラリはそれとは無関係なので,loader: {load: []}の中に記載するだけでおk.

その他

  • startup
    • 先にある通り,srcの先のurlの末尾をstartupにすると,loader: {load: []}で指定したライブラリを決め打ちでロードできる
    • 同時にライブラリとしても存在し,loaderを動かしてロードさせた後にタイプセットまでを行う
    • オプションでロードやタイプセットに関する挙動を制御できる
  • ui/safe
    • ユーザーがWebページ上に入力する際に,変なコードが紛れて走らないように防御できる
    • 基本的にhtmlコードに関してであり,オプションで制限する内容をカスタマイズできる
  • ui/menu
    • 数式を右クリックすると出現するメニューを追加する
    • 先のアクセシビリティの注意書きには「機能が被っているので別々に読み込まなくても良い」って書いてる
      • つまりアクセシビリティを突っ込んだからコレと同じものが勝手に読み込まれるらしい
      • ただしコレ単体でもソースコードを表示したり拡大縮小したりchtmlとsvgを切り替えたりはできる
    • あと「自動で折り畳まれる機能はv3.x.yにはまだない」って書いてある
    • オプションで表示や機能の設定をカスタマイズできる
  • adaptors/liteDOM
    • ブラウザのDOMに対して直接のアクセスができないもの(nodeのアプリとか)でMathJaxのタイプセット等を実行するために,ブラウザのDOMの代替品を実装させる
    • …というものらしいが使ったことがない
  • core
    • その他の機能の全てを含む,基本的には勝手に操作されるので弄らなくてもいい
  • loader
    • startupが自動で持ってくるライブラリで,追加のライブラリをロードする
    • オプションでロードするライブラリの中身やロードの設定をカスタマイズできる

loaderのオプション

オプション自体はこ↑こ↓が公式ドキュメント,機能の一覧を見ると割と使いそうなものも…?

MathJax = {
  loader: {
    paths: {mathjax: Loader.getRoot()},          // the path prefixes for use in specifying components
    source: {},                                  // the URLs for components, when defaults aren't right
    dependencies: {},                            // arrays of dependencies for each component
    provides: {},                                // components provided by each component
    load: [],                                    // array of components to load
    ready: Loader.defaultReady.bind(Loader),     // function to call when everything is loaded
    failed: function (error) {                   // function to call if a component fails to load
      console.log(`MathJax(${error.package || '?'}): ${error.message}`);
    },
    require: null                                // function to use for loading components
  }
};

loader: {load: []}

今まで何度も出たしもういいよね…

loader: {paths: {}}

[xxx]/yyyってなんだ?

loader: {paths: {}}が関係する,例えばxxxのソースのurlが

MathJax = {
  loader: {
    paths: {xxx: 'https://example.com/xxx'},
  }
};

と定義されれば,その下のyyy

MathJax = {
  loader: {
    load: ['[xxx]/yyy']
  }
};

でロードできる,更に数珠つなぎにしてしまえば

MathJax = {
  loader: {
    paths: {
      xxx: 'https://example.com/xxx',
      yyy: '[xxx]/yyy'
    },
    load: ['[yyy]/zzz']
  }
};

となる,すなわちTeX系のライブラリにおいては

MathJax = {
  loader: {
    paths: {
      tex: '何かしらのurl',
    },
    load: ['[tex]/何かしらのライブラリ']
  }
};

みたいな構造になっていると推測される.

ちなみにさっきの最小構成においてloader: {load: []}を以下のように改変すると…

MathJax = {
  loader: {load: ['input/tex-base', 'output/svg', 'tex']},
  tex: {
    packages: {
      '[+]': ['base']
    }
  }
};

スクリーンショット 2020-11-29 20.34.26.png

MathJax = {
  loader: {load: ['input/tex-base', 'output/svg', '[tex]']},
  tex: {
    packages: {
      '[+]': ['base']
    }
  }
};

スクリーンショット 2020-11-29 20.34.47.png

要するに最終的に末端で確実にファイルが手に入るようなurlにならないとCDNに突き返される,こんな横着みたいな一括ロードはできないそりゃそうじゃ

更に,

MathJax = {
  loader: {load: ['input/tex-base', 'output/svg', '[tex]/boldsymbol.js']},
  tex: {
    packages: {
      '[+]': ['base', 'boldsymbol']
    }
  }
};

スクリーンショット 2020-11-29 20.56.36.png

MathJax = {
  loader: {load: ['input/tex-base', 'output/svg', '[tex]/boldsymbol.js']},
  tex: {
    packages: {
      '[+]': ['base', 'boldsymbol.js']
    }
  }
};

スクリーンショット 2020-11-29 20.57.55.png

tex: {packages: {}}にはちゃんとライブラリの名前を表記しなきゃいけない割にはloader: {load: []}はurlの体を成してさえいればおkらしい…

loader: {source: {}}

xxx/yyyってなんだ?

絶対urlを設置できるloader: {source: {}}が関係する,例えば

MathJax = {
  loader: {
    source: {
      'xxx/yyy': 'https://example.com/xxx/yyy.js'
    },
    load: ['xxx/yyy']
  }
};

となる,すなわちアクセシビリティ系のライブラリにおいては

MathJax = {
  loader: {
    source: {
      '何かしらのライブラリ': '何かしらのurl',
    },
    load: ['何かしらのライブラリ']
  }
};

みたいな構造になっていると推測される. 見ての通りで1対1構造の決め打ち方式であり,loader: {paths: {}}のような拡張性はない.

dependencies: {}provides: {}

dependencies: {}はライブラリ間の依存関係を記憶するリストであり,startupload: []'の依頼に基づきdependencies: {}を使って必要な(ロードするべき)ライブラリを考えprovides: {}に列挙していく. すなわち最終的にロードされるライブラリの実態はprovides: {}に現れる,先に述べたurlとライブラリの対応関係はこれで実現されているとも言える,ドキュメントにも

loader: {
  provides: {
    'input/tex': [
      'input/tex-base',
      '[tex]/ams',
      '[tex]/newcommand',
      '[tex]/noundefined',
      '[tex]/require',
      '[tex]/autoload',
      '[tex]/configmacros'
    ]
  }
}

みたいな例が載っているし多分そう. また基本的なライブラリであればdependencies: {}は既に完全に整備されている.

そもそも普通にMathJaxを使うだけなら(コピー品や独自品を自前のサーバーから配信したいとか出ない限り),loaderにわざわざ手を加える場所は何もなさそうだけど…

その他のオプション

投げられる関数だったりエラーの処理だったり色々とあるけど,もういいよね…

startupのオプション

オプション自体はこ↑こ↓が公式ドキュメント,ロードとタイプセットの挙動を弄るのはもう完全に動的なサービスを作りたい人くらいだと思うので,パスさせていただきますね…

それにタイプセット自体のお話はまた後述…