Skip to content
Merged
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
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,23 @@

All notable changes to Jdot Utilities. Dates are YYYY-MM-DD.

## 1.1.1 — 2026-07-24

- **Word → PDF is now a true 1:1 conversion.** Converting a `.docx` to PDF used
to run it through a text-only converter that threw away tables, column widths,
cell colours, fonts, and rotated headers — so a form-style document (a shift
log, an inspection sheet) came out looking nothing like the original. It now
renders the real Word layout, so the PDF matches the document you see in Word:
borders, shaded cells, merged cells, sideways labels, coloured text, and the
same page breaks. Still fully offline, and it needs no LibreOffice install.
- **New "PDF colour" option: Color or Black & White.** Leave it on Color to keep
the document's original colours, or choose Black & White to convert everything
to greyscale for black-and-white printing. (A B&W PDF is a larger file because
the page is flattened to greyscale — that's expected.) The option also applies
to Markdown/HTML/text → PDF.
- If a particular `.docx` can't be rendered the new way, the converter quietly
falls back to the old path so the conversion still succeeds.

## 1.1.0 — 2026-07-24

- **Drag to reorder** in Merge PDFs and Images → PDF. Grab a file by the grip and
Expand Down
41 changes: 38 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Two layers, because the PDF path needs Chromium:
`pdfshrink`, `bmp`/`ico`, `engines`, the registry (incl. kinds/validation),
and every non-PDF `document-convert` path. Runs in plain node.
- Electron-hosted smoke tests (run directly): `npx electron test/electron-pdf.js`
(PDF output, pooled windows, `<base href>`, orientation),
(PDF output, pooled windows, `<base href>`, orientation, Word→PDF color+B&W),
`npx electron test/electron-ops.js` (collect/explode via the discovered tools),
and `npx electron test/electron-offline.js` (the render pool makes no remote
requests). `npm run test:pdf` runs the first. **Anything touching PDF output or
Expand Down Expand Up @@ -82,7 +82,7 @@ src/
ops.js Runners for collect (N->1) and explode (1->N) kinds
pagespec.js Parse "1-3,5,8-" page ranges (shared by split/delete/extract)
htmlutil.js HTML normalization: strip <style>/<script>, <base href>, text extraction
pdfrender.js HTML -> PDF via a POOL of offscreen Electron windows
pdfrender.js HTML -> PDF, and Word (.docx) -> PDF, via POOLs of offscreen Electron windows
pdfops.js PDF merge/split/rotate/delete/extract/inspect via pdf-lib
imgpdf.js Images -> one PDF (sharp normalizes, pdf-lib assembles)
pdfjs.js Shared pdfjs-dist loader (dynamic import of the ESM build)
Expand Down Expand Up @@ -142,7 +142,9 @@ async convert({ inputPath, outputPath, outputFormat, options, signal, onProgress
png/jpg/jpeg/webp/avif/tiff/gif/bmp/ico. Options: resize preset, custom max
width, quality, ICO size set.
- **Document** (pure JS + Electron PDF): md/markdown/html/htm/docx/txt ↔
html/md/txt/pdf/docx. Option: PDF page size.
html/md/txt/pdf/docx. Options: PDF page size, landscape, colour (Color / B&W).
**Word → PDF is a dedicated high-fidelity path** (docx-preview, offline, no
LibreOffice) — a true 1:1 render; the other targets still use mammoth.
- **Office** (LibreOffice, three families): Word (docx/doc/odt/rtf), Spreadsheets
(xlsx/xls/ods/csv), Presentations (pptx/ppt/odp) — each within-family + → pdf.
- **PDF toolkit** (offline, auto-discovered tools):
Expand Down Expand Up @@ -261,6 +263,39 @@ To retheme, edit those token blocks only — all accent colour is confined to th
`test/pagepicker.test.js`) rather than keeping a second copy that will drift.
- **HEIC decode** goes through `heic-convert` because sharp's prebuilt libvips
usually omits HEIF decode.
- **Word → PDF must NOT go through mammoth.** mammoth (used for docx → md/txt/html)
is a *semantic* converter: it discards table borders, column widths, cell fills,
fonts, and rotated text — so a form-style .docx collapsed into an unrecognisable
blob. docx → pdf instead uses **docx-preview** (pure JS, reads the real OOXML)
rendered in an offscreen Electron window, then `printToPDF` — a genuine 1:1
conversion, still fully offline and needing no LibreOffice. `renderDocxPdf` in
`pdfrender.js` owns it; on any parse failure it falls back to the mammoth
intermediate so the conversion never hard-fails. **Fidelity was validated by
rendering the real user document; don't "simplify" it back onto the mammoth
path.**
- **Word → PDF paginates from the .docx's own `w:pgSz`, not the page-size option.**
`parseDocxPageSize` reads the section width/height (twips ÷ 1440 = inches;
orientation is already baked into w:w/w:h) and emits an `@page` rule, printed
with `preferCSSPageSize:true`. Using the UI's Letter/A4 option instead would
clip or re-paginate the document. The pageSize/landscape options only apply to
the md/html/txt → pdf paths.
- **Two render pools, not one.** `pdfrender.js` runs `htmlPool` (javascript:**false**,
for untrusted converted HTML) and `docxPool` (javascript:**true**, because
docx-preview must execute). Both use the same private offline session that
cancels every non-local request, so enabling JS changes nothing about the
offline promise (`test/electron-offline.js` still passes). **Do not render docx
by creating a window per file and destroying it** — on Electron 43 destroying a
window and immediately creating another in that session deadlocks the second
(reproduced: first docx converts, the next hangs forever). Reuse a pooled window
and park it on `about:blank` (awaited) before `release`, exactly like the HTML
pool.
- **The B&W ("Black & White") PDF option is a CSS `filter:grayscale(100%)` on
`<html>`**, applied at print time with `printBackground:true`. Any CSS filter
makes Chromium **rasterise** the page, so a greyscale PDF is much larger than the
vector colour one and its text is no longer selectable — that's expected and
fine for B&W printing, not a bug. `docx-preview`/`jszip` are pure JS and stay in
the asar; `pdfrender.js` reads their browser builds with `fs` (works inside asar)
and inlines them into the render page, escaping any `</script` in the source.

## Roadmap (unbuilt)
Smaller PDF follow-ons now that the engine is in place:
Expand Down
10 changes: 7 additions & 3 deletions FORMATS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ output, unless a note or an excluded-pair says otherwise.

## 1. Documents (shipping; planned expansion below)

**Engine now:** `marked` + `turndown` + `mammoth` + `html-to-docx` + Electron's
Chromium for PDF — pure JS, offline, no binary.
**Engine now:** `marked` + `turndown` + `mammoth` + `html-to-docx` +
`docx-preview` + Electron's Chromium for PDF — pure JS, offline, no binary.
**Word → PDF** uses `docx-preview` for a full 1:1 render (tables, cell fills,
column widths, rotated headers); the other targets use `mammoth`.

| From \ To | html | md | txt | pdf | docx |
|-----------|:----:|:--:|:---:|:---:|:----:|
Expand All @@ -29,7 +31,9 @@ Chromium for PDF — pure JS, offline, no binary.
| **txt** | Shipping | Shipping | — | Shipping | Shipping |

Inputs: md, markdown, html, htm, docx, txt. Options: PDF page size (Letter/A4/Legal/
Tabloid), landscape. Same-format pairs (docx→docx, etc.) are intentionally hidden.
Tabloid), landscape, colour (Color / Black & White). Word → PDF ignores the page-size
option and uses the document's own page geometry so pagination matches Word.
Same-format pairs (docx→docx, etc.) are intentionally hidden.

**Planned expansion — `pandoc` sidecar (~130 MB bundled).** One binary takes this from 6
formats to ~45 and writes *real* `.docx` (retiring `html-to-docx` + `mammoth`):
Expand Down
30 changes: 13 additions & 17 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "jdot-utilities",
"version": "1.1.0",
"version": "1.1.1",
"description": "Local, offline file utilities — conversion, PDF tools, and more. No cloud, no account, no upload.",
"main": "src/main/main.js",
"author": "Joe",
Expand All @@ -18,10 +18,12 @@
},
"dependencies": {
"@napi-rs/canvas": "^1.0.2",
"docx-preview": "^0.4.0",
"fast-xml-parser": "^5.10.1",
"heic-convert": "^2.1.0",
"html-to-docx": "^1.8.0",
"js-yaml": "^5.2.1",
"jszip": "^3.10.1",
"mammoth": "^1.8.0",
"marked": "^4.3.0",
"pdf-lib": "^1.17.1",
Expand Down
Loading
Loading