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 can offer the ability 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.

3.4. Requesting specific fonts

In some cases, a web application may wish to request access to specific fonts. For example, it may be presenting previously authored content that embeds font names. The query() call takes a select option that scopes the request to fonts identified by PostScript names. Only matching fonts will be returned.

User agents may provide a different user interface to support this. For example, if the fingerprinting risk is deemed minimal, the request may be satisfied without prompting the user for permission. Alternately, a picker could be shown with only the requested fonts included.

// User activation is required.
requestFontsButton.onclick = async function() {
  try {
    const array = await navigator.fonts.query({select: ['Verdana', 'Verdana-Bold', 'Verdana-Italic']});

    array.forEach(metadata => {
      console.log(<code data-opaque bs-autolink-syntax='`Access granted for ${metadata.postscriptName}`'>Access granted for ${metadata.postscriptName}</code>);
    });

  } catch(e) {
    // Handle error. It could be a permission error.
    console.warn(<code data-opaque bs-autolink-syntax='`Local font access not available: ${e.message}`'>Local font access not available: ${e.message}</code>);
  }
};

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.

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]

4.4. Metrics Table

A font representation has a metrics table, which is the font table in its table list with tag `OS/2`. The table is a mapping from a string to a value.

A font representation's italic property is true if its metrics table has a `fcSelection` entry (a 16-bit unsigned number), and if bit 0 of the entry’s value is 1, or false otherwise.

A font representation's stretch property is the value of its metrics table's `usWidthClass` entry (a 16-bit unsigned number) if present, with the value mapped according to the following table, or 1.00 (the default) otherwise:

`usWidthClass` stretch property common name
1 0.50 ultra-condensed
2 0.625 extra-condensed
3 0.75 condensed
4 0.825 semi-condensed
5 1.00 normal
6 1.125 semi-expanded
7 1.25 expanded
8 1.50 extra-expanded
9 2.00 ultra-expanded

A font representation's weight property is the value of its metrics table's `usWeightClass` entry (a 16-bit unsigned number, in the range 1 to 1000) if present, or 400 (the default) otherwise.

Give wiggle room for other font definitions/APIs to provide values here. Maybe something like "In OpenType font definitions, the foo property is given by .... In other font definitions, an equivalent value should be inferred."

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

await navigator . fonts . query())

Asynchronously query for available/allowed fonts. The returned promise resolves to an array of FontMetadata objects.

If the {persistentAccess} option is true, the user will be prompted for permission for ongoing access to query fonts without further prompts. If the option is not passed, the user agent will prompt the user to select fonts.

If the {select} option is a non-empty array, then only fonts with matching PostScript names will be included in the results.

[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 {
  Promise<sequence<FontMetadata>> query(optional QueryOptions options = {});
};

dictionary QueryOptions {
  boolean persistentAccess = false;
  sequence<DOMString> select = [];
};
The query(options) method steps are:
  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 select be options"select" member.

    2. If options"persistentAccess" member is true, then run these steps:

      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 fonts be list of all local fonts on the system.

    3. Otherwise, let fonts be a list of fonts on the system selected by the user.

    4. Let result be an new list.

    5. For each font font in fonts, run these steps:

      1. Let representation be a font representation for font.

      2. Let postscriptName be representation’s name string 6 for `en`.

      3. If select is empty, or if select contains postscriptName, then append a new FontMetadata instance associated with representation to result.

    6. Sort list in ascending order by using postscriptName as the sort key and store the result as list.

    7. Resolve promise with list.

  4. Return promise.

Make "selected by the user" more spec-like.

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. This corresponds with the CSS font-family property. Example: "Arial"

metadata . style

The font style (or subfamily) name. Example: "Regular", "Bold Italic"

metadata . italic

Returns true if this font is labeled as italic or oblique, false otherwise. This corresponds with the CSS font-style property’s italic value.

metadata . stretch

Returns the stretch or width of the font, as a number from 0.5 (50%) to 2.0 (200%), with a default of 1.0 (100%). This corresponds with the CSS font-stretch property numeric value.

metadata . style

Returns the weight of the font, as a number from 1 to 1000, with a default of 400. This corresponds with the CSS font-weight property numeric value.

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

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

  // Metrics
  readonly attribute boolean italic;
  readonly attribute float stretch;
  readonly attribute float weight;
};

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.

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

The italic getter steps are to return this's associated font representation's italic property.

The stretch getter steps are to return this's associated font representation's stretch property.

The weight getter steps are to return this's associated font representation's weight property.

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 blob be a new Blob whose contents are this's data bytes and type attribute is `application/octet-stream`.

    2. 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, family, and style 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 metadata 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

Document conventions

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.

Conformant Algorithms

Requirements phrased in the imperative as part of algorithms (such as "strip any leading space characters" or "return false and abort these steps") are to be interpreted with the meaning of the key word ("must", "should", "may", etc) used in introducing the algorithm.

Conformance requirements phrased as algorithms or specific steps can be implemented in any manner, so long as the end result is equivalent. In particular, the algorithms defined in this specification are intended to be easy to understand and are not intended to be performant. Implementers are encouraged to optimize.

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. 4 June 2021. 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. 17 June 2021. 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
[SFNT]
Spline/Scalable font format. January 2019. URL: https://www.iso.org/obp/ui/#iso:std:iso-iec:14496:-22:ed-4:v1:en
[WebIDL]
Boris Zbarsky. Web IDL. 15 December 2016. ED. URL: https://heycam.github.io/webidl/

Informative References

[BCP47]
A. Phillips, Ed.; M. Davis, Ed.. Tags for Identifying Languages. September 2009. Best Current Practice. URL: https://datatracker.ietf.org/doc/html/rfc5646
[CSS-FONTS-3]
John Daggett; Myles Maxfield; Chris Lilley. CSS Fonts Module Level 3. 20 September 2018. REC. URL: https://www.w3.org/TR/css-fonts-3/
[CSS-FONTS-4]
John Daggett; Myles Maxfield; Chris Lilley. CSS Fonts Module Level 4. 17 November 2020. WD. URL: https://www.w3.org/TR/css-fonts-4/
[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 {
  Promise<sequence<FontMetadata>> query(optional QueryOptions options = {});
};

dictionary QueryOptions {
  boolean persistentAccess = false;
  sequence<DOMString> select = [];
};

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

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

  // Metrics
  readonly attribute boolean italic;
  readonly attribute float stretch;
  readonly attribute float weight;
};

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?
Give wiggle room for other font definitions/APIs to provide values here. Maybe something like "In OpenType font definitions, the foo property is given by .... In other font definitions, an equivalent value should be inferred."
Make "selected by the user" more spec-like.
Verify source for all of the above. See Microsoft Typography
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?