Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,17 @@ describe("injectDeterministicFontFaces — failClosedFontFetch: true", () => {
expect(result).toBe(html);
});

it("does NOT throw when font-family uses a CSS var() reference with a fallback", async () => {
const html = `<!doctype html><html><head><style>
.title { font-family: var(--brand-font, inherit); }
</style></head><body><h1 class="title">hello</h1></body></html>`;
const result = await injectDeterministicFontFaces(html, {
failClosedFontFetch: true,
fetchImpl: makeFailingFetch(),
});
expect(result).toBe(html);
});

it("resolves simple CSS var() font aliases when injecting deterministic fonts", async () => {
const html = `<!doctype html><html><head><style>
:root { --ui-font: "Inter"; --vowel-font: "Montserrat"; }
Expand Down
38 changes: 36 additions & 2 deletions packages/producer/src/services/deterministicFonts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,44 @@ export const GENERIC_FAMILIES: ReadonlySet<string> = new Set([
* Whitespace and surrounding `"…"` / `'…'` quotes are stripped; case is
* preserved. Pass each name through `normalizeFamilyName` for case-
* insensitive comparisons.
*
* Only top-level commas split: a `var(--x, fallback)` expression stays one
* token, as does a comma inside a quoted family name.
*/
export function parseFontFamilyValue(value: string): string[] {
return value
.split(",")
const pieces: string[] = [];
let start = 0;
let depth = 0;
let quote: "'" | '"' | null = null;
for (let index = 0; index < value.length; index += 1) {
const char = value[index];
if (char === "\\") {
index += 1;
continue;
}
if (quote) {
if (char === quote) quote = null;
continue;
}
if (char === "'" || char === '"') {
quote = char;
continue;
}
if (char === "(") {
depth += 1;
continue;
}
if (char === ")") {
depth = Math.max(0, depth - 1);
continue;
}
if (char !== "," || depth !== 0) continue;
pieces.push(value.slice(start, index));
start = index + 1;
}
pieces.push(value.slice(start));

return pieces
.map((piece) => piece.trim().replace(/^['"]/, "").replace(/['"]$/, "").trim())
.filter((piece) => piece.length > 0);
}
Expand Down
20 changes: 20 additions & 0 deletions packages/producer/src/services/render/planValidation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,26 @@ describe("parseFontFamilyValue", () => {
expect(parseFontFamilyValue(`'My Custom Font', serif`)).toEqual(["My Custom Font", "serif"]);
});

it("keeps a comma inside a quoted family name", () => {
expect(parseFontFamilyValue(`"Display, Condensed", serif`)).toEqual([
"Display, Condensed",
"serif",
]);
});

it("keeps a var() fallback in a single token", () => {
expect(parseFontFamilyValue(`var(--brand-font, inherit), sans-serif`)).toEqual([
"var(--brand-font, inherit)",
"sans-serif",
]);
});

it("keeps a nested var() fallback in a single token", () => {
expect(
parseFontFamilyValue(`var(--brand-font, var(--fallback-font, "Inter")), sans-serif`),
).toEqual([`var(--brand-font, var(--fallback-font, "Inter"))`, "sans-serif"]);
});

it("ignores empty entries (trailing commas)", () => {
expect(parseFontFamilyValue(`Inter,,sans-serif`)).toEqual(["Inter", "sans-serif"]);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
top: 30%;
left: 50%;
transform: translateX(-50%);
font-family: var(--display-font), sans-serif;
font-family: var(--display-font, "Montserrat"), sans-serif;
font-size: 48px;
font-weight: 900;
color: #e94560;
Expand Down
Loading