GithubHelp home page GithubHelp logo

wicg / local-font-access Goto Github PK

View Code? Open in Web Editor NEW
75.0 20.0 16.0 448 KB

Web API for enumerating fonts on the local system

Home Page: https://wicg.github.io/local-font-access

License: Apache License 2.0

Bikeshed 100.00%
shipping-chromium

local-font-access's Introduction

Local Font Access Explained

CI

August 14th, 2018
Last Update: April 6th, 2022

Josh Bell <[email protected]>

Table of Contents generated with DocToc

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 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 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:

  • A font enumeration API, which allows users to grant access to the full set of available system font metadata.
  • From each enumeration result, the ability to request low-level (byte-oriented) SFNT container access that includes the full font data.

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 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).

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.

NOTE: Long term, we expect that this proposal would merge into an existing CSS-related spec rather than stand on its own.

Goals

A successful API should:

  • Where allowed, 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, for security and privacy, 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
  • 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 a memory efficient implementation, avoiding leaks and copies by design
  • 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 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
  • Provide access to commonly used properties of fonts (for example, metrics used in CSS selectors, or when building font selection UI) without requiring parsing every font.

Possible/Future Goals

  • Direct access to localized font names (can be done via data API)
  • Access to font table data for web (network-loaded) fonts
  • Registration of new font families (extensibility)
  • Additional metadata available during enumeration (ascender, descender, baseline, x-height, etc.). Will require feedback from developers; can be determined using data access, even if not exposed during enumeration.
  • Signals when system font configuration changes (fonts added/removed); some designers work with tools that swap font portfolios at the system level
  • Provide access to named instances and subfamilies (e.g. "semibold", "light")

Non-goals

This API will not try to:

  • Fully describe how font loading works within the web platform. Fonts are a complex topic and Web Font loading implicates aspects of layout and style recalculation which are not at this time pluggable. As this design isn't addressing those aspects, we will not describe font application or CSS recalculation semantics
  • Standardize font family detection or grouping
  • Describe or provide full access to an existing WOFF/TTF/PS parser.
  • Provide access to the underlying WOFF/TTF/PS font files or describe their locations on disk.
  • Provide a guarantee that the set of available font data matches the font on disk byte to byte.
  • Normalize differences in processed font data across browser implementations. The font data that will be exposed will have been processed by browser-provided parsers, but we will not describe or constrain them except to say that their output will continue to be in a valid OpenType format. For instance, if a library like OTS reduces the available information for a font, this spec will not require implementations to do more than they already would or provide alternative ways of getting such information back from the source font files.

Key scenarios

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 (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

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.

At the same time, when creating font-based content, specific fonts need to be identified and used.

Font enumeration can help by enabling:

  • Logging of likely-available fonts to improve server-side font rule generation.
  • Scripts to generate style rules based on "similar" local fonts, perhaps saving a download
  • Improving styling options for user-generated content, allowing the generation of style rules via more expressive font selection menus
// Asynchronous Query and Iteration

// User activation is required.
showLocalFontsButton.onclick = async function() {
  // 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.
  try {
    const array = await self.queryLocalFonts();

    array.forEach(metadata => {
      console.log(metadata.postscriptName);
      console.log(` full name: ${metadata.fullName}`);
      console.log(` family: ${metadata.family}`);
      console.log(` style: ${metadata.style}`);

      console.log(` italic: ${metadata.italic}`);
      console.log(` stretch: ${metadata.stretch}`);
      console.log(` weight: ${metadata.weight}`);
    });
   } catch(e) {
    // Handle error, e.g. user cancelled the operation.
    console.warn(`Local font access not available: ${e.message}`);
  }
};

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 can allow the user to select from a richer set of choices:

// User activation is required.
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 => {
      console.log("selected:", fontSelect.value);
      // 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(metadata => {
      const option = document.createElement("option");
      option.text = metadata.fullName;
      // postscriptName works well as an identifier of sorts.
      // It is unique as returned by the API, the OpenType spec expects
      // it to be in ASCII, and it can be used by @font-face src: local
      // matching to be used to style elements.
      option.value = metadata.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}`);
  }
};

Accessing Full Font Data

Here we use the FontMetadata blob() method to access a full and valid SFNT font data payload; we can use this to parse out specific data or feed it into, e.g., WASM version of HarfBuzz or Freetype:

// User activation is required.
useLocalFontsButton.onclick = async function() {
  // 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. A user agent may return all fonts, or show UI allowing selection
  // of a subset of fonts.
  try {
    const array = await self.queryLocalFonts();

    array.forEach(metadata => {
      // 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}`);
    }
  } catch(e) {
    // Handle error. It could be a permission error.
    console.warn(`Local font access not available: ${e.message}`);
  }
};

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 postscriptNames 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 self.queryLocalFonts({postscriptNames: ['Verdana', 'Verdana-Bold', 'Verdana-Italic']});

    array.forEach(metadata => {
      console.log(`Access granted for ${metadata.postscriptName}`);
    });

  } catch(e) {
    // Handle error. It could be a permission error.
    console.warn(`Local font access not available: ${e.message}`);
  }
};

Detailed design discussion (data)

Several aspects of this design need validation:

  • This design tries to address concerns with FontFaceSet and friends at the cost of introducing a new API surface.

Detailed design discussion (enumeration)

Several aspects of this design need validation:

  • What precisely is being iterated over needs to be identified. Is it over files on disk, families, or other groupings that a system level enumeration API provides? There is not a 1:1 relationship between files and named instances.
  • Grouping of related fonts and variants into a parent object is difficult. Some families can be represented by one file or many, and the definition of a "family" is heuristic to start with. Is grouping needed? Design currently leaves this open to future additions.
  • This design tries to address concerns with FontFace, FontFaceSet and friends at the cost of introducing a new API surface.

Other issues that feedback is needed on:

  • Font "name" propertes in OpenType are quite logically a map of (language tag → string) rather than just a string. The sketch just provides a single name (the "en" variant or first?) - should we introduce a map? Or have query() take a language tag? Or defer for now?

Privacy and Security Considerations

  • The local-fonts permission appears to provide a highly fingerprintable surface. However, UAs are free to return anything they like. For example, the Tor Browser or Brave may choose to only provide a set of default fonts built into the browser. Similarly, UAs are not required to provide table data exactly as it appears on disk. Browsers, e.g., may choose to only provide access to table data after sanitization via OTS and would fail to reflect certain tables entirely.

  • Another fingerprinting vector could be the font version. Providing access to the raw font data could provide further bits of entropy, due to developers being able to discern between users having certain versions of a given font file.

  • Some users (mostly in big organizations) have custom fonts installed on their system. Listing these could provide highly identifying information about the user's company.

  • Wherever possible, these APIs are designed to only expose exactly the information needed to enable the mentioned use cases. System APIs may produce a list of installed fonts not in a random or a sorted order, but in the order of font installation. Returning exactly the list of installed fonts given by such a system API can expose additional entropy bits, and use cases we want to enable aren't assisted by retaining this ordering. As a result, this API requires that the returned data be sorted before being returned.

Considered alternatives

FontFaceSource

FontFaceSource is specified in the CSS 3 Font Loading draft. At first glance, this is the most appropriate interface from which to hang something like the proposed query() method. It is, however, a synchronous iterator. In conversation with implemeners, this contract may be problematic from a performance perspective across OSes. Instead of providing a potentially unbounded way for developers to naively lock up the main thread, we've chosen to introduce a different root object from which to hang asynchronous iteratation and query methods.

This might be the wrong thing to do! Hopefully vendors can weigh in more thoroughly on this point.

Add a browser/OS-provided font chooser

The proposed API exposes some more bits about the user via the web that could improve fingerprinting efforts. The bits are based on the presence or lack of presence of certain fonts in the enumeration-returned list.

An alternative to the API that only exposes a single user-selected font was considered. This alternative enumeration API would trigger a browser/OS-provided font chooser and, from that chooser, the user would select a single font. This would reduce the bits exposed to help mitigate fingerprinting at the cost of significant new functionality.

We've heard interest from partners in a full-fledged enumeration API to get access to the list of available fonts on the system, and haven't heard interest in a font-chooser approach to the enumeration API. However, we're keeping the alternative in mind as we balance the need for new functionality with privacy concerns.

Exposing Font Tables as a map

The proposed API exposes font data as Blob containing a complete and valid SFNT data payload, itself containing valid OpenType font data.

An alternative to the API is to expose the data as a map of the tables contained in the SFNT wrapper. This alternative would provide a higher level API whereby font table data could be parsed individually instead of the font data as a whole.

We've heard from partners that this alternative does not provide a lot of value, and may in fact be counter-productive, because intended use-cases of this API subsume font data parsing tasks and require re-assembling the tables into a whole.

Metadata Properties

Including a subset of useful font metrics (ascender, descender, xheight, baseline) in the metadata was considered. Some are complicated (baseline), others more straightforward but may not be of practical use, especially if the full pipeline involves passing tables into Harfbuzz/FreeType for rendering. They are not included in the latest version of the sketch.

Additional metadata properties such whether the font uses color (SBIX, CBDT, SVG etc), or is a variable font could be provided, but may not be of use.

Exposing Building Blocks

To be of use, font table data must be consumed by a shaping engine such as HarfBuzz, in conjunction with Unicode libraries such as ICU for bidirectional text support, text segmentation, and so on. Web applications could include these existing libraries, for example compiled via WASM, or equivalents. Necessarily, user agents and operating systems already provide this functionality, so requiring web applications to include their own copies leads to additional download and memory cost. In some cases, this may be required by the web application to ensure identical behavior across browsers, but in other cases exposing some of these libraries directly to script as additional web APIs could be beneficial.

We are not considering these options right now for the API, but we'll keep them in mind in case there's demand for them.

(Parts of ICU are being incrementally exposed to the web via the ECMA-402 effort.)

local-font-access's People

Contributors

chasephillips avatar cwilso avatar dbaron avatar foolip avatar inexorabletash avatar oyiptong avatar pwnall avatar tomayac avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

local-font-access's Issues

Clarify and extend behavior around names in the FontMetadata object

I noticed a few shortcomings here; they can all be addressed by applications by parsing the SFNT directly, but it'd certainly be helpful to have some improvements made:

  • What happens if a font has no name of a given type in the current language? Do you get en? What happens in cases involving multi-label IETF language tags?
  • As mentioned in the "Issue 9" mentioned in the spec draft, it may be useful to retrieve a specific language; however, it may also be useful to retrieve a full list of names of a given type for the font, particularly for matching purposes.
  • What happens if a font has multiple names with the same type and language, but different vendors? This is remarkably common, the names can differ, and some applications may need the names from a specific vendor. This should be clarified for both query({select}) and the metadata objects (even if it just ends up being implementation-defined).

Specifying the async iterator

Sorry @domenic, I think I need your help on this one.

If we were to solve #27, I'd go for an API like:

class SystemFonts {
  async* matchAll(query = {}) {
    while (true) {
      const match = await getNextMatchSomehow(query);
      if (match) yield match;
      else return;
    }
  }

  [Symbol.asyncIterator]() {
    return this.matchAll();
  }
}

self.systemFonts = new SystemFonts();

In this case:

for await (const match of self.systemFonts) 

…is an alias of:

for await (const match of self.systemFonts.matchAll())  

But matchAll provides the ability to query:

for await (const match of self.systemFonts.matchAll({ name: 'foo' }))  

You could also do:

for await (const match of self.systemFonts[Symbol.asyncIterator]({ name: 'foo' }))  

…but I think it's worth providing the named method for ergonomics.

However, I can't see a way in IDL to declare a method that returns an async iterator like this. I guess I could call the @@asynciterator directly, but that seems not-how-we-usually-do-things.

Matching the underlying OS increases fidelity, rather than reducing it it

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.

Matching the underlying OS increases fidelity, rather than reducing it it. Or, put another way: Way more users use multiple apps on the same machine than use the same app on multiple machines.

"This must be a valid PostScript name."

This seems like a conformance requirements for people creating fonts? I'm not sure how this requirement helps implementers. What should they do if it's not a valid PostScript name, for instance?

Planning to incubate?

I saw this pop onto the TAG review repo, but wondered why I haven't seen this anywhere in WICG? Did I miss the post? Do you not intend to incubate it?

Permissions example

navigator.permissions.queryLocalFonts({ name: "local-fonts" }) seems wrong? I guess this was meant to be just query?

Context for Security Considerations

Related to #6, I was just curious if there was a good place to find context around security considerations in general. I have some thoughts/questions regarding this being a privileged action, but before I do that I wanted to know if there was a good sum up of what the potential dangers actually are. Given OTS, do all security concerns basically boil down to fingerprinting? Or is there some additional things to worry about?

Asking for permission

I think it would be better if the permission request is tied to query(). The end user flow I envision is that the website invokes query() and the user then gets a browser-driven dialog with which they can select zero or more fonts to expose to the website.

(Aside: Mozilla is not interested in navigator.permissions.request() and it's not even part of Permissions.)

Double-check you're using the right language

As mentioned in whatwg/html#7757, many other specs that need a default language use the document's language rather than the user's language. Since the font name is expected to be presented inside the document, it might work better if this spec switches to the document's language.

However, it's not obvious what to do inside of a Worker if we want to default to the document's language.

Font serialization variations

When a font (e.g. "Optima-Bold") is provided through the API, whether the source is the OS or a locally installed font, the actual data of the font may vary across browsers, OSes, or even time on the same device.

  • The font could be a non-OpenType version (e.g. Type 1, SVG, or some other format). See #19
  • The font could be a future format that is not serialized using SFNT
  • The font could be an alternate version from a different foundry.
  • The font could include bug fixes or improvements
  • The font could include additional data, e.g. expanded to include glyphs from other scripts, etc
    ... and much much more!

There is the case where the underlying font data is actually identical, but the SFNT serialization has changed in ways such that the files are not byte-identical, e.g. the tables are in a different order, or e.g. within the name table the entries are in a different order.

Do we need browsers to normalize this? Or is this the equivalent of e.g. a JPEG/JFIF file having markers in an undefined order when uploading <input type=file>?

The expected use case is that font data is parsed with a small number of robust libraries (e.g. HarfBuzz and FreeType) which conceal this from the calling code, so for most API users this will not be an issue.

mention additional fingerprinting surface in details of font versions

From w3ctag/design-reviews#400 (comment) :

A very brief comment here after looking at #399 -- it seems like there's less additional fingerprinting surface here -- but it's not zero, and worth mentioning. In particular, it seems like there are some changes in fonts that are not detectable on the web today, but would be detectable with raw font table access. Thus this might be providing the additional fingerprinting entropy to distinguish a user with version 1.0.4 of a font from one with version 1.0.5 of the same font in cases where that wasn't detectable today.

This seems worth at least briefly mentioning in the section on fingerprinting.

Anne's feedback re: Non-goals and alternative approaches

@annevk writes at w3ctag/design-reviews#400 (comment):

The various non-goals seem like they would make it rather hard for alternative approaches to doing fonts to enter the market or for smaller players with alternative approaches to compete. If even on the same platform the data you get back depends on the implementation it's highly likely sites will code toward a single implementation.

Hi Anne, thank you very much for your feedback! I'd like to understand it better.

Can you please clarify which of the explainer's non-goals you're referring to and why that is likely to introduce problems?

explainer suggests iterating all the fonts in order to find Consolas

The Accessing Font Tables section of the explainer has an example containing:

  for await (const metadata of navigator.fonts.query()) {
    // Looking for a specific font:
    if (metadata.postscriptName !== "Consolas")
      continue;

This seems like a bad example in two ways:

First, it seems inefficient to iterate all the fonts in order to find "Consolas", which is inefficient; it seems like the API should perhaps have a way to ask for the "Consolas" font.

Second, while it's using local font enumeration, it doesn't provide a particularly strong use case for the enumeration feature, since it's a use where enumeration isn't particularly a desirable feature. So showing this as an example rather weakens the argument that enumeration as a feature is worth the additional fingerprinting risk that it adds.

Why does this API elide tables/subtables not supported by the UA?

The draft says:

Table types within the original font file that are not supported by the user agent should be elided from the font representation's table list as defined by this specification. Tables with subtables not supported by the user agent should be transcoded to elide those subtables.

This seems odd to me. Surely one reasonable use case for an API like this would be to allow a page or webapp to implement things that the user agent does not natively support. For example, if the font contains bitmap glyphs (or perhaps embedded SVG glyphs) in a format that the UA doesn't support, the app might wish to parse those tables itself to extract and render the glyph images itself. Or if the UA doesn't support a particular shaping technology (e.g. SIL Graphite), the app might want to embed its own WASM-compiled engine, but to do this it will need access to the Graphite tables in the font, which this proposal says would be elided as "unsupported".

So I think this aspect should be reconsidered.

The async iteration definition isn't very async

Right now it gathers up all the font representations in the initialisation steps. It does this on the main thread, which suggests sync I/O, which I don't think is the intention.

But also, gathering everything up front isn't really how async iteration works. If it's ok to gather everything up front, this should just be a sequence behind a promise.

However, if returning everything at once is likely to be slow, then maybe there's a benefit to returning them one by one, so UI can update progressively. This might depend on how much up-front work is required to do the sorting vs computing the metadata. It'd be good to do some science here to influence the decision.

If this becomes a true async iterator, it raises some questions that need to be answered in the prose:

// User has fonts a, c, and e.
const iterator = navigator.fonts.query();
const font1 = await iterator.next();
// font1 is a.
// But then, the user deletes font c.
const font2 = await iterator.next();
// What is font2?

And:

// User has fonts a, c, and e.
const iterator = navigator.fonts.query();
const font1 = await iterator.next();
// font1 is a.
// But then, the user installs font b.
const font2 = await iterator.next();
// What is font2?

Additionally, a true async iterator should probably check for permission in "get the next iteration result".

Need support for sanitized font stream

Currently there is provision of making local font information available via font tables. In addition to this, it will be helpful if there is provision of making complete font stream available.

We already have an existing solution for the fonts available on web where we download the font and create a memory buffer out of the stream. Later, a module which is already in place takes care of processing the font information and rendering it.

With table based approach, there is an extra burden of assembling the tables and creating the font stream in the manner it is expected by the module above.

In order to get rid of this extra step, making this request for sanitized font stream.

I am not saying that the table based approach has any issue as such. Its about flexibility for handling different use cases. Later we may need the font table access as well if there is any difficulty from memory management perspective for large fonts.

Non-OpenType fonts

Many MacOS and iOS fonts are not OpenType fonts. Requiring browsers to synthesize fake OpenType font tables for underlying non-OpenType fonts seems unfortunate.

Related Internationalization APIs

I know you already mention it several times in the docs, but I thought I would create an explicit issue for it. Working with raw font data in a correct way internationally requires lots of large APIs, so it would be good if you included an API to them too. There seems to be good progress on Intl.Segmenter, but with raw font data, web devs also need all the bidi/ltr/rtl stuff and a Harfbuzz-equivalent. If you don't include these APIs, there's going to be a lot of websites that only support "English-only" fonts as well as many others that download Harfbuzz every time you use them.

postscript names of variable fonts

Hi,

I'm using the Local Fonts API, and writing my own font parser to parse the blob.
With regards to variable fonts:

The Local Fonts API gives me this in its array of fonts (the postscriptName)
...
Segoe-UI-Variable-Display
Segoe-UI-Variable-Display-Bold
Segoe-UI-Variable-Display-Light
Segoe-UI-Variable-Display-Semibold
etc.

These names I believe are all coming from the same font file, a variable font file with an "fvar" table, when I'm parsing that file, the postscript names of these variable font "instances" do not seem to be included, but must be generated. Though I did find a document that suggests how to generate a postscript name from a variable font instance:

https://adobe-type-tools.github.io/font-tech-notes/pdfs/5902.AdobePSNameGeneration.pdf

This document doesn't seem to give the same results however as that returned from Local Fonts API. Even if I tweak it, I'm not getting same results. What I can get however is e.g this:

Segoe-UI-Variable-Light-Display

But Notice: "Light" and "Display" is reversed (There's a string in the file that says "Light Display", which I've used to build the final string) Local Fonts API returns "Display-Light", so its reversed from what I've managed to get, and I don't want to just HACK and reverse strings etc.

In short: How would I parse the truetype font to match it with the postscript names from a variable font, that the Local Fonts API is giving me ??

Thanks,
Sigurd Lerstad

Gaps in "Styling with Local Fonts" use-case

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:

CSS divides fonts up into family * weight * style * stretch.

It isn't clear how I'd use this API to map the raw font data into the CSS equivalents.

Extra FontManager indirection seems unnecessary

Why is the following necessary:

const fontManager = navigator.fonts;
const fonts = await fontManager.query();
const fontIterator = fontIterator[Symbol.asyncIterator]();

instead of the following?

const fonts = await navigator.queryFonts();
const fontIterator = fonts[Symbol.asyncIterator]();

Notification of installed font change

Have you considered adding an event when a font is added or activated in the OS? And removed or deactivated? Users and applications can install and remove fonts at any time, and in many cases add them when they are required for editing a document.

Preference would be a notification on an individual font basis. Could be batched. Reason is because enumerating all fonts and updating caches, font menus, etc. is expensive. Is best to know specifically which fonts have been added or removed.

Create a definition for getting fonts from the system

Right now the spec says "For each local font font on the system".

I think it'd be better to create definitions for this. Eg:

Font metadata has:

  • A postscript name, a DOMString.
  • A full name, a DOMString.
  • A family, a DOMString.

A system font is a font that is available system-wide by the operating system.

To read a system font as a font representation… (this lets you detail the kinds of filtering a browser may perform here)

To get all system font metadata, perform the following steps:

  1. Let |fonts| be a subset of [=system fonts=] [=read as font representations=]. Subsetting may be defined by the user agent, and can include safelists, or a selection UI shown to the user.
  2. Let |fontMetadatas| be a new list.
  3. [=For each=] |font| of |fonts|:
    1. Let |metadata| be a new [=font metadata=].
    2. Set |metadata|'s [=postscript name=] to…
    3. Etc etc
  4. Sort |fontMetadatas| by…
  5. Return |fontMetadatas|.

IDL mismatches spec for `query()`

The IDL says that query() returns a FontIterator. However the spec algorithm for query() says that it returns a Promise<FontIterator>.

Review

Small things in the explainer:

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.

To me, this reads like a disadvantage of local fonts, and an advantage of web fonts. As in, if you want your files to look the same once shared, you shouldn't rely on system fonts.


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.

This point seems a bit weak hand-wavy. Is there a more concrete example?


A font enumeration API, which allows users to grant access to the full set of available system fonts.

The level of access isn't clear here. Maybe:

A font enumeration API, which allows users to grant access to the full set of available system font metadata.


glyf table for glyph vector data, the GPOS table for glyph placement, and the GSUB table for ligatures and other glyph substitution

Nit: Only "glyf" is linked to its definition. Maybe link to GPOS and GSUB too?


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.

Enable access to all browser-allowed font tables (may vary per browser)

These two sound bad on the surface as they encourage breaking interoperability. Maybe detail why these are desirable?


  • Logging of likely-available fonts to improve server-side font rule generation.
  • Scripts to generate style rules based on "similar" local fonts, perhaps saving a download

I don't really understand these. Given that the process may involve a permission prompt, this sounds pretty intrusive vs just specifying a long list of fonts in font-family.


const fonts_iterator = navigator.fonts.query();

Nit: In JavaScript names are generally camelcased. Same goes for font_select in the next example.

I also agree with @domenic that this isn't an iterator, so maybe call it systemFonts?


option.value = metadata.fullName;

You don't need this line. If no value is provided, it defaults to the text.


document.body.appendChild(font_select);

Kinda nitty, but this code produces a useless select if permission isn't granted. Maybe move the <select> creation/append until after permission is granted?


option.setAttribute("postscriptName", metadata.postscriptName);

We shouldn't encourage the use of non-standard attribute names.

Instead, either use a data attribute:

option.dataset.postscriptName = metadata.postscriptName;

Or create associated data:

const optionToMetadata = new WeakMap();
// ...
optionToMetadata.set(option, metadata);

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());

Some of the formatting here is a little unusual. I'd go with:

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()
);

If in doubt, pass code through https://prettier.io/playground/. Oh actually, it could just be:

// 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();

FontFaceSource is specified in the CSS 3 Font Loading draft. At first glance, this is the most appropriate interface from which to hang something like the proposed query() method. It is, however, a synchronous iterator.

FontFaceSource isn't an iterator, but FontFaceSet is.

The FontFaceSet is exposed in pages as document.fonts, which doesn't seem like a good fit for this feature as it's of the system, not of the document.


Necessarily, user agents and operating systems already provide this functionality, so requiring web applications to include their own copies leads to additional download and memory cost. In some cases, this may be required by the web application to ensure identical behavior across browsers, but in other cases exposing some of these libraries directly to script as additional web APIs could be beneficial.

"ensure identical behavior across browsers" - this seems to clash with the earlier claim "UAs are not required to provide table data exactly as it appears on disk".

Also, it isn't clear what the conclusion is for this section. Is it being considered in future, or dismissed?


It isn't clear to me from reading this if the API presented is exhaustive. Eg, is there anything else hanging off navigator.fonts or is it just query?

It might be worth adding an MDN-like description of the API, aimed at developers.


Styling with Local Fonts

It isn't clear to me which bit of metadata I'd use in CSS's font-family.

My assumption would be metadata.family, since that's closest in naming to font-family, but the example suggests otherwise, as that's the one bit of metadata that isn't used.


Can fonts contain direct personal information that might be exposed here? Eg, name of the user in some kind of licencing information?


Former editors:

  • Emil A. Eklund

😢

Nice callout at the end, thanks for adding that.


I'll create issues for things that might need further discussion.

Access to default system fonts w/o permission?

Can we provide a way to get access to system fonts w/o a permission prompt?

This could be scoped down to be the same as "system-ui".

Depending on fingerprinting concerns... if the OS/version(/locale?) is already known, then exposing fonts known to be pre-installed on all Windows 10 en-us systems could be an acceptable amount of entropy as well. This would not expose manually installed fonts, which is a greater sense of entropy.

Maybe query({system:true}) or some such?

isVariable -> list of axes

isVariable is extremely useful since a family name by itself will not reveal if a font is variable, or not. While https://github.com/inexorabletash/font-table-access will allow developers to create their own JS libraries that f.getTables([ "fvar" ]); and list the axes, it may be more convenient for developers (especially less savvy ones, like CSS-only sort of folks) to be able to know what axes are in a family that is enumerated with this API.

More font metadata (weight, stretch, italic, unlocalized names)

The current font metadata fields are not sufficient for Figma. In addition to the existing fields, we also need:

  • weight (0 - 1000 inclusive, like CSS font-weight)
  • stretch (50% - 200% inclusive, like CSS font-stretch)
  • italic boolean
  • style name
  • unlocalized names

Further details below!

weight/stretch/italic

We have a font picker that looks like this:

font-picker

When you select the style (Italic in the example above), we show you a dropdown like this:

style-picker

As you can see, the dropdown is categorized and sorted by a combination of the font weight, stretch, and whether it is italic or not.

It would be ideal if the font API could provide this metadata given that they are already available from the OS font libraries. Otherwise, we would have to manually load the blob of each font, parse them manually or with Freetype, cache the info somewhere, and also worry about cache invalidation in case e.g. a new version of the font is installed.

Loading and parsing all fonts just to display our font picker would be prohibitively expensive. It is not uncommon for designers to have 1000s of fonts installed on their system.

Exposing the existing OS font library metrics would make this a lot simpler for us and allow us to only load and parse fonts that are actually in use.

style name

In the previous screenshots, you can also see that in our font picker, you first choose the family, and the separately choose the style. We could extract it from the fullName field, but that's pretty hacky. Additionally, fonts are weird and font designers do all sorts of weird things so there is no guarantee that fullName even has the style name.

unlocalized names

We need the unlocalized family/style name because we persist it in our file format and need it to be consistent across operating systems and different languages. Our existing methods for local font access provide the unlocalized name so this is also important from an interop perspective.

SecureContext Reasoning?

I was curious what the motivation was for restricting this feature to only Secure Contexts. If you are accessing local fonts then there shouldn't be any possibility of a man-in-the-middle attack right?

Font Metadata lacks information about supported character set and language for fonts

I can't find any reference to supported character set or language or locale in the FontMetadata definition. This seems like a major limitation to any kind of text processing application that wants to work in more than one locale.

My particular use case is to find out what set of fonts are needed to render a particular each character of a string (i.e. itemization) so that those fonts can be requested and embedded in a PDF and text objects created referencing the embedded fonts.

Take, for example, the string "Привет दुनिया". The first half can be typeset in Helvetica, but the second half causes TextEdit on my Mac to fallback to "Kohinoor Devanagari" to render the text.

If I want to find out that same information from a web API, there doesn't seem to be any way to do it. I can't even use a library like fontconfig compiled for wasm as used by Chrome unless I go ahead and request every single font installed on the system and load them in (100s of MBs?).

Ideally there would some web API that could take styled element and tell me which fonts were actually used to render each character, which I could then request with the local font API for writing out into the PDF.

Allow querying based on fields other than the PostScript name?

In some cases, I need to search for fonts by family name or fullname (specifically the Microsoft names). The current API allows only either requesting a specific list of PostScript names, or the entire list (which may be fairly expensive to iterate over in its entirety). Could the API be extended to allow for searching by other name fields?

API shouldn't give out 6-13 bits of fingerprinting entropy with one 'yes'

As described in Hiding in the Crowd: an Analysis of the Effectiveness of
Browser Fingerprinting at Large Scale
, knowing the list of fonts on a desktop computer gives between 6.9 bits (by checking a fixed list of fonts) and 13.9 bits (by using flash to get the full list) of fingerprinting entropy. That's a lot to give away with a single permission prompt.

As described in https://github.com/WICG/local-font-access#add-a-browseros-provided-font-chooser, it would be straightforward to have users choose one or a few fonts at a time to "add" to a web page, after which the page could present them in its own font list. That's probably about one extra click for users for the second and subsequent fonts, with the benefit of providing no fingerprinting information at all, since other sites can't expect that a user will give them all the same set of fonts.

API should be compatible with iOS font privacy model

This is a continuation of issue #60 . It was closed with a request for more information and told to reopen the issue, but I can't figure out how to reopen that issue, so I'll just create a new one.

Anyway, the information about the iOS font privacy model is described in this video
https://developer.apple.com/videos/play/wwdc2019/227/

Unlike your font privacy model, iOS doesn't allow applications to enumerate the list of fonts. Applications can show a font picker, allowing the user to authorize a single font be made available to an app, or applications can present a list of fonts to the system, and the system will check which ones are installed and ask the user to make them available to the application. It looks like your font privacy model is just a switch that makes all fonts available to an application or not. This will likely never be implemented in Safari on iOS because then apps could bypass the iOS font security model by simply embedding a WebView in their app and grabbing the font list from the browser.

So my general concerns with the proposed standard (I haven't looked at it in a couple of months, so I apologize if the standard has changed) are

  1. you may be pursuing a font security model that differs from and is incompatible with the direction that other major systems are going with, which is fine but it should be acknowledged
  2. the fact that your design docs don't mention this other security model suggests a general lack of due diligence
  3. the API you propose is looser than the iOS model, but you could design your API to offer a more restrictive subset that would be compatible with the iOS model. That way, developers could code to the more restrictive subset if they want something compatible with iOS
  4. Web developers won't use an API unless it's available on iOS too. Making your API more compatible with iOS will make it more likely that Apple will implement it in Safari.

Typos

"in a the font" -> "in the font"

Is this approach compatible with iOS?

Is the proposed approach to local font access compatible with the iOS 13 font security model? I’m not well-versed in the issue since I don’t think anyone uses the new font stuff in iOS 13, but my understanding was that the new iOS 13 approach to font privacy means that font enumeration isn’t allowed; only font pickers and querying for specific fonts are allowed.

I suppose it doesn’t matter since Safari is not subject to the security mechanisms of normal apps.

FontIterator name is not good

Consider the following code sample:

const fontManager = navigator.fonts;
const fontIterator = await fontManager.query();
const fontIterator2 = fontIterator[Symbol.asyncIterator]();

const { value, done } = await fontIterator2.next();

In JavaScript, "iterator" has a very specific meaning, which is an object with a next() method which returns a { value, done } tuple. (Or a promise for such a tuple, in the case of async iterators.)

That is, per the JavaScript spec and ecosystem's definitions, fontIterator2 is an iterator. fontIterator is not an iterator; it is an iterable.

So the interface would be better named FontIteratable or Fonts or something like that. (Although IMO it could probably be eliminated entirely... I'll file another issue for that.)

Consistency in code sample

The code sample in the explainer has status.state != "granted" and f.family !== "Consolas". Please consider switching != to !== for consistency?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.