Canvas 文本排版新思路初探——字体解析

Canvas 文本排版

前言

Canvas 原生的文字排版功能很薄弱,甚至做不到自动换行。相较之下 HTML 的排版功能就很强大了。今天我们就来探索一下 Canvas 排版的新思路。

旧方法

接触 Canvas 排版的开发者应该都看过张鑫旭的《canvas文本绘制自动换行、字间距、竖排等实现》这篇文章吧。其原理使用了 CanvasRenderingContext2D.measureText 这个 API。文本换行循环调用此方法实现,不断测量文本宽度直到抵达最大宽度,截断换行。

相信大家也看出这个方法的问题了,不断循环渲染测量会很耗性能。那有没有不用循环测量的方案,甚至不想用 CanvasRenderingContext2D.measureText 呢。其实有的,下面就是本文的重点了。

新思路

文本排版的问题无非就是文本定位的问题,只需要知道文字宽高与文字之间的距离就能排版。这些信息可以通过解析字体文件得到,这里使用 opentype.js 来解析。

首先科普几个字体基础的概念:

1. Units Per Em

Units Per Em 指字体设计时的网格大小,一般多为 1000 或 1024。这里我们只需要记住这个数字,后续与字号运算用得到。

2. Ascender 与 Descender

Ascender 指基线(Baseline)到升部(Ascent)的高度,Descender 指基线到降部(Descent)的高度(为负值)。Canvas 的 textBaseline 默认值为 alphabetic,指的就是字符的基线。

3. Advance width

Advance width 指字符宽度,包括设计上的留白。

4. Kerning

Kerning 指字母组合的间距调整。这个间距调整是很重要的,用于修正视觉平衡。只要是非等宽字体,都会存在 Kerning。

具体实现

这里我使用开源的得意黑字体做演示,使用 opentype.js 解析后的数据如下图:

可以看到 unitsPerEm 为 1000, ascender 为 970,descender 为 -230。

下面我们定义一些常规参数:

首先定义测试常量: fontSize = 48,lineHeight = 1.5。

字体比例:fontRatio = fontSize / unitsPerEm = 0.048。

字体高度:fontHeight = (ascender - descender) * fontRatio = 57.6。

升部高度:ascenderHeight = ascender * fontRatio = 46.56。

行间距:lineGap = fontSize * (lineHeight - 1) = 24。

好了,有了这些参数就可以布局了,先来布局一个字符串 Hello World! 试试。

Hello 以单个字符循环,计算出每个字符的 advanceWidth 与前一个字符的 kerning。计算方式请参考 opentype.js 文档,得出结果分别是:

字符 advanceWidth kerning
H 24.624 0
e 21.552 -0.48
l 9.504 -0.48
l 9.504 -0.48
o 22.08 -0.48
9.12 0
W 33.744 0
o 22.08 -1.44
r 14.016 -0.48
l 9.504 -0.48
d 21.888 -0.48
! 15.456 0

字符占位宽度 = advanceWidth + kerning,按顺序排列就得到排版完的文本了,这里给每个字符加上边框方便观察。可以明显看到 Wo 的距离被 kerning 缩短了。

遇到长文本换行也变得容易了,判断当前字符占位宽度末尾的横坐标是否大于当前容器,另起一行,效果如下:

优化空间

本案例没有做分词行为,可以看到换行时,单词被截断了。这个也容易实现,CJK 字符可以任意断行,其余字符则需要空格等分词符断行。以分词后的整体宽度为一个单位进行排版。这里推荐一个 html2canvas 使用分词库 css-line-break

本次没有考虑复杂情况,如文字方向、中东语言、组合字符等。由于只是初步探索,这些复杂的问题以后再来解决吧。希望给大家提供一种新的排版思路。

项目地址

项目源码:github.com/kooriookami...

在线演示:kooriookami.github.io/canvas-text...

相关推荐
冻感糕人~4 小时前
【珍藏必备】ReAct框架实战指南:从零开始构建AI智能体,让大模型学会思考与行动
java·前端·人工智能·react.js·大模型·就业·大模型学习
程序员agions4 小时前
2026年,“配置工程师“终于死绝了
前端·程序人生
alice--小文子4 小时前
cursor-mcp工具使用
java·服务器·前端
晚霞的不甘4 小时前
揭秘 CANN 内存管理:如何让大模型在小设备上“轻装上阵”?
前端·数据库·经验分享·flutter·3d
小迷糊的学习记录4 小时前
0.1 + 0.2 不等于 0.3
前端·javascript·面试
梦帮科技5 小时前
Node.js配置生成器CLI工具开发实战
前端·人工智能·windows·前端框架·node.js·json
VT.馒头5 小时前
【力扣】2695. 包装数组
前端·javascript·算法·leetcode·职场和发展·typescript
css趣多多5 小时前
一个UI内置组件el-scrollbar
前端·javascript·vue.js
C澒6 小时前
前端整洁架构(Clean Architecture)实战解析:从理论到 Todo 项目落地
前端·架构·系统架构·前端框架
C澒6 小时前
Remesh 框架详解:基于 CQRS 的前端领域驱动设计方案
前端·架构·前端框架·状态模式