flowchart LR
subgraph xaringanHost [xaringan]
beforeInit["mathjax-config.js<br/>MathJax 2 tex2jax"]
hook["wrap-display-math.R"]
end
subgraph quartoHost [Quarto]
html["html-math-method<br/>相对路径加载 load-mathjax.js"]
reveal["RevealMath.MathJax3<br/>字面相对路径"]
runtime["tex-chtml.js + woff2 + dynamic"]
end
beforeInit --> hook
html --> runtime
reveal --> runtime
MathJax 配置资产:Quarto 网站与 Reveal 幻灯片
MathJax 4 本地运行时、相对路径与和 xaringan 资产的分工
摘要
js/mathjax-quarto/ 提供 Quarto 普通 HTML 与 Reveal.js 幻灯片 的离线公式运行时。运行时是 MathJax 4.1.3 的组合组件 tex-chtml.js,加上 CHTML 需要的 woff2/ 与 dynamic/。入口脚本 load-mathjax.js 按自身地址设置字体路径,再加载这个组件。
js/mathjax-xaringan/ 继续只服务 xaringan。两套资产的宿主、MathJax 世代和接入点都不同。
本地清点见 验收指引。
1. 和 js/mathjax-xaringan 的分工
js/mathjax-xaringan |
js/mathjax-quarto |
|
|---|---|---|
| 宿主 | xaringan / remark.js | Quarto HTML 与 Reveal.js |
| MathJax | 2.x,由 xaringan 自己加载 | 4.1.3,仓库内自带组合组件 |
| 配置入口 | nature.beforeInit 指向 mathjax-config.js |
html-math-method.url 与 Reveal 的 mathjax3.mathjax |
| 配置对象 | tex2jax、TeX.extensions |
tex、output.fontURL;3 的配置对象可继续用在 4 上 |
| 配套文件 | wrap-display-math.R(knitr hook,成对使用) |
无 R hook;Reveal 另需改 Pandoc 模板插件名 |
| 字体 | 随 MathJax 2 的按需请求 | 只收录 @mathjax/mathjax-newcm-font 的 CHTML woff2 与 dynamic |
把 mathjax-config.js 配进 Quarto,或把 tex-chtml.js 配进 xaringan 的 beforeInit,都不能得到另一套资产的行为。
2. 这套资产解决的问题
Quarto 默认把公式脚本指到 jsDelivr。课堂网络不稳,或用 Chrome、Cursor 直接打开 _site 里的 HTML 时,请求发不出去,页面上会留下 \(u_i\) 和 \[...\]。
MathJax 2.7.9 的完整发行包含未压缩源码、文档、测试和多套字体,解压后约 40MB。放进 project.resources 时,Quarto 递归扫描可能报 Maximum call stack size exceeded。2.x 还要先加载 MathJax.js,再按 ?config= 继续请求组件。3 与 4 改为一个组合组件,官方说明见 配置 与 加载。
Reveal 自带模板使用 math: 与插件 RevealMath。那是 MathJax 2 插件,加载后调用 MathJax.Hub。MathJax 4 没有这个接口。应改用模板里已经提供的 RevealMath.MathJax3:它先写入 window.MathJax,再加载一个组件文件。load-mathjax.js 会保留插件已经写好的 startup,翻页后仍会重新排版。
还有一条只在直接打开 HTML 时出现的路径问题。若用 new URL(...).href 从 reveal.js 的地址去推脚本地址,会同时错两处:从 site_libs/revealjs/dist/ 向上两级只到 site_libs/,请求落到 site_libs/vendor/;.href 还会把 Windows 盘符冒号编成 %3A,Chrome 不把 file:///d%3A/... 当作 D:\...。插件配置里的脚本地址因此写成字面相对路径。加载器内部的 new URL("woff2/", currentScript.src) 使用的是浏览器已经解析好的脚本 URL,这和“自己拼 file URL”不是同一件事。
3. 运行时内容
runtime/ 安装到业务项目的 vendor/mathjax/:
vendor/mathjax/
├── load-mathjax.js
├── tex-chtml.js
├── input/tex/extensions/boldsymbol.js
├── VERSION
├── LICENSE
├── NOTICE
├── woff2/
└── dynamic/
tex-chtml.js 约 1MB,是 TeX 输入加 CommonHTML 输出,来自 MathJax 4.1.3。woff2/ 现为 105 个文件,dynamic/ 现为 40 个文件,取自 @mathjax/mathjax-newcm-font 4.1.3 的 CHTML 子集。数量与版本写在 runtime/VERSION。组件与字体均为 Apache License 2.0,全文在 runtime/LICENSE,归属在 runtime/NOTICE。其中没有 MathJax 2 整包,也没有 SVG 字体包。boldsymbol.js 是 TeX 扩展,组合组件的预载列表里没有 \boldsymbol。
load-mathjax.js 用当前 <script> 的地址解析同目录资源,并合并 Reveal 插件可能已经写入的 window.MathJax。它还会把 $$ 里的 align、equation 等外层环境改成 aligned、gathered,避免 MathJax 4 报嵌套公式结构,并加载 [tex]/boldsymbol。普通 HTML、Reveal 与 xaringan 离线稿共用这一份加载器。xaringan 的联网稿仍用 MathJax 2,见 xaringan 双工作流。
4. 安装到业务项目
Profile 名是 mathjax-quarto。它没有放进 all,避免每个课程仓库都复制字体。
cd D:\github\harness
.\scripts\powershell\Install-Harness.ps1 `
-InstallProfile mathjax-quarto `
-ProjectRoot "D:\path\to\your-quarto-site"安装结果:
| 源 | 目标 |
|---|---|
js/mathjax-quarto/runtime/ |
vendor/mathjax/ |
js/mathjax-quarto/snippets/quarto-html-math.yml |
_quarto.harness-mathjax.yml |
把 _quarto.harness-mathjax.yml 合并进项目自己的 _quarto.yml:
project:
resources:
- vendor/mathjax
html-math-method:
method: mathjax
url: /vendor/mathjax/load-mathjax.js/vendor/... 只作为 Quarto 的源配置。普通 HTML 渲染时会改写成相对于该页面的路径,例如主题页两级目录写成 ../../vendor/mathjax/load-mathjax.js,更深的页面层数更多。这条相对路径在 HTTP 与直接打开文件时都能找到资源。
.renvignore 建议加入 vendor/,避免 R 依赖扫描把这些 JavaScript 当成包代码。
5. Reveal 模板
Reveal 不走上面的自动改写。Quarto 1.10 的 share/formats/revealjs/pandoc/template.html 在 math: 里写入 mathjax: '$mathjaxurl$',插件名是 RevealMath。$mathjaxurl$ 会变成站点根绝对路径 /vendor/mathjax/load-mathjax.js,只有 HTTP 根目录正好是站点输出目录时才成立。
在业务项目根生成一份模板副本:
.\js\mathjax-quarto\patch-reveal-template.ps1 `
-Destination "D:\path\to\your-quarto-site\vendor\mathjax\reveal-template.html"默认路径是两级目录用的 ../../vendor/mathjax/load-mathjax.js。课件放在输出目录下三级时,把 -RelativePath 改成 ../../../vendor/mathjax/load-mathjax.js。脚本拒绝盘符、file:、http: 和反斜杠。
然后在幻灯片格式里引用这份模板,例如:
format:
revealjs:
template: ../../vendor/mathjax/reveal-template.html这里的 ../../ 是 qmd 到模板文件 的路径,按源文件位置调整。模板内部的 mathjax: 则按 生成 HTML 到输出根 的深度填写。两套深度分别计算。
脚本针对 Quarto 1.10 模板中的 math: / TeX-AMS_HTML-full 块。升级 Quarto 后若脚本报找不到该块,按 snippets/reveal-math-block.txt 手工改,并确认插件仍是 RevealMath.MathJax3。
Pandoc 模板里的 $ 是变量。模板 JavaScript 若要输出 $ 定界符,需写成 $$。现行片段只保留 \( 与 \[。
生成后的幻灯片源码应类似:
mathjax3: {
mathjax: "../../vendor/mathjax/load-mathjax.js"
}插件列表中出现 RevealMath.MathJax3。源码里不应出现 cdn.jsdelivr.net、MathJax.Hub 或 file:///。
6. 验收
- 在 harness 中执行
Rscript js/mathjax-quarto/test/Rscript/test-inventory.R,看到L0 PASS。 - 直接打开
js/mathjax-quarto/test/fixture/page.html,确认\(u_i\)已排版。 - 业务项目渲染后,普通页面的
<script src>是指向vendor/mathjax/load-mathjax.js的相对路径。 - 同一份 Reveal HTML 用站点 HTTP 服务打开一次,再用浏览器直接打开文件一次。两种方式下公式都排版,且脚本 URL 的盘符未被编成
%3A。
移动 Reveal 课件的目录深度后,要改模板里的相对路径,并重新渲染。只改 _quarto.yml 的 html-math-method 不会更新幻灯片模板。
已经提交进仓库、且不是当前 qmd 渲染产物的静态 HTML,仍可能指向旧的 CDN。改 YAML 不会改写这些文件。