Community编程与开发github.com

gustavdelius/github-markdown-math

在Markdown中编写、审查和修复LaTeX数学公式($...$ 和 $$...$$),使其在GitHub上正确渲染(README、文档、Issue、PR、Wiki、仓库内的笔记),避免公式因 \tag 被压缩成垂直条、\operatorname 等宏不被允许、$$ 块不渲染以及 CommonMark 吃掉反斜杠等问题。每当起草或编辑将在GitHub上显示的数学Markdown时使用,或当GitHub显示红色或乱码公式时使用。对于LaTeX、Pandoc、Jupyter或VS Code输出无需使用。

github-markdown-math 是什么?

该技能解决了GitHub在渲染Markdown中的LaTeX数学公式时特有的渲染问题。GitHub的渲染过程分为服务端(cmark-gfm + 数学清洗器)和客户端(<math-renderer> + MathJax.tex2mml)两个阶段,两个阶段都存在会导致普通LaTeX失效的Bug。技能提供了四个核心规则:避免使用 \tag 以防止公式被挤压成单字符宽度的垂直列;避免使用 \operatorname、\hbox 或 {\rm ...} 以防止服务器端清洗器报错;使用替代字符(如 \lbrace 代替 \{、\cr 代替 \\、\thinspace 代替 \,)以安全通过CommonMark的反斜杠取消转义;正确格式化 $$...$$ 块的边界以防止cmark-gfm无法识别。该技能适用于所有需要在GitHub上正确显示数学公式的Markdown文件,包括README、文档、Issue、PR、Wiki和仓库内笔记。技能还附带了一个基于正则表达式的Python脚本,可以自动将Markdown文件中的数学公式按照规则进行转换,并支持 --check 模式用于CI或pre-commit hook。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/gustavdelius/claude-skills/tree/HEAD/github-markdown-math

Installed? Explore more 编程与开发 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

GitHub Markdown 数学公式:渲染规则与变通方案

GitHub 分两个阶段渲染 Markdown 中的数学公式:

  1. 服务端(cmark-gfm + 数学清洗器): 找到 $ ... $ 和 $$ ... $$ 的边界,对 ASCII 标点应用 CommonMark 反斜杠取消转义,并拒绝不允许的 LaTeX 宏,然后发出 <math-renderer> 标签。
  2. 客户端(<math-renderer> + MathJax.tex2mml): 将 TeX 转换为 MathML 并注入页面,以便浏览器原生 MathML Core 布局。

这两个阶段都存在破坏普通 LaTeX 的 Bug。以下规则是针对这些 Bug 的变通方案。

何时使用此技能

  • 在将在 GitHub 上阅读的 .md 文件中编写或编辑数学公式时。
  • 修复在 GitHub 上渲染为被挤压、红色或原始 $$ 文本的公式时。
  • 对于仅通过 LaTeX、Pandoc、Jupyter、MkDocs 或 VS Code 处理的文件则不需要。如果一个文件在两个地方都被读取,下面的替换仍然是普通的 TeX/MathJax,因此它们在别处仍然可以渲染。

工作流程

  1. 在编写时,遵循下面的规则 1 到 4。
  2. 在提交之前,对更改过的 Markdown 文件运行检查脚本(参见 脚本)。不加 --check 运行则就地修复。
  3. 使用 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, \rbraceCommonMark 将 \{ 变成 {
单行中的 \\\crCommonMark 将 \\ 变成 \
\,\thinspaceCommonMark 将 \, 变成 ,
\,, ,(连续), 否则会渲染成双逗号
q_*(一个块中有多个 *)q_\ast成对的 * 可能变成 Markdown 强调

1. 永远不要使用 \tag{...}(防止垂直挤压成一字符宽)

  • Bug: MathJax.tex2mml 将任何包含 \tag{2.1} 的公式序列化为 <mtable displaystyle="true"><mlabeledtr><mtd>...。浏览器 MathML Core 规范移除了 <mlabeledtr>,因此 Chromium/Blink 和 Firefox 不会将其视为表格行,而是将公式挤压成一字符宽的列。
  • 变通方案: 在显示公式的末尾放置 \qquad\text{(2.1)} 代替。如果需要标签中的撇号,请在 \text{(3.3′)} 内使用 Unicode ′,而不是嵌套的 $\tag{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 ...}(防止不允许的宏错误)

  • Bug: GitHub 的服务端清洗器会拦截 \operatorname 和 \operatorname*,并显示提示 The following macros are not allowed: operatorname。
  • 变通方案:
    • 将 \operatorname{Foo} 替换为 \mathrm{Foo}。当它后面紧跟着一个字母或反斜杠宏时(例如 \operatorname{Im} D_t 或 \operatorname{pr} v),添加 \thinspace(\mathrm{Im}\thinspace D_t)以保持运算符间距。
    • 将纯 TeX 的 \hbox{...} 替换为 \text{...},将 {\rm ...} 替换为 {\mathrm{...}}。
<!-- 在 GitHub 上损坏("The following macros are not allowed: operatorname") -->
$\operatorname{diag}(1, -1)$ and $\operatorname{Im} D_t$

<!-- 在 GitHub 上正常工作 -->
$\mathrm{diag}(1, -1)$ and $\mathrm{Im}\thinspace D_t$

3. 使分隔符、间距和换行符能够安全通过 CommonMark 的取消转义

  • Bug: 在数学公式到达 <math-renderer> 之前,GitHub 的 Markdown 解析器会在 $ ... $ 和 $$ ... $$ 内部对 ASCII 标点应用 CommonMark 反斜杠取消转义:
    • \{ 变成 {,\} 变成 }。这会导致 \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),并将 \,, , 替换为 , 。
    • 当方程包含多个星号时,使用 \ast(q_\ast)代替 *(q_*)。
<!-- 在 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 格式化 $$ ... $$ 块的边界

  • Bug: 当一个方程包含多个 _ 或 * 字符时,cmark-gfm 在三种情况下无法识别 $$ ... $$ 块,留下原始 $$ 文本并使下划线之间的文本变为斜体:
    1. 顶层 $$ ... $$ 块前后没有空行。
    2. 多行 $$\begin{aligned} ... \end{aligned}$$ 块中 $$ 与 \begin{aligned} 在同一行,而不是单独一行。
    3. 在列表项(1. ... 或 - ...)或块引用(> ...)内缩进的多行 $$ ... $$ 块。
  • 变通方案:
    • 顶层显示数学: 将开闭的 $$ 放在各自的行的第 0 列,前后各有一个空行:

      
      $$
      \begin{aligned}
      a &= b + c, \\
      d &= e + f.
      \end{aligned} \qquad\text{(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 hook)
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{...} 仅在其参数没有嵌套花括号时被转换。仅对一组固定的名称(Im、im、pr、graph、Div、ker、dim、deg、span、spec、End、Re、tr、rank)在 \mathrm{...} 之后添加 \thinspace;对于其他名称,请手动添加。
  • 在星号中,只有 q_* 被重写为 q_\ast;请手动检查其他 *(规则 3)。
  • 块引用方程仅处理一层深度(> ),不处理嵌套(> > )。
  • 带有两个货币符号的行($5 and $10)可能被读取为内联数学。除非它们之间的文本包含规则会重写的反斜杠序列,否则这是无害的。
  • 这些规则描述了观察到的 GitHub 行为。如果 GitHub 修复了这些 Bug 中的某一个,相应的变通方案就变得不必要了。

相关技能