修复 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.jscalligraphic.jsfraktur.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 步完全相同的两处修改:
- 在
loader.paths中加入fonts: '[mathjax]/../../MathJax4-fonts'; - 在
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 的字体文件。
五、注意事项
-
Typora 升级会重置
resources目录
升级后上述字体文件和两处配置都可能被覆盖。若升级后发现\mathbb又空白了,按本文重新做一遍即可。 -
字体版本要与内置 MathJax 一致
本文对应 MathJax 4.1.0。跨大版本混用字体文件可能出现字形错位或加载失败。 -
两个页面都要改
只改window.html只能修复编辑时的预览;导出 PDF / HTML 依赖html/content.html。 -
修改的是应用安装目录
操作前请先备份原文件;如果介意修改应用资源,也可以等待 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} 验证
参考
- 修复 Typora 无法渲染 \mathbb 和 \mathcal 的问题:本文在该文思路的基础上,补充了
\mathfrak对应的字体、版本核对方法以及导出页面content.html的处理。
出处:https://www.cnblogs.com/lunyu/p/23656817
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任
浙公网安备 33010602011771号