mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-06-22 04:11:55 +10:00
fix(fonts): restore legacy local font names via metric-compatible ali… (#3057)
* fix(fonts): restore legacy local font names via metric-compatible aliases Closes #2989. In v5.0.x the Puppeteer renderer resolved fonts like 'Times New Roman' or 'Arial' through the browser's font stack. The v5.1 migration to @react-pdf/renderer requires every font to be Font.register()-ed; the legacy local-font names were not carried over, so resumes upgraded from v5.0.x had their typography silently replaced with IBM Plex Serif, changing line breaks, page counts and overall layout. This adds a render-time alias layer mapping the old names to metric-compatible web fonts already shipped in the webfont list: Times New Roman → Tinos Cambria → Tinos Arial → Arimo Garamond → EB Garamond Calibri → Source Sans 3 - packages/fonts: - new `legacyFontAliases` map and `resolveLegacyFontAlias` helper. - `getFont` falls back to the alias map when the direct lookup misses, so any caller that asked 'is this a known family?' now answers truthfully for the legacy names. - `getFontDisplayName` is intentionally unchanged: the typography sidebar keeps showing the user's original choice ('Times New Roman'), while the renderer transparently swaps in the alias target. - packages/pdf/use-register-fonts: - `resolvePdfFontFamily` returns the alias target when one applies, so `Font.register` runs against the right web font and templates receive a family name they can actually render. Backwards compatible: families that were never aliased (Roboto, IBM Plex Serif, the standard PDF fonts, ...) take exactly the same code path as before. The CJK glyph fallback added in #2986 / PR #3013 continues to apply on top of the resolved primary family. * fix(fonts): use Carlito (not Source Sans 3) as Calibri alias Per maintainer review feedback: Carlito is metric-compatible with Calibri, while Source Sans 3 only matches visually. Switching gives upgraded resumes the same line widths, line breaks and page counts they had under v5.0.x. - packages/fonts/webfontlist.json: add Carlito (Google Fonts, weights 400/700 + italics) so it's a registerable target. - packages/scripts/fonts/generate.ts: add a getMetricCompatibleFonts helper and merge it into the output, mirroring how Computer Modern fonts are appended. This way regenerating the list (`pnpm generate`) re-emits Carlito automatically and dedupes if it ever enters the Google Fonts popularity slice. - packages/fonts/src/index.ts: alias `Calibri → Carlito`. - packages/fonts/src/index.test.ts: update alias test cases.
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
getWebFont,
|
||||
getWebFontSource,
|
||||
isStandardPdfFontFamily,
|
||||
resolveLegacyFontAlias,
|
||||
standardFontList,
|
||||
webFontList,
|
||||
webFontMap,
|
||||
@@ -257,3 +258,45 @@ describe("buildResumeFontFamily", () => {
|
||||
expect(result).toContain("Bob\\'s Font");
|
||||
});
|
||||
});
|
||||
|
||||
describe("legacy font compatibility (#2989)", () => {
|
||||
it.each([
|
||||
["Times New Roman", "Tinos"],
|
||||
["Arial", "Arimo"],
|
||||
["Garamond", "EB Garamond"],
|
||||
["Calibri", "Carlito"],
|
||||
["Cambria", "Tinos"],
|
||||
])("aliases %s → %s", (legacy, target) => {
|
||||
expect(resolveLegacyFontAlias(legacy)).toBe(target);
|
||||
});
|
||||
|
||||
it("returns null for non-aliased families", () => {
|
||||
expect(resolveLegacyFontAlias("Roboto")).toBeNull();
|
||||
expect(resolveLegacyFontAlias("IBM Plex Serif")).toBeNull();
|
||||
expect(resolveLegacyFontAlias("UnknownFont")).toBeNull();
|
||||
});
|
||||
|
||||
it("every alias target is actually registered as a known font", () => {
|
||||
const aliasTargets = ["Tinos", "Arimo", "EB Garamond", "Carlito"];
|
||||
for (const target of aliasTargets) {
|
||||
expect(getFont(target), `alias target ${target} must be a known font`).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
it("getFont resolves a legacy family to its alias target", () => {
|
||||
const tnr = getFont("Times New Roman");
|
||||
expect(tnr).toBeDefined();
|
||||
expect(tnr?.family).toBe("Tinos");
|
||||
});
|
||||
|
||||
it("getFont still returns the direct font when both legacy and direct lookup would succeed", () => {
|
||||
// Sanity check: for non-aliased families the direct path is used.
|
||||
expect(getFont("Roboto")?.family).toBe("Roboto");
|
||||
});
|
||||
|
||||
it("getFontDisplayName preserves the legacy family name (UI is not rewritten)", () => {
|
||||
// Users who picked "Times New Roman" should keep seeing that label
|
||||
// in the typography sidebar — the alias is a render-time concern only.
|
||||
expect(getFontDisplayName("Times New Roman")).toBe("Times New Roman");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -123,8 +123,28 @@ for (const font of fontList) {
|
||||
fontMap.set(font.family, font);
|
||||
}
|
||||
|
||||
// Compatibility aliases for fonts that v5.0.x resolved via the browser
|
||||
// (Arial, Times New Roman, ...) but that aren't registered with
|
||||
// @react-pdf/renderer in v5.1+. Targets are metric-compatible web fonts
|
||||
// already shipped in webfontlist (#2989).
|
||||
const legacyFontAliases: Record<string, string> = {
|
||||
Arial: "Arimo",
|
||||
Cambria: "Tinos",
|
||||
Calibri: "Carlito",
|
||||
Garamond: "EB Garamond",
|
||||
"Times New Roman": "Tinos",
|
||||
};
|
||||
|
||||
export function resolveLegacyFontAlias(family: string): string | null {
|
||||
return legacyFontAliases[family] ?? null;
|
||||
}
|
||||
|
||||
export function getFont(family: string) {
|
||||
return fontMap.get(family);
|
||||
const direct = fontMap.get(family);
|
||||
if (direct) return direct;
|
||||
|
||||
const alias = legacyFontAliases[family];
|
||||
return alias ? fontMap.get(alias) : undefined;
|
||||
}
|
||||
|
||||
function getFontCategory(family: string): FontCategory | null {
|
||||
|
||||
@@ -7509,5 +7509,18 @@
|
||||
"weights": ["400"],
|
||||
"preview": "https://fonts.gstatic.com/s/secularone/v14/8QINdiTajsj_87rMuMdKyqDkOO0.ttf",
|
||||
"files": { "400": "https://fonts.gstatic.com/s/secularone/v14/8QINdiTajsj_87rMuMdKypDlMul7LJpK.ttf" }
|
||||
},
|
||||
{
|
||||
"type": "web",
|
||||
"category": "sans-serif",
|
||||
"family": "Carlito",
|
||||
"weights": ["400", "700"],
|
||||
"preview": "https://fonts.gstatic.com/s/carlito/v4/3Jn9SDPw3m-pk039DDeBSQ.ttf",
|
||||
"files": {
|
||||
"400": "https://fonts.gstatic.com/s/carlito/v4/3Jn9SDPw3m-pk039DDeBSQ.ttf",
|
||||
"700": "https://fonts.gstatic.com/s/carlito/v4/3Jn4SDPw3m-pk039BIykWXolVg.ttf",
|
||||
"400italic": "https://fonts.gstatic.com/s/carlito/v4/3Jn_SDPw3m-pk039DDKxTl0F.ttf",
|
||||
"700italic": "https://fonts.gstatic.com/s/carlito/v4/3Jn6SDPw3m-pk039DDK59XgVUcBD.ttf"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
getPdfCjkFallbackFontFamily,
|
||||
getWebFontSource,
|
||||
isStandardPdfFontFamily,
|
||||
resolveLegacyFontAlias,
|
||||
sortFontWeights,
|
||||
} from "@reactive-resume/fonts";
|
||||
import { isCJKLocale } from "@reactive-resume/utils/locale";
|
||||
@@ -51,8 +52,14 @@ const toFontWeight = (weight: number): FontWeight => {
|
||||
return "900";
|
||||
};
|
||||
|
||||
// Resolves the user-stored family to the one we hand to Font.register:
|
||||
// direct match → legacy alias (#2989) → IBM Plex Serif fallback.
|
||||
const resolvePdfFontFamily = (family: string) => {
|
||||
return getFont(family) ? family : fallbackFontFamily;
|
||||
if (getFont(family)) {
|
||||
const alias = resolveLegacyFontAlias(family);
|
||||
return alias ?? family;
|
||||
}
|
||||
return fallbackFontFamily;
|
||||
};
|
||||
|
||||
const resolvePdfTypography = (typography: Typography): Typography => {
|
||||
|
||||
@@ -134,6 +134,32 @@ function getComputerModernWebFonts(): WebFont[] {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper: Additional metric-compatible web fonts that aren't in the
|
||||
* popularity-sorted Google Fonts slice but are needed as render targets
|
||||
* for legacy local-font aliases (#2989). Carlito is the open-source
|
||||
* metric-compatible equivalent of Calibri.
|
||||
*/
|
||||
function getMetricCompatibleFonts(): WebFont[] {
|
||||
const CDN = "https://fonts.gstatic.com/s/carlito/v4";
|
||||
|
||||
return [
|
||||
{
|
||||
type: "web",
|
||||
category: "sans-serif",
|
||||
family: "Carlito",
|
||||
weights: ["400", "700"],
|
||||
preview: `${CDN}/3Jn9SDPw3m-pk039DDeBSQ.ttf`,
|
||||
files: {
|
||||
"400": `${CDN}/3Jn9SDPw3m-pk039DDeBSQ.ttf`,
|
||||
"700": `${CDN}/3Jn4SDPw3m-pk039BIykWXolVg.ttf`,
|
||||
"400italic": `${CDN}/3Jn_SDPw3m-pk039DDKxTl0F.ttf`,
|
||||
"700italic": `${CDN}/3Jn6SDPw3m-pk039DDK59XgVUcBD.ttf`,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export async function generateFonts() {
|
||||
const response = await getGoogleFontsJSON();
|
||||
console.log(`Found ${response.items.length} fonts in total (Google Fonts).`);
|
||||
@@ -168,10 +194,17 @@ export async function generateFonts() {
|
||||
|
||||
// Manually append Computer Modern web fonts
|
||||
const computerModernFonts = getComputerModernWebFonts();
|
||||
const allWebFonts: WebFont[] = [...computerModernFonts, ...googleFontResults];
|
||||
// Manually append metric-compatible fonts not covered by the popularity slice
|
||||
const metricCompatFonts = getMetricCompatibleFonts();
|
||||
// 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 allWebFonts: WebFont[] = [...computerModernFonts, ...googleFontResults, ...filteredMetricCompat];
|
||||
|
||||
console.log(
|
||||
`Added ${computerModernFonts.length} Computer Modern Web Fonts. Total output: ${allWebFonts.length} web fonts.`,
|
||||
`Added ${computerModernFonts.length} Computer Modern Web Fonts and ${filteredMetricCompat.length} metric-compatible fonts. Total output: ${allWebFonts.length} web fonts.`,
|
||||
);
|
||||
|
||||
const jsonString = argCompress ? JSON.stringify(allWebFonts) : JSON.stringify(allWebFonts, null, 2);
|
||||
|
||||
Reference in New Issue
Block a user