Skip to content
HN On Hacker News ↗

GitHub - floodtide/dom-docx: Convert semantic HTML fragments to native, editable Word documents (OOXML)

▲ 158 points • 40 comments • by fishbone • 4mo ago • HN discussion ↗

Pangram verdict · v3.3

We believe that this document is fully AI-generated

95 %

AI likelihood · overall

AI
0% human-written 100% AI-generated
SEGMENTS · HUMAN 0 of 6
SEGMENTS · AI 6 of 6
WORD COUNT 1,388
PEAK AI % 99% · §6
Analyzed
Jul 13
backend: pangram/v3.3
Segments scanned
6 windows
avg 231 words each
Distribution
0 / 100%
human / AI fraction
Verdict
AI
Pangram v3.3

Article text · 1,388 words · 6 segments analyzed

Human AI-generated
§1 AI · 97%

Convert semantic HTML fragments to native, editable Word documents (OOXML): paragraphs, runs, lists, tables, images. Not screenshots or layout hacks. Live demo: dom-docx.com. Try the converter, browse showcases, read the learn guide. Built with a visual regression loop: render HTML in Chromium, convert to docx, rasterize via LibreOffice, score layout + structural fidelity against a human-validated metric, iterate. Latest scores: TEST-SCORES.md · methodology: SCORING.md. Install npm install dom-docx Requires Node.js ≥ 20. No browser or Playwright is needed for the default inline path. When is Playwright needed?

Entry styleSource: "inline" styleSource: "computed" rasterizeInPlace

Node (dom-docx) Pure JS, no browser Playwright + Chromium Playwright + Chromium (same headless page)

Browser (dom-docx/browser) Pure JS, no live DOM Live page: native getComputedStyle Live page: canvas/SVG → PNG <img> in the tab

On Node, playwright is an optional peer dependency. npm install dom-docx pulls only docx, cheerio and fflate, nothing heavy. It is loaded lazily when you pass styleSource: "computed" or rasterizeInPlace. To use those paths, install Playwright and Chromium yourself, once: npm install playwright npx playwright install chromium Playwright is also used by the dev test harness (not required to use the library). Contributors: npm run setup after clone. LibreOffice is not needed to convert. It is only used for the visual test harness. CLI Convert a file without writing any code: npx dom-docx input.html -o output.docx npx dom-docx input.html # writes input.docx next to it cat fragment.html | npx dom-docx - -o - # stdin → binary stdout (pipelines) npx dom-docx input.html -s computed # stylesheet/class HTML (needs playwright installed) npm install -g dom-docx # optional: install globally, then run "dom-docx" without npx Input is a body HTML fragment, same as the API.

§2 AI · 99%

--help for all options. Quick start Browser import { convertHtmlToDocx } from "dom-docx/browser";

const html = ` <h1 style="color:#1a1a2e">Quarterly Report</h1> <p>Revenue grew <strong>12%</strong> year over year.</p> <ul> <li>North America</li> <li>EMEA</li> </ul> `;

const blob = await convertHtmlToDocx(html); // e.g. trigger a download in the browser const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = "output.docx"; a.click(); No Playwright, no Node. This runs entirely in the user's tab. See Browser bundle below. Node import { writeFile } from "node:fs/promises"; import { convertHtmlToDocx } from "dom-docx";

const html = ` <h1 style="color:#1a1a2e">Quarterly Report</h1> <p>Revenue grew <strong>12%</strong> year over year.</p> <ul> <li>North America</li> <li>EMEA</li> </ul> `;

const docx = await convertHtmlToDocx(html); await writeFile("output.docx", docx); Pass a body fragment only (no <!DOCTYPE> / <html> / <body> required). Defaults: US Letter, 1″ margins, Arial 10.5pt body text. v0.1.x capability Supported (default styleSource: "inline"):

Headings, paragraphs, lists (<ul>/<ol> including list-style-type), tables, links, inline formatting Block backgrounds, blockquotes, <hr>, simple flex rows (≤4 items) data: images; remote images via your imageResolver Page size/orientation/margins, default font, metadata, header/footer HTML, page

§3 AI · 99%

numbers, table of contents, lang/direction Low-complexity inline SVG (bars + text) CSS bar divs in table cells (background + height/width → native shaded bands)

Advanced (optional styleSource: "computed"):

Resolves <style> blocks and class/#id selectors via getComputedStyle Node: requires playwright (optional peer dependency, installed separately) + Chromium. The library launches headless Chromium to render the fragment Browser bundle: uses the live DOM in the user's tab. No Playwright, no extra install SPA fragment export: pass root (browser) or rootSelector (Node + live page) when converting element.innerHTML so computed-style paths match the fragment tree Inline is the supported default for npm installs; computed is for stylesheets/classes or when you already have a rendered page

Charts & complex SVG (optional rasterizeInPlace):

Rasterizes <canvas> and complex <svg> (e.g. Highcharts) to PNG <img> before conversion Recommended for charts: rasterizeInPlace: { scale: 2 } — supersamples at 2× density for sharper images in Word (default scale: 1; max 4) Browser: requires root. Clones off-screen by default so the live page is not mutated Node: uses the same Playwright/Chromium context as computed styles; ephemeral spawn pages mutate in place by default Simple inline SVG (rect + text bar charts) still converts natively without rasterization

Not supported in v0.1.x:

External stylesheets on the inline path (use computed or inline all styles) Web fonts, CSS grid/float layout, forms, <pre> polish, <dl>, table rowspan Header/footer first/even page variants; guaranteed multi-page layout fidelity Complex SVG (paths, gradients, <use>) unless rasterizeInPlace is used on a live rendered page

See AGENTS.md for HTML authoring tiers and API.md for full options. API convertHtmlToDocx(html, options?) Returns Promise<Buffer> (Node) with a valid .docx file.

§4 AI · 82%

import { convertHtmlToDocx, type ConvertOptions } from "dom-docx";

const docx = await convertHtmlToDocx(html, { pageSize: "a4", orientation: "landscape", margins: { top: 0.75, bottom: 0.75 }, // inches; omitted sides default to 1 defaultFont: { family: "Georgia", sizePt: 11 }, metadata: { title: "Q3 Report", creator: "Finance" }, headerHtml: "<p style='font-size:12px;color:#666'>Confidential</p>", footerHtml: "<p style='font-size:12px'>© 2026 ACME</p>", pageNumber: true, lang: "en-US", direction: "ltr", coverHtml: "<h1 style='text-align:center'>Quarterly Review</h1>", // page 1, before the TOC tocHtml: "<ol><li><a href='#intro'>Introduction</a></li></ol>", // your TOC; links jump to id="intro" }); Options

Option Default Description

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).

browser / page — Node only. Reuse an open Playwright browser or page (computed styles and/or rasterizeInPlace). Not used by dom-docx/browser.

rootSelector — Node only. CSS selector for the export root when converting element.innerHTML from a live Playwright page. Must match the node whose HTML you pass.

rasterizeInPlace — Rasterize <canvas> / chart <svg> to PNG <img> before conversion. Recommended: { scale: 2 } for chart exports. Node: Playwright page; browser: requires root. See API.md.

imageResolver — Hook to fetch non-data: <img src> (library never fetches on its own).

pageSize "letter" "letter", "a4" or { width, height } in inches.

§5 AI · 86%

orientation "portrait" "landscape" swaps dimensions.

margins 1 inch each Per-side overrides in inches.

defaultFont Arial 10.5pt { family, sizePt } for body text without explicit CSS.

metadata — title, subject, creator, keywords[], description → docProps/core.xml.

headerHtml / footerHtml — HTML fragments for page header/footer.

pageNumber false Appends centered Page N field to footer.

lang / direction — Spell-check locale; "rtl" for right-to-left.

coverHtml — HTML fragment rendered as a cover page — the first content, before the TOC, followed by an automatic page break. Inline styles + data: images (e.g. a logo). Header/footer/page number are suppressed on the cover page.

tocHtml — HTML fragment rendered as a table-of-contents "slot" — placed after the cover, before the body. You control the markup/styling (numbered, boxed, columns…); in-page links (<a href="#id">) jump to the matching id in the body. Add a trailing <div style="break-after:page"></div> to put it on its own page.

Images Only data: URLs embed automatically. For http(s): or file paths, supply a resolver. You control fetch policy and security: const docx = await convertHtmlToDocx(html, { imageResolver: async (src) => { const res = await fetch(src); // your allowlist / SSRF checks if (!res.ok) return null; return { data: new Uint8Array(await res.arrayBuffer()), type: "png" }; }, }); Browser bundle For client-side conversion (returns a Blob). No Playwright. The bundle runs entirely in the user's browser.

§6 AI · 99%

import { convertHtmlToDocx } from "dom-docx/browser";

// Inline styles: pass HTML string directly const blob = await convertHtmlToDocx(htmlFragment, { styleSource: "inline" });

// Computed styles: render the fragment in the live DOM first, then convert document.body.innerHTML = htmlFragment; const blob2 = await convertHtmlToDocx(htmlFragment, { styleSource: "computed" });

// SPA export: pass the live root so computed paths match element.innerHTML const root = document.querySelector(".page-body")!; const blob3 = await convertHtmlToDocx(root.innerHTML, { styleSource: "computed", root, });

// Charts (Highcharts, canvas): rasterize to PNG <img> on a clone, then convert const blob4 = await convertHtmlToDocx(root.innerHTML, { styleSource: "computed", root, rasterizeInPlace: { scale: 2 }, // recommended for charts; default scale is 1 }); Browser-only options: root, document, rasterizeInPlace. Build: npm run build:browser → dist/browser/dom-docx.browser.js. For advanced usage (buildDocxBuffer, custom StyleResolver, engine architecture), see API.md.

What converts well Optimized for Word-friendly semantic HTML: headings, paragraphs, lists, data tables, inline formatting, shaded callouts, simple flex rows.

Excellent Good Avoid (or use rasterizeInPlace)

Headings, lists, simple tables Shaded banners, flex (≤4 items) Live chart libraries (Highcharts, canvas) unless rasterized

Inline strong / em / links Table row/cell backgrounds Complex SVG paths/gradients unless rasterized

Explicit page breaks (break-before: page, break-after: page)

CSS grid, floats, absolute layout

Short span highlights Blockquotes, <hr> External stylesheets (inline path)

Simple SVG bars (rect + text) data: images Forms, web fonts, CSS grid/float layout

Full authoring guide for agents: AGENTS.md.