/**
* Build-time Google Fonts automation - the `next/font/google` equivalent. At **build time** (never on
* the request path) it: builds the Google Fonts CSS2 URL, downloads the stylesheet, parses the
* `@font-face` rules, downloads each `.woff2`, content-hashes it, writes it next to your assets, and
* hands back a **self-hosted** `@font-face` stylesheet + the matching ``s. The
* result is identical to dropping the files in yourself and calling {@link fontFace} - no runtime CDN
* hotlink, no layout shift, hashed filenames for immutable caching.
*
* // fonts.build.ts - run once at build time (e.g. a prebuild step)
* import { loadGoogleFont } from "@nifrajs/web/fonts"
* const inter = await loadGoogleFont(
* { family: "Inter", weights: [400, 700], subsets: ["latin"] },
* { outDir: "public/fonts" }, // → public/fonts/inter-latin-normal-400-.woff2
* )
* await Bun.write("app/fonts.css", inter.css) // import this stylesheet from your app
* // inter.preloads → spread into a root layout's `meta.link`
*
* Security: this fetches remote content and writes it to disk, so every input is validated and the
* font-file host is **allowlisted to `fonts.gstatic.com` over https** - a tampered/MITM'd stylesheet
* cannot make the build fetch an arbitrary URL (SSRF) or write an attacker-chosen blob. Downloads are
* size-capped. Filenames are derived only from validated tokens + a content hash (no path traversal).
*/
import { type FontDisplay } from "./fonts.js";
import type { LinkDescriptor } from "./manifest.js";
/** Options describing the Google font to fetch + self-host. */
export interface GoogleFontOptions {
/** Family name exactly as Google lists it, e.g. `"Inter"`, `"Open Sans"`, `"Roboto Mono"`. */
readonly family: string;
/** Weights to request - numbers (`400`), numeric strings, a variable range (`"100..900"`, the
* CSS-style `"100 900"` also accepted), or the keywords `"normal"`/`"bold"`. Defaults to `[400]`. */
readonly weights?: readonly (number | string)[];
/** Styles to request. Defaults to `["normal"]`. */
readonly styles?: readonly ("normal" | "italic")[];
/** Keep only these named subsets (`"latin"`, `"latin-ext"`, `"cyrillic"`, …). Google returns every
* subset it has as a separate `@font-face`; this filters to the ones you serve. Defaults to keeping
* all returned subsets. Ignored when {@link text} is set (glyph subsetting supersedes it). */
readonly subsets?: readonly string[];
/** `font-display` strategy for the generated faces. Defaults to `"swap"`. */
readonly display?: FontDisplay;
/** Glyph subsetting: request only the glyphs needed to render exactly this text (Google's `&text=`).
* Ideal for a logo/heading font - produces one tiny file. */
readonly text?: string;
/** CLS metric overrides forwarded to every generated `@font-face` (the layout-shift fix). */
readonly sizeAdjust?: string;
readonly ascentOverride?: string;
readonly descentOverride?: string;
readonly lineGapOverride?: string;
}
/** A single `@font-face` block parsed out of Google's stylesheet. */
export interface ParsedFontFace {
readonly family: string;
readonly style: string;
readonly weight: string;
readonly subset: string;
readonly unicodeRange?: string;
readonly src: readonly {
readonly url: string;
readonly format?: string;
}[];
}
/** One downloaded + written font file. */
export interface FontAsset {
/** The hashed filename written under `outDir` (no directory part). */
readonly fileName: string;
/** The public URL the generated `@font-face`/preload reference (`${publicPath}/${fileName}`). */
readonly href: string;
/** The original `fonts.gstatic.com` URL the bytes came from. */
readonly sourceUrl: string;
readonly bytes: Uint8Array;
readonly subset: string;
readonly weight: string;
readonly style: string;
}
export interface LoadGoogleFontResult {
readonly family: string;
/** A self-hosted `@font-face` stylesheet (one rule per written file). Import it from your app. */
readonly css: string;
/** Every file written to `outDir`. */
readonly assets: readonly FontAsset[];
/** `fontPreload()` link-attribute sets - spread the ones you want into a layout's `meta.link`.
* Preloading *every* weight/subset is wasteful; usually preload just the primary subset + weight. */
readonly preloads: readonly LinkDescriptor[];
}
export interface LoadGoogleFontIO {
/** Directory to write the hashed `.woff2` files into (created if missing). */
readonly outDir: string;
/** URL prefix the files are served under. Defaults to `"/fonts"`. */
readonly publicPath?: string;
/** Injectable `fetch` (defaults to the global). Tests pass a canned implementation. */
readonly fetch?: typeof fetch;
/** Injectable writer (defaults to `node:fs`). Tests pass an in-memory sink. */
readonly writeFile?: (path: string, bytes: Uint8Array) => Promise;
/** Per-file download cap in bytes. Defaults to 5 MB (real woff2 are well under 1 MB). */
readonly maxBytesPerFile?: number;
}
/** `true` iff `raw` is an `https://fonts.gstatic.com/…` URL - the only host we'll download from. */
export declare function isAllowedFontUrl(raw: string): boolean;
/** Build the Google Fonts CSS2 request URL. Pure + fully validated, so it's safe to feed a dynamic
* family/weights/text. Exported for advanced callers who fetch + parse the stylesheet themselves. */
export declare function googleFontsCssUrl(options: GoogleFontOptions): string;
/** Parse Google's stylesheet into structured faces, capturing the `/* subset */` label that precedes
* each `@font-face`. Pure - exported so callers can run their own download/write pipeline. */
export declare function parseGoogleFontCss(css: string): ParsedFontFace[];
/**
* Download a Google font, self-host it, and return a CLS-safe `@font-face` stylesheet + preloads.
* See the module header for the full flow and security model. I/O (`fetch`, `writeFile`) is injectable
* so this is unit-testable without the network.
*/
export declare function loadGoogleFont(options: GoogleFontOptions, io: LoadGoogleFontIO): Promise;
//# sourceMappingURL=fonts-google.d.ts.map