Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implemented math syntax with MathJax #89

Merged
merged 10 commits into from
Apr 26, 2021
Merged

Conversation

akabekobeko
Copy link
Member

#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
Copy link
Member

<body data-math-typeset>

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

<body data-math-typeset="true">

@akabekobeko
Copy link
Member Author

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

@MurakamiShinyu
Copy link
Member

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

まず、$$ ... $$ のあいだに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
Copy link
Member Author

akabekobeko commented Apr 20, 2021

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

以上を踏まえて

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

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

@MurakamiShinyu
Copy link
Member

MurakamiShinyu commented Apr 20, 2021

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

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
Copy link
Member Author

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

@akabekobeko
Copy link
Member Author

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

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

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

@MurakamiShinyu
Copy link
Member

MurakamiShinyu commented Apr 20, 2021

すみません、

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
Copy link
Member

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

  • HTML を見たときに <span class="math inline"><span class="math display"> のところが数式だということが分かりやすいし、論理構造的にセマンティックなマークアップとなる。
  • スタイルシートで、 span.math.inline { ... }span.math.display { ... } として数式にスタイルを指定することが可能。

@akabekobeko
Copy link
Member Author

ありがとうございます。

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

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

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

あわせて

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

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

@akabekobeko akabekobeko mentioned this pull request Apr 20, 2021
@MurakamiShinyu
Copy link
Member

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

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

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

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

  • 開始
    • $
    • 直後は空白以外
  • 終端
    • $
    • 直前は空白以外
    • 直後は数字以外
    • \$$ の直前に \ が奇数個)は終端扱いしない

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

  • 開始
    • $$
  • 終端
    • $$
  • 途中に空白行がないかぎり、複数行にわたってもよい(注)

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

@akabekobeko
Copy link
Member Author

akabekobeko commented Apr 20, 2021

ありがとうございます。

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

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

@akabekobeko
Copy link
Member Author

akabekobeko commented Apr 22, 2021

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

  • inline: /\$([^($| )].*?[^($| )])\$(?!(\$|\d))/gs
  • display: /\$\$([^$].*?[^$])\$\$(?!\$)/gs

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

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

@akabekobeko
Copy link
Member Author

akabekobeko commented Apr 22, 2021

/\$([^($| )].*?[^(\\|$| )])\$(?!(\$|\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
Copy link
Member Author

前述のコメントを見返して $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
Copy link
Member Author

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

@MurakamiShinyu
Copy link
Member

気がついた問題:

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

$ 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
Copy link
Member

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

$ 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
Copy link
Member Author

覚書。

以下を修正。

  • $ のあとが (| だと数式扱いにならない
  • U+0020 以外の空白文字

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

残件。

  • 1文字のみのインライン数式 $a$ が認識されない
    • .*? で 0 文字以上だが開始直後と終了直前で「それぞれ 1 文字ずつ判定 = 合計 2 文字は必要」なのが原因と思われる
    • 開始と終了を文字消費なしの判定にできる?
  • 偶数個の \ のあとの $ でインライン数式が終了するべき
    • 条件として提示されていて途中まで検討していたのに対応を忘れていた
    • 偶数、奇数回の繰り返しを判定できるか調べる必要あり

src/plugins/math.ts Outdated Show resolved Hide resolved
src/plugins/math.ts Outdated Show resolved Hide resolved
docs/vfm.md Outdated Show resolved Hide resolved
akabekobeko and others added 3 commits April 24, 2021 18:20
… in math syntax (inline)

Co-authored-by: Shinyu Murakami <murakami@vivliostyle.com>
Co-authored-by: Shinyu Murakami <murakami@vivliostyle.com>
…reak immediately before it was deleted to make it only display syntax

Co-authored-by: Shinyu Murakami <murakami@vivliostyle.com>
docs/vfm.md Outdated

- `$...$`, `$$...$$` ...Range specification matches
- `$...\n...$`, `$\n...\n$` ...Within the same paragraph
- `$...\$...$`, `$$...\$...$$` ...Escape `$` by `\`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ディスプレイ数式では "Escape $ by \" という処理はされていない。
$$...\$$
をテストするとそれが確認できる。pandoc でも同様なので、この動作はたぶん問題ない。

$ lib/cli.js --math --partial
$$...\$$
^D
<p><span class="math display">$$...\$$</span></p>

$ pandoc --mathjax
$$...\$$
<p><span class="math display">\[...\\]</span></p>

次のように、奇数個のエスケープの例にするとよいかなと思います。

Suggested change
- `$...\$...$`, `$$...\$...$$` ...Escape `$` by `\`
- `$...\$...$`, `$...\\\$...$` ...Escape `$` by `\`

@akabekobeko
Copy link
Member Author

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

docs/vfm.md Outdated Show resolved Hide resolved
@akabekobeko
Copy link
Member Author

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

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

Copy link
Member

@MurakamiShinyu MurakamiShinyu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@akabekobeko akabekobeko merged commit e2cf3a3 into master Apr 26, 2021
@akabekobeko akabekobeko deleted the feat-math-syntax branch April 26, 2021 07:10
@akabekobeko
Copy link
Member Author

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

@yamasy1549
Copy link
Member

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants