Introduction
(This article is also available in form of a video.)
Boxy SVG is a vector graphics editor. Its main use case is editing drawings in the SVG file format, for creating illustrations, logos, icons, and other elements of graphic design. It’s developed by Polish developer Jarosław Foksa and was initially released on March 15, 2013. Jarosław runs a Boxy SVG blog in which he announces new features he adds to the app. The developer is a strong supporter of Chromium’s Project Fugu and even has a Fugu tag on the app’s ideas tracker.
Local Font Access API in Boxy SVG
One feature addition Jarosław blogged about was the Local Font Access API. The Local Font Access API lets users access their locally installed fonts, including higher-level details such as names, styles, and families, as well as the raw bytes of the underlying font files. In the following screenshot you can see how I have granted the app access to the locally installed fonts on my MacBook and chosen the Marker Felt font for my text.
The underlying code is quite straightforward. When the user opens the font family picker for the first time, the application first checks if the web browser supports the Local Font Access API.
It also checks for the old experimental version of the API and uses it if present. As of 2023, you can safely ignore the old API as it was available only for a short time via experimental Chrome flags, but some Chromium-derivatives may still use it.
let isLocalFontsApiEnabled = (
// Local Font Access API, Chrome >= 102
window.queryLocalFonts !== undefined ||
// Experimental Local Font Access API, Chrome < 102
navigator.fonts?.query !== undefined
);
If the Local Font Access API is not available, the font family picker will turn gray. A placeholder text will be displayed to the user instead of the fonts list:
if (isLocalFontsApiEnabled === false) {
showPlaceholder("no-local-fonts-api");
return;
}
Otherwise, the Local Font Access API is used to retrieve the list of all fonts from the operating system. Notice the try…catch
block which is needed in order to handle permission errors properly.
let localFonts;
if (isLocalFontsApiEnabled === true) {
try {
// Local Font Access API, Chrome >= 102
if (window.queryLocalFonts) {
localFonts = await window.queryLocalFonts();
}
// Experimental Local Font Access API, Chrome < 102
else if (navigator.fonts?.query) {
localFonts = await navigator.fonts.query({
persistentAccess: true,
});
}
} catch (error) {
showError(error.message, error.name);
}
}
Once the list of local fonts is retrieved, a simplified and normalized fontsIndex
is created from it:
let fontsIndex = ;
for (let localFont of localFonts) {
let face = "400";
// Determine the face name
{
let subfamily = localFont.style.toLowerCase();
subfamily = subfamily.replaceAll(" ", "");
subfamily = subfamily.replaceAll("-", "");
subfamily = subfamily.replaceAll("_", "");
if (subfamily.includes("thin")) {
face = "100";
} else if (subfamily.includes("extralight")) {
face = "200";
} else if (subfamily.includes("light")) {
face = "300";
} else if (subfamily.includes("medium")) {
face = "500";
} else if (subfamily.includes("semibold")) {
face = "600";
} else if (subfamily.includes("extrabold")) {
face = "800";
} else if (subfamily.includes("ultrabold")) {
face = "900";
} else if (subfamily.includes("bold")) {
face = "700";
}
if (subfamily.includes("italic")) {
face += "i";
}
}
let descriptor = fontsIndex.find((descriptor) => {
return descriptor.family === localFont.family);
});
if (descriptor) {
if (descriptor.faces.includes(face) === false) {
descriptor.faces.push(face);
}
} else {
let descriptor = {
family: localFont.family,
faces: ,
};
fontsIndex.push(descriptor);
}
}
for (let descriptor of fontsIndex) {
descriptor.faces.sort();
}
The normalized fonts index is then stored in the IndexedDB database so that it can be easily queried, shared between app instances, and preserved between sessions. Boxy SVG uses Dexie.js to manage the database:
let database = new Dexie("LocalFontsManager");
database.version(1).stores({cache: "family"}).
await database.cache.clear();
await database.cache.bulkPut(fontsIndex);
Once the database is populated, the font picker widget can query it and display the results on the screen:
It’s worth mentioning that Boxy SVG renders the list in a custom element named <bx-fontfamilypicker>
and styles each font list item so that it’s displayed in the particular font family. To isolate from the rest of the page, Boxy SVG uses the Shadow DOM in this and other custom elements.
Conclusions
The local fonts feature has been really popular, with users enjoying access to their local fonts for their designs and creations. When the API shape changed and the feature broke briefly, users noted immediately. Jarosław was quick to change the code to the defensive pattern you can see in the snippet above that works with the up-to-date Chrome and also other Chromium derivatives that may not have switched to the latest version. Take Boxy SVG for a spin and be sure to check out your locally installed fonts. You might discover some long forgotten classics like Zapf Dingbats or Webdings.
This post is also available in: English