opentype.js 使用与文字渲染

笔者在某个需求实现中使用了 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 绘制了,剩下的步骤待读者自行探索。

相关推荐
灵感__idea4 小时前
Hello 算法:贪心的世界
前端·javascript·算法
GreenTea6 小时前
一文搞懂Harness Engineering与Meta-Harness
前端·人工智能·后端
killerbasd7 小时前
牧苏苏传 我不装了 4/7
前端·javascript·vue.js
吴声子夜歌7 小时前
ES6——二进制数组详解
前端·ecmascript·es6
码事漫谈8 小时前
手把手带你部署本地模型,让你Token自由(小白专属)
前端·后端
ZC跨境爬虫8 小时前
【爬虫实战对比】Requests vs Scrapy 笔趣阁小说爬虫,从单线程到高效并发的全方位升级
前端·爬虫·scrapy·html
爱上好庆祝8 小时前
svg图片
前端·css·学习·html·css3
王夏奇8 小时前
python中的__all__ 具体用法
java·前端·python
大家的林语冰9 小时前
《前端周刊》尤大开源 Vite+ 全家桶,前端工业革命启动;尤大爆料 Void 云服务新产品,Vite 进军全栈开发;ECMA 源码映射规范......
前端·javascript·vue.js
jiayong239 小时前
第 8 课:开始引入组合式函数
前端·javascript·学习