logo hsb.horse
← Back to blog index

Blog

Notes on failed attempts to retrieve WOFF files from Google Fonts in browser

A record of trial and error trying to obtain ttf/otf/woff files from Google Fonts on the client side and use them with @vercel/satori. Sorted out User-Agent header limitations and woff2 non-compatibility issues.

Published:

I wanted to generate an image using vercel/satori on the browser.

If you self-host the font files, you can use satori without any problems, but if possible, you don’t want to self-host (it will be troublesome if the number of supported fonts increases).

I tried downloading a font file via Google Font and seeing if it could be used with satori, but in conclusion it failed.

Conclusion

This can be achieved if satori supports woff2, but it cannot be achieved with only the front end (client side), which does not currently support it.

This can be achieved if there is a server side.

Requirements

  • No server side
  • It is assumed that the user selects the name and style of the font they want to use from [Google Fonts] (https://fonts.google.com/) and enters it in the input field.

Assumed processing flow

  1. User enters font name and style
  2. Get CSS from Google Fonts API
  3. Extract font file URL from CSS
  4. Download font file
  5. Image generation with satori

CSS Fetch

Google Font changes the format of the font file it returns depending on the user agent, so I tried to solve this by rewriting the User-Agent header.

However, if you think about it carefully, since it is a prohibited header, it cannot be overwritten, and the plan is stalled here.

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 cannot be overwritten with fetch because it is a [prohibited header] (https://developer.mozilla.org/ja/docs/Glossary/Forbidden_header_name).

Extract data from CSS

If you can obtain CSS, the implementation is as follows.

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

At the end

So, I ended up concluding that I needed a server side or proxy.

Due to restrictions from the browser (prohibited headers, only delivery of woff2), we gave up on implementing it only on the client side.

Reference materials

summary

I tried to get woff/ttf from Google Fonts in the browser, but it failed due to User-Agent restrictions and satori’s incompatibility with woff2.

Need to go through server side or proxy. Keep a record of what you learned from your failures.