mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-09-29 16:24:22 +10:00
fix(fonts): register Vazirmatn in webfont catalog for JSON imports (#3331)
* fix(fonts): register Vazirmatn in webfont catalog for JSON imports Imported resumes can set typography.fontFamily to Vazirmatn, but the popularity-sorted Google Fonts slice omits it so PDF registration fell back to IBM Plex Serif and Persian/Arabic glyphs stacked or tofu (#3098). Add Vazirmatn as a locale-coverage manual entry (same pattern as Carlito) and cover catalog resolution with unit tests. * test(pdf): keep Vazirmatn as primary family for fa-IR registration Prove JSON-imported Vazirmatn is handed to Font.register instead of being rewritten to IBM Plex Serif (#3098). * fix(fonts): address CodeRabbit review on Vazirmatn catalog Assert getWebFontSource resolves files["400"] for Vazirmatn instead of only matching the preview fallback, and split the font-generation log line to satisfy the 120-column Biome limit.
This commit is contained in:
@@ -289,3 +289,20 @@ describe("legacy font compatibility (#2989)", () => {
|
||||
expect(getFontDisplayName("Times New Roman")).toBe("Times New Roman");
|
||||
});
|
||||
});
|
||||
|
||||
describe("locale coverage fonts (#3098)", () => {
|
||||
it("resolves Vazirmatn from the webfont catalog", () => {
|
||||
expect(getFont("Vazirmatn")?.family).toBe("Vazirmatn");
|
||||
expect(getWebFont("Vazirmatn")?.category).toBe("sans-serif");
|
||||
});
|
||||
|
||||
it("returns a gstatic source for Vazirmatn regular so PDF registration can load it", () => {
|
||||
const regularSource = getWebFont("Vazirmatn")?.files["400"];
|
||||
expect(regularSource).toBeDefined();
|
||||
|
||||
const source = getWebFontSource("Vazirmatn", "400");
|
||||
expect(source).toBe(regularSource);
|
||||
expect(source).toEqual(expect.stringContaining("fonts.gstatic.com"));
|
||||
expect(source).toEqual(expect.stringMatching(/\.ttf(\?|$)/));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7522,5 +7522,23 @@
|
||||
"400italic": "https://fonts.gstatic.com/s/carlito/v4/3Jn_SDPw3m-pk039DDKxTl0F.ttf",
|
||||
"700italic": "https://fonts.gstatic.com/s/carlito/v4/3Jn6SDPw3m-pk039DDK59XgVUcBD.ttf"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "web",
|
||||
"category": "sans-serif",
|
||||
"family": "Vazirmatn",
|
||||
"weights": ["100", "200", "300", "400", "500", "600", "700", "800", "900"],
|
||||
"preview": "https://fonts.gstatic.com/s/vazirmatn/v16/Dxx78j6PP2D_kU2muijPEe1n2vVbfJRklWgzCRCT60LA7Qdazg.ttf",
|
||||
"files": {
|
||||
"100": "https://fonts.gstatic.com/s/vazirmatn/v16/Dxx78j6PP2D_kU2muijPEe1n2vVbfJRklWgyCRCT60LA7Qdazg.ttf",
|
||||
"200": "https://fonts.gstatic.com/s/vazirmatn/v16/Dxx78j6PP2D_kU2muijPEe1n2vVbfJRklegzCRCT60LA7Qdazg.ttf",
|
||||
"300": "https://fonts.gstatic.com/s/vazirmatn/v16/Dxx78j6PP2D_kU2muijPEe1n2vVbfJRklTYzCRCT60LA7Qdazg.ttf",
|
||||
"400": "https://fonts.gstatic.com/s/vazirmatn/v16/Dxx78j6PP2D_kU2muijPEe1n2vVbfJRklWgzCRCT60LA7Qdazg.ttf",
|
||||
"500": "https://fonts.gstatic.com/s/vazirmatn/v16/Dxx78j6PP2D_kU2muijPEe1n2vVbfJRklVozCRCT60LA7Qdazg.ttf",
|
||||
"600": "https://fonts.gstatic.com/s/vazirmatn/v16/Dxx78j6PP2D_kU2muijPEe1n2vVbfJRklbY0CRCT60LA7Qdazg.ttf",
|
||||
"700": "https://fonts.gstatic.com/s/vazirmatn/v16/Dxx78j6PP2D_kU2muijPEe1n2vVbfJRklY80CRCT60LA7Qdazg.ttf",
|
||||
"800": "https://fonts.gstatic.com/s/vazirmatn/v16/Dxx78j6PP2D_kU2muijPEe1n2vVbfJRkleg0CRCT60LA7Qdazg.ttf",
|
||||
"900": "https://fonts.gstatic.com/s/vazirmatn/v16/Dxx78j6PP2D_kU2muijPEe1n2vVbfJRklcE0CRCT60LA7Qdazg.ttf"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -299,6 +299,25 @@ describe("registerFonts", () => {
|
||||
expect(registerSpy).not.toHaveBeenCalledWith(expect.objectContaining({ family: "Lato", fontWeight: 600 }));
|
||||
expect(registerSpy).toHaveBeenCalledWith(expect.objectContaining({ family: "Lato", fontWeight: 700 }));
|
||||
});
|
||||
|
||||
it("keeps Vazirmatn as the primary PDF family instead of substituting IBM Plex Serif (#3098)", async () => {
|
||||
const registerSpy = vi.spyOn(Font, "register").mockImplementation(() => {});
|
||||
vi.spyOn(Font, "registerHyphenationCallback").mockImplementation(() => {});
|
||||
const { registerFonts } = await import("./use-register-fonts");
|
||||
|
||||
const vazirTypography = {
|
||||
...typography,
|
||||
body: { ...typography.body, fontFamily: "Vazirmatn", fontWeights: ["400"] },
|
||||
heading: { ...typography.heading, fontFamily: "Vazirmatn", fontWeights: ["700"] },
|
||||
} satisfies Typography;
|
||||
|
||||
const pdfTypography = registerFonts(vazirTypography, "fa-IR");
|
||||
|
||||
expect(pdfTypography.body.fontFamily).toEqual(["Vazirmatn", "Noto Sans Arabic", "Noto Sans"]);
|
||||
expect(pdfTypography.heading.fontFamily).toEqual(["Vazirmatn", "Noto Sans Arabic", "Noto Sans"]);
|
||||
expect(registerSpy).toHaveBeenCalledWith(expect.objectContaining({ family: "Vazirmatn", fontWeight: 400 }));
|
||||
expect(registerSpy).toHaveBeenCalledWith(expect.objectContaining({ family: "Vazirmatn", fontWeight: 700 }));
|
||||
});
|
||||
});
|
||||
|
||||
describe("resumeContentContainsCJK", () => {
|
||||
|
||||
@@ -160,6 +160,37 @@ function getMetricCompatibleFonts(): WebFont[] {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: locale-coverage web fonts that aren't in the popularity-sorted
|
||||
* Google Fonts slice but are stored as `typography.*.fontFamily` in imported
|
||||
* resume JSON. Without a catalog entry, PDF registration silently substitutes
|
||||
* IBM Plex Serif and Persian/Arabic glyphs stack or tofu (#3098).
|
||||
*/
|
||||
function getLocaleCoverageFonts(): WebFont[] {
|
||||
const CDN = "https://fonts.gstatic.com/s/vazirmatn/v16";
|
||||
|
||||
return [
|
||||
{
|
||||
type: "web",
|
||||
category: "sans-serif",
|
||||
family: "Vazirmatn",
|
||||
weights: ["100", "200", "300", "400", "500", "600", "700", "800", "900"],
|
||||
preview: `${CDN}/Dxx78j6PP2D_kU2muijPEe1n2vVbfJRklWgzCRCT60LA7Qdazg.ttf`,
|
||||
files: {
|
||||
"100": `${CDN}/Dxx78j6PP2D_kU2muijPEe1n2vVbfJRklWgyCRCT60LA7Qdazg.ttf`,
|
||||
"200": `${CDN}/Dxx78j6PP2D_kU2muijPEe1n2vVbfJRklegzCRCT60LA7Qdazg.ttf`,
|
||||
"300": `${CDN}/Dxx78j6PP2D_kU2muijPEe1n2vVbfJRklTYzCRCT60LA7Qdazg.ttf`,
|
||||
"400": `${CDN}/Dxx78j6PP2D_kU2muijPEe1n2vVbfJRklWgzCRCT60LA7Qdazg.ttf`,
|
||||
"500": `${CDN}/Dxx78j6PP2D_kU2muijPEe1n2vVbfJRklVozCRCT60LA7Qdazg.ttf`,
|
||||
"600": `${CDN}/Dxx78j6PP2D_kU2muijPEe1n2vVbfJRklbY0CRCT60LA7Qdazg.ttf`,
|
||||
"700": `${CDN}/Dxx78j6PP2D_kU2muijPEe1n2vVbfJRklY80CRCT60LA7Qdazg.ttf`,
|
||||
"800": `${CDN}/Dxx78j6PP2D_kU2muijPEe1n2vVbfJRkleg0CRCT60LA7Qdazg.ttf`,
|
||||
"900": `${CDN}/Dxx78j6PP2D_kU2muijPEe1n2vVbfJRklcE0CRCT60LA7Qdazg.ttf`,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
async function generateFonts() {
|
||||
const response = await getGoogleFontsJSON();
|
||||
console.log(`Found ${response.items.length} fonts in total (Google Fonts).`);
|
||||
@@ -196,16 +227,27 @@ async function generateFonts() {
|
||||
const computerModernFonts = getComputerModernWebFonts();
|
||||
// Manually append metric-compatible fonts not covered by the popularity slice
|
||||
const metricCompatFonts = getMetricCompatibleFonts();
|
||||
const localeCoverageFonts = getLocaleCoverageFonts();
|
||||
// De-duplicate against the Google Fonts slice in case a manual entry
|
||||
// later enters the popularity top-N.
|
||||
const googleFontFamilies = new Set(googleFontResults.map((f) => f.family));
|
||||
const filteredMetricCompat = metricCompatFonts.filter((f) => !googleFontFamilies.has(f.family));
|
||||
const filteredLocaleCoverage = localeCoverageFonts.filter((f) => !googleFontFamilies.has(f.family));
|
||||
|
||||
const allWebFonts: WebFont[] = [...computerModernFonts, ...googleFontResults, ...filteredMetricCompat];
|
||||
const allWebFonts: WebFont[] = [
|
||||
...computerModernFonts,
|
||||
...googleFontResults,
|
||||
...filteredMetricCompat,
|
||||
...filteredLocaleCoverage,
|
||||
];
|
||||
|
||||
console.log(
|
||||
`Added ${computerModernFonts.length} Computer Modern Web Fonts and ${filteredMetricCompat.length} metric-compatible fonts. Total output: ${allWebFonts.length} web fonts.`,
|
||||
);
|
||||
const manualFontSummary = [
|
||||
`${computerModernFonts.length} Computer Modern Web Fonts`,
|
||||
`${filteredMetricCompat.length} metric-compatible fonts`,
|
||||
`and ${filteredLocaleCoverage.length} locale-coverage fonts`,
|
||||
].join(", ");
|
||||
|
||||
console.log(`Added ${manualFontSummary}. Total output: ${allWebFonts.length} web fonts.`);
|
||||
|
||||
const jsonString = argCompress ? JSON.stringify(allWebFonts) : JSON.stringify(allWebFonts, null, 2);
|
||||
await mkdir(FONTS_DIR, { recursive: true });
|
||||
|
||||
Reference in New Issue
Block a user