Local Font Access API

Draft Community Group Report,

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
Inline In Spec
Editors:
(Google Inc.)
(Google Inc.)
(Google Inc.)
Former Editor:
Emil A. Eklund

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 access to the raw table data of fonts, allowing for more detailed custom text rendering.

Status of this document

This specification was published by the Web Platform Incubator Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.

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. For each font, low-level (byte-oriented) access to an SFNT [SFNT] container that includes full font data.

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:

While the web has its origins as a text-focused medium and user agents provide very high quality typography support, they have limitations that impact some classes of web-based applications:

Professional-quality design and graphics tools have historically been difficult to deliver on the web. These tools provide extensive typographic features and controls as core capabilities.

This API provides these tools access to the same underlying data tables that browser layout and rasterization engines use for drawing text. Examples include the OpenType [OPENTYPE] glyf table for glyph vector data, the GPOS table for glyph placement, and the GSUB table for ligatures and other glyph substitution. This information is necessary for these tools in order to guarantee both platform-independence of the resulting output (by embedding vector descriptions rather than codepoints) and to enable font-based art (treating fonts as the basis for manipulated shapes).

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:

3.3. Accessing font data

The API allows script to request font data, providing access to the internal tables of fonts.

4. Concepts

Define any new concepts beyond just the API

4.1. Font Representation

A font representation is an OpenType [OPENTYPE] definition of a font. Even if the font was originally described in another file format, it is assumed that if it is supported by a user agent then an OpenType representation can be derived for it. This includes True Type [TrueType], Web Open Font Format 1.0 [WOFF] and Web Open Font Format 2.0 [WOFF2] files.

A font representation is serialized in SFNT [SFNT] format, a flexible and extensible tabled-based container format which can contain font data in a multitude of other formats.

An SFNT [SFNT] container is represented in data bytes, which is a byte sequence encoding a table list, a list of font tables.

4.2. Font Table

A font table is an OpenType [OPENTYPE] table.

A font table has a tag, which is a ByteString of length 4, derived from the Tag of the table record.

A font table has table bytes, which are a byte sequence corresponding to the table data.

4.3. Name Table

A font representation has a name table, which is the font table in its table list with tag `name`.

The name table has a map names, which is a mapping from an unsigned short to a localized string table.

A localized string table is a map from a BCP 47 language tag to a USVString. [BCP47]

A font representation's name string id for tag is given by these steps:

  1. Let names be the font representation's name table's names.

  2. Let table be names[id].

  3. Return table[tag].

What if there is no matching id or tag? Empty string? Where does fallback occur?

The current language is the BCP 47 language tag returned by the NavigatorLanguage mixin’s language propertyy. [BCP47]

5. Local font access permission

Enumeration of local fonts requires a permission to be granted.

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

6. API

6.1. Font manager

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

Asynchronously iterate over the available fonts. Each time through the loop, metadata will be a new FontMetadata 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 getter steps are to return this's relevant settings object's FontManager object.

[SecureContext,
 Exposed=(Window,Worker)]
interface FontManager {
  FontIterator query();
};
The query() method steps are:

This is returning an async iterable; should it be defined using Promise jargon here?

Do we want query() to implicitly request the permission?

  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 "font-access".

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

    3. Resolve promise with a newly created FontIterator.

  4. Return promise.

[SecureContext,
 Exposed=(Window,Worker)]
interface FontIterator {
  async iterable<FontMetadata>;
};

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

The asynchronous iterator initialization steps for FontIterator are as follows:
  1. Create a new empty queue, temp queue.

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

    1. Let representation be a font representation for font.

    2. Enqueue representation to temp queue.

  3. Sort temp queue in ascending order by using postscriptName as the sort key and store the result as temp queue.

  4. Set this's [[FontList]] to a new empty queue.

  5. For each representation in temp queue, run these steps:

    1. Enqueue representation to this's [[FontList]].

Make it clear that the user agent can filter fonts and/or prompt the user to select a subset of system fonts.

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 FontIterator, 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 representation be the result of dequeuing from this's [[FontList]].

    2. Let metadata be a new FontMetadata instance associated with representation.

    3. Resolve promise with metadata.

  4. Return promise.

6.2. The FontMetadata interface

A FontMetadata provides details about a font face. Each FontMetadata has an associated font representation.

metadata . postscriptName

The PostScript name for the font. Example: "Arial-Bold". The OpenType spec expects this to be encoded in a subset of ASCII and is unique for metadata

metadata . fullName

The full font name, including family subfamily names. Example: "Arial Bold"

metadata . family

The font family name. Example: "Arial"

[Exposed=(Window,Worker)]
interface FontMetadata {
  Promise<Blob> blob();

  readonly attribute USVString postscriptName;
  readonly attribute USVString fullName;
  readonly attribute USVString family;
};

The postscriptName getter steps are to return this's associated font representation's name string 6 for `en`.

The fullName getter steps are to return this's associated font representation's name string 4 for the current language.

The family getter steps are to return this's associated font representation's name string 1 for the current language.

Include name ID 2 (Font subfamily/style, e.g. "Regular", "Bold", etc.) as well?

Include name ID 3 (Unique identifier) as well?

await blob = metadata . blob()

Request the font data of metadata. The result blob contains data bytes.

The blob() method steps are:

  1. Let promise be a new promise.

  2. Run these steps in parallel:

    1. Let permission be the result of requesting permission to use "font-access".

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

    3. Let blob be a new Blob whose contents are this's data bytes and type attribute is `application/octet-stream`.

    4. Resolve promise with blob.

  3. Return promise.

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 FontMetadata properties postscriptName, fullName, and family are provided by this API simply as strings, using the `en` locale. This matches the behavior of the FontFace family property.

The above does not match the spec/implementation. Resolve the ambiguity.

Web applications that need to provide names in other languages can request and parse the `name` table directly.

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.

User agents may also allow the user to select a set of fonts to make available via the API.

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, Domenic Denicola, Dominik Röttsches, Igor Kopylov, and Jake Archibald

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/
[FileAPI]
Marijn Kruisselbrink; Arun Ranganathan. File API. 31 May 2019. WD. URL: https://www.w3.org/TR/FileAPI/
[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/
[PROMISES-GUIDE]
Domenic Denicola. Writing Promise-Using Specifications. 9 November 2018. TAG Finding. URL: https://www.w3.org/2001/tag/doc/promises-guide
[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
[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. 13 December 2012. REC. URL: https://www.w3.org/TR/WOFF/
[WOFF2]
Vladimir Levantovsky; Raph Levien. WOFF File Format 2.0. 1 March 2018. REC. URL: https://www.w3.org/TR/WOFF2/

IDL Index

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

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

[SecureContext,
 Exposed=(Window,Worker)]
interface FontIterator {
  async iterable<FontMetadata>;
};

[Exposed=(Window,Worker)]
interface FontMetadata {
  Promise<Blob> blob();

  readonly attribute USVString postscriptName;
  readonly attribute USVString fullName;
  readonly attribute USVString family;
};

Issues Index

Define any new concepts beyond just the API
What if there is no matching id or tag? Empty string? Where does fallback occur?
This is returning an async iterable; should it be defined using Promise jargon here?
Do we want query() to implicitly request the permission?
Make it clear that the user agent can filter fonts and/or prompt the user to select a subset of system fonts.
Verify source for all of the above. See Microsoft Typography
Include name ID 2 (Font subfamily/style, e.g. "Regular", "Bold", etc.) as well?
Include name ID 3 (Unique identifier) as well?
Document internationalization consideration, e.g. string localization
The above does not match the spec/implementation. Resolve the ambiguity.
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?