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

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

ブラウザ サポート

対応ブラウザ

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

ソース

Local Fonts 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 データへのアクセス

FontData オブジェクトの blob() メソッドを介して、SFNT に完全にアクセスできます。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 が確認しました。UnsplashBrett Jordan によるヒーロー画像。