MathJax自动换行算法

784 阅读1分钟

前言

为追求数学表达式更好的显示效果,本文通过剖析MathJax换行算法源码,对其实现方式进行参考学习

前置配置

  1. 当前自动换行功能在MathJax version 3尚未更新,本文选择v-2.7.9版本
  2. 直接clone仓库代码进行调试,按官网教程在引用unpacked进行代码调试时,AssistiveMML.js的handleMML函数读取MML.copyAttributes.id时,MML.copyAttributes为undefined报错
    • 在仓库issue搜索较接近的方法是html上直接引用cdn代码,无效
    • 将MML.copyAttributes.id、MML.copyAttributes.id = true;两行注释掉(本文采用方法,暂没出现异常问题)
  3. MathJax输出格式包括:CommonHTML、HTML-CSS、NativeMML、PlanSource、PreviewHTML、SVG。本文以HTML-CSS格式(即应用html5的span+css)进行说明。

MathJax自动换行算法主要思路:

设计相关规则,计算表达式中各个元素与容器最大长度的距离作为penalty值,以最小的惩罚值选择换行位置, 主要伪代码如下:

设置扫描起始位置index = 0
while(存在betterBreak && (一行累计扫描长度 >= 行宽 || 换行)){
    // 其中遍历至<mrow>包裹元素时,递归遍历
	while(未遍历完表达式所有元素 && (一行扫描长度 < 最大行宽 || 扫描元素长度为 0)) {
            计算第i个元素的panalty值
            保存最小panalty值的相关信息(如index)
	}
    panalty值最小的元素位置断行
    计算分行位置信息
}

实际实现中,判断是否存在betterBreak 的过程即为计算panalty过程,两者合并实现。

具体规则设计

1. 不允许换行的位置
1. 扫描元素为数字时,即数字前不换行
2. 扫描为linebreakContainer时
3. 伪代码中“最大行宽”为容器行宽的1.33倍

企业微信截图_16139625095564.png

2. penalty值计算

(1) 初始值设定

1. 换行宽度 > 扫描宽度时,为 (换行宽度 - 扫描宽度)/换行宽度 * 1000
2. 换行宽度 < 扫描宽度时,为阙值 - 3倍步骤(i)的值
3. 右括号(fence)增加设定的因子值(500)
4. after扫描方式或括号,增加设定的因子值(500)
5. 增加包裹层次(如括号) * 设定的因子值(500)

具体代码实现:
企业微信截图_16139615522107.png

(2) 根据break类型修改penalty值 企业微信截图_16139618596475.png
目前应用普通四则运算(1+2*3)、加括号四则运算(0+(1+2)*3+4)、分式,均走else分支

break断点记录数据结构

  • 在函数中命名为info;
  • 确认为break节点添加values属性记录详细信息 企业微信截图_16139777638485.png

测试样例及显示如下:

image.png

项目应用

经调研,该算法并不满足需求。因此自定义方法实现:

规则设计
  1. 小数点两边不可换行
  2. -、+号为单目运算符是不可换行;为双目运算符时 左侧不可换行
  3. 左括号、左绝对值|符号的右侧不可换行
  4. 其他运算符左侧不可换行

伪代码算法实现:

const 不可换行集
const 左不可换行集
const 右不可换行集

_wrapUnbreakables($mathEl) {
	遍历所有mo标签:
		按"相关规则"将元素及元素的prev()、next()添加至相应的不可换行集中
	遍历所有mn,mi标签:
		按"相关规则"将元素及元素的prev()、next()添加至相应的不可换行集中
	遍历不可换行集:
		用<mrow></mrow>包裹集合元素,并添加noBreakClass
}