改进了字体回退

Katie Hempenius
Katie Hempenius

摘要

本文将深入探讨字体回退以及 size-adjustascent-overridedescent-overrideline-gap-override API。借助这些 API,您可以使用本地字体创建与网页字体大小高度或完全匹配的后备字体。这样可以减少或消除由字体交换引起的布局偏移。

如果您不想再阅读本文,不妨参考下面这些可以立即开始使用这些 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 可以创建与其对应的网页字体所占空间相同的后备字体,从而减少或消除此问题。

改进了字体回退

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

字体指标替换项的工作原理

简介

字体指标替换项提供了一种方法来替换字体的上升、下降和线间距:

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

描绘字体上升、下降和线间距的示意图。

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

字体指标替换值在样式表中使用,如下所示:

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%;
}

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

计算字体指标替换值

以下等式可得出给定网页字体的字体指标替换值。字体指标替换值应采用百分比形式(例如 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 的值均来自网页字体的元数据。本文的下一部分将介绍如何获取这些值。

读取字体表

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

FontForge 中“字体信息”对话框的屏幕截图。该对话框中会显示字体指标,如“错别字、上下误”和“错别字间”。
使用 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 CSS 描述符会按比例缩放字体字形的宽度和高度。例如,size-adjust: 200% 会将字形缩放至原始大小的两倍;size-adjust: 50% 会将字形缩放至原始大小的一半。

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

就其本身而言,size-adjust 在改进字体回退方面的应用有限:在大多数情况下,回退字体需要略微缩小或加宽(而不是按比例放大)才能匹配网页字体。不过,将 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)

其中大多数输入(即上升、下降和行间距)都可以直接从网页字体的元数据中读取。不过,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(Roboto 是 Android 上唯一的系统字体)。

以下示例使用了三种后备字体来确保广泛覆盖设备:针对 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 的体验有任何反馈,请与我们联系。