有趣的字体文件

字体历史

话说在计算机市场刚刚开辟的时候,字体市场 Adobe 一家独大,通过贩卖格式认证赚了很多钱,但是苹果想白嫖 Adobe 自然不干。

于是苹果公司另起炉灶搞起了自己的 TrueType,但是当时已经有了一大批 Adobe 付费用户,自然没必要支持苹果。

然后苹果拉起了同盟,微软,两人情投意合,眉来眼去一起搞起了字体协议。

两人联手把 Adobe 搞的不行,目前市场上最常见的就是 TrueType 的字体格式了。这场没有硝烟的战争最后赢在了想白嫖但是没成功的人身上。

两家分别在自己的字体文档外链对方的文档地址,惺惺相惜。

TrueType 的字体文件存储格式

如果刚看字体文件的官方文档对于很少基础二进制的人来说会有那么一点点陌生。

首先字体文件是用表进行字体文件描述的。

按照文档字段约束读取描述文件信息。

首先我们需要用到的 api 是 DataView,DataView 是一个可以方便读取二进制的对象。

详情可查看MDN DataVuew,浏览器兼容性很好。

比如对于下面一段字体信息我们应该怎么做

  Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
00000000: 00 01 00 00 00 0E 00 80 00 03 00 60 4F 53 2F 32    ...........`OS/2

对于单个字体的文件

按照文档约定从文件第 0 位开始的

TypeNameDescription
uint32sfntVersion0x00010000 or 0x4F54544F (‘OTTO’) — see below.
uint16numTablesNumber of tables.
uint16searchRangeMaximum power of 2 less than or equal to numTables, times 16 ((2floor(log2(numTables))) * 16, where “” is an exponentiation operator).
uint16entrySelectorLog2 of the maximum power of 2 less than or equal to numTables (log2(searchRange/16), which is equal to floor(log2(numTables))).
uint16rangeShiftnumTables times 16, minus searchRange ((numTables * 16) - searchRange).

所以我们的版本信息读取代码就是

// 4 sfntVersuin 2 numTables 2 searchRange 2 entrySelector 2 rangeShift
const tableDirectory = new DataView(ttfArrayBuffer, 0, 4 + 2 + 2 + 2 + 2);
let offset = 0;
let sfntVersion = view1.getUint32(offset);
offset += 4;
let numTables = view1.getUint16(offset);
offset += 2;
let searchRange = view1.getUint16(offset);
offset += 2;
let entrySelector = view1.getUint16(offset);
offset += 2;
let rangeShift = view1.getUint16(offset);

这样我们就可以读取字体信息了。

字形的查找

字体的查找

对于一个 unicode 我们如何找到它对应的字体形状呢。

首先字体是以表存储的,每个表都代表不同的信息。

字体到字形映射是通过 cmap 进行映射的。所以我们需要在 cmap 查找到当前平台对应的子表

然后通过 cmap 子表不同的 format 规则 gid 字形 id

获取的 SVG 或者 CFF 或者 glyf 表的位置

然后就是字体渲染了。

具体处理逻辑很绕,有兴趣的可以看看一个简单的 font 处理库的源码Typr.U.js#L61

字体文件应用

字体优化

无论是移动端用户还是 PC 端用户,如果上了字体必然要做字体优化。

面对一个几 M 大小的字体文件,对于用户可能页面信息看完了,也没记载出来,所以很少有人用网络加载自定义字体。

不过如果场景固定(文案不会变)我们就能对其进行字体抽离,然后生成新的字体文件,只包含我们需要的,字体文件的大小将大大减少。

一方面可以将压缩文件上传cdn

另一方面我们可以直接转化成 base64 通过@font-face{url(base64)}去加载

字体加密

我们换一种思路,既然屏幕显示是根据 unicode 去查找对应的显示字形。

比如

// 有一个字母A 的unicode 编码是0x41
0x41 -> A
// 那么我们修改成
0x7b -> A

我们提供我们加密后的字体文件,如果他复制粘贴到别的地方就会变成{

这样如果不用我们的字体文件,那么 shi yong fang 使用方就会一脸蒙蔽。

行文仓促,如有纰漏,欢迎指出。