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:
JamesGoslings
2026-05-14 17:36:15 +08:00
committed by GitHub
parent affa1d6646
commit 22c60c64b6
5 changed files with 120 additions and 4 deletions
+35 -2
View File
@@ -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);