字體格式解析筆記整理一: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)的不容易茫陆,所以后面有時間慢慢的整理一些筆記仗岖,給后面對字體格式有解析需求或者有興趣的人保留一些頭發(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í)現(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)一共有多少張表奕筐,這個時候在遍歷所有表頭部結(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讀取的時候就在相關(guān)表所在的位置了翎猛,然后我們再根據(jù)表頭的length字段讀取指定的長度的內(nèi)容胖翰,這樣就把每個表的數(shù)據(jù)都讀取了出來,方便我們后續(xù)針對各個表在進(jìn)行單獨(dú)的表內(nèi)容解析切厘,這樣就完成了SFNT包裝格式的解析萨咳。

這個時候你再回顧之前所看到的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包裝格式的解析嗡综,等后面有時間在慢慢的詳細(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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市锈至,隨后出現(xiàn)的幾起案子晨缴,更是在濱河造成了極大的恐慌,老刑警劉巖裹赴,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喜庞,死亡現(xiàn)場離奇詭異诀浪,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)延都,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門雷猪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人晰房,你說我怎么就攤上這事求摇。” “怎么了殊者?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵与境,是天一觀的道長。 經(jīng)常有香客問我猖吴,道長摔刁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任海蔽,我火速辦了婚禮共屈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘党窜。我一直安慰自己拗引,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布幌衣。 她就那樣靜靜地躺著矾削,像睡著了一般。 火紅的嫁衣襯著肌膚如雪豁护。 梳的紋絲不亂的頭發(fā)上哼凯,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音择镇,去河邊找鬼挡逼。 笑死,一個胖子當(dāng)著我的面吹牛腻豌,可吹牛的內(nèi)容都是我干的家坎。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼吝梅,長吁一口氣:“原來是場噩夢啊……” “哼虱疏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起苏携,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤做瞪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體装蓬,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡著拭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了牍帚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片儡遮。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖暗赶,靈堂內(nèi)的尸體忽然破棺而出鄙币,到底是詐尸還是另有隱情,我是刑警寧澤蹂随,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布十嘿,位于F島的核電站,受9級特大地震影響岳锁,放射性物質(zhì)發(fā)生泄漏绩衷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一激率、第九天 我趴在偏房一處隱蔽的房頂上張望唇聘。 院中可真熱鬧,春花似錦柱搜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至表制,卻和暖如春健爬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背么介。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工娜遵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人壤短。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓设拟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親久脯。 傳聞我的和親對象是個殘疾皇子纳胧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容

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