ByteScope

WOFF2 フォントファイル形式

Web Open Font Format 2 — WOFF の後継で、Brotli 圧縮を使います。

MIME タイプ
font/woff2
拡張子
.woff2
カテゴリ
フォント

マジックナンバー(ファイル署名)

0x00000000774F4632wOF2'wOF2' tag

WOFF2 フォント形式について

WOFF2 は WOFF の外形を保ったまま、その下にあるものをすべて入れ替えました。ヘッダは圧縮本体のサイズを入れるために 48 バイトへ伸び、テーブルディレクトリはよく使うテーブルタグを 1 バイトで綴る可変長構造になり、テーブルごとの zlib ストリームはフォント全体を覆う 1 本の Brotli ストリームに置き換わり、glyf と loca は可逆な変換を通してアウトラインを Brotli がずっとよく縮められる形に再符号化されます。取り出したフォントが入れたものと等価なのは変わりませんが、WOFF と違って、全部展開しないと中の何一つ読めません。

ヘッダーの構造(バイト単位)

オフセット長さフィールド意味
04signature77 4F 46 32、ASCII の wOF2。WOFF のタグと 1 文字違いで、しかも同じ位置。雑な文字列比較が別の形式に一致してしまう、まさにその手の紛らわしさです。
44flavor中に入っているフォントの sfntVersion で、TrueType アウトラインなら 00 01 00 00、CFF なら 4F 54 54 4F。意味も位置も WOFF のフィールドと同一で、先頭 48 バイトの中で唯一、ラッパーではなくフォントについて教えてくれる場所です。
84lengthWOFF2 ファイル全体のサイズ。WOFF と同じくディスク上のファイルと厳密に一致していなければならず、Brotli のデコーダも要らない、コストゼロの切り詰め検査になります。
122numTablesディレクトリレコードの数。WOFF と違って、これだけではデータの開始位置は分かりません。レコードが可変長なので、実際に歩いてみる必要があります。
142reservedゼロでなければならず、ブラウザのサニタイザがそれを強制します。それ以外の値はウェブでは即座の拒否につながり、オフラインのツールの多くでは黙って無視されます。同じフォントがページとデスクトップで挙動を変える理由の、良い実例です。
164totalSfntSize再構成した sfnt フォントのサイズで、デコーダが出力バッファを一度に確保するために使います。オフセット 8 の length に対して 3 対 1 に近い比は、ラテン文字の本文用書体では普通のことです。
204totalCompressedSizeBrotli ストリームの長さ。WOFF に無いのがこのフィールドで、ヘッダが 44 ではなく 48 バイトになった理由でもあり、圧縮本体をまったく展開せずにその終端を知ることのできる唯一の数値でもあります。
244majorVersion and minorVersionオフセット 24 と 26 にある 16 ビットの数値 2 つで、形式ではなくフォントにバージョンを付けます。WOFF とまったく同じですが、手前のフィールドのぶん 4 バイトずれています。WOFF のパーサーで WOFF2 を読むと、ここから先はもっともらしく見えるでたらめが出てきます。綺麗に拒否されるよりたちの悪い失敗です。
2812metaOffset, metaLength, metaOrigLength省略可能な圧縮 XML メタデータのブロックを、オフセット 28、32、36 の 3 つの 32 ビットフィールドが記述します。通常はすべてゼロ。存在する場合はフォント本体とは別に圧縮されているので、メインのストリームに触れずに読めます。
408privOffset and privLengthオフセット 40 と 44 にある、省略可能なプライベートデータのブロックで、48 バイトのヘッダを締めくくります。中身は何も定義されておらず通常はゼロ。非ゼロなら、フォントを作った誰かが自分のものを入れたということです。
48variableTable directory可変長のレコードなので、sfnt 系の他のどの形式とも違い、2 つ目のレコードのアドレスを計算で求める方法がありません。各レコードはフラグバイトで始まり、その下位 6 ビットがよく知られたテーブルタグの固定リストへのインデックス(値 63 なら、代わりに 4 バイトのタグがそのまま続く意味)、上位 2 ビットが変換のバージョンです。続く長さは 1 バイトあたり 7 ビットで格納され、最上位ビットが継続を示すので、レコード 1 つが 2 バイトで済むこともあります。
variablevariableBrotli 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

よくある質問

ファイルのマジックナンバーとは何ですか?

ファイル先頭の数バイトで、名前が何であろうとその形式を示すものです。PNG は必ず 89 50 4E 47 で始まり、ZIP は 50 4B 03 04 で始まります。OS もブラウザも file コマンドも、拡張子を信用せずこのバイト列を読みます。だからファイル名を変えても中身の正体は変わりません。

マジックナンバーを自分で確認する方法は?

macOS や Linux なら xxd -l 16 ファイル名 で先頭 16 バイトが表示されます。Windows なら PowerShell で Format-Hex -Path ファイル名 -Count 16 です。あるいはこのサイトのファイル形式判別ツールにドロップすれば、ローカルでバイト列を読んで形式を教えてくれます。アップロードはありません。

2 つの形式が同じマジックナンバーを持つのはなぜ?

一方が他方の上に作られているからです。docx、xlsx、pptx、apk、jar、epub はすべて中身が ZIP アーカイブなので ZIP の署名で始まり、区別するには内部のエントリを見る必要があります。Java の class ファイルと macOS のユニバーサルバイナリはどちらも CA FE BA BE で始まり、直後のフィールドで見分けます。

マジックナンバーがまったくないファイルもある?

あります。XML、HTML、JSON、SVG、CSV といったテキストベースの形式には固定のバイト署名がないため、構造から判別します。先頭の <?xml 宣言、<svg 要素、あるいは { に続く引用符付きのキーなどです。これは確定ではなく推測なので、このリファレンスではそうした形式をプローブ判別として示しています。

マジックナンバーが一致すればそのファイルは安全?

いいえ。わかるのは形式であって、中身が無害かどうかではありません。正規の PDF や docx でも悪意のあるペイロードを含んでいることがあります。特に拡張子とバイト列が食い違うときに真の形式を知るのは有用な第一段階ですが、マルウェアスキャンではありません。

WOFF2 フォントかもしれないファイルをお持ちですか?ファイル形式判別ツールで調べる — マジックナンバーをローカルで読み取ります。アップロードは一切ありません。またはすべての形式を見る。