ローカル フォントを使用して高度なタイポグラフィを使用する

Local Font Access API を使用して、ユーザーがローカルにインストールしたフォントにアクセスし、その詳細情報を取得する方法について説明します

ウェブセーフ フォント

ウェブ開発の経験が長い方なら、いわゆるウェブ セーフ フォントをご存じかもしれません。これらのフォントは、最もよく使用されているオペレーティング システム(Windows、macOS、最も一般的な Linux ディストリビューション、Android、iOS)のほぼすべてのインスタンスで利用できることがわかっています。2000 年代初頭には、Microsoft が TrueType core fonts for the Web というイニシアチブを主導し、「これらのフォントを指定しているウェブサイトにアクセスすると、サイトのデザイナーが意図したとおりにページが表示される」ことを目的に、これらのフォントを無料でダウンロードできるようにしました。はい。Comic Sans MS に設定されたサイトも含まれます。ウェブセーフ フォントの一般的なスタック(最終的なフォールバックは sans-serif フォント)は次のようになります。

body {
  font-family: Helvetica, Arial, sans-serif;
}

ウェブフォント

ウェブセーフ フォントが本当に重要だった時代は、はるか昔のことです。現在、ウェブフォントがあり、その一部は可変フォントです。可変フォントでは、さまざまな公開軸の値を変更することで、さらに調整できます。ウェブフォントを使用するには、CSS の先頭で @font-face ブロックを宣言し、ダウンロードするフォントファイルを指定します。

@font-face {
  font-family: 'FlamboyantSansSerif';
  src: url('flamboyant.woff2');
}

その後、通常どおり font-family を指定して、カスタム ウェブフォントを使用できます。

body {
  font-family: 'FlamboyantSansSerif';
}

フィンガープリント ベクターとしてのローカル フォント

ほとんどのウェブフォントは、ウェブから取得されます。ただし、@font-face 宣言の src プロパティは、url() 関数だけでなく、local() 関数も受け取ります。これにより、カスタム フォントをローカルで読み込むことができます。ユーザーのオペレーティング システムに FlamboyantSansSerif がインストールされている場合、ダウンロードされるのではなく、ローカル コピーが使用されます。

@font-face {
  font-family: 'FlamboyantSansSerif';
  src: local('FlamboyantSansSerif'), url('flamboyant.woff2');
}

このアプローチでは、帯域幅を節約できるフォールバック メカニズムが提供されます。インターネットでは、残念ながら、良いことは起こりません。local() 関数には、ブラウザ フィンガープリントに悪用される可能性があるという問題があります。ユーザーがインストールしたフォントのリストは、ユーザーを特定するのに十分な情報であることがわかりました。多くの企業では、従業員のノートパソコンにインストールされている独自の企業フォントを使用しています。たとえば、Google には Google Sans という企業フォントがあります。

macOS のフォントブック アプリに Google Sans フォントのプレビューが表示されている。
Google 社員のノートパソコンにインストールされている Google Sans フォント。

攻撃者は、Google Sans などの既知の企業フォントが多数存在するかどうかをテストすることで、ユーザーがどの企業に勤務しているかを特定しようとする可能性があります。攻撃者は、これらのフォントで設定されたテキストをキャンバスにレンダリングし、グリフを測定しようとします。グリフが既知の企業フォントの形状と一致する場合、攻撃者はヒットします。グリフが一致しない場合、攻撃者は、企業フォントがインストールされていないため、デフォルトの置換フォントが使用されたことを知ります。この攻撃や他のブラウザ フィンガープリント攻撃の詳細については、Laperdix 調査論文をご覧ください。

会社のフォントは別として、インストールされているフォントのリストだけでも識別が可能になります。この攻撃ベクトルの状況は非常に悪化しており、最近では WebKit チームが「ウェブフォントとオペレーティング システムに付属するフォントのみを(利用可能なフォントのリストに)含め、ローカルにユーザーがインストールしたフォントは含めない」と決定しました。(ローカル フォントへのアクセス権を付与する方法に関する記事を執筆している私です)。

Local Font Access API

この記事の冒頭で、気分を害された方もいらっしゃるかもしれません。本当に良いものを手に入れることはできないのでしょうか?ご安心ください。Google はそうできると考えています。すべてが絶望的というわけではありません。まず、皆さんが疑問に思っているであろう質問にお答えします。

ウェブ フォントがあるのに Local Font Access API が必要なのはなぜですか?

プロ品質のデザイン ツールやグラフィック ツールは、これまでウェブで提供することが困難でした。デザイナーがローカルにインストールした、プロが作成しヒントが設定されたさまざまなフォントにアクセスして使用できないことが、障害となっていました。ウェブフォントは一部のパブリッシング ユースケースを可能にしますが、ラスタライザーがグリフのアウトラインをレンダリングするために使用するベクター グリフの形状とフォント テーブルへのプログラムによるアクセスを可能にしません。同様に、ウェブフォントのバイナリデータにアクセスする方法もありません。

  • デザインツールは、独自の OpenType レイアウト実装を行い、デザインツールが下位レベルでフックインできるようにするために、フォント バイトにアクセスする必要があります。たとえば、グリフ形状に対してベクトル フィルタや変換を行うなどのアクションです。
  • デベロッパーは、ウェブに移行するアプリケーションのレガシー フォント スタックを持っている場合があります。これらのスタックを使用するには、通常、フォントデータへの直接アクセスが必要ですが、ウェブフォントでは提供されていません。
  • 一部のフォントは、ウェブ経由での配信のライセンスを取得していない場合があります。たとえば、Linotype の一部のフォントのライセンスには、デスクトップでの使用のみが含まれています。

Local Font Access API は、これらの課題を解決するための試みです。次の 2 つの部分で構成されます。

  • フォント列挙 API。ユーザーが利用可能なシステム フォントのフルセットへのアクセスを許可できます。
  • 各列挙結果から、完全なフォントデータを含む低レベル(バイト指向)の SFNT コンテナ アクセスをリクエストする機能。

ブラウザ サポート

Browser Support

  • Chrome: 103.
  • Edge: 103.
  • Firefox: not supported.
  • Safari: not supported.

Source

Local Font Access API の使用方法

特徴検出

Local Font Access API がサポートされているかどうかを確認するには、次のコマンドを使用します。

if ('queryLocalFonts' in window) {
  // The Local Font Access API is supported
}

ローカル フォントの列挙

ローカルにインストールされているフォントのリストを取得するには、window.queryLocalFonts() を呼び出す必要があります。初回は、権限を求めるプロンプトが表示され、ユーザーが承認または拒否できます。ユーザーがローカル フォントのクエリを承認すると、ブラウザはフォントデータの配列を返します。この配列をループ処理できます。各フォントは、family"Comic Sans MS" など)、fullName"Comic Sans MS" など)、postscriptName"ComicSansMS" など)、style"Regular" など)のプロパティを持つ FontData オブジェクトとして表されます。

// Query for all available fonts and log metadata.
try {
  const availableFonts = await window.queryLocalFonts();
  for (const fontData of availableFonts) {
    console.log(fontData.postscriptName);
    console.log(fontData.fullName);
    console.log(fontData.family);
    console.log(fontData.style);
  }
} catch (err) {
  console.error(err.name, err.message);
}

フォントのサブセットのみに関心がある場合は、postscriptNames パラメータを追加して、PostScript 名に基づいてフィルタすることもできます。

const availableFonts = await window.queryLocalFonts({
  postscriptNames: ['Verdana', 'Verdana-Bold', 'Verdana-Italic'],
});

SFNT データへのアクセス

SFNT への完全アクセスは、FontData オブジェクトの blob() メソッドを介して利用できます。SFNT は、PostScript、TrueType、OpenType、Web Open Font Format(WOFF)などのフォントを含むことができるフォント ファイル形式です。

try {
  const availableFonts = await window.queryLocalFonts({
    postscriptNames: ['ComicSansMS'],
  });
  for (const fontData of availableFonts) {
    // `blob()` returns a Blob containing valid and complete
    // SFNT-wrapped font data.
    const sfnt = await fontData.blob();
    // Slice out only the bytes we need: the first 4 bytes are the SFNT
    // version info.
    // Spec: https://docs.microsoft.com/en-us/typography/opentype/spec/otff#organization-of-an-opentype-font
    const sfntVersion = await sfnt.slice(0, 4).text();

    let outlineFormat = 'UNKNOWN';
    switch (sfntVersion) {
      case '\x00\x01\x00\x00':
      case 'true':
      case 'typ1':
        outlineFormat = 'truetype';
        break;
      case 'OTTO':
        outlineFormat = 'cff';
        break;
    }
    console.log('Outline format:', outlineFormat);
  }
} catch (err) {
  console.error(err.name, err.message);
}

デモ

Local Font Access API の動作については、デモをご覧ください。ソースコードも必ずご確認ください。このデモでは、ローカル フォント ピッカーを実装する <font-select> というカスタム要素を紹介します。

プライバシーへの配慮

"local-fonts" 権限は、フィンガープリントの取得が容易なサーフェスを提供しているように見えます。ただし、ブラウザは任意の値を返すことができます。たとえば、匿名性に重点を置いたブラウザでは、ブラウザに組み込まれたデフォルトのフォントのセットのみを提供する場合があります。同様に、ブラウザはテーブルデータをディスク上の表示とまったく同じように提供する必要はありません。

可能な限り、Local Font Access API は、前述のユースケースを有効にするために必要な情報のみを公開するように設計されています。システム API は、インストールされたフォントのリストをランダムな順序や並べ替えられた順序ではなく、フォントのインストール順序で生成する場合があります。このようなシステム API によって提供されるインストール済みフォントのリストを正確に返すことで、フィンガープリントに使用される可能性のある追加のデータが公開される可能性があります。また、この順序を保持しても、有効にしたいユースケースはサポートされません。そのため、この API では、返されるデータが返される前に並べ替えられる必要があります。

セキュリティと権限

Chrome チームは、強力なウェブ プラットフォーム機能へのアクセスを制御するで定義されているユーザー制御、透明性、人間工学などの基本原則を使用して、Local Font Access API を設計、実装しました。

ユーザー コントロール

ユーザーのフォントへのアクセスは完全にユーザーの制御下にあり、権限レジストリに記載されている "local-fonts" 権限が付与されない限り許可されません。

透明性

サイトにユーザーのローカル フォントへのアクセス権が付与されているかどうかは、サイト情報シートで確認できます。

権限の永続性

"local-fonts" 権限は、ページを再読み込みしても保持されます。サイト情報シートから取り消すことができます。

フィードバック

Chrome チームは、Local Font Access API の使用感について皆様のご意見をお待ちしています。

API 設計について教えてください

API について、想定どおりに動作しない点はありますか?アイデアを実装するために必要なメソッドやプロパティが不足している場合はどうすればよいですか?セキュリティ モデルについてご質問やご意見がある場合は、対応する GitHub リポジトリで仕様に関する問題を報告するか、既存の問題に意見を追加してください。

実装に関する問題を報告する

Chrome の実装にバグが見つかりましたか?それとも、実装が仕様と異なるのでしょうか?new.crbug.com でバグを報告します。できるだけ詳細な情報と再現手順を記載し、[Components] ボックスに Blink>Storage>FontAccess と入力してください。

API のサポートを表示する

Local Font Access API を使用する予定はありますか?公開サポートは、Chrome チームが機能の優先順位を決定するのに役立ち、他のブラウザ ベンダーにサポートの重要性を示すことができます。

ハッシュタグ #LocalFontAccess を使用して @ChromiumDev にツイートし、どこでどのように使用しているかをお知らせください。

謝辞

Local Font Access API 仕様が Emil A. EklundAlex RussellJoshua BellOlivier Yiptong。この記事は、Joe MedleyDominik RöttschesOlivier Yiptong によってレビューされました。ヒーロー画像: Brett JordanUnsplash