本文是课程 Quarto 网站的公式排版指南。交互课件的页面结构见《交互课件开发总指南》。这里只处理数学公式如何在无外网、以及直接打开 HTML 时仍然排版。
1 要达到的结果
生成后的页面要同时满足四件事:
- 公式在浏览器里排版,而不是停在
\[...\]或\(u_i\)。 - 不请求 jsDelivr 或其他 CDN。
- 用
http://127.0.0.1/打开整站,和用 Chrome、Cursor 直接打开_site里的 HTML,结果一致。 - 仓库里只保留排版真正需要的文件,而不是几十兆的完整发行包。
2 失败时页面长什么样
还没排版时,Reveal 幻灯片里能直接看到 TeX 分隔符:
<h2><span class="math inline">\(u_i\)</span> 与 <span class="math inline">\(e_i\)</span> 能互换吗?</h2>
<span class="math display">\[ u_i=Y_i-E(Y_i\mid X_i) \]</span>排版成功后,这些节点内部会出现 mjx-container,可见文字变成带下标的 \(u_i\)、\(e_i\)。网络面板里应能看到本站的 load-mathjax.js、tex-chtml.js 和 woff2,状态都是成功。
下面几种“看起来像加载了”的地址都是失败:
| 地址 | 实际去了哪里 |
|---|---|
https://cdn.jsdelivr.net/npm/mathjax@... |
外网,离线即失败 |
/vendor/mathjax/load-mathjax.js |
仅当 HTTP 根目录就是 _site 时成立 |
file:///d%3A/github/course-em/_site/site_libs/vendor/mathjax/load-mathjax.js |
盘符被编码,而且目录多了一层 site_libs |
3 本站有两条加载路径
Quarto 对普通 HTML 和 Reveal 幻灯片用的不是同一段加载代码。
普通 HTML 由 Pandoc 写入:
<script defer src="../../../vendor/mathjax/load-mathjax.js"></script>层数随页面深度变化。exam/test/single-choice/index.html 是三层,topics/某日/index.html 是两层。这条路径相对 HTML 文件,直接打开文件也能找到资源。
Reveal 幻灯片由 vendor/mathjax/reveal-template.html 写入:
mathjax3: {
mathjax: "../../vendor/mathjax/load-mathjax.js"
}
plugins: [RevealMath.MathJax3]当前课件都在 _site/slide/slide-reactive/ 或 _site/slide/slide-reveal/,所以上面这个固定相对深度是对的。Quarto 自带模板默认写的是 math: 和 RevealMath,那是 MathJax 2 插件,加载后调用 MathJax.Hub。MathJax 4 没有这个接口。
4 曾经踩过的三条路
4.1 CDN
_quarto.yml 不写 html-math-method 时,Quarto 默认把 MathJax 指到 jsDelivr。课堂网络不稳定或离线打开 HTML 时,脚本不到,公式保持原样。
4.2 MathJax 2.7.9 整包
2.x 必须先加载 MathJax.js,再靠 ?config=TeX-AMS_HTML-full 继续请求配置、输入输出组件和多种字体。官方说明 3 和 4 改为一个组合组件,避免这一串请求:配置、加载。
GitHub 上的 2.7.9 标签解压后约 40MB,里面还有未压缩源码、文档、测试和多套字体格式。把它放进 project.resources 时,Quarto 递归扫描会报 Maximum call stack size exceeded。把复制塞进整站 robocopy 同步脚本,又会让每次渲染都搬运整站,并且一关闭同步,_site/vendor 就丢了。
4.3 用 JavaScript 计算绝对 file 地址
曾以 reveal.js 的地址为基准执行:
new URL("../../vendor/mathjax/load-mathjax.js", revealJsSrc).hrefreveal.js 位于 _site/site_libs/revealjs/dist/reveal.js。从 dist/ 向上两级只到 site_libs/,于是请求变成 _site/site_libs/vendor/mathjax/load-mathjax.js。文件实际在 _site/vendor/mathjax/。
new URL().href 还会把 Windows 盘符冒号编成 %3A。Chrome 不会把 file:///d%3A/... 当作 D:\...,脚本直接 404。相关记录见 百分号编码的盘符 和 file URL 再解析。
5 现行方案
资产固定为 MathJax 4.1.3,放在项目根目录:
vendor/mathjax/
├── load-mathjax.js
├── tex-chtml.js
├── reveal-template.html
├── woff2/
└── dynamic/
tex-chtml.js 约 1MB,是 TeX 输入加 CommonHTML 输出的组合组件。woff2/ 与 dynamic/ 只取 @mathjax/mathjax-newcm-font 里 CHTML 需要的文件。load-mathjax.js 用当前脚本地址设置 fontURL 和 dynamicPrefix,再加载 tex-chtml.js。Reveal 的 MathJax 3 插件可能已经写过 window.MathJax,加载器保留其中的 startup,避免盖掉翻页后的重新排版。
_quarto.yml 有两项:
project:
resources:
- vendor/mathjax
html-math-method:
method: mathjax
url: /vendor/mathjax/load-mathjax.js/vendor/... 只作为 Quarto 的源配置。普通 HTML 渲染时会改写成相对于该页面的路径。Reveal 模板不使用这个绝对地址,而是写死 ../../vendor/mathjax/load-mathjax.js。
接入 _reveal.yml、slide/slide-reveal/_metadata.yml 和 chpt00-revealjs-basic.qmd 的幻灯片,都要引用 vendor/mathjax/reveal-template.html。模板里的插件是 RevealMath.MathJax3。该插件先设置 window.MathJax,再加载一个组件文件,这与 MathJax 4 的官方加载方式一致;3 的配置对象可以继续用在 4 上。
6 验收
渲染后先看源码,再看页面。
源码里应出现本站相对路径,不应出现 cdn.jsdelivr.net、MathJax.Hub 或 file:///。幻灯片配置类似:
mathjax3: {
mathjax: "../../vendor/mathjax/load-mathjax.js"
}然后用两种方式打开同一份 _site/slide/slide-reactive/chpt02-population-sample-regression.html:
- 站点根目录是
_site的 HTTP 服务。 - Chrome 或 Cursor 直接打开该 HTML 文件。
两种方式下,标题中的 \(u_i\) 和正文中的 \[...\] 都应排版。开发者工具里 MathJax-script 的地址应落在 _site/vendor/mathjax/load-mathjax.js,盘符保持 D: 这种未编码形式。
7 以后修改时要守住的做法
- 新增或移动 Reveal 课件时,先数它相对于
_site的目录深度。深度变了,就要改模板里的../../vendor/mathjax/load-mathjax.js,不能只改普通 HTML 的自动相对路径。 - 幻灯片脚本地址写成相对路径。不要用
new URL(...).href生成file://绝对地址。 - 不要把 MathJax 整包放进
project.resources,也不要靠整站镜像脚本搬运它。 - Pandoc 模板里的
$是变量符号。模板 JavaScript 如果要输出$分隔符,需写成$$;现行模板只保留\(和\[,避免这个问题。 - 升级 Quarto 后,对照
share/formats/revealjs/pandoc/template.html检查本仓库的reveal-template.html。数学插件段落必须仍是RevealMath.MathJax3。 tutorial/book-em/和lab/lab-em/中已生成的 HTML 仍可能指向 MathJax 3 CDN。它们不是当前 qmd 渲染产物,改_quarto.yml不会更新它们。.renvignore忽略vendor/,避免 R 依赖扫描把这些 JavaScript 当成包代码。
8 和交互课件规范的关系
公式资产属于全站公共资源,放在项目根目录 vendor/mathjax/。它不属于某一份 slide/slide-reactive/chptXX/ 课件。课件专用的 CSS、脚本和 R 代码仍按交互课件开发总指南放在同名目录。