(三)文字排版与渲染:现代字体的对象与结构

22 阅读29分钟

给一段文字更换字体时,我们通常先选择 Source Sans 3,再从 Regular、Italic、Bold 中选择一个。从 Regular 切换到 Bold 后,文字变粗了,但界面没有告诉我们:软件换用了另一份数据,还是在同一份数据中选中了另一个状态?

上一篇《(二)文字排版与渲染:数字字体的发展历程》已经说明,现代数字字体是一份能够被系统识别和读取的字体资源。但这个结论还没有回答:界面显示的共同名称、当前样式、系统实际读取的资源和磁盘文件,究竟是不是同一个对象。

继续看这次切换。在静态字体中,Regular 和 Bold 通常各自对应一份数据;而对于可变字体,它们也可能是同一份数据里的两个状态。也就是说,界面上的一次样式切换,不一定等于磁盘上的一次文件切换。具体样式、字体资源和字体文件并没有固定的一一对应关系。

本文就从这次更换字体继续追查:Source Sans 3 与 Bold 分别指什么,系统实际读取哪组数据,这些数据怎样组织,又存放在哪个文件里。到文章结束时,我们应当能够判断日常所说的“一个字体”具体指向哪一层,以及还需要确认什么。

一、字体家族与样式选择

1.1 字体家族与具体样式

假设我们从网上下载两个字体,分别是 Source Sans 3 Bold 和 Source Sans 3 Regular。当把这两个字体安装到系统中之后,在设计软件的字体菜单里,通常会发现它们都被组织到 Source Sans 3 下,形成 Regular 和 Bold 两个子项。这里的 Source Sans 3 就是一个字体家族(font family),它把具有共同设计特征的一组样式组织在同一个名称下。而 Regular、Italic 和 Bold 则是家族中可以分别选择的具体样式,也就是字体样式(font face,后文简称 face)。

字体家族:Source Sans 3
├── Regular face
├── Italic face
└── Bold face

family 组织相关样式,face 是其中一个可以单独选择的成员。
这是一种名称与选择关系,不是资源或文件的包含结构。

浏览器中的操作也遵循这组关系。例如,页面已经加载 Source Sans 3 时,可以在 CSS 中这样指定字体:

font-family: "Source Sans 3";
font-weight: 700;

font-family 先指定 Source Sans 3 这个家族,font-weight: 700 再让浏览器从中匹配 Bold face;如果改为 400,通常会匹配 Regular face。也就是说,我们在代码里分别填写的家族名称和字重,最终共同决定这一次使用哪个 face。CSS Fonts Level 4 对 family 与 face 选择关系的说明

术语边界:设计领域还经常使用 typeface,泛指一套能够被辨认出来的整体字体设计。它适合讨论风格来源和设计共性,却没有在不同软件、操作系统和 API 中稳定对应某一个更精确的技术层级。为了继续追踪系统实际使用的对象,本文后面只保留 family、face、字体资源和字体文件。

1.2 每个 face 都对应一组已经确定的设计参数

Regular、Italic 和 Bold 分别对应一组已经确定的设计参数。例如,Bold 会采用较大的字重,Italic 会采用相应的斜体设计。选择 Bold,就是选择这组已经完成的设计结果,而不是让软件临时把 Regular 的线条机械加粗。

当粗细、字宽和倾斜等参数都有了确定取值,就指明了一个可以直接选择和使用的设计结果。这就是一个 face,也可以把它理解为一个固定设计状态。

同一个结果:Source Sans 3 Bold
├── 在家族与样式选择中:Bold face
└── 在设计维度中:粗细、宽度、倾斜等参数已经确定的状态

到这里,我们只确定了想使用哪种设计结果,还没有确定系统实际读取哪份数据。因此,即使两个环境都显示 Source Sans 3 Bold,最终使用的内容也不一定一致。这里需要分开两种情况:

  • 名称相同,设计或数据不同:旧版本和新版本都可能显示 Source Sans 3 Bold,但其中的字形设计或字符范围已经发生变化;
  • 设计状态相同,实际数据不同:字体生产者可以基于同一套 Bold 设计分别发布桌面版本和 Web 版本,两边显示的样式相同,但文件中的字符范围或其他数据不一定完全一致。

所以,同一个名称不能证明设计完全相同,同一个设计状态也不能证明使用的是同一份数据。下一章要区分的,正是设计层面的 face 和数据层面的字体资源。

二、字体资源

2.1 字体资源与Font Face

假设现在已经选中了 Source Sans 3 Regular。这个选择只确定了想要的设计结果,系统还要找到一组可以实际读取的数据。能够被字体系统独立识别和使用的这组数据,就是字体资源(font resource)。

face 和字体资源不是上下级关系,而是从两个层面描述同一个字体:face 表示选中了哪种设计状态,字体资源表示系统实际读取哪组数据。静态资源通常记录一个已经确定的设计状态;可变资源则能根据参数得到多个具体状态。OpenType Font Variations 对 font face 与 font resource 的定义

说明范围:下文以 OpenType 字体资源为主线。为了先把对象关系说清楚,本章只讨论资源是什么;第三章再进入资源内部。

设计层:Source Sans 3 Regular face
          │ 要被系统使用,需要对应到
          ▼
数据层:一份确切的字体资源

上一章的两个例子其实在说明两种不同问题:同一个名称可能指向不同设计或数据,同一个设计状态也可能被记录成不同版本的数据。它们不是两个彼此对称的例子,共同点只是:face 的名称和设计参数不能代替资源身份。因此,只比较 Source Sans 3 Regular 这样的显示名称,不能证明两个环境正在读取同一份资源。

只有确认具体的字体资源,才能确定系统这一次实际使用的是哪组数据。

2.2 静态字体资源

静态字体资源(static font resource),是各项设计参数已经固定的字体数据。使用静态字体时,Regular、Italic 和 Bold 通常分别对应不同的字体资源。

当各个样式分别使用静态字体资源时,从 Regular 切换到 Bold,实际上也会从 Regular 资源切换到 Bold 资源。

因为这里的 face 与资源通常近似一一对应,日常使用时很容易把它们当成同一个对象。但是,可变字体会打破这种对应:一份资源可以得到多个具体状态。

2.3 可变字体资源与实例

可变字体资源(variable font resource),是能够在一定范围内改变设计参数的字体数据。它把可以变化的范围组织成一段设计空间。在一份可变资源中,一个可以变化的设计维度由一条可变轴(variation axis) 表示:例如,字重维度通常由 wght 轴表示,宽度维度由 wdth 轴表示,光学尺寸维度由 opsz 轴表示。一份资源有几条可变轴,它的设计空间就有几个维度;每条轴都有自己的数值范围和默认坐标值。

要从设计空间中选出一个固定结果,系统需要为每条轴确定一个坐标值。只有一条轴时,一个坐标值就能确定位置,例如 wght=537;有多条轴时,各轴的坐标值会组成一组有顺序、并与各轴一一对应的坐标值,例如 (wght=537, wdth=90)。这组值共同确定多维设计空间中的一个位置,由此得到的固定结果叫作可变实例(variation instance)。

例如,网页已经加载一份带有字重轴的可变字体时,可以直接在 CSS 中写下:

font-family: "Source Sans 3";
font-weight: 537;

这里的 537 是字重维度上 wght 轴的坐标值。如果这份资源还有其他可变轴,浏览器还会从相应的 CSS 设置或资源默认值中得到那些轴的坐标值,再用完整的一组坐标值选出相应实例。

从设计层面看,实例仍然是一个参数已经确定的 face。静态资源直接对应一个固定 face;可变资源则与一组完整的坐标值结合,得到一个实例 face。以一份同时包含字重和宽度两个维度的资源为例:

静态资源直接提供固定 face;可变资源通过设计空间中的一组轴坐标值得到实例 face

在描述可变资源选出的结果时,常会遇到下面三种说法:OpenType Font Variations 对 variation space 与 instance 的说明

  • 默认实例:每条轴都采用资源规定的默认坐标值;
  • 具名实例:生产者为一组常用坐标值预先附上名称,例如 Bold;
  • 不具名实例:使用者选择另一组合法坐标值,这个位置不必预先作为一份“预设”写进资源。

默认位置也可以拥有样式名称,所以“默认”和“具名”描述的是两个不同属性,并不是互相排斥的分类。

选中一个实例不会创建新的字体资源或字体文件。在 CSS 中把 font-weight 设为 537,只是显式确定了当前可变资源中 wght 轴的坐标值;如果还有其他轴,完整实例也包含那些轴的坐标值。要在另一处复现同一个实例,至少要同时固定可变资源和每条轴的坐标值;只写 family 名称,甚至只写“使用可变字体”,都还没有把输入说完整。

现在可以明确:family 组织相关 faces,face 表示已经确定的设计结果;静态资源通常对应一个 face,可变资源则在各轴的坐标值共同确定后得到一个实例 face。

三、OpenType 字体资源

上一篇介绍了 OpenType 用数据表组织现代字体,前两章又区分了设计层面的 face 和数据层面的字体资源。现在把两条线合到一起:先以“一份独立 OpenType 文件承载一份字体资源”这个常见情况为例,看看资源内部的数据怎样在文件中组织。

3.1 数据表目录

一份 OpenType 文件保存着多种数据,但是,不同任务不会同时用到全部数据。例如,寻找字符对应的字形、读取字形形状和处理排版关系,是三个不同的任务;解析器会根据当前任务按需读取相应数据。

为了让按需读取成为可能,OpenType 文件的开头有一个数据表目录(table directory)。目录中的每条记录先用一个简短的表名标出数据类型,例如 cmap 表示字符映射;然后再记录这张表在文件中的位置、长度和校验信息。解析器先读目录,就能直接跳到当前任务需要的数据。OpenType Font File 对 table directory 的说明

OpenType 规范定义了许多数据表,但一份具体文件只会带上自己需要的部分。因此,目录回答的是“眼前这份文件实际有什么”,而不是“OpenType 理论上允许什么”。即使两份文件都以 .ttf 结尾,实际携带的数据也可能不同。

目录解决了“每张表放在哪里”的问题。不过,不同表中的数据还需要共同指向同一枚字形,这就要用到字形编号。

3.2 字形编号

OpenType 把字符映射、字形形状和排版关系分别保存在不同的数据表中。字符代码不能直接充当这些表共同使用的索引,因为字体里还可能有连字、字形变体等没有独立字符代码的字形。系统需要先把字符转换为资源内部的统一编号,再用这个编号关联各张表。

例如,字符 A 的代码是 U+0041。当前资源的字符映射可能把它连接到字形编号 42。这个编号本身没有说明 A 长什么样,但是,系统可以继续在同一份资源里查找“42 号字形”的形状、占位方式和排版关系。

这就是字形编号(glyph ID):它不是另一种字符编码,而是当前资源内部共同引用一枚字形的标识。凡是围绕同一枚字形保存的数据,都可以通过这个编号建立联系。

同一份 OpenType 资源中,数据表目录负责定位各张表,glyph ID 负责关联一枚字形的相关数据

有了字形编号,就能看出不同数据表怎样围绕同一枚字形配合:

  • cmap 把字符代码连接到基础字形编号;
  • TrueType 路线用 glyf 和 loca 保存字形形状,CFF 或 CFF2 路线则使用相应的轮廓数据;
  • hmtx、vmtx 记录字形在横排或竖排时怎样占位;
  • GSUB、GPOS 通过字形编号描述字形替换和位置调整。

每张表只完成一部分工作,它们通过资源内部的编号空间共同服务这份字体。OpenType Font File 的数据表分类

编号边界:glyph ID 只在当前资源中有效。另一份资源也可能有编号 42,但它可以连接到完全不同的字符和形状。切换资源以后,系统必须重新从字符建立映射,不能沿用上一份资源的编号。

3.3 轮廓与内置点阵

上一篇所说的“点阵字形”,是指用像素网格表达一枚字形的方法;当许多点阵字形连同名称和字符映射一起保存时,可以组成一份点阵字体文件。OpenType 的内置点阵则不是另一种独立文件格式,而是保存在 OpenType 文件内部的点阵字形数据。一份 OpenType 字体可以只带轮廓,也可以同时带上为特定像素尺寸准备的点阵。

字形源图形资源保存什么与尺寸的关系在资源中的表达
轮廓由直线和曲线组成的封闭边界保存形状,系统再按当前尺寸缩放并生成设备结果TrueType 的 glyf/loca,或 CFF/CFF2
内置点阵字体生产者预先准备的字形像素图像按特定像素尺寸组织,尺寸匹配时直接选用相应结果按目标像素尺寸分组保存的点阵数据

为同一个目标像素尺寸准备的一组内置点阵,称为一个 bitmap strike。这里的“一组”是指这个 strike 实际收录的字形位图;它不一定覆盖当前资源里的每一个字符或每一枚字形。

OpenType 用 ppem 标记目标像素尺度。ppem 表示一个 em 在水平或垂直方向上对应多少像素,这里的 em 是字体内部用来统一衡量字形与空间的参考尺度。以常见的方形像素为例,为 12 ppem 准备的那组内置点阵是一个 strike;为 16 ppem 准备的另一组内置点阵则是另一个 strike。一份资源可以包含多个 strike,分别服务不同的目标像素尺度。OpenType 对 embedded bitmap strikes 的说明

轮廓适合需要跨字号使用的正文、界面和印刷;内置点阵适合生产者希望精确控制小尺寸或像素风格结果的场景。例如,复古像素风格游戏可以为 8、12、16 ppem 分别准备字形位图;一些面向低分辨率屏幕的小字号界面字体,也会用手工调整的点阵保证笔画清楚。二者都可以在字体发布前进入资源,并随字体一起交付。

归属边界:应用把轮廓画成像素以后,也可能暂时保存结果,但那是运行环境生成的位图,不是字体内置点阵。两者虽然都表现为像素图,产生时间、归属和可复用条件并不相同。

3.4 彩色字形

普通的轮廓字形只记录字形边界,并不把最终填充色固定在轮廓里。绘制时,排版或绘制环境再决定填什么颜色;例如,网页可以用 CSS 的 color 指定文字颜色,没有额外指定时则常显示为黑色。

这种由外部环境统一决定颜色的方式,适合通常的单色文字;但有些字形本身需要同时呈现多种颜色,彩色 emoji 是最常见的例子。说到这里,还要先区分 emoji 字符和 emoji 字体:emoji 本身是 Unicode 字符或字符序列;彩色 emoji 字体与普通字体一样,先把字符代码映射到具体字形,只是这些字形还需要颜色、图层或更完整的图形组合信息。

彩色字形(color glyph),就是把这些呈现信息也写入字体资源的字形。它可以建立在轮廓、点阵或 SVG 图形之上,因此不是轮廓和点阵之外的第三种基础形状。

在看具体路线前,先补充两个概念。多层轮廓是把多个轮廓图层叠在一起,并为各层指定颜色;SVG 图形则是一种矢量图形描述,可以记录路径、填充、描边和渐变,而不是把结果固定成一格格像素。OpenType 中常见的彩色字形路线有三类:

路线保存的对象代表性数据
多层轮廓多个可以分别着色的图形层,以及相应调色板COLR/CPAL
彩色点阵已经带颜色的字形像素图像CBDT/CBLC 或 sbix
SVG能够表达渐变、描边和复杂组合的 SVG 图形OpenType SVG 数据

这三条路线保存的对象不同,共同点是颜色或图形组合已经成为字体资源的一部分。Microsoft 对 OpenType 彩色字形路线的说明

彩色字形可以通过多层轮廓、彩色点阵或 SVG 图形写入字体资源

彩色字形常见于 emoji,也可以用于多色图标、符号和装饰字体。字体文件负责提供颜色和图形数据,外部字体系统仍要理解相应路线,才能把它们画出来。但是,不同平台支持的路线可能不同:某个平台可以读取 COLR/CPAL,却未必能读取 OpenType 中的 SVG 或 sbix 数据。如果平台不支持当前字体采用的路线,就可能只能退回单色字形,或无法呈现设计者预期的彩色结果。

3.5 字体资源派生结果:位图缓存与距离纹理

字体资源被系统选中以后,外部系统还会为当前应用和绘制条件生成新的数据。有些结果是最终的字形像素,有些是供后续绘制使用的纹理。它们虽然也像图像,但是产生时间和归属都与字体内置点阵不同。

外部系统可以把已经生成的字形位图暂时保留下来,便于相同绘制条件时可以直接复用。这份可以被复用、也可以被淘汰并从原资源重新生成的临时结果,就是字形位图缓存(glyph bitmap cache)。它不会因此写回字体文件。

图形应用和游戏还可以把许多已经生成的字形位图排进一张或少量几张纹理,形成字形图集(glyph atlas)。这些字形位图与上一段所说的缓存结果属于同一类像素数据,但是,缓存和图集不是同一个对象:缓存强调在相同绘制条件下复用结果,图集强调把多枚字形排进纹理并记录各自位置。应用也可以在生成位图后直接放入图集,不必先建立一份独立缓存。

字形图集在组织方式上很像前端曾经常用的图标雪碧图:都把许多小图像排在一张大图里,再按位置取出需要的区域。不过,图标雪碧图常用来减少网络请求;字形图集通常已经位于本地或 GPU 中,主要作用是减少纹理切换,让许多字形能够批量绘制。

有符号距离场(signed distance field,SDF) 是另一种纹理表示。纹理由许多小格组成,这些小格叫作纹理像素(texel);绘制时,GPU 会在相应位置读取,也就是“采样”这些值。SDF 让每个纹理像素保存它到最近字形边界的距离,并用正负号区分边界内外。距离为零的位置就是边界。

SDF 纹理不是前面装着普通字形位图的那张图集。它保存的是距离,而不是最终黑白像素;不过,多枚字形的 SDF 区域同样可以被排进一张字形图集中。

绘制程序可以直接读取这些距离值,在边界附近选择阈值或平滑过渡,再得到当前像素的透明度。边界距离已经在生成 SDF 时预先计算,后续绘制便不必每次都从原始轮廓重新计算这段关系。因此,同一份 SDF 能在一定缩放范围内重复使用,也方便产生描边、阴影等效果。Valve 对 SDF 字形纹理的原始说明

多通道有符号距离场(multi-channel signed distance field,MSDF) 则是把不同轮廓边缘的距离信息分配到多个颜色通道,让绘制程序可以结合各通道重新判断边界。与单通道 SDF 相比,它能在相同纹理分辨率下更好地保留尖角。它仍然是一种面向特定绘制管线的距离纹理,不是 OpenType 为字形规定的另一种标准源图形。msdfgen 对 MSDF 的实现说明

SDF 用单通道保存到最近边界的有符号距离,MSDF 用多个颜色通道区分不同边缘

现在可以用产生时间和归属比较这几类对象:

对象保存什么通常何时形成属于哪里
字体内置点阵某个 strike 中预先设计的字形像素图像字体发布前字体资源
字形位图缓存当前字号、每条可变轴的坐标值、变换和渲染条件下生成的字形像素字体被使用以后字体系统或运行环境
字形图集多枚已经生成并排入同一纹理的字形位图或距离场,以及各自位置应用构建或运行时应用或图形系统
SDF/MSDF字形边界附近的距离信息应用构建或运行时特定产品与绘制管线
轮廓、内置点阵和彩色字形属于字体资源,位图缓存、字形图集与距离纹理是应用或运行环境生成的派生结果

无论字形图集和距离纹理是在应用构建时预先生成,还是在运行时按需生成,它们都是根据字体源数据得到的派生结果,服务于当前产品的绘制管线。由此可以确定资源边界:轮廓、内置点阵和彩色字形可以随字体资源交付;位图缓存、字形图集和距离纹理则属于应用或运行环境。

四、字体文件、集合与网络包装

前两章区分了设计层面的 face 和数据层面的字体资源,第三章又打开一份独立 OpenType 文件,看到了资源内部的数据组织。接下来把这些对象落到实际文件上,分别说明独立文件、字体集合和网络包装与资源是什么关系。

4.1 独立文件与网络包装

下载字体以后,我们在磁盘上直接看到的通常是 .ttf 或 .otf 文件。这种在磁盘、网络或其他介质上具有名称和明确字节边界的存储对象,就是字体文件(font file)。

字体文件是资源的存储载体,字体资源则是文件中能够被系统独立识别和使用的数据对象。日常最常见的情况,是一个独立 .ttf 或 .otf 文件承载一份 OpenType 资源,所以文件和资源看起来像是同一个东西。但是,两者回答的问题不同:文件说明字节存放在哪里,资源说明系统实际使用哪组数据。

.ttf 和 .otf 都是常见的独立 OpenType 文件扩展名。.ttf 通常使用 TrueType 轮廓;.otf 常使用 CFF 或 CFF2 轮廓,但也可能使用 TrueType 轮廓。因此,扩展名只能提供线索,文件内部的数据表才是判断实际内容的依据。OpenType Font File 对文件扩展名和轮廓数据的说明

需要通过网络分发时,还可以把已有的 OpenType 字体数据重新组织并压缩为 WOFF 或 WOFF2。浏览器下载并解码以后,字体系统继续使用其中的字体数据。

WOFF/WOFF2 改变的是网络交付形式,不是字体设计,也不是与 TrueType 或 CFF 并列的新轮廓格式。W3C WOFF 1.0 · W3C WOFF 2.0

同一份 OpenType 字体数据可以由独立文件直接承载,也可以经过 WOFF/WOFF2 包装、网络传输和浏览器解码后交给字体系统

4.2 字体集合

独立文件与资源的常见一对一关系并不总是成立:一个 .ttc 或 .otc 文件可以同时承载多份资源。

本文所说的字体集合(font collection) 是一个具体的数据实体,指的是一份内部装有多份可独立使用资源的 .ttc 或 .otc 文件。“把多份资源装进同一个文件”是它采用的组织方式,而不是字体设计上的样式分组。

这里涉及两个彼此独立的分类维度。静态资源与可变资源描述的是一份字体资源能够提供一个固定 face,还是能够沿可变轴提供多个实例 face;独立文件与集合文件描述的则是一份文件承载一份资源,还是同时承载多个资源。因此,.ttf 并不等于静态资源,.ttc 也不等于可变资源:一份独立 .ttf 可以承载静态资源,也可以承载可变资源;一个 .ttc 集合中的每个资源成员也都可以是静态的或可变的。

以下文件名只用于示意两种文件结构的差别:

示例文件文件内部从资源得到 face 的方式
Example-Regular.ttf一份静态字体资源资源直接对应 Regular face
Example-Variable.ttf一份可变字体资源为每条轴确定坐标值后,得到一个实例 face
Example.ttc多个资源成员;每个成员可以是静态资源或可变资源先选中成员;若成员是可变资源,再用每条轴的坐标值确定实例 face

字体集合文件的开头会记录内部有多少成员,以及每个成员的数据表目录从哪里开始;每个成员因此仍然是一份能够被独立选择的字体资源。OpenType Font Collections 规范

字体集合通过成员索引定位多份字体资源,每个成员有自己的数据表目录,并可以选择性共享表数据

使用集合中的某款字体时,只知道文件名还不够。系统需要先定位集合文件,再用成员位置选中其中一份资源,然后才能读取这份资源的数据。

例如,PingFang SC 是设计层面的 family,PingFang SC Regular 和 PingFang SC Semibold 是其中两个 face;PingFang.ttc 则是资源与文件层面的集合文件,内部可以包含分别对应这些 face 的静态资源成员。系统要使用 PingFang SC Regular,不仅要找到 PingFang.ttc,还要定位文件中与它对应的资源成员。如果集合中的某个成员是可变资源,选中成员以后还要继续确定每条轴的坐标值,才能得到具体的实例 face。

集合还允许不同成员引用相同的数据表内容,从而减少重复字节。例如,多份 CJK 字体资源可以保留各自的名称和局部设计,同时共享完全相同的大量字形数据。不过,共享只是一种节省空间的能力,并不要求所有成员都共享内容。

这里需要区分两个概念:

对象所在层面组织的内容成员之间的关系
字体家族(family)设计与选择一组具有共同设计特征的 facesRegular、Italic、Bold 等样式之间有直接的设计关联
字体集合(collection)资源与文件同一文件中的多份字体资源为了存储和管理放在一起,彼此不必有设计关联

所以,family 是设计层面的分组,collection 是资源层面的文件组织。一个 family 的多个 faces 可以分散在不同文件中;一个 collection 也可以同时包含同一 family 的多个资源,或包含来自不同 families 的资源。两者没有固定的包含关系。

把这一章的关系收拢起来,family 在设计层组织多个相关 faces;独立 .ttf/.otf 通常承载一份字体资源,.ttc/.otc 可以承载多份字体资源,WOFF/WOFF2 则负责对 OpenType 字体数据进行网络包装。这些对象所在的层面和常见对应,会在下一章的四层关系图中一起归位。

因此,文件说明数据存放和交付的方式,资源说明系统能够独立使用哪组数据,face 则说明当前选中了哪种设计状态。三者在常见情况下可能一一对应,但并不是同一个对象。

五、总结与边界

5.1 “一个字体”的四个层次

现在回到开篇的那次选择。用户为一段文字选择 Source Sans 3 Regular 时,界面只展示了名称和样式;系统要真正使用它,还要找到提供这项设计结果的数据及其文件载体。要看清这些对象的关系,可以先从两个角度认识它们。

从设计与使用的角度看,family 是选择时的分组:它把共享核心设计特征的一组 faces 组织在同一个名称下。face 则是一次具体选择落到的样式,也就是各项设计参数已经确定的固定设计状态。Source Sans 3 是 family,Source Sans 3 Regular 和 Source Sans 3 Bold 是其中两个 faces。

从数据的角度看,font resource 是系统能够独立识别和使用的一组字体数据。静态资源记录一个已经确定的设计状态,通常直接提供一个固定 face;可变资源保存设计轴与变化数据,需要再为每条轴确定坐标值,才能得到一个实例 face。资源内部的数据表目录负责定位各类数据,glyph ID 则作为共同索引,让字符映射、字形描述和单 glyph 度量指向相应字形。

把这两个角度与文件载体连接起来,可以得到一张总关系图:

日常所说的一个字体需要沿设计与使用、数据实体、资源内部、文件与分发四个层次定位

这两个角度不是两套互相竞争的分类。family 和 face 回答“用户选择了哪项设计”,resource 回答“系统实际读取哪组数据”,文件则回答“这些数据存放和交付在哪里”;资源内部的数据表与 glyph ID,进一步说明这组数据怎样被组织和关联。

这四层之间经常存在常见对应,却没有固定的一一关系。同一家族和样式名称可以对应版本、字符覆盖不同的多份资源;一份可变资源可以提供多个实例;一个集合文件可以承载多份资源;相同扩展名也不能证明内部轮廓、字符覆盖或排版能力相同。

因此,询问“两个环境是不是用了同一个字体”时,只比较 family 名称还不够。至少还要确认具体 face、资源来源和版本;若使用可变资源,要记录每条轴的坐标值;若使用集合,要定位成员;若比较文件,还可以进一步固定文件版本或校验值。做到这些以后,“一个字体”才从日常简称变成了可以复查的对象。

5.2 当前边界与下一篇

本文比较的是同一时刻并存的对象,拆开了名称、样式、资源、内部数据和文件之间的关系,也区分了字体内置数据与应用派生结果。到这里,日常所说的“一个字体”已经能够被逐层定位。不过,这些对象怎样产生和流动还没有展开。下一篇将改用时间顺序,追踪一份字体资源怎样从设计结果变成可交付数据,又怎样被系统加载、选择和绘制。