UNPKG

@cf-wasm/og

Version:

Generate Open Graph Images dynamically from HTML/CSS without a browser.

249 lines (248 loc) 7.57 kB
// src/core/font.ts import { cache } from "./cache.js"; import { FetchError } from "./errors.js"; var FONT_CACHE_MAP = /* @__PURE__ */ new Map(); var GOOGLE_FONT_CSS_DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; de-at) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1"; var GoogleFontsUtils = class { constructor() { /** Default Google font css loader */ this._defaultCssLoader = async (cssUrl, userAgent) => { const cssResponse = await cache.serve( cssUrl, () => fetch(cssUrl, { headers: { "User-Agent": userAgent } }).then((res) => { if (!res.ok) { throw new FetchError(`Response was not successful (status: \`${res.status}\`, statusText: \`${res.statusText}\`)`, { response: res }); } return res; }).catch((e) => { throw new FetchError(`An error ocurred while fetching \`${cssUrl}\``, { cause: e, response: e instanceof FetchError ? e.response : void 0 }); }) ); return cssResponse.text(); }; /** Google font css loader */ this._cssLoader = this._defaultCssLoader; } /** * Loads Google font css * * @param cssUrl The Google font css url * @param userAgent The user agent header to be used * * @returns On success, the css as string */ async loadCss(cssUrl, userAgent = GOOGLE_FONT_CSS_DEFAULT_USER_AGENT) { let css = await this._cssLoader(cssUrl, userAgent); if (typeof css === "undefined") { css = await this._defaultCssLoader(cssUrl, userAgent); } else if (typeof css !== "string") { throw new Error("Google font css loader return value must resolve to string"); } return css; } /** * Sets Google font css loader * * @param loader The {@link GoogleFontCssLoader} */ setCssLoader(loader) { if (typeof loader !== "function") { throw new TypeError("Argument 1 must be a function."); } this._cssLoader = loader; } }; var googleFonts = new GoogleFontsUtils(); var constructGoogleFontCssUrl = (family, { text, weight = 400, style = "normal", display } = {}) => { if (typeof family !== "string" || family.trim().length === 0) { throw new Error("Not a valid font family name was provided"); } const params = { family: `${family.replaceAll(" ", "+")}:${style === "italic" ? "ital," : ""}wght@${style === "italic" ? "1," : ""}${weight}` }; if (text) { params.text = encodeURIComponent(text); } else { params.subset = "latin"; } if (typeof display === "string") { params.display = display; } const cssUrl = `https://fonts.googleapis.com/css2?${Object.keys(params).map((key) => `${key}=${params[key]}`).join("&")}`; return cssUrl; }; var loadGoogleFont = async (family, { text, weight = 400, style = "normal", display } = {}) => { const cssUrl = constructGoogleFontCssUrl(family, { text, weight, display, style }); const fromMap = FONT_CACHE_MAP.get(cssUrl); if (fromMap) { return fromMap; } const css = await googleFonts.loadCss(cssUrl); const fontUrl = css.match(/src: url\((.+)\) format\('(opentype|truetype)'\)/)?.[1]; if (!fontUrl) { throw new Error("The css does not content source for truetype font."); } const fontResponse = await cache.serve( fontUrl, () => fetch(fontUrl).then((res) => { if (!res.ok) { throw new FetchError(`Response was not successful (status: ${res.status}, statusText: ${res.statusText})`, { response: res }); } return res; }).catch((e) => { throw new FetchError(`An error ocurred while fetching ${fontUrl}`, { cause: e, response: e instanceof FetchError ? e.response : void 0 }); }) ); const buffer = await fontResponse.arrayBuffer(); FONT_CACHE_MAP.set(cssUrl, buffer); return buffer; }; var BaseFont = class { constructor(name, input, { weight = 400, style = "normal" } = {}) { this.input = input; this.name = name; this.style = style; this.weight = weight; } get data() { return this.input; } }; var CustomFont = class extends BaseFont { /** * Creates an instance of {@link CustomFont} * * @param name The name of the font (can be used for font-family css property) * @param input Font data as `ArrayBuffer` or a promise which resolves to `ArrayBuffer` * @param options */ constructor(name, input, options) { super(name, input, options); this.input = input; this.lang = options?.lang; } /** * A promise which resolves to font data as `ArrayBuffer` */ get data() { const fallback = async () => typeof this.input === "function" ? this.input() : this.input; this.promise = this.promise?.then(null, fallback) ?? fallback(); return this.promise; } }; var GoogleFont = class extends BaseFont { /** * Creates an instance of {@link GoogleFont} * * @param family The name of font family to load * @param options The {@link GoogleFontOptions} */ constructor(family, options = {}) { super(options.name || family, void 0, options); this.family = family; this.text = options.text; this.input = void 0; } /** A promise which resolves to font data as `ArrayBuffer` */ get data() { const fallback = async () => loadGoogleFont(this.family, { weight: this.weight, style: this.style, text: this.text }); this.promise = this.promise?.then(null, fallback) ?? fallback(); return this.promise; } /** * Checks whether font can load buffer * * @returns On success, true otherwise the error object thrown */ async canLoad() { try { await this.data; } catch (e) { return e; } return true; } }; var DefaultFont = class { constructor() { this._fontShouldResolve = true; } /** * Sets default font for image rendering * * @param input {@link FontInput} */ set(input) { if (!input) { throw new TypeError("Argument 1 type is not acceptable"); } this._fontShouldResolve = true; this._fallbackFont = input; } /** * Gets default font buffer for image rendering if it is set * * @returns A Promise which resolves to ArrayBuffer if default font is set, * otherwise undefined */ async get() { const { _fallbackFont, _fontData, _fontShouldResolve } = this; let buffer; const fontInput = typeof _fallbackFont === "function" ? _fallbackFont() : _fallbackFont; if (_fontShouldResolve && fontInput) { if (fontInput instanceof Promise) { const result = await fontInput; if (result instanceof Response) { buffer = await result.arrayBuffer(); } else { buffer = result; } } else if (fontInput instanceof Response) { buffer = await fontInput.arrayBuffer(); } else if ("data" in fontInput) { buffer = await fontInput.data; } else { buffer = fontInput; } } else if (_fontData) { buffer = _fontData; } this._fontData = buffer; this._fontShouldResolve = false; return buffer; } /** * Check whether default font is set or not * * @returns true if default font is set, otherwise false */ has() { return Boolean(this._fallbackFont); } }; var defaultFont = new DefaultFont(); export { BaseFont, CustomFont, FONT_CACHE_MAP, GOOGLE_FONT_CSS_DEFAULT_USER_AGENT, GoogleFont, constructGoogleFontCssUrl, defaultFont, googleFonts, loadGoogleFont };