公式渲染方案
我在写博客主题时,经常会列出来可能会使用哪些信息表现形式,几乎所有内容发布平台都支持的有:文字、图片、超链接。但是,我作为编程人员,代码也应当支持。然后,还有很多其他形式要考虑:音频、视频、公式等等,不仅仅要支持,还要考虑支持到什么程度,用户体验会更好些。大体上,信息可能会有如下表现形式:
- 文字
- 图片
- 表格
- 音频
- 视频
- 公式
- 代码
- 等等
另外,还有可编辑代码、可交互式动画、VR 等表现形式,乃至游戏也算一种。不过有些几乎所有场景都需要的,有些特定场景才需要。
公式作为知识的重要组成部分,很多场景也都需要,尤其数学和物理相关领域,比如百科网站、问答网站、在线教育网站、内容发布平台、论文、书籍等。而现在信息展现的媒介也是多种多样,有书籍、电脑、平板、手机等。一些与公式展示相关的指标有:网络传输速率、屏幕尺寸、像素密度、字体的支持等。
公式通常使用特定的标记语言来书写,比如 TeX。TeX 是数学家和计算机科学家 Donald Knuth 设计的一个排版系统,广泛用于数学、计算机科学等领域的公式排版。
公式的渲染要考虑下面问题:
- 屏幕尺寸:从手机到宽屏桌面显示器,也包括书籍。
- 屏幕像素密度:有时公式可能以栅格图片展示,要考虑到高像素密度屏幕,随着技术发展也会越来越高。
- 性能:我们可能会一次性渲染很多公式,也包括非常复杂的公式,我们期望能尽快高质量将渲染好的公式呈现给用户。
按照渲染的设备划分可有三种方式:客户端渲染、服务端渲染、客户端与服务端混合渲染。
客户端渲染
顾名思义,用户端渲染,有电脑、手机、平板等。应用类型主要是:原生应用与 Web 应用。当然,因为两类应用都支持图片,所以可以使用图片形式来展现公式。
无论是桌面端还是移动端原生应用,因为无相关经验,不太了解,客户端渲染公式方式。Github 上面可以搜到一些渲染公式的包,但是对于可靠性不太了解。
Web 应用主要使用 MathJax 与 KaTeX 两个包。
MathJax
- 高质量:精确到像素的排版
- 支持 LaTeX,MathML 等语言
- 可扩展
- 可靠性:无论是从开发者背景,还是使用者背景,都是经过多年检验的
- 可访问性
- 渲染速度慢:尤其遇到复杂的公式时
KaTeX
- 渲染速度极快:大约 10 倍于 MathJax
- 可靠性:可汗学院开发与使用
- 渲染质量:满足大部分需求
可以查看 compare the output of KaTeX with MathJax 两者的渲染速度与渲染效果。
另外,在实际的使用当中,结合使用两者效果更好,优先使用 KaTeX 来渲染,如果渲染失败就是用 MathJax 来渲染。
服务端渲染
即使结合使用了 KaTeX 和 MathJax,有些时候仍然不能满足要求,用户不希望等三秒以上才能渲染好公式。通常,公式的数量也是有限度的,按照一亿用户,每人贡献 100 个公式,那就是大约 100 亿条公式,可能还有一些重复的。没有必要用户每次查看时都重复渲染一遍,也没有必要在用户的设备上面各自渲染。
缩减计算时间的第一条规则就是空间换时间,也称之为缓存。
公式通常使用 TeX 标记语言来书写,输出结果通常有:HTML、MathML、图片,其中图片又分栅格图片(png、jpg、webp)和矢量图片(svg)。输出结果自然是计算的结果,然后可以使用分布式缓存(如CDN)来分发这些输出结果。
接口设计
创建公式对象
POST /equations
-
请求参数:
- input:可选,输入格式,比如 tex,mathml, asciimath,默认 tex。
- output:可选,输出格式,可选值:svg, png, mathml, html 等,可以多个组合,默认 svg。
-
请求内容:
- 公式标记语言。
- 返回值:公式对象 ID。
接受公式标记语言,比如 TeX、MathML、AsciiMath 等,分配一个唯一标识 ID,然后渲染公式为指定的一个活多个输出格式,公式输入与渲染结果都存储起来,可能还包括自动分发 CDN。
获取公式对象
GET /equations/:equationId
-
请求参数:
- output:输出格式。
- 返回值:公式渲染结果
渲染服务
可以使用 Node 来运行 MathJax 或者 KaTeX,并结合 Puppeteer 来转成图片,当然这种方式性能不高,不过基本需求可以满足。如果对于渲染服务性能要求很高,可以考虑自己写 TeX 渲染组件或者考虑其他语言的 TeX 渲染组件,这方面不太了解,只知道维基百科就是自己写的 TeX 渲染组件。
性能
- 使用 CDN,可以多域名来突破浏览器同时请求数最大上限。
- 图片延迟加载:即公式图片将要进入可视区域再加载。
栅格图片与矢量图片
因为屏幕的像素密度有高有低,而栅格图片放大与缩小通常采用插值算法,这样会造成模糊,可以针对不同像素密度生成不同的图片来解决。如果使用矢量图片(比如 SVG)就可以无限缩放。
混合渲染
即时我们采用了服务端渲染,并且做了一些性能优化,如果一个页面有很多公式,可能几十上百个,有简单的,也有复杂的。如果每个简单的公式都发送一个单独的请求,对于网络环境较差的客户端可能会加载慢,降低用户体验。
可以将简单的公式与复杂的公式区分开:简单的公式使用客户端渲染,复杂的公式使用服务端渲染。简单与复杂划分的界限可能是由公式标记语言字节数传输时间
加上公式客户端渲染时间
与公式单独一个请求传输时间
比较来决定。
可搜索性
常见的搜索功能都是输入一些关键词,根据关键词匹配,也有搜索引擎支持图片搜索。简单的文本输入很难满足公式,而如果让用户使用 TeX 之类标记语言输入公式又才繁琐,可以通过手写公式,然后以图片形式传给搜索服务,可能会经过手写识别、公式识别等一系列解析,然后解析成某种格式,比如 TeX,然后使用 TeX 标记语言来匹配,至于匹配算法也会与常见的字符串匹配不同吧。
公式渲染不仅仅输出图片,也要输出其他标记语言,比如 MathML,然后便于搜索爬虫抓取,然后用于识别匹配。
目前一些国内拍题搜答案的移动应用比较类似,上传题目图片搜结果,这方面特别依赖人工智能相关技术,