You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Due for payment 2026-07-07] [PDF Viewer] Black text disappears from certain PDF receipts after they finish rendering (web) #94484
Certain PDF receipts viewed in New Expensify (web) render incorrectly: while the
PDF is loading, the text is briefly visible, but once it has fully loaded the
black text on the receipt disappears, leaving a blank/near-blank page. The same
PDFs display correctly in Expensify Classic.
This only affects some PDFs — it appears to be font-specific. PDFs using a
different embedded font render fine. The blank/blink behaviour does not reproduce
in macOS Preview, so it looks specific to how we render the affected fonts.
Observed workarounds / clues reported
Hovering over the receipt thumbnail shows the content correctly.
Opening the receipt shows the text briefly during load (while the green progress
bar is shown), then it vanishes when rendering completes.
Navigating to the next receipt and back makes the text reappear.
Expected
PDF text renders and stays visible after the document finishes loading, matching
Classic and other PDF viewers.
Actual
Text flashes in during load, then disappears once the PDF fully renders. Re-rendering
(navigate away + back) restores it.
Platforms
Web / Desktop (New Expensify)
Other platforms not yet confirmed.
Technical notes
New Expensify renders PDFs on web via react-pdf + pdfjs-dist (^5.5.207).
The symptoms (text visible mid-load then cleared on completion, font-specific,
fixed by a re-render) point to a text-layer / embedded-font rendering issue in
the pdf.js pipeline rather than a data problem. A good starting point would be src/components/PDFView / src/components/PDFThumbnail and the pdf.js text-layer
cmaps/font handling.
Repro file
A sample PDF that reproduces the issue is available but cannot be shared
publicly (client document). It can be shared privately with the engineer who
picks this up.
Warning: Error during font loading: Unable to load CMap data at: cmaps/UniGB-UCS2-H.bcmap
and part of the text in the PDF did not render. The stack trace originated in PDFThumbnail (src/components/PDFThumbnail/index.tsx:46).
Root Cause (confirmed in source)
UniGB-UCS2-H is the Adobe-GB1 (Simplified Chinese) CMap. When it fails to load, that CJK text can't be mapped to glyphs and doesn't render.
The CMap files are copied into the build at the server root (/cmaps/) via webpack CopyPlugin (config/webpack/webpack.common.ts:218), and UniGB-UCS2-H.bcmap exists in pdfjs-dist.
The bug is the relativecMapUrl: 'cmaps/'. PDF.js builds the URL as `${cMapUrl}${filename}` and fetch()s it against document.baseURI (node_modules/pdfjs-dist/legacy/build/pdf.mjs:7566).
With publicPath: '/' and path-based browser routing, on a deep route like /r/123/... the relative URL resolves to /r/123/cmaps/UniGB-UCS2-H.bcmap → 404.
PDFView → react-fast-pdf's PDFPreviewer has the same relative-cmaps/ bug in its hardcoded DEFAULT_DOCUMENT_OPTIONS (node_modules/react-fast-pdf/dist/constants.js:17), and exposes no override prop. So full-screen receipt view will still drop the CJK text.
Decision: fix this via an upstream PR to react-fast-pdf.
Guidance for the Upstream PR
Don't just swap the default to /cmaps/.
Downside: /cmaps/ breaks for consumers deploying under a sub-path (non-root publicPath), since it resolves to /cmaps/ instead of /subpath/cmaps/. It's correct for Expensify only because our publicPath is /. So a straight default swap is a potential breaking change for other consumers.
Recommended approach:
Add an optional prop (a cMapUrl, or broader options passthrough merged into DEFAULT_DOCUMENT_OPTIONS).
[Contributor] The offending PR and associated issue have been commented on, pointing out the bug it caused and why, so the author and reviewers can learn from the mistake.
[Contributor] If the regression was CRITICAL (e.g. interrupts a core flow) A discussion in #expensify-open-source has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner.
Link to discussion: N/A
[Contributor] If it was decided to create a regression test for the bug, please propose the regression test steps using the template below to ensure the same bug will not reach production again.
[BugZero Assignee] Create a GH issue for creating/updating the regression test once above steps have been agreed upon.
Activity
mountiny commentedon Jun 24, 2026
PDF to test with is here https://expensify.slack.com/archives/C05LX9D6E07/p1782316539163899?thread_ts=1782209979.435539&cid=C05LX9D6E07
DO NOT SHARE IT IN GITHUB OR IN VIDEOS WITH IT - post the videos in Slack
VickyStash commentedon Jun 25, 2026
Problem
A specific PDF receipt logged:
and part of the text in the PDF did not render. The stack trace originated in
PDFThumbnail(src/components/PDFThumbnail/index.tsx:46).Root Cause (confirmed in source)
UniGB-UCS2-His the Adobe-GB1 (Simplified Chinese) CMap. When it fails to load, that CJK text can't be mapped to glyphs and doesn't render./cmaps/) via webpackCopyPlugin(config/webpack/webpack.common.ts:218), andUniGB-UCS2-H.bcmapexists inpdfjs-dist.cMapUrl: 'cmaps/'. PDF.js builds the URL as`${cMapUrl}${filename}`andfetch()s it againstdocument.baseURI(node_modules/pdfjs-dist/legacy/build/pdf.mjs:7566).publicPath: '/'and path-based browser routing, on a deep route like/r/123/...the relative URL resolves to/r/123/cmaps/UniGB-UCS2-H.bcmap→ 404.Fix Applied
src/components/PDFThumbnail/index.tsx: changedcMapUrl: 'cmaps/'→'/cmaps/'(root-relative).Still Outstanding — The Full Viewer
PDFView→react-fast-pdf'sPDFPreviewerhas the same relative-cmaps/bug in its hardcodedDEFAULT_DOCUMENT_OPTIONS(node_modules/react-fast-pdf/dist/constants.js:17), and exposes no override prop. So full-screen receipt view will still drop the CJK text.Guidance for the Upstream PR
Don't just swap the default to
/cmaps/./cmaps/breaks for consumers deploying under a sub-path (non-rootpublicPath), since it resolves to/cmaps/instead of/subpath/cmaps/. It's correct for Expensify only because ourpublicPathis/. So a straight default swap is a potential breaking change for other consumers.cMapUrl, or broaderoptionspassthrough merged intoDEFAULT_DOCUMENT_OPTIONS).cMapUrl="/cmaps/"from ourPDFView.tsx.VickyStash commentedon Jun 25, 2026
react-fast-pdf PR: Expensify/react-fast-pdf#54
Draft app PR: #94568
7 remaining items
melvin-bot commentedon Jul 5, 2026
Triggered auto assignment to @mallenexpensify (
Awaiting Payment)FitseTLT commentedon Jul 6, 2026
BugZero Checklist:
[Contributor] The offending PR and associated issue have been commented on, pointing out the bug it caused and why, so the author and reviewers can learn from the mistake.
Link to comment:https://github.com/Expensify/App/pull/35255/changes#r3529573983
[Contributor] If the regression was CRITICAL (e.g. interrupts a core flow) A discussion in #expensify-open-source has been started about whether any other steps should be taken (e.g. updating the PR review checklist) in order to catch this type of bug sooner.
Link to discussion: N/A
[Contributor] If it was decided to create a regression test for the bug, please propose the regression test steps using the template below to ensure the same bug will not reach production again.
[BugZero Assignee] Create a GH issue for creating/updating the regression test once above steps have been agreed upon.
Link to issue: https://github.com/Expensify/Expensify/issues/657657
Regression Test Proposal
Test:
Do we agree 👍 or 👎
Zapier Logs
Run ID: 00040eee-7731-a441-2bbf-db5d539b75a4melvin-bot commentedon Jul 6, 2026
Payment Summary
Upwork Job
BugZero Checklist (@mallenexpensify)
mallenexpensify commentedon Jul 8, 2026
Payment Summary
Contributor+: @FitseTLT due $250 via NewDot
$125 cuz no tests
FitseTLT commentedon Jul 9, 2026
@mallenexpensify The test is confidential as it involves customer receipt that's why 😄 we communicated in slack.
mallenexpensify commentedon Jul 9, 2026
@FitseTLT that's a rare one! Updated price to $250
flaviadefaria commentedon Jul 31, 2026
Approved: @FitseTLT due $250 via NewDot