WOFF 字型檔案格式
Web Open Font Format——把 TTF/OTF 壓縮起來給網頁用的外殼。
- MIME 類型
- font/woff
- 副檔名
- .woff
- 分類
- 字型
Magic number(檔案簽章)
| 0x00000000 | 774F4646 | wOFF | 'wOFF' tag |
關於 WOFF 字型格式
WOFF 不是它自己的字型格式——它是個容器。它拿一份完整的 TrueType 或 OpenType 字型,把每一張表分別用 zlib 壓縮,前面加上 44 個位元組的標頭和一份新的表目錄,而還原出來的字型和放進去的那份逐位元組等價。推動它的有兩件事:體積省下來,以及 WOFF 一眼就看得出是網頁字型而不是可安裝的桌面檔案——光這一點就足以說服字體公司願意替 @font-face 授權。它還帶著一個選用的壓縮 XML 中繼資料區塊,設計用途正是放那些授權資訊。
檔頭結構,一個位元組一個位元組看
| 位移 | 長度 | 欄位 | 意義 |
|---|---|---|---|
| 0 | 4 | signature | 77 4F 46 46,ASCII 的 wOFF,w 是小寫。這種大小寫混用是刻意的,也值得記住,因為在 hex 檢視器裡用不分大小寫的方式搜尋 WOFF,也會命中中繼資料區塊裡的文字和任何內嵌的版權字串。 |
| 4 | 4 | flavor | 裡面那份字型的 sfntVersion:TrueType 外框是 00 01 00 00,CFF 是 4F 54 54 4F。.ttf 或 .otf 在位移 0 帶的就是這個,只是往後移了 4 個位元組;它告訴你外殼拆掉之後手上會是什麼。 |
| 8 | 4 | length | WOFF 檔案的總位元組數。它必須等於實際檔案長度,所以光看前 12 個位元組就能判斷下載有沒有被截斷,完全不用解壓縮、也不用懂字型——這種免費的檢查,格式裡少之又少。 |
| 12 | 2 | numTables | 表目錄有幾筆。每筆剛好 20 個位元組,所以這個數字同時也告訴你壓縮資料從哪裡開始。 |
| 14 | 2 | reserved | 必須是零。瀏覽器在把網頁字型交給點陣化引擎之前都會先過一遍消毒器,而「沒人會讀的欄位裡出現非零值」正是那種讓檔案在網頁上被直接拒收、桌面工具卻開得很開心的原因之一。 |
| 16 | 4 | totalSfntSize | 還原後字型的大小,包含它的目錄與表與表之間的填補。把它和位移 8 的 length 對照,不用解壓縮就得到壓縮比;一般文字字體大概略優於二比一。 |
| 20 | 4 | majorVersion and minorVersion | 位移 20 和 22 兩個 16 位元數字。它們標的是字型的版本、不是 WOFF 格式的版本,通常是從來源字型的 head 表複製過來或與之對齊;你多半會看到 1.0 和 1.1。 |
| 24 | 12 | metaOffset, metaLength, metaOrigLength | 位移 24、28、32 三個 32 位元欄位,描述一段選用的 zlib 壓縮 XML 中繼資料區塊——授權、廠商與描述本來就是要放在那裡的。沒有的時候三個都是 0,而實務上那才是常態。 |
| 36 | 8 | privOffset and privLength | 第二個選用區塊,位在位移 36 和 40,保留給字型作者放任何他想放的東西,沒有任何規範。通常也是 0,而非零的話,在追查字型來歷時很值得看一眼。 |
| 44 | variable | Table directory | 每張表 20 個位元組,緊接在標頭後面開始:標籤、在 WOFF 檔案裡的位移、壓縮後長度、原始長度,以及原始未壓縮表的檢查碼。所以第一張表的壓縮資料從 44 加上 20 乘 numTables 的地方開始,這個算術在 hex 檢視器前面心算就做得完。 |
| variable | variable | Compressed table data | 每張表各一條獨立的 zlib 資料流,依目錄順序排列。當壓縮某張表反而不會變小時,格式允許原封不動存放,你可以在目錄裡看出來——壓縮後長度等於原始長度的那些項目,幾十個位元組的小表很常見。 |
怎麼跟長得像的格式分開
| 容易搞混的 | 怎麼分 |
|---|---|
| WOFF2 字型 | 位移 0 的 77 4F 46 46 對上 77 4F 46 32,而之後的一切也都不一樣:WOFF2 的標頭是 48 個位元組而不是 44,因為它在位移 20 多了一個 totalCompressedSize 欄位;它的目錄記錄是長度不定的,不是固定 20 個位元組;它的主體是整個字型一條 Brotli 資料流,而不是每張表一條 zlib。 |
| TrueType 字型 | WOFF 裡面那份字型就是 TrueType 或 OpenType 字型,內容沒有任何改動,只是辨識它的那 4 個位元組從位移 0 搬到了位移 4。只檢查位移 0 的讀取端看到 wOFF、報一個未知字型;連位移 4 也檢查的讀取端,就能告訴你解壓縮之後會拿到哪一種。 |
| GZIP | 兩者都帶著 zlib 壓縮的內容,也都不能拿通用解壓縮工具對著位移 0 就開始解。gzip 檔案是 1F 8B,只有一條資料流、而且從很前面就開始;WOFF 裝的是好幾條資料流,起點只有目錄知道,而且每一條前面都沒有自己的標頭。把 WOFF 改名成 .gz 再跑 gunzip 會立刻失敗,而那個失敗完全說明不了字型本身好不好。 |
常見的壞法
- length 欄位和檔案對不上。位移 8 那 4 個位元組寫的是整個檔案大小,所以下載被截斷或被補過東西,在解壓縮任何內容之前就能在 hex 檢視器裡證明。這是第一個該跑的檢查,而且光靠它就解決了相當高比例的網頁字型載入失敗。
- 字型網址底下其實是一頁錯誤頁。一個 404、一次登入轉址或一則流量限制通知被存成 .woff,用文字編輯器打開就是 HTML,而瀏覽器主控台報的是「無法解碼下載的字型」外加一個消毒器解析錯誤。那句訊息完全沒有暗示這個檔案其實是一個網頁,所以特別浪費時間。
- 被瀏覽器的消毒器拒收。瀏覽器比桌面字型工具嚴格得多:保留欄位非零、欄位超出允許範圍、結構互相重疊,都會讓一個在你自己機器上裝得乾乾淨淨的檔案在網頁上被拒。字型在本機正常、在頁面上不行,通常就是這個,不是伺服器的問題。
- 單一張表壞掉。因為每張表都有自己的 zlib 資料流,一個位元組被翻掉就只殺掉其中一張,其他完好無缺。於是出現一種怪異的失敗:字型載入了、度量正確、字形也畫得出來,卻掉了字距微調或垂直度量——就看是哪一張表解不開。
- 文字模式傳輸。會轉換換行的部署步驟會改寫壓縮資料流裡的 0D 0A,而那些資料不是文字、撐不過這一關。標頭和目錄毫髮無傷地通過,所以檔案看起來一直都像字型,直到 inflate 失敗的那一刻。
這個格式現在還在用嗎
從大約 2012 年到 WOFF2 追上來之前,WOFF 就是那個網頁字型格式,而它現在的工作是相容性而不是交付。近十年發行的每個瀏覽器都偏好 WOFF2,同樣內容小上大約三分之一,所以現代的 @font-face 規則會把 WOFF2 排第一、WOFF 只當備援——而且越來越常連備援都不寫了,因為需要備援的那些瀏覽器早就從分析資料裡消失了。WOFF 現在還會冒出來的地方,是幾年前做好之後就沒重建過的封裝軟體、說明文件包與佈景主題,以及那些比 Brotli 更早的工具鏈。它真正的成就從來不是技術上的。把字型包在一個明顯不能安裝的東西裡,再帶上一個能讓授權隨檔案一起走的中繼資料區塊,這件事化解了字體公司當年完全不放行網頁使用的爭議,而此後每一種網頁字型都靠著那次和解過日子。
在這個站上打開 WOFF 字型檔
這些全部在你的瀏覽器裡跑,檔案不會離開你的電腦。
相關格式
容易和 WOFF 字型搞混的格式,可以比對它們的開頭位元組。
| 格式 | 開頭位元組 | 副檔名 |
|---|---|---|
| WOFF2 字型 | 77 4F 46 32 | .woff2 |
| TrueType 字型 | 00 01 00 00 | .ttf |
| OpenType 字型 | 4F 54 54 4F | .otf |