モチベーション
Web で数式を表示するための仕様として、 MathML というものがある。ただ、 MathML はブラウザーの対応がイマイチなので、もっぱら MathJax や KaTeX などの JavaScript で書かれた代替手段を使うことが多いだろう。しかし、 EPUB の場合は標準的な数式記述方法は MathML なので、 LaTeX 数式を MathML に変換する必要がある。
そこで、「LaTeXで記述された数式をMathMLに変換する」ことを考える。
(尤も、 Kindle は MathML に対応していなかったり、EPUBリーダーで多く採用されていると思われる WebKit 系のレンダリングエンジンでどれほどの見た目になるのかという問題がある。しかし、それらの問題はおいておく。)
この記事では LaTeX で書かれた単体の数式を対象とし、 LaTeX 文書をまるごと HTML+MathML に変換することはここでは扱わない。また、可換図式など、 MathML が扱っていなさそうな対象は扱わない。
目次
LaTeX vs MathML
LaTeXとMathMLの思想的な違いの一つとして、 MathML は印刷物・画面への表示以外への利用も考慮している(アクセシビリティーへの配慮というか)。そのため、 LaTeX よりもさらに「数式の意味」を表せるようにしている。
例えば、
f(x+1)
x(x+1)
という2つの式を考えよう。前者は関数 f を引数 x+1 により適用している。後者は、変数 x と x+1 の積を計算している。
LaTeX では通常この2つを書き分けることはしないだろう。しかし、 MathML では両者を区別する。具体的には、U+2061 FUNCTION APPLICATION や U+2062 INVISIBLE TIMES という不可視の演算子を使い、書き分ける:
<mrow> <mi>f</mi> <mo>⁡<!--FUNCTION APPLICATION--></mo> <mrow> <mo>(</mo><mrow><mi>x</mi><mo>+</mo><mn>1</mn></mrow><mo>)</mo> </mrow> </mrow>
<mrow> <mi>x</mi> <mo>⁢<!--INVISIBLE TIMES--></mo> <mrow> <mo>(</mo><mrow><mi>x</mi><mo>+</mo><mn>1</mn></mrow><mo>)</mo> </mrow> </mrow>
このような不可視の演算子としては、他に U+2064 INVISIBLE PLUS や U+2063 INVISIBLE SEPARATOR などがある。INVISIBLE PLUS は \(9\frac{3}{4}\) のような帯分数に使い、 INVISIBLE SEPARATOR は \(a_{ij}\) の ij のような添字を区切るのに使う。
これら「関数適用」「積」「帯分数の区切り」「添字の区切り」は、 LaTeX で書く上では基本的に明示することはない。従って、 LaTeX→MathML の変換器は、数式の意味を適当に推測して補うか、あるいは「関数適用」「積」「帯分数の区切り」「添字の区切り」に対応する LaTeX コマンドを導入し、 MathML 変換器がそれを使って適切な演算子を出力するという手段を取る必要がある。
この他、 MathML の <mrow> の使い方も、 LaTeX とは素直に対応しないように思える。
MathML は <mrow> タグを使ってグルーピングを表す。このグルーピングは、文字や記号間の空白の制御にも影響する。一方、 LaTeX は文字・記号に数式クラスという分類を割り当てて空白を制御する。
例えば、 (-1) という式を LaTeX で書く際は、単に文字通り記号を並べれば、「このマイナス -
は開きカッコの直後にあるから前置演算子」というのを適当に計らってくれる。
一方、 MathML で同等の記号列を並べるとどうなるか。
<mo>(</mo><mo>-</mo><mn>1</mn><mo>)</mo>
単に並べただけでは、開きカッコ直後のマイナスが中置演算子として扱われてしまう。これを正しく前置演算子として扱わせるには、
<mo>(</mo><mrow><mo>-</mo><mn>1</mn></mrow><mo>)</mo>
と、 <mrow> でカッコの中身をグルーピングしてやる必要がある。あるいは、
<mo>(</mo><mo form="prefix">-</mo><mn>1</mn><mo>)</mo>
という風に「前置演算子」であることを明示するという手もある。
<mrow> は当然開きタグと閉じタグの対応が取れていないといけないので、 LaTeX から MathML への変換を実装する際にも、開きカッコと閉じカッコの対応を取らなくてはいけない。例えば、入力として ]-1,1[ のようなカッコのバランスの取れていないものを与えても、何らかの正当な MathML 出力を吐き出さなくてはならない。
(尤も、開区間を LaTeX で ]-1,1[ と書く際は、 ]-1,1[
ではなくて \mathopen{]}-1,1\mathclose{[}
と書くのが適切である。)
Pandoc (texmath)
Pandoc はみなさんご存知だろう。Markdown 等を HTML 等に変換してくれる、 Haskell 製のすごいやつである。
Pandoc では LaTeX 記法の数式を MathML として出力することができるが、その実際の変換部分は texmath パッケージとして切り出されている(Pandoc の一部と思っていいだろう)。
Pandoc (texmath) を使って LaTeX の数式を MathML に変換する方法については、以前このブログにも書いた:
ただし、 texmath で生成される MathML の品質は低い。例えば、 (-1)
を変換させてみると
$ pandoc -f latex -t html5 --mathml $(-1)$ <p> <math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"> <semantics> <mrow> <mo stretchy="false" form="prefix">(</mo> <mo>−</mo> <mn>1</mn> <mo stretchy="false" form="postfix">)</mo> </mrow> <annotation encoding="application/x-tex">(-1)</annotation> </semantics> </math> </p>
となり(出力は適当に整形している)、マイナスが中置になってしまう。
f(x+1)
を変換させてみると
$ pandoc -f latex -t html5 --mathml $f(x+1)$ <p> <math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"> <semantics> <mrow> <mi>f</mi> <mo stretchy="false" form="prefix">(</mo> <mi>x</mi> <mo>+</mo> <mn>1</mn> <mo stretchy="false" form="postfix">)</mo> </mrow> <annotation encoding="application/x-tex">f(x+1)</annotation> </semantics> </math> </p>
と、不可視の演算子が入らない。
この他にも、Pandoc (texmath) には「align系環境がarray環境と同様に扱われ、位置揃え &
の部分に不適切な空白が入る」「\mathrm{hoge}
が <mi>hoge</mi> ではなくて <mi>h</mi><mi>o</mi><mi>g</mi><mi>e</mi> になる」等の問題がある。
まあ Pandoc の問題は Pandoc filter で大体解決できる(?)ので、 Pandoc を使って Markdown を処理しつつ texmath を介さずに独自処理で MathML を出力することは可能だろう(AST 中の数式を RawInline (Format "html") "..."
等に置き換えるような filter を書く)。
LaTeXML
LaTeXML は、 Perl 製の LaTeX→XML コンバーターである。数式として MathML を吐くようにもできる。筆者の印象では結構しっかりしている。
LaTeX 文書を処理するには latexml コマンドと latexmlpost コマンドを利用するが、単体の数式を変換するだけなら latexmlmath コマンドを使うのが手軽である。
例:
$ latexmlmath --pmml=- 'f(x)' | iconv -f utf-8 -t ascii --unicode-subst='&#x%04x;' <?xml version="1.0" encoding="UTF-8"?> <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="f(x+1)" display="block"> <mrow> <mi>f</mi> <mo>⁢</mo> <mrow> <mo stretchy="false">(</mo> <mrow> <mi>x</mi> <mo>+</mo> <mn>1</mn> </mrow> <mo stretchy="false">)</mo> </mrow> </mrow> </math>
コマンドライン引数として変換したい数式を与えられるが、標準入力を入力としたい場合は
$ echo 'f(x)' | latexmlmath --pmml=- -
と、 -
をコマンドライン引数に渡す。
LaTeXML はカッコの前後に適切に <mrow> を挿入してくれていることが見て取れる。ただ、関数適用のカッコであるにも関わらず U+2062 INVISIBLE TIMES が挿入されている。
例:(-1)
$ latexmlmath --pmml=- '(-1)' | iconv -f utf-8 -t ascii --unicode-subst='&#x%04x;' <?xml version="1.0" encoding="UTF-8"?> <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="(-1)" display="block"> <mrow> <mo stretchy="false">(</mo> <mrow> <mo>-</mo> <mn>1</mn> </mrow> <mo stretchy="false">)</mo> </mrow> </math>
例: \mathopen{]}-1,1\mathclose{[}
$ latexmlmath --pmml=- '\mathopen{]}-1,1\mathclose{[}' | iconv -f utf-8 -t ascii --unicode-subst='&#x%04x;' <?xml version="1.0" encoding="UTF-8"?> <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\mathopen{]}-1,1\mathclose{[}" display="block"> <mrow> <mo stretchy="false">]</mo> <mrow> <mo>-</mo> <mn>1</mn> </mrow> <mo>,</mo> <mn>1</mn> <mo stretchy="false">[</mo> </mrow> </math>
例:a_{ij}
$ latexmlmath --pmml=- 'a_{ij}' | iconv -f utf-8 -t ascii --unicode-subst='&#x%04x;' <?xml version="1.0" encoding="UTF-8"?> <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="a_{ij}" display="block"> <msub> <mi>a</mi> <mrow> <mi>i</mi> <mo>⁢</mo> <mi>j</mi> </mrow> </msub> </math>
添字の区切りには U+2063 INVISIBLE SEPARATOR を使って欲しいところだが、相変わらずというか INVISIBLE TIMES が使われている。
例:9\frac{3}{4}
$ latexmlmath --pmml=- '9\frac{3}{4}' | iconv -f utf-8 -t ascii --unicode-subst='&#x%04x;' <?xml version="1.0" encoding="UTF-8"?> <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="9\frac{3}{4}" display="block"> <mrow> <mn>9</mn> <mo>⁤</mo> <mfrac> <mn>3</mn> <mn>4</mn> </mfrac> </mrow> </math>
帯分数は認識され、 U+2064 INVISIBLE PLUS が使用された。
というわけで、 LaTeXML では帯分数は認識されるが、「関数適用」「積」「添字の区切り」の区別がされないように見える。
しかし話はこれで終わらない。
LaTeXML は latexml.sty という LaTeX パッケージを提供している。このパッケージではいくつかのマクロが定義されており、 LaTeXML で処理した場合に利用されるメタデータを与えることができる(LaTeXML 以外の TeX 処理系では単に無視される)。
TeX 処理系用の定義:https://github.com/brucemiller/LaTeXML/blob/master/lib/LaTeXML/texmf/latexml.sty
LaTeXML 用の定義:https://github.com/brucemiller/LaTeXML/blob/master/lib/LaTeXML/Package/latexml.sty.ltxml
数式の中で利用できるものには以下がある:
\lxFcn{f}
: 識別子を関数名として扱う\lxID{...}
: 識別子を変数として扱う?\lxPunct{...}
: 記号として扱う?\lxWithClass{foo}{f}
: XMLのclass属性を与える
latexmlmath コマンドのワンライナーでパッケージを読み込むには、 --preload
オプションを使えば良い。
例: \lxFcn{f}(x+1)
$ latexmlmath --preload=latexml --pmml=- '\lxFcn{f}(x+1)' | iconv -f utf-8 -t ascii --unicode-subst='&#x%04x;' <?xml version="1.0" encoding="UTF-8"?> <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="f(x+1)" display="block"> <mrow> <mi>f</mi> <mo>⁡</mo> <mrow> <mo stretchy="false">(</mo> <mrow> <mi>x</mi> <mo>+</mo> <mn>1</mn> </mrow> <mo stretchy="false">)</mo> </mrow> </mrow> </math>
\lxFcn
コマンドを使用したため、 U+2061 FUNCTION APPLICATION が使用されている。
例:\lxWithClass{foo}{\lxFcn{f}}(x)
$ latexmlmath --preload=latexml --pmml=- '\lxWithClass{foo}{\lxFcn{f}}(x)' | iconv -f utf-8 -t ascii --unicode-subst='&#x%04x;' <?xml version="1.0" encoding="UTF-8"?> <math xmlns="http://www.w3.org/1998/Math/MathML" alttext="\lxWithClass{foo}{f}(x)" display="block"> <mrow> <mi class="foo">f</mi> <mo>⁡</mo> <mrow> <mo stretchy="false">(</mo> <mi>x</mi> <mo stretchy="false">)</mo> </mrow> </mrow> </math>
\lxWithClass
コマンドにより、出力の XML に class 属性が付加された。
\(a_{ij}\) に INVISIBLE SEPARATOR を使うことができるのかどうかは、よくわからなかった。
blahtex(ml)
Apple 謹製の Pages および iBooks Author には、 LaTeX または MathML で数式を入力する機能がある。このページ によると、LaTeX 記法の数式を MathML に変換するのには blahtex(ml) というプログラムが使用されている。blahtex(ml) は C++ で書かれているOSSである。
blahtex(ml) の著作権表記が個人なので、 Apple は blahtex(ml) の開発に関わっているのではなく、単に利用しているだけだと思われる。
Apple の製品に採用された要因としては
- C++ 製のライブラリーなのでアプリケーションに組み込みやすい(いくら出来が良くても、 Perl で書かれたものを組み込む気にはならないよね?)
- ライセンスが緩い
等の要因が考えられる。
blahtex(ml) は単体のプログラムとしても利用できる。筆者は MacPorts で port install blahtexml で入れた。
例:
$ echo 'f(x)' | blahtex --mathml <blahtex> <mathml> <markup> <mrow> <mi>f</mi> <mo lspace="0" rspace="0" stretchy="false">(</mo> <mi>x</mi> <mo lspace="0" rspace="0" stretchy="false">)</mo> </mrow> </markup> </mathml> </blahtex>
$ echo '(-1)' | blahtex --mathml <blahtex> <mathml> <markup> <mrow> <mo lspace="0" rspace="0" stretchy="false">(</mo> <mo lspace="0" rspace="0">-</mo> <mn>1</mn> <mo lspace="0" rspace="0" stretchy="false">)</mo> </mrow> </markup> </mathml> </blahtex>
blahtex(ml) は不可視の記号を出力するということは特にしないようだ。また、記号に対して lspace, rspace 等の属性をつけている。
MathJax や KaTeX
MathJax や KaTeX は基本的には MathML を使わずに HTML や SVG を使って数式を描画する。しかし、こいつらが出力する HTML を見てみると、アクセシビリティー目的なのか(ちゃんと確認してない)、不可視の MathML が吐き出されている。
ただ、 MathJax や KaTeX が吐き出す MathML はあくまで補助的なもので、 EPUB 等の見た目が大事な用途で利用するのには適さない。
その他
他にも LaTeX→MathML コンバーターはいくつかあるはずだが、片っ端から試すのは大変そうなので、目に留まった3つ(Pandoc (texmath), LaTeXML, blahtex(ml))を調べてみた。他のコンバーターはどうやっているのか、特に LaTeXML の \lxFcn
等のように、 LaTeX に対するお独自のマークアップを用意しているものがあるのか、気になる。
リンク:
ピンバック: TeXっぽいものを実装するにあたっての雑記 | 雑記帳
ピンバック: LaTeX数式 to MathML を考える その2 | 雑記帳