字體格式解析筆記整理一:SFNT包裝格式

0.前言

因?yàn)橹皹I(yè)務(wù)線上有一個字體預(yù)覽的需求,所以經(jīng)歷了一次自己從頭開始實(shí)現(xiàn)一個字體文件格式的解析器,實(shí)現(xiàn)的過程中差點(diǎn)沒把我頭給撓禿,以至于成功實(shí)現(xiàn)后還能感覺到頭頂挺涼快的。

因?yàn)閷?shí)現(xiàn)的不容易,所以后面有時(shí)間慢慢的整理一些筆記,給后面對字體格式有解析需求或者有興趣的人保留一些頭發(fā)(禿頂了你們怎么找女朋友,禿頂?shù)氖虑榫徒唤o我了!)。

字體操作相關(guān)的庫在其它語言中是相當(dāng)豐富和完善的,比如Google開發(fā)的庫sfntly(支持Java和C++),并且這個庫在Chromium瀏覽器(Google對Chrome瀏覽器的開源實(shí)現(xiàn))中也用作字體相關(guān)的處理。而因?yàn)槲覀儤I(yè)務(wù)線上的所使用的語言是PHP,而在PHP的生態(tài)下字體操作相關(guān)的庫也有,但是數(shù)量相對稀少,并且大部分的庫已經(jīng)常年沒有更新和進(jìn)行BUG修復(fù)。

1.字體格式

了解一種格式必然是要先了解這個格式的規(guī)范定義,而目前市面上主流的字體格式為TrueType(.tff)和OpenType(.otf),而其中OpenType也可以叫做TrueType2.0,除去OpenType所擴(kuò)展的一些特性外,基本上TrueType和OpenType的大部分定義是一樣的。

因?yàn)門rueType和OpenType這兩個格式都是由Apple和Micrososft一起開發(fā),所以你能分別在Apple和Micrososft的網(wǎng)站上找到相關(guān)完整的格式說明文檔:
Apple關(guān)于TrueType格式的參考文檔
Micrososft關(guān)于OpenType格式的參考文檔
為什么要貼兩份文檔的地址呢,因?yàn)楫?dāng)你發(fā)現(xiàn)其中一個文檔描述不清楚或者看的不是太懂的時(shí)候,可以換另一份作為參考看看,是的,我實(shí)現(xiàn)過程中就經(jīng)常這樣做(畢竟上學(xué)期間英文考試只能靠選擇題來得分)。

2.SFNT包裝格式概述

一般來說使用SFNT包裝格式這個文件就是otf或者ttf的字體格式,但是Apple的平臺上會有所區(qū)分,因?yàn)樵贗OS或者OS X上會使用這個格式來包裝其它類型的字體,而關(guān)于這個的描述Apple的文檔內(nèi)也會有提到:

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字體實(shí)際上都是有很多張表所組成,每張表都會負(fù)責(zé)記錄一些和特定功能相關(guān)的數(shù)據(jù),比如cmap表記錄一種類型的字符編碼和字體形狀對應(yīng)的關(guān)系。而SFNT則是一種組織這些表數(shù)據(jù)以及可擴(kuò)展的格式。

我們可以以一個ttf格式的字體文件為例子,來展示一下SFNT包裝格式的大概樣子:


SFNT包裝格式

上圖就是SFNT的基本結(jié)構(gòu),而字體格式內(nèi)的數(shù)據(jù)都使用大端存儲,所以上圖上所說的uint32實(shí)際上就是無符號大端32位,現(xiàn)在我們首先來解釋一下頭部字段的作用:

類型 名稱 描述
uint32 sfntVersion 字體格式類型和版本
uint16 numTables 這個字體文件內(nèi)有多少張表
uint16 searchRange 用于優(yōu)化搜索查找參考值
uint16 entrySelector 用于優(yōu)化搜索查找參考值
uint16 rangeShift 用于優(yōu)化搜索查找參考值

因?yàn)橐话銇碚f一個字體所包含的表數(shù)量不會特別夸張,所以我們依靠numTables這個值來線性遍歷讀取即可,所以后面的參數(shù)可以只解析出來但不用關(guān)心。而解析的流程實(shí)際上就是按照上圖列出的結(jié)構(gòu)順序讀取即可,而用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'      =>  []
];

當(dāng)讀取完這個頭部后,就得到了這個表結(jié)構(gòu)內(nèi)一共有多少張表,這個時(shí)候在遍歷所有表頭部結(jié)構(gòu),也就是這個結(jié)構(gòu)體:

類型 名稱 描述
uint32 tableTag 表名稱,不足4字節(jié)用空格補(bǔ)充,可直接轉(zhuǎn)為ASCII得到表英文字符名稱
uint32 checkSum 表數(shù)據(jù)的校驗(yàn)和
uint32 offset 這個表數(shù)據(jù)位于這個文件內(nèi)的哪個位置
uint32 length 這個表數(shù)據(jù)的長度

這個結(jié)構(gòu)的讀取次數(shù)取決于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']);
}

我們先把所有表頭全部讀取出來,因?yàn)楸眍^包含了我們需要的每個表在文件內(nèi)的偏移位置以及長度,當(dāng)我們把表頭全部讀取出來以后,就可以用fseek函數(shù)設(shè)置表頭讀取出來的offset,這樣下次fread讀取的時(shí)候就在相關(guān)表所在的位置了,然后我們再根據(jù)表頭的length字段讀取指定的長度的內(nèi)容,這樣就把每個表的數(shù)據(jù)都讀取了出來,方便我們后續(xù)針對各個表在進(jìn)行單獨(dú)的表內(nèi)容解析,這樣就完成了SFNT包裝格式的解析。

這個時(shí)候你再回顧之前所看到的SFNT包裝結(jié)構(gòu)的圖例,結(jié)合代碼就應(yīng)該能大概理解這個格式了。

當(dāng)然,每個表都是有對應(yīng)的作用的,有一些表并不是必須的但有一些表是必須的,下面列出TrueType(OpenType基本一致)所必須包含的表:

Tag 描述
cmap 多種字符編碼對應(yīng)到字體形狀的映射表
glyf 包含每個字體的形狀數(shù)據(jù)
head 字體頭部,包含一些設(shè)置參數(shù)
hhea horizontal header(不知道怎么翻譯,避免歧義直接用原文檔的術(shù)語)
hmtx horizontal metrics(不知道怎么翻譯,避免歧義直接用原文檔的術(shù)語)
loca 記錄每個字體形狀存在于文件內(nèi)的哪個offset上
maxp 記錄字體對于內(nèi)存上的一些需求參數(shù)
name 包含了人類可讀的相關(guān)名稱數(shù)據(jù),比如字體名稱等
post PostScript相關(guān)數(shù)據(jù)

以上就是必須包含的表,其它表的種類因?yàn)樘嗔耍赃@里不一一列出來,感興趣的可以從開頭所貼出來的文檔中了解其它種類的表。

3.總結(jié)

SFNT包裝結(jié)構(gòu)的解析相對來說還是特別簡單的,解析起來沒有太多的難度,而真正讓人頭禿的是對SFNT包裝格式里面包含的表數(shù)據(jù)進(jìn)行解析,比如CMAP表就有CMAP0 ~ CMAP14的規(guī)范定義(可參考文檔定義)。而每個規(guī)范都是為一些特定的字符編碼提供支持,雖然常用的只有CMAP4,但正確實(shí)現(xiàn)解析和生成CMAP4也夠你玩上一整天了。

所以對于后面一些復(fù)雜的表的格式和解析,一篇文章不太容易一次性說明白,所以這里先整理一下SFNT包裝格式的解析,等后面有時(shí)間在慢慢的詳細(xì)整理字體內(nèi)一些關(guān)鍵表(CMAP、GLYF、LOCA等)的格式解析(懶癌晚期)。

因?yàn)樽煮w格式的一些相關(guān)細(xì)節(jié)還是特別多,所以如果有未提到或者說明不詳細(xì)的細(xì)節(jié),我們可以多多交流!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、概念 參考網(wǎng)頁字體Serif和Sans-serif的區(qū)別及瀏覽器字體的設(shè)置CSS Font知識整理總結(jié) 1.F...
    合肥黑閱讀 6,590評論 0 12
  • 學(xué)會使用CSS選擇器熟記CSS樣式和外觀屬性熟練掌握CSS各種選擇器熟練掌握CSS各種選擇器熟練掌握CSS三種顯示...
    七彩小鹿閱讀 6,452評論 2 66
  • 最近興致上來,就想更換了那Blog標(biāo)題字體(漢字的);網(wǎng)上搜索了一番,發(fā)現(xiàn)蘇新詩柳繁體這款甚合我心;然后就著手搞將...
    晚晴幽草閱讀 2,559評論 1 7
  • @Ryekee:最近在看關(guān)于字體渲染技術(shù)的時(shí)候在SmashingMagazine上看到了這篇文章,覺得算是對 Wi...
    Ryekee閱讀 14,266評論 3 52
  • 一首箏曲暗傷情,夜闌人靜獨(dú)自憐。倚窗又見彎明月。 無奈故人已遠(yuǎn)去,多年情誼化塵煙。春花飄落意決絕。
    夏諾xn閱讀 1,102評論 63 79

友情鏈接更多精彩內(nèi)容