AIGC标识 修复 Typora 无法渲染 \mathbb、\mathcal、\mathfrak 的问题

测试环境:Typora 1.14.15(Electron 42.2.0,内置 MathJax 4.1.0),Windows。

一句话结论:MathJax 4 把空心字母、花体字母等字形拆成了「动态字体」,渲染时才从 CDN 异步加载;而 Typora 使用同步 API 渲染公式,等不到异步加载完成。把字体文件放到本地,并在启动阶段预载,即可解决。

一、现象

Typora 升级到 1.13.6 及以后版本(渲染引擎从 MathJax 2 换成 MathJax 4)后,会出现一个很迷惑的问题:

  • 普通公式完全正常,例如 \sum_{i=1}^{n} a_i、\alpha\beta\gamma;
  • 但 \mathbb{R}、\mathcal{L}、\mathfrak{g} 这类公式不报错,也不显示,直接是一片空白;
  • 在调试控制台里偶尔能看到这样的报错:
MathJax retry -- an asynchronous action is required;
try using one of the promise-based functions and await its resolution.

二、原因

1. MathJax 4 的动态字体机制

MathJax 4 使用的 mathjax-newcm 字体,并没有把所有字形都打进主 bundle。常用字符在主 bundle 中,而空心体(double-struck)、花体(calligraphic)、哥特体(fraktur)等字形被拆分到独立的动态字体文件里,例如:

double-struck.js
calligraphic.js
fraktur.js

字体配置中的 dynamicPrefix 指向 @mathjax/mathjax-newcm-font/svg/dynamic,而 loader 的 fonts 路径默认是:

https://cdn.jsdelivr.net/npm/@mathjax

因此,第一次渲染 \mathbb{R} 时,MathJax 会去 jsDelivr 异步下载 double-struck.js。

2. Typora 的同步渲染

Typora 内部调用的是同步接口 MathJax.tex2svg()。当渲染过程中需要异步加载字体时,MathJax 会抛出 MathJax retry,要求调用方改用 Promise 接口并等待加载完成。

Typora 并不会处理这个 retry,于是最终结果就是:公式位置一片空白,且不显示错误。

这里有一个关键点:仅仅把 CDN 地址换成本地路径是不够的。换成本地以后加载动作仍然是异步的,同步渲染依旧会失败。必须配合官方提供的启动选项 startup.loadAllFontFiles: true,让 MathJax 在初始化阶段把所有动态字体预载完毕,后续渲染才能同步完成。

三、解决步骤

下面以 Typora 安装在 D:\ProgramFiles\Typora 为例。如果你的安装路径不同,把路径替换成自己的即可。

第 0 步:确认内置 MathJax 的版本

动态字体文件的版本必须与 Typora 内置的 MathJax 版本一致。可以用 PowerShell 从 lib.asar 中粗略提取版本号:

$asar = "D:\ProgramFiles\Typora\resources\lib.asar"
$text = [System.IO.File]::ReadAllText($asar)
[regex]::Matches($text, '"4\.\d+\.\d+"') |
    ForEach-Object { $_.Value } |
    Sort-Object -Unique

例如输出 "4.1.0",就说明内置的是 MathJax 4.1.0。MathJax 的版本号通常就是输出中唯一一个 4.x.x。

本文环境为 Typora 1.14.15 + MathJax 4.1.0。若你的 Typora 版本不同,请把下文的版本号换成实际版本。

第 1 步:备份原文件

需要修改的两个文件是:

D:\ProgramFiles\Typora\resources\window.html
D:\ProgramFiles\Typora\resources\html\content.html

先把它们复制到别处备份:

$src = "D:\ProgramFiles\Typora\resources"
$bak = "D:\Typora-backup"
New-Item -ItemType Directory -Force -Path $bak | Out-Null
Copy-Item "$src\window.html" "$bak\window.html" -Force
Copy-Item "$src\html\content.html" "$bak\content.html" -Force

第 2 步:下载动态字体文件

需要三个文件(对应 \mathbb、\mathcal、\mathfrak):

  • double-struck.js
  • calligraphic.js
  • fraktur.js

注意版本号要与第 0 步一致。下面使用国内的 npmmirror 镜像(cdn.jsdelivr.net 在部分地区可能被重置):

$ver  = "4.1.0"
$base = "https://registry.npmmirror.com/@mathjax/mathjax-newcm-font/$ver/files/svg/dynamic"
$dir  = "D:\ProgramFiles\Typora\resources\MathJax4-fonts\mathjax-newcm-font\svg\dynamic"

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
New-Item -ItemType Directory -Force -Path $dir | Out-Null
foreach ($f in @("double-struck.js", "calligraphic.js", "fraktur.js")) {
    Invoke-WebRequest -Uri "$base/$f" -OutFile (Join-Path $dir $f)
}

如果该镜像也不可用,可以换用下面任一地址,文件内容完全相同:

https://fastly.jsdelivr.net/npm/@mathjax/mathjax-newcm-font@4.1.0/svg/dynamic/
https://unpkg.com/@mathjax/mathjax-newcm-font@4.1.0/svg/dynamic/
https://cdn.jsdelivr.net/npm/@mathjax/mathjax-newcm-font@4.1.0/svg/dynamic/

下载完成后可以核对文件大小,这三个文件分别约为 41 KB、40 KB、77 KB:

文件 大小(字节)
double-struck.js 41,826
calligraphic.js 39,877
fraktur.js 76,776

下载完成后目录结构应为:

D:\ProgramFiles\Typora\resources\MathJax4-fonts\
└─ mathjax-newcm-font\
   └─ svg\
      └─ dynamic\
         ├─ calligraphic.js
         ├─ double-struck.js
         └─ fraktur.js

注意:这里不要多套一层 @mathjax 目录。MathJax 的字体包名会被 loader 解析为 [mathjax-newcm],其对应的本地路径就是 [fonts]/mathjax-newcm-font。

第 3 步:修改 window.html

用任意文本编辑器打开:

D:\ProgramFiles\Typora\resources\window.html

找到 window.MathJax = { 下面的 loader.paths,在 xypic 后面增加一行 fonts:

loader: {
    paths: {
        xypic: '[mathjax]/extensions',
        fonts: '[mathjax]/../../MathJax4-fonts'
    },
    source: {
        '[tex]/xypic': '[xypic]/xypic.js'
    },
    load: ['[tex]/xypic']
},

再找到 startup,在 typeset: false 后面增加一行 loadAllFontFiles: true:

startup: {
    typeset: false,
    loadAllFontFiles: true,
    ready: function () {
        // ...原有内容保持不变...
    }
},

路径解释:[mathjax] 是主 bundle tex-svg-full.js 所在目录,即

...\resources\lib.asar\MathJax4

因此 [mathjax]/../../MathJax4-fonts 正好回到:

...\resources\MathJax4-fonts

第 4 步:修改 html/content.html

导出、预览等场景使用的是另一个宿主页面,同样需要修改,否则导出的 PDF / HTML 中 \mathbb 仍然会空白。

打开:

D:\ProgramFiles\Typora\resources\html\content.html

做与第 3 步完全相同的两处修改:

  1. 在 loader.paths 中加入 fonts: '[mathjax]/../../MathJax4-fonts';
  2. 在 startup 中加入 loadAllFontFiles: true。

第 5 步:重启 Typora 并验证

完全退出 Typora(包括托盘/后台进程),重新打开,新建一个文档并粘贴下面的内容:

行内公式:$\mathbb{R}$、$\mathcal{L}$、$\mathfrak{g}$

$$
\mathbb{E}[X] = \sum_{i=1}^{n} a_i, \qquad \boldsymbol{\mathbb{R}}
$$

正常情况下,行内公式应显示为空心体、花体和哥特体。把光标移出公式块后,下面那行公式也应正常渲染;\sum 与希腊字母等普通公式保持正常。

\Bbb{R}、\Reals、\mathbb{N} 等写法也会一并修复,因为它们最终都映射到 \mathbb。

四、为什么只放三个文件就够了

这三个文件分别覆盖了主 bundle 中缺失的三类字形:

文件 修复的命令
double-struck.js \mathbb、\Bbb
calligraphic.js \mathcal
fraktur.js \mathfrak

MathJax 4 还有三十多个动态字体文件(希腊字母扩展、西里尔、盲文、希伯来文等)。开启 loadAllFontFiles 后,启动阶段会尝试加载它们;由于本地没有对应文件,控制台会出现若干 Could not load script 警告。这些异常会被 MathJax 捕获,不会影响上述公式的渲染。

如果希望彻底消除这些警告,也可以把 svg/dynamic 下的文件全部下载到同一目录,代价是启动阶段会多加载约 10 MB 的字体文件。

五、注意事项

  1. Typora 升级会重置 resources 目录
    升级后上述字体文件和两处配置都可能被覆盖。若升级后发现 \mathbb 又空白了,按本文重新做一遍即可。

  2. 字体版本要与内置 MathJax 一致
    本文对应 MathJax 4.1.0。跨大版本混用字体文件可能出现字形错位或加载失败。

  3. 两个页面都要改
    只改 window.html 只能修复编辑时的预览;导出 PDF / HTML 依赖 html/content.html。

  4. 修改的是应用安装目录
    操作前请先备份原文件;如果介意修改应用资源,也可以等待 Typora 官方后续版本修复。

六、快速重做清单(升级后使用)

[ ] 确认 resources\lib.asar 中内置的 MathJax 版本
[ ] 重新放置 MathJax4-fonts\mathjax-newcm-font\svg\dynamic\{double-struck,caligraphic,fraktur}.js
[ ] window.html:loader.paths 加 fonts 行
[ ] window.html:startup 加 loadAllFontFiles: true
[ ] html\content.html:重复上面两项
[ ] 重启 Typora,用 \mathbb{R} / \mathcal{L} / \mathfrak{g} 验证

参考

posted on 2026-11-13 16:21  论语  阅读(2)  评论(0)    收藏  举报

导航