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 绘制了,剩下的步骤待读者自行探索。

相关推荐
90后的晨仔3 小时前
Vue 3 组合式函数(Composables)全面解析:从原理到实战
前端·vue.js
今天头发还在吗3 小时前
【React】动态SVG连接线实现:图片与按钮的可视化映射
前端·javascript·react.js·typescript·前端框架
小刘不知道叫啥3 小时前
React 源码揭秘 | suspense 和 unwind流程
前端·javascript·react.js
szial3 小时前
为什么 React 推荐 “不可变更新”:深入理解 React 的核心设计理念
前端·react.js·前端框架
mapbar_front4 小时前
面试是一门学问
前端·面试
90后的晨仔4 小时前
Vue 3 中 Provide / Inject 在异步时不起作用原因分析(二)?
前端·vue.js
90后的晨仔4 小时前
Vue 3 中 Provide / Inject 在异步时不起作用原因分析(一)?
前端·vue.js
90后的晨仔4 小时前
Vue 异步组件(defineAsyncComponent)全指南:写给新手的小白实战笔记
前端·vue.js
木易 士心5 小时前
Vue 与 React 深度对比:底层原理、开发体验与实际性能
前端·javascript·vue.js