dom-docx 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/API.md +533 -0
  2. package/LICENSE +21 -0
  3. package/README.md +236 -0
  4. package/dist/browser.d.ts +34 -0
  5. package/dist/browser.d.ts.map +1 -0
  6. package/dist/browser.js +35 -0
  7. package/dist/browser.js.map +1 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/cli.js +118 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/converter/bordered-block.d.ts +54 -0
  13. package/dist/converter/bordered-block.d.ts.map +1 -0
  14. package/dist/converter/bordered-block.js +124 -0
  15. package/dist/converter/bordered-block.js.map +1 -0
  16. package/dist/converter/build-docx.d.ts +46 -0
  17. package/dist/converter/build-docx.d.ts.map +1 -0
  18. package/dist/converter/build-docx.js +161 -0
  19. package/dist/converter/build-docx.js.map +1 -0
  20. package/dist/converter/computed-style-snapshot.browser.js +73 -0
  21. package/dist/converter/computed-style-snapshot.d.ts +10 -0
  22. package/dist/converter/computed-style-snapshot.d.ts.map +1 -0
  23. package/dist/converter/computed-style-snapshot.js +78 -0
  24. package/dist/converter/computed-style-snapshot.js.map +1 -0
  25. package/dist/converter/constants.d.ts +51 -0
  26. package/dist/converter/constants.d.ts.map +1 -0
  27. package/dist/converter/constants.js +163 -0
  28. package/dist/converter/constants.js.map +1 -0
  29. package/dist/converter/css.d.ts +112 -0
  30. package/dist/converter/css.d.ts.map +1 -0
  31. package/dist/converter/css.js +621 -0
  32. package/dist/converter/css.js.map +1 -0
  33. package/dist/converter/flex.d.ts +59 -0
  34. package/dist/converter/flex.d.ts.map +1 -0
  35. package/dist/converter/flex.js +252 -0
  36. package/dist/converter/flex.js.map +1 -0
  37. package/dist/converter/image.d.ts +38 -0
  38. package/dist/converter/image.d.ts.map +1 -0
  39. package/dist/converter/image.js +159 -0
  40. package/dist/converter/image.js.map +1 -0
  41. package/dist/converter/inline.d.ts +18 -0
  42. package/dist/converter/inline.d.ts.map +1 -0
  43. package/dist/converter/inline.js +213 -0
  44. package/dist/converter/inline.js.map +1 -0
  45. package/dist/converter/ooxml-patch.d.ts +23 -0
  46. package/dist/converter/ooxml-patch.d.ts.map +1 -0
  47. package/dist/converter/ooxml-patch.js +54 -0
  48. package/dist/converter/ooxml-patch.js.map +1 -0
  49. package/dist/converter/style-path.d.ts +4 -0
  50. package/dist/converter/style-path.d.ts.map +1 -0
  51. package/dist/converter/style-path.js +17 -0
  52. package/dist/converter/style-path.js.map +1 -0
  53. package/dist/converter/style-resolver-node.d.ts +7 -0
  54. package/dist/converter/style-resolver-node.d.ts.map +1 -0
  55. package/dist/converter/style-resolver-node.js +26 -0
  56. package/dist/converter/style-resolver-node.js.map +1 -0
  57. package/dist/converter/style-resolver.d.ts +24 -0
  58. package/dist/converter/style-resolver.d.ts.map +1 -0
  59. package/dist/converter/style-resolver.js +122 -0
  60. package/dist/converter/style-resolver.js.map +1 -0
  61. package/dist/converter/svg.d.ts +11 -0
  62. package/dist/converter/svg.d.ts.map +1 -0
  63. package/dist/converter/svg.js +116 -0
  64. package/dist/converter/svg.js.map +1 -0
  65. package/dist/converter/table.d.ts +8 -0
  66. package/dist/converter/table.d.ts.map +1 -0
  67. package/dist/converter/table.js +745 -0
  68. package/dist/converter/table.js.map +1 -0
  69. package/dist/converter/text-metrics.d.ts +17 -0
  70. package/dist/converter/text-metrics.d.ts.map +1 -0
  71. package/dist/converter/text-metrics.js +51 -0
  72. package/dist/converter/text-metrics.js.map +1 -0
  73. package/dist/converter/types.d.ts +82 -0
  74. package/dist/converter/types.d.ts.map +1 -0
  75. package/dist/converter/types.js +9 -0
  76. package/dist/converter/types.js.map +1 -0
  77. package/dist/converter/visitor.d.ts +11 -0
  78. package/dist/converter/visitor.d.ts.map +1 -0
  79. package/dist/converter/visitor.js +910 -0
  80. package/dist/converter/visitor.js.map +1 -0
  81. package/dist/converter.d.ts +28 -0
  82. package/dist/converter.d.ts.map +1 -0
  83. package/dist/converter.js +44 -0
  84. package/dist/converter.js.map +1 -0
  85. package/dist/html-wrap.d.ts +3 -0
  86. package/dist/html-wrap.d.ts.map +1 -0
  87. package/dist/html-wrap.js +26 -0
  88. package/dist/html-wrap.js.map +1 -0
  89. package/dist/index.d.ts +17 -0
  90. package/dist/index.d.ts.map +1 -0
  91. package/dist/index.js +16 -0
  92. package/dist/index.js.map +1 -0
  93. package/examples/README.md +39 -0
  94. package/examples/balance-sheet/compare_side_by_side.png +0 -0
  95. package/examples/balance-sheet/input.html +41 -0
  96. package/examples/balance-sheet/output.docx +0 -0
  97. package/examples/balance-sheet/preview.png +0 -0
  98. package/examples/invoice/compare_side_by_side.png +0 -0
  99. package/examples/invoice/input.html +88 -0
  100. package/examples/invoice/logo.png +0 -0
  101. package/examples/invoice/output.docx +0 -0
  102. package/examples/invoice/preview.png +0 -0
  103. package/examples/javascript-essay/compare_side_by_side.png +0 -0
  104. package/examples/javascript-essay/input.html +39 -0
  105. package/examples/javascript-essay/output.docx +0 -0
  106. package/examples/javascript-essay/preview.png +0 -0
  107. package/examples/product-launch-brief/compare_side_by_side.png +0 -0
  108. package/examples/product-launch-brief/input.html +120 -0
  109. package/examples/product-launch-brief/output.docx +0 -0
  110. package/examples/product-launch-brief/preview.png +0 -0
  111. package/examples/quarterly-financials/compare_side_by_side.png +0 -0
  112. package/examples/quarterly-financials/input.html +27 -0
  113. package/examples/quarterly-financials/output.docx +0 -0
  114. package/examples/quarterly-financials/preview.png +0 -0
  115. package/examples/react-dashboard/compare_side_by_side.png +0 -0
  116. package/examples/react-dashboard/input.html +1 -0
  117. package/examples/react-dashboard/output.docx +0 -0
  118. package/examples/react-dashboard/preview.html +107 -0
  119. package/examples/react-dashboard/preview.png +0 -0
  120. package/examples/regional-sales-dashboard/compare_side_by_side.png +0 -0
  121. package/examples/regional-sales-dashboard/input.html +129 -0
  122. package/examples/regional-sales-dashboard/output.docx +0 -0
  123. package/examples/regional-sales-dashboard/preview.png +0 -0
  124. package/examples/sales-contract/compare_side_by_side.png +0 -0
  125. package/examples/sales-contract/input.html +68 -0
  126. package/examples/sales-contract/output.docx +0 -0
  127. package/examples/sales-contract/preview.png +0 -0
  128. package/examples/sprint-retrospective/compare_side_by_side.png +0 -0
  129. package/examples/sprint-retrospective/input.html +51 -0
  130. package/examples/sprint-retrospective/output.docx +0 -0
  131. package/examples/sprint-retrospective/preview.png +0 -0
  132. package/package.json +108 -0
package/README.md ADDED
@@ -0,0 +1,236 @@
1
+ # dom-docx
2
+
3
+ Convert semantic **HTML fragments** to native, editable **Word documents** (OOXML)—paragraphs, runs, lists, tables, images—not screenshots or layout hacks.
4
+
5
+ Built with a visual regression loop: render HTML in Chromium, convert, rasterize via LibreOffice, score layout + structural fidelity against a human-validated metric, iterate. Latest scores: [TEST-SCORES.md](./docs/TEST-SCORES.md) · methodology: [SCORING.md](./docs/SCORING.md).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install dom-docx
11
+ ```
12
+
13
+ Requires **Node.js ≥ 20**. No browser or Playwright is needed for the default **`inline`** path.
14
+
15
+ ### When is Playwright needed?
16
+
17
+ | Entry | `styleSource: "inline"` | `styleSource: "computed"` |
18
+ |-------|-------------------------|---------------------------|
19
+ | **Node** (`dom-docx`) | Pure JS — no browser | **Playwright + Chromium** (optional peer dependency) to render and snapshot styles |
20
+ | **Browser** (`dom-docx/browser`) | Pure JS — no live DOM | **Live page only** — native `getComputedStyle`; **Playwright not used** |
21
+
22
+ On Node, `playwright` is an **optional peer dependency** — `npm install dom-docx` pulls only `docx`, `cheerio`, and `fflate`, nothing heavy. It is loaded lazily only when you pass `styleSource: "computed"`. To use the computed path, install Playwright and Chromium yourself, once:
23
+
24
+ ```bash
25
+ npm install playwright
26
+ npx playwright install chromium
27
+ ```
28
+
29
+ Playwright is also used by the **dev test harness** (not required to use the library). Contributors: `npm run setup` after clone.
30
+
31
+ LibreOffice is **not** needed to convert—only for the visual test harness.
32
+
33
+ ## CLI
34
+
35
+ Convert a file without writing any code:
36
+
37
+ ```bash
38
+ npx dom-docx input.html -o output.docx
39
+ npx dom-docx input.html # writes input.docx next to it
40
+ cat fragment.html | npx dom-docx - -o - # stdin → binary stdout (pipelines)
41
+ npx dom-docx input.html -s computed # stylesheet/class HTML (needs playwright installed)
42
+ ```
43
+
44
+ Input is a **body HTML fragment**, same as the API. `--help` for all options.
45
+
46
+ ## Quick start
47
+
48
+ ```typescript
49
+ import { writeFile } from "node:fs/promises";
50
+ import { convertHtmlToDocx } from "dom-docx";
51
+
52
+ const html = `
53
+ <h1 style="color:#1a1a2e">Quarterly Report</h1>
54
+ <p>Revenue grew <strong>12%</strong> year over year.</p>
55
+ <ul>
56
+ <li>North America</li>
57
+ <li>EMEA</li>
58
+ </ul>
59
+ `;
60
+
61
+ const docx = await convertHtmlToDocx(html);
62
+ await writeFile("output.docx", docx);
63
+ ```
64
+
65
+ Pass a **body fragment only** (no `<!DOCTYPE>` / `<html>` / `<body>` required). Defaults: US Letter, 1″ margins, Arial 10.5pt body text.
66
+
67
+ ## v0.1.0 capability
68
+
69
+ **Supported (default `styleSource: "inline"`):**
70
+
71
+ - Headings, paragraphs, lists (`<ul>`/`<ol>` including `list-style-type`), tables, links, inline formatting
72
+ - Block backgrounds, blockquotes, `<hr>`, simple flex rows (≤4 items)
73
+ - `data:` images; remote images via your `imageResolver`
74
+ - Page size/orientation/margins, default font, metadata, header/footer HTML, page numbers, lang/direction
75
+ - Low-complexity inline SVG (bars + text)
76
+ - CSS bar divs in table cells (background + height/width → native shaded bands)
77
+
78
+ **Advanced (optional `styleSource: "computed"`):**
79
+
80
+ - Resolves `<style>` blocks and class/`#id` selectors via `getComputedStyle`
81
+ - **Node:** requires **`playwright`** (optional peer dependency, installed separately) + Chromium — the library launches headless Chromium to render the fragment
82
+ - **Browser bundle:** uses the **live DOM** in the user's tab — no Playwright, no extra install
83
+ - **Inline is the supported default** for npm installs; computed is for stylesheets/classes or when you already have a rendered page
84
+
85
+ **Not supported in v0.1.0:**
86
+
87
+ - External stylesheets on the inline path (use computed, or inline all styles)
88
+ - Web fonts, CSS grid/float layout, forms, `<pre>` polish, `<dl>`, table `rowspan`
89
+ - Header/footer first/even page variants; guaranteed multi-page layout fidelity
90
+ - Complex SVG (paths, gradients, `<use>`)
91
+
92
+ See [AGENTS.md](./AGENTS.md) for HTML authoring tiers and [API.md](./API.md) for full options.
93
+
94
+ ## API
95
+
96
+ ### `convertHtmlToDocx(html, options?)`
97
+
98
+ Returns `Promise<Buffer>` (Node) with a valid `.docx` file.
99
+
100
+ ```typescript
101
+ import { convertHtmlToDocx, type ConvertOptions } from "dom-docx";
102
+
103
+ const docx = await convertHtmlToDocx(html, {
104
+ pageSize: "a4",
105
+ orientation: "landscape",
106
+ margins: { top: 0.75, bottom: 0.75 }, // inches; omitted sides default to 1
107
+ defaultFont: { family: "Georgia", sizePt: 11 },
108
+ metadata: { title: "Q3 Report", creator: "Finance" },
109
+ headerHtml: "<p style='font-size:12px;color:#666'>Confidential</p>",
110
+ footerHtml: "<p style='font-size:12px'>© 2026 ACME</p>",
111
+ pageNumber: true,
112
+ lang: "en-US",
113
+ direction: "ltr",
114
+ });
115
+ ```
116
+
117
+ ### Options
118
+
119
+ | Option | Default | Description |
120
+ |--------|---------|-------------|
121
+ | `styleSource` | `"inline"` | `"inline"` parses `style=""` only (pure JS, fast). `"computed"` uses `getComputedStyle` — on **Node** this requires Playwright + Chromium; in the **browser bundle** it reads from the live DOM (no Playwright). |
122
+ | `browser` / `page` | — | **Node computed only.** Reuse an open Playwright browser or page instead of launching per call. Not used by `dom-docx/browser`. |
123
+ | `imageResolver` | — | Hook to fetch non-`data:` `<img src>` (library never fetches on its own). |
124
+ | `pageSize` | `"letter"` | `"letter"`, `"a4"`, or `{ width, height }` in inches. |
125
+ | `orientation` | `"portrait"` | `"landscape"` swaps dimensions. |
126
+ | `margins` | `1` inch each | Per-side overrides in inches. |
127
+ | `defaultFont` | Arial 10.5pt | `{ family, sizePt }` for body text without explicit CSS. |
128
+ | `metadata` | — | `title`, `subject`, `creator`, `keywords[]`, `description` → `docProps/core.xml`. |
129
+ | `headerHtml` / `footerHtml` | — | HTML fragments for page header/footer. |
130
+ | `pageNumber` | `false` | Appends centered `Page N` field to footer. |
131
+ | `lang` / `direction` | — | Spell-check locale; `"rtl"` for right-to-left. |
132
+
133
+ ### Images
134
+
135
+ Only **`data:`** URLs embed automatically. For `http(s):` or file paths, supply a resolver—you control fetch policy and security:
136
+
137
+ ```typescript
138
+ const docx = await convertHtmlToDocx(html, {
139
+ imageResolver: async (src) => {
140
+ const res = await fetch(src); // your allowlist / SSRF checks
141
+ if (!res.ok) return null;
142
+ return { data: new Uint8Array(await res.arrayBuffer()), type: "png" };
143
+ },
144
+ });
145
+ ```
146
+
147
+ ### Browser bundle
148
+
149
+ For client-side conversion (returns a `Blob`). **No Playwright** — the bundle runs entirely in the user's browser.
150
+
151
+ ```typescript
152
+ import { convertHtmlToDocx } from "dom-docx/browser";
153
+
154
+ // Inline styles — pass HTML string directly
155
+ const blob = await convertHtmlToDocx(htmlFragment, { styleSource: "inline" });
156
+
157
+ // Computed styles — render the fragment in the live DOM first, then convert
158
+ document.body.innerHTML = htmlFragment;
159
+ const blob2 = await convertHtmlToDocx(htmlFragment, { styleSource: "computed" });
160
+ // reads getComputedStyle from document.body — no Playwright, no headless Chromium
161
+ ```
162
+
163
+ Build: `npm run build:browser` → `dist/browser/dom-docx.browser.js`.
164
+
165
+ For advanced usage (`buildDocxBuffer`, custom `StyleResolver`, engine architecture), see [API.md](./API.md).
166
+
167
+ ---
168
+
169
+ ## What converts well
170
+
171
+ Optimized for **Word-friendly semantic HTML**—headings, paragraphs, lists, data tables, inline formatting, shaded callouts, simple flex rows.
172
+
173
+ | Excellent | Good | Avoid |
174
+ |-----------|------|-------|
175
+ | Headings, lists, simple tables | Shaded banners, flex (≤4 items) | SVG charts, CSS grid/float layout |
176
+ | Inline `strong` / `em` / links | Table row/cell backgrounds | External stylesheets (inline path) |
177
+ | Short span highlights | Blockquotes, `<hr>` | Forms, web fonts |
178
+
179
+ Full authoring guide for agents: [AGENTS.md](./AGENTS.md).
180
+
181
+ ---
182
+
183
+ ## How it was built
184
+
185
+ dom-docx maps a practical HTML subset to native OOXML through a three-stage pipeline:
186
+
187
+ 1. **Style resolution** — inline `style=""` (default) or browser computed styles
188
+ 2. **Visitor** — Cheerio walk → `docx` paragraphs, tables, numbering, hyperlinks
189
+ 3. **Pack + patch** — generate OOXML, patch list numbering and shaded-block alignment for LibreOffice/Word PDF export
190
+
191
+ Quality is driven by an autonomous loop rather than one-off visual checks:
192
+
193
+ - **33 regression cases** (`npm run test:suite`) — human-validated **layout fidelity** (ink-projection structure comparison, 85.6% concordance with blind human quality ratings), plus guards for bad contrast, missing list markers, wrong text, and imbalanced shaded blocks; raw pixel match is recorded as a regression tripwire
194
+ - **Engine score** — 50% visual (layout-based) + 35% editability (native structure, not 1×1 layout tables) + 15% compile speed
195
+ - **OSS benchmark** — same harness scores [html-to-docx](https://www.npmjs.com/package/html-to-docx) and [@turbodocx/html-to-docx](https://www.npmjs.com/package/@turbodocx/html-to-docx) for ongoing comparison ([BENCHMARK.md](./docs/BENCHMARK.md))
196
+
197
+ The default **`inline`** path is pure JavaScript (`docx` + `cheerio` + `fflate`) with no browser required. **Playwright is Node-only** — for `styleSource: "computed"` on the server and for the dev harness. The **`dom-docx/browser`** bundle never uses Playwright; computed styles come from the live page's `getComputedStyle`.
198
+
199
+ Full scoring formulas, subscores, calibration, and the agent iteration workflow: [SCORING.md](./docs/SCORING.md).
200
+
201
+ ---
202
+
203
+ ## Development
204
+
205
+ For contributors and harness runs (not required to use the library):
206
+
207
+ ```bash
208
+ git clone … && npm install && npm run setup
209
+ npm run build # dist/ for npm pack
210
+ npm run typecheck
211
+ npm run test:suite # 35-case visual + XML regression
212
+ npm run test:suite:priority # 10-case fast subset
213
+ npm run test:benchmark # vs html-to-docx + TurboDocx
214
+ npm run test:config # ConvertOptions OOXML checks
215
+ ```
216
+
217
+ Prerequisites for the harness: **LibreOffice** (`soffice`) for PDF rasterization, **Playwright Chromium** (`npm run setup`).
218
+
219
+ ---
220
+
221
+ ## Documentation
222
+
223
+ | Doc | Contents |
224
+ |-----|----------|
225
+ | [API.md](./API.md) | Full API reference, engine architecture, usage patterns |
226
+ | [AGENTS.md](./AGENTS.md) | HTML authoring guide for AI agents |
227
+ | [SCORING.md](./docs/SCORING.md) | Validation methodology and engine score |
228
+ | [TEST-SCORES.md](./docs/TEST-SCORES.md) | Latest suite metrics and per-case scores |
229
+ | [BENCHMARK.md](./docs/BENCHMARK.md) | Comparison vs OSS html-to-docx libraries |
230
+ | [examples/](./examples/) | Sample HTML, DOCX output, and side-by-side previews |
231
+ | [SHOWCASE.md](./docs/SHOWCASE.md) | How to run and extend showcase examples |
232
+ | [CONTRIBUTING.md](./CONTRIBUTING.md) | Library vs harness layout, dev setup |
233
+
234
+ ## License
235
+
236
+ [MIT](./LICENSE)
@@ -0,0 +1,34 @@
1
+ import { type DocumentConfig } from "./converter/build-docx.js";
2
+ import type { ImageResolver } from "./converter/image.js";
3
+ import { type StyleSource } from "./converter/style-resolver.js";
4
+ export type { ImageResolver, ResolvedImage } from "./converter/image.js";
5
+ export type { StyleResolver, StyleSource } from "./converter/style-resolver.js";
6
+ export type { DocumentConfig } from "./converter/build-docx.js";
7
+ export { buildDocxBlob, buildDocxUint8Array } from "./converter/build-docx.js";
8
+ export { snapshotComputedStylesFromDocument } from "./converter/computed-style-snapshot.js";
9
+ export interface BrowserConvertOptions extends DocumentConfig {
10
+ styleSource?: StyleSource;
11
+ /** Document to snapshot for `styleSource: "computed"`. Defaults to the host page. */
12
+ document?: Document;
13
+ /** Resolve non-`data:` `<img src>` before conversion (caller owns fetch policy). */
14
+ imageResolver?: ImageResolver;
15
+ }
16
+ export declare function convertHtmlToDocxUint8Array(html: string, options?: BrowserConvertOptions): Promise<Uint8Array>;
17
+ /**
18
+ * Convert an HTML body fragment to a `.docx` Blob in the browser.
19
+ *
20
+ * - `inline` (default): parses `style=""` only — no live DOM required for styles.
21
+ * - `computed`: batch-reads `getComputedStyle` from the **current** `document.body`
22
+ * tree (or `options.document` when provided). The page must already render the same fragment.
23
+ */
24
+ export declare function convertHtmlToDocx(html: string, options?: BrowserConvertOptions): Promise<Blob>;
25
+ export interface DomDocxGlobal {
26
+ convertHtmlToDocx: typeof convertHtmlToDocx;
27
+ convertHtmlToDocxUint8Array: typeof convertHtmlToDocxUint8Array;
28
+ }
29
+ declare global {
30
+ interface Window {
31
+ domDocx?: DomDocxGlobal;
32
+ }
33
+ }
34
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAErF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,+BAA+B,CAAC;AAEvC,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACzE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAChF,YAAY,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,EAAE,kCAAkC,EAAE,MAAM,wCAAwC,CAAC;AAE5F,MAAM,WAAW,qBAAsB,SAAQ,cAAc;IAC3D,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,qFAAqF;IACrF,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,oFAAoF;IACpF,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAQD,wBAAsB,2BAA2B,CAC/C,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,UAAU,CAAC,CASrB;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,MAAM,WAAW,aAAa;IAC5B,iBAAiB,EAAE,OAAO,iBAAiB,CAAC;IAC5C,2BAA2B,EAAE,OAAO,2BAA2B,CAAC;CACjE;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,OAAO,CAAC,EAAE,aAAa,CAAC;KACzB;CACF"}
@@ -0,0 +1,35 @@
1
+ import { buildDocxUint8Array } from "./converter/build-docx.js";
2
+ import { snapshotComputedStylesFromDocument } from "./converter/computed-style-snapshot.js";
3
+ import { ComputedStyleResolver, INLINE_STYLE_RESOLVER, } from "./converter/style-resolver.js";
4
+ export { buildDocxBlob, buildDocxUint8Array } from "./converter/build-docx.js";
5
+ export { snapshotComputedStylesFromDocument } from "./converter/computed-style-snapshot.js";
6
+ function documentConfig(options) {
7
+ if (!options)
8
+ return undefined;
9
+ const { pageSize, orientation, margins, defaultFont, metadata, headerHtml, footerHtml, pageNumber, lang, direction } = options;
10
+ return { pageSize, orientation, margins, defaultFont, metadata, headerHtml, footerHtml, pageNumber, lang, direction };
11
+ }
12
+ export async function convertHtmlToDocxUint8Array(html, options) {
13
+ const styleSource = options?.styleSource ?? "inline";
14
+ const resolver = styleSource === "inline"
15
+ ? INLINE_STYLE_RESOLVER
16
+ : ComputedStyleResolver.fromSnapshots(snapshotComputedStylesFromDocument(options?.document ?? document));
17
+ return buildDocxUint8Array(html, resolver, options?.imageResolver, documentConfig(options));
18
+ }
19
+ /**
20
+ * Convert an HTML body fragment to a `.docx` Blob in the browser.
21
+ *
22
+ * - `inline` (default): parses `style=""` only — no live DOM required for styles.
23
+ * - `computed`: batch-reads `getComputedStyle` from the **current** `document.body`
24
+ * tree (or `options.document` when provided). The page must already render the same fragment.
25
+ */
26
+ export async function convertHtmlToDocx(html, options) {
27
+ const bytes = await convertHtmlToDocxUint8Array(html, options);
28
+ return new Blob([bytes.slice()], {
29
+ type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
30
+ });
31
+ }
32
+ if (typeof window !== "undefined") {
33
+ window.domDocx = { convertHtmlToDocx, convertHtmlToDocxUint8Array };
34
+ }
35
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAuB,MAAM,2BAA2B,CAAC;AACrF,OAAO,EAAE,kCAAkC,EAAE,MAAM,wCAAwC,CAAC;AAE5F,OAAO,EACL,qBAAqB,EACrB,qBAAqB,GAEtB,MAAM,+BAA+B,CAAC;AAKvC,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,EAAE,kCAAkC,EAAE,MAAM,wCAAwC,CAAC;AAU5F,SAAS,cAAc,CAAC,OAA+B;IACrD,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAC/B,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;IAC/H,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AACxH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAC/C,IAAY,EACZ,OAA+B;IAE/B,MAAM,WAAW,GAAG,OAAO,EAAE,WAAW,IAAI,QAAQ,CAAC;IACrD,MAAM,QAAQ,GACZ,WAAW,KAAK,QAAQ;QACtB,CAAC,CAAC,qBAAqB;QACvB,CAAC,CAAC,qBAAqB,CAAC,aAAa,CACjC,kCAAkC,CAAC,OAAO,EAAE,QAAQ,IAAI,QAAQ,CAAC,CAClE,CAAC;IACR,OAAO,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;AAC9F,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAY,EACZ,OAA+B;IAE/B,MAAM,KAAK,GAAG,MAAM,2BAA2B,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAC/D,OAAO,IAAI,IAAI,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,EAAE;QAC/B,IAAI,EAAE,yEAAyE;KAChF,CAAC,CAAC;AACL,CAAC;AAaD,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;IAClC,MAAM,CAAC,OAAO,GAAG,EAAE,iBAAiB,EAAE,2BAA2B,EAAE,CAAC;AACtE,CAAC"}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * dom-docx CLI — convert an HTML fragment file to a native .docx.
4
+ *
5
+ * npx dom-docx input.html -o output.docx
6
+ * cat fragment.html | npx dom-docx - -o out.docx
7
+ * npx dom-docx input.html -o - > out.docx # binary to stdout
8
+ *
9
+ * Input is a BODY fragment (no <!DOCTYPE>/<html> wrapper needed). The default
10
+ * inline path is pure JS; --style-source computed needs the optional peer
11
+ * `playwright` (+ `npx playwright install chromium`) installed by the caller.
12
+ */
13
+ import { readFile, writeFile } from "node:fs/promises";
14
+ import path from "node:path";
15
+ import process from "node:process";
16
+ import { parseArgs } from "node:util";
17
+ import { convertHtmlToDocx } from "./converter.js";
18
+ const USAGE = `Usage: dom-docx <input.html> [options]
19
+ dom-docx - [options] read HTML from stdin
20
+
21
+ Options:
22
+ -o, --output <file> output path (default: <input>.docx; "-" writes binary to stdout)
23
+ -s, --style-source <s> "inline" (default, pure JS) or "computed"
24
+ (requires: npm i playwright && npx playwright install chromium)
25
+ -h, --help show this help
26
+ -v, --version print version
27
+
28
+ Input is a body HTML fragment — headings, paragraphs, lists, tables, inline
29
+ styles. See https://github.com/freeman-g/dom-docx#readme for what converts best.`;
30
+ async function readStdin() {
31
+ const chunks = [];
32
+ for await (const chunk of process.stdin)
33
+ chunks.push(chunk);
34
+ return Buffer.concat(chunks).toString("utf-8");
35
+ }
36
+ async function packageVersion() {
37
+ try {
38
+ const pkg = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf-8"));
39
+ return pkg.version ?? "unknown";
40
+ }
41
+ catch {
42
+ return "unknown";
43
+ }
44
+ }
45
+ function fail(message) {
46
+ console.error(`dom-docx: ${message}`);
47
+ console.error(`Try: dom-docx --help`);
48
+ process.exit(1);
49
+ }
50
+ function parseCliArgs() {
51
+ return parseArgs({
52
+ allowPositionals: true,
53
+ options: {
54
+ output: { type: "string", short: "o" },
55
+ "style-source": { type: "string", short: "s" },
56
+ help: { type: "boolean", short: "h" },
57
+ version: { type: "boolean", short: "v" },
58
+ },
59
+ });
60
+ }
61
+ async function main() {
62
+ let parsed;
63
+ try {
64
+ parsed = parseCliArgs();
65
+ }
66
+ catch (err) {
67
+ fail(err instanceof Error ? err.message : String(err));
68
+ }
69
+ const { values, positionals } = parsed;
70
+ if (values.help) {
71
+ console.log(USAGE);
72
+ return;
73
+ }
74
+ if (values.version) {
75
+ console.log(await packageVersion());
76
+ return;
77
+ }
78
+ const input = positionals[0];
79
+ if (!input)
80
+ fail("missing input file (or '-' for stdin)");
81
+ if (positionals.length > 1)
82
+ fail(`unexpected extra arguments: ${positionals.slice(1).join(" ")}`);
83
+ const styleSource = (values["style-source"] ?? "inline");
84
+ if (styleSource !== "inline" && styleSource !== "computed") {
85
+ fail(`--style-source must be "inline" or "computed" (got "${styleSource}")`);
86
+ }
87
+ const html = input === "-"
88
+ ? await readStdin()
89
+ : await readFile(input, "utf-8").catch(() => fail(`cannot read ${input}`));
90
+ if (!html.trim())
91
+ fail("input HTML is empty");
92
+ const output = values.output ??
93
+ (input === "-"
94
+ ? "output.docx"
95
+ : path.join(path.dirname(input), `${path.basename(input, path.extname(input))}.docx`));
96
+ let docx;
97
+ try {
98
+ docx = await convertHtmlToDocx(html, { styleSource });
99
+ }
100
+ catch (err) {
101
+ const message = err instanceof Error ? err.message : String(err);
102
+ if (styleSource === "computed" && /playwright/i.test(message)) {
103
+ fail(`computed styles need the optional peer dependency:\n npm install playwright && npx playwright install chromium\n(${message})`);
104
+ }
105
+ fail(`conversion failed: ${message}`);
106
+ }
107
+ if (output === "-") {
108
+ process.stdout.write(docx);
109
+ return;
110
+ }
111
+ await writeFile(output, docx);
112
+ console.error(`dom-docx: wrote ${output} (${docx.length} bytes)`);
113
+ }
114
+ main().catch((err) => {
115
+ console.error(`dom-docx: ${err instanceof Error ? err.message : err}`);
116
+ process.exit(1);
117
+ });
118
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import { Table, type ParagraphChild } from "docx";
2
+ import type { BlockBorders, BlockLayout, DocxBlock, RunTypography } from "./types.js";
3
+ export declare function visibleCellBorders(borders?: BlockBorders): {
4
+ top: {
5
+ style: "none";
6
+ size: number;
7
+ color: string;
8
+ } | {
9
+ style: "single";
10
+ size: number;
11
+ color: string;
12
+ space: number;
13
+ };
14
+ right: {
15
+ style: "none";
16
+ size: number;
17
+ color: string;
18
+ } | {
19
+ style: "single";
20
+ size: number;
21
+ color: string;
22
+ space: number;
23
+ };
24
+ bottom: {
25
+ style: "none";
26
+ size: number;
27
+ color: string;
28
+ } | {
29
+ style: "single";
30
+ size: number;
31
+ color: string;
32
+ space: number;
33
+ };
34
+ left: {
35
+ style: "none";
36
+ size: number;
37
+ color: string;
38
+ } | {
39
+ style: "single";
40
+ size: number;
41
+ color: string;
42
+ space: number;
43
+ };
44
+ };
45
+ /** Multi-side borders break OOXML element order on Paragraph — use a table wrapper instead. */
46
+ export declare function shouldUseBorderedTable(layout: BlockLayout): boolean;
47
+ /** Shaded blocks with borders use a table cell — paragraph border + shading order is fragile. */
48
+ export declare function needsShadedTableWrapper(layout: BlockLayout): boolean;
49
+ export declare function makeBorderedTableBlock(runs: ParagraphChild[], layout: BlockLayout, typography: RunTypography): Table;
50
+ /** Single-border shaded block (e.g. border-left callout) — table cell for fill + border. */
51
+ export declare function makeShadedBlockTable(runs: ParagraphChild[], layout: BlockLayout, typography?: RunTypography): Table;
52
+ /** One cell, borderless table — continuous background for block children inside a shaded `<div>`. */
53
+ export declare function makeShadedContainerTable(blocks: DocxBlock[], layout: BlockLayout, borders?: BlockBorders): Table;
54
+ //# sourceMappingURL=bordered-block.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bordered-block.d.ts","sourceRoot":"","sources":["../../src/converter/bordered-block.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,EAKL,KAAK,cAAc,EACpB,MAAM,MAAM,CAAC;AAId,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AA8BtF,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAexD;AAED,+FAA+F;AAC/F,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAEnE;AAED,iGAAiG;AACjG,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAEpE;AA+BD,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,cAAc,EAAE,EACtB,MAAM,EAAE,WAAW,EACnB,UAAU,EAAE,aAAa,GACxB,KAAK,CAKP;AAED,4FAA4F;AAC5F,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,cAAc,EAAE,EACtB,MAAM,EAAE,WAAW,EACnB,UAAU,CAAC,EAAE,aAAa,GACzB,KAAK,CAeP;AAED,qGAAqG;AACrG,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,SAAS,EAAE,EACnB,MAAM,EAAE,WAAW,EACnB,OAAO,CAAC,EAAE,YAAY,GACrB,KAAK,CA4BP"}
@@ -0,0 +1,124 @@
1
+ import { BorderStyle, LineRuleType, Paragraph, ShadingType, Table, TableCell, TableRow, TextRun, WidthType, } from "docx";
2
+ import { BODY_LINE_BOX_PX, PRINTABLE_CONTENT_WIDTH_TWIPS } from "./constants.js";
3
+ import { pxToTwips } from "./css.js";
4
+ import { typographyToTextRunOptions } from "./inline.js";
5
+ function countBorderSides(borders) {
6
+ if (!borders)
7
+ return 0;
8
+ return [borders.top, borders.right, borders.bottom, borders.left].filter(Boolean).length;
9
+ }
10
+ const BORDERLESS = { style: BorderStyle.NONE, size: 0, color: "auto" };
11
+ function borderlessTableBorders() {
12
+ return {
13
+ top: BORDERLESS,
14
+ bottom: BORDERLESS,
15
+ left: BORDERLESS,
16
+ right: BORDERLESS,
17
+ insideHorizontal: BORDERLESS,
18
+ insideVertical: BORDERLESS,
19
+ };
20
+ }
21
+ function toCellBorder(side) {
22
+ if (!side)
23
+ return undefined;
24
+ return {
25
+ style: BorderStyle.SINGLE,
26
+ size: side.size,
27
+ color: side.color,
28
+ space: side.space,
29
+ };
30
+ }
31
+ export function visibleCellBorders(borders) {
32
+ if (!borders || countBorderSides(borders) === 0) {
33
+ return {
34
+ top: BORDERLESS,
35
+ bottom: BORDERLESS,
36
+ left: BORDERLESS,
37
+ right: BORDERLESS,
38
+ };
39
+ }
40
+ return {
41
+ top: toCellBorder(borders.top) ?? BORDERLESS,
42
+ right: toCellBorder(borders.right) ?? BORDERLESS,
43
+ bottom: toCellBorder(borders.bottom) ?? BORDERLESS,
44
+ left: toCellBorder(borders.left) ?? BORDERLESS,
45
+ };
46
+ }
47
+ /** Multi-side borders break OOXML element order on Paragraph — use a table wrapper instead. */
48
+ export function shouldUseBorderedTable(layout) {
49
+ return countBorderSides(layout.borders) >= 2;
50
+ }
51
+ /** Shaded blocks with borders use a table cell — paragraph border + shading order is fragile. */
52
+ export function needsShadedTableWrapper(layout) {
53
+ return countBorderSides(layout.borders) > 0;
54
+ }
55
+ /** Inner paragraph for shaded table cells — padding lives in cell margins. */
56
+ function makeTableCellInnerParagraph(runs, layout, typography) {
57
+ return new Paragraph({
58
+ ...(layout.indentLeft || layout.indentRight
59
+ ? { indent: { left: layout.indentLeft, right: layout.indentRight } }
60
+ : {}),
61
+ spacing: {
62
+ before: 0,
63
+ after: 0,
64
+ line: pxToTwips(layout.shadedContentLinePx ?? BODY_LINE_BOX_PX),
65
+ lineRule: LineRuleType.EXACT,
66
+ },
67
+ contextualSpacing: false,
68
+ children: runs.length > 0
69
+ ? runs
70
+ : [
71
+ new TextRun({
72
+ text: "",
73
+ ...(typography ? typographyToTextRunOptions(typography) : {}),
74
+ }),
75
+ ],
76
+ });
77
+ }
78
+ export function makeBorderedTableBlock(runs, layout, typography) {
79
+ const { borders, shading, ...paragraphLayout } = layout;
80
+ const innerParagraph = makeTableCellInnerParagraph(runs, paragraphLayout, typography);
81
+ return makeShadedContainerTable([innerParagraph], layout, borders);
82
+ }
83
+ /** Single-border shaded block (e.g. border-left callout) — table cell for fill + border. */
84
+ export function makeShadedBlockTable(runs, layout, typography) {
85
+ const innerParagraph = makeTableCellInnerParagraph(runs, {
86
+ ...layout,
87
+ shading: undefined,
88
+ borders: undefined,
89
+ paddingTop: 0,
90
+ paddingBottom: 0,
91
+ paddingLeft: 0,
92
+ paddingRight: 0,
93
+ }, typography);
94
+ return makeShadedContainerTable([innerParagraph], layout, layout.borders);
95
+ }
96
+ /** One cell, borderless table — continuous background for block children inside a shaded `<div>`. */
97
+ export function makeShadedContainerTable(blocks, layout, borders) {
98
+ const cellChildren = blocks.length > 0 ? blocks : [new Paragraph({ children: [new TextRun("")] })];
99
+ return new Table({
100
+ width: { size: PRINTABLE_CONTENT_WIDTH_TWIPS, type: WidthType.DXA },
101
+ columnWidths: [PRINTABLE_CONTENT_WIDTH_TWIPS],
102
+ layout: "fixed",
103
+ borders: borderlessTableBorders(),
104
+ rows: [
105
+ new TableRow({
106
+ children: [
107
+ new TableCell({
108
+ width: { size: PRINTABLE_CONTENT_WIDTH_TWIPS, type: WidthType.DXA },
109
+ borders: visibleCellBorders(borders),
110
+ shading: layout.shading ? { type: ShadingType.CLEAR, ...layout.shading } : undefined,
111
+ margins: {
112
+ top: layout.paddingTop ?? 0,
113
+ bottom: layout.paddingBottom ?? 0,
114
+ left: layout.paddingLeft ?? 0,
115
+ right: layout.paddingRight ?? 0,
116
+ },
117
+ children: cellChildren,
118
+ }),
119
+ ],
120
+ }),
121
+ ],
122
+ });
123
+ }
124
+ //# sourceMappingURL=bordered-block.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bordered-block.js","sourceRoot":"","sources":["../../src/converter/bordered-block.ts"],"names":[],"mappings":"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"}