diff --git a/README.md b/README.md
index e5a519e..7c75fcb 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
# Local Font Access Explained
> August 14th, 2018
-> Last Update: September 1st, 2020
+> Last Update: October 7th, 2020
>
> Alex Russell ``
> Josh Bell ``
@@ -15,12 +15,13 @@
## What’s all this then?
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.
One stumbling block has been an inability to access and use the full variety of professionally constructed and hinted fonts which designers have locally installed. The web's answer to this situation has been the introduction of [Web Fonts](https://developer.mozilla.org/en-US/docs/Learn/CSS/Styling_text/Web_fonts) which are loaded dynamically by browsers and are subsequently available to use via CSS. This level of flexibility enables some publishing use-cases but fails to fully enable high-fidelity, platform independent vector-based design tools for several reasons:
* System font engines (and browser stacks) may display certain glyphs differently. These differences are necessary, in general, to create fidelity with the underlying OS (so web content doesn't "look wrong"). These differences reduce consistency for applications that span across multiple platforms, e.g. when pixel-accurate layout and rendering is required.
* Design tools need access to font bytes to do their own OpenType layout implementation and allow design tools to hook in at lower levels, for actions such as performing vector filters or transforms on the glyph shapes.
-* Developers may have legacy font stacks for their applications that they are bringing to the web. To use these stacks, they usually require direct access to font data, something web fonts do not provide.
+* Developers may have custom font handling strategies for their applications they are bringing to the web. To use these strategies, they usually require direct access to font data, something web fonts do not provide.
* Some fonts may not be licensed for delivery over the web. For example, Linotype has a license for some fonts that only includes desktop use.
We propose a two-part API to help address this gap:
@@ -28,7 +29,7 @@ We propose a two-part API to help address this gap:
* A font enumeration API, which allows users to grant access to the full set of available system fonts.
* From each enumeration result, the ability to request low-level (byte-oriented) SFNT container access that includes the full font data.
-Taken together, these provide high-end tools access to the same underlying data tables that browser layout and rasterization engines use for drawing text. Examples of these data tables include the [glyf](https://docs.microsoft.com/en-us/typography/opentype/spec/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).
+The API provides the aforementioned tools access to the same underlying data tables that browser layout and rasterization engines use for drawing text. Examples of these data tables include the [glyf](https://docs.microsoft.com/en-us/typography/opentype/spec/glyf) table for glyph vector data, the [GPOS](https://docs.microsoft.com/en-us/typography/opentype/spec/gpos) table for glyph placement, and the [GSUB](https://docs.microsoft.com/en-us/typography/opentype/spec/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).
Note that this implies that the web application provides its own shaper and libraries for Unicode, bidirectional text, text segmentation, and so on, duplicating the user agent and/or operating system's text stack. See the "Considered alternatives" section below.
@@ -44,11 +45,10 @@ A successful API should:
* Allow multiple levels of privacy preservation; e.g. full access for "trusted" sites and degraded access for untrusted scenarios
* Reflect local font access state in the [Permissions API](https://developer.mozilla.org/en-US/docs/Web/API/Permissions_API)
* Provide the ability to uniquely identify a specific font in the case of conflicting names (e.g. Web Font aliases vs. local PostScript font names)
- * Enable access to all [browser-allowed font tables](https://chromium.googlesource.com/external/ots/+/master/docs/DesignDoc.md) (may vary per browser)
* Enable a memory efficient implementation, avoiding leaks and copies by design
- * Shield applications from unnecessary complexity by requiring that browser implementations produce valid OpenType data in the returned data
- * Restrict access to local font data to Secure Contexts and to only the top-most frame by default via the [Feature Policy](https://wicg.github.io/feature-policy) spec
- * Sort any result list by font name to reduce possible fingerprinting entropy bits; e.g. .query() returns an iterator which will be sorted by Unicode code point given font names
+ * Shield applications from unnecessary complexity by requiring that browser implementations produce valid SFNT data in the returned data
+ * Restrict access to local font data to Secure Contexts and to only the top-most frame by default via the [Permissions Policy](https://w3c.github.io/webappsec-permissions-policy/) spec
+ * Sort any result list by font name to reduce possible fingerprinting entropy bits; e.g. .query() returns an iterable which will be [sorted](https://infra.spec.whatwg.org/#list-sort-in-ascending-order) by given font names
#### Possible/Future Goals
@@ -72,7 +72,7 @@ This API will not try to:
## Key scenarios
-> Note: Earlier versions of this document attempted to sketch out two versions of each API; one based on `FontFaceSource` and the other the fully-asynchronous version that survives in this doc. While attractive from a re-use perspective, [`FontFaceSource`](https://drafts.csswg.org/css-font-loading/#font-face-source) (and the implied global `window.fonts`) implies synchronous iteration over a potentially unbounded (and perhaps slow) set of files, and each item may require synchronous IPCs and I/O. This, combined with the lack of implementations of `FontFaceSource` caused us to abandon this approach.
+> Note: Earlier versions of this document attempted to sketch out two versions of each API; one based on `FontFaceSet` and the other the fully-asynchronous version that survives in this doc. While attractive from a re-use perspective, [`FontFaceSet`](https://drafts.csswg.org/css-font-loading/#fontfaceset) (and the implied global `document.fonts`) implies synchronous iteration over a potentially unbounded (and perhaps slow) set of files, and each item may require synchronous IPCs and I/O. This, combined with the lack of implementations of `FontFaceSet` caused us to abandon this approach.
### Enumerating Local Fonts
@@ -87,20 +87,25 @@ Font enumeration can help by enabling:
```js
// Asynchronous Query and Iteration
(async () => { // Async block
- // May prompt the user:
- const status = await navigator.permissions.request({ name: "local-fonts" });
- if (status.state !== "granted")
+ const status = await navigator.permissions.query({ name: "font-access" });
+ if (status.state === "denied")
throw new Error("Cannot enumerate local fonts");
// This sketch returns individual FontMetadata instances rather than families:
// In the future, query() could take filters e.g. family name, and/or options
// e.g. locale.
- const fonts_iterator = navigator.fonts.query();
-
- for await (const metadata of fonts_iterator) {
- console.log(metadata.postscriptName);
- console.log(metadata.fullName);
- console.log(metadata.family);
+ const iterable = navigator.fonts.query();
+
+ try {
+ // May prompt the user:
+ for await (const metadata of iterable) {
+ console.log(metadata.postscriptName);
+ console.log(metadata.fullName);
+ console.log(metadata.family);
+ }
+ } catch(e) {
+ // Handle error. It could be a permission error.
+ throw new Error(e);
}
})();
```
@@ -110,26 +115,30 @@ Font enumeration can help by enabling:
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 can allow the user to select from a richer set of choices:
```js
-const font_select = document.createElement("select");
-font_select.onchange = e => {
- console.log("selected:", font_select.value);
+const fontSelect = document.createElement("select");
+fontSelect.onchange = e => {
+ console.log("selected:", fontSelect.value);
// Use the selected font to style something here.
};
-document.body.appendChild(font_select);
+document.body.appendChild(fontSelect);
(async () => { // Async block
- // May prompt the user:
- const status = await navigator.permissions.request({ name: "local-fonts" });
- if (status.state !== "granted")
+ const status = await navigator.permissions.query({ name: "font-access" });
+ if (status.state === "denied")
throw new Error("Cannot continue to style with local fonts");
- for await (const metadata of navigator.fonts.query()) {
- const option = document.createElement("option");
- option.text = metadata.fullName;
- option.value = metadata.fullName;
- option.setAttribute("postscriptName", metadata.postscriptName);
- font_select.append(option);
+ try {
+ // May prompt the user:
+ for await (const metadata of navigator.fonts.query()) {
+ const option = document.createElement("option");
+ option.text = metadata.fullName;
+ option.value = metadata.postscriptName;
+ fontSelect.append(option);
+ }
+ } catch(e) {
+ // Handle error. It could be a permission error.
+ throw new Error(e);
}
})();
```
@@ -140,38 +149,38 @@ Here we use enumeration and new APIs on `FontMetadata` to access a full and vali
```js
(async () => { // Async block
- // May prompt the user
- const status = await navigator.permissions.request({ name: "local-fonts" });
- if (status.state !== "granted")
+ const status = await navigator.permissions.query({ name: "font-access" });
+ if (status.state === "denied")
throw new Error("Cannot continue to style with local fonts");
- for await (const metadata of navigator.fonts.query()) {
- // Looking for a specific font:
- if (metadata.postscriptName !== "Consolas")
- continue;
-
- // blob()' returns a Blob containing valid and complete SFNT
- // wrapped font data.
- const sfnt = await metadata.blob();
-
- const sfntVersion = (new TextDecoder).decode(
- // Slice out only the bytes we need: the first 4 bytes are the SFNT
- // version info.
- // Spec: https://docs.microsoft.com/en-us/typography/opentype/spec/otff#organization-of-an-opentype-font
- await sfnt.slice(0, 4).arrayBuffer());
-
- let outlineFormat = "UNKNOWN";
- switch (sfntVersion) {
- case '\x00\x01\x00\x00':
- case 'true':
- case 'typ1':
- outlineFormat = "truetype";
- break;
- case 'OTTO':
- outlineFormat = "cff";
- break;
+ try {
+ // May prompt the user
+ for await (const metadata of navigator.fonts.query()) {
+ // blob()' returns a Blob containing valid and complete SFNT
+ // wrapped font data.
+ const sfnt = await metadata.blob();
+
+ // Slice out only the bytes we need: the first 4 bytes are the SFNT
+ // version info.
+ // Spec: https://docs.microsoft.com/en-us/typography/opentype/spec/otff#organization-of-an-opentype-font
+ const sfntVersion = await sfnt.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(`${metadata.fullName} outline format: ${outlineFormat}`);
}
- console.log("Consolas outline format:", outlineFormat);
+ } catch(e) {
+ // Handle error. It could be a permission error.
+ throw new Error(e);
}
})();
```
@@ -274,5 +283,5 @@ We'd like to acknowledge the contributions of:
* Daniel Nishi, Owen Campbell-Moore, and Mike Tsao who helped pioneer the previous local font access proposal
* Evan Wallace, Biru, Leah Cassidy, Katie Gregorio, Morgan Kennedy, and Noah Levin of Figma who have patiently enumerated the needs of their ambitious web product.
* Tab Atkins and the CSS Working Group who have provided usable base-classes which only need slight extension to enable these cases
-* Dominik Röttsches and Igor Kopylov for their thoughtful feedback
+* Domenic Denicola, Dominik Röttsches, Igor Kopylov and Jake Archibald for their thoughtful feedback
* Lastly, we would like to express our gratitude to former editor Emil A. Eklund, who passed away in 2020. Emil was instrumental in getting this proposal underway, providing technical guidance, and championing the needs of users and developers
diff --git a/index.bs b/index.bs
index f85fec8..81fbddc 100644
--- a/index.bs
+++ b/index.bs
@@ -15,10 +15,8 @@ Assume Explicit For: yes
Markup Shorthands: markdown yes, css yes
Complain About: accidental-2119 yes, missing-example-ids yes
Favicon: logo-font-enumeration.svg
+Test Suite: https://github.com/web-platform-tests/wpt/tree/master/font-access
-
spec: webidl; urlPrefix: https://heycam.github.io/webidl/
@@ -62,7 +60,7 @@ div.head h1 { clear: left; }
# Introduction # {#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 the various OpenType tables is provided.
+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.
@@ -74,10 +72,14 @@ 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:
-* System font engines (and browser stacks) may handle the parsing and display of certain glyphs differently. These differences are necessary, in general, to create fidelity with the underlying OS (so web content doesn't "look wrong"). These differences reduce fidelity.
-* Web application developers may have legacy font stacks for their applications which they are bringing to the web. To use these engines, they usually require direct access to font data; something Web Fonts do not provide.
+* System font engines (and browser stacks) may display certain glyphs differently. These differences are necessary, in general, to create fidelity with the underlying OS (so web content doesn't "look wrong"). These differences reduce consistency for applications that span across multiple platforms, e.g. when pixel-accurate layout and rendering is required.
+* Design tools need access to font bytes to do their own OpenType layout implementation and allow design tools to hook in at lower levels, for actions such as performing vector filters or transforms on the glyph shapes.
+* Developers may have custom font handling strategies for their applications that they are bringing to the web. To use these strategies, they usually require direct access to font data, something Web Fonts do not provide.
+* Some fonts may not be licensed for delivery over the web. For example, Linotype has a license for some fonts that only includes desktop use.
-This API provide high-end 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).
+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).
@@ -87,13 +89,16 @@ This API provide high-end tools access to the same underlying data tables that b
The API should:
* Provide efficient enumeration of all local fonts without blocking the main thread
+* Ensure UAs are free to return anything they like. If a browser implementation prefers, they may choose to only provide a set of default fonts built into the browser.
* Be available from Workers
* Allow multiple levels of privacy preservation; e.g., full access for "trusted" sites and degraded access for untrusted scenarios
* Reflect local font access state in the Permissions API
* Restrict access to local font data to Secure Contexts
* Provide unique identification of families and instances (variants like "bold" and "italic"), including PostScript names
-* Provide access to all browser-allowed font tables (may vary per browser)
+* Shield applications from unnecessary complexity by requiring that browser implementations produce valid SFNT data in the returned data
* Enable a memory efficient implementation, avoiding leaks and copies by design
+* Restrict access to local font data to Secure Contexts and to only the top-most frame by default via the Permissions Policy spec
+* Sort any result list by font name to reduce possible fingerprinting entropy bits; e.g. .query() returns an iterable which will be sorted by given font names
-## Accessing table data ## {#example-accessing-table-data}
+## Accessing font data ## {#example-accessing-font-data}
-The API allows script to request the internal tables of fonts.
+The API allows script to request font data, providing access to the internal tables of fonts.
-
-Issue: Include `name` ID 2 (Font subfamily, e.g. "Regular") as well?
+Issue: Include `name` ID 2 (Font subfamily/style, e.g. "Regular", "Bold", etc.) as well?
Issue: Include `name` ID 3 (Unique identifier) as well?
- : await |map| = |metadata| . {{FontMetadata/getTables()}}
- : await |map| = |metadata| . {{FontMetadata/getTables()|getTables}}(|tableNames|)
- :: Request the internal tables of |metadata|. The result |map| is {{FontTableMap}} with an API like a {{ECMAScript/Map}}, where the keys are matching table names and the values are {{Blob}}s with the table binary data. If |tableNames| is not specified, all tables are returned.
+ : await |blob| = |metadata| . {{FontMetadata/blob()}}
+ :: Request the font data of |metadata|. The result |blob| contains [=font representation/data bytes=].
-The getTables(|tableNames|) method steps are:
+The blob() method steps are:
1. Let |promise| be [=/a new promise=].
-1. Otherwise, if |tableNames| was given but is [=list/empty=], then [=/reject=] |promise| with a {{TypeError}}.
-1. Otherwise, run these steps [=in parallel=]:
- 1. Let |permission| be the result of [=requesting permission to use=] {{PermissionName/"local-fonts"}}.
+1. Run these steps [=in parallel=]:
+ 1. Let |permission| be the result of [=requesting permission to use=] {{PermissionName/"font-access"}}.
1. If |permission| is not {{PermissionState/"granted"}}, then [=/reject=] |promise| with a "{{NotAllowedError}}" {{DOMException}}, and abort these steps.
- 1. Let |map| be a new {{FontTableMap}}.
- 1. Let |backing| be |map|'s \[[BackingMap]] (an {{ECMAScript/Map}}.)
- 1. Let |font| be [=/this=]'s [=/font representation=].
- 1. [=list/For each=] |table| of |font|'s [=font representation/table list=]:
- 1. Let |tag| be |table|'s [=font table/tag=].
- 1. If |tableNames| was given and does not [=list/contain=] |tag|, then [=iteration/continue=].
- 1. Let |blob| be a new {{Blob}} whose contents are |table|'s [=font table/data bytes=] and {{Blob/type}} attribute is \``application/octet-stream`\`.
- 1. Append the [=ecma-record|Record=] { \[[Key]]: |tag|, \[[Value]]: |blob| } to |backing|.\[[MapData]].
- 1. [=/Resolve=] |promise| with |map|.
+ 1. Let |blob| be a new {{Blob}} whose contents are [=this=]'s [=font representation/data bytes=] and {{Blob/type}} attribute is \``application/octet-stream`\`.
+ 1. [=/Resolve=] |promise| with |blob|.
1. Return |promise|.
-Issue: Table order? If returned in requested order, what if the |tableNames| argument is omitted? (Observable by iterating the map.)
-
-Issue: What is the expected behavior if requested tables do not exist? If developers call `getTables()` with one name, they might expect a rejection if the table does not exist. On the other hand, if they call `getTables()` with a set of names they might expect all matching tables to be provided with no rejection. The above algorithm provides the latter behavior.
-
@@ -568,6 +574,9 @@ Special thanks (again!) to Tab Atkins, Jr. for creating and maintaining [Bikeshe
And thanks to
Chase Phillips,
-Dominik Röttsches, and
-Igor Kopylov
+Domenic Denicola,
+Dominik Röttsches,
+Igor Kopylov, and
+Jake Archibald
+
for suggestions, reviews, and other feedback.