ベクター画像編集アプリ 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 Font Access API

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

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

基盤となるコードは非常にシンプルです。ユーザーが初めてフォント ファミリー選択ツールを開くと、アプリケーションはまず、ウェブブラウザが Local Font Access API をサポートしているかどうかを確認します。

また、古い試験運用版の API がないかチェックし、存在する場合はそのバージョンを使用します。2023 年現在、試験運用版の Chrome フラグを通じて短期間しか利用できなかったため、古い API を無視しても問題ありませんが、一部の 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 の [ストレージ] セクション。

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

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

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

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

まとめ

ローカル フォント機能は非常に好評で、ユーザーはデザインや創作でローカル フォントを利用することができます。API の形状が変更され、機能が短時間使用できなくなると、ユーザーはすぐにそのことに気付きました。Jarosław はコードを、最新の Chrome や、最新バージョンに切り替えられていない他の Chromium の派生物で機能する防御パターンにすばやく変更しました。上記のスニペットをご覧ください。Boxy SVG を実際に使ってみて、ローカルにインストールしたフォントを確認します。Zapf DingbatsWebdings など、長く忘れられてきた名作が見つかることもあります。