This content originally appeared on web.dev and was authored by Thomas Steiner
The Local Font Access API is part of the capabilities project and is currently in development. This post will be updated as the implementation progresses.
Web safe fonts
If you have been doing web development long enough, you may remember the so-called web safe fonts. These fonts are known to be available on nearly all instances of the most used operating systems (namely Windows, macOS, the most common Linux distributions, Android, and iOS). In the early 2000s, Microsoft even spearheaded an initiative called TrueType core fonts for the Web that provided these fonts for free download with the objective that "whenever you visit a Web site that specifies them, you'll see pages exactly as the site designer intended". Yes, this included sites set in Comic Sans MS. Here is a classic web safe font stack (with the ultimate fallback of whatever
sans-serif
font) might look like this:
body {
font-family: Helvetica, Arial, sans-serif;
}
Web fonts
The days where web safe fonts really mattered are long gone. Today, we have web fonts, some of which are even variable fonts that we can tweak further by changing the values for the various exposed axes. You can use web fonts by declaring an
@font-face
block at the start of the CSS, which specifies the font file(s) to download:
@font-face {
font-family: "FlamboyantSansSerif";
src: url("flamboyant.woff2");
}
After this, you can then use the custom web font by specifying the
font-family
,
as normal:
body {
font-family: "FlamboyantSansSerif";
}
Local fonts as fingerprint vector
Most web fonts come from, well, the web. An interesting fact, though, is that the
src
property in the @font-face
declaration, apart from the
url()
function, also accepts a
local()
function. This allows custom fonts to be loaded (surprise!) locally. If the user happens to have FlamboyantSansSerif installed on their operating system, the local copy will be used rather than it being downloaded:
@font-face {
font-family: "FlamboyantSansSerif";
src: local("FlamboyantSansSerif"),
url("flamboyant.woff2");
}
This approach provides a nice fallback mechanism that potentially saves bandwidth. On the Internet, unfortunately, we cannot have nice things. The problem with the local()
function is that it can be abused for browser fingerprinting. Turns out, the list of fonts a user has installed can be pretty identifying. A lot of companies have their own corporate fonts that are installed on employees' laptops. For example, Google has a corporate font called Google Sans.
An attacker can try to determine what company someone works for by testing for the existence of a large number of known corporate fonts like Google Sans. The attacker would attempt rendering text set in these fonts on a canvas and measure the glyphs. If the glyphs match the known shape of the corporate font, the attacker has a hit. If the glyphs do not match, the attacker knows that a default replacement font was used since the corporate font was not installed. For full details on this and other browser fingerprinting attacks, read the survey paper by Laperdix et al.
Company fonts apart, even just the list of installed fonts can be identifying. The situation with this attack vector has become so bad that recently the WebKit team decided to "only include [in the list available fonts] web fonts and fonts that come with the operating system, but not locally user-installed fonts". (And here I am, with an article on granting access to local fonts.)
The Local Font Access API
The beginning of this article may have put you in a negative mood. Can we really not have nice things? Fret not. We think we can, and maybe everything is not hopeless. But first, let me answer a question that you might be asking yourself.
Why do we need the Local Font Access API when there are web fonts?
Professional-quality design and graphics tools have historically been difficult to deliver on the web. One stumbling block has been an inability to access and use the full variety of professionally constructed and hinted fonts that designers have locally installed. Web fonts enable some publishing use-cases, but fail to enable programmatic access to the vector glyph shapes and font tables used by rasterizers to render the glyph outlines. There is likewise no way to access a web font's binary data.
- 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.
- 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.
The Local Font Access API is an attempt at solving these challenges. It consists of two parts:
- 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.
Current status
Step | Status |
---|---|
1. Create explainer | Complete |
2. Create initial draft of specification | In progress |
3. Gather feedback & iterate on design | In progress |
4. Origin trial | In progress |
5. Launch | Not started |
How to use the Local Font Access API
Enabling via chrome://flags
To experiment with the Local Font Access API locally, enable the #font-access
flag in chrome://flags
.
Enabling support during the origin trial phase
Starting in Chrome 87, the Local Font Access API will be available as an origin trial in Chrome. The origin trial is expected to end in Chrome 89 (April 7, 2021).
Origin trials allow you to try new features and give feedback on their usability, practicality, and effectiveness to the web standards community. For more information, see the Origin Trials Guide for Web Developers. To sign up for this or another origin trial, visit the registration page.
Register for the origin trial
- Request a token for your origin.
- Add the token to your pages. There are two ways to do that:
- Add an
origin-trial
<meta>
tag to the head of each page. For example, this may look something like:
<meta http-equiv="origin-trial" content="TOKEN_GOES_HERE">
- If you can configure your server, you can also add the token
using an
Origin-Trial
HTTP header. The resulting response header should look something like:
Origin-Trial: TOKEN_GOES_HERE
- Add an
Feature detection
To check if the Local Font Access API is supported, use:
if ('fonts' in navigator) {
// The Local Font Access API is supported
}
Asking for permission
Access to a user's local fonts is gated behind the "local-fonts"
permission,
which you can request with
navigator.permissions.request()
.
// Ask for permission to use the API
try {
const status = await navigator.permissions.request({
name: 'local-fonts',
});
if (status.state !== 'granted') {
throw new Error('Permission to access local fonts not granted.');
}
} catch(err) {
// A `TypeError` indicates the 'local-fonts'
// permission is not yet implemented, so
// only `throw` if this is _not_ the problem.
if (err.name !== 'TypeError') {
throw err;
}
}
Warning: In this early phase of development of the Local Font Access API, the permission mentioned above is not yet implemented. In the meantime, a permission prompt will pop up the moment font enumeration starts. This behavior was implemented in crbug.com/1112552.
Enumerating local fonts
Once the permission has been granted, you can then, from the FontManager
interface that
is exposed on navigator.fonts
, call query()
to ask the browser
for the locally installed fonts.
This results in an asynchronous iterator that you can loop over in a
for await...of
statement.
Each font is represented as a FontMetadata
object with the properties
family
(for example, "Comic Sans MS"
), fullName
(for example, "Comic Sans MS"
),
and postscriptName
(for example, "ComicSansMS"
).
// Query for all available fonts and log metadata.
const fonts = navigator.fonts.query();
try {
for await (const metadata of fonts) {
console.log(metadata.postscriptName);
console.log(metadata.fullName);
console.log(metadata.family);
}
} catch (err) {
console.error(err.name, err.message);
}
Accessing SFNT data
Full SFNT
access is available via the blob()
method of the FontMetadata
object.
SFNT is a font file format which can contain other fonts, such as PostScript, TrueType, OpenType,
Web Open Font Format (WOFF) fonts and others.
const fonts = navigator.fonts.query();
try {
for await (const metadata of fonts) {
// We're only interested in a particular font.
if (metadata.family !== 'Comic Sans MS') {
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;
}
console.log('Outline format:', outlineFormat);
}
} catch (err) {
console.error(err.name, err.message);
}
Demo
You can see the Local Font Access API in action in the
demo below.
Be sure to also check out the
source code.
The demo showcases a custom element called <font-select>
that implements a local font picker.
Privacy considerations
The "local-fonts"
permission appears to provide a highly fingerprintable surface.
However, browsers are free to return anything they like.
For example, anonymity-focused browsers may choose to only provide
a set of default fonts built into the browser.
Similarly, browsers are not required to provide table data exactly as it appears on disk.
Wherever possible, the Local Font Access API is 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 data that may be used for fingerprinting, and use cases we want to enable are not assisted by retaining this ordering. As a result, this API requires that the returned data be sorted before being returned.
Security and permissions
The Chrome team has designed and implemented the Local Font Access API using the core principles defined in Controlling Access to Powerful Web Platform Features, including user control, transparency, and ergonomics.
User control
Access to a user's fonts is fully under their control and will not be allowed unless the "local-fonts"
permission, as listed in the
permission registry, is granted.
Transparency
Whether a site has been granted access to the user's local fonts will be visible in the site information sheet.
Permission persistence
The "local-fonts"
permission will be persisted between page reloads. It can be revoked via the site information sheet.
Feedback
The Chrome team wants to hear about your experiences with the Local Font Access API.
Tell us about the API design
Is there something about the API that does not work like you expected? Or are there missing methods or properties that you need to implement your idea? Have a question or comment on the security model? File a spec issue on the corresponding GitHub repo, or add your thoughts to an existing issue.
Report a problem with the implementation
Did you find a bug with Chrome's implementation? Or is the implementation different from the spec?
File a bug at new.crbug.com. Be sure to include as much detail as you can, simple instructions for reproducing, and enter Blink>Storage>FontAccess
in the Components box. Glitch works great for sharing quick and easy repros.
Show support for the API
Are you planning to use the Local Font Access API? Your public support helps the Chrome team to prioritize features and shows other browser vendors how critical it is to support them.
Send a Tweet to @ChromiumDev with the #LocalFontAccess
hashtag and let us know where and how you're using it.
Helpful links
- Explainer
- Spec draft
- Chromium bug for font enumeration
- Chromium bug for font table access
- ChromeStatus entry
- GitHub repo
- TAG review
- Mozilla standards position
- Origin Trial
Acknowledgements
The Local Font Access API spec was edited by Emil A. Eklund, Alex Russell, Joshua Bell, and Olivier Yiptong. This article was reviewed by Joe Medley, Dominik Röttsches, and Olivier Yiptong. Hero image by Brett Jordan on Unsplash.
This content originally appeared on web.dev and was authored by Thomas Steiner
Thomas Steiner | Sciencx (2020-08-24T00:00:00+00:00) Use advanced typography with local fonts. Retrieved from https://www.scien.cx/2020/08/24/use-advanced-typography-with-local-fonts/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.