WOFF2 フォントファイル形式
Web Open Font Format 2 — WOFF の後継で、Brotli 圧縮を使います。
- MIME タイプ
- font/woff2
- 拡張子
- .woff2
- カテゴリ
- フォント
マジックナンバー(ファイル署名)
| 0x00000000 | 774F4632 | wOF2 | 'wOF2' tag |
WOFF2 フォント形式について
WOFF2 は WOFF の外形を保ったまま、その下にあるものをすべて入れ替えました。ヘッダは圧縮本体のサイズを入れるために 48 バイトへ伸び、テーブルディレクトリはよく使うテーブルタグを 1 バイトで綴る可変長構造になり、テーブルごとの zlib ストリームはフォント全体を覆う 1 本の Brotli ストリームに置き換わり、glyf と loca は可逆な変換を通してアウトラインを Brotli がずっとよく縮められる形に再符号化されます。取り出したフォントが入れたものと等価なのは変わりませんが、WOFF と違って、全部展開しないと中の何一つ読めません。
ヘッダーの構造(バイト単位)
| オフセット | 長さ | フィールド | 意味 |
|---|---|---|---|
| 0 | 4 | signature | 77 4F 46 32、ASCII の wOF2。WOFF のタグと 1 文字違いで、しかも同じ位置。雑な文字列比較が別の形式に一致してしまう、まさにその手の紛らわしさです。 |
| 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 に対して 3 対 1 に近い比は、ラテン文字の本文用書体では普通のことです。 |
| 20 | 4 | totalCompressedSize | Brotli ストリームの長さ。WOFF に無いのがこのフィールドで、ヘッダが 44 ではなく 48 バイトになった理由でもあり、圧縮本体をまったく展開せずにその終端を知ることのできる唯一の数値でもあります。 |
| 24 | 4 | majorVersion and minorVersion | オフセット 24 と 26 にある 16 ビットの数値 2 つで、形式ではなくフォントにバージョンを付けます。WOFF とまったく同じですが、手前のフィールドのぶん 4 バイトずれています。WOFF のパーサーで WOFF2 を読むと、ここから先はもっともらしく見えるでたらめが出てきます。綺麗に拒否されるよりたちの悪い失敗です。 |
| 28 | 12 | metaOffset, metaLength, metaOrigLength | 省略可能な圧縮 XML メタデータのブロックを、オフセット 28、32、36 の 3 つの 32 ビットフィールドが記述します。通常はすべてゼロ。存在する場合はフォント本体とは別に圧縮されているので、メインのストリームに触れずに読めます。 |
| 40 | 8 | privOffset and privLength | オフセット 40 と 44 にある、省略可能なプライベートデータのブロックで、48 バイトのヘッダを締めくくります。中身は何も定義されておらず通常はゼロ。非ゼロなら、フォントを作った誰かが自分のものを入れたということです。 |
| 48 | variable | Table directory | 可変長のレコードなので、sfnt 系の他のどの形式とも違い、2 つ目のレコードのアドレスを計算で求める方法がありません。各レコードはフラグバイトで始まり、その下位 6 ビットがよく知られたテーブルタグの固定リストへのインデックス(値 63 なら、代わりに 4 バイトのタグがそのまま続く意味)、上位 2 ビットが変換のバージョンです。続く長さは 1 バイトあたり 7 ビットで格納され、最上位ビットが継続を示すので、レコード 1 つが 2 バイトで済むこともあります。 |
| variable | variable | Brotli stream | 全テーブルをディレクトリ順に連結し、まとめて 1 回圧縮したもの。glyf と loca は既定で変換されますが、その符号化が本当に直感に反します。この 2 つのタグでは変換バージョン 0 が「変換済み」を意味し、他のすべてのタグでは 0 が「未変換」を意味するのです。サードパーティの実装が定期的に取り違えます。 |
似た形式との見分け方
| 紛らわしい形式 | 見分け方 |
|---|---|
| WOFF フォント | 手早い判定はオフセット 0 のタグで、77 4F 46 32 か 77 4F 46 46 か。構造上の違いは、WOFF2 がオフセット 20 に totalCompressedSize を挿入し、majorVersion・minorVersion・メタデータのフィールドを WOFF より 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 と違ってテーブルごとの隔離がなく、全テーブルが 1 本のストリームに入っているので、末尾の数バイトが欠けると失うのはテーブル 1 つではありません。破損地点より後ろの全テーブル、実際にはフォント全体です。部分的に使える状態に退避する道はありません。
- フォントの URL の下にエラーページが置かれていた。404 やログインへのリダイレクトが .woff2 として保存されると、ブラウザは wOF2 を期待した場所で HTML の doctype を受け取ります。コンソールに出るのはデコード失敗とサニタイザのエラーで、ウェブページに言及するものは何もありません。
- リネームされたファイル。フォントサービスからダウンロードした .woff2 に .ttf や .otf の拡張子を付けても、どこにもインストールできません。しかも OS は「形式が違う」ではなく「ファイルが壊れています」と報告します。先頭 4 バイトを見れば一瞬で決着します。
- テキストモード転送、あるいは何であれ改行の書き換え。Brotli の出力はテキストではないので 0D 0A を変えられれば生き延びられませんし、ストリームが 1 本しかないので救える残りもありません。ヘッダだけは完璧に読めてしまうのが、また紛らわしいところです。
この形式の現在地
WOFF2 はウェブフォントの形式であり、実質的に競合は残っていません。現役のブラウザはすべて対応し、Google Fonts をはじめとするホスティングサービスは既定でこれを配信します。今どきの @font-face でこれ以外を列挙するのは、もうトラフィックに現れないブラウザのために書いているようなものです。同じフォントで WOFF より 3 割ほど小さくなり、それは実際には「フォントを待ってからの初回描画」か「待たない初回描画」かの差になります。向いていないのは、描画ではなくファイルを開く必要がある場面です。デスクトップにはインストールできず、たいていのフォントエディタは読めず、調査ツールは何か言う前に全体を展開しなければなりません。だからビルドパイプラインは 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 |