文字渲染的那些事(一)字体是如何存储的?

在现代生活里,我们几乎每天都会和屏幕上的文字打交道——文字看起来是如此平凡,以至于不少与 UI 相关的专业人士都对其下的复杂性知之甚少。这个系列旨在以开发者的角度,介绍一些从文字的二进制数据到像素之间流程的科普知识,希望对感兴趣的同学能有一些启发。

字体的标准与格式

喜欢折腾系统的同学,对于常见的字体格式肯定不会陌生:Windows 系统长久以来,在 C:\Windows\Fonts 里面就放着一大堆 TTF 格式的字体文件。相应地,在 macOS 的 /Library/Fonts 目录里,也有一堆字体:不过这里除了 TTF 之外,还有后缀名为 TTC 和 OTF 的格式。它们又有什么关系呢?

有个有趣的问题,那就是为什么 TTF 格式字体在 macOS 和 Windows 上都能通用呢?这里其实藏着一段微软与苹果的 py 交易史:在 80 年代,Adobe 开发出了基于矢量的私有字体格式 Type 1,以及打印语言 PostScript(在 PDF 格式里就能看到)。矢量字体比起当时的点阵字体,那妥妥的就是步兵和骑兵的区别啊:

Adobe 虽然活好,但由于一些非技术因素(钱)的问题,苹果和微软决定另起炉灶。苹果开发出了矢量字体标准 TrueType,而微软则开发出了 PostScript 的替代品 TrueImage。这两项技术虽然在 Mac 和 Windows 之间互相授权,但真正落地成为事实标准的只有苹果搞的 TrueType,这对应的就是 TTF 字体格式了。

知道了 TTF 代表着 TrueType Font 之后,其它的格式都可以举一反三地推出来:

  • 怎么把一堆 TTF 字体全家桶打包发布呢?我们来个 Collection(合集)吧——于是有了 TTC 格式。
  • 大佬们不要再打啦,我们一起合作开放吧——于是有了 OpenType 的 OTF 格式。
  • 我们搞 Web 最在乎体积了,你们这些玩意都太大了不行啊——于是有了 WOFF 格式。

当然了,光知道后缀名,跟精通 Java/C++ 的拼写没啥区别。我们不妨来看看,字体文件的里面都藏着些什么呢?

小探 TTF 文件

很多字体格式的规范文档中,都会强调字体文件是由构成的。喵喵喵?这里说的表是 Excel 那样的表格吗?打开一个 TTF 格式的字体文件,你的第一印象恐怕和表格很难沾上边:

说好的几行几列呢?不过,表格暗示着一种相对规整的数据结构。眼尖的同学也许已经注意到了,上面的数据最右一列都是一组四个字母的合集。这并非偶然,依据的是 TTF 格式的规范。

在继续介绍它们的具体含义之前,我们不妨考虑这样的一个问题:怎么样为设计一种符合下面这些需求的数据格式呢?

  • 你有多种不同的字段需要存储,每种数据有固定的格式,但长度都是可变的。
  • 需要存储的字段种类可能不同,也有可能在日后扩展出一些新字段。这时应该能够向上和向下兼容。
  • 无需遍历整个文件,就应该能够获知字段的基本信息(位置、长度等)。
  • 数据体积需要尽量小,还需要支持对内容完整性的检验。

现在应用层开发中流行的 JSON 格式,光是在体积尽量小这一项上就会被首先干掉。而 TTF 规范则给出了一种在设计数据格式规范时,可供参考的工程实践:

  • 给所有的字段取个四个字母的唯一名字,它们各自的内容都是一段连续的二进制数据。
  • 在文件头部,首先存储一张「表达整体表结构」的表。在其中指定有多少种不同字段,以及它们的长度、起始位置等信息。这张表叫做 Offset 表。
  • 紧接在这张表之后,逐段将这些字段表的内容拼接起来,就获得了最终的 TTF 格式字体。

让我们来看看这种设计是如何解决上面的这些需求的吧:

  • 用于存储字体信息的各个表,其长度和排列顺序都完全自由(是不是有些类似 Map 结构呢)。
  • 字段种类和后续扩展都没有兼容问题,解析程度可以从 Offset 表中判断自己对数据的支持程度。
  • 在 Offset 表中,各字段数据的偏移量和长度都可以直接获知。
  • 各个字段数据都以约定的二进制形式存储,Offset 表中还存储了它们的校验和,作为完整性判断的基本依据。

举个简单的例子就能说明二进制数据结构的紧凑性。例如,在表达字体是否为粗体、斜体、等宽等元数据的时候,JSON 格式对每个状态,都需要约定好一个形如 xxx: true 的字段,这至少需要五个字节。而基于位运算的约定,在一个字节的 8 位中就可以保存 8 个这样 true|false 的布尔类型变量,往往还留有冗余。在需要区分存储不同精度数据的时候,它也有着得天独厚的优势。所以说,在需要构建专有的数据结构的时候,TTF 这种表驱动的设计还是有一定的参考价值的。另外,在解析这样的二进制格式的时候,传统命令式编程的控制流也会相当趁手:别被社区里浮躁的声音淹没了,学好真正适用于不同场景的技术吧。

回到最初的话题,字体里面存储着哪些表达不同内容的表呢?这里推荐 Typr.js 这个非常简单的 Web 工具,可以点开即用,大概是这样:

看到了解析出的各个表内容了吗?它们之中就存储着从二进制比特到屏幕像素的关键信息。暂时到此为止,我们介绍了字体文件的格式与解析它的基本方式。但怎样基于字形数据来渲染出文字呢?让我们下一篇见吧(如果有的话)

P.S.1 如果希望对字体的数据结构有更进一步的了解,这篇科普性的文章是远远不够的,不妨从这篇正式的 TrueType Reference Manual 文档开始吧。注意,这篇苹果的文档一开头就甩出了个到微软官网的链接,这在其他场合恐怕很难看到了……

P.S.2 我们的前端团队非常欢迎有志于对「渲染」这件事追根究底的同学,感兴趣请邮件 xuebi at gaoding.com

猜你喜欢

转载自juejin.im/post/5c1e2e576fb9a049d5197af2
今日推荐