从零基于 Canvas 实现一个文本渲染器

一、前因后果

1.1 目的

起因是女朋友想做小红薯的账号。她每天会先把小故事写好复制到手机上,然后按照一定的图片规格一张张裁剪,最后发布到平台上。

接着我就在想,有没有什么高效的文本转成图片并且能够自动分页的方案呢?查了很久都没找到合适的方案,最后还是决定自己写一个转换工具。

1.2 需求

总结了一下我的场景,发现需求如下:

  1. 能够内容转成指定尺寸的图片
  2. 能够设置字体、背景、样式
  3. 支持自动换行、自定义换行
  4. 支持分页
  5. 支持图片下载

根据输入文本,通过 Canvas 渲染

展示全部分页图片的内容,可以批量下载

1.3 思路

基于需求,最后决定采用 Canvas 的绘制方案。思路如下:

  1. 根据输入文本,完成分行、分页的计算
  2. 将分页数据绘制到 Canvas
  3. 批量将 Canvas 的内容导出图片进行下载

1.4 难点

做的时候发现几个核心的问题难点,分别是换行、分页的问题。

I. 换行问题

在 canvas 中进行文本绘制不同于 HTML 标签,文本是不能自动换行的。所以这个需要自己去计算什么时候换行,在哪个字符段该换行。

II. 分页问题

当内容超出当前页了,我们希望能够自动进行分页,这个就需要去计算行高页面内容区高度

二、设计方案

下面介绍一下 Canvas 文本渲染器的设计方案。主要会围绕着文本计算、文本绘制、导出图片来讲解。

2.1 如何计算文本

计算文本主要做的事情就是,根据用户输入的文本和想要生成的图片、字体参数,来计算需要分多少页,每一页具体要展示多少行,每一行要展示多少内容。

I. 分词

要实现换行,就要知道一句话中从哪个分词开始是超出了当前行的最大宽度。这个分词可能是某个中文字符、某个英文单词、某串数字、某个其他字符等。

这里我们只讨论简单的中英文数字的场景

所以要做的第一步,就是将输入的文本拆解成一个个分词,然后去筛选掉空的字符。

核心代码如下:

II. 分行

现在已经将文本拆分成了足够细的分词。接下来要做的就是将每个分词不断地塞入每一行中。

你可以把每一行理解成一个固定宽度的容器,一但某个分词塞不进了,就得创建一个新的容器再把这个分词塞进去。

所这个遍历的过程,需要知道行的最大宽度以及当前分词的真实宽度。最大宽度的计算规则,根据用户设置的页面宽度减去左右边距的宽度,就是内容区的最大行宽度。

代码如下:

分词的真实宽度则是用 canvas 中的 measureText 来测量字符串的宽度。

接下来就是一个累加的过程。把分词加入当前行,判断是否超出最大宽度,如果超出就新起一行。

最后遍历完后就会得出所有的分行内容。

上面是分行大致的思路,但在实际的代码实现上,会随着分词类型、换行需求的增加变得更加复杂。例如:

  1. 英文单词前面需要追加空格
  1. 用户想自定义空行逻辑:匹配到句号自动换行并且空一行。

III. 分页

有了分行的数据,就可以进行分页了。其实逻辑差不多,只不过一个是横向,一个是纵向。把每个页当成一个固定高度的容器,不断的把行塞进去,塞不进就新起一个页容器。

这个遍历的过程需要知道页面内容区的最大高度以及每一行的行高。行高一般是用户设置的,所以只需关注页面内容区的最大高度的计算规则。

代码如下:

分页计算流程,遍历 lines 把分行不断塞入当前页。如果超出高度就放到新的页面。最后就会得出所有分页的数据。

2.2 如何绘制

有了分页数据以后,绘制主要做的事情就是根据用户设置的样式(页面边距、字体和背景)来渲染每一页具体的内容。

I. 指定图片尺寸

通过设置 Canvas 画布的大小即可。

II. 绘制背景

背景直接通过 fillRect 绘制即可

III. 绘制内容

绘制内容的时候,主要考虑两个点:

  1. 设置字体样式
  2. 根据边距、行高计算每行绘制的位置

2.3 如何导出图片

导出图片就是将 Canvas 上的内容转成 DataURL 然后下载成图片

三、最后

基于上面的思路,你不仅仅可以开发一个简单的文本渲染器,你甚至可以做一个复杂的编辑器哦~

最后我将这个工具封装成了一个 npm 库,直接导入这个库就可以完成文本到图片的一个转换了。

如果感兴趣,完整代码放置在 GitHub 了:github.com/zixingtangm...

相关推荐
最新资讯动态1 小时前
HDC 2026 | 对话鲸鸿动能:存量时代,品牌如何夺回营销“主动权”?
前端
最新资讯动态1 小时前
游戏出海,从产品走向体系
前端
最新资讯动态1 小时前
20人团队跑出百万DAU、大厂也来抢量:谁在鸿蒙生态跑出加速度
前端
最新资讯动态1 小时前
千万开发者背后,鸿蒙商业化的B面
前端
爱勇宝3 小时前
AI 时代:智商决定起点,情商决定走多远
前端·ai编程
kyriewen3 小时前
用了半年 Claude Code 后,我尝试关掉它写了一周代码——结果比想象中严重
前端·javascript·ai编程
IT_陈寒4 小时前
Vite的静态资源打包让我熬夜到三点,这坑千万别跳
前端·人工智能·后端
徐小夕5 小时前
万字拆解 JitWord:企业级实时协同文档底层架构 + 大模型 AI 融合完整实践
前端·vue.js·github
一份执念5 小时前
uni-app 小程序分包限制处理与主包体积优化实战
前端·微信小程序
MariaH5 小时前
初识MySQL
前端