vivliostyle / vfm

⬇️ Open and extendable Markdown syntax and toolchain.
https://vivliostyle.github.io/vfm/#/vfm
Other
71 stars 12 forks source link

feat: implemented math syntax with MathJax #89

Closed akabekobeko closed 3 years ago

akabekobeko commented 3 years ago

37 の議論を踏まえて MathJax 形式の数式構文を実装しました。

@MurakamiShinyu @yamasy1549 ドキュメント docs/vfm.md とテスト tests/math.test.ts のレビューをお願いします。

補足として当初は unified のみに依存する独自処理を実装していたのですが、remark を利用するとこれが拡張する tokenizer/method として登録しないと Markdown 構文間の排他がおこなわれないため、VFM 1.0 時点では仕方なく remark プラグインとすることにしました。

VFM 2.0 で remark 13 未満のインターフェースを判定して分岐しているため 13 以降では独自処理に切り替わるはずです。ただし remark 13 でも構文の排他は remark (micromark) として実装しないと機能しないと予想しています。

MurakamiShinyu commented 3 years ago
<body data-math-typeset>

が出力されますが、これは次のようにしないとVivliostyleは数式有効にしてくれません。

<body data-math-typeset="true">
akabekobeko commented 3 years ago

@MurakamiShinyu ありがとうございます。真偽値から文字列に修正して data-math-typeset="true" となるようにしました。

MurakamiShinyu commented 3 years ago

数式が複数行で書かれているときの処理に疑問があります。

まず、$$ ... $$ のあいだにLaTeX形式の1行だけの場合

$ lib/cli.js --math
$$
\LaTeX \\ \LaTeX
$$
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.9/MathJax.js?config=TeX-MML-AM_CHTML"></script>
  </head>
  <body data-math-typeset="true">
    <p>
      $$
      \LaTeX \\ \LaTeX
      $$
    </p>
  </body>
</html>

\\ がそのまま出力されていて問題ありません。 しかし、$$ ... $$ のあいだに2行書いた次の場合

$ lib/cli.js --math
$$
\LaTeX
\LaTeX \\ \LaTeX
$$
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script async src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.9/MathJax.js?config=TeX-MML-AM_CHTML"></script>
  </head>
  <body data-math-typeset="true">
    <p>
      $$
      \LaTeX
      \LaTeX \ \LaTeX
      $$
    </p>
  </body>
</html>

\\ がMarkdownのエスケープの処理がされて \ になってます。LaTeX数式扱いにならないようです。

akabekobeko commented 3 years ago

@MurakamiShinyu

37 の議論でインライン (Markdown としてのもの、MathJax のインラインとディスプレイ両方とも単一パラグラフ想定) のみ実装だと認識していました。Markdown 構文間の排他を remark で管理するためには remark-parse 依存のメソッドを実装する必要があり、そちらはインラインとブロックが分かれています。希望される複数行の対応はブロックになると思われます。

以上を踏まえて

  1. ブロックも実装する
  2. 行ごとにインライン構文を記述することで代替する

のどちらにするのがよいでしょうか?

MurakamiShinyu commented 3 years ago

単一パラグラフ想定 のみ実装でよいと思います。つまり、複数行にわたっていても、空白行(連続した改行)がない場合は単一パラグラフです。

pandoc での数式の扱いを調べたところ、そのようになっているのが分かりました。 そのうえpandocでは、インライン数式 $ ... $ は、はじめの $ のあとは空白や数字ではないこと、おわりの $ のまえは空白でないこと、おわりの $ のあとが数字でないことという制限があり、それにより $ が意図せず数式記法として扱われてしまうのを防ぐようになっていることが分かりました。

詳しくは pandoc のマニュアル→ https://pandoc.org/MANUAL.html#math

pandoc --mathjax を試すと、

$ pandoc --mathjax
Text $foo bar$ text
^D
<p>Text <span class="math inline">\(foo bar\)</span> text</p>
$ pandoc --mathjax
Text $50 text $100
^D
<p>Text $50 text $100</p>

$ のあとが数字やスペースだと数式扱いにならない。

$ pandoc --mathjax
Text $$ \LaTeX
\LaTeX \\ LaTeX
\LaTeX $$ text.
^D
<p>Text <span class="math display">\[ \LaTeX
\LaTeX \\ LaTeX
\LaTeX \]</span> text.</p>
$ pandoc --mathjax
$$
foo

bar
$$
^D
<p>$$ foo</p>
<p>bar $$</p>

↑数式の途中に空白行は入れられない(数式扱いにならない)。

このpandocの数式の扱いの仕様は参考になるかと思います。

akabekobeko commented 3 years ago

ありがとうございます。remark の inline 系でこの条件を満たせるかを調べてみます。

akabekobeko commented 3 years ago

それとこれも追加ですね。

そのうえpandocでは、インライン数式 $ ... $ は、はじめの $ のあとは空白や数字ではないこと、おわりの $ のまえは空白でないこと、という制限があり、それにより $ が意図せず数式記法として扱われてしまうのを防ぐようになっていることが分かりました。

現在は $ または $$ 開始をチェックしていますが、前者は $$、後者は $$$...N のみを回避するようにしています。よって $1 + 1 = 2$ を通しているのですが、数字と空白の否定を正規表現へ追加します。

MurakamiShinyu commented 3 years ago

すみません、

pandocでは、インライン数式 $ ... $ は、はじめの $ のあとは空白や数字ではないこと、おわりの $ のまえは空白でないことという制限があり、

と書いたのですが、間違ってたので、次のように直しました:

pandocでは、インライン数式 $ ... $ は、はじめの $ のあとは空白ではないこと、おわりの $ のまえは空白でないこと、おわりの $ のあとが数字でないことという制限があり、

https://pandoc.org/MANUAL.html#math をよく読むと "and must not be followed immediately by a digit." というのは、$...$ の直後に数字がこないことということなので、はじめの $ ではなくておわりの $ です。

これによって、 $1 + 1 = 2$ はその直後が数字でないかぎりはインライン数式として扱われます。

$ pandoc --mathjax
$1 + 1 = 2$
<p><span class="math inline">\(1 + 1 = 2\)</span></p>
$ pandoc --mathjax
$1 + 1 = 2$100
<p>$1 + 1 = 2$100</p>
MurakamiShinyu commented 3 years ago

<span class="math inline">\(...\)</span>, <span class="math display">\[...]</span> というHTMLマークアップ出力も pandoc --mathjax と互換にするのがよいかもしれません。MathJax はこれらのタグがなくても \(...\), \[...\], $$...$$ を数式扱いにしますが、これらのタグがあると次のメリットがあります。

akabekobeko commented 3 years ago

ありがとうございます。

と書いたのですが、間違ってたので、次のように直しました:

この仕様により $... is $50 みたいなものを数式の範囲としないようにしているのですね。箇条書きにしてみました。間違えていたらツッコミお願いします。

あわせて

<span class="math inline">\(...\)</span>, <span class="math display">\[...]</span> というHTMLマークアップ出力も pandoc --mathjax と互換にするのがよいかもしれません。

も追加実装します。今回の対応で元の remark-prase 時代にあった HTML 出力が廃止されたので対応する CSS セレクターをドキュメントから削除したのですが、この元処理は村上さんの書かれているとおり明示的なスタイル付けを動機としているのだと思われます。そして数式の利用者にも需要はありそうなので新版でも↑の HTML 出力を実装してみます (ドキュメントにも CSS セレクターを改めて掲載します)。

MurakamiShinyu commented 3 years ago

箇条書きにしてみました。間違えていたらツッコミお願いします。

  • 開始
    • $ または $$
    • 直後は空白と数字以外
  • 終端
    • $ または $$
    • 直前は空白以外
    • 直後は数字以外

インライン数式 $...$ とディスプレイ数式 $$...$$ で規則が違います。

インライン数式 $...$:

ディスプレイ数式 $$...$$:

(注)pandocのマニュアル https://pandoc.org/MANUAL.html#math では、途中に空白行がないかぎり複数行にわたってもよいというのはディスプレイ数式だけのように読めるのですが、試してみるとインライン数式でも同様なようです。

akabekobeko commented 3 years ago

ありがとうございます。

(注)pandocのマニュアル https://pandoc.org/MANUAL.html#math では、途中に空白行がないかぎり複数行にわたってもよいというのはディスプレイ数式だけのように読めるのですが、試してみるとインライン数式でも同様なようです。

についてはインライン側も複数行で対応してみます。なお空白行がある場合、おそらくコード ブロックなどの特殊な構文をのぞき remark-parse (の CommonMark/GFM) によってパラグラフが分離されると思われます。念の為、これが除外されることのテストは実装します。

akabekobeko commented 3 years ago

以下の正規表現でおおむね実現できた。

フラグ s.*? を改行を含めて最短一致できるのと、remark の インライン tokenizer 仕様によるパラグラフ限定のあわせ技となる。display のほうはパラグラフ限定により s フラグ追加だけでいける。

インラインの「\$$ の直前に \ が奇数個)は終端扱いしない」が課題。

akabekobeko commented 3 years ago

/\$([^($| )].*?[^(\\|$| )])\$(?!(\$|\d))/gs でいけそうに思えるが $1 + 1 = 2$ $x=y$5 $x = \\$y$ $1 + 1 = 2$ のように終端として除外対象となる数値などがあった場合は $x=y$5 でトークン消費されないため $x=y$5 $x = \\$y$ がヒットする。$5 とスペースではじまる $x が終端除外されて y$ に至るわけだ。

よって正規表現を変えるか tokenizer 側でなんとかする必要あり。

akabekobeko commented 3 years ago

前述のコメントを見返して $1 + 1 = 2$ $x=y$5 $x = \\$y$ $1 + 1 = 2$

  1. $1 + 1 = 2$ = \(1 + 1 = 2\)
  2. $x=y$5 $x = \\$y$ = \(x=y$5 $x = \\$y\)
  3. $1 + 1 = 2$ = \(1 + 1 = 2\)

になるのは正しい挙動に思える。2 を問題としていたが y$5\\$y は数式中の $ といえるので、むしろ 2 を $x=y$5\($x = \\$y$\) にしないほうがよさそうだ。この認識でよければ現時点のローカル実装で要件は満たせたかもしれない。

akabekobeko commented 3 years ago

@MurakamiShinyu @yamasy1549 提案された内容をひととおり実装してみました。テスト コードとドキュメントのレビューをお願いします

MurakamiShinyu commented 3 years ago

気がついた問題:

$ のあとが (| だと数式扱いにならない

$ lib/cli.js --math --partial
$(1+2)$
^D
<p>$(1+2)$</p>

$ lib/cli.js --math --partial
$|a+b|$
^D
<p>$|a+b|$</p>

正規表現に問題があります。

const regexpInline = /\$([^($| )].*?[^(\\|$| )])\$(?!(\$|\d))/gs;

[^($| )] では (, $, |, `,)` を除く文字ということになります。

1文字のみのインライン数式 $a$ が認識されない

この正規表現では2文字以上でないとマッチしません。

U+0020 以外の空白文字

pandoc の場合では U+0020 SPACE 以外の空白文字も同様に扱われます。pandocでのテスト:

開始 $ の後や終了 $ の前がタブ U+0009 の場合も数式扱いしない

$ pandoc --mathjax
$       a+b     $
<p>$ a+b $</p>

開始 $ の後や終了 $ の前が改行の場合も数式扱いしない

$ pandoc --mathjax
$
a+b
$
<p>$ a+b $</p>

この pandoc の動作にあわせるのがよいと思います。

MurakamiShinyu commented 3 years ago

偶数個の \ のあとの $ でインライン数式が終了するべき

$ lib/cli.js --math --partial
$a+b\\$
^D
<p>$a+b\$</p>

偶数個の \ でも $ をエスケープしてしまう。

pandocではそんなことはない:

$ pandoc --mathjax
$a+b\\$
<p><span class="math inline">\(a+b\\\)</span></p>
akabekobeko commented 3 years ago

覚書。

以下を修正。

否定 [^] において () でグルーピングして区切りは | だと勘違いしていたのが原因。ここへはそのまま対象文字を列挙するだけでよかった。空白はそれのみが仕様だと認識していたので意図的に文字 ` を指定していたが、改行やタブ文字も除外してよいとのことなのでメタ文字\s` に変更。

残件。

akabekobeko commented 3 years ago

@MurakamiShinyu ありがとうございます!手元で試して OK でした。そのテスト コードとドキュメント修正を加えてコミットします。

akabekobeko commented 3 years ago

@MurakamiShinyu テスト コードと村上さんによる修正を反映したドキュメントの変更をおこないました。これでよろしれけば merge します。

@yamasy1549 docs/vfm.mdMath equation をレビューお願いします。特に問題ないようであれば approve してください。

akabekobeko commented 3 years ago

特に問題点の指摘もなさそうなので merge しました。利用してみて意見などがありましたら、改めて Issue などでご指摘ください。

yamasy1549 commented 3 years ago

@akabekobeko たいへん遅くなってすみません、今確認しました。ありがとうございます!