一、问题现象
在 Asciidoctor 中使用公式(stem:[...] 或 [latexmath] 块)时,公式里带数字下标的部分出现位置错位(如下标没有落在正确基线、上下飘移、与主字符间距异常、重叠等),而不是完全无法显示。
asciidoc 公式如下:
* 页内偏移 = 低 12 位:stem:[000000000100_2=4]
生成公式如下:数字下标 2 错位了
通过以下 options.rb 配置可以解决:
require 'asciidoctor'
# 官方属性:让 Asciidoctor 生成交给 MathJax 的标记
Asciidoctor::DEFAULT_ATTRIBUTES['stem'] = 'latexmath'
# 非官方属性无法直接改 MathJax 配置,用 postprocessor 替换渲染器
Asciidoctor::Extensions.register do
postprocessor do
process do |doc, output|
output.gsub('config=TeX-MML-AM_HTMLorMML', 'config=TeX-MML-AM_CHTML')
end
end
end
二、根本原因
1. Asciidoctor 只负责"标记",不负责"排版"
Asciidoctor 不做公式排版。它把公式原样包进 HTML 元素(如 <span class="math">\(...\)</span>),真正的字形定位与基线对齐由前端 MathJax 完成。
因此,下标错位是 MathJax 渲染阶段的问题,与 Asciidoctor 的标记生成无关(只要标记正确)。
2. stem=latexmath 决定"公式是否进入渲染管线"
- 若未设置(或设错)
stem,Asciidoctor 不生成 MathJax 需要的标记,公式可能原样显示或完全不渲染。 - 设置
stem=latexmath后,Asciidoctor 会:- 按 LaTeX 语法处理公式;
- 在文档头注入
latexmath相关属性; - 触发 MathJax 脚本加载。
这是"公式能被正确交给 MathJax"的前提,但不是下标错位的直接原因。
3. 下标错位的直接元凶:HTMLorMML → HTML-CSS 渲染器
Asciidoctor 默认生成的 MathJax 配置串是:
config=TeX-MML-AM_HTMLorMML
其中 HTMLorMML 表示输出渲染器优先使用 HTML-CSS,回退到 MathML。
HTML-CSS 渲染器的下标错位机制:
HTML-CSS 通过测量数学字体的字形度量(font metrics)来手工定位上下标。它对字体度量数据与浏览器实际渲染的字体之间的匹配非常敏感。当出现以下情况时,下标定位会算错:
- 字体度量缓存与真实字体不一致:MathJax 使用内置的字体度量文件(如
TeX/fonts/HTML-CSS/TeX/woff/...及对应 metrics),若字体加载失败或回退到系统字体,度量数据与实际字形不匹配; - 字体版本/子集差异:某些字体子集缺少数字字形或度量异常,导致下标字符的宽度、深度被算错,基线下移量出错;
- 浏览器对字体度量(ascent/descent/unitsPerEm)解析差异:HTML-CSS 依赖精确的 CSS 基线定位,现代浏览器在字体度量四舍五入、subpixel 定位上的差异会被放大成可见的错位;
- 缩放/DPR 影响:在高 DPI 或页面缩放时,HTML-CSS 的手工定位误差累积,数字下标这类小字号元素最易暴露问题。
结果就是:主字符正常,但数字下标位置偏移、上下飘移或与主字符间距异常——正是你遇到的现象。
4. 为什么换成 CHTML 就好了
CHTML = CommonHTML,是 MathJax 2.7+ 推出的新一代 HTML 渲染器:
- 同样用 HTML+CSS 排版,但字体加载与度量机制更健壮,对字体缺失、度量不一致有更好的回退处理;
- 生成的 DOM 更简洁,基线定位更稳定,对浏览器渲染差异、缩放/DPR 不敏感;
- 对现代浏览器兼容性明显优于 HTML-CSS。
因此换用 CommonHTML 后,下标定位不再依赖那套脆弱的字体度量匹配,错位消失。
5. 为什么必须用 postprocessor 改,而不能设属性
config=TeX-MML-AM_HTMLorMML 这个配置串是 Asciidoctor 内置模板里硬编码的,官方没有暴露对应文档属性来修改它(mathjax-url 等属性无法覆盖这段 config)。所以只能:
- 用 postprocessor 在生成的 HTML 上做字符串替换(你的做法),或
- 自定义 backend / 修改模板。
三、原因归纳
| 环节 | 因素 | 对下标错位的作用 |
|---|---|---|
| Asciidoctor 标记 | stem=latexmath | 保证公式进入 MathJax 渲染管线;缺了它公式根本不渲染,但不是错位的直接原因 |
| MathJax 配置 | 默认 HTMLorMML(HTML-CSS 渲染器) | 直接原因:HTML-CSS 依赖脆弱的字体度量手工定位下标,字体度量与实际字体不匹配时数字下标错位 |
| 修复 | 替换为 CHTML(CommonHTML 渲染器) | 改用基线定位更稳定的渲染器,绕开字体度量匹配问题 |
一句话总结:下标错位 = MathJax 的 HTML-CSS 渲染器在你这套环境(字体加载/度量与浏览器渲染不匹配)下手工定位上下标出错;stem=latexmath 保证公式进入渲染管线,把渲染器换成 CommonHTML 则从根本上消除了错位。
四、方案对比与建议
| 方案 | 做法 | 适用场景 |
|---|---|---|
| 当前方案 | postprocessor 替换 HTMLorMML → CHTML | MathJax 2.x,改动最小,立即见效 |
| 升级 MathJax 3 | 用 tex-chtml 组合 | 新浏览器环境,字体与渲染管线彻底重写,更根本 |
| 改用 SVG 渲染器 | config=TeX-MML-AM_SVG | 需要矢量输出、打印/PDF、对定位精度要求高 |
注:若目标是导出 PDF 或对排版精度要求极高,SVG 渲染器(
TeX-MML-AM_SVG)在上下标定位上通常比 CommonHTML 更稳定,可作为进一步优化方向。