Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions docs/plans/document-viewer-checkbox-bug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Bug: PDF Form Checkboxes Not Displaying Checked State

**Widget:** Document Viewer v1.2.0
**Mendix version:** 10.24.9
**Status:** Fixed

---

## Symptom

A W9 PDF generated via .NET has a checked checkbox in Section 3.a ("C corporation"). When opened directly in a browser (Chrome, Firefox native viewer), the checkbox renders correctly as checked. When displayed in the Document Viewer widget, the checkbox appears unchecked.

---

## Root Cause

The checkmark is drawn by a PDF Form XObject using the `/ZaDb` (ZapfDingbats) standard font with glyph `0x34`. PDF.js substitutes standard fonts with bundled Foxit equivalents, fetching them from `standardFontDataUrl`.

`PDFViewer.tsx` configured this as a relative URL:

```ts
const options = {
cMapUrl: "/widgets/com/mendix/shared/pdfjs/cmaps/",
standardFontDataUrl: "/widgets/com/mendix/shared/pdfjs/standard_fonts/"
};
```

The PDF.js worker is loaded from `//unpkg.com/pdfjs-dist@.../pdf.worker.min.mjs` — a cross-origin URL. A worker loaded from a different origin has no document base URL, so `fetch()` cannot resolve relative paths. The worker throws:

```
TypeError: Failed to execute 'fetch' on 'WorkerGlobalScope':
Failed to parse URL from /widgets/com/mendix/shared/pdfjs/standard_fonts/FoxitDingbats.pfb
```

Font load silently fails → ZapfDingbats not available → checkmark glyph renders as blank rectangle.

The browser's native PDF viewer is unaffected because it handles font resolution internally without a web worker.

---

## Fix

**File:** `packages/pluggableWidgets/document-viewer-web/src/components/PDFViewer.tsx`

```diff
+const origin = window.location.origin;
const options = {
- cMapUrl: "/widgets/com/mendix/shared/pdfjs/cmaps/",
- standardFontDataUrl: "/widgets/com/mendix/shared/pdfjs/standard_fonts/"
+ cMapUrl: `${origin}/widgets/com/mendix/shared/pdfjs/cmaps/`,
+ standardFontDataUrl: `${origin}/widgets/com/mendix/shared/pdfjs/standard_fonts/`
};
```

`window.location.origin` is the Mendix app origin (e.g. `https://myapp.mendixcloud.com`). The worker can fetch absolute URLs regardless of where it was loaded from.

---

## Verification

1. Load customer W9 PDF — Section 3.a "C corporation" checkbox shows as checked ✓
2. Build: `pnpm --filter @mendix/document-viewer-web run build`
3. Network tab shows absolute URL `http://<host>/widgets/.../FoxitDingbats.pfb` with 200 response (or request goes to worker — confirmed via console, no more `loadFont` warning)
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-11
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
## Context

Document Viewer v1.2.0 uses `react-pdf` to render PDFs via PDF.js. The `options` object passed to the `<Document>` component includes `cMapUrl` and `standardFontDataUrl` as relative paths. PDF.js passes these to the worker thread for font/cmap fetching. When the worker is loaded from a cross-origin URL (the default is unpkg CDN: `//unpkg.com/pdfjs-dist@.../pdf.worker.min.mjs`), the worker's `fetch()` cannot resolve relative URLs — it has no document origin to resolve against — causing a `TypeError: Failed to parse URL` and silent font load failure.

The customer's W9 PDF draws a checkmark using the ZapfDingbats standard font (glyph `0x34`) via a Form XObject. Without the font, PDF.js renders a blank rectangle.

## Goals / Non-Goals

**Goals:**

- Fix standard font and cmap fetching when the PDF.js worker is cross-origin
- No new XML properties, no API surface changes, no dependency updates

**Non-Goals:**

- Changing how the worker URL is configured
- Supporting self-hosted worker deployments (already supported via `pdfjsWorkerUrl` prop)

## Decisions

### Use `window.location.origin` to make resource URLs absolute

Prepend `window.location.origin` to both `cMapUrl` and `standardFontDataUrl` at module evaluation time.

**Rationale:** The worker needs absolute URLs. `window.location.origin` is always the Mendix app origin — correct for all deployment environments. Evaluated at module load (not per-render), so no React re-render cost.

**Alternative considered:** Move `options` inside the component and use `useMemo`. Rejected — no reactive dependencies, module-scope evaluation is simpler and equivalent.

**Alternative considered:** Set `useWorkerFetch: false` to force main-thread font loading. Rejected — works around the symptom, not the cause; disabling worker fetch has broader performance implications.

## Risks / Trade-offs

- `window.location.origin` is not available in SSR/test environments. Tests currently stub this or don't exercise PDF rendering — no impact. If server-side rendering is ever added, this will need to be guarded.
- If the Mendix app is served from a subpath (e.g. `/app/`), `origin` alone is correct — fonts live under `/widgets/`, not the subpath.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## Why

PDFs containing glyphs from ZapfDingbats (a PDF standard font) — such as checkmarks generated by .NET PDF libraries — render blank in Document Viewer. The checkmark is drawn via a Form XObject using the ZapfDingbats font, which PDF.js must fetch from `standardFontDataUrl`. The URL was relative, causing the PDF.js worker (loaded from unpkg CDN, a different origin) to fail parsing it as an absolute URL.

## What Changes

- `standardFontDataUrl` and `cMapUrl` in `PDFViewer.tsx` are prefixed with `window.location.origin` to produce absolute URLs, allowing the worker to fetch font resources regardless of where it was loaded from.

## Capabilities

### New Capabilities

- `pdf-form-rendering`: Correct visual rendering of standard-font glyphs (ZapfDingbats checkmarks, Symbol characters) in PDFs rendered by the Document Viewer widget

### Modified Capabilities

<!-- none -->

## Impact

- **File**: `packages/pluggableWidgets/document-viewer-web/src/components/PDFViewer.tsx`
- **Behavior**: ZapfDingbats and other standard font glyphs now render correctly when PDF.js worker is loaded from a cross-origin URL (e.g. unpkg CDN)
- **No API or XML changes**
- **No dependency version changes**
- **Affected widget**: `@mendix/document-viewer-web` v1.2.0+
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
## ADDED Requirements

### Requirement: Standard font glyphs render correctly in PDFs

The Document Viewer SHALL correctly render PDF glyphs that depend on PDF standard fonts (ZapfDingbats, Symbol, etc.) by fetching font resources using absolute URLs resolvable by the PDF.js worker.

#### Scenario: ZapfDingbats checkmark renders in cross-origin worker context

- **WHEN** a PDF contains a glyph drawn from the ZapfDingbats standard font (e.g. a checkmark drawn via a Form XObject)
- **AND** the PDF.js worker is loaded from a cross-origin URL (e.g. unpkg CDN)
- **THEN** the glyph SHALL render visibly on the canvas

#### Scenario: Font fetch uses absolute URL

- **WHEN** PDF.js requests a standard font file from the worker thread
- **THEN** the request URL SHALL be an absolute URL including the application origin (e.g. `https://example.com/widgets/.../FoxitDingbats.pfb`)

#### Scenario: PDFs without standard fonts are unaffected

- **WHEN** a PDF contains no glyphs requiring standard font substitution
- **THEN** rendering SHALL be identical to previous behavior
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
## 1. Implementation

- [x] 1.1 Prepend `window.location.origin` to `cMapUrl` and `standardFontDataUrl` in `packages/pluggableWidgets/document-viewer-web/src/components/PDFViewer.tsx`

## 2. Verification

- [x] 2.1 Run unit tests: `cd packages/pluggableWidgets/document-viewer-web && pnpm run test`
- [x] 2.2 Build widget: `pnpm --filter @mendix/document-viewer-web run build`
- [x] 2.3 Verify customer W9 PDF shows Section 3.a checkbox as checked in Document Viewer
- [x] 2.4 Verify a PDF without AcroForms renders correctly (no regression)
4 changes: 4 additions & 0 deletions packages/pluggableWidgets/document-viewer-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

- We changed the internal structure of the widget

### Fixed

- We fixed an issue where PDF standard fonts (e.g. ZapfDingbats) failed to load when the PDF.js worker was served from a cross-origin URL, causing glyphs such as checkmarks to render as blank rectangles.

## [1.2.0] - 2025-10-29

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
import "react-pdf/dist/Page/TextLayer.css";
import { downloadFile } from "../utils/helpers";
import { useZoomScale } from "../utils/useZoomScale";
import BaseViewer from "./BaseViewer";

Check warning on line 7 in packages/pluggableWidgets/document-viewer-web/src/components/PDFViewer.tsx

View workflow job for this annotation

GitHub Actions / Run code quality check

`./BaseViewer` import should occur before import of `../utils/helpers`
import { DocRendererElement, DocumentRendererProps, DocumentStatus } from "./documentRenderer";

Check warning on line 8 in packages/pluggableWidgets/document-viewer-web/src/components/PDFViewer.tsx

View workflow job for this annotation

GitHub Actions / Run code quality check

`./documentRenderer` import should occur before import of `../utils/helpers`
import { If } from "@mendix/widget-plugin-component-kit/If";

Check warning on line 9 in packages/pluggableWidgets/document-viewer-web/src/components/PDFViewer.tsx

View workflow job for this annotation

GitHub Actions / Run code quality check

`@mendix/widget-plugin-component-kit/If` import should occur before import of `../utils/helpers`

const origin = window.location.origin;
const options = {
cMapUrl: "/widgets/com/mendix/shared/pdfjs/cmaps/",
standardFontDataUrl: "/widgets/com/mendix/shared/pdfjs/standard_fonts"
cMapUrl: `${origin}/widgets/com/mendix/shared/pdfjs/cmaps/`,
standardFontDataUrl: `${origin}/widgets/com/mendix/shared/pdfjs/standard_fonts/`
};

const PDFViewer: DocRendererElement = (props: DocumentRendererProps) => {
Expand Down
Loading