OpenType字体文件结构解析

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 字体文件是一串含表格格式的数据,字形轮廓等信息是存储在特定的表格中,操作系统可以快速通过字符来快速定位到需要的字形数据来渲染。每个字体文件答大体可以分为三部分:

  1. 文件头 (Header)
  2. 描述表目录 (Table Directory)
  3. 描述表 (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

相关推荐
Sokach3865 分钟前
vue3引入tailwindcss 4.1
前端·css
Wgllss10 分钟前
Kotlin 享元设计模式详解 和对象池及在内存优化中的几种案例和应用场景
android·架构·android jetpack
云水边16 分钟前
vue模版中.gitignore和.prettierrc功能区分
前端
尝尝你的优乐美18 分钟前
封装那些Vue3.0中好用的指令
前端·javascript·vue.js
敲代码的彭于晏21 分钟前
localStorage 不够用?试试 IndexedDB !
前端·javascript·浏览器
chxii24 分钟前
5.4 4pnpm 使用介绍
前端·javascript·vue.js
好好好明天会更好32 分钟前
Vue 中 slot 的常用场景有哪些
前端·vue.js
奔赴_向往1 小时前
【qiankun 踩坑】路由切换回来,子应用 Vuex Store 数据居然还在
前端
sorryhc1 小时前
【AI解读源码系列】ant design mobile——Image图片
前端·javascript·react.js
老猴_stephanie1 小时前
Sentry On-Premise 21.7 问题排查与处理总结
前端