mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-07 18:26:17 +00:00
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
79 lines
2.3 KiB
TypeScript
79 lines
2.3 KiB
TypeScript
import { describe, it, expect, beforeAll } from "vitest";
|
|
import {
|
|
locateSystemFont,
|
|
clearSystemFontCache,
|
|
SYSTEM_FONT_SIZE_LIMIT,
|
|
} from "./systemFontLocator";
|
|
|
|
describe("systemFontLocator", { timeout: 15_000 }, () => {
|
|
beforeAll(() => {
|
|
clearSystemFontCache();
|
|
});
|
|
|
|
it("returns null for nonexistent fonts", () => {
|
|
const result = locateSystemFont("nonexistent-font-xyz-12345");
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("normalizes case when looking up fonts", () => {
|
|
const lower = locateSystemFont("helvetica");
|
|
const upper = locateSystemFont("HELVETICA");
|
|
if (lower === null) {
|
|
expect(upper).toBeNull();
|
|
} else {
|
|
expect(upper).not.toBeNull();
|
|
expect(upper!.path).toBe(lower.path);
|
|
}
|
|
});
|
|
|
|
it("returns a valid format field when a font is found", () => {
|
|
const result =
|
|
locateSystemFont("Helvetica") ?? locateSystemFont("Arial") ?? locateSystemFont("DejaVu Sans");
|
|
if (result) {
|
|
expect(["ttf", "otf", "woff2", "woff", "ttc"]).toContain(result.format);
|
|
expect(result.path).toBeTruthy();
|
|
}
|
|
});
|
|
|
|
it("caches results across calls", () => {
|
|
const first = locateSystemFont("nonexistent-font-xyz-12345");
|
|
const second = locateSystemFont("nonexistent-font-xyz-12345");
|
|
expect(first).toBeNull();
|
|
expect(second).toBeNull();
|
|
});
|
|
|
|
it("clearSystemFontCache resets the cache", () => {
|
|
locateSystemFont("nonexistent-font-xyz-12345");
|
|
clearSystemFontCache();
|
|
const result = locateSystemFont("nonexistent-font-xyz-12345");
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("exports SYSTEM_FONT_SIZE_LIMIT as 5MB", () => {
|
|
expect(SYSTEM_FONT_SIZE_LIMIT).toBe(5 * 1024 * 1024);
|
|
});
|
|
|
|
it("strips quotes from family name input", () => {
|
|
const result = locateSystemFont('"nonexistent-font-xyz-12345"');
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("returns null for empty string", () => {
|
|
expect(locateSystemFont("")).toBeNull();
|
|
expect(locateSystemFont(" ")).toBeNull();
|
|
});
|
|
|
|
if (process.platform === "darwin") {
|
|
it("finds Helvetica on macOS", () => {
|
|
const result = locateSystemFont("Helvetica");
|
|
expect(result).not.toBeNull();
|
|
expect(result!.path).toMatch(/\.(ttf|ttc|otf)$/i);
|
|
});
|
|
|
|
it("finds Courier on macOS", () => {
|
|
const result = locateSystemFont("Courier");
|
|
expect(result).not.toBeNull();
|
|
});
|
|
}
|
|
});
|