笔者在某个需求实现中使用了 opentype.js 这个库,现将一些使用过程记录在本篇文章中。
opentype.js 是一个 JavaScript 库,支持浏览器和 Node.js,可以解析字体文件,拿到字体信息,并提供一些渲染方法。
虽然名字叫做 opentype.js,但除了可以解析 OpenType,也可以解析 TrueType。
支持常见的字体类型,比如 WOFF, OTF, TTF,像是 AutoCAD 的 shx 就不支持了。
需要注意的是,woff2 字体是用 Brotli 压缩过的文件,需要额外用解压库做解压。 opentype.js 没有提供对应解压 Brotli 的能力,倒是提供了 Inflate 解压能力,所以可以解析 woff 字体。
opentype.js 解析字体
js
// 从 URL 下载字体
const response: HttpClientResponse = await makeHttpRequest(url);
if (!response.status || response.status !== 200) {
handleError(`HTTP error! status: ${response.status}`);
}
// 加载文件字体为二进制数据,然后使用 opentype.js 解析
const buffer = response.data as Buffer;
const arrayBuffer = buffer.buffer.slice(
buffer.byteOffset,
buffer.byteOffset + buffer.byteLength
);
const font = await opentype.parse(arrayBuffer);
这个 font 这个对象保存了很多属性,比如所有的 glyph(字形)、一些 table(表)、字体的信息(字体名、设计师等)等等。

获取字形(glyph)信息
字形(glyph)是一个用于在字体排印中表示一个或多个字符的视觉表征的术语。
js
const glyph = font.charToGlyph('A')
有了字形,我们就能拿到某个或者某段文本字符串渲染所需要的一些关键信息(width、height、ascender、descender):
js
/**
* 测量文本宽度
* @param text 文本
* @param fontUrl 字体URL
* @param fontSize 字体大小
* @returns 宽度、高度以及字体的上下边界信息
*/
async measureText(
text: string,
fontUrl: string,
fontSize: number
): Promise<FontMetrics> {
// 1. 加载字体文件(opentrue
const font = await this.loadFontFromUrl(fontUrl);
// 2. 计算缩放比例:将字体的原始单位(unitsPerEm)转换为实际像素大小
// font.unitsPerEm 通常是 1000 或 2048,表示字体设计时的基准网格
// fontSize 是你想要渲染的大小(比如 32px)
// 所以 scale = fontSize / unitsPerEm,用于把字体的"逻辑单位"转为"像素"
const scale = fontSize / font.unitsPerEm;
// 3. 将字符串转换为字形(glyph)数组
// 每个字符可能对应一个或多个 glyph(比如连字 "fi")
// glyphs 是字体中实际的图形对象,包含路径、宽度等信息
const glyphs = font.stringToGlyphs(text);
// 4. 计算文本总宽度
let width = 0;
glyphs.forEach((glyph, i) => {
// 如果不是第一个字符,加上前一个字符和当前字符之间的"字距调整"(kerning)
// kerning 是为了让某些字符组合(如 "A" 和 "V")看起来更美观,自动缩小间距
if (i > 0) {
width += font.getKerningValue(glyphs[i - 1], glyph);
}
// 加上当前字形的"前进宽度"(advanceWidth)
// 注意:这不是字形的绘制宽度,而是光标移动的距离(包含右侧空白)
width += glyph.advanceWidth;
});
// 5. 返回测量结果(全部乘以 scale 转为像素单位)
return {
// 文本总宽度(含 kerning)
width: width * scale,
// 文本总高度 = ascender(上部) - descender(下部)
// ascender 是基线以上部分(如 "b", "h" 的顶部)
// descender 是基线以下部分(如 "g", "y" 的底部)
height: (font.ascender - font.descender) * scale,
// 基线以上的高度(正数),可用于垂直对齐计算
ascender: font.ascender * scale,
// 基线以下的高度(通常是负数,但这里保留原值)
descender: font.descender * scale,
};
}

获取文字轮廓(path)
getPaths 计算得到一段字符串中每个 glyph 的轮廓数据。 注意:传入的y坐标确实表示的是基线坐标(baseline),而不是字符的顶部或底部。
js
const textPaths = font.getPaths(text, x, y, fontSize);
textPaths 是一个 path 数组。 字符串长度为 6,产生了 6 个 glyph(字形),所以一共有 6 个 path 对象。 形状的表达使用了经典的 SVG 的 Path 命令,对应着 command 属性。 TrueType 字体的曲线使用二阶贝塞尔曲线(对应 Q 命令);而 OpenType 支持三阶贝塞尔曲线(对应 C 命令)。

转成真正能用的path路径,需要调用OpenType.js 暴露的另一个方法:
js
textPaths.toPathData(2);

基于生成的 Path 路径与字形信息,我们便能实现文本在某种字体下的 SVG 绘制了,剩下的步骤待读者自行探索。