不直接绘制文字,而是交给平台处理的理由
你是否遇到过韩文看起来很正常,唯独汉字显得有些陌生的情况?当我们决定不亲自绘制文字,而是交给平台的文本引擎时,我们的工作就不再是决定“要画什么”,而是决定“该请求哪种字体”。

文字要显示在屏幕上,需要经过许多步骤。要把字符转换为形状,进行换行,再绘制成像素。还要处理韩文组合、汉字、表情符号、阿拉伯文字以及字体回退。
我们没有重新实现这一过程。我们把它交给了 Linux 的 Pango 和 HarfBuzz、Windows 的 DirectWrite,以及 macOS 的 TextKit 2。
因为这是操作系统长期以来不断打磨的领域。如果由我们自己实现,在 CJK 字符或无障碍功能等棘手之处,质量反而很可能会下降。我们也认为,产品的差异化并不在渲染器本身,而在于排版设计和符合平台习惯的使用体验。
但是,由我们决定何时创建
文本布局的创建需要时间和内存。大型文档中可能有数万行,因此无法全部创建。
所以我们只创建屏幕上可见的行及其周边的行。远离屏幕的行则立即丢弃。即使长时间滚动一个 1MB 的文档,仍然存活的布局也只有大约四十到六十个。
对于尚未创建的行,我们会预先估算其高度。因为滚动条需要知道整个文档的长度。
当然,估算也可能出错。如果重新计算出的高度与预期不同,画面可能会发生偏移。这时,我们会在同一帧内按照差值校正滚动位置。在用户眼中,应该像什么都没有发生一样。
正在组合韩文的行会不加任何样式地绘制。因为如果输入过程中插入粗体或隐藏等属性,输入法所知道的字符数可能会与屏幕上的字符数不同。
最先遇到的问题是汉字的面孔
交给平台处理,也意味着会沿用平台的默认值。
Ubuntu 的默认 UI 字体中没有韩文。因此,系统代为选择的字体是 Noto Sans CJK JP。韩文显示正常,但部分汉字呈现为日式字形。因为即使是同一个 Unicode 字符,在韩国和日本也可能具有不同的形状。
解决方法很简单。我们在 UI 字体列表中把 Noto Sans CJK KR 放在了前面。
但还有一点更为重要。如果我们不制定标准,平台就会按照自身的默认值,而不是用户的语言来给出答案。
通过字体的顺序塑造不同语言的字形
字体栈会从前往后查找字符。只有第一个字体中没有的字符,才会转到下一个字体。
利用这一特性,就可以让英文显示为衬线体,让韩文显示为无衬线体。也就是说,只需一行设置,就能为不同语言组合适合的字体。
如果阅读字体留空,就直接使用正文字体。如果单独指定,则只在阅读模式中改变。源码模式使用代码字体。
字号会遵循操作系统的无障碍设置。在系统中放大了文字的用户,无需在每个应用中重新设置。

屏幕阅读器也利用同一基础。平台的文本层已经与无障碍功能相连,因此我们只需准确传递所请求行的内容。
缺少翻译时,构建会失败
UI 文本使用英语编写,并将该文本作为翻译表的键。没有翻译时,屏幕上会直接显示英语。
问题在于,这不会产生错误。即使遗漏翻译,也只会悄无声息地夹杂着英语显示。
因此,我们编写了检查脚本。它会收集代码中实际使用的文本,并与 12 种语言的翻译表进行比较。如果只在一种语言中添加了文本,或遗漏了翻译,构建就会失败。
每个翻译表有 304 个键,代码中实际使用的文本有 282 条。支持的语言为 English、한국어、日本語、简体中文、繁體中文、Deutsch、Français、Español、Português (Brasil)、Русский、Italiano、Polski。

切换语言时,会使用同一文档打开一个新窗口,并关闭原有窗口。如果逐一翻译已经显示的数百个控件,很容易出现遗漏。我们判断,与其维护一条几乎不会使用的复杂路径,不如在切换语言的瞬间重新打开一次窗口,这样更加安全。
交给平台处理,结果也会带有平台的特征
即使是同一个文档,不同操作系统上的像素也不会完全相同。因为文字的优化方式和默认字体有所不同。
我们不会强行让它们变得一致。取而代之的是,我们会保持字号比例、行高和边距等文档基准一致。
字体回退的结果也可能因已安装的字体而不同。因此,我们不会承诺特定的结果,而是透明地展示请求字体的顺序。用户也可以自行更改。
不亲自绘制,并不意味着责任也随之消失。我们必须更准确地决定把什么交给平台,以及以怎样的顺序发出请求。