Font Enumeration API

A Collection of Interesting Ideas,

Issue Tracking:
GitHub
Inline In Spec
Editors:
(Google Inc.)
(Google Inc.)
(Google Inc.)

Abstract

This specification documents web browser support for allowing users to grant web sites access to the full set of available system fonts for enumeration and use.

logo

1. Introduction

This specification describes a font enumeration API for web browsers which may, optionally, allow users to grant access to the full set of available system fonts.

Web developers historically lack anything more than heuristic information about which local fonts are available for use in styling page content. Web developers often include complex lists of font-family values in their CSS to control font fallback in a heuristic way. Generating good fallbacks is such a complex task for designers that tools have been built to help "eyeball" likely-available local matches.

Font enumeration helps by enabling:

2. Goals

The API should:

3. Examples

This section is non-normative.

3.1. Enumerating local fonts

The API allows script to enumerate local fonts, including properties about each font.

3.2. Styling with local fonts

Advanced creative tools may wish to use CSS to style text using all available local fonts. In this case, getting access to the local font name allows the user to select from a richer set of choices:

4. Concepts

Define any new concepts beyond just the API

5. Local font access permission

Enumeration of local fonts requires a permission to be granted.

The "local-fonts" powerful feature’s permission-related flags, algorithms, and types are defaulted.

6. API

6.1. Font manager

for await (const fontFace of navigator . fonts . query() { ... }

Asynchronously iterate over the available fonts. Each time through the loop, fontFace will be a new FontFace object.

[SecureContext]
interface mixin NavigatorFonts {
  [SameObject] readonly attribute FontManager fonts;
};
Navigator includes NavigatorFonts;
WorkerNavigator includes NavigatorFonts;
Each environment settings object has an associated FontManager object.

The fonts attribute’s getter must return this's relevant settings object's FontManager object.

[SecureContext,
 Exposed=(Window,Worker)]
interface FontManager {
  FontFaceIterator query();
};
The query() method, when invoked, must run these steps:
  1. Let promise be a new promise.

  2. If this’s relevant settings object's origin is an opaque origin, then reject promise with a TypeError.

  3. Otherwise, run these steps in parallel:

    1. Let permission be the result of requesting permission to use "local-fonts".

    2. If permission is not "granted", then reject promise with a "NotAllowedError" DOMException, and abort these steps.

    3. Resolve promise with a newly created FontFaceIterator.

  4. Return promise.

[SecureContext,
 Exposed=(Window,Worker)]
interface FontFaceIterator {
  /*async*/ iterable<FontFace>;
};

All FontFaceIterator objects contain an internal [[FontList]] slot.

The asynchronous iterator initialization steps for FontFaceIterator are as follows:
  1. Set this's [[FontList]] to a new empty queue.

  2. For each local font font on the system, run these steps:

    1. Let font face be a new FontFace describing font. The font face is not CSS-connected.

    2. Set font face’s [[FontStatusPromise]] slot to a new fulfilled promise.

    3. Set font face’s [[Urls]] slot to null.

    4. Set font face’s [[Data]] slot to null.

      Modify the definition of FontFace to allow [[Urls]] and [[Data]] to both be null.

      Are there further details about the FontFace objects we need to specify here?

    5. Enqueue font face to this's [[FontList]].

Note: User agents are expected to actually populate the iterator’s queue asynchronously and possibly lazily, although this is not observable.

To get the next iteration result for FontFaceIterator, run the following steps:
  1. Let promise be a new promise.

  2. If this's [[FontList]] is empty, then:

    1. Resolve promise with undefined.

  3. Otherwise:

    1. Let font face be the result of dequeuing from this's [[FontList]].

    2. Resolve promise with font face.

  4. Return promise.

6.2. FontFace additions

await metadata = fontFace . getMetadata()

Request additional metadata about a FontFace. The returned object metadata contains properties describing fontFace in more detail.

This method is only usable for local fonts, and will throw an exception if called for a web font.

[Exposed=(Window,Worker)]
partial interface FontFace {
  [SecureContext] Promise<FontFaceMetadata> getMetadata();
};

The getMetadata() method, when invoked, must run these steps:

  1. Let promise be a new promise.

  2. If either of this's [[Urls]] or [[Data]] slots are not null, then reject promise with a TypeError.

  3. Otherwise, run these steps in parallel:

    1. Let permission be the result of requesting permission to use "local-fonts".

    2. If permission is not "granted", then reject promise with a "NotAllowedError" DOMException, and abort these steps.

    3. Let metadata be a newly created FontFaceMetadata populated with more details about this's font.

    4. Resolve promise with metadata.

  4. Return promise.

dictionary FontFaceMetadata {
  USVString instanceName;
  USVString postScriptName;
  USVString fullName;
  boolean isVariable;
  boolean isColor;
};

A FontFaceMetadata provides details about a font face. The descriptions below assume the font is an OpenType [OPENTYPE] font.

The instanceName member corresponds to name ID 2 in the font’s name table (the font subfamily name.) Example: "Bold".

The postScriptName member corresponds to name ID 6 in the font’s name table (the PostScript name.) Example: "Arial-Bold".

The fullName member corresponds to name ID 4 in the font’s name table (the full font name.) Example: "Arial Bold".

Include name ID 3 (Unique identifier) as well?

The isVariable member is true if the font incorporates multiple faces; the presence of a fvar table indicates support.

The isColor member is true if the font contains multi-colored glyphs; the presence of a COLR table indicates support.

Should user agents that support SBIX, CBDT, SVG etc also set this flag?

7. Internationalization considerations

Document internationalization consideration, e.g. string localization

7.1. Font Names

The name table in OpenType [OPENTYPE] fonts allows names (family, subfamily, etc) to have multilingual strings, using either platform-specific numeric language identifiers or language-tag strings conforming to [BCP47]. For example, a font could have family name strings defined for both "en" and "zh-Hant-HK".

The FontFaceMetadata properties instanceName, postScriptName, and fullName are provided by this API simply as strings, using the "en" locale. This matches the behavior of the FontFace family property.

Web applications that need to provide names in other language can request and parse the name table directly using the Font Table Access API.

Should we define an option to the query() method to specify the desired language for strings (e.g. {lang: 'zh'}), falling back to "en" if not present?

8. Accessibility considerations

There are no known accessibility impacts of this feature.

9. Security considerations

There are no known security impacts of this feature.

10. Privacy considerations

10.1. Fingerprinting

The font list includes:

This provides several "bits of entropy" to distinguish users.

User agents could mitigate this in certain cases (e.g. when the permission is denied, or in Private Browsing / "incognito" mode) by providing an enumeration of a fixed set of fonts provided with the user agent.

10.2. Identification

Users from a particular organization could have specific fonts installed. Employees of "Example Co." could all have an "Example Corporate Typeface" installed by their system administrator, which would allow distinguishing users of a site as employees.

There are services which create fonts based on handwriting samples. If these fonts are given names including personally identifiable information (e.g. "Alice’s Handwriting Font"), then personally identifiable information would be made available. This may not be apparent to users if the information is included as properties within the font, not just the font name.

11. Acknowledgements

We’d like to acknowledge the contributions of:

Special thanks (again!) to Tab Atkins, Jr. for creating and maintaining Bikeshed, the specification authoring tool used to create this document.

And thanks to Chase Phillips, Dominik Röttsches, and Igor Kopylov for suggestions, reviews, and other feedback.

Conformance

Conformance requirements are expressed with a combination of descriptive assertions and RFC 2119 terminology. The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in the normative parts of this document are to be interpreted as described in RFC 2119. However, for readability, these words do not appear in all uppercase letters in this specification.

All of the text of this specification is normative except sections explicitly marked as non-normative, examples, and notes. [RFC2119]

Examples in this specification are introduced with the words “for example” or are set apart from the normative text with class="example", like this:

This is an example of an informative example.

Informative notes begin with the word “Note” and are set apart from the normative text with class="note", like this:

Note, this is an informative note.

Index

Terms defined by this specification

Terms defined by reference

References

Normative References

[CSS-FONT-LOADING-3]
Tab Atkins Jr.. CSS Font Loading Module Level 3. 22 May 2014. WD. URL: https://www.w3.org/TR/css-font-loading-3/
[HTML]
Anne van Kesteren; et al. 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]
Mounir Lamouri; Marcos Caceres; Jeffrey Yasskin. Permissions. 25 September 2017. WD. URL: https://www.w3.org/TR/permissions/
[RFC2119]
S. Bradner. Key words for use in RFCs to Indicate Requirement Levels. March 1997. Best Current Practice. URL: https://tools.ietf.org/html/rfc2119
[WebIDL]
Boris Zbarsky. Web IDL. 15 December 2016. ED. URL: https://heycam.github.io/webidl/

Informative References

[BCP47]
A. Phillips; M. Davis. Tags for Identifying Languages. September 2009. IETF Best Current Practice. URL: https://tools.ietf.org/html/bcp47

IDL Index

[SecureContext]
interface mixin NavigatorFonts {
  [SameObject] readonly attribute FontManager fonts;
};
Navigator includes NavigatorFonts;
WorkerNavigator includes NavigatorFonts;

[SecureContext,
 Exposed=(Window,Worker)]
interface FontManager {
  FontFaceIterator query();
};

[SecureContext,
 Exposed=(Window,Worker)]
interface FontFaceIterator {
  /*async*/ iterable<FontFace>;
};

[Exposed=(Window,Worker)]
partial interface FontFace {
  [SecureContext] Promise<FontFaceMetadata> getMetadata();
};

dictionary FontFaceMetadata {
  USVString instanceName;
  USVString postScriptName;
  USVString fullName;
  boolean isVariable;
  boolean isColor;
};

Issues Index

Define any new concepts beyond just the API
Modify the definition of FontFace to allow [[Urls]] and [[Data]] to both be null.
Are there further details about the FontFace objects we need to specify here?
Verify source for all of the above. See Microsoft Typography
Include name ID 3 (Unique identifier) as well?
Should user agents that support SBIX, CBDT, SVG etc also set this flag?
Document internationalization consideration, e.g. string localization
Should we define an option to the query() method to specify the desired language for strings (e.g. {lang: 'zh'}), falling back to "en" if not present?