文本压缩 Leafer Compress Text
前言
之前一直用 html2canvas 导出页面上的元素,在大多数情况下还是好用的。有时候也会存在文字偏移,不能完美还原的问题。其次不同浏览器渲染的 dom 元素略有区别,比如 FireFox 与 Chrome 渲染文字差异还是挺大的。FireFox 中 white-space: pre-wrap 与 text-align: justify 冲突等。
于是我想直接用 Canvas 来实现一个文本压缩工具,原生 Canvas 太繁琐,之前有尝试过 PixiJS。当时版本文字不支持透明度,国外的项目没有中文文档,用起来比较繁琐,还有些影响实现的 bug,写到一半放弃了。
前几天看到国人开源的 Leafer UI 这个库,上手简单,我就试试用它来实现我的功能,虽然目前 beta 版本还有挺多 bug,但是 issue 反馈及时,甚至可以直接在群里问开发者问题,这点很赞。
文本压缩用来做什么
先上一张图,可以看到文字在 x 轴被压缩了。最直观的作用就是压缩文本不超出容器范围。
如果只是单行文本,计算压缩率是很简单的,只需要用 容器宽度 / 文本宽度,当多行文本就不能如此简单粗暴了。当情况更复杂一些,我个人有需求,需要给文字注音,类似 html 中 ruby 与 rt 的效果。还要支持换行、文字对齐等一些细节定制。如下图展示:
流程图
我把主要功能的流程图贴出来,并解释具体实现。
文本解析
由于传进来的是一个 string 类型的文本,所以需要对文本、注音进行分离,统一采用 [文本(注音)] 的形式解析,匹配正则为 \[.*?\(.*?\)]。
接下来需要确定每个文本能否断行,比如一个单词,汉字加标点,需要视为一个整体,不能断开。还有一些复杂的规则,如某些标点不能出现在行首。如果要考虑所有字符的情况,文本断行功能已经可以作为一个单独的库了。目前我仅仅简单考虑了常用字符情况,具体实现代码可以参考 split-break-word.js。 现在改用 niklasvh 大佬的 css-line-break 库来做文本断词。
创建 ruby
创建除了注音的文本,此处记录每个断词的原始宽高信息,记为 originalWidth 与 originalHeight。缓存下来,避免使用 textDrawData.bounds 重复获取宽高,影响性能。
压缩 ruby
多行压缩没法直接算,需要循环去“试”压缩率了,最简单的方法是将 textScale 从 1 开始,依次递减比如 0.01,等文本刚好在不超出容器停止循环。但是这样效率有点低,于是我采用了二分法快速收敛优化性能。
文本换行也需手动计算,当当前行容纳不下字符宽度时,即换行。计算完换行后,需要定位每一个字符的位置,此步骤与换行依次来回执行,每个字符先计算换行,再定位。
对齐 ruby
文本对齐有 4 种,分别是:左对齐、居中对齐、右对齐、两端对齐。对齐原理很简单,获取每一行文本的剩余宽度空间,左对齐不变;居中对齐则平均分配给行两侧;右对齐只分配给行左侧;两端对齐平均分配给每个断词文本。
创建 rt
与创建 ruby 流程一致,记录原始宽高信息。
压缩 rt
先将 rt 的 x 坐标等于 ruby 的 x 坐标,最大宽度不超过 ruby 的宽度。然后再去细节定位,保持居中于 ruby 之上。当 rt 压缩率小于 0.6 时,给 ruby 左右加上合适的 padding 撑大。由于此举改变了 ruby 的位置,所以等待 rt 定位完成后需要重新压缩定位一遍 ruby。
性能测试
这是编辑 100 多个字符的时间,控制在几毫秒内。
这是编辑 1000 多个字符,且压缩率达到 0.12 (低于 0.5 的压缩率已经不容易看清文字了),大概需要 20 ms 多。
总体上性能还是可以的,可以在项目中加入防抖或节流来进一步优化。
总结
这是一个高度定制的工具,不适合所有人遇到的场景。我把思路与优化方式分享出来,有需要可以定制开发自己的功能。再次感谢一下 Leafer JS 和他的开发者,祝愿越来越好。