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.
- package/API.md +533 -0
- package/LICENSE +21 -0
- package/README.md +236 -0
- package/dist/browser.d.ts +34 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +35 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +118 -0
- package/dist/cli.js.map +1 -0
- package/dist/converter/bordered-block.d.ts +54 -0
- package/dist/converter/bordered-block.d.ts.map +1 -0
- package/dist/converter/bordered-block.js +124 -0
- package/dist/converter/bordered-block.js.map +1 -0
- package/dist/converter/build-docx.d.ts +46 -0
- package/dist/converter/build-docx.d.ts.map +1 -0
- package/dist/converter/build-docx.js +161 -0
- package/dist/converter/build-docx.js.map +1 -0
- package/dist/converter/computed-style-snapshot.browser.js +73 -0
- package/dist/converter/computed-style-snapshot.d.ts +10 -0
- package/dist/converter/computed-style-snapshot.d.ts.map +1 -0
- package/dist/converter/computed-style-snapshot.js +78 -0
- package/dist/converter/computed-style-snapshot.js.map +1 -0
- package/dist/converter/constants.d.ts +51 -0
- package/dist/converter/constants.d.ts.map +1 -0
- package/dist/converter/constants.js +163 -0
- package/dist/converter/constants.js.map +1 -0
- package/dist/converter/css.d.ts +112 -0
- package/dist/converter/css.d.ts.map +1 -0
- package/dist/converter/css.js +621 -0
- package/dist/converter/css.js.map +1 -0
- package/dist/converter/flex.d.ts +59 -0
- package/dist/converter/flex.d.ts.map +1 -0
- package/dist/converter/flex.js +252 -0
- package/dist/converter/flex.js.map +1 -0
- package/dist/converter/image.d.ts +38 -0
- package/dist/converter/image.d.ts.map +1 -0
- package/dist/converter/image.js +159 -0
- package/dist/converter/image.js.map +1 -0
- package/dist/converter/inline.d.ts +18 -0
- package/dist/converter/inline.d.ts.map +1 -0
- package/dist/converter/inline.js +213 -0
- package/dist/converter/inline.js.map +1 -0
- package/dist/converter/ooxml-patch.d.ts +23 -0
- package/dist/converter/ooxml-patch.d.ts.map +1 -0
- package/dist/converter/ooxml-patch.js +54 -0
- package/dist/converter/ooxml-patch.js.map +1 -0
- package/dist/converter/style-path.d.ts +4 -0
- package/dist/converter/style-path.d.ts.map +1 -0
- package/dist/converter/style-path.js +17 -0
- package/dist/converter/style-path.js.map +1 -0
- package/dist/converter/style-resolver-node.d.ts +7 -0
- package/dist/converter/style-resolver-node.d.ts.map +1 -0
- package/dist/converter/style-resolver-node.js +26 -0
- package/dist/converter/style-resolver-node.js.map +1 -0
- package/dist/converter/style-resolver.d.ts +24 -0
- package/dist/converter/style-resolver.d.ts.map +1 -0
- package/dist/converter/style-resolver.js +122 -0
- package/dist/converter/style-resolver.js.map +1 -0
- package/dist/converter/svg.d.ts +11 -0
- package/dist/converter/svg.d.ts.map +1 -0
- package/dist/converter/svg.js +116 -0
- package/dist/converter/svg.js.map +1 -0
- package/dist/converter/table.d.ts +8 -0
- package/dist/converter/table.d.ts.map +1 -0
- package/dist/converter/table.js +745 -0
- package/dist/converter/table.js.map +1 -0
- package/dist/converter/text-metrics.d.ts +17 -0
- package/dist/converter/text-metrics.d.ts.map +1 -0
- package/dist/converter/text-metrics.js +51 -0
- package/dist/converter/text-metrics.js.map +1 -0
- package/dist/converter/types.d.ts +82 -0
- package/dist/converter/types.d.ts.map +1 -0
- package/dist/converter/types.js +9 -0
- package/dist/converter/types.js.map +1 -0
- package/dist/converter/visitor.d.ts +11 -0
- package/dist/converter/visitor.d.ts.map +1 -0
- package/dist/converter/visitor.js +910 -0
- package/dist/converter/visitor.js.map +1 -0
- package/dist/converter.d.ts +28 -0
- package/dist/converter.d.ts.map +1 -0
- package/dist/converter.js +44 -0
- package/dist/converter.js.map +1 -0
- package/dist/html-wrap.d.ts +3 -0
- package/dist/html-wrap.d.ts.map +1 -0
- package/dist/html-wrap.js +26 -0
- package/dist/html-wrap.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/examples/README.md +39 -0
- package/examples/balance-sheet/compare_side_by_side.png +0 -0
- package/examples/balance-sheet/input.html +41 -0
- package/examples/balance-sheet/output.docx +0 -0
- package/examples/balance-sheet/preview.png +0 -0
- package/examples/invoice/compare_side_by_side.png +0 -0
- package/examples/invoice/input.html +88 -0
- package/examples/invoice/logo.png +0 -0
- package/examples/invoice/output.docx +0 -0
- package/examples/invoice/preview.png +0 -0
- package/examples/javascript-essay/compare_side_by_side.png +0 -0
- package/examples/javascript-essay/input.html +39 -0
- package/examples/javascript-essay/output.docx +0 -0
- package/examples/javascript-essay/preview.png +0 -0
- package/examples/product-launch-brief/compare_side_by_side.png +0 -0
- package/examples/product-launch-brief/input.html +120 -0
- package/examples/product-launch-brief/output.docx +0 -0
- package/examples/product-launch-brief/preview.png +0 -0
- package/examples/quarterly-financials/compare_side_by_side.png +0 -0
- package/examples/quarterly-financials/input.html +27 -0
- package/examples/quarterly-financials/output.docx +0 -0
- package/examples/quarterly-financials/preview.png +0 -0
- package/examples/react-dashboard/compare_side_by_side.png +0 -0
- package/examples/react-dashboard/input.html +1 -0
- package/examples/react-dashboard/output.docx +0 -0
- package/examples/react-dashboard/preview.html +107 -0
- package/examples/react-dashboard/preview.png +0 -0
- package/examples/regional-sales-dashboard/compare_side_by_side.png +0 -0
- package/examples/regional-sales-dashboard/input.html +129 -0
- package/examples/regional-sales-dashboard/output.docx +0 -0
- package/examples/regional-sales-dashboard/preview.png +0 -0
- package/examples/sales-contract/compare_side_by_side.png +0 -0
- package/examples/sales-contract/input.html +68 -0
- package/examples/sales-contract/output.docx +0 -0
- package/examples/sales-contract/preview.png +0 -0
- package/examples/sprint-retrospective/compare_side_by_side.png +0 -0
- package/examples/sprint-retrospective/input.html +51 -0
- package/examples/sprint-retrospective/output.docx +0 -0
- package/examples/sprint-retrospective/preview.png +0 -0
- 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"}
|
package/dist/browser.js
ADDED
|
@@ -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 @@
|
|
|
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
|
package/dist/cli.js.map
ADDED
|
@@ -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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"}
|