Skip to content

Word → PDF: true 1:1 render + Color/B&W option - #2

Merged
AxialForge merged 2 commits into
mainfrom
claude/jdot-pdf-converter-word-t22l51
Jul 24, 2026
Merged

Word → PDF: true 1:1 render + Color/B&W option#2
AxialForge merged 2 commits into
mainfrom
claude/jdot-pdf-converter-word-t22l51

Conversation

@AxialForge

Copy link
Copy Markdown
Owner

The problem

A user reported that converting a Word document to PDF (a maintenance shift-log form) produced output that "does not look right." The docx → pdf path ran the file through mammoth, which is a semantic converter — it deliberately discards table borders, column widths, cell fills, fonts, and rotated text. For a form-style document that collapses the entire layout, so the PDF looked nothing like the original.

Before → After (the actual reported document, rendered through the real app code):

Before (mammoth) After (this PR)
Borderless, colorless, section labels flattened into a narrow column, form structure lost Colored header cells, shaded/merged cells, rotated section labels, red maintenance text, correct 2-page pagination — a genuine 1:1 render

The fix

  • New high-fidelity Word → PDF path. renderDocxPdf() in pdfrender.js renders the real OOXML with docx-preview (pure JS) inside an offscreen Chromium window, then printToPDF. Fully offline, no LibreOffice required. Page geometry — and therefore pagination — is read from the document's own w:pgSz (parseDocxPageSize), so it matches Word. If docx-preview can't parse a given file, it falls back to the mammoth intermediate so the conversion never hard-fails.
  • New "PDF colour" option: Color / Black & White. B&W applies filter:grayscale(100%) at print time and also works for the md/html/txt → pdf paths.

Implementation notes

  • Two render pools now. docx-preview needs JavaScript enabled, so pdfrender.js runs htmlPool (javascript:false, for untrusted converted HTML) and docxPool (javascript:true). Both share the same private session that cancels every non-local request, so the offline guarantee is unchanged (test/electron-offline.js still passes).
  • Windows are pooled and reused, not created/destroyed per file. On Electron 43, destroying a window and immediately creating another in the same session deadlocks the second one — the first docx converts and the next hangs forever. Reuse + park on about:blank before release, the same discipline the HTML pool already follows. Reproduced the hang and confirmed the fix.
  • docx-preview + jszip are pure JS and stay in the asar; their browser builds are read with fs and inlined into the render page.
  • The greyscale option rasterises the page (any CSS filter does), so a B&W PDF is larger and its text isn't selectable — expected for B&W printing, documented in CLAUDE.md.

Testing

  • New test/pdfrender.test.js — unit-tests parseDocxPageSize (Letter/A4/landscape/malformed) under plain node.
  • Extended test/electron-pdf.js — a docx → pdf case that builds a fixture with html-to-docx, converts it Color and B&W, and asserts both are valid PDFs and that greyscale changed the output.
  • All three Electron suites pass locally (pdf 6/6, offline 5/5, ops 9/9). Plain-node suite: no new failures (the 5 that fail here are pre-existing and environmental — a container with broken LibreOffice / no PowerShell).
  • Verified end-to-end against the real reported document (2 pages, matching Word) and a concurrent 6-file batch (pool caps at POOL_SIZE, no hang).

Version

Bumped to 1.2.0; CHANGELOG.md, CLAUDE.md, and FORMATS.md updated.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GoZVDGfDjZwmGi3HNWda9B


Generated by Claude Code

claude added 2 commits July 24, 2026 13:17
The Document converter's docx → pdf path ran the file through mammoth, a
semantic converter that discards table borders, column widths, cell fills,
fonts, and rotated text. For a form-style document (e.g. a maintenance shift
log) that collapsed the entire layout — the PDF looked nothing like Word.

Add a dedicated high-fidelity path: renderDocxPdf() in pdfrender.js renders the
real OOXML with docx-preview inside an offscreen Chromium window, then prints to
PDF. It's a genuine 1:1 conversion, still fully offline, and needs no
LibreOffice. Page geometry (and thus pagination) comes from the document's own
w:pgSz via parseDocxPageSize, so it matches Word. On any parse failure it falls
back to the mammoth intermediate so the conversion never hard-fails.

Add a "PDF colour" option (Color / Black & White). B&W applies a
filter:grayscale(100%) at print time; it also works for the md/html/txt → pdf
paths.

Implementation notes:
- docx-preview needs JavaScript enabled, so pdfrender now runs two pools:
  htmlPool (javascript:false, untrusted converted HTML) and docxPool
  (javascript:true). Both share the same private session that cancels every
  non-local request, so the offline guarantee is unchanged
  (test/electron-offline.js still passes).
- Windows are pooled and reused, not created/destroyed per file: on Electron 43,
  destroying a window and immediately creating another in the same session
  deadlocks the second (first docx converts, next hangs). Reuse + park on
  about:blank before release, same discipline as the HTML pool.
- docx-preview + jszip are pure JS and stay in the asar; their browser builds are
  read with fs and inlined into the render page.

Tests: new test/pdfrender.test.js unit-tests parseDocxPageSize under plain node;
test/electron-pdf.js gains a docx → pdf case (color + B&W, sequential renders).
Verified end-to-end against the real reported document (2 pages, matching Word)
and under a concurrent 6-file batch (pool caps at POOL_SIZE, no hang).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GoZVDGfDjZwmGi3HNWda9B
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GoZVDGfDjZwmGi3HNWda9B
@AxialForge
AxialForge marked this pull request as ready for review July 24, 2026 14:56
@AxialForge
AxialForge merged commit d66d29e into main Jul 24, 2026
1 check passed
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