MathJax 3.x 实现 LaTex 公式转图片

date
Mar 1, 2022
slug
mathjax-latex-to-image
status
Published
tags
Frontend
JavaScript
summary
简要实现过程介绍。
type
Post

前言

在制作这个动作时查阅 MathJax 3.x 官方文档发现默认没有提供图片输出,需要自己实现,在这里记录一下流程。

原理

首先从最根本的需求进行考虑,也就是怎么把公式以图片的格式渲染出来。MathJax 提供了 SVG 渲染,而 SVG 可以用 Base64 格式显示。Canvas 当中有一个 HTMLCanvasElement.toDataURL() 方法,该方法可以将 Canvas 的内容以图片 data URI 的形式呈现。所以我们可以先将 SVG 转换为 Base64,将其放到一个 Canvas 画布上,再使用 toDataURI() 方法转换为图片 data URI,最后渲染到页面上。
现在我们解决了公式 SVG 转换到公式图片的问题,接下来就需要考虑怎么把这个操作与 MathJax 的渲染流程结合起来,以达到最佳性能。官方文档提及可以使用 MathJax.typesetPromise() 实现一些公式渲染完成之后的操作, MathJax.typesetPromise() 返回一个 Promise,在公式转换完成并更新文档 DOM 后的状态才为 onFulfilled 。虽然这也能实现我们的需求,但如果使用这个 Promise 的话,就需要在 DOM 更新完成后再操作,这时我们再把 DOM 当中的 SVG 转换为图片,就相当于整个过程对 DOM 整体进行了两次操作。具体表现就是在公式 SVG 渲染完成之后,已经展现给了用户,这时我们再对 SVG 的 DOM 进行操作,就将这个过程暴露给了用户,影响体验。
万幸的是,MathJax 在 options 当中有一项 renderActions 可以让我们自定义 MathJax 的各个渲染阶段。所以我们可以配置 renderActions 实现我们的需求。下文主要介绍 renderActions 的配置过程。

实现步骤

MathJax 的整个渲染分为 find、compile、metrics、typeset、update 、reset 这几个流程。需要我们关注的是:
  • find: 使用正则查找 options 当中 elements(默认为 body)下的 LaTex 公式,记录其位置及内容、分隔符等;
  • typeset: 使用用户配置的 output 格式对公式进行排版,这里我们用的是 SVG;
  • update: 更新文档 DOM。
我们需要自定义的是 update 流程。
自定义 update 就需要知道 MathJax 的默认实现是什么,想要知道默认实现是什么就得看源码。
在 MathJax 源码当中搜索 renderActions,可以知道 renderActions 被定义在 MathDocument 接口当中,并由 AbstractMathDocument 类实现了 MathDocument 接口。
可以看到在这个地方定义了 renderActions 的默认值。默认为调用一个名为 updateDocument 的函数:
再在源码当中搜索有关 updateDocument 的相关信息,可以看到 AbstractMathDocument 有相关实现。其中对 this.math.reversed() 进行遍历,并调用其中的每个元素的 updateDocument 方法。
找到 AbstractMathDocument 类的 math 成员的定义位置
发现 math 是一个以 MathItem 类为成员的链表,故我们转到 MathItem 的相关代码。
MathItem 中定义了 updateDoucment 方法,参数是 MathDocument。至此,可以知道,update 过程就是对 MathDocument 当中的存储 MathItem 的链表进行遍历,调用 MathItem 的 updateDocument 传入当前的 MathDocument,以实现文档更新。
继续查找 updateDocument 的相关信息,在 HTMLMathItem 当中找到了 MathItem 的同名方法的实现,并且根据源码注释可知,这就是我们找的更新 DOM 相关的代码。
经过以上分析,大概需要配置什么了,下面直接放上最后的效果。

最终效果

 

© Fronz 2021 - 2022