logo hsb.horse
← ブログ一覧に戻る

ブログ

ブラウザでGoogle FontsからWOFFファイルを取得しようとして失敗した備忘録

クライアントサイドでGoogle Fontsからttf/otf/woffファイルを取得して@vercel/satoriで使おうとした試行錯誤の記録。User-Agentヘッダー制限とwoff2非対応問題について整理。

公開日:

ブラウザ上でvercel/satoriを使って画像生成したかった。

フォントファイルをセルフホストすれば問題なくsatoriを利用できるが、できればセルフホストしたくない(対応フォントが増えたら面倒)。

Google Font経由でフォントファイルをダウンロードして、satoriで利用できないか試したが、結論から言うと失敗した。

結論

satoriがwoff2に対応していれば実現できるが、対応していない現状フロントエンド(クライアントサイド)のみでは実現できない。

サーバサイドがあれば実現できる。

要件

  • サーバサイドなし
  • ユーザがGoogle Fontsから使いたいフォントの名前とスタイルを選んで、入力欄に入力する想定

想定した処理フロー

  1. ユーザーがフォント名とスタイルを入力
  2. Google Fonts APIからCSSを取得
  3. CSSからフォントファイルのURLを抽出
  4. フォントファイルをダウンロード
  5. satoriで画像生成

CSSの Fetch

Google Fontはユーザーエージェントに合わせて、返却するフォントファイルの形式が変わるのでUser-Agentヘッダーを書き換えることで解決を試みた。

が、よくよく考えれば禁止ヘッダーなので上書きができず、目論見はここで頓挫する。

export const USER_AGENTS_BY_FONT_TYPE = {
// IE11 as woff
woff: "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko",
// Googlebot as truetype
ttf: "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)",
};
export type FontType = keyof typeof USER_AGENTS_BY_FONT_TYPE;
export async function fetchGoogleFontCSS(
url: string,
fontType: FontType = "ttf"
) {
const response = await fetch(url, {
headers: {
"User-Agent": USER_AGENTS_BY_FONT_TYPE[fontType],
},
});
if (!response.ok) {
throw new Error(
`Failed to load font: ${response.status} ${response.statusText}`
);
}
return response.text();
}

User-Agentは禁止ヘッダーのため、fetchで上書きできない。

CSSからデータを抽出する

仮にCSSが取得できた場合の実装は以下の通り。

const subsetRegex = /\/\* (.+?) \*\//;
const fontFileRegex = /src: url\((.+?)\)/;
type ExtractResult = {
subsets: Record<string, string>;
hasItems: boolean;
};
const extract = (regex: RegExp, line: string) => regex.exec(line)?.[1];
export function extractFontFromCSS(css: string): ExtractResult {
const subsets: Record<string, string> = {};
let currentSubset = "";
for (const line of css.split("\n")) {
const subset = extract(subsetRegex, line);
if (subset) {
currentSubset = subset;
continue;
}
const url = extract(fontFileRegex, line);
if (url && !subsets[currentSubset]) {
subsets[currentSubset] = url;
}
}
return {
subsets,
hasItems: Object.keys(subsets).length > 0,
};
}

終わりに

というわけで、結局サーバサイドないしプロキシが必要になるという結論で終わり。

ブラウザからの制約(禁止ヘッダー、woff2のみ配信)により、クライアントサイドだけでの実現は断念した。

参考資料

まとめ

ブラウザでGoogle Fontsからwoff/ttfを取得しようとしたが、User-Agent制限とsatoriのwoff2非対応により失敗。

サーバサイドまたはプロキシを経由する必要がある。失敗から学んだ知見として記録を残しておく。