Skip to content

fix: For garbled non-Latin characters in certificate PDFs - #3685

Open
daniellefrappier18 wants to merge 1 commit into
mainfrom
daniellef/hq-12554-certificate-pdf-font-fallback
Open

fix: For garbled non-Latin characters in certificate PDFs#3685
daniellefrappier18 wants to merge 1 commit into
mainfrom
daniellef/hq-12554-certificate-pdf-font-fallback

Conversation

@daniellefrappier18

Copy link
Copy Markdown
Contributor

What are the relevant tickets?

Fixes https://github.com/mitodl/hq/issues/12554
Fixes https://github.com/mitodl/hq/issues/12475

Description (What does it do?)

Learners with non-Latin characters in their name see garbled/blank text when they use the certificate Print / download PDF

The certificate PDF route renders with @react-pdf/renderer, which embeds only the fonts we register — the Latin-only Neue Haas Grotesk brand font — and, unlike a browser, does no automatic system-font fallback. Any character outside that font's glyph set (all CJK, plus some extended-Latin letters) has no glyph to render, so it comes out garbled.

This is why the bug is print-only: the live certificate page is HTML, so the browser silently substitutes a system font for those characters. The PDF has nothing to fall back to.

  • Register Noto Sans (latin-ext), Noto Sans SC (Simplified Chinese), and Noto Sans TC (Traditional Chinese) as fallback fonts in the certificate PDF route (400 + 700 weights, version-pinned CDN — same remote-font approach already used for the brand font).
  • Add these to the fontFamily stack for every text element via two shared stacks (FONT_STACK_400 FONT_STACK_700). @react-pdf/renderer resolves fonts per-character, so branded text stays in Neue Haas Grotesk and only the unsupported glyphs fall through to Noto.

Screenshots (if appropriate):

  • Desktop screenshots
  • Mobile width screenshots

BEFORE
Screenshot 2026-07-27 at 1 43 53 PM

AFTER
Screenshot 2026-07-27 at 1 39 02 PM

How can this be tested?

  1. Give a local user a non-Latin name. Easiest via Django admin on your local MITx Online (.../admin/users/user/<id>/change/) — set Name to something like 志云 黄 / YAVUZHAN ÇAĞRI and save.
  2. Open that user's certificate PDF: .../certificate/<course|program>//pdf
  • Expected: the name renders as real glyphs (not boxes/garbage), matching the live HTML page (drop /pdf). Confirm Cmd+P / download looks the same.

Additional Context

The PDF route fetches the Noto fonts (~2.5 MB CJK) from the CDN on the first render after a cold start, then caches in-process — same remote-dependency model as the existing brand font.

@daniellefrappier18
daniellefrappier18 marked this pull request as ready for review July 27, 2026 18:08
Copilot AI review requested due to automatic review settings July 27, 2026 18:08
@github-actions

Copy link
Copy Markdown

OpenAPI Changes

No changes detected

View full changelog

Unexpected changes? Ensure your branch is up-to-date with main (consider rebasing).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the certificate PDF generation route (Next.js App Router route.tsx using @react-pdf/renderer) to prevent garbled output for learner names containing non‑Latin characters by registering additional Noto Sans font files and attempting to apply them as fallbacks in text styling.

Changes:

  • Registers Noto Sans (latin-ext), Noto Sans SC, and Noto Sans TC fonts (400/700) from a version-pinned CDN.
  • Introduces shared font “stacks” (FONT_STACK_400, FONT_STACK_700) and replaces prior single-font fontFamily assignments to use those stacks across the PDF layout.

Comment on lines +99 to +110
const FONT_STACK_400 = [
"Neue Haas Grotesk Text 400",
"Noto Sans 400",
"Noto Sans SC 400",
"Noto Sans TC 400",
]
const FONT_STACK_700 = [
"Neue Haas Grotesk Text 700",
"Noto Sans 700",
"Noto Sans SC 700",
"Noto Sans TC 700",
]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

react-pdf types fontFamily as string | string[] and its layout engine turns the array into a per-character fallback stack (loading every family), so it typechecks and works at runtime

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants