Chromium 83 中 macOS 系统 UI 字体更多可变字体选项

Catalina 为 macOS 带来了新的统一可变系统字体。

CSS 字体模块级别 4 规范的 'system-ui' 部分定义了一个 system-ui 字体关键字,允许开发者在其网站和应用中直接使用内置的、涡轮优化的、本地化的、超高质量的、无需下载的默认操作系统字体。

body {
  font-family: system-ui;
}

这种排版选择类似于说“对此用户当前区域设置使用默认系统字体”。

在 macOS 上,system-ui 字体是 San Francisco,这是一种经过设计团队审查、测试以及…最近升级的字体!首先,我们将介绍 Catalina 中令人兴奋的新可变字体功能,然后我们将介绍一些 错误以及 Chromium 工程师如何解决这些错误

这篇文章假设您已经熟悉可变字体。如果不是,请查看《Web 上的可变字体简介》以及下面的视频。

浏览器兼容性

在撰写本文时,system-ui 已获得 Chromium(自 56 版起)、Edge(自 79 版起)、Safari(自 11 版起)和 Firefox(自 43 版起)的支持,但 Firefox 使用 -apple-system 关键字。有关更新,请参阅《Can I use variable fonts?》。

新功能

Catalina 为系统字体带来的新功能现在已在 Chromium 83 中提供给 Web 开发者。system-ui 字体现在具有更多可变设置:光学尺寸调整和 2 种独特的粗细调整

Mojave
h1 {
  font-family: system-ui;
  font-weight: 700;
  font-variation-settings:
    'wght' 750
  ;
}
Catalina
h1 {
  font-family: system-ui;
  font-weight: 700;
  font-variation-settings:
    'wght' 750,
    'opsz' 20,
    'GRAD' 400,
    'YAXS' 400
  ;
}

在 Mojave 上,system-ui 是一种可变字体,仅具有 wght 设置。而在 Catalina 上,system-ui 是一种可变字体,具有 wghtopszGRADYAXS 设置。

在我看来,这看起来是一些巧妙的渐进增强设计机会!如果您愿意,可以深入研究系统字体的细微之处。

wght

接受介于 0900 之间的字体粗细,并均匀应用于所有字符。

/* 0-900 */
font-variation-settings: 'wght' 750;

opsz

光学尺寸调整类似于字距调整或字母间距,但间距是由人眼而不是数学计算完成的。值 19 或以下适用于文本和正文副本间距,而 20 或以上适用于间距显示标题和标题。

/* 19 or 20 */
font-variation-settings: 'opsz' 20;

GRAD

类似于粗细,但不影响水平间距。它接受介于 4001000 之间的值。

/* 400-1000 */
font-variation-settings: 'GRAD' 500;

YAXS

垂直拉伸字形。它接受介于 4001000 之间的值。

/* 400-1000 */
font-variation-settings: 'YAXS' 500;

组合选项

只需几行 CSS,我们就可以将字体设置调整为我们选择的粗体,或尝试其他有趣的组合

font-weight: 700;
font-weight: bold;
font-variation-settings: 'wght' 750, 'YAXS' 600, 'GRAD' 500, 'opsz' 20;

就这样,macOS 上的 Chromium 用户看到了您升级后的自定义 750 粗细以及其他一些有趣的调整 👍

Playground

点击下面 Glitch 中的“Remix to Edit”以获取 Glitch 的可编辑副本,然后编辑新的 font-variation-settings 选项,以查看它如何影响您的字体。请记住,此 Glitch 仅在使用 macOS Catalina 设备时有效。

macOS 10.15 为其系统字体添加了新功能,并且在 macOS 10.15 中,Chromium 错误跟踪器中记录了一个棘手的 system-ui 错误。我想知道它们是否相关!?

附录:system-ui 回归

这个故事从另一个错误开始:#1005969。这是针对 macOS 10.15 报告的,因为 system-ui 字体间距看起来狭窄而拥挤。

A comparison of two paragraphs from a Facebook group page. On the left is Chrome and the right is Safari, and Chrome is subtle but slightly tighter in spacing
左侧为 Chrome(更紧密的字距),右侧为 Safari(更好的光学间距)

背景

您是否注意到在 macOS 10.14 上,当大小增大或减小时,您的段落或标题如何“捕捉”到外观不同的字体?

在 Mojave (macOS 10.14) 上,system-ui 字体根据目标字体大小在两种字体之间切换。当文本小于 20px 时,macOS 使用“San Francisco Text”。当文本为 20px 或以上时,macOS 使用“San Francisco Display”。光学尺寸调整静态地构建到两个单独的字体中。

Catalina (macOS 10.15) 为 San Francisco 发布了新的统一可变字体。不再需要管理“Text”和“Display”。它还获得了前面描述的新的变体设置 opsz

h1 {
  font-variation-settings: 'opsz' 20;
}

不幸的是,新的 Catalina 字体中的默认 opsz 值为 20,而 Chromium 工程师没有准备好将 opsz 应用于系统字体。这导致较小的尺寸显示得太窄。

为了解决这个问题,Chromium 需要将 opsz 正确地应用于系统字体。这导致 问题 #1005969 得到修复。胜利!还是…?

尚未完成

这就是棘手的地方:Chromium 应用了 opsz,但某些东西看起来仍然不对劲。Mac 上的系统字体有一个名为 trak 的附加字体表,用于调整水平间距。在进行修复工作时,Chromium 工程师注意到,在 macOS 上,当从 CTFontRef 对象检索水平度量时,trak 度量已经计入度量结果中。Chromium 的整形库 HarfBuzz 需要尚未计入 trak 值的度量。

A display of system-ui and all of it's font weight and variations in a list. Half of them have no weight differences applied.
左侧:粗体应用于字体大小 19 及以下。右侧:字体大小 20 及以上失去粗体样式

在内部,Skia(图形库,而不是同名的字体)同时使用来自 CoreGraphicsCGFontRef 类和来自 CoreTextCTFontRef 类。由于这些对象之间所需的内部转换(用于保持向后兼容性并访问两个类上所需的 API),Skia 在某些情况下会丢失粗细信息,并且粗体字体将停止工作。这在 问题 #1057654 中进行了跟踪。

Skia 仍然需要支持 macOS 10.11,因为 Chromium 仍然支持它。在 10.11 上,“San Francisco Text”和“San Francisco Display”字体甚至都不是可变字体。相反,每种字体都是适用于每个可用粗细的单独字体系列。在某些时候,它们的字形 ID 彼此失去同步。因此,如果 Skia 使用“San Francisco Text”进行文本整形(将文本转换为可以绘制的字形),则如果使用“San Francisco Display”绘制,则会变成乱码,反之亦然。即使 Skia 只是请求不同的尺寸,macOS 也可能会切换到另一个。应该可以始终使用其中一种字体并对其进行缩放(使用矩阵来放大它而不是请求更大的尺寸),但 CoreText 存在一个问题,它不会向上缩放 sbix(彩色表情符号)字形(仅向下缩放)。这比这更复杂。CoreText 实际上似乎在矩阵应用后限制了垂直范围,这似乎与它无法以 45 度角绘制表情符号有关。无论如何,如果您希望表情符号显示得很大,则需要制作字体副本以获得大版本。

因此,为了在内部创建不同尺寸的 CTFont 对象副本,同时确保使用相同的底层字体数据,Chromium 从 CTFont 中提取 CGFont,然后从 CGFont 创建新的 CTFontCGFont 对象与尺寸无关,神奇的切换发生在 CoreText 级别)。这在 10.154 之前都运行良好。在 10.15 中,这种往返最终丢失了太多信息,导致了粗细问题。Flutter 注意到了粗细问题,并对调整大小进行了替代修复,以直接从原始 CTFont 创建新的 CTFont,同时使用 CoreText 中旧的但未记录的属性直接控制光学尺寸。这使 10.11 上的内容保持正常运行,并修复了其他问题(例如显式将光学尺寸设置为默认值)。

但是,这在字体中保留了更多 CoreText 的“魔力”。其中之一似乎是它仍然以某种方式调整字形前进量,而不仅仅是 trak 表(Chromium 已经尝试通过另一个未记录的属性来抑制 trak 表的应用)。

CGFont 不会执行任何这些“魔力”,因此 Chromium 或许可以从 CTFont 中提取 CGFont 并仅使用它来获取前进量?不幸的是,这行不通,因为众所周知 CoreText 也会以其他方式修改字体。例如,它使小表情符号比您实际请求的稍大(稍微增加它们的大小)。CGFont 不知道这一点,因此您最终会得到基于 sbix 的表情符号彼此太靠近,因为您将以一种尺寸进行测量,但 CoreText 会将它们绘制得更大一些。Chromium 确实想要 CTFont 前进量,但它希望它们没有跟踪,最好没有任何其他干扰。

由于间距问题的修复需要一组相互关联的 Blink 和 Skia 修复,因此 Chromium 工程师无法“仅仅还原”来解决问题。Chromium 工程师还尝试使用不同的构建标志来更改 Skia 中与字体相关的代码路径,这修复了粗体字体问题,但使间距问题倒退。

修复方案

最终,Chromium 当然希望同时修复这两个问题。Chromium 现在转而使用 HarfBuzz 内置的字体 OpenType 字体度量函数,以直接从系统字体字体表中的二进制数据中检索水平度量。通过这种方式,当字体具有 trak 表时(除非是表情符号字体),Chromium 正在绕过 CoreText 和 Skia。

A display of system-ui and all of it's font weight and variations in a list. The half previously not working looks great now.

与此同时,仍然存在 Skia 问题 #10123,用于跟踪在 Skia 中完全修复此问题,并返回使用 Skia 从那里检索系统字体度量,而不是当前通过 HarfBuzz 的修复。