前言
为追求数学表达式更好的显示效果,本文通过剖析MathJax换行算法源码,对其实现方式进行参考学习
前置配置
- 当前自动换行功能在MathJax version 3尚未更新,本文选择v-2.7.9版本
- 直接clone仓库代码进行调试,按官网教程在引用unpacked进行代码调试时,AssistiveMML.js的handleMML函数读取MML.copyAttributes.id时,MML.copyAttributes为undefined报错
- 在仓库issue搜索较接近的方法是html上直接引用cdn代码,无效
- 将MML.copyAttributes.id、MML.copyAttributes.id = true;两行注释掉(本文采用方法,暂没出现异常问题)
- 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倍
2. penalty值计算
(1) 初始值设定
1. 换行宽度 > 扫描宽度时,为 (换行宽度 - 扫描宽度)/换行宽度 * 1000
2. 换行宽度 < 扫描宽度时,为阙值 - 3倍步骤(i)的值
3. 右括号(fence)增加设定的因子值(500)
4. after扫描方式或括号,增加设定的因子值(500)
5. 增加包裹层次(如括号) * 设定的因子值(500)
具体代码实现:
(2) 根据break类型修改penalty值
目前应用普通四则运算(1+2*3)、加括号四则运算(0+(1+2)*3+4)、分式,均走else分支
break断点记录数据结构
- 在函数中命名为info;
- 确认为break节点添加values属性记录详细信息
测试样例及显示如下:
项目应用
经调研,该算法并不满足需求。因此自定义方法实现:
规则设计
- 小数点两边不可换行
- -、+号为单目运算符是不可换行;为双目运算符时 左侧不可换行
- 左括号、左绝对值|符号的右侧不可换行
- 其他运算符左侧不可换行
伪代码算法实现:
const 不可换行集
const 左不可换行集
const 右不可换行集
_wrapUnbreakables($mathEl) {
遍历所有mo标签:
按"相关规则"将元素及元素的prev()、next()添加至相应的不可换行集中
遍历所有mn,mi标签:
按"相关规则"将元素及元素的prev()、next()添加至相应的不可换行集中
遍历不可换行集:
用<mrow></mrow>包裹集合元素,并添加noBreakClass
}