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

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

ウェブセーフ フォント

ウェブ開発を長く行っている場合は、いわゆるウェブセーフ フォントを覚えているかもしれません。これらのフォントは、最も使用されているオペレーティング システム(Windows、macOS、最も一般的な Linux ディストリビューション、Android、iOS)のほぼすべてのインスタンスで利用可能であることが確認されています。2000 年代初頭、Microsoft は TrueType コアフォント 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 という企業フォントがあります。

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

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

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

Local Fonts Access API

この記事の冒頭で、ネガティブな印象を受けたかもしれません。本当にいいものは手に入らないのですか?ご安心ください。すべてが絶望的ではないと考えています。その前にまず、皆さんの疑問に答えましょう。

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

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

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

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

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

ブラウザ サポート

対応ブラウザ

  • Chrome: 103。
  • Edge: 103。
  • Firefox: サポートされていません。
  • Safari: サポートされていません。

ソース

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 でバグを報告します。できるだけ詳細な情報を含め、再現手順を簡単に説明してください。[コンポーネント] ボックスに Blink>Storage>FontAccess を入力します。Glitch は、簡単な再現手順をすばやく共有するのに適しています。

API のサポートを表示する

Local Font Access API を使用する予定ですか?一般公開されている機能へのサポートは、Chrome チームが機能の優先順位を決める際に役立ちます。また、他のブラウザ ベンダーに、その機能のサポートがどれほど重要であるかを示します。

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

謝辞

Local Font Access API の仕様は、Emil A. EklundAlex RussellJoshua BellOlivier Yiptong にお問い合わせください。この記事は、Joe MedleyDominik RöttschesOlivier Yiptong が確認しました。Brett Jordan 氏による Unsplash のヒーロー画像