最近在处理公式跨系统迁移需求时,遇到一个 MathJax 导出 MathML 的典型兼容性问题:LaTeX 中下划线公式在 MathJax 渲染SVG显示正常,但导出 MathML 交给浏览器原生或第三方引擎渲染后,下划线长度严重缩短,完全达不到预期宽度。这里完整记录排查过程最终选择的方案。

问题复现

原始 LaTeX 公式为一段带长度的填空下划线,写法如下:

\underline{\hspace{2em}} cm

通过 MathJax v3 序列化导出后,得到的 MathML 代码为:

<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
  <munder>
    <mspace width="2em"></mspace>
    <mo accent="true">&#x2015;</mo>
  </munder>
  <mi>c</mi>
  <mi>m</mi>
</math>

异常现象:在 MathJax 自身的 CHTML/SVG 渲染环境下,下划线长度正常;但将该 MathML 代码交给浏览器原生 MathML 渲染器、或其他标准 MathML 引擎渲染时,下划线仅保留字符自身宽度,不会随上方 2em 的空格拉伸,视觉效果远短于预期。

根因排查

1. 字符本身不具备拉伸属性

MathJax 输出的下划线使用了 &#x2015;(U+2015,Horizontal Bar 水平横杠字符)。根据 MathML 规范,<mo> 元素的 stretchy(拉伸)属性由运算符字典决定,默认值为 false。U+2015 不属于 MathML 默认定义的可水平拉伸运算符,本身不具备随相邻元素自动延长的能力。

2. accent 属性锁死了字符尺寸

导出代码中 <mo accent="true"> 将该横杠标记为「重音符号」。在 <munder> 结构的规则中,被标记为 accent 的下标元素会保持自身固有尺寸,不会跟随基元素(此处为 <mspace width="2em">)的宽度进行拉伸,进一步限制了下划线的长度。

3. 渲染引擎差异导致的隐蔽性

该问题很容易被忽略,核心原因是 MathJax 自身的渲染逻辑不依赖原生 MathML 规则。MathJax 内部采用自主布局计算,会自行计算下划线的长度和位置,因此在 MathJax 环境中验证一切正常;只有当序列化导出的 MathML 交给标准渲染引擎时,规范层面的特性差异才会暴露出来。

修复方案

方案一:LaTeX 层替换为 \rule 命令

绕开 \underline 依赖的 <munder> + stretchy 机制,直接使用 TeX 原生的 \rule 命令绘制精确尺寸的横线。该方案在所有渲染环境下效果完全一致,是跨场景兼容性最好的选择。

语法格式:\rule[垂直偏移]{横线长度}{线条粗细}

修复后示例:

\rule[-0.2em]{2em}{0.4pt}\ \text{cm}

参数说明:

  • -0.2em:向下偏移 0.2em,模拟常规下划线的垂直位置
  • 2em:横线总长度,与原写法中 \hspace{2em} 的宽度完全对应
  • 0.4pt:线条粗细,接近标准下划线的视觉粗细

适用场景:对跨环境一致性要求高、需要精确控制下划线尺寸的场景,比如试卷填空题、表单占位下划线。

方案二:手动修正输出的 MathML 代码

如果只能操作已生成的 MathML 代码,可以通过修改 <mo> 元素的字符和属性来恢复拉伸能力,适合存量代码批量修复。

修正后完整代码:

<math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
  <munder>
    <mspace width="2em"></mspace>
    <mo stretchy="true" accent="false">_</mo>
  </munder>
  <mi>c</mi>
  <mi>m</mi>
</math>

三个核心修改点:

  1. &#x2015; 替换为普通下划线字符 _(U+005F),该字符在 MathML 中默认具备水平拉伸特性
  2. 移除 accent="true" 或显式设为 false,解除重音模式的尺寸限制
  3. 显式添加 stretchy="true",确保拉伸行为在所有渲染器中一致生效

适用场景:已有大量存量 MathML 文件、无法修改源 LaTeX 代码的场景,可通过正则批量替换完成修复。

方案三:全局配置 MathJax,统一修正 \underline 输出

如果项目中大量使用 \underline 命令,逐个修改 LaTeX 成本较高,可以通过 MathJax 的 TeX 宏配置重定义命令,让其默认输出符合原生拉伸规则的 MathML 结构。

全局配置代码:

window.MathJax = {
  tex: {
    macros: {
      underline: [
        '\\mmlMunder{#1}{\\mmlToken{mo}[stretchy=true,accent=false]{_}}',
        1
      ]
    }
  }
};

该配置会全局替换 \underline 的输出逻辑,所有使用该命令的公式都会生成带 stretchy="true" 的标准下划线字符,无需修改业务层的 LaTeX 代码。

适用场景:项目全量导出 MathML、且大量使用 \underline 命令的场景,一次配置全局生效。

方案四:弃用MathJax,改 Katex

上面三种方案都是临时补丁,没有正真解决问题。

除了\underline\overline也是一样的错误,横线没有拉伸。

这算是MathJax官方bug, 这个问题从老早之前就提出了,结果到现在还是这样,Issue on equation overline.

鉴于项目中使用的mermaidmathlive两个库都采用了katex,于是我们项目也直接弃用mathjax了。

总结

MathJax 默认使用 U+2015 字符 + accent 标记实现下划线,没有处理标准 MathML 环境下的拉伸兼容性。

选型建议:

  • 直接弃用MathJax