ローカルフォントアクセス API

ドラフト・コミュニティグループ報告,

This version:
https://wicg.github.io/local-font-access/
Test Suite:
https://github.com/web-platform-tests/wpt/tree/master/font-access
Issue Tracking:
GitHub
仕様内のインライン
編集者:
(Google Inc.)
元編集者:
Emil A. Eklund
Alex Russell
Olivier Yiptong

概要

この仕様は、ウェブブラウザーがユーザーにウェブサイトへ利用可能なシステムフォント全体の列挙アクセスおよびフォントの生データへのアクセス許可を与えることを文書化し、より詳細なカスタムテキスト描画を可能にします。

この文書の状態

この仕様は Web Platform Incubator Community Group によって公開されました。 W3C 標準でもなく、W3C 標準化のトラックにも含まれていません。 W3C Community Contributor License Agreement (CLA) の下では、限定的なオプトアウトやその他の条件が適用されることに留意してください。 W3C コミュニティおよびビジネスグループについてさらに学んでください。

1. 導入

この仕様は、ウェブブラウザ向けのフォント列挙APIについて記述しており、オプションでユーザーが利用可能なシステムフォント全体へのアクセス権を与えることができます。各フォントごとに、SFNT [SFNT] コンテナや同等のものへの低レベル(バイト指向)アクセスによって、完全なフォントデータが提供されます。

ウェブ開発者は従来、ページコンテンツのスタイリングに利用できるローカルフォントについて、ヒューリスティック情報以上のものを持っていませんでした。ウェブ開発者はしばしば、フォントフォールバックをヒューリスティックに制御するため、CSSで複雑な font-family リストを記述します。適切なフォールバックを生成する作業はデザイナーにとって非常に困難であり、「目視」で候補となるローカルマッチを探すためのツールも生み出されています。

フォント列挙は次のことに役立ちます:

ウェブは元々テキスト中心のメディアであり、ユーザーエージェントは高品質なタイポグラフィサポートを提供してきましたが、ウェブベースアプリケーションの一部には制約があります:

プロ品質のデザインやグラフィックツールは、従来ウェブ上で提供するのが困難でした。これらのツールは、幅広いタイポグラフィ機能やコントロールをコア能力としています。

このAPIは、これらのツールがブラウザのレイアウトやラスタライズエンジンがテキスト描画に利用する基盤フォントデータへアクセスすることを可能にします。例として、OpenType glyf テーブル(グリフベクターデータ)、GPOS テーブル(グリフ配置)、GSUB テーブル(合字やグリフ置換)などがあります。これらの情報は、結果出力のプラットフォーム非依存性保証(ベクターディスクリプションを埋め込むことでコードポイントを用いない)や、フォントベースのアート(フォントを操作可能な形状の基礎とする)に必要です。

2. 目標

APIは以下を満たすべきです:

Worker対応は上記で目標とされているが、現仕様ではAPIはWindowコンテキストのみで公開されている。

3.

このセクションは規範的ではありません。

3.1. ローカルフォントの列挙

APIは各フォント情報も含め、ローカルフォントを列挙できます。

下記コードは利用可能なローカルフォントを問い合わせ、それぞれの名前やメトリクスをコンソール出力します。
showLocalFontsButton.onclick = async function() {
  try {
    const array = await self.queryLocalFonts();

    array.forEach(font => {
      console.log(font.postscriptName);
      console.log(` full name: ${font.fullName}`);
      console.log(` family: ${font.family}`);
      console.log(` style: ${font.style}`);
    });
   } catch(e) {
    // Handle error, e.g. user cancelled the operation.
    console.warn(`Local font access not available: ${e.message}`);
  }
};

3.2. ローカルフォントによるスタイリング

高度なクリエイティブツールは、利用可能なローカルフォントによるテキストスタイリング機能を提供できます。この場合、ローカルフォント名にアクセスできればユーザーはより多くの選択肢から選ぶことが可能です:

下記コードは利用可能なローカルフォントをドロップダウンフォームで一覧化し、編集アプリのUIにも使用可能です。

useLocalFontsButton.onclick = async function() {
  try {
    // Query for allowed local fonts.
    const array = await self.queryLocalFonts();

    // Create an element to style.
    const exampleText = document.createElement("p");
    exampleText.id = "exampleText";
    exampleText.innerText = "The quick brown fox jumps over the lazy dog";
    exampleText.style.fontFamily = "dynamic-font";

    // Create a list of fonts to select from, and a selection handler.
    const textStyle = document.createElement("style");
    const fontSelect = document.createElement("select");
    fontSelect.onchange = e => {
      const postscriptName = fontSelect.value;
      console.log("selected:", postscriptName);
      // An example of styling using @font-face src: local matching.
      textStyle.textContent = `
        @font-face {
          font-family: "dynamic-font";
          src: local("${postscriptName}");
        }`;
    };

    // Populate the list with the available fonts.
    array.forEach(font => {
      const option = document.createElement("option");
      option.text = font.fullName;
      // postscriptName can be used with @font-face src: local to style elements.
      option.value = font.postscriptName;
      fontSelect.append(option);
    });

    // Add all of the elements to the page.
    document.body.appendChild(textStyle);
    document.body.appendChild(exampleText);
    document.body.appendChild(fontSelect);
  } catch(e) {
    // Handle error, e.g. user cancelled the operation.
    console.warn(`Local font access not available: ${e.message}`);
  }
};

3.3. フォントデータへのアクセス

APIはスクリプトでフォントデータの取得リクエストが可能です。

下記コードは利用可能なローカルフォントを問い合せ、それぞれの詳細をコンソールに出力します。

ここでは列挙を使い特定ローカルフォントデータへアクセスしています。例えば特定テーブルのパースや、HarfBuzzFreetype のWASM版へデータ供給が可能です:

useLocalFontsButton.onclick = async function() {
  try {
    const array = await self.queryLocalFonts();

    array.forEach(font => {
      // blob() returns a Blob containing the bytes of the font.
      const bytes = await font.blob();

      // Inspect the first four bytes, which for SFNT define the format.
      // Spec: https://docs.microsoft.com/en-us/typography/opentype/spec/otff#organization-of-an-opentype-font
      const sfntVersion = await bytes.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(`${font.fullName} outline format: ${outlineFormat}`);
    }
  } catch(e) {
    // Handle error. It could be a permission error.
    console.warn(`Local font access not available: ${e.message}`);
  }
};

フォントファイルの詳細なパース(テーブル列挙など)は本仕様の範囲外です。

3.4. 特定フォントの要求

場合によっては、ウェブアプリは特定フォントへのアクセスをリクエストしたい場合があります。例えば事前作成コンテンツ内にフォント名が埋め込まれている場合などです。queryLocalFonts()postscriptNames オプションでPostScript名によりフォントを指定・絞込できます。リストで完全一致するフォントだけが返されます。

ユーザーエージェントは対応するUIを別途提供することもあります。例えばフィンガープリンティングの懸念が少ない場合、ユーザー許可なしでリクエストが認可されることもあり、あるいは指定されたフォントだけを含むピッカーが表示される場合もあります。

// User activation is needed.
requestFontsButton.onclick = async function() {
  try {
    const array = await self.queryLocalFonts({postscriptNames: ['Verdana', 'Verdana-Bold', 'Verdana-Italic']});

    array.forEach(font => {
      console.log(`Access granted for ${font.postscriptName}`);
    });

  } catch(e) {
    // Handle error. It could be a permission error.
    console.warn(`Local font access not available: ${e.message}`);
  }
};

4. 概念

ユーザー言語とは、妥当な言語またはユーザーが最も優先する言語を表す有効なBCP 47言語タグです。 [BCP47]

4.1. フォント表現

フォント表現は、フォントの具体的な表現のことです。例として OpenTypeTrueType、ビットマップフォント、Type1フォント、SVGフォント、将来のフォント形式などがあります。本仕様は フォント表現の属性を以下のように定義します:

備考: フォント表現データバイト列は、一般的にユーザーのファイルシステム上のフォントファイルとまったく同じバイトごとの内容が期待されます。UAはフォントデータを正規化する必要はなく、特定OS・ユーザーでUA間の違いが生じないことが想定されています。正規化しないことにより、ウェブアプリで最大忠実度でフォント描画を実現する目標に寄与します。

備考: 本仕様はこれらの値の厳密な定義は行いません。OSによる違いでUAの非準拠状態とならないことを目的としています。ただしOpenType [OPENTYPE] フォントの場合、以下の値が妥当と考えられます。

これらの名前属性は [CSS-FONTS-4] などでのプロパティ利用と整合させて公開されます。例えば @font-facefont-family などです。PostScript名は一意キーとして、コンテンツ作成や既存コンテンツとの重複照合時などに使えます。フルネームファミリ名はユーザー向けフォント選択UIに、スタイル名はより詳細な選択に使えます。

有効なPostScript名は、スカラー値文字列であり、長さが64未満かつ U+0021 (!) から U+007E (~) の文字のみで、ただし下記10コードユニットは除外されます: U+005B ([), U+005D (]), U+0028 (左括弧), U+0029(右括弧), U+007B ({), U+007D (}), U+003C (<), U+003E (>), U+002F (/), U+0025 (%)。

備考: これは nameID = 6 の要件と一致するよう意図されています。[OPENTYPE]

4.2. システムフォント

システムフォントとは、オペレーティングシステムがシステム全体で利用可能にしているフォントです。

システムフォントをフォント表現として読むとは、フォントと同等のフォント表現を提供することです。これは、フォント表現のすべての属性を提供することを意味します:

この操作は、フォント表現の提供が不可能な場合は失敗します。

ユーザーエージェントは任意アルゴリズムで フォント表現システムフォントに対して提供できます。実際、多くの現代OSやAPIは SFNT [SFNT] 形式でフォントを永続化し OpenTypeTrueTypeWeb Open Font Format などが要件を満たします。加えて、これらの共通プロパティ付きでフォントコレクションの効率的な列挙もサポートされています。

システムフォント表現をすべて取得するには、以下の手順を実行します:
  1. fontsを、すべてのシステムフォントリストとする。

  2. resultを新しいリストとする。

  3. fontsの各fontについて、

    1. representationを、fontフォント表現として読むことで得る。失敗した場合は次へ

    2. ユーザーエージェントがそのフォントを決してウェブへ公開すべきでないと判断した場合、次へ

    3. representationresultに追加する。

  4. resultを返す。

5. 権限連携

ローカルフォントの列挙には権限の付与が必要です。

5.1. 権限

Local Font Access APIは、既定の強力な機能であり、名称 "local-fonts"として識別されます。

queryLocalFonts() APIを呼び出す際、ユーザーエージェントはフォント選択リストやはい/いいえ選択、その他のUIオプションを提示する場合があります。ユーザーエージェントは適切な形で選択結果を権限状態に反映するべきです(例:ユーザーが公開するフォント集合を選択した場合、その後のAPI呼び出しも同じフォント集合を返すなら権限状態は「granted」、再度プロンプトされる場合は「prompt」など)。

ローカルフォントの列挙権限はnavigator.permissions APIで問い合わせできます:
// このコードは権限の既存状態を問い合わせるだけで、状態を変更しません。
const status = await navigator.permissions.query({ name: "local-fonts" });
if (status.state === "granted")
  console.log("権限が付与されています 👍");
else if (status.state === "prompt")
  console.log("権限リクエストが表示されます");
else
  console.log("権限が拒否されています 👎");

5.2. 権限ポリシー

この仕様は、文字列 "local-fonts" で識別される ポリシー制御機能を定義します。その既定の許可リスト'self' です。

備考: 既定の許可リスト 'self'は、デフォルトで同一オリジンのネストされたフレームで本機能利用を許可しますが、サードパーティコンテンツではアクセスを防ぎます。

サードパーティ利用を個別許可するには、iframe 要素へ allow="local-fonts" 属性を追加します:

<iframe src="https://example.com" allow="local-fonts"></iframe>

また、HTTPレスポンスヘッダーでこの機能をファーストパーティ文脈で完全に無効化することも可能です:

Permissions-Policy: local-fonts 'none'

詳細は [PERMISSIONS-POLICY] を参照。

6. API

6.1. フォントタスクソース

フォントタスクソース は、新しい汎用タスクソースであり、本仕様でキューされるすべてのタスクに使用されます。

6.2. フォントマネージャー

await self . queryLocalFonts()
await self . queryLocalFonts({ postscriptNames: [ ... ] })

利用可能/許可されたフォントを非同期で照会します。成功すると、戻り値のpromiseはFontData オブジェクトの配列に解決されます。

このメソッドがドキュメントの一時的なアクティベーション(例:クリックイベントへの反応)中に呼ばれていない場合、戻り値のpromiseは拒否されます。

ユーザーはローカルフォントへのアクセス許可、もしくはサイトに提供するフォントを選択するためのプロンプトを受けます。権限が付与されない場合、戻り値のpromiseは拒否されます。

postscriptNames オプションが指定された場合、一致するPostScript名のフォントのみが結果に含まれます。

[SecureContext]
partial interface Window {
  Promise<sequence<FontData>> queryLocalFonts(optional QueryOptions options = {});
};

dictionary QueryOptions {
  sequence<DOMString> postscriptNames;
};
queryLocalFonts(options) メソッドのステップ:
  1. promise新しいpromiseとする。

  2. descriptorPermissionDescriptor オブジェクトとし、その name プロパティを "local-fonts" に設定する。

  3. もしthis関連設定オブジェクトorigin不透明origin であれば、promiseをSecurityError DOMExceptionで拒否 し、promise を返す。

  4. もしthis関連グローバルオブジェクトに関連付けられた Document が、ポリシー制御機能 "local-fonts" の利用を許可されていない場合、 promiseをSecurityError DOMExceptionで拒否 し、promise を返す。

  5. もしthis関連グローバルオブジェクト一時的アクティベーションがなければ、 promiseをSecurityError DOMExceptionで拒否 し、promise を返す。

  6. それ以外の場合、これらの手順を並列で実行

    1. system fontsすべてのシステムフォント表現の取得の結果とする。

    2. selectable fonts を新しいリストとする。

    3. system fontsの各representationについて:

      1. postscriptNamerepresentationPostScript名 とする。

      2. Assert: postscriptName有効なPostScript名である。

      3. もしoptions["postscriptNames"] が存在しoptions["postscriptNames"] postscriptNameを含まない場合、次へ

      4. 新しいFontDataインスタンス をrepresentationに関連づけてselectable fonts追加する。

    4. ユーザーに選択を促す。(descriptorallowMultipleをtrueとして)、selectable fontsから一つ以上の項目を選ばせ、結果をresultとする。 UAはリストの代わりにyes/no選択肢を提示する場合があり、その場合はresult = selectable fonts とする。

    5. もしresult"denied" なら、 promiseをNotAllowedError DOMExceptionで拒否し、これらの手順を中止。

    6. resultpostscriptName をソートキーとして昇順ソートし、新たなresultとする。

    7. fontタスクソースpromiseをresolveするタスクをキューする(解決値はresult)。

  7. promise を返す。

WindowOrWorkerGlobalScope へ移動し、権限周りの整理を検討。

6.3. FontDataインタフェース

FontData は、 フォントフェイスの詳細情報を提供します。各FontDataフォント表現に紐づきます。

fontdata . postscriptName

フォントのPostScript名。例: "Arial-Bold"

fontdata . fullName

フォントのフルネーム(ファミリー&サブファミリー名を含む)。例: "Arial Bold"

fontdata . family

フォントのファミリー名。これはCSSのfont-familyプロパティに対応。例: "Arial"

fontdata . style

フォントのスタイル(またはサブファミリー)名。例: "Regular", "Bold Italic"

[Exposed=Window]
interface FontData {
  Promise<Blob> blob();

  // Names
  readonly attribute USVString postscriptName;
  readonly attribute USVString fullName;
  readonly attribute USVString family;
  readonly attribute USVString style;
};
postscriptName のゲッター手順:
  1. postscriptNamethisPostScript名とする。

  2. Assert: postscriptName有効なPostScript名である。

  3. postscriptName を返す。

fullName のゲッター手順は this の関連フォント表現フルネームを返す。

family のゲッター手順は this の関連フォント表現ファミリ名を返す。

style のゲッター手順は thisの関連フォント表現スタイル名を返す。

FontDataシリアライズ可能オブジェクトにし、queryLocalFonts() の結果をWorkerに渡すことを検討。

await blob = fontdata . blob()

フォントの基礎バイト列を取得要求します。結果のblobにはデータバイト列が含まれます。

blob() メソッドの手順:

  1. realmthis関連Realmとする。

  2. promise新しいpromise とする。

  3. 以下の手順を並列で実行

    1. bytesthisの関連フォント表現データバイト列とする。

    2. type を `application/octet-stream` とする。

    3. fontタスクソースで以下のタスクをキュー:

    4. blobBlobインスタンスとし、内容はbytestype属性はtype

    5. promiseをblobで解決

  4. promise を返す。

7. 国際化に関する考慮事項

文字列のローカライズ以外の国際化に関する考慮事項を記載してください。例: https://github.com/WICG/local-font-access/issues/72, https://github.com/WICG/local-font-access/issues/59 など。

7.1. フォント名

OpenTypeフォントの`name`テーブルは、ファミリーやサブファミリーなどの名称に対して、プラットフォーム固有の数値言語識別子または[BCP47]準拠の言語タグ文字列を使用し、多言語の文字列を設定できます。例えば、フォントには、en-USzh-Hant-HKの両方のファミリー名が設定されていることがあります。

FontData のプロパティである postscriptName, fullName, family, および style は、このAPIによって単なる文字列として提供され、名称には米国英語ローカライズまたはユーザー言語ローカライズが使用されます。名称が存在しない場合は最初のローカライズがフォールバックされます。

他の言語で名称を提供する必要があるWebアプリケーションは、`name`テーブルを直接取得して解析することができます。

文字列の希望する言語を指定するためのオプション(例:{lang: 'zh'})をqueryLocalFonts() メソッドに定義すべきか?存在しない場合は `en-US` にフォールバックするようにするべきか?または、すべての名称へのアクセス(例えば、[BCP47]言語タグから名称へのマップとして)ができるようにすべきか?[Issue #69]

8. アクセシビリティに関する考慮事項

この機能による既知のアクセシビリティへの影響はありません。

9. セキュリティに関する考慮事項

この機能による既知のセキュリティへの影響はありません。

10. プライバシーに関する考慮事項

10.1. フィンガープリント

フォントデータには以下が含まれます:

これにより、ユーザーを識別するためのいくつかの「エントロピーのビット」が提供されます。

ユーザーエージェントは、特定の場合(例:許可が拒否された場合やプライベートブラウジング/「シークレット」モードの場合)に、ユーザーエージェントに付属の固定されたフォントセットの列挙を提供することでこれを軽減できます。

ユーザーエージェントはAPIを通じて利用可能にするフォントのセットをユーザーが選択できるようにもすることができます。

フォントが複数のローカライズされた名称を持つ場合、ユーザーのロケールはフォント名を通じて露出する可能性があります。ユーザーエージェントは、このような形でロケールが公開される場合、navigator.languageで公開されるものと同じロケールとなるようにする必要があります。

10.2. 識別

特定の組織のユーザーは、特定のフォントをインストールしている場合があります。例えば「Example Co.」の従業員は、システム管理者によって「Example Corporate Typeface」がインストールされており、サイトのユーザーが従業員であることが識別されます。

手書きサンプルに基づきフォントを作成するサービスも存在します。これらのフォントが「Alice’s Handwriting Font」など個人を特定できる情報を名称に含んでいる場合、個人情報が公開されることになります。これは、情報がフォント名だけでなくプロパティ内に含まれている場合、ユーザーには必ずしも明らかにならない可能性があります。

11. 謝辞

以下の方々の貢献に感謝いたします:

仕様書作成ツールBikeshedの作成・保守をしているTab Atkins, Jr.に(再度!)特別な感謝を表します。

Anne van Kesteren、 Chase Phillips、 Domenic Denicola、 Dominik Röttsches、 Igor Kopylov、 Jake Archibald、および Jeffrey Yasskin には、提案、レビュー、その他のフィードバックをいただき感謝します。

適合性

文書上の規則

適合性の要件は、記述的なアサーションとRFC 2119の用語の組み合わせで表現されます。 本仕様の規範部分で使われる “MUST”、 “MUST NOT”、 “REQUIRED”、 “SHALL”、 “SHALL NOT”、 “SHOULD”、 “SHOULD NOT”、 “RECOMMENDED”、 “MAY”、 “OPTIONAL”などのキーワードは、RFC 2119で説明されている通りに解釈されます。ただし、可読性のため、これらの語句は本仕様ではすべて大文字で表記しません。

本仕様のすべての本文は規範とし、例や注記、非規範セクションとして明示的に記載された箇所を除きます。[RFC2119]

本仕様における例は「例えば」という言葉で始まるか、または以下のようにclass="example"で書式設定されて規範的テキストから区別されます:

これは情報を補う例です。

情報としての注記は「注:」で始まり、以下のようにclass="note"で規範的テキストから区別されます:

注:これは情報を補う注記です。

索引

本仕様で定義される用語

参照により定義される用語

参考文献

規範的参考文献

[BCP47]
A. Phillips, Ed.; M. Davis, Ed.. Tags for Identifying Languages. 2009年9月. Best Current Practice. URL: https://www.rfc-editor.org/rfc/rfc5646
[FileAPI]
Marijn Kruisselbrink. File API. URL: https://w3c.github.io/FileAPI/
[HTML]
Anne van Kesteren; ほか. HTML Standard. Living Standard. URL: https://html.spec.whatwg.org/multipage/
[INFRA]
Anne van Kesteren; Domenic Denicola. Infra Standard. Living Standard. URL: https://infra.spec.whatwg.org/
[OPENTYPE]
OpenType specification. URL: http://www.microsoft.com/typography/otspec/default.htm
[PERMISSIONS]
Marcos Caceres; Mike Taylor. Permissions. URL: https://w3c.github.io/permissions/
[PERMISSIONS-POLICY]
Ian Clelland. Permissions Policy. URL: https://w3c.github.io/webappsec-permissions-policy/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. 1997年3月. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc2119
[SFNT]
Spline/Scalable font format. 2019年1月. URL: https://www.iso.org/obp/ui/#iso:std:iso-iec:14496:-22:ed-4:v1:en
[WEBIDL]
Edgar Chen; Timothy Gu. Web IDL Standard. Living Standard. URL: https://webidl.spec.whatwg.org/

情報提供のための参考文献

[CSS-FONTS-4]
Chris Lilley. CSS Fonts Module Level 4. URL: https://drafts.csswg.org/css-fonts-4/
[CSS-FONTS-5]
Chris Lilley. CSS Fonts Module Level 5. URL: https://drafts.csswg.org/css-fonts-5/
[TRUETYPE]
TrueType™ Reference Manual. URL: https://developer.apple.com/fonts/TrueType-Reference-Manual/
[WOFF]
Jonathan Kew; Tal Leming; Erik van Blokland. WOFF File Format 1.0. 2012年12月13日. REC. URL: https://www.w3.org/TR/WOFF/

IDL索引

[SecureContext]
partial interface Window {
  Promise<sequence<FontData>> queryLocalFonts(optional QueryOptions options = {});
};

dictionary QueryOptions {
  sequence<DOMString> postscriptNames;
};

[Exposed=Window]
interface FontData {
  Promise<Blob> blob();

  // Names
  readonly attribute USVString postscriptName;
  readonly attribute USVString fullName;
  readonly attribute USVString family;
  readonly attribute USVString style;
};

課題索引

Workerの対応については上記で目標として言及されているが、現時点で規定されているAPIはWindowコンテキストでのみ公開されている。
WindowOrWorkerGlobalScope への移行と、権限の課題整理を検討する。
FontDataシリアライズ可能 オブジェクトにし、queryLocalFonts()の結果をWorkerへ渡せるようにすることを検討。
文字列のローカライズ以外の国際化対応に関する考慮事項を記載する。例:https://github.com/WICG/local-font-access/issues/72, https://github.com/WICG/local-font-access/issues/59 など。
queryLocalFonts() メソッドに文字列の希望言語指定オプション(例:{lang: 'zh'})を定義し、存在しない場合はen-USへフォールバックするべきか?または[BCP47]言語タグから名称へのマップで全ての名称にアクセスできるようにするべきか?[Issue #69]