编者按

SFNT 包装格式是 Apple 为 TrueType 开发的一种字体文件格式。现在这种格式已经被广泛采用为通用的字体格式标准,除了 TrueType,OpenType、WOFF 也采用这种包装格式。

操作 SFNT 字体的库是相当丰富和完善的,比如 Google 开发的库 sfntly (支持 Java 和 C++),这个库在 Chromium 浏览器 中也用作字体相关的处理。

本文对 SFNT 包装格式的内部结构进行简要说明。

本文是系列 “数字化的活字印刷术” 的附属文档。本文中或存在一些基本概念需要提前认知,可前往 主文档 中查看详情。

SFNT 格式文件结构概览

SFNT 包装格式十分简单,由若干张表(下图中蓝色部分)串联构成,第一张表称为 Font Directory。每张表实际是一个数据块。

SFNT 包装格式的表结构SFNT 包装格式的表结构

基本概念

表 (Table)

SFNT 结构的第一张表名为「Font Directory」,是一张特殊的表,其中记录了字体的基本信息和其他表的信息。
除了 Font Directory 外,每张表都有其名字及特定的含义。例如:

  • ‘head’ 表中记录了字体的全局信息、版本号等元数据;
  • ‘cmap’表(Character Code Mapping)存储了字符编码(Character Code)到字形(Glyph)的映射关系;
  • ‘glyf’(Glyph Outline)表是整个字体中最大的表,记录了每个字形(Glyph)的轮廓、指令等数据;
  • ‘loca’表(Glyph Location)是位置索引,它存储了各个字符相对于 ‘glyf’ 表头的偏移量;

所有表中的数据都以二进制的方式存储在电脑中,例如 ‘head’ 表在计算机中就是一串二进制数:

1
2
3
4
5
6
7
8
9
10
11
12
13
from fontTools.ttLib import TTFont
#载入字体文件
font = TTFont("simkai.ttf")
#'head'表的二进制形式
bin(int(font.getTableData('head').hex(), 16))
#'0b100000000000000000000000000000101000000101000111100001010
# 11001001111010000101101101011111000011110011110011110101000
# 00000000010110000000100000000000000000000000000000000000000
# 00101111000100011001100000101110000000000000000000000000000
# 00000001100000101100011011101010101011111111111111101001111
# 11111101000100000001000010000000000011011100000000000000000
# 00000000000001100000000000000001000000000000000010000000000
# 000000'

每个表中二进制资料都有其约定的、独特的解析方法。

字体目录 (Font Directory) 表

字体目录表可分为两个部分:Offset Subtable、Table Directory。

Offset Subtable

此处使用 5 个数字记录了该 SFNT 字体的格式、表的数量,以及一些用于优化搜索速度的字段。

Type Name Description
uint32 SFNT type 0x74727565(即 ASCII ‘true’)表示 Apple TrueType 格式字体;0x00010000 表示 Microsoft、Adobe 的 TrueType 格式字体;0x4F54544F(即 ASCII ‘OTTO’)表示使用 PostScript 轮廓的 OpenType 字体;……
uint16 numTables 表的数量
uint16 searchRange 用来优化后面表的二分搜索速度:(maximum power of 2 <= numTables)*16
uint16 entrySelector 用来优化后面表的二分搜索速度:log2(maximum power of 2 <= numTables)
uint16 rangeShift 用来优化后面表的二分搜索速度:numTables*16-searchRange

Table Directory

此处使用一系列串联的表信息结构体(如下所示),描述每一张表的名字、偏移量、长度等信息。

Type Name Description
uint32 tag 表的名字,4 个 ASCII 字符构成,如 ‘head’ ‘glyf’ ‘loca’,不足 4 个字符的表名在尾部用空格补齐
uint32 checkSum 表中数据的校验和
uint32 offset 该表的数据在该文件中的偏移量
uint32 length 该表的实际长度(不含补 0)

字符相关概念

  1. 字符集(Character Set)定义了一系列支持的字符,如 GB18030 字符集、Big5 字符集、Unicode 字符集。
  2. 字符编码(Character Encoding)是指将字符编码成数字的编码规则,例如 ASCII 编码、Unicode 编码、Big5 编码、GB18030 编码。
    1. 旧的西文字体的编码方式可能不是 Unicode,但也有将不是 Unicode 编码的字体转换成 Unicode 字体的方法 [1]。
    2. OpenType、TrueType 标准都推荐(should always use)使用 Unicode 编码的字体 [2],以支持跨平台可用。不同平台也支持不同编码方式的字体,如 Windows 就支持 ShiftJIS、Big5 等编码的 SFNT 字体。
  3. 码点、码位、内码(Code Point、Code Position):字符集中的每一个字符都能通过字符编码对应到一个数字,称为码点。
    1. 「我」字的 Unicode 码点是「U+6211」,GB18030 码点是「CED2」。
    2. 「A」的 ASCII 码点是 42。

  1. Unicode 名称(Unicode Name):Unicode 字符集中的每一个码点都拥有一个字符串「名字」
    1. 「我」字的 Unicode 名称为「CJK UNIFIED IDEOGRAPH-6211」
    2. 「A」的 Unicode 名称为「LATIN CAPITAL LETTER A」

几张必需表的功能

Tag 描述
cmap 码点对应到 Glyph 的映射表
glyf 包含每个 Glyph 的轮廓数据
head 表中记录了字体的全局信息,例如版本号等
hhea horizontal header (不知道怎么翻译,避免歧义直接用原文档的术语)
hmtx horizontal metrics (不知道怎么翻译,避免歧义直接用原文档的术语)
loca 存储了各个字符相对于 ‘glyf’ 表头的偏移量
maxp 记录字体对于内存上的一些需求参数
name 包含了人类可读的相关名称数据,比如字体名称等
post PostScript相关数据

‘head’

此表是一个结构体,包括但不限于以下基本信息:

  • 字体版本
  • 创建时间、修改时间
  • Direction hint
  • 所有字型的 x、y 坐标范围

选字的过程:’cmap’、’loca’ 和 ‘glyf’ 表

‘cmap’,’loca’ 和 ‘glyf’ 三张表之间有着映射关系。

‘glyf’表 (Glyph Outline) 是最大的一张表,包括了每个字形的轮廓、指令等数据;

‘cmap’表 (Character Code Mapping) 存储的是字符到字形的映射;

‘loca’表 (Glyph Location) 是位置索引表,它存储了各个字符相对于’glyf’表头的偏移量,’loca’表的存在是为了提升特定字符数据的快速访问速度。

通常的字符映射方法为:由 ‘cmap’ 表中的映射关系,定位到 ‘loca’ 表中存储的字型偏移量,再由 ‘loca’ 表提供的偏移量定位到 ‘glyf’ 表中的字形中,就能获取到字形。

例如中易楷体 simkai.ttf 中,简体汉字「马」的字符映射示意图是:

在 ‘cmap’ 表中存储了字符 “马” 的 Unicode 码点 uni9A6C 和字形编号 20642 的对应关系,’loca’表负责通过字形编号 20642 查找到字形 “马” 在 ‘glyf’ 表中的具体位置,’glyf’ 表中存储了“马”的字形数据,例如轮廓线条数,边界框坐标等等。