文字渲染的那些事(一)字型是如何儲存的?

doodlewind發表於2018-12-22

在現代生活裡,我們幾乎每天都會和螢幕上的文字打交道——文字看起來是如此平凡,以至於不少與 UI 相關的專業人士都對其下的複雜性知之甚少。這個系列旨在以開發者的角度,介紹一些從文字的二進位制資料到畫素之間流程的科普知識,希望對感興趣的同學能有一些啟發。

字型的標準與格式

喜歡折騰系統的同學,對於常見的字型格式肯定不會陌生:Windows 系統長久以來,在 C:\Windows\Fonts 裡面就放著一大堆 TTF 格式的字型檔案。相應地,在 macOS 的 /Library/Fonts 目錄裡,也有一堆字型:不過這裡除了 TTF 之外,還有字尾名為 TTC 和 OTF 的格式。它們又有什麼關係呢?

有個有趣的問題,那就是為什麼 TTF 格式字型在 macOS 和 Windows 上都能通用呢?這裡其實藏著一段微軟與蘋果的 py 交易史:在 80 年代,Adobe 開發出了基於向量的私有字型格式 Type 1,以及列印語言 PostScript(在 PDF 格式裡就能看到)。向量字型比起當時的點陣字型,那妥妥的就是步兵和騎兵的區別啊:

文字渲染的那些事(一)字型是如何儲存的?

Adobe 雖然活好,但由於一些非技術因素(錢)的問題,蘋果和微軟決定另起爐灶。蘋果開發出了向量字型標準 TrueType,而微軟則開發出了 PostScript 的替代品 TrueImage。這兩項技術雖然在 Mac 和 Windows 之間互相授權,但真正落地成為事實標準的只有蘋果搞的 TrueType,這對應的就是 TTF 字型格式了。

知道了 TTF 代表著 TrueType Font 之後,其它的格式都可以舉一反三地推出來:

  • 怎麼把一堆 TTF 字型全家桶打包釋出呢?我們來個 Collection(合集)吧——於是有了 TTC 格式。
  • 大佬們不要再打啦,我們一起合作開放吧——於是有了 OpenType 的 OTF 格式。
  • 我們搞 Web 最在乎體積了,你們這些玩意都太大了不行啊——於是有了 WOFF 格式。

當然了,光知道字尾名,跟精通 Java/C++ 的拼寫沒啥區別。我們不妨來看看,字型檔案的裡面都藏著些什麼呢?

小探 TTF 檔案

很多字型格式的規範文件中,都會強調字型檔案是由構成的。喵喵喵?這裡說的表是 Excel 那樣的表格嗎?開啟一個 TTF 格式的字型檔案,你的第一印象恐怕和表格很難沾上邊:

文字渲染的那些事(一)字型是如何儲存的?

說好的幾行幾列呢?不過,表格暗示著一種相對規整的資料結構。眼尖的同學也許已經注意到了,上面的資料最右一列都是一組四個字母的合集。這並非偶然,依據的是 TTF 格式的規範。

在繼續介紹它們的具體含義之前,我們不妨考慮這樣的一個問題:怎麼樣為設計一種符合下面這些需求的資料格式呢?

  • 你有多種不同的欄位需要儲存,每種資料有固定的格式,但長度都是可變的。
  • 需要儲存的欄位種類可能不同,也有可能在日後擴充套件出一些新欄位。這時應該能夠向上和向下相容。
  • 無需遍歷整個檔案,就應該能夠獲知欄位的基本資訊(位置、長度等)。
  • 資料體積需要儘量小,還需要支援對內容完整性的檢驗。

現在應用層開發中流行的 JSON 格式,光是在體積儘量小這一項上就會被首先幹掉。而 TTF 規範則給出了一種在設計資料格式規範時,可供參考的工程實踐:

  • 給所有的欄位取個四個字母的唯一名字,它們各自的內容都是一段連續的二進位制資料。
  • 在檔案頭部,首先儲存一張「表達整體表結構」的表。在其中指定有多少種不同欄位,以及它們的長度、起始位置等資訊。這張表叫做 Offset 表。
  • 緊接在這張表之後,逐段將這些欄位表的內容拼接起來,就獲得了最終的 TTF 格式字型。

讓我們來看看這種設計是如何解決上面的這些需求的吧:

  • 用於儲存字型資訊的各個表,其長度和排列順序都完全自由(是不是有些類似 Map 結構呢)。
  • 欄位種類和後續擴充套件都沒有相容問題,解析程度可以從 Offset 表中判斷自己對資料的支援程度。
  • 在 Offset 表中,各欄位資料的偏移量和長度都可以直接獲知。
  • 各個欄位資料都以約定的二進位制形式儲存,Offset 表中還儲存了它們的校驗和,作為完整性判斷的基本依據。

舉個簡單的例子就能說明二進位制資料結構的緊湊性。例如,在表達字型是否為粗體、斜體、等寬等後設資料的時候,JSON 格式對每個狀態,都需要約定好一個形如 xxx: true 的欄位,這至少需要五個位元組。而基於位運算的約定,在一個位元組的 8 位中就可以儲存 8 個這樣 true|false 的布林型別變數,往往還留有冗餘。在需要區分儲存不同精度資料的時候,它也有著得天獨厚的優勢。所以說,在需要構建專有的資料結構的時候,TTF 這種表驅動的設計還是有一定的參考價值的。另外,在解析這樣的二進位制格式的時候,傳統指令式程式設計的控制流也會相當趁手:別被社群裡浮躁的聲音淹沒了,學好真正適用於不同場景的技術吧。

回到最初的話題,字型裡面儲存著哪些表達不同內容的表呢?這裡推薦 Typr.js 這個非常簡單的 Web 工具,可以點開即用,大概是這樣:

文字渲染的那些事(一)字型是如何儲存的?

看到了解析出的各個表內容了嗎?它們之中就儲存著從二進位制位元到螢幕畫素的關鍵資訊。暫時到此為止,我們介紹了字型檔案的格式與解析它的基本方式。但怎樣基於字形資料來渲染出文字呢?讓我們下一篇見吧(如果有的話)

P.S.1 如果希望對字型的資料結構有更進一步的瞭解,這篇科普性的文章是遠遠不夠的,不妨從這篇正式的 TrueType Reference Manual 文件開始吧。注意,這篇蘋果的文件一開頭就甩出了個到微軟官網的連結,這在其他場合恐怕很難看到了……

P.S.2 我們的前端團隊非常歡迎有志於對「渲染」這件事追根究底的同學,感興趣請郵件 xuebi at gaoding.com

相關文章