How vector image editing app Boxy SVG uses the Local Font Access API to let users pick their favorite local fonts

The Local Font Access API provides a mechanism to access the user's locally installed font data, including higher-level details such as names, styles, and families, as well as the raw bytes of the underlying font files. Learn how the SVG editing app Boxy SVG makes use of this API.

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.

The Boxy SVG app editing the Project Fugu icon SVG.

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 Boxy SVG app editing the Project Fugu icon SVG adding the text 'Project Fugu rocks' set in the font Marker Felt, which is shown selected in the font picker.

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;
}

Font picker showing the message 'Your browser does not support the Local Font Access API'.

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: [face],
    };

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

Chrome DevTools Storage section showing the IndexedDB table with the fonts cache.

Once the database is populated, the font picker widget can query it and display the results on the screen:

Font picker populated with fonts.

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.

Chrome DevTools Elements panel showing the font picker being inspected: a custom element named 'bx-fontfamiliypicker'.

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.