ベクター画像編集アプリ Boxy SVG が、Local Font Access API を使用してユーザーがお気に入りのローカル フォントを選択できるようにする方法

Local Font Access API は、ユーザーがローカルにインストールしたフォントデータにアクセスするためのメカニズムを提供します。名前、スタイル、ファミリーなどの上位レベルの詳細情報や、基盤となるフォント ファイルの未加工バイトなどです。SVG 編集アプリ Boxy SVG がこの API を使用する方法を学びます。

はじめに

(この記事は動画でもご覧いただけます)。

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 Fonts Access API

Jarosław がブログ投稿で解説した追加機能の一つに、Local Font Access API があります。Local Font Access API を使用すると、ローカルにインストールされているフォント(名前、スタイル、ファミリーなどの上位レベルの詳細情報や、基盤となるフォント ファイルの未加工バイトなど)にアクセスできます。次のスクリーンショットは、MacBook にローカルにインストールされているフォントへのアクセスをアプリに許可し、テキストに Marker Felt フォントを選択した様子を示しています。

フォント選択ツールで選択されているフォント「Marker Felt」で「Project Fugu rocks」というテキストを追加して、Project Fugu アイコンの SVG を編集している Boxy SVG アプリ。

基盤となるコードは非常にシンプルです。ユーザーがフォント ファミリー選択ツールを初めて開いたとき、アプリはまず、ウェブブラウザが 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);

フォント キャッシュを含む IndexedDB テーブルが表示された Chrome DevTools の [Storage] セクション。

データベースにデータが入力されると、フォント選択ウィジェットはデータベースにクエリを実行し、結果を画面に表示できます。

フォントが入力されたフォント選択ツール。

Boxy SVG は、リストを <bx-fontfamilypicker> という名前のカスタム要素でレンダリングし、各フォントリスト アイテムにスタイルを適用して、特定のフォント ファミリーで表示します。Boxy SVG では、ページの他の部分から分離するために、このカスタム要素と他のカスタム要素で Shadow DOM を使用しています。

検証中のフォント選択ツール(「bx-fontfamiliypicker」という名前のカスタム要素)が表示された Chrome DevTools の [Elements] パネル。

まとめ

ローカル フォント機能は非常に人気があり、ユーザーはローカル フォントを使用してデザインや作成を楽しんでいます。API の形状が変更され、機能が短時間停止したとき、ユーザーはすぐに気付きました。Jarosław はコードを、上記のスニペットに示されている防御パターンに迅速に変更しました。この防御パターンは、最新の Chrome だけでなく、最新バージョンに切り替わっていない可能性のあるその他の Chromium の派生物でも動作します。Boxy SVG を試して、ローカルにインストールされているフォントを確認してください。Zapf DingbatsWebdings など、長い間忘れられていたクラシックなフォントを見つけられるかもしれません。