kmuto’s blog

はてな社でMackerel CREをやっています。料理と旅行といろんなIT技術

Re:VIEWでダイアグラムツールのMermaidに対応する作業を進めてみた

3行で

Mermaidで書いたのを画像化するバッチツールと、Re:VIEW//graph[ID][mermaid]でMermaid記法を使えるようにするパッチを作った。公式取り込みはしばし待て。被験者歓迎。

背景

最近は「ダイアグラムを描くならMermaid」らしい。「Graphvizの時代は終わった、これからはBlockdiag」「Blockdiagの時代は終わった、これからはPlantUML」と言われていた気がするが、そのうち1周まわってtroffになりそうだ。

Mermaidはこれまでの静的コマンドと異なり、ブラウザ上でJavaScriptSVGを動的に作ることが前提となる。

そのため、ブラウザで見る前提であれば、単純にCDN配布のJavaScriptを取り込んで、Mermaidコード部のpre要素にclassを指定するHTMLを書き出すだけで済む。シンプル版PR

とはいえ、大半のRe:VIEWの用途を考えると、以下のどちらかだろう。

  • 印刷用あるいは電子用のPDFにしたい(LaTeXあるいはVivliostyle CLI経由)
  • EPUBにしたい

LaTeXEPUBでは直接ネットワーク接続してJavaScriptを動かして描画するということはそもそもできない。Vivliostyleならできるかなと思ったら、こちらもHTMLの書き方が悪かったのか動作競合するのかpre要素のまま変わってくれなかった。

ということで、Mermaidをなんとかして画像化することからまず始めなければならない。一応Mermaidにもmermaid-cliというのがあるが、puppeteerを使って単一ファイルを作る仕組みで、数が多くなるとオーバーヘッドが悲惨なことになりそうなのと、Re:VIEWからの呼び出しを考えるとせめてRubyのラッパーレベルで閉じたいなという思いがある。

いずれにせよ、mermaid-cliを見て、やり方としては結局ブラウザを中で動かすしかないということは理解できた。

Playwright Runner gemを作った

とりあえずブラウザを動かして何かさせるということで、puppeteerより扱いやすいPlaywrightを使う。Rubyラッパーのplaywright-ruby-clientをYusukeIwakiさんが精力的に保守されているので、尊い

ということで、拝みながらPlaywright Runner gemを作った。

github.com

現時点ではまだmermaids_to_imagesメソッドだけ用意した状況だけだが、気が向いたらほかにもRe:VIEW的に便利メソッドを追加していこうという気概を込めた名前にしてみた。

さて、mermaids_to_imagesのほうは、Mermaid記載のHTMLファイルを複数入れたフォルダに対してChromiumを使ってPDFあるいはSVGにするようにしている。mermaid-cliではSVGはそのままMermaidの要素を使っているようだったが、外部参照していたりするようなものが登場するとEPUBでは困るので、SVGはPDFにしたものをSVG化する、という手順にしてある。

厄介なこととしてはPDF化はChromiumからの印刷相当(page.pdf)を使っているため、紙サイズぶんだけ図版周囲に巨大な余白ができてしまう。そのため、切り出しには外部コマンドのpdfcropを使っているのだが、これはTeXLive収録のPerlスクリプトで、そこからさらにほかのコマンドも芋づるで呼び出している。Re:VIEWならTeXLiveもあるでしょ、ということで目をそむけることにするが、これだと自動テストもしづらいので悩ましい。

また、PDF→SVGも厄介で、これも外部コマンドのpdftocairoを使っており、これはPopplerに収録されているもの。とはいえ、pdftocairoが今のところ一番性能がいいので、仕方がないとしよう。vvakame Re:VIEW Dockerなら全部入っているしね。

ブラウザ起動インスタンスおよびページを1つにして、page.gotoでHTMLファイルを切り替えながら生成することで高速化を図っている。最初は全部Mermaidコードを並べてdisplayで表示するものを切り替えながら作ろうと目論んでいたのだが、PlaywrightでCSSを動的に変える方法がeval必死みたいなので辛そうなのと、変わった結果を関知する方法がよくわからなかったので、諦めた。gotoでもそんなに遅くはない。

Mermaid図のブラウザSVG描画の完了待ちは、page.locator('svg').clickという形で、クリックできる状態まで待って、それから(それ自体に意味はないけれども)クリックするという方法で対処している。たまに真っ白なのができることもあり、いまいちよくわからない。ベターな描画完了イベント検知方法の情報求む。

そしてRe:VIEW

ここまでできたらRe:VIEW//graph[ID][mermaid]から各ビルダにSVGやPDFを用意するのも簡単だろう……と思ってたのだけど、これが予想外に難儀している。

一応動くレベルのPR。

github.com

PDFの出力としてはこんな感じ。

現状、//graphの今の枠組みに割り込もうとすると、だいぶ込み入った形になってしまうことがわかった。@takahashimさんと相談しながら良い形にしていきたい。

テストしづらいのが一番難しいところ。Playwright Runner側でモックを用意したほうがいいのかもしれない。

あと、LaTeX PDFの場合の注意点として、Playwright Runnerで作成したMermaid図のPDFのフォントにType 3が紛れ込む可能性がある(OpenTypeフォントChromiumがType 3にしてしまう)。Type 3フォントは同人誌レベルだったら許容される可能性はあるが、商業刊行物や一部のPODではダメなので、TrueType環境で作る(CSSファイルを差し込む余地を作らないとだね……)か、アウトライン化を行う必要がある。