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。
本次没有考虑复杂情况,如文字方向、中东语言、组合字符等。由于只是初步探索,这些复杂的问题以后再来解决吧。希望给大家提供一种新的排版思路。
项目地址
在线演示:kooriookami.github.io/canvas-text…
原文链接:https://juejin.cn/post/7348991061702459432 作者:kooriookami