矢量图像编辑应用 Boxy SVG 如何使用 Local Font Access API 让用户选择他们喜爱的本地字体

Local Font Access API 提供了一种机制来访问用户本地安装的字体数据,包括名称、样式和字体系列等更高级别的详细信息,以及底层字体文件的原始字节。了解 SVG 编辑应用 Boxy SVG 如何使用此 API。

简介

(本文也可以视频形式提供)。

Boxy SVG 是一款矢量图形编辑器。其主要用例是编辑 SVG 文件格式的绘图,以创建插图、徽标、图标和其他平面设计元素。该应用由波兰开发者 Jarosslaw Foksa 开发,最初于 2013 年 3 月 15 日发布。Jarosław 运营着 Boxy SVG 博客,在其中宣布他向应用添加的新功能。这位开发者是 Chromium 的 Project Fugu 的坚定支持者,甚至在该应用的想法跟踪器上设置了 Fugu 标记

使用 Boxy SVG 应用修改 Project Fugu 图标 SVG。

Boxy SVG 中的 Local Font Access API

Jarosław 在博客中提到的新增功能之一是 Local Font Access API。借助 Local Font Access API,用户可以访问本地安装的字体,包括名称、样式和系列等更高级别的详细信息,以及底层字体文件的原始字节。在下面的屏幕截图中,您可以看到我如何授权该应用访问我的 MacBook 上本地安装的字体,并为我的文本选择 Marker Felt 字体。

Boxy SVG 应用中,正在编辑 Project Fugu 图标 SVG,并在字体选择器中选择的 Marker Felt 字体中添加了文本“Project Fugu rocks”。

底层代码相当简单。当用户首次打开字体系列选择器时,应用会先检查网络浏览器是否支持 Local Font Access API。

此外,它还会检查 API 的旧实验版本,如果存在的话,则使用该版本。从 2023 年开始,您可以放心地忽略旧版 API,因为它仅通过实验性 Chrome 标志提供短期服务,但某些 Chromium 衍生产品可能仍会使用它。

let isLocalFontsApiEnabled = (
  // Local Font Access API, Chrome >= 102
  window.queryLocalFonts !== undefined ||
  // Experimental Local Font Access API, Chrome < 102
  navigator.fonts?.query !== undefined
);

如果 Local Font Access API 不可用,字体系列选择器将变灰。系统会向用户显示占位符文本,而不是字体列表:

if (isLocalFontsApiEnabled === false) {
  showPlaceholder("no-local-fonts-api");
  return;
}

显示“您的浏览器不支持 Local Font Access API”消息的字体选择器。

否则,系统会使用 Local Font Access API 从操作系统检索所有字体的列表。请注意 try…catch 块,它是正确处理权限错误所必需的。

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

检索到本地字体列表后,系统会根据该列表创建经过简化和标准化的 fontsIndex

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

然后,标准化字体索引会存储在 IndexedDB 数据库中,以便可以轻松查询、在应用实例之间共享以及在会话之间保留。Boxy SVG 使用 Dexie.js 来管理数据库:

let database = new Dexie("LocalFontsManager");
database.version(1).stores({cache: "family"}).
await database.cache.clear();
await database.cache.bulkPut(fontsIndex);

Chrome DevTools 中的“Storage”部分,显示了包含字体缓存的 IndexedDB 表。

填充数据库后,字体选择器 widget 可以对其进行查询,并在屏幕上显示结果:

填充了字体的字体选择器。

值得注意的是,Boxy SVG 会在名为 <bx-fontfamilypicker> 的自定义元素中呈现列表,并为每个字体列表项设置样式,以便以特定字体系列显示。为了与网页的其余部分隔离开来,Boxy SVG 在此自定义元素和其他自定义元素中使用了 Shadow DOM

Chrome DevTools 的“Elements”面板,其中显示了正在检查的字体选择器:一个名为“bx-fontfamiliypicker”的自定义元素。

总结

本地字体功能非常受欢迎,用户喜欢在设计和作品中使用本地字体。当 API 形状发生变化并导致功能暂时无法使用时,用户会立即注意到。Jarosław 很快就将代码更改为上述代码段中显示的防御模式,该模式适用于最新版 Chrome 以及可能尚未改用最新版本的其他 Chromium 派生产品。试用一下 Boxy SVG,并务必查看您在本地安装的字体。您可能会发现一些早已被遗忘的经典字体,例如 Zapf DingbatsWebdings