") no-repeat}.external-link:visited:after{background-color:#3f0071}main{font:400 1.6rem/3.2rem f-sans,-apple-system,sans-serif;margin:0 auto;width:calc(min(100%,840px) - 32px)}main h1{font:400 2.8rem/4.8rem f-sans,-apple-system,sans-serif}main h2{font:400 2.4rem/3.2rem f-sans,-apple-system,sans-serif}main h3,main h4,main h5,main h6{font:400 2.2rem/2.4rem f-sans,-apple-system,sans-serif}main h1:not(:first-child),main h2:not(:first-child),main h3:not(:first-child),main h4:not(:first-child),main h5:not(:first-child),main h6:not(:first-child){margin-top:24px}main p{text-align:justify}main ol,main ul{padding-left:24px}main ol{list-style:decimal}main ul{list-style:disc}main strong{font-weight:700}main h1,main h2,main h3,main h4,main h5,main h6,main p,main ul,main ol,main img,main pre{margin-bottom:16px}main img{display:block;max-width:100%;height:auto;margin:0 auto}main table{border-collapse:collapse;margin:16px auto}main table td,main table th{border:0;padding:2px 8px}main table tr:first-child th{border-top:2px solid #74796c;border-bottom:1px solid #74796c}main table tr:last-child td{border-bottom:2px solid #74796c}main hr{width:100%}main blockquote{margin:0;padding:0 0 0 12px;border-left:2px solid #74796c}main code{font-family:f-mono,f-sans,monospace}main pre{outline:1px solid #74796c;border-radius:2px}main pre code{display:block;width:fit-content;padding:12px}main math{font-family:"Fira Math",f-sans,-apple-system,sans-serif}main details summary{cursor:pointer;list-style:none;width:fit-content;border-radius:2px}main details summary:before{content:"";display:inline-block;margin-right:4px;width:14px;height:14px;background-color:#1a1d16;mask:url("data:image/svg+xml;utf8,") no-repeat}@media (hover: hover){main details summary:hover{color:#0001b4;outline:1px dashed}main details summary:hover:before{background-color:#0001b4}}main details summary:active{color:#0001b4;text-decoration:none;outline:1px solid}main details summary:active:before{background-color:#0001b4}main details[open] summary:before{mask:url("data:image/svg+xml;utf8,") no-repeat}main .footnote{overflow:hidden;width:100%;height:0px;border:none;border-bottom:1px solid #74796c}main sup a{font-weight:700;text-decoration:none;border-radius:2px}main sup a:visited{color:#0001b4}@media (hover: hover){main sup a:hover{text-decoration:none;outline:1px dashed}}main sup a:active{text-decoration:none;outline:1px solid}main .data-footnote-backref{font-weight:700;text-decoration:none;border-radius:2px}main .data-footnote-backref:visited{color:#0001b4}@media (hover: hover){main .data-footnote-backref:hover{outline:1px dashed}}main .data-footnote-backref:active{outline:1px solid}.aa--l-pm{margin-left:-.5em}.aa--r-pm{margin-right:-.5em}.aa--m-pm{margin:0 -.25em}.aa--jw-aki:after{font-size:57.5862068966%;vertical-align:top;content:" "}.aa--lr-pm-aki:after{font-size:172.4137931034%;vertical-align:top;content:" "}.aa--m-pm-aki:after{font-size:86.2068965517%;vertical-align:top;content:" "}.aa--d-pm-aki:after{font-size:344.8275862069%;vertical-align:top;content:" "}header[data-astro-cid-3ef6ksr2] .header-content[data-astro-cid-3ef6ksr2]{display:flex;align-items:baseline;margin:0 auto;padding:24px 0 18px;width:calc(min(100%,840px) - 32px)}header[data-astro-cid-3ef6ksr2] .header-content[data-astro-cid-3ef6ksr2] a[data-astro-cid-3ef6ksr2]{color:inherit;text-decoration:none;border-radius:2px}@media (hover: hover){header[data-astro-cid-3ef6ksr2] .header-content[data-astro-cid-3ef6ksr2] a[data-astro-cid-3ef6ksr2]:hover{color:#0001b4;outline:1px dashed}}header[data-astro-cid-3ef6ksr2] .header-content[data-astro-cid-3ef6ksr2] a[data-astro-cid-3ef6ksr2]:active{color:#0001b4;outline:1px solid}header[data-astro-cid-3ef6ksr2] .header-content[data-astro-cid-3ef6ksr2] .title[data-astro-cid-3ef6ksr2]{font:500 1.6rem/2.4rem f-sans,-apple-system,sans-serif;margin-bottom:6px}header[data-astro-cid-3ef6ksr2] .header-content[data-astro-cid-3ef6ksr2] nav[data-astro-cid-3ef6ksr2]{font:500 1.4rem/2.4rem f-sans,-apple-system,sans-serif;margin-left:auto}header[data-astro-cid-3ef6ksr2] .header-content[data-astro-cid-3ef6ksr2] nav[data-astro-cid-3ef6ksr2] ul[data-astro-cid-3ef6ksr2]{display:flex}header[data-astro-cid-3ef6ksr2] .header-content[data-astro-cid-3ef6ksr2] nav[data-astro-cid-3ef6ksr2] ul[data-astro-cid-3ef6ksr2] li[data-astro-cid-3ef6ksr2]:not(:first-child){margin-left:12px}header[data-astro-cid-3ef6ksr2] .header-content[data-astro-cid-3ef6ksr2] nav[data-astro-cid-3ef6ksr2] ul[data-astro-cid-3ef6ksr2] .nav-item[data-astro-cid-3ef6ksr2]{display:inline}header[data-astro-cid-3ef6ksr2] .header-content[data-astro-cid-3ef6ksr2] nav[data-astro-cid-3ef6ksr2] ul[data-astro-cid-3ef6ksr2] .nav-item[data-astro-cid-3ef6ksr2] svg[data-astro-cid-3ef6ksr2]{margin-right:4px;vertical-align:-7.5%}footer[data-astro-cid-sz7xmlte]{position:sticky;top:100lvh;margin-top:64px}footer[data-astro-cid-sz7xmlte] .footer-content[data-astro-cid-sz7xmlte]{display:flex;margin:0 auto;padding:8px 0 24px;width:calc(min(100%,840px) - 32px)}footer[data-astro-cid-sz7xmlte] .footer-content[data-astro-cid-sz7xmlte] .copyright[data-astro-cid-sz7xmlte]{font:500 1.4rem/2.4rem f-sans,-apple-system,sans-serif;margin-right:12px}footer[data-astro-cid-sz7xmlte] .footer-content[data-astro-cid-sz7xmlte] .nav[data-astro-cid-sz7xmlte]{font:500 1.4rem/2.4rem f-sans,-apple-system,sans-serif;margin-left:auto}footer[data-astro-cid-sz7xmlte] .footer-content[data-astro-cid-sz7xmlte] .nav[data-astro-cid-sz7xmlte] li[data-astro-cid-sz7xmlte]:not(:first-child){margin-top:8px}footer[data-astro-cid-sz7xmlte] .footer-content[data-astro-cid-sz7xmlte] .nav[data-astro-cid-sz7xmlte] .external-link[data-astro-cid-sz7xmlte]:after{height:14px}footer[data-astro-cid-sz7xmlte] .footer-content[data-astro-cid-sz7xmlte] .nav[data-astro-cid-sz7xmlte] a[data-astro-cid-sz7xmlte]{text-decoration:none}@media (hover: hover){footer[data-astro-cid-sz7xmlte] .footer-content[data-astro-cid-sz7xmlte] .nav[data-astro-cid-sz7xmlte] a[data-astro-cid-sz7xmlte]:hover{text-decoration:underline 8%}}footer[data-astro-cid-sz7xmlte] .footer-content[data-astro-cid-sz7xmlte] .nav[data-astro-cid-sz7xmlte] a[data-astro-cid-sz7xmlte]:active{text-decoration:underline 8%}
.title[data-astro-cid-egg7nqdx]{font:400 2.8rem/4.8rem f-sans,-apple-system,sans-serif;margin-top:24px}.date[data-astro-cid-egg7nqdx]{font:400 1.4rem/2.4rem f-sans,-apple-system,sans-serif;color:#44483e;display:flex;margin-top:4px}.date[data-astro-cid-egg7nqdx] .updated[data-astro-cid-egg7nqdx]{margin-left:12px}.date[data-astro-cid-egg7nqdx] .space[data-astro-cid-egg7nqdx]{margin-left:4px}.tags[data-astro-cid-egg7nqdx]{font:400 1.4rem/2.4rem f-sans,-apple-system,sans-serif;margin:2px -8px 32px 0}.tags[data-astro-cid-egg7nqdx] .tag[data-astro-cid-egg7nqdx]{display:inline-block;margin-right:8px;color:#44483e;text-decoration:none;border-radius:2px}@media (hover: hover){.tags[data-astro-cid-egg7nqdx] .tag[data-astro-cid-egg7nqdx]:hover{color:#0001b4;outline:1px dashed}}.tags[data-astro-cid-egg7nqdx] .tag[data-astro-cid-egg7nqdx]:active{color:#0001b4;outline:1px solid}
[data-astro-image]{width:100%;height:auto;object-fit:var(--fit);object-position:var(--pos);aspect-ratio:var(--w) / var(--h)}[data-astro-image=responsive]{max-width:calc(var(--w) * 1px);max-height:calc(var(--h) * 1px)}[data-astro-image=fixed]{width:calc(var(--w) * 1px);height:calc(var(--h) * 1px)}
`rehype-mathml`: TeX数式をMathMLにするrehypeプラグイン | Daiji Blogrehype-mathml
: TeX数式をMathMLにするrehypeプラグイン
この記事は、フラー株式会社 Advent Calendar 2024の7日目の記事です。6日目はsu8のサポート終了間近 しがない30代エンジニアの本棚(2024年版)でした。
はじめに
unified(remark、rehype)を使ってMarkdownをHTMLへ変換する人は多いでしょう。unifiedはGatsbyやNuxt.js、Astroなど、多くのフレームワークで利用されています。
unifiedで数式を扱う場合、Markdown中の$f(x)=ax+b$
のようなTeX(LaTeX)数式をremark-math
で見つけ、rehype-mathjax
やrehype-katex
でMathJaxやKaTeXにより表示できる形式に変換するのが一般的です。
ブラウザで数式を表示するために、HTMLにMathMLを記述する方法があります。KaTeXは数式表示のためのHTMLに変換するだけでなく、MathMLへの変換もサポートしています。しかし、KaTeXは少しTeX数式のカバレッジが低く、純粋なMathML以外も出力されるなど、いくつかの不満がありました。
そんなとき、カバレッジが優れ、軽量なMathMLを出力するTemmlというライブラリを見つけました。Temmlを使ってMathMLに変換したいと思い、@daiji256/rehype-mathml
を作成しました。
rehype-mathml
で出来ること
このようなMarkdownからMathMLを含むHTMLに変換できます。
本文中や$f(x)=ax+b$、別行立てで、
$$
\begin{align}
f(x) &= ax + b\\
g(x) &= cx^{2} + dx + e
\end{align}
$$
MathMLによる数式表示が可能です。
本文中や、別行立てで、
MathMLによる数式表示が可能です。
MathML
MathMLについて簡単に説明します。
MathML(Mathematical Markup Language)とはXML/HTML5で数式を記述するためのマークアップ言語です。Can I useによると、現在ではメジャーなブラウザがMathMLをサポートしているそうです。
ブラウザで数式を表示する方法として、MathMLの他に、MathJaxやKaTeXがあります。これらはJavaScriptの処理や画像の読み込み、HTMLの肥大化などによりパフォーマンスが優れないです。一方でMathMLは標準機能で数式を扱い非常に軽量です。さらに、MathMLは数式の表示だけでなく、そのコードに数式としての意味を持たせるため、スクリーンリーダーなどでも扱うことができます1。
しかし、MathMLはコンピューターが認識しやすいように設計されたものであり、人間が直接扱うのは難しいです。そのため、MathML専用のエディタなどを使うのが一般的です。
MathMLによる数式記述例
例えば二次方程式の解の公式
はMathMLで記述すると次のようになる。
<math>
<mrow>
<mi>x</mi>
<mo>=</mo>
<mfrac>
<mrow>
<mo>−</mo>
<mi>b</mi>
<mo>±</mo>
<msqrt>
<mrow>
<msup>
<mi>b</mi>
<mn>2</mn>
</msup>
<mo>−</mo>
<mn>4</mn>
<mi>a</mi>
<mi>c</mi>
</mrow>
</msqrt>
</mrow>
<mrow>
<mn>2</mn>
<mi>a</mi>
</mrow>
</mfrac>
</mrow>
</math>
rehype-mathml
の使い方
rehype-mathjax
やrehype-katex
をすでに使っている場合、その部分を@daiji256/rehype-mathml
に置き換えるだけで完了します。詳しくはrehype-mathml/README.mdに書いてあります。
インストール
npmの場合、
npm install @daiji256/rehype-mathml
でインストールできます。
remark-math
とrehype-mathml
を以下のようにして使うことができます。
import { unified } from 'unified';
import rehypeParse from 'rehype-parse';
import rehypeStringify from 'rehype-stringify';
import remarkRehype from 'remark-rehype'
import remarkMath from 'remark-math';
import rehypeMathML from '@daiji256/rehype-mathml';
import { read, write } from 'to-vfile';
const file = await unified()
.use(remarkParse)
.use(remarkMath)
.use(remarkRehype)
.use(rehypeMathML)
.use(rehypeStringify)
.process(await read('input.html'));
file.basename = 'output.html';
await write(file);
文献
rehype-mathml
(GitHub)
rehype-mathml
(npm)
- unified
- remark-math
- W3C Math Home
- “MathML” | Can I use
フラー株式会社 Advent Calendar 2024の8日目はいのりこさんの復職記 〜第二子〜です。