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

相关推荐
weixin_3823952314 分钟前
为小工厂量身打造:本地部署的物料管理系统带缺料计算
前端·制造
__zRainy__20 分钟前
解决pnpm v10+不自动构建
前端·pnpm·工程化
猫猫不是喵喵.1 小时前
Vue3 Props 属性
前端·javascript·vue.js
醉城夜风~2 小时前
CSS元素显示模式(display)
前端·css
AI大模型-小华3 小时前
Codex 三方充值快速入门指南
java·前端·数据库·chatgpt·ai编程·codex·chatgpt pro
做前端的娜娜子5 小时前
同一链接实现 PC Web 与移动 H5 自适应
前端·掘金·金石计划
小帅不太帅5 小时前
架构没变、规模没变,DeepSeek V4 Flash 正式版凭什么暴涨 47 分?
前端·aigc·deepseek
jarvisuni5 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
卷福同学7 小时前
AI编程出海第二步:验证关键词能否做站
前端·人工智能·后端