Introduce createRtlStyleHelpers and a single rtl flag on RenderProvider,
migrate every template page to mirrored layout styles, and rename
alignRight to alignEnd. Fix plain rich text rendering via PdfText
paragraph renderers and map legacy Times New Roman to Times-Roman.
Co-authored-by: Cursor <cursoragent@cursor.com>
The CJK fallback (Noto Sans SC / Noto Serif SC) was only registered at
weight 400. When react-pdf rendered CJK characters with font-weight 700
(e.g. <strong> from a rich-text section, or templates' bold style), it
walked the font-family stack [primary, cjkFallback], failed on the
primary (no CJK glyphs), then fell back to the only registered fallback
variant (400) — and react-pdf does not synthesize bold. The bold style
was silently dropped for CJK runs in both the live preview and the
exported PDF, while still working for Latin runs.
Register the CJK fallback at the same weight range as the primary font
(lowest + highest, both styles). When body and heading share the same
fallback (the common case where both are sans or both are serif), merge
their weight ranges so each weight is registered exactly once.
webfontlist.json already ships all weights for the default CJK
fallbacks, so no font-list changes are required.
Closes#3079
Co-authored-by: Amruth Pillai <im.amruth@gmail.com>
* 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.
All Heading elements apply overflow:hidden via safeTextStyle in primitives.tsx.
At 1.5× heading font size with lineHeight 1.2, the line box is too tight to
fully render descenders (g, p, y, etc.) in the resume header name field,
causing them to appear visually cut off.
Raising headerNameLineHeight from 1.2 to 1.3 adds enough vertical room for
descenders across all 13 templates that share this constant.
Fixes#3042
- Bump @tanstack/react-form version to 1.32.0 in package.json.
- Refactor rich-input component to simplify highlight configuration.
- Improve rich text HTML normalization to handle <mark> elements and apply styles correctly in PDF output.
- Update global CSS for WYSIWYG to adjust paragraph and list margins.
Closes#2986.
Since v5.1.0 the renderer was migrated from Puppeteer to
@react-pdf/renderer. The new pipeline only registers the user-selected
typography family (e.g. Roboto, IBM Plex Serif), which contains no CJK
glyphs, so any Chinese / Japanese / Korean characters in the resume
fall back to .notdef and render as garbled boxes in both the in-app
preview and the exported PDF.
@react-pdf/renderer's textkit layer already supports per-codepoint
font substitution when a Text node is styled with `fontFamily` as a
string array — but only if every family in the stack has been
registered via Font.register. This change wires that up:
- packages/fonts: new `getPdfCjkFallbackFontFamily(family)` returns
Noto Sans SC / Noto Serif SC depending on whether the primary font
is sans-serif or serif, and `null` when no fallback is needed
(standard PDF font, or primary already is the fallback). Source Han
Sans/Serif SC covers all CJK-Unified ideographs, so a single font
transparently handles Simplified/Traditional Chinese, Japanese
kanji and Korean hanja.
- packages/pdf/hooks/use-register-fonts: after registering the
primary body/heading fonts as before, additionally register the
resolved CJK fallback (regular weight only — substitution is
per-codepoint, not per-weight, so one face is enough). The
function's return type is widened to a new `PdfTypography` whose
`body.fontFamily` and `heading.fontFamily` become
`[primary, cjkFallback]` two-element stacks.
- packages/pdf/document: cast the widened typography back through the
schema-typed `ResumeData` so the wider runtime value reaches
templates without changing the public `Typography` schema. All 15
templates already consume `metadata.typography.body.fontFamily`
directly, and `StyleSheet.fontFamily` accepts both string and
string[], so no template edits are required.
Latin-only resumes are unaffected:
- `getPdfCjkFallbackFontFamily` returns `null` for standard PDF fonts
and existing CJK selections, so the extra Font.register call is
skipped.
- When no fallback applies, `registerFonts` returns the original
typography reference unchanged (zero allocation).
- Even when the fallback is registered, textkit only consults it for
codepoints the primary font cannot render, so Latin glyphs still
come from the user-selected font with identical metrics.