Closed poiru closed 2 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
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.
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:
WM_FONTCHANGE
RegNotifyChangeKeyValue
for changes to Software\Microsoft\Windows NT\CurrentVersion\Fonts
under both HKEY_LOCAL_MACHINE
and HKEY_CURRENT_USER
(the latter is for Windows 10 build 1809, which added per-user font installation)Let me know if you'd like to see our code for the other metadata (e.g. weight/stretch/italic).
RE: style
Just to make sure, on Windows, this is expected to be the DWRITE_INFORMATIONAL_STRING_WIN32_SUBFAMILY_NAMES
, correct?
@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.
Thanks, that's very helpful!
@poiru is italic
as a boolean useful if it appears in style
?
@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.
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.
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.
API sketch:
partial interface FontMetadata {
float weight;
float stretch;
bool italic;
};
weight
is a floating point value corresponding to the numeric keyword values of the CSS font-weight
property, e.g. 100.0 for extra-light, 400.0 for regular, 900.0 for black. Exact values should not be expected as some operating systems / fonts may support additional values e.g. 375 for book. stretch
is a floating point value corresponding to the numeric percentages of the CSS font-stretch
property, e.g. 0.5 (50%) for ultra-condensed, 1.0 (100%) for normal, 2.0 (200%) for ultra-expandeditalic
is true for fonts that identify as italic or oblique, or have a positive slant. Since operating systems and fonts haven't converged e.g. on a fractional slant value or enumeration (roman/italic/oblique), this is simplified to a boolean and future expansion here is anticipated.How's that look?
Looks great!
FYI, the change to add italic
, stretch
and weight
has landed in Chrome, should be in an upcoming Canary.
@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?
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.
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.
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.
Closing for now
The current font metadata fields are not sufficient for Figma. In addition to the existing fields, we also need:
font-weight
)font-stretch
)Further details below!
weight/stretch/italic
We have a font picker that looks like this:
When you select the style (Italic in the example above), we show you a dropdown like this:
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 thatfullName
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.