OpenType 字体文件是 Microsoft 和 Adobe 基于 TrueType 字体,推出的一种通用字体文件格式,用于应用程序呈现文字。OpenType 字体文件应使用扩展名 .OTF、.TTF、.OTC 或 .TTC。(扩展名可以是大写或小写。)扩展名 .OTC 和 .TTC 只能用于字体集合文件。
OpenType 是 TrueType 的扩展,所以 OpenType 是兼容 TureType 的,对于目前的操作系统来说都是支持 OpenType 字体的。
OpenType 字体使用非常广泛,任何可以显示文字的系统都有它的身影。像客户端开发最常见的就是页面文字展示,各种平台都有对应的实现方案。在 Android 中 Typeface 内就包含了 OpenType 的支持。
Typeface
Android的字体加载是在应用启动时来触发的,在Typeface的静态代码块中加载。不同的系统版本实现略有不同,例如高版本系统已经将部分逻辑通过JNI来加载以优化应用启动速度。
其中系统字体文件的加载还在Java中实现:
java
FontConfig config = SystemFonts.getSystemPreinstalledFontConfig();
通过解析 /system/etc/fonts.xml
内声明的字体文件名来加载字体文件,字体文件在路径下 /system/fonts/
存储,这些在 SystemFonts 内都有声明。
xml
<familyset version="23">
<!-- first font is default -->
<family name="sans-serif">
<font weight="100" style="normal">Roboto-Regular.ttf
<axis tag="ital" stylevalue="0" />
<axis tag="wdth" stylevalue="100" />
<axis tag="wght" stylevalue="100" />
</font>
<!-- 省略 -->
</family>
<!-- 省略 -->
</familyset>
解析完这些字体路径关系,再通过 Typeface.nativeCreateWeightAlias
JNI方法来初始化 Typeface 对象。
由于字体绘制有一定的复杂性,Android系统已经为我们封装了一套上层API来调用,不仅优化了性能也减少了程序复杂度。
kotlin
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
typeface = Typeface.createFromFile("custom.ttf")
}
override fun draw(canvas: Canvas) {
cancas.drawText("text", 0, 0, paint)
}
OpenType 字体结构
如果想要一探究竟,可以查看 Microsoft 对 OpenType 介绍的文档,或者直接查看Android Typeface 的 C 实现,这里通过文档来简单介绍一下。
Microsoft, The OpenType Font File
Apple, TrueType Font Tables
OpenType 字体文件是一串含表格格式的数据,字形轮廓等信息是存储在特定的表格中,操作系统可以快速通过字符来快速定位到需要的字形数据来渲染。每个字体文件答大体可以分为三部分:
- 文件头 (Header)
- 描述表目录 (Table Directory)
- 描述表 (Tables)
所有 OpenType 字体都使用 Motorola 风格的字节顺序(Big Endian,大端模式)。什么是大端模式,这里是一种计算机存储数据的方式,没错还有对应的小端模式。
- 大端模式,是指数据的高位字节保存在内存高位地址中,而数据的低位字节保存在内存低位地址中;
- 小端模式,是指数据的高位字节保存在低位地址中,而数据的低位字节保存在高位地址中。
简单来讲就是大端模式是顺序存储,小端模式是倒序存储。例如:
java
int value = 0x12345678;
// value对应的二进制数组为
byte[] buf = new byte[]{0x12, 0x34, 0x56, 0x78};
// 对buf进行存储或传输
地址值 | 大端模式 | 小端模式 |
---|---|---|
addr0 | 0x12 | 0x78 |
addr1 | 0x34 | 0x56 |
addr2 | 0x56 | 0x34 |
addr3 | 0x78 | 0x12 |
而 OpenType 使用的是大端模式也就是按序存储的,我们不用处理字节转换问题。
文件头
其中文件头会存储在文件开头,我们可以通过前几个字节快速确定一个文件是不是 OpenType 字体文件。
类型 | 字段名 | 描述 |
---|---|---|
uint32 | sfntVersion | 0x00010000 or 0x4F54544F ('OTTO') |
uint16 | numTables | 字体表数量 |
uint16 | searchRange | 描述表快速查找范围 |
uint16 | entrySelector | 描述表入口选择 |
uint16 | rangeShift | 范围调整 |
tableRecord | tableRecords[numTables] | 按序存储的每个描述表的信息,字节长度为 tableRecord单位长度 * munTables |
sfntVersion 就是 OpenType 字体文件特有的信息,包含 TrueType 轮廓的 OpenType 字体应使用 0x00010000 ,包含 CFF 数据的 OpenType 字体应使用 0x4F54544F。如果不符合这两个值都不是 OpenType 字体文件 (.TTF 和 .OTF)。
但是 OpenType 字体集除外(.OTC 和 .TTC),字体集使用 ttcf
标识表明是字体集文件。
tableRecord 是描述表结构,下面会介绍
文件头除 tableRecord 以外,共5个字段,共占用 12 个字节。
描述表目录(tableRecord)
类型 | 字段名 | 描述 |
---|---|---|
uint8 * 4 | tableTag | 当前描述表的标识,可以直接按照Unicode来解析为4个长度的字符串表明 |
uint32 | checksum | 当前表的校验位 |
uint32 | offset | 当前表的数据距字体文件开头的偏移量 |
uint32 | length | 当前表的长度 |
tableRecord 共4个字段,单个长度 16 个字节。
描述表
一个正常的字体文件,至少要包含下列这些表结构才可能:
描述表明 | 描述 |
---|---|
cmap | 字符代码到文字序号的映射表 (Character to glyph mapping) |
head | 文件头信息 (Font header) |
hhea | 水平度量头信息 (Horizontal header) |
hmtx | 水平度量信息 (Horizontal metrics) |
maxp | 最大值描述 (Maximum profile) |
name | 名字表 (Naming table) |
OS/2 | OS/2和Windows度量信息 (OS/2 and Windows specific metrics) |
post | 打印机控制 (PostScript information) |
字体结构总览
大体来说一个 OpenType 字体的结构就是:
text
----------------------------- Header -------------------------------
| sfntVersion | numTables | searchRange | entrySelector | rangeShift |
-------------------------- Table Records ---------------------------
| (tableTag | checksum | offset | length) * numTables |
------------------------------ Tables ------------------------------
| Table1 Content | Table2 Content | Table3 Content | ... |
--------------------------------------------------------------------
- 其中 Header 和 Table Records 被称为 Table Directory,下面TTC介绍时会引用
- 所有表必须以四字节边界开始,表之间的任何剩余空间必须用零填充
可以看出,OpenType 字体存储的顺序类似于一本书的结构:有文件标识头,有描述表目录,有描述表内容。
TTC 字体集结构
TTC 字体集是多个字体的集合,一般包含多个字形,例如:将普通字体、斜体和粗体封装在一起,组成一个TTC集合,这个文件的后缀为 .TTC 或 .OTC。
上面讲到我们可以通过前4个字节确定一个文件是不是 OpenType 字体,字体集使用 ttcf
标识表明是字体集文件,转换为16进制也就是 0x74746366 。
所以它的结构如下:
text
-------------------- TTC Header -----------------
| ttcTag | majorVersion | minorVersion | numFonts |
-------------------------------------------------
| Table Directory offset * numFonts |
-------------------------------------------------
| (major 2.0) dsigTag | dsigLength | dsigOffset |
----------------- Table Directorys --------------
| TableDirectory1 | TableDirectory2 | ... |
--------------------- Tables --------------------
| Table1 Content | Table2 Content | ... |
-------------------------------------------------
- dsigTag、dsigLength 和 dsigOffset 为 Version 2.0 时才有的字段。表明存在 DSIG (Digital Signature Table) 表,可以通过字体签名和系统证书结合来验证字体是不是合法的。
- Table Directorys 和 Tables 可能不是连续的,因为它们可以通过 offset 来确定数据位置。
字体文件大体结构就是这样,然后由于每个表的结构存在差异,篇幅比较多,这里留作下次再深入介绍。也可以通过 Font Tables 文档来查看各个表的结构。
总结
- OpenType 是 TrueType 的扩展类型,对现在的计算机来说是相互兼容的。字体文件扩展名 .OTF、.TTF、.OTC 或 .TTC,其中 .OTC 和 .TTC 表示字体集;
- Android 系统中字体存储在
/system/fonts/
目录下, 配置声明在/system/etc/fonts.xml
内,字体解析主要依靠 Typeface; - OpenType 字体文件使用大端模式 存储 ,通过前4个字节就可以确定一个文件是不是 OpenType 字体;
- OpenType 字体存储的顺序类似于一本书的结构:有文件标识头,有描述表目录,有描述表内容;
相关文档
Microsoft, The OpenType Font File
Apple, TrueType Font Tables