字体格式解析笔记整理一:SFNT包装格式

0.前言

因为之前业务线上有一个字体预览的需求,所以经历了一次自己从头开始实现一个字体文件格式的解析器,实现的过程中差点没把我头给挠秃,以至于成功实现后还能感觉到头顶挺凉快的。

因为实现的不容易,所以后面有时间慢慢的整理一些笔记,给后面对字体格式有解析需求或者有兴趣的人保留一些头发(秃顶了你们怎么找女朋友,秃顶的事情就交给我了!)。

字体操作相关的库在其它语言中是相当丰富和完善的,比如Google开发的库sfntly(支持Java和C++),并且这个库在Chromium浏览器(Google对Chrome浏览器的开源实现)中也用作字体相关的处理。而因为我们业务线上的所使用的语言是PHP,而在PHP的生态下字体操作相关的库也有,但是数量相对稀少,并且大部分的库已经常年没有更新和进行BUG修复。

1.字体格式

了解一种格式必然是要先了解这个格式的规范定义,而目前市面上主流的字体格式为TrueType(.tff)和OpenType(.otf),而其中OpenType也可以叫做TrueType2.0,除去OpenType所扩展的一些特性外,基本上TrueType和OpenType的大部分定义是一样的。

因为TrueType和OpenType这两个格式都是由Apple和Micrososft一起开发,所以你能分别在Apple和Micrososft的网站上找到相关完整的格式说明文档:
Apple关于TrueType格式的参考文档
Micrososft关于OpenType格式的参考文档
为什么要贴两份文档的地址呢,因为当你发现其中一个文档描述不清楚或者看的不是太懂的时候,可以换另一份作为参考看看,是的,我实现过程中就经常这样做(毕竟上学期间英文考试只能靠选择题来得分)。

2.SFNT包装格式概述

一般来说使用SFNT包装格式这个文件就是otf或者ttf的字体格式,但是Apple的平台上会有所区分,因为在IOS或者OS X上会使用这个格式来包装其它类型的字体,而关于这个的描述Apple的文档内也会有提到:

Apple makes a distinction between a "TrueType font" (which refers to a particular font outline definition technology) and an "sfnt-housed font," which refers to any font which uses the same packaging format as a TrueType font: that is, it uses the same directory structure and the same table format and meaning for any tables present

This is an important distinction, because Apple supports other varieties of sfnt-housed font on OS X and iOS, most notably bitmap only fonts and OpenType fonts. Informally, people often to any sfnt-housed font as a "TrueType font," but this is strictly speaking inaccurate.

OpenType和TrueType字体实际上都是有很多张表所组成,每张表都会负责记录一些和特定功能相关的数据,比如cmap表记录一种类型的字符编码和字体形状对应的关系。而SFNT则是一种组织这些表数据以及可扩展的格式。

我们可以以一个ttf格式的字体文件为例子,来展示一下SFNT包装格式的大概样子:


SFNT包装格式

上图就是SFNT的基本结构,而字体格式内的数据都使用大端存储,所以上图上所说的uint32实际上就是无符号大端32位,现在我们首先来解释一下头部字段的作用:

类型 名称 描述
uint32 sfntVersion 字体格式类型和版本
uint16 numTables 这个字体文件内有多少张表
uint16 searchRange 用于优化搜索查找参考值
uint16 entrySelector 用于优化搜索查找参考值
uint16 rangeShift 用于优化搜索查找参考值

因为一般来说一个字体所包含的表数量不会特别夸张,所以我们依靠numTables这个值来线性遍历读取即可,所以后面的参数可以只解析出来但不用关心。而解析的流程实际上就是按照上图列出的结构顺序读取即可,而用PHP代码解析这个SFNT头部部分就如下:

function read_uint32($fd)
{
    $data = fread($fd, 4);
    return unpack('NN', $data)['N'];
}

function read_uint16($fd)
{
    $data = fread($fd, 2);
    return unpack('nn', $data)['n'];
}

$fd = fopen('微软雅黑.ttf', 'r');

$sfnt = [
    'sfntVersion'    =>  read_uint32($fd),
    'numTables'      =>  read_uint16($fd),
    'searchRange'    =>  read_uint16($fd),
    'entrySelector'  =>  read_uint16($fd),
    'rangeShift'     =>  read_uint16($fd),
    'tableHeaders'   =>  [],
    'tableData'      =>  []
];

当读取完这个头部后,就得到了这个表结构内一共有多少张表,这个时候在遍历所有表头部结构,也就是这个结构体:

类型 名称 描述
uint32 tableTag 表名称,不足4字节用空格补充,可直接转为ASCII得到表英文字符名称
uint32 checkSum 表数据的校验和
uint32 offset 这个表数据位于这个文件内的哪个位置
uint32 length 这个表数据的长度

这个结构的读取次数取决于SFNT头部中的numTables字段,PHP的解析代码如下:

for ($i = 0; $i < $sfnt['numTables']; $i++) {

    $tableHeader = [
        'tag'       =>  read_uint32($fd),
        'checkSum'  =>  read_uint32($fd),
        'offset'    =>  read_uint32($fd),
        'length'    =>  read_uint32($fd),
    ];
    $sfnt['tableHeaders'][$tableHeader['tag']] = $tableHeader;
}

foreach ($sfnt['tableHeaders'] as $tableTag => $tableHeader) {
    fseek($fd, $tableHeader['offset'], SEEK_SET);
    $sfnt[$tableTag] = fread($fd, $tableHeader['length']);
}

我们先把所有表头全部读取出来,因为表头包含了我们需要的每个表在文件内的偏移位置以及长度,当我们把表头全部读取出来以后,就可以用fseek函数设置表头读取出来的offset,这样下次fread读取的时候就在相关表所在的位置了,然后我们再根据表头的length字段读取指定的长度的内容,这样就把每个表的数据都读取了出来,方便我们后续针对各个表在进行单独的表内容解析,这样就完成了SFNT包装格式的解析。

这个时候你再回顾之前所看到的SFNT包装结构的图例,结合代码就应该能大概理解这个格式了。

当然,每个表都是有对应的作用的,有一些表并不是必须的但有一些表是必须的,下面列出TrueType(OpenType基本一致)所必须包含的表:

Tag 描述
cmap 多种字符编码对应到字体形状的映射表
glyf 包含每个字体的形状数据
head 字体头部,包含一些设置参数
hhea horizontal header(不知道怎么翻译,避免歧义直接用原文档的术语)
hmtx horizontal metrics(不知道怎么翻译,避免歧义直接用原文档的术语)
loca 记录每个字体形状存在于文件内的哪个offset上
maxp 记录字体对于内存上的一些需求参数
name 包含了人类可读的相关名称数据,比如字体名称等
post PostScript相关数据

以上就是必须包含的表,其它表的种类因为太多了,所以这里不一一列出来,感兴趣的可以从开头所贴出来的文档中了解其它种类的表。

3.总结

SFNT包装结构的解析相对来说还是特别简单的,解析起来没有太多的难度,而真正让人头秃的是对SFNT包装格式里面包含的表数据进行解析,比如CMAP表就有CMAP0 ~ CMAP14的规范定义(可参考文档定义)。而每个规范都是为一些特定的字符编码提供支持,虽然常用的只有CMAP4,但正确实现解析和生成CMAP4也够你玩上一整天了。

所以对于后面一些复杂的表的格式和解析,一篇文章不太容易一次性说明白,所以这里先整理一下SFNT包装格式的解析,等后面有时间在慢慢的详细整理字体内一些关键表(CMAP、GLYF、LOCA等)的格式解析(懒癌晚期)。

因为字体格式的一些相关细节还是特别多,所以如果有未提到或者说明不详细的细节,我们可以多多交流!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 一、概念 参考网页字体Serif和Sans-serif的区别及浏览器字体的设置CSS Font知识整理总结 1.F...
    合肥黑阅读 6,067评论 0 12
  • 学会使用CSS选择器熟记CSS样式和外观属性熟练掌握CSS各种选择器熟练掌握CSS各种选择器熟练掌握CSS三种显示...
    七彩小鹿阅读 6,303评论 2 66
  • 最近兴致上来,就想更换了那Blog标题字体(汉字的);网上搜索了一番,发现蘇新詩柳繁體这款甚合我心;然后就着手搞将...
    晚晴幽草阅读 2,353评论 1 8
  • @Ryekee:最近在看关于字体渲染技术的时候在SmashingMagazine上看到了这篇文章,觉得算是对 Wi...
    Ryekee阅读 13,410评论 1 52
  • 一首筝曲暗伤情,夜阑人静独自怜。倚窗又见弯明月。 无奈故人已远去,多年情谊化尘烟。春花飘落意决绝。
    夏诺xn阅读 1,036评论 63 79