WICG / local-font-access

Web API for enumerating fonts on the local system
https://wicg.github.io/local-font-access
Apache License 2.0
75 stars 16 forks source link

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

Closed poiru closed 2 years ago

poiru commented 3 years ago

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

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.

sicking commented 3 years ago

One thing that's a bit tricky is that extracting font metadata from a font file is not straight forward. As I understand it, the main problem is that metadata in the font tables is often lacking or wrong. And so different OSs have taken to implementing different heuristics for how to combine the data in the font tables with the font/face/style name in order to determine the "correct" metadata. Here is how windows did it at least some time back.

Ideally we'd have some flexibility in implementing similar heuristics ourselves. We've talked about doing that in order to get consistency across different OSs and different ways of loading fonts.

One potential solution is that Chrome supplies the values as interpreted by the OS or browser. But in addition also provides a font file checksum which would enable us to implement our own caching+cache invalidation

sicking commented 3 years ago

Updating here after some further dicussions. In order for Figma to display a font picker, we need to quickly load metadata on all fonts. There are two ways we can accomplish that:

Use the metadata provided by the API

With this approach we rely strictly on the data provided by the API. Here we would need the following information

In all cases we need the information as computed by the OS. I.e. only relying on the data in the font file is insufficient since font files often contain invalid data, and so OSes contain a lot of heuristics to calculate the "real" values.

Note that we don't need the font weight or font stretch in any sort of standardized form. It's completely fine to pass through the values as provided directly by the OS. Figma can do the work of translating the OS values into CSS values. Though getting CSS values is fine too.

The reason we need the unlocalized font family and style name is that these are the values we use as font identity within Figma. So these are the values that we'd use when deciding which font to actually load.

Calculcate metadata ourselves

We could load the font file and extract the metadata ourselves. This would have the advantage that we can be consistent across all platforms for a given font file, and we could extract additional metadata in the future as needs arise (things like variable fonts or oblique styles)

This would only require the following information

This would allow us to extract all information from all font files and then cache it in indexedDB or similar. Then we'd just need to load the information from indexedDB in order to display a font picker.

This solution has the advantage that we need less from the API and be more future proof. But has the downside that we need to do significantly more work on our end so would likely significantly delay when we could adopt the API. But ultimately this is a direction we want to go, just a question of when.

Update notifications

On a separate note, it would also be great to get some sort of callback when fonts are installed/removed/updated. I.e. when the list of fonts change. This wouldn't be a blocker for adoption for us since it's not something we currently do.

But it's something we have received feedback about from users. I.e. in some native applications there is no need to restart the application in order to get updated fonts. Currently they have to reload the Figma tab which is a negative experience for them.

poiru commented 3 years ago

From the code perpective, for unlocalized font/style name on macOS, you can use:

CTFontDescriptorCopyAttribute(font, kCTFontFamilyNameAttribute);
CTFontDescriptorCopyAttribute(font, kCTFontStyleNameAttribute);

For update notifications on Windows, you could monitor the following:

Let me know if you'd like to see our code for the other metadata (e.g. weight/stretch/italic).

oyiptong commented 3 years ago

RE: style

Just to make sure, on Windows, this is expected to be the DWRITE_INFORMATIONAL_STRING_WIN32_SUBFAMILY_NAMES, correct?

poiru commented 3 years ago

@oyiptong On Windows, for the family we check DWRITE_INFORMATIONAL_STRING_PREFERRED_FAMILY_NAMES first and fallback to DWRITE_INFORMATIONAL_STRING_WIN32_FAMILY_NAMES. Likewise for the style we check DWRITE_INFORMATIONAL_STRING_PREFERRED_SUBFAMILY_NAMES first and fallback to DWRITE_INFORMATIONAL_STRING_WIN32_SUBFAMILY_NAMES. The preferred family/style is a better fit for cross platform compatibility (the Win32 names are restricted to conform with GDI limitations). From MSDN:

Indicates the string containing the family name preferred by the designer. This enables font designers to group more than four fonts in a single family without losing compatibility with GDI. This name is typically only present if it differs from the GDI-compatible family name.

We also always try to get the en-us string, it should not be localized.

oyiptong commented 3 years ago

Thanks, that's very helpful!

oyiptong commented 3 years ago

@poiru is italic as a boolean useful if it appears in style?

poiru commented 3 years ago

@oyiptong Yes! Font designers can name the style arbitrarily (e.g. Italic, Cursive, Incline, Oblique) so we check for DWRITE_FONT_STYLE_ITALIC/DWRITE_FONT_STYLE_OBLIQUE on Windows and for a non-zero kCTFontSlantTrait on macOS to determine whether the font is italic.

sicking commented 3 years ago

In our last conversation there was a request to describe the use cases for having font weight and other style metadata in the enumeration API. We have two use cases that require this.

First off our font picker groups and sorts fonts by various font properties, such as font weight, italics and density. This can look like the below screenshot. This is a dropdown lets the user chose which style variant of the Inter font to use. The fonts are ordered by weight and grouped by italics. We'd like to have this metadata available very quickly, so loading all the fonts in a given family once the user chooses that family would likely be too slow.

Screen Shot 2021-02-18 at 2 47 42 PM

The second use case is to implement the Cmd+B action (i.e. the user making text bold). Exactly which font style to apply can depend on the list of available styles in a text's family.

For example if the user has selected some weight 400 text and the available weights are 400, 700 and 900, we'd probably want to apply the font weight 700. However if the list of available weights are 200 and 400, then we'd probably want to "turn bold off" by applying the weight 200.

Being able to figure out which font style to apply without having to load all the fonts in the family requires access to style metadata during enumeration.

inexorabletash commented 3 years ago

API sketch:

partial interface FontMetadata {
    float weight;
    float stretch;
    bool italic;
};

How's that look?

poiru commented 3 years ago

Looks great!

inexorabletash commented 3 years ago

FYI, the change to add italic, stretch and weight has landed in Chrome, should be in an upcoming Canary.

poiru commented 3 years ago

@inexorabletash Great! It seems like the origin trial is no longer active – do you plan to do another trial?

Also, on Canary, navigator.fonts.query prompts the user to select the fonts on every single call even when "Enable persistent access to the Font Access API" is enabled. Is this expected?

inexorabletash commented 3 years ago

The implementation in Chrome (OT and flag) is paused while we explore the permission issues, so the API is a bit rough at the moment. Hopefully more news soon.

inexorabletash commented 2 years ago

FYI that the state of the API implementation hasn't changed; still exploring the permission issues, so it has rough edges. Again, hopefully more news soon.

inexorabletash commented 2 years ago

We heard from Figma that italic/stretch/weight are not important to surface in the API directly, since fonts will be parsed to extract other data anyway. Curious if other potential users of the API consider these properties high priority.

inexorabletash commented 2 years ago

Closing for now