WOFF2 字型檔案格式
Web Open Font Format 2——WOFF 的後繼者,改用 Brotli 壓縮。
- MIME 類型
- font/woff2
- 副檔名
- .woff2
- 分類
- 字型
Magic number(檔案簽章)
| 0x00000000 | 774F4632 | wOF2 | 'wOF2' tag |
關於 WOFF2 字型格式
WOFF2 維持 WOFF 的外形,把底下的東西全部換掉。標頭長到 48 個位元組,好放進壓縮主體的大小;表目錄變成長度不定的結構,常見的表標籤用單一個位元組表示;每張表各一條 zlib 資料流換成整個字型一條 Brotli 資料流;而 glyf 與 loca 表會先過一次可逆轉換,把外框重新編碼成 Brotli 壓得好很多的形狀。還原出來的字型仍然等價於放進去的那份,但和 WOFF 不同,不把整份解壓縮就什麼都讀不到。
檔頭結構,一個位元組一個位元組看
| 位移 | 長度 | 欄位 | 意義 |
|---|---|---|---|
| 0 | 4 | signature | 77 4F 46 32,ASCII 的 wOF2。和 WOFF 的標籤在同一個位置、只差一個字元,正是那種會讓草率的字串比較配到錯格式的近似撞號。 |
| 4 | 4 | flavor | 裡面那份字型的 sfntVersion:TrueType 外框是 00 01 00 00,CFF 是 4F 54 54 4F。意義與位置都和 WOFF 那個欄位相同,而且它是前 48 個位元組裡唯一在講字型、而不是在講外殼的地方。 |
| 8 | 4 | length | WOFF2 檔案的總大小。和 WOFF 一樣,它必須和磁碟上的檔案完全相符,所以你得到一個不花成本、也不需要 Brotli 解碼器的截斷檢查。 |
| 12 | 2 | numTables | 目錄記錄的筆數。和 WOFF 不同,它不會告訴你資料從哪裡開始,因為記錄的長度是不定的;你得一筆一筆走過去。 |
| 14 | 2 | reserved | 必須是零,而且瀏覽器消毒器會強制。這裡出現別的值,在網頁上是快速拒收,在多數離線工具裡則是被默默忽略——這是「同一個字型在頁面裡和在桌面上表現不同」很好的例子。 |
| 16 | 4 | totalSfntSize | 還原後 sfnt 字型的大小,解碼器用它一次配置好輸出緩衝區。拉丁文字字體相對於位移 8 的 length,接近三比一的比例很平常。 |
| 20 | 4 | totalCompressedSize | Brotli 資料流的長度。這是 WOFF 沒有的欄位,也是標頭從 44 變成 48 個位元組的原因,更是唯一能讓讀取端不解壓縮任何東西就找到壓縮主體結尾的數字。 |
| 24 | 4 | majorVersion and minorVersion | 位移 24 和 26 的兩個 16 位元數字,標的是字型而不是格式的版本,和 WOFF 完全一樣,只是因為前面多了一個欄位而往後移了 4 個位元組。用 WOFF 解析器去讀 WOFF2,從這裡開始就會產出一堆看起來很像樣的胡說八道,這比乾脆被拒收更難纏。 |
| 28 | 12 | metaOffset, metaLength, metaOrigLength | 選用的壓縮 XML 中繼資料區塊,由位移 28、32、36 三個 32 位元欄位描述。通常全是 0。它存在時是和字型主體分開壓縮的,所以不用碰主資料流就讀得到。 |
| 40 | 8 | privOffset and privLength | 選用的私有資料區塊,位在位移 40 和 44,把 48 位元組的標頭收尾。沒有任何規範,通常是 0;非零代表做這個字型的人在檔案裡塞了自己的東西。 |
| 48 | variable | Table directory | 長度不定的記錄,所以和其他 sfnt 衍生格式都不同,沒有任何算術能算出第二筆的位址。每筆記錄以一個旗標位元組開頭,它的低 6 位元是一份常見表標籤固定清單的索引——值 63 代表後面接一個字面的 4 位元組標籤——最高 2 位元則帶著轉換版本。後面的長度以每位元組 7 位元儲存、最高位元代表還有下一個,所以整筆記錄可以短到只有 2 個位元組。 |
| variable | variable | Brotli stream | 所有表依目錄順序串接起來、壓成一條。glyf 與 loca 預設會被轉換,而它的編碼方式真的很反直覺:對這兩個標籤而言,轉換版本 0 代表「已轉換」,而對其他所有標籤,0 代表「未轉換」。第三方讀取端經常把它搞反。 |
怎麼跟長得像的格式分開
| 容易搞混的 | 怎麼分 |
|---|---|
| WOFF 字型 | 位移 0 的標籤是最快的測試——77 4F 46 32 對上 77 4F 46 46——而結構上的差別是 WOFF2 在位移 20 插入了 totalCompressedSize,把 majorVersion、minorVersion 與中繼資料欄位全部往後推了 4 個位元組。拿 WOFF 解析器去讀 WOFF2 不會當掉;它會把壓縮大小當成版本號讀出來,然後一路走進垃圾裡。 |
| TrueType 字型 | sfnt 讀取端在該有版本號的地方看到 77 4F 46 32,就停手了。字型真正的 sfntVersion 在位移 4,所以這個檔案裡確實有 00 01 00 00 或 4F 54 54 4F,只是不在字型工具會看的位置。除了 WOFF2 解碼器,沒有東西弄得出那些表。 |
| Brotli (.br) | 原始的 Brotli 完全沒有簽章位元組,所以 .br 檔案在位移 0 沒有任何可辨識的東西,只能靠檔名或 Content-Encoding 認出來。而 WOFF2 也不是 Brotli 檔案:前 48 個位元組加上整份表目錄都是未壓縮的明文結構,所以從位移 0 把檔案餵給 Brotli 解壓縮器會立刻失敗。資料流要到目錄之後才開始,而那個位移只能靠走過目錄才找得到。 |
常見的壞法
- length 欄位和檔案對不上。位移 8 那 4 個位元組寫的是精確的檔案大小,所以截斷從標頭就證明得了。這件事要第一個做;相當高比例的網頁字型失敗到這裡就結案了。
- Brotli 資料流被截斷。和 WOFF 不同,這裡沒有逐表隔離——所有表都住在同一條資料流裡——所以尾端少幾個位元組,代價不是損失一張表,而是損失損壞點之後的每一張表,實務上等於整份字型。沒有「還能用一部分」的退路可走。
- 字型網址底下其實是一頁錯誤頁。404 或登入轉址被存成 .woff2,瀏覽器在期待 wOF2 的地方看到 HTML doctype,主控台報的是解碼失敗與消毒器錯誤,沒有一句話提到網頁。
- 檔案被改名。從字型服務下載的 .woff2 給它 .ttf 或 .otf 副檔名,到哪都裝不起來,而作業系統會回報檔案損毀而不是格式不對。前 4 個位元組立刻就能結案。
- 文字模式傳輸,或任何會改寫換行的處理。Brotli 的輸出不是文字,0D 0A 被改掉就活不下來,而且因為只有一條資料流,連能搶救的殘餘都沒有。標頭讀起來還是完美無缺,這才是讓人困惑的地方。
這個格式現在還在用嗎
WOFF2 就是那個網頁字型格式,基本上已經沒有對手。目前在用的每個瀏覽器都支援它,Google Fonts 和其他託管服務預設送的都是它,而現代的 @font-face 區塊裡如果還列了別的,那是列給流量裡早就不存在的瀏覽器看的。同一份字型比 WOFF 省下大約三成,實務上就是「首次繪製要不要等字型」的差別。它從來不是正確選擇的場合,是任何「要打開它、而不是拿它來算繪」的地方:桌面裝不起來、多數字型編輯器不讀它,而每個檢視工具都得先整份解壓縮才說得出任何東西。所以建置流程會把 TTF 或 OTF 留作單一事實來源、把 WOFF2 當成產出,就像它們留著來源圖片、產出 WebP 一樣——而一個把原檔弄丟、只剩 WOFF2 的專案,是真的替自己製造了一個麻煩。
在這個站上打開 WOFF2 字型檔
這些全部在你的瀏覽器裡跑,檔案不會離開你的電腦。
相關格式
容易和 WOFF2 字型搞混的格式,可以比對它們的開頭位元組。
| 格式 | 開頭位元組 | 副檔名 |
|---|---|---|
| WOFF 字型 | 77 4F 46 46 | .woff |
| TrueType 字型 | 00 01 00 00 | .ttf |
| OpenType 字型 | 4F 54 54 4F | .otf |