一言で言うと
フォントのサブセット化と変換は、週末の小旅行のパッキングみたいなものだ。絶対に使いもしないキリル文字や装飾記号(dingbats)が詰まった6,000文字入りのスーツケースを引きずり回す代わりに、本当に必要な文字だけを、限界まで圧縮したWeb向けのバックパックに詰め込むってわけ。
これで解決できる課題
2005年当時にWebサイトを作っていたなら、タイポグラフィの選択肢なんて人質に取られてるようなもんだった。使えるのは Arial、Times New Roman、Georgia、Verdana、それから世界が燃えるのを見たいなら Comic Sans くらいだ。これらは「Webセーフ」フォントと呼ばれていた。つまり、ユーザーのOSに最初からインストールされていることを指をクロスして祈るしかなかったってことだ。
そこで @font-face 革命が起きた。突然、デザイナーが好きなカスタム書体を自由に埋め込めるようになったんだ。Webは美しくなったけど、同時に信じられないくらい遅くなった。
標準的なデスクトップフォント(TrueTypeやOpenType)は、印刷やユニバーサルな言語サポートを前提に設計されている。たった1つの .ttf ファイルに何千ものグリフ(字形)が含まれていて、英語、ギリシャ語、ベトナム語、特殊な数学記号、そして20種類ものアンパサンド(&)がサポートされていたりする。これはワープロソフトにとっては最高だけど、不安定な3G回線越しに読み込むWebブラウザにとっては大惨事だ。400KBのフォントファイルをダウンロードするとレンダリングがブロックされ、あの忌まわしき「Flash of Unstyled Text (FOUT)(スタイルの当たっていないテキストが一瞬表示される現象)」や、もっとひどい「Flash of Invisible Text (FOIT)(テキストがしばらく透明になる現象)」を引き起こす。
このタイポグラフィの「肥満」を解消するには、2つのことが必要だった:
- Webに特化した より優れた圧縮(だからWOFFやWOFF2といったフォーマットが生まれた)。
- サブセット化(Subsetting)。特定のWebサイトで実際には使わない文字や言語テーブルを外科手術のように切り落とすことだ。
裏側の仕組み
フォントをどうやって縮小しているかを理解するには、そもそもフォントファイルとは何なのかを知る必要がある。モダンなフォント(TTFやOTFなど)は、単なるベクター画像の詰め合わせフォルダではない。SFNT コンテナフォーマットをベースにした、複雑なバイナリデータベースなんだ。
フォントの解剖学
SFNTコンテナの中では、データが特定のテーブルに整理されている:
glyfまたはCFF: 実際のベクター画像(文字の曲線や直線)。cmap: キャラクターマップ。「A」ならU+0041のようなUnicodeポイントを、glyfテーブル内の特定の画像に変換する。GSUB/GPOS: リガチャ('f'と'i'を繋げるなど)やカーニング(特定の文字ペア間のスペース調整)といった高度なOpenType機能。name: フォント名、著作権、デザイナー情報などのメタデータ。
サブセット化の魔法
フォントをサブセット化するとき、パーサーはこのSFNTコンテナをこじ開ける。そしてパーサーに文字のリスト(例えば、基本的なアルファベット、数字、よく使う記号だけ)を渡す。
パーサーは単に glyf テーブルから使わないベクター画像を削除するだけじゃない。データベース全体を細心の注意を払って再構築しなければならない。見捨てられたUnicodeポイントを削除するために cmap を書き直す。GSUB や GPOS テーブルを隅々までチェックし、もう存在しない文字のカーニングペアを取り除き、ファイルが壊れないようにする。最後に、ファイルヘッダーのメモリオフセットを再計算する。
標準的な300KBのフォントでも、基本的なラテン文字だけにサブセット化すれば、あっさりと15KBまで軽量化できたりするんだ。
フォーマット戦争:WOFFとWOFF2
サブセット化が終わったら、次は圧縮だ。
| フォーマット | 圧縮技術 | 評価 |
|---|---|---|
| TTF / OTF | なし(通常) | デスクトップのデザインアプリには最高。Web配信には最悪。 |
| WOFF | Zlib | 初代 Web Open Font Format。良いけど、もう古い。 |
| WOFF2 | Brotli | 今のスタンダード。圧縮する前にフォントテーブルを変換する。 |
WOFF2は文句なしのWebフォントの王者だ。単にTTFをZip圧縮するだけじゃない。WOFF2はSFNTテーブルを物理的に変換し、特に似たようなデータ型をグループ化することで、背後にある Brotli 圧縮アルゴリズムがより長い繰り返しパターンを見つけられるようにする。結果として、ファイルサイズはWOFF1より最大30%小さくなり、サブセット化と組み合わせれば元のTTFから95%も削れる可能性がある。
CSSとの連携
極限まで圧縮・サブセット化されたWOFF2ファイルを手に入れたら、CSSを使ってブラウザにそれを教えてやろう。ここで鍵になるのが unicode-range プロパティだ。「ページ上の文字がこの範囲に実際にマッチした場合だけ、このファイルをダウンロードしてね」とブラウザに指示できる。
@font-face {
font-family: 'MyCustomFont';
/* 極限まで圧縮されたWOFF2フォーマットを読み込む */
src: url('mycustomfont-subset.woff2') format('woff2');
/* ページで基本ラテン文字が使われている場合のみダウンロードをトリガーする */
unicode-range: U+0000-00FF, U+0131, U+0152-0153;
font-display: swap;
}
現場のリアルな話
「たかがアンパサンド」の悲劇
あるオシャレなデザインエージェンシーが、高級レストランのランディングページを作っていた。見出しのタイポグラフィは素晴らしかった。なぜなら、「Salt & Ash」という店名のアンパサンド(&)だけのために、美しく装飾された筆記体フォントをわざわざ使っていたからだ。
しかし、Lighthouseのパフォーマンススコアは真っ赤っか。開発陣は、たった1文字を描画するためだけに、250KBのTTFファイルをユーザーにダウンロードさせていることに気づいた。そのフォントをサブセット化ツールにかけ、& の文字(Unicode U+0026)だけを抽出した結果、笑っちゃうくらい軽い 800バイト のWOFF2ファイルが生成された。
教訓: 装飾的なドロップキャップ、アイコン、あるいは1つの文字のために、フォントファミリー全体を読み込んではいけない。ガンガンサブセット化すべし。
多言語メガバイトの罠
あるSaaS企業が、日本と韓国のユーザー向けにマーケティングサイトをローカライズした。ブランディングに一貫性を持たせるため、評価の高いCJK(中国語、日本語、韓国語)フォントを埋め込んだ。CJKフォントは数万の表意文字を含んでいるため、バカでかいことで悪名高い。そのファイルはなんと4.5MBもあった。
突然、アメリカやヨーロッパのユーザーは、テキストが表示されるまで何秒も待たされることになった。絶対に見ることのないアジア圏の文字を4.5MBもブラウザがダウンロードしていたからだ。エンジニアチームは、マスターフォントを複数の小さな「チャンク(塊)」(基本ラテン文字用のWOFF2、ひらがな/カタカナ用のWOFF2など)にサブセット化し、厳格な unicode-range 記述子を持つ @font-face ブロックでデプロイすることでこれを修正した。これにより、英語のユーザーのブラウザは15KBのラテン文字チャンクだけをダウンロードするようになった。
教訓: サブセット化と unicode-range を駆使して、ユーザーの実際の言語ニーズに合わせて動的にタイポグラフィのチャンクを配信すべし。
よくあるミスと罠
- うっかりスペースキーを消しちゃう: サブセット化したい文字をベタ打ちするとき(例:
abcdefghijklmnopqrstuvwxyz)、開発者はよくスペース文字(U+0020)を含めるのを忘れる。これがないと、単語の間に空白ができるたびに、せっかく作ったカスタムフォントがArialにフォールバックしてしまい、身代金要求の手紙みたいなホラーな見た目になってしまう。 - 不要なレガシーフォーマットの配信: 「念のため」と言って、未だにEOT、TTF、SVGフォントを生成してCSSスタックに突っ込んでいる開発者は多い。やめとけ。WOFF2は世界の97%以上のブラウザでサポートされている。WOFF2を配信し、古代のブラウザ向けのフォールバックとしてWOFFを置くくらいにして、そこで止めておこう。
- 必須のOpenType機能を削ってしまう: 攻めたサブセット化ツールの中には、数キロバイト節約するために
GSUBやGPOSテーブルを削除するオプションを提供しているものがある。もし君のフォントが複雑なリガチャ(->を矢印に変えるなど)に依存していたり、ひどい見た目にならないようカーニングに大きく依存しているなら、これらのテーブルを削るとタイポグラフィが台無しになる。 font-display: swapの指定忘れ: 12KBの超軽量なWOFF2サブセットであっても、ダウンロードには数ミリ秒かかる。CSSにfont-display: swapを入れないと、モダンブラウザはフォントが到着するまでテキストを完全に隠してしまう。カスタムフォントが届くまでの間、ユーザーにはシステムフォントのテキストを読ませてあげよう。
なぜ知っておくべきなのか
歴史的に見ても、Webフォントは Web Vitals をぶち壊す最悪の戦犯のひとつだ。Largest Contentful Paint (LCP) や Cumulative Layout Shift (CLS) を気にするなら、フォントの配信にも気を配らなきゃいけない。
デザイナーから .ttf や .otf ファイルを受け取ったり、ファウンドリからダウンロードしたりした瞬間に、フォントのサブセット化と変換を考えるべきだ。デスクトップフォントをそのままWebで配信するのは、WebPの代わりに無圧縮の10MBの生ビットマップ画像を配信しているのと同じくらいヤバい。
WOFF2に変換し、使わない何千ものグリフを削ぎ落とすことで、帯域幅のコストを削減し、アプリケーションのカーボンフットプリントを減らし、ユーザーがリンクをクリックしたミリ秒単位の瞬間からサイトをちゃんと読める状態にできるんだ。
さらに深く知るには
- MDN Web Docs: @font-face - カスタムフォントの実装方法とCSSでの
unicode-rangeの正しい使い方に関する絶対的なバイブル。 - W3C WOFF2 Specification - WOFF2がSFNTテーブルをどのように変換し圧縮するのかを正確に詳述した公式の技術仕様。
- RFC 7932: Brotli Compressed Data Format - WOFF2にスーパーパワーを与えている圧縮アルゴリズムに関するIETFドキュメント。
- Web.dev: Optimize WebFonts - Webフォントのペイロードを最小化し、レイアウトシフトを防ぐためのGoogleの権威あるガイド。
- Google Fonts Knowledge: What is a font? - SFNTテーブルの構造と、裏側でグリフがどのように保存されているかを深掘りする記事。