改进了字体回退

Katie Hempenius
Katie Hempenius

摘要

本文将深入探讨字体回退以及 size-adjustascent-overridedescent-overrideline-gap-override API。借助这些 API,您可以使用本地字体创建与 Web 字体的尺寸大致或完全匹配的后备字体。这可以减少或消除由字体切换导致的布局偏移。

如果您不想阅读本文,可以使用以下工具立即开始使用这些 API:

框架工具

  • @next/font:从 Next 13 开始,next/font 会自动使用字体指标替换值和 size-adjust 来提供匹配的字体回退。
  • @nuxtjs/fontaine:从 Nuxt 3 开始,您可以使用 nuxt/fontaine 自动生成匹配的字体回退,并将其插入 Nuxt 应用使用的样式表。

非框架工具:

  • Fontaine:Fontaine 是一个库,可自动生成和插入使用字体测量值替换项的字体后备选项。
  • 代码库包含由 Google Fonts 托管的所有字体的字体指标替换值。您可以将这些值复制并粘贴到样式表中。

背景

回退字体是在主字体尚未加载或缺少渲染网页内容所需的字形时使用的字体。例如,以下 CSS 指示应将 sans-serif 字体系列用作 "Roboto" 的字体回退。

font-family: "Roboto" , sans-serif;

后备字体可用于更快地呈现文本(即,通过使用 font-display: swap)。因此,网页内容更便于阅读且实用,但从以往来看,这是以布局不稳定的代价的:当将后备字体换成网页字体时,通常会发生布局偏移。不过,下面介绍的新 API 可以创建与其 Web 字体对应项占用相同空间的后备字体,从而减少或消除此问题。

改进了字体回退

有两种可能的方法可以生成“改进的”字体回退。更简单的方法仅使用字体指标替换项 API。更复杂(但更强大)的方法同时使用了字体指标替换项 API 和 size-adjust。本文介绍了这两种方法。

字体指标替换项的运作方式

简介

字体指标替换项提供了一种替换字体的上行、下行和行距的方法:

  • 上升测量的是字体的字形延伸到基线以上的最远距离。
  • 下降用于衡量字体的字形延伸到基准线下方的最远距离。
  • 行间距也称为“前导”,用于衡量连续文字行之间的距离。

一张图表,描绘了字体的上行、下行和行间距。

字体指标替换项可用于替换后备字体的上行高度、下行高度和行距,以匹配 Web 字体的上行高度、下行高度和行距。因此,网页字体和调整后的后备字体将始终具有相同的垂直尺寸。

字体测量值替换项在样式表中使用,如下所示:

body {
    font-family: Poppins, "fallback for poppins";
}

@font-face {
    font-family: "fallback for poppins";
    src: local("Times New Roman");
    ascent-override: 105%;
    descent-override: 35%;
    line-gap-override: 10%;
}

本文开头列出的工具可生成正确的字体指标替换值。不过,您也可以自行计算这些值。

计算字体指标替换值

以下等式可用于为给定 Web 字体生成字体指标替换项。字体指标替换项的值应写为百分比(例如 105%),而不是小数。

ascent-override = ascent/unitsPerEm
descent-override = descent/unitsPerEm
line-gap-override = line-gap/unitsPerEm

例如,以下是 Poppins 字体的字体指标替换项:

/*
Poppins font metrics:
ascent = 1050
descent = 350
line-gap = 100
UPM: 1000
*/

ascent-override: 105%;  /* = 1050/1000 */
descent-override: 35%;  /* = 350/1000 */
line-gap-override: 10%; /* = 100/1000 */

ascentdescentline-gapunitsPerEm 的值均来自 Web 字体的元数据。本文的下一部分将介绍如何获取这些值。

读取字体表

字体的元数据(具体而言,是其字体表)包含计算其字体指标替换项所需的所有信息。

FontForge 中“Font Information”对话框的屏幕截图。该对话框会显示“Typo Ascent”“Typo Descent”和“Typo Line Gap”等字体测量值。
使用 FontForge 查看字体元数据

以下是一些可用于读取字体的元数据的工具:

  • fontkit 是专为 Node.js 构建的字体引擎。此代码段展示了如何使用 Fontkit 来计算字体指标替换值。
  • Capsize 是一个字体大小和布局库。Capsize 提供了一个 API,用于获取有关各种字体指标的信息。
  • fontdrop.info 是一个网站,允许您通过浏览器查看字体表格和其他字体相关信息。
  • Font Forge 是一款热门的桌面字体编辑器。如需查看 ascentdescentline-gap:打开 Font Info 对话框,选择 OS/2 菜单,然后选择 Metrics 标签页。如需查看 UPM:打开 Font Info 对话框,然后选择 General 菜单。

了解字体表

您可能会注意到,“上升”等概念由多个指标引用,例如,有 hheaAscenttypoAscentwinAscent 指标。这是因为不同操作系统采用了不同的字体渲染方法:OSX 设备上的程序通常使用 hhea* 字体指标,而 Windows 设备上的程序通常使用 typo*(也称为 sTypo*)或 win* 字体指标。

系统会根据字体、浏览器和操作系统,使用 hheatypowin 指标来呈现字体。

Mac Windows
Chromium 使用“hhea”表中的指标。 如果已设置“USE_TYPO_METRICS”,则使用“typo”表中的指标,否则使用“win”表中的指标。
Firefox 如果设置了“USE_TYPO_METRICS”,则使用“typo”表中的指标,否则使用“hhea”表中的指标。 如果已设置“USE_TYPO_METRICS”,则使用“typo”表中的指标;否则,使用“win”表中的指标。
Safari 使用“hhea”表中的指标。 如果已设置“USE_TYPO_METRICS”,则使用“typo”表中的指标,否则使用“win”表中的指标。

如需详细了解字体指标在不同操作系统上的运作方式,请参阅这篇有关垂直指标的文章

跨设备兼容性

对于绝大多数字体(例如,Google Fonts 托管的大约 90% 的字体),可以在不了解用户的操作系统的情况下安全地使用字体指标替换值:换言之,对于这些字体,无论使用 hheatypo 还是 win 指标,ascent-overridedescent-overridelinegap-override 的值都完全相同。此代码库提供了有关此规则适用和不适用的字体的相关信息。

如果您使用的字体需要为 OSX 和 Windows 设备使用单独的一组字体测量值替换项,则只有在您能够根据用户的操作系统来调整样式表的情况下,才建议使用字体测量值替换项和 size-adjust

使用字体测量值替换项

由于字体指标替换项是使用网页字体(而非后备字体)的元数据中的测量值计算得出的,因此无论使用哪种字体作为后备字体,这些替换项都不会改变。例如:

body {
  font-family: "Poppins", "fallback for Poppins", "another fallback for Poppins";
}

@font-face {
  font-family: "fallback for Poppins";
  src: local("Arial");
  ascent-override: 105%;
  descent-override: 35%;
  line-gap-override: 10%;
}

@font-face {
  font-family: "another fallback for Poppins";
  src: local("Roboto");
  ascent-override: 105%;
  descent-override: 35%;
  line-gap-override: 10%;
}

size-adjust 的运作方式

简介

size-adjust CSS 描述符会按比例缩放字体字形的宽度和高度。例如,size-adjust: 200% 会将字体字形放大到原始大小的两倍;size-adjust: 50% 会将字体字形缩小到原始大小的一半。

显示使用“size-adjust: 50%”和“size-adjust: 200%”的结果的示意图。

size-adjust 本身在改进字体后备选项方面应用有限:在大多数情况下,后备字体需要略微缩窄或展宽(而不是按比例缩放),才能与 Web 字体匹配。不过,将 size-adjust 与字体指标替换项结合使用,可以让任意两种字体在水平和垂直方向上都匹配。

size-adjust 在样式表中的用法如下:

@font-face {
  font-family: "fallback for poppins";
  src: local("Arial");
  size-adjust: 60.85099821%;
  ascent-override: 164.3358416%;
  descent-override: 57.51754455%;
  line-gap-override: 16.43358416%;
}

由于 size-adjust 的计算方式(将在下一部分中介绍),size-adjust 的值(以及相应的字体指标替换值)会因所使用的后备字体而发生变化:

body {
  font-family: "Poppins", "fallback for Poppins", "another fallback for Poppins";
}

@font-face {
  font-family: poppins-fallback;
  src: local("Arial");
  size-adjust: 60.85099821%;
  ascent-override: 164.3358416%;
  descent-override: 57.51754455%;
  line-gap-override: 16.43358416%;
}

@font-face {
  font-family: poppins-fallback-android;
  src: local("Roboto");
  size-adjust: 55.5193474%:
  ascent-override: 180.1173909%;
  descent-override: 63.04108683%;
  line-gap-override: 18.01173909%;
}

计算大小调整和字体指标替换值

以下是计算 size-adjust 和字体指标替换项的等式:

size-adjust = avgCharacterWidth of web font / avgCharacterWidth of fallback font
ascent-override = web font ascent / (web font UPM * size-adjust)
descent-override = web font descent / (web font UPM * size-adjust)
line-gap-override = web font line-gap / (web font UPM * size-adjust)

大多数输入(即上行、下行和行距)都可以直接从 Web 字体的元数据中读取。不过,avgCharacterWidth 需要近似估算。

近似平均字符宽度

一般来说,平均字符宽度只能近似计算,但在某些情况下,可以精确计算字符宽度:例如,使用等宽字体时或已事先知道文本字符串的内容时。

计算 avgCharacterWidth 的一种简单方法是取所有 [a-z\s] 字符的平均宽度。

 比较 Roboto [a-zs] 字符各个字体的宽度的图表。
Roboto 字形宽度

不过,对所有字符进行均等加权可能会降低常用字母(例如 e)的宽度,并使不常用字母(例如 z)的宽度超支。

一种更复杂但准确性更高的方法是考虑字母频率,并改为计算 [a-z\s] 字符的频率加权平均宽度。这篇文章非常有助于了解英语文本的字母出现频率和平均字词长度。

显示英语字母频率的图表。
英语字母出现频率

选择方法

本文讨论的两种方法各有利弊:

  • 如果您刚开始优化字体后备方案,不妨单独使用字体指标替换项。虽然这是两种方法中较为简单的一种,但它通常功能强大,可以显著减少与字体相关的布局偏移的幅度。

  • 另一方面,如果您希望提高精确度,并且愿意多做一些工作和测试,则可以考虑采用 size-adjust 方法。如果实现得当,此方法可以有效消除与字体相关的布局偏移。

选择后备字体

本文中介绍的技术依赖于使用字体指标替换项和 size-adjust 来转换广泛可用的本地字体,而不是尝试查找与网页字体非常相似的本地字体。选择本地字体时,请务必注意,很少有字体在本地广泛使用,并且没有任何一种字体适用于所有设备。

Arial 是 Sans Serif 字体的推荐回退字体,Times New Roman 是 Serif 字体的推荐回退字体。不过,这两种字体都不适用于 Android(Android 上唯一的系统字体是 Roboto)。

以下示例使用了三种后备字体来确保广泛覆盖设备:针对 Windows/Mac 设备的后备字体、针对 Android 设备的后备字体,以及使用通用字体系列的后备字体。

body {
  font-family: "Poppins", poppins-fallback, poppins-fallback-android, sans-serif;
}

/*
Poppins font metrics:
- ascent = 1050
- descent = 350
- line-gap = 100
- UPM: 1000
AvgCharWidth:
- Poppins: 538.0103768
- Arial: 884.1438804
- Roboto: 969.0502537
*/

@font-face {
  font-family: poppins-fallback;
  src: local("Arial");
  size-adjust: 60.85099821%;
  ascent-override: 164.3358416%;
  descent-override: 57.51754455%;
  line-gap-override: 16.43358416%;
}

@font-face {
  font-family: poppins-fallback-android;
  src: local("Roboto");
  size-adjust: 55.5193474%:
  ascent-override: 180.1173909%;
  descent-override: 63.04108683%;
  line-gap-override: 18.01173909%;
}

征求反馈

如果您对使用字体指标替换项和 size-adjust 的体验有任何反馈,欢迎与我们联系。