一、前因后果
1.1 目的
起因是女朋友想做小红薯的账号。她每天会先把小故事写好复制到手机上,然后按照一定的图片规格一张张裁剪,最后发布到平台上。
接着我就在想,有没有什么高效的文本转成图片并且能够自动分页的方案呢?查了很久都没找到合适的方案,最后还是决定自己写一个转换工具。
1.2 需求
总结了一下我的场景,发现需求如下:
- 能够内容转成指定尺寸的图片
- 能够设置字体、背景、样式
- 支持自动换行、自定义换行
- 支持分页
- 支持图片下载
根据输入文本,通过 Canvas 渲染
展示全部分页图片的内容,可以批量下载
1.3 思路
基于需求,最后决定采用 Canvas 的绘制方案。思路如下:
- 根据输入文本,完成分行、分页的计算
- 将分页数据绘制到 Canvas
- 批量将 Canvas 的内容导出图片进行下载
1.4 难点
做的时候发现几个核心的问题难点,分别是换行、分页的问题。
I. 换行问题
在 canvas 中进行文本绘制不同于 HTML 标签,文本是不能自动换行的。所以这个需要自己去计算什么时候换行,在哪个字符段该换行。
II. 分页问题
当内容超出当前页了,我们希望能够自动进行分页,这个就需要去计算行高 和页面内容区高度。
二、设计方案
下面介绍一下 Canvas 文本渲染器的设计方案。主要会围绕着文本计算、文本绘制、导出图片来讲解。
2.1 如何计算文本
计算文本主要做的事情就是,根据用户输入的文本和想要生成的图片、字体参数,来计算需要分多少页,每一页具体要展示多少行,每一行要展示多少内容。
I. 分词
要实现换行,就要知道一句话中从哪个分词开始是超出了当前行的最大宽度。这个分词可能是某个中文字符、某个英文单词、某串数字、某个其他字符等。
这里我们只讨论简单的中英文数字的场景
所以要做的第一步,就是将输入的文本拆解成一个个分词,然后去筛选掉空的字符。
核心代码如下:
II. 分行
现在已经将文本拆分成了足够细的分词。接下来要做的就是将每个分词不断地塞入每一行中。
你可以把每一行理解成一个固定宽度的容器,一但某个分词塞不进了,就得创建一个新的容器再把这个分词塞进去。
所这个遍历的过程,需要知道行的最大宽度以及当前分词的真实宽度。最大宽度的计算规则,根据用户设置的页面宽度减去左右边距的宽度,就是内容区的最大行宽度。
代码如下:
分词的真实宽度则是用 canvas 中的 measureText
来测量字符串的宽度。
接下来就是一个累加的过程。把分词加入当前行,判断是否超出最大宽度,如果超出就新起一行。
最后遍历完后就会得出所有的分行内容。
上面是分行大致的思路,但在实际的代码实现上,会随着分词类型、换行需求的增加变得更加复杂。例如:
- 英文单词前面需要追加空格
- 用户想自定义空行逻辑:匹配到句号自动换行并且空一行。
III. 分页
有了分行的数据,就可以进行分页了。其实逻辑差不多,只不过一个是横向,一个是纵向。把每个页当成一个固定高度的容器,不断的把行塞进去,塞不进就新起一个页容器。
这个遍历的过程需要知道页面内容区的最大高度以及每一行的行高。行高一般是用户设置的,所以只需关注页面内容区的最大高度的计算规则。
代码如下:
分页计算流程,遍历 lines 把分行不断塞入当前页。如果超出高度就放到新的页面。最后就会得出所有分页的数据。
2.2 如何绘制
有了分页数据以后,绘制主要做的事情就是根据用户设置的样式(页面边距、字体和背景)来渲染每一页具体的内容。
I. 指定图片尺寸
通过设置 Canvas 画布的大小即可。
II. 绘制背景
背景直接通过 fillRect
绘制即可
III. 绘制内容
绘制内容的时候,主要考虑两个点:
- 设置字体样式
- 根据边距、行高计算每行绘制的位置
2.3 如何导出图片
导出图片就是将 Canvas 上的内容转成 DataURL 然后下载成图片
三、最后
基于上面的思路,你不仅仅可以开发一个简单的文本渲染器,你甚至可以做一个复杂的编辑器哦~
最后我将这个工具封装成了一个 npm 库,直接导入这个库就可以完成文本到图片的一个转换了。
如果感兴趣,完整代码放置在 GitHub 了:github.com/zixingtangm...