GitHub Markdown 数式:レンダリングルールと回避策
GitHub は Markdown の数式を2段階でレンダリングします:
- サーバーサイド(
cmark-gfm+ math sanitizer):$ ... $と$$ ... $$の境界を見つけ、CommonMark のバックスラッシュ解除を ASCII 句読点に適用し、許可されていない LaTeX マクロを拒否してから<math-renderer>タグを出力します。 - **クライアントサイド(
<math-renderer>+MathJax.tex2mml):**TeX を MathML に変換し、ネイティブブラウザの MathML Core レイアウトのためにページに注入します。
両方の段階にバグがあり、通常の LaTeX が壊れる原因となります。以下のルールは、それらのバグに対する回避策です。
このスキルを使うべき時
- GitHub で読まれる
.mdファイルに数式を書いたり編集したりするとき。 - GitHub 上で数式が潰れて表示されたり、赤くなったり、生の
$$テキストとして表示される場合の修正。 - LaTeX、Pandoc、Jupyter、MkDocs、VS Code のみを通すファイルでは不要です。ファイルが両方の場所で読まれる場合、以下の置き換えは通常の TeX/MathJax であるため、他の場所でもレンダリングされます。
ワークフロー
- 記述中は、以下のルール1〜4に従います。
- コミット前に、変更された Markdown ファイルに対してチェックスクリプトを実行します(スクリプトを参照)。
--checkなしで実行すると、その場で修正します。 git diffで結果を確認します。スクリプトは正規表現ベースなので、出力は下書きとして扱います。レンダリングはローカルでは確認できません。不明な場合は GitHub でプレビューします。
クイックリファレンス
| 代わりに使用しないもの | 代わりに使用するもの | 理由 |
|---|---|---|
\tag{2.1} | \qquad\text{(2.1)} | \tag は数式を垂直方向の細い帯に潰します |
\operatorname{Foo} | \mathrm{Foo} | operatorname はサニタイザーによってブロックされます |
\mathrm{Im} D_t | \mathrm{Im}\thinspace D_t | 演算子の間隔を復元します |
\hbox{...}、{\rm ...} | \text{...}、{\mathrm{...}} | プレーン TeX マクロはブロックされます |
\{、\} | \lbrace、\rbrace | CommonMark は \{ を { に変換します |
1行の \\ | \cr | CommonMark は \\ を \ に変換します |
\, | \thinspace | CommonMark は \, を , に変換します |
\, は | , | 二重カンマとしてレンダリングされる可能性があります |
q_*(1つのブロック内に複数の *) | q_\ast | ペアになった * は Markdown の強調になります |
1. \tag{...} を使用しないでください(1文字の垂直方向の潰れを防ぐ)
- バグ:
MathJax.tex2mmlは、\tag{2.1}を含む数式を<mtable displaystyle="true"><mlabeledtr><mtd>...としてシリアル化します。ブラウザの MathML Core 仕様は<mlabeledtr>を削除したため、Chromium/Blink と Firefox はそれをテーブル行として扱わず、数式を1文字幅の列に押し込みます。 - **回避策:**代わりに、
\qquad\text{(2.1)}をディスプレイ数式の最後に置きます。ラベルにプライムが必要な場合は、ネストされた$\tag{3.3$'$}$ではなく、Unicode の′を\text{(3.3′)}内で使用します。
<!-- GitHub では壊れます(垂直方向の列に潰れます) -->
$$
\partial_t \mathcal{Q} + \partial_x \mathcal{J} = 0 \tag{2.1}
$$
<!-- GitHub では機能します -->
$$
\partial_t \mathcal{Q} + \partial_x \mathcal{J} = 0 \qquad\text{(2.1)}
$$
2. \operatorname{...}、\hbox{...}、{\rm ...} を使用しないでください(許可されていないマクロエラーを防ぐ)
- **バグ:**GitHub のサーバーサイドサニタイザーは、
\operatornameと\operatorname*を「次のマクロは許可されていません:operatorname」というバナーでブロックします。 - 回避策:
\operatorname{Foo}を\mathrm{Foo}に置き換えます。直後に文字やバックスラッシュマクロが続く場合(例:\operatorname{Im} D_tや\operatorname{pr} v)、\thinspaceを追加して(\mathrm{Im}\thinspace D_t)演算子の間隔を維持します。- プレーン TeX の
\hbox{...}を\text{...}に置き換え、{\rm ...}を{\mathrm{...}}に置き換えます。
<!-- GitHub では壊れます(「次のマクロは許可されていません:operatorname」) -->
$\operatorname{diag}(1, -1)$ and $\operatorname{Im} D_t$
<!-- GitHub では機能します -->
$\mathrm{diag}(1, -1)$ and $\mathrm{Im}\thinspace D_t$
3. 区切り文字、スペース、改行が CommonMark のアンエスケープを生き残るようにする
- **バグ:**数式が
<math-renderer>に到達する前に、GitHub の Markdown パーサーは$ ... $と$$ ... $$の内部で CommonMark の ASCII 句読点アンエスケープを適用します:\{は{に、\}は}になります。これにより、\left\{で「Missing or unrecognized delimiter for \left」が発生し、ポアソン括弧\{F, G\}から中括弧が静かに削除されます。\\(改行が続かない場合)は\になります。これにより、単一行の行列とケース(例:\begin{pmatrix} 0 & 1 \\ -1 & 0 \end{pmatrix})が\-1に壊れ、赤い未定義マクロエラーが発生します。\,は,,(二重カンマ)になり、\,は,(dxの前のリテラルカンマ)になります。- 数式ブロック内でペアになった
*(例:U_+(z - 2q_*), \quad z = w + Dq_*)は、Markdown の<em>強調として解析される可能性があります。
- 回避策:
\{と\}の代わりに\lbraceと\rbraceを使用します(文字が続く場合はスペースを追加:\lbrace F, G\rbrace)。- 単一行の行列や環境での改行には
\\の代わりに\crを使用するか、複数行の$$...$$ブロック内では\\の直後に改行を入れます。 \,の代わりに\thinspaceを使用し(文字の前に末尾スペース:dt\thinspace dx)、\,は,に置き換えます。- 数式に複数のアスタリスクが含まれる場合は、
*(q_*)の代わりに\ast(q_\ast)を使用します。
<!-- GitHub では壊れます -->
インラインポアソン括弧 $\{F, G\} = 0$、集合 $\left\{ x : x > 0 \right\}$、積分 $\int dx\,dy$、行列 $\bigl(\begin{smallmatrix} 0 & 1 \\ -1 & 0 \end{smallmatrix}\bigr)$。
<!-- GitHub では機能します -->
インラインポアソン括弧 $\lbrace F, G\rbrace = 0$、集合 $\left\lbrace x : x > 0 \right\rbrace$、積分 $\int dx\thinspace dy$、行列 $\bigl(\begin{smallmatrix} 0 & 1 \cr -1 & 0 \end{smallmatrix}\bigr)$。
4. cmark-gfm のために $$ ... $$ ブロックの境界をフォーマットする
- **バグ:**数式に複数の
_や*文字が含まれる場合、cmark-gfmは3つの状況で$$ ... $$ブロックを認識できず、生の$$テキストを残し、アンダースコア間のテキストをイタリック体にします:- 前後に空白行がないトップレベルの
$$ ... $$ブロック。 $$が\begin{aligned}と同じ行にある複数行の$$\begin{aligned} ... \end{aligned}$$ブロック。- リストアイテム(
1. ...または- ...)やブロッククォート(> ...)内でインデントされた複数行の$$...$$ブロック。
- 前後に空白行がないトップレベルの
- 回避策:
-
**トップレベルのディスプレイ数式:**開始と終了の
$$をそれぞれ列0の独自の行に置き、前後に空白行を挿入します:$$ \begin{aligned} a &= b + c, \\ d &= e + f. \end{aligned} \qquad\text{(1.1)} $$ -
**リストアイテムまたはブロッククォート内のディスプレイ数式:**数式全体を 1行 に
$$\displaystyle ...$$として保持し(環境内の改行には\crを使用)、前後に空白行で囲みます:1. 番号付きリストアイテム内: $$\displaystyle S_d = \int_{\mathbb{R}} dt\thinspace \mathcal{L}_d(\phi_t, \psi_t) \qquad\text{(1.2)}$$
-
スクリプト
scripts/fix_github_math.py は上記のルールを Markdown ファイルに適用します。$...$ と $$...$$ の内容のみを書き換え、フェンスコードとインラインコードはそのままにし、数式のないファイルには触れず、冪等です。任意の場所から実行できます:
# 変更せずにチェック(変更が必要なファイルがある場合は終了コード1を返します。CI や pre-commit フックで使用可能)
python3 ~/.claude/skills/github-markdown-math/scripts/fix_github_math.py --check path/to/file.md
# その場で修正
python3 ~/.claude/skills/github-markdown-math/scripts/fix_github_math.py path/to/file.md
このスキルがプロジェクトにインストールされている場合(.claude/skills/github-markdown-math/)、そのパスを使用します。
制限
- 正規表現ベースなので、差分を確認してください。また、GitHub が結果を正しくレンダリングすることを確認できません。
\operatorname{...}は、引数にネストされた括弧がない場合のみ変換されます。\thinspaceは、固定リスト(Im、im、pr、graph、Div、ker、dim、deg、span、spec、End、Re、tr、rank)の名前に対してのみ\mathrm{...}の後に追加されます。他の名前は手動で追加してください。- アスタリスクのうち、
q_*のみがq_\astに書き換えられます。他の*は手動で確認してください(ルール3)。 - ブロッククォートの数式は1レベルの深さ(
>)まで処理され、ネスト(> >)は処理されません。 - 2つの通貨記号を含む行(
$5 and $10)はインライン数式として読み取られる可能性があります。これは、その間のテキストにルールが書き換えるバックスラッシュシーケンスが含まれていない限り無害です。 - これらのルールは観察された GitHub の動作を説明しています。GitHub がこれらのバグのいずれかを修正した場合、対応する回避策は不要になります。