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

相关推荐
速盾cdn23 分钟前
速盾:网页游戏部署高防服务器有什么优势?
服务器·前端·web安全
小白求学125 分钟前
CSS浮动
前端·css·css3
什么鬼昵称26 分钟前
Pikachu-csrf-CSRF(POST)
前端·csrf
golitter.43 分钟前
Vue组件库Element-ui
前端·vue.js·ui
golitter.1 小时前
Ajax和axios简单用法
前端·ajax·okhttp
沐言人生1 小时前
Android10 Framework—Init进程-9.服务端属性值初始化
android·android studio·android jetpack
雷特IT1 小时前
Uncaught TypeError: 0 is not a function的解决方法
前端·javascript
追光天使2 小时前
【Mac】和【安卓手机】 通过有线方式实现投屏
android·macos·智能手机·投屏·有线
长路 ㅤ   2 小时前
vite学习教程02、vite+vue2配置环境变量
前端·vite·环境变量·跨环境配置
亚里士多没有德7752 小时前
强制删除了windows自带的edge浏览器,重装不了怎么办【已解决】
前端·edge