ダウンロードされたフォントが使われない原因として、CSSが別の書体を選択している、対象の文字がフォントの収録範囲外である、または表示期間が過ぎていることが考えられます。Macでは、まずラベルの一つを実際に描画しているフォントを調べ、選択と読み込みの経路をさかのぼって確認します。

このガイドは、フォントのリクエストが成功しているのに、フォールバックのテキストが表示されるWebページを保守している方向けです。確認手順にはmacOS版Chromeを使用します。以下の例にある架空のラベル Panel 204 とファミリー名 Sample Sans は、診断方法を説明するためのもので、特定のWebサイトを指すものではありません。

ダウンロードの成功から実際に何がわかる?

診断を4つの別々の問いに分けて考えます。リソースは取得されたか。使用可能な書体として登録されたか。CSSは対象の文字にその書体を選択したか。レンダラーはその書体で描画したか。フォントのプリロードは早期の取得を開始しますが、それだけでは表示されているテキストにどのフォントが使われているかはわかりません。MDN: rel="preload"

フォントのマッチングは、単にダウンロード済みファイルを対象とするのではなく、テキストを対象に行われます。ブラウザーは指定されたファミリーとフォントの各種プロパティを確認し、次に文字がサポートされているかを調べます。そのため、1つのラベルに複数のフォントが使われることもあれば、ページ内の別の箇所がフォントのダウンロードを引き起こし、対象のラベルについてほとんど手がかりにならないこともあります。W3C: CSS Fonts Module Level 4, font matching

次の判断表を使って、次に確認する項目を選びます。これは、文書化されている読み込みとマッチングの境界をもとにした診断の補助資料であり、ブラウザーで測定した結果を記録したものではありません。

観察結果 次に確認する項目 判別に役立つこと
計算済みのファミリーが意図したファミリーと異なる 優先されたCSS宣言をたどる カスケードまたは対象指定の問題
計算済みのファミリーは正しいが、描画されたフォントが異なる 書体の記述子と文字の収録範囲を調べる 選択またはフォールバックの問題
JavaScriptは書体が読み込み済みと報告するが、ドキュメントのフォントセットにはない 登録コードを調べる 登録されないまま読み込まれている状態
再読み込み後はフォントが表示されるが、初回訪問では表示されない font-displayと読み込みのタイミングを調べる 表示期間の挙動
通常のテキストでは機能するが、canvasのテキストは変わらない 描画順序とcanvasのフォント設定を調べる 別個の描画処理

対象のラベルを実際に描画しているフォントは?

問題が起きているページで Command + Shift + C を押し、テキストを選択します。Elements で、対象のラベルを含む最小の要素を選択します。Computed を開き、Rendered Fonts までスクロールします。Chromeのドキュメントでは、描画されたテキストに使われているフォントを調べる場所としてこのビューが案内されています。Chrome DevTools: CSS features reference、Chrome DevTools: Apply other effects

描画された書体と、そのソースがローカルかネットワークリソースかを記録します。また、計算済みの font-family、font-weight、font-style も記録します。計算済みのファミリーは要求されたスタックを示し、描画フォントの結果はグリフの表示に実際に使われたフォントを示します。見た目が似ている書体もあるため、見慣れた外観だけでは確かな根拠になりません。Chrome DevTools: What font is that?

対象のテキストに意図した書体が使われている場合は、太さや機能など、見た目の違いに焦点を移します。使われていない場合は、以下の手順に進みます。観察結果を比較できるよう、テスト中は元のテキストとビューポートを変えないでください。

実際に適用されるCSSは意図した書体を要求している?

Computed で実際に適用されるフォントプロパティを確認し、Styles でそれらの宣言を探します。Chromeではこの2つのビューは区別されています。Styles には上書きされた宣言も含まれますが、Computed には実際に適用された値が表示されます。カードに複数のラベルがある場合は、テキストを含む子要素を確認してください。親のフォント宣言だけでは、子要素に関する根拠として不十分です。Chrome DevTools: CSS features reference

優先される font の一括指定と、個別のプロパティの両方を確認します。たとえば font: 16px sans-serif は総称ファミリーを選択し、省略された太さやスタイルなど、一括指定の各成分を初期値に戻します。また、font-feature-settings や font-variation-settings などのプロパティも初期値に戻します。予期しない値の原因となっている宣言を修正し、もう一度確認します。MDN: font

次に、要求されたファミリーを、該当する @font-face ルールの font-family 記述子と比較します。この記述子は、その書体を参照する際に使うCSSのファミリー名を定義します。ファイル名は、要素のフォントスタックに指定する名前とは限りません。src、太さ、スタイル、宣言された範囲を含め、ブラウザーに配信されているルールを確認します。MDN: @font-face

可変フォントの対応可能な太さの範囲は2つの値で表せますが、静的フォントでは1つの太さを宣言できます。記述子が実際に提供されているリソースを正確に表すようにしてください。不一致は調べる必要がありますが、それだけでフォールバックの原因と決まるわけではありません。正確なスタイルが見つからない場合も、フォントのマッチングでは代替候補が考慮されます。MDN: @font-face、W3C: Matching font styles

ローカルフォントが優先されている可能性は?

src に local() の代替候補がないか確認します。Macにそのフォントがインストールされていると、別途プリロードされたWebフォントが使われないまま、ブラウザーがローカルのコピーを使うことがあります。Chromeでこの状態を切り分けるには、Rendering パネルを開いて Disable local fonts を有効にし、再読み込みします。前後の Rendered Fonts を比較してから、設定を元に戻します。このテストの対象は、@font-face ルールにある local() ソースです。Chrome DevTools: Apply other effects

書体は正しいドキュメントに読み込まれ、登録されている?

new FontFace() で作成した書体は、フォントセットに追加する前に読み込みが完了することがあります。ドキュメントで描画するには、document.fonts に追加する必要があります。読み込み処理の両方の操作を確認してください。書体の読み込みPromiseが解決しただけでは、登録は完了しません。MDN: CSS Font Loading API

次のコンソール診断コードは、現在のドキュメントに登録されている書体を一覧表示します。問題が起きているページのコンソールコンテキストで実行してください。読み込みの開始やスタイルの変更はせずにセットを読み取ります。また、どのグリフが描画されたかは特定しません。ブラウザーに依存するこの挙動は、ここでは実行していません。

(() => {
  const faces = Array.from(document.fonts, face => ({
    family: face.family,
    weight: face.weight,
    style: face.style,
    stretch: face.stretch,
    unicodeRange: face.unicodeRange,
    status: face.status
  }));

  console.table(faces);
  console.log("Registered face count:", faces.length);
})();

この一覧を使い、登録されていない状態と、登録済みだが使われていない状態を区別します。ステータスが error の項目については、タイポグラフィを調整する前に読み込みの失敗を調べてください。loaded の項目からわかるのは読み込み状態です。書体の選択は、引き続き要素とテキストに左右されます。MDN: CSS Font Loading API

document.fonts.check() が描画の証明になると考えないでください。存在しないファミリーでも true を返すことがあります。省略可能なテキスト引数は、宣言されたUnicode範囲によってマッチングの対象を絞るもので、個々のグリフの収録状況はテストしません。同様に、document.fonts.ready は使用中のフォントの読み込みとレイアウトが終わると解決しますが、宣言されたすべての書体が描画されたことを保証するものではありません。MDN: FontFaceSet.check()、MDN: Document.fonts

テストしている文字はフォントに収録されている?

CSSの unicode-range と実際のフォントリソースの両方を確認します。この記述子は、どの文字に書体を使用できるかを制御します。記述子の範囲を広げても、ファイルにないグリフは作り出せません。フォントの文字マップは、引き続きマッチングに関わります。MDN: unicode-range、W3C: Matching font styles

U+0030-0039 だけに限定された例示ルールを考えてみます。Panel 204 では、2、0、4 だけがこの範囲に入ります。文字と空白は範囲外です。そのため、数字がきっかけでフォントがダウンロードされても、ラベル全体を描画できるとは限りません。MDNの説明によると、条件を満たす文字に遭遇すると、フォントリソース全体のダウンロードが始まることがあります。MDN: unicode-range

次の単独で実行できるJavaScriptの例で、この違いを確認できます。準備なしでブラウザーのコンソールまたはNode.jsで実行できます。示した例については、決定的な範囲計算を実行済みです。

const text = "Panel 204";
const rows = Array.from(text, character => {
  const value = character.codePointAt(0);
  return {
    character,
    codePoint: "U+" + value.toString(16).toUpperCase().padStart(4, "0"),
    inDeclaredRange: value >= 0x30 && value <= 0x39
  };
});

console.table(rows);

必要な文字が範囲外なら、不正確な記述子を修正するか、それらの文字を含むサブセットを用意します。結果を確認するときは、問題が起きた元のテキストをそのまま使ってください。文字列の反復処理をシェーピングのテストだと考えないでください。結合文字列にはクラスター単位の処理が必要であり、標準では必要に応じてシェーピング情報も考慮されます。表示結果を比較する際は、文字列を分割しないでください。W3C: Font matching and cluster matching

フォントの表示期間を過ぎていた?

ファミリーと収録範囲に問題がなさそうな場合は、font-display を確認します。optional のブロック期間は非常に短く、スワップ期間はありません。fallback のスワップ期間は短く設定されています。リソースの到着が該当する期間を過ぎると、そのページの読み込みではフォールバックのテキストが表示されたままになることがあります。swap では、最初のブロック期間後のスワップ期間に制限がありません。MDN: font-display

条件を揃えて診断するには、配信中のスタイルシートで問題が起きているルールを一時的に font-display: swap に変更し、初回読み込みと同じ条件で繰り返します。読み込み後に書体が表示された場合、タイミングが原因だと考えられます。その後の表示変更がページに適しているか確認してから、設定を残してください。初回読み込みとキャッシュ済みの再読み込みを両方試します。キャッシュ済みの結果だけでは、元の到着タイミングを再現できません。

ライブラリーやcanvasがテキストを描画する場合は?

すでにFont Face Observerを使っているページでは、監視しているファミリーと weight、style、stretch の各オプションを確認します。ドキュメントによると、これはローダー兼モニターであり、通常の @font-face ルールが必要です。また、フォントに既定のテスト文字が含まれない場合は、独自のテスト文字列も必要です。既定のタイムアウトは3秒です。Observerのタイムアウトとブラウザーの font-display 期間は別々の条件です。Font Face Observer documentation

診断上の推論として、Observerの成功メッセージを確認した後は、対象の要素も調べてください。1つの書体が利用可能か監視しても、その要素で優先されるCSSがその書体を要求しているとは限りません。どの書体が描画されたかが未解決の問題なら、ローダーを追加してもほとんど役に立ちません。

canvasには別個の描画境界があります。描画コンテキストには独自の font 設定があり、初期値は 10px sans-serif です。無効な値を代入しても無視されます。そのため、canvas要素にCSSファミリーを指定しただけでは、コンテキストのフォントは特定できません。描画コンテキストに割り当てた値と、読み込みおよび描画処理の順序を確認します。WHATWG: Canvas text styles

canvasのテキストでは、意図した書体が登録されて読み込まれるのを待ち、コンテキストのフォントを設定してから、アプリケーションの通常の描画処理で対象のシーンを再描画します。フォントが到着しても、すでに描画されたピクセルの書体は変わりません。また、canvasのテキスト準備では、幅に制限のない仮想行ボックスが使われます。そのため、canvasのテストで成功しても、幅が制限されたWebページのラベルが同じように折り返されるとは限りません。WHATWG: Text preparation and drawing text

修正をどう確認する?

問題がわかった元のラベルに戻り、同じ観察を繰り返します。元の読み込み条件で、計算済みの要求、描画された書体、関連する文字を確認します。canvasの場合は、比較する前に再描画します。要素、要求されたプロパティ、実際に描画された書体、リソース、問題を解決した変更を簡潔に記録しておくと、診断を再現できます。

フォントが使われなかった理由を説明でき、対象のテキストに意図した結果が表示されれば診断は完了です。ファミリー宣言の修正、正確なサブセット、書体の登録、適切な表示ポリシー、適切なタイミングでの再描画は、それぞれ異なる境界の問題に対処します。観察結果が裏付ける修正を選んでください。

参考資料