/** * 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