问题前瞻
工作中遇到要对接出版社,只能使用Microsoft Word 与Mathtype。 为什么要绕过Mathtype批量格式化功能? 首先就是微软office太卡,普通电脑运行压力大。其次Mathtype过于古早,处理逻辑较为呆板。Mathtype自带批量格式化处理公式的流程是先在word里面定位到公式,删除该公式,插入一个新的符合配置的公式。完成后继续重复此流程。并不会检查公式配置是否符合,也不支持多线程、多文档同时进行。从 MTEF 二进制修补到 OLE 预览重渲染:一条可验证的 DOCX 公式修复流水线
批量修改 Word 里的 MathType 公式,最容易产生一种误判:脚本已经把字号和间距写进公式,审计也显示配置正确,但重新打开文档,屏幕上的公式几乎没有变化。 这通常不是修补失败,而是只解决了问题的一半。 一枚传统 MathType 公式在 DOCX 中至少跨越三个彼此独立的层次:可编辑的 MTEF 数据、Word 用于显示的 WMF 预览,以及决定预览如何摆放的对象几何。修改任意一层,都不会自动保证另外两层同步。 因此,一个可靠的批处理方案不能止于“改到了某些字节”。它必须回答三个问题:- 公式内部的配置是否已经变成目标值?
- 除目标配置外,文档和 OLE 中的其他数据是否保持不变?
- Word 当前显示的预览及其尺寸,是否与修改后的公式一致?
mathtype-re-render 流水线,正是围绕这三个问题设计的。它处理可编辑的 Equation.DSMT4 对象,以 .eqp 为目标配置,完成二进制审计、保守修补、完整性证明、OLE 重渲染、几何同步和整页视觉检查。
在一份包含 5,758 个公式对象的真实文档上,这条流水线完成了全部预览替换和几何同步;5,326 份唯一公式数据中,5,208 份实际渲染、118 份命中缓存,串行渲染耗时 445.01 秒。更重要的是,每个阶段都留下了可以复核的证据。
先建立正确的心智模型
.docx 本质上是一个 ZIP 包。对于传统 MathType OLE 公式,相关数据分散在多个部件中:
这就是整个方案最重要的结论:
MTEF 已更新,不等于 Word 显示已更新;预览已替换,也不等于页面几何正确。只有三个层次分别通过验收,才能说公式真正“改好了”。
问题为什么比预想中更难
最初的需求很直接:把文档中不符合目标.eqp 的 MathType 公式批量规范化。
在一个较小样本中,修补器成功把 3 个偏离目标的公式改为合规;每个对象只需修改 1 个字节,复审也从 3 个偏差降为 0。可是在 Word 中查看时,公式仍然保持旧外观。
这个现象暴露了两个独立问题:
.eqp应用到 MTEF,解决的是公式的可编辑配置;- Word 继续显示旧 WMF,说明缓存预览没有刷新。
整体方案:七道门,而不是一个脚本
完整流程如下:1. 预检:先知道文档里有什么
.eqp,要求得到恰好 38 个数值:[Sizes] 中 8 项,[Spacing] 中 30 项。随后按 OOXML 关系枚举所有 Equation.DSMT4 对象,并逐个读取 OLE 中的 Equation Native 流。
这里有一个容易遗漏的范围问题:公式不只存在于 word/document.xml。页眉、页脚、脚注、尾注等 Word XML 部件同样可能包含 OLE 对象,因此枚举必须覆盖所有相关 owner part,而不是只扫描正文。
预检报告至少要说明:
- 公式对象总数;
- 对应的 owner part、关系 ID 和 OLE 部件;
- 哪些公式无法解析;
- 哪些数值或字体与目标 EQP 不一致。
2 表示“发现偏差”。在待修复文档中,这是预期结果,不应被误判为程序崩溃。
2. 修补 MTEF:只改能够证明的目标
Equation Native 中的偏好配置位于 EQN_PREFS 记录。38 个数值采用半字节编码:单位、十进制数字、小数点和结束符被压缩为 nibble 序列。第 8 项之后,还有用于分隔字号和间距的 1E。
困难不在“把 38 个值写进去”,而在于准确找到记录边界,并在长度发生变化时保持容器一致。
安全修补遵循以下规则:
- 先解析 OLE2 复合文件,只读取具名的
Equation Native流; - 将
12 00仅视为候选记录标记,不能见到这两个字节就修改; - 从候选位置完整解析 38 项数值和分隔符;
- 重新编码目标值并替换旧偏好块;
- 对新流做一次完整回读,确认 38 项全部等于目标 EQP;
- 同步 MTEF 内部长度字段;
- 如果流跨越 64 字节 mini-sector 边界,同步 OLE mini-FAT 链、目录项长度和根 mini-stream 的逻辑长度。
1E 前插入一个 0。解析器只接受已验证的两种形式:
3. 修补后证明:把“看起来能打开”升级为可审计结论
- 修补前后 ZIP 成员名和顺序一致;
- 公式对象、owner part、关系 ID 和 OLE 目标映射一致;
- 所有非目标 DOCX 部件逐字节相同;
- 每个 OLE 中除
Equation Native外的流逐字节相同; - 修改后的 MTEF 内部长度等于
stream length - 28; - 38 项配置和所需字体全部通过目标 EQP 审计;
- ZIP CRC 校验通过。
- 从源文档到 MTEF 修补结果,目标 OLE 的整体哈希发生变化是正常的,因为
Equation Native被有意修改; - 从修补结果到预览刷新结果,OLE 哈希必须保持不变,因为后半段只允许修改预览、关系和几何。
4. 映射公式和预览:关系 ID 才是事实来源
oleObject17.bin 对应 image17.wmf。Word 保存、复制或去重文档时可能重新编号,数字后缀没有可靠语义。
正确做法是沿 OOXML 关系查找:从公式所在 XML 部件取得关系 ID,再解析对应的 .rels,分别确定 OLE 部件和预览部件。映射清单同时记录容器哈希与 Equation Native 哈希;后者才是稳定的公式内容标识,因为 Word 可能重写 OLE 容器元数据而不改变公式本身。
5. 拆分共享预览:避免一张图覆盖多个公式
多个公式对象可能引用同一个 WMF。若它们的内容不同,直接替换这张共享图片会让多个位置显示同一个公式。 因此,当公式对象数大于唯一预览部件数时,必须先拆分共享关系:6. 用 32 位 OLE 服务重新生成 WMF
MathType 的Equation.DSMT4 OLE 服务端是 32 位组件。64 位 Python 不能直接加载它,因此流水线使用一个小型 x86 STA Worker 负责 OLE 调用。
每台机器首次使用或 Worker 源码变更后,先构建渲染器:
OleRun 返回 0x8007000E。加入 IOleObject.Close 后,同一个 Worker 可以连续处理公式;再配合默认每 25 次缓存未命中重启一次,MathType 服务端的内存占用得到可控释放。
缓存键由 Equation Native 内容哈希和渲染器版本哈希组成。这样,相同公式只需渲染一次;渲染器代码变化后,旧缓存也会自动失效。
7. 同步几何,并检查整份文档
新 WMF 的 placeable header 给出了边界框和 units-per-inch。由此可以换算出公式的物理宽高,再同步 Word 的对象尺寸:w:position,只调整大小,不擅自改变公式基线。
- 所有 WMF 均可解析;
- 每个公式都有合法的 VML 尺寸;
- VML 和
dxaOrig/dyaOrig与 WMF 物理尺寸一致; - 预览刷新阶段的 OLE 哈希保持不变;
- DOCX 的 ZIP CRC 通过。
实测结果:性能之外,更重要的是证据链
在 M19 全量文档上的记录如下:
这些数字需要按阶段解释:
- “预览阶段保持不变的 OLE 为 5,758”表示刷新 WMF 和几何时,没有再次改动已经修补好的 OLE;
- 它不表示源文档到最终文档的 OLE 完全没变;前面的 MTEF 修补正是有意修改
Equation Native; - 5,326 个唯一内容中有 118 个已有可信缓存,因此实际新渲染 5,208 个;
- 5,758 个对象仍全部获得了对应的预览替换和几何更新。
为什么没有选择 Word 宏
最初考虑过让 Word 打开整篇文档,再通过 MathType 宏批量套用偏好。这个方案的问题并非“宏不够优雅”,而是它把处理绑定在完整 Word UI、MathType 插件初始化和长时间进程状态上:- 5,758 个 OLE 对象逐个激活,启动和保存成本很高;
- 64 位 Word 与 32 位 MathType 组件之间存在额外兼容问题;
MathPage.wll、MTInitAPI和旧接口的中文路径支持都可能成为失败点;- 长时间运行需要处理 Word 与 MathType 的内存累积和中途保存。
- 最重要的是,经过测试,老板的顶配电脑渲染起来,也就1、2S/公式,普通的办公电脑不仅缓慢,内容占用累计过高极其容易闪退。
适用边界
这条流水线适用于:.docx文档;- 可编辑的
Equation.DSMT4OLE 公式; - 含 8 项
[Sizes]、30 项[Spacing]的受支持.eqp; - 已安装并注册 MathType 32 位 OLE 服务端的 Windows 环境。
- 静态 PNG、EMF 等图片公式;
- Office 原生 OMML 公式;
- 修改公式的数学内容;
- 未经对照验证的新字体记录或未知 MTEF 变体;
- 没有 MathType OLE 服务端的环境。
一键运行与可复现交付
完整流程已经封装为一个入口:--overwrite 覆盖已有输出。
工作目录会保留各阶段 JSON 报告,包括源文档审计、修补报告、修补完整性证明、映射与预览刷新报告。一次合格交付至少应报告:
- 使用的 EQP;
- 公式对象数和唯一公式数;
- 修改与未修改的 MTEF 数量;
- 共享预览拆分数量;
- 新渲染数和缓存命中数;
- 几何审计结果;
- 预览阶段 OLE 保持结果;
- 整页视觉 QA 状态。
结论
批量修复 MathType 公式的核心,不是找到一段神奇的替换代码,而是把一个模糊目标拆成三个可验收的问题:- MTEF 配置是否正确;
- WMF 预览是否来自修改后的公式;
- Word 几何是否与新预览一致。
项目目录:
C:\MXQwork\skills\mathtype-re-render
关键资料:SKILL.md、README.md、references/mtef-binary-patching.md