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

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

亚雷克·福克萨
Jarek Foksa

简介

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

Boxy SVG 是一种矢量图形编辑器。它的主要用途是修改 SVG 文件格式的绘图,用于创建插图、徽标、图标和平面设计的其他元素。该应用由波兰开发者 Jarosław Foksa 开发,最初于 2013 年 3 月 15 日发布。Jarosław 运营着一个 Boxy SVG 博客,他在该博客中宣布自己在应用中添加的新功能。该开发者是 Chromium Project Fugu 的有力支持者,甚至还在该应用的创意跟踪器上添加了 Fugu 标记

用于修改 Project Fugu 图标 SVG 的 Boxy SVG 应用。

Boxy SVG 中的 Local Font Access API

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

用于修改 Project Fugu 图标 SVG 的 Boxy 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 开发者工具的“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