Files
hyperframes/packages/studio/src/components/editor/fontCatalog.ts
T
Miguel Ángel 0bf15119f8 feat: font resolution pipeline — compositions capture and embed their own fonts (#1255)
Compositions are now self-contained: the compiler captures font files
and embeds them as woff2 data URIs, eliminating silent render-time
fallback when the render environment lacks the author's fonts.

Resolution order (each tier falls through to the next):
1. Existing @font-face → use as-is
2. Bundled alias (38 cross-platform mappings) → embed data URI
3. Google Fonts → fetch, cache, embed
4. Local system font → locate on OS, compress to woff2, embed
5. Local @font-face paths → read file, compress, inline as data URI
6. External CDN stylesheets → fetch CSS, extract @font-face, inline
7. Alias map fallback → closest bundled equivalent
8. Actionable error with guidance

Key changes:
- System font locator (macOS/Windows/Linux) with path-bounding and
  symlink defense (realpathSync + O_NOFOLLOW)
- woff2 compression via wawoff2 (WASM, cross-platform)
- Multi-weight/style variant capture with length-sorted token matching
- External stylesheet inlining with SSRF defense (assertPublicHttpsUrl,
  HTTPS-only, private-host blocking, 2MB cap, 4-concurrent limit)
- Studio auto-import via GET /fonts/file API + renderAliasFor() derived
  from shared FONT_ALIAS_MAP (no more hand-curated drift)
- failClosedFontFetch throws on unresolved fonts in distributed renders
- Single source of truth: @hyperframes/core/fonts/aliases
- system_font_will_alias lint rule (escalates to warning for distributed)
- Default to Inter + JetBrains Mono in templates and CSS reset
2026-06-07 17:52:19 -04:00

134 lines
2.5 KiB
TypeScript

export const POPULAR_GOOGLE_FONT_FAMILIES = [
"ABeeZee",
"Abel",
"Abril Fatface",
"Alegreya",
"Alegreya Sans",
"Anton",
"Archivo",
"Archivo Black",
"Arimo",
"Assistant",
"Barlow",
"Barlow Condensed",
"Bebas Neue",
"Bitter",
"Bricolage Grotesque",
"Cabin",
"Cardo",
"Catamaran",
"Caveat",
"Chivo",
"Cormorant Garamond",
"Crimson Text",
"Dancing Script",
"DM Sans",
"DM Serif Display",
"Domine",
"EB Garamond",
"Exo 2",
"Figtree",
"Fira Code",
"Fira Sans",
"Fraunces",
"Fredoka",
"IBM Plex Mono",
"IBM Plex Sans",
"IBM Plex Serif",
"Inconsolata",
"Instrument Sans",
"Instrument Serif",
"Inter",
"JetBrains Mono",
"Josefin Sans",
"Jost",
"Kanit",
"Karla",
"Lato",
"League Gothic",
"Lexend",
"Libre Baskerville",
"Libre Franklin",
"Lora",
"Manrope",
"Merriweather",
"Montserrat",
"Mukta",
"Mulish",
"Newsreader",
"Noto Sans",
"Noto Sans JP",
"Noto Serif",
"Nunito",
"Nunito Sans",
"Open Sans",
"Oswald",
"Outfit",
"Overpass",
"Pacifico",
"Pathway Extreme",
"Permanent Marker",
"Playfair Display",
"Plus Jakarta Sans",
"Poppins",
"Prata",
"PT Sans",
"PT Serif",
"Public Sans",
"Quicksand",
"Raleway",
"Red Hat Display",
"Roboto",
"Roboto Condensed",
"Roboto Mono",
"Roboto Serif",
"Rubik",
"Schibsted Grotesk",
"Signika",
"Source Code Pro",
"Source Sans 3",
"Source Serif 4",
"Space Grotesk",
"Space Mono",
"Spectral",
"Sora",
"Syne",
"Teko",
"Titillium Web",
"Ubuntu",
"Ubuntu Mono",
"Unbounded",
"Urbanist",
"Varela Round",
"Work Sans",
"Young Serif",
"Zilla Slab",
] as const;
export const COMMON_LOCAL_FONT_FAMILIES = [
"TT Norms Pro",
"SF Pro Display",
"SF Pro Text",
"Avenir",
"Avenir Next",
"Menlo",
"Monaco",
] as const;
import { resolveAliasDisplayName } from "@hyperframes/core/fonts/aliases";
/**
* Resolves the render-time canonical font for a local font family name.
* Derived from the shared FONT_ALIAS_MAP — no hand-curation needed.
*/
export function renderAliasFor(family: string): string | undefined {
const display = resolveAliasDisplayName(family);
if (!display || display.toLowerCase() === family.toLowerCase()) return undefined;
return display;
}
export function googleFontStylesheetUrl(family: string): string {
const encodedFamily = encodeURIComponent(family.trim()).replace(/%20/g, "+");
return `https://fonts.googleapis.com/css2?family=${encodedFamily}:wght@300;400;500;600;700;800;900&display=swap`;
}