Repository navigation
PDF export: broken oxc-parser native binding silently disables print post-pass (includeBleed/marks no-op) #142
Description
Summary
/design:export pdf (and presumably the ⌘E dialog, same code path) silently ignores options.pdfPrint (includeBleed, marks) for kind="print" artboards. No error, no degraded flag — the output is just the raw Chromium-captured page (full bleed box, i.e. the artboard's own pixel size), regardless of what includeBleed/marks were set to.
Root cause
exporters/pdf.ts's post-pass resolves each page's print geometry via readArtboardPrintProp (canvas-edit.ts), which calls parseSync from oxc-parser. In this environment, oxc-parser's native binding fails to load when the dev-server process (a bundled Bun binary) tries to resolve it:
error: Cannot find module '/$bunfs/root/parser.darwin-arm64-3x28d8cw.node' from
'/Users/iagh/.bun/install/cache/oxc-parser@0.152.0@@@1/src-js/bindings.js'
Since readArtboardPrintProp treats parsed.errors.length > 0 (or a thrown parse failure) as "not a print artboard" and returns null, the post-pass's if (printProp && printOpts) branch is never taken for ANY page — includeBleed: false, marks: {crop, registration}, all become silent no-ops. The exported PDF comes back as plain Chromium output (full bleed box, no TrimBox/BleedBox set, no marks drawn), with no error and no degraded flag on the export result.
Repro
curl -s -X POST http://localhost:<port>/_api/export \
-H "Content-Type: application/json" \
-d '{"format":"pdf","scope":"canvas-as-separate","options":{"pdfPrint":{"includeBleed":false}}}' \
-o out.pdf
pdfinfo out.pdf | grep "Page size"
# Expected: trimmed page size (artboard size minus 2× bleed)
# Actual: unchanged — same size as the raw artboard (bleed included)Reproduced against a kind="print" artboard whose print prop is a referenced const (print={A4_PRINT as any} where const A4_PRINT = { paper: "a4", orientation: "landscape", bleedMm: 3 } as const;) — resolveValueNode in canvas-edit.ts is written to follow exactly this pattern (identifier → VariableDeclarator → unwrap as const), so the prop-resolution logic itself isn't the issue; the parse step upstream of it never runs successfully.
Impact
- Any export requesting
includeBleed/markson a print artboard silently returns the untrimmed, unmarked page instead. - No error surfaces to the caller —
ExportResult.degradedis only set by the font-preflight path, not by a parse failure here. A user has no signal that their print-ready request was ignored. - Worked around locally by exporting via the fast path (no
pdfPrintoptions → raw Chromium page, bypassesoxc-parserentirely) and post-processing the MediaBox withpypdfoutside Maude.
Suggested fix
- Fix/pin the
oxc-parsernative binding resolution for the bundled Bun dev-server binary (likely an asset-bundling issue with Bun's$bunfsvirtual FS not including the platform.node/.wasi.cjsbinary oxc-parser expects at runtime). - Defense in depth: when
readArtboardPrintProp/parseSyncfails for a page that explicitly requestedpdfPrintoptions, surface this viaExportResult.degraded(or throw) rather than silently falling through to "not a print artboard" — a parse failure and "this isn't a print artboard" are different conditions and currently collapse to the same silent no-op.
Environment
- macOS (darwin), Bun v1.3.3
oxc-parser@0.152.0- Maude dev-server running as a long-lived bundled process (
maude design serve)
Activity