1. 導入
この仕様は、ウェブブラウザ向けのフォント列挙APIについて記述しており、オプションでユーザーが利用可能なシステムフォント全体へのアクセス権を与えることができます。各フォントごとに、SFNT [SFNT] コンテナや同等のものへの低レベル(バイト指向)アクセスによって、完全なフォントデータが提供されます。
ウェブ開発者は従来、ページコンテンツのスタイリングに利用できるローカルフォントについて、ヒューリスティック情報以上のものを持っていませんでした。ウェブ開発者はしばしば、フォントフォールバックをヒューリスティックに制御するため、CSSで複雑な
font-family リストを記述します。適切なフォールバックを生成する作業はデザイナーにとって非常に困難であり、「目視」で候補となるローカルマッチを探すためのツールも生み出されています。
フォント列挙は次のことに役立ちます:
-
ユーザー生成コンテンツのスタイリングオプションの向上。
-
既存コンテンツで宣言されたフォントとのマッチング。
ウェブは元々テキスト中心のメディアであり、ユーザーエージェントは高品質なタイポグラフィサポートを提供してきましたが、ウェブベースアプリケーションの一部には制約があります:
-
システムフォントエンジン(およびブラウザスタック)は、特定のグリフを異なる方法で表示する場合があります。これらの違いは、基盤となるOSとの整合性を保つために必要ですが(ウェブコンテンツが「おかしく見えない」ように)、複数プラットフォームを跨ぐアプリの場合には一貫性を損ないます。例として、ピクセル精度のレイアウトやレンダリングが求められるケースがあります。
-
デザインツールは、プラットフォームに依存しない独自のレイアウトや、グリフシェイプに対するベクターフィルターや変形などの処理を行うために、フォントデータへのアクセスが必要です。
-
開発者は、メトリクスやテーマに基づいたフォント選択UI、またはメトリクス等による自動マッチングなどを提供することがあり、これにはフォントデータへの直接アクセスが必要です。
-
一部のフォントはウェブ配信のライセンスを持たない場合があります。例えば、Linotypeは一部のフォントでデスクトップ利用のみのライセンスを持っています。
プロ品質のデザインやグラフィックツールは、従来ウェブ上で提供するのが困難でした。これらのツールは、幅広いタイポグラフィ機能やコントロールをコア能力としています。
このAPIは、これらのツールがブラウザのレイアウトやラスタライズエンジンがテキスト描画に利用する基盤フォントデータへアクセスすることを可能にします。例として、OpenType glyf テーブル(グリフベクターデータ)、GPOS テーブル(グリフ配置)、GSUB テーブル(合字やグリフ置換)などがあります。これらの情報は、結果出力のプラットフォーム非依存性保証(ベクターディスクリプションを埋め込むことでコードポイントを用いない)や、フォントベースのアート(フォントを操作可能な形状の基礎とする)に必要です。
2. 目標
APIは以下を満たすべきです:
-
メインスレッドをブロックせずにローカルフォントを効率的に列挙すること
-
UAは任意の回答を返してよい。ブラウザ実装によっては、組み込みのデフォルトフォントだけを提供する選択も可能である。
-
Workerから利用可能であること
-
複数レベルのプライバシー保護を可能にすること。例:「信頼済み」サイトには完全アクセス、「非信頼」な場合は限定的なアクセスなど。
-
Permissions API にローカルフォントアクセス状態を反映すること
-
ファミリーやインスタンス(「bold」「italic」などのバリアント)を一意に識別可能にすること。PostScript名も含む。
-
メモリ効率の良い実装を可能にし、設計上リークやコピーの回避を行うこと
-
Permissions Policy仕様により、デフォルトでSecure Contextおよび最上位フレームにのみローカルフォントデータへのアクセスを制限すること
-
結果リストをフォント名でソートし、フィンガープリンティングのエントロピーを低減すること。例:.queryLocalFonts() は与えられたフォント名でソートされたイテラブルを返す。
-
フォントデータの生バイトへアクセスを提供すること。多くの利用ケースで、既存ライブラリに全フォントデータを提供する用途となる。事前解析済みテーブルデータのみだと、開発者は全データを含むBlobを再構成する必要がある。
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はスクリプトでフォントデータの取得リクエストが可能です。
ここでは列挙を使い特定ローカルフォントデータへアクセスしています。例えば特定テーブルのパースや、HarfBuzz や Freetype の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. フォント表現
フォント表現は、フォントの具体的な表現のことです。例として OpenType、TrueType、ビットマップフォント、Type1フォント、SVGフォント、将来のフォント形式などがあります。本仕様は フォント表現の属性を以下のように定義します:
-
データバイト列は、バイト列としてフォントのシリアライズを含むものです。
備考: フォント表現の データバイト列は、一般的にユーザーのファイルシステム上のフォントファイルとまったく同じバイトごとの内容が期待されます。UAはフォントデータを正規化する必要はなく、特定OS・ユーザーでUA間の違いが生じないことが想定されています。正規化しないことにより、ウェブアプリで最大忠実度でフォント描画を実現する目標に寄与します。
-
PostScript名はフォントのnameテーブル内の、nameID = 6の名前レコードで見つかります。複数のローカライズが存在する場合、US英語バージョンがあればそれを使用し、なければ最初のローカライズが使用されます。
-
フルネームはフォントのnameテーブル内の、nameID = 4の名前レコードで見つかります。複数のローカライズが存在する場合、ユーザー言語バージョンがあればそれを使用し、なければ最初のローカライズが使用されます。
-
ファミリー名はフォントのnameテーブル内の、nameID = 1の名前レコードで見つかります。複数のローカライズが存在する場合、ユーザー言語バージョンがあればそれを使用し、なければ最初のローカライズが使用されます。
-
スタイル名はフォントのnameテーブル内の、nameID = 2の名前レコードで見つかります。複数のローカライズが存在する場合、ユーザー言語バージョンがあればそれを使用し、なければ最初のローカライズが使用されます。
これらの名前属性は [CSS-FONTS-4] などでのプロパティ利用と整合させて公開されます。例えば @font-face や font-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. システムフォント
システムフォントとは、オペレーティングシステムがシステム全体で利用可能にしているフォントです。
システムフォントをフォント表現として読むとは、フォントと同等のフォント表現を提供することです。これは、フォント表現のすべての属性を提供することを意味します:
-
PostScript名(有効である必要あり)
この操作は、フォント表現の提供が不可能な場合は失敗します。
ユーザーエージェントは任意アルゴリズムで フォント表現を システムフォントに対して提供できます。実際、多くの現代OSやAPIは SFNT [SFNT] 形式でフォントを永続化し OpenType、TrueType、Web Open Font Format などが要件を満たします。加えて、これらの共通プロパティ付きでフォントコレクションの効率的な列挙もサポートされています。
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" 属性を追加します:
また、HTTPレスポンスヘッダーでこの機能をファーストパーティ文脈で完全に無効化することも可能です:
詳細は [PERMISSIONS-POLICY] を参照。
6. API
6.1. フォントタスクソース
フォントタスクソース は、新しい汎用タスクソースであり、本仕様でキューされるすべてのタスクに使用されます。
6.2. フォントマネージャー
- await self .
queryLocalFonts()- await self .
queryLocalFonts({postscriptNames: [ ... ] }) - await self .
-
利用可能/許可されたフォントを非同期で照会します。成功すると、戻り値のpromiseは
FontDataオブジェクトの配列に解決されます。このメソッドがドキュメントの一時的なアクティベーション(例:クリックイベントへの反応)中に呼ばれていない場合、戻り値のpromiseは拒否されます。
ユーザーはローカルフォントへのアクセス許可、もしくはサイトに提供するフォントを選択するためのプロンプトを受けます。権限が付与されない場合、戻り値のpromiseは拒否されます。
postscriptNamesオプションが指定された場合、一致するPostScript名のフォントのみが結果に含まれます。
[SecureContext ]partial interface Window {Promise <sequence <FontData >>queryLocalFonts (optional QueryOptions = {}); };options dictionary {QueryOptions sequence <DOMString >; };postscriptNames
queryLocalFonts(options) メソッドのステップ:
-
promise を 新しいpromiseとする。
-
descriptor を
PermissionDescriptorオブジェクトとし、そのnameプロパティを"local-fonts"に設定する。 -
もしthis の 関連設定オブジェクトの origin が 不透明origin であれば、promiseをSecurityError DOMExceptionで拒否 し、promise を返す。
-
もしthis の 関連グローバルオブジェクトに関連付けられた Document が、ポリシー制御機能 "
local-fonts" の利用を許可されていない場合、 promiseをSecurityError DOMExceptionで拒否 し、promise を返す。 -
もしthis の 関連グローバルオブジェクトに一時的アクティベーションがなければ、 promiseをSecurityError DOMExceptionで拒否 し、promise を返す。
-
それ以外の場合、これらの手順を並列で実行:
-
system fonts を すべてのシステムフォント表現の取得の結果とする。
-
selectable fonts を新しいリストとする。
-
system fontsの各representationについて:
-
postscriptName を representation の PostScript名 とする。
-
Assert: postscriptName は有効なPostScript名である。
-
もしoptions[
"postscriptNames"] が存在し、options["postscriptNames"] がpostscriptNameを含まない場合、次へ -
新しい
FontDataインスタンス をrepresentationに関連づけてselectable fontsに追加する。
-
-
ユーザーに選択を促す。(descriptorと allowMultipleをtrueとして)、selectable fontsから一つ以上の項目を選ばせ、結果をresultとする。 UAはリストの代わりにyes/no選択肢を提示する場合があり、その場合はresult = selectable fonts とする。
-
もしresultが
"denied"なら、 promiseをNotAllowedError DOMExceptionで拒否し、これらの手順を中止。 -
resultを
postscriptNameをソートキーとして昇順ソートし、新たなresultとする。 -
fontタスクソース で promiseをresolveするタスクをキューする(解決値はresult)。
-
-
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 (); // Namesreadonly attribute USVString postscriptName ;readonly attribute USVString fullName ;readonly attribute USVString family ;readonly attribute USVString style ; };
postscriptName のゲッター手順:
-
postscriptName をthisのPostScript名とする。
-
Assert: postscriptName は有効なPostScript名である。
-
postscriptName を返す。
fullName のゲッター手順は this の関連フォント表現のフルネームを返す。
FontData をシリアライズ可能オブジェクトにし、queryLocalFonts()
の結果をWorkerに渡すことを検討。
blob() メソッドの手順:
-
promise を 新しいpromise とする。
-
以下の手順を並列で実行:
-
type を `
application/octet-stream` とする。 -
fontタスクソースで以下のタスクをキュー:
-
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-USとzh-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. 謝辞
以下の方々の貢献に感謝いたします:
-
Daniel Nishi、Owen Campbell-Moore、Mike Tsaoは、以前のローカルフォントアクセス提案で先駆的な役割を果たしました。
-
Evan Wallace、Biru、Leah Cassidy、Katie Gregorio、Morgan Kennedy、Noah Levin(Figma)は、野心的なWeb製品のニーズを根気強く列挙しました。
-
Alex Russellは、この提案の初版を作成しました。
-
Olivier Yiptongは、APIの初期実装と形状の改善を提供しました。
-
Tab Atkins, Jr.とCSS作業グループは、これらのケースにわずかに拡張するだけで使える基本クラスを提供しました。
-
Dominik RöttschesとIgor Kopylovは、配慮のあるフィードバックをいただきました。
-
2020年に亡くなられた前編集者Emil A. Eklundに心より感謝申し上げます。Emilはこの提案を推進し、技術的なガイダンスを行い、ユーザーや開発者のニーズを擁護しました。
仕様書作成ツールBikeshedの作成・保守をしているTab Atkins, Jr.に(再度!)特別な感謝を表します。
Anne van Kesteren、 Chase Phillips、 Domenic Denicola、 Dominik Röttsches、 Igor Kopylov、 Jake Archibald、および Jeffrey Yasskin には、提案、レビュー、その他のフィードバックをいただき感謝します。