# Rendering to HTML

Render DOCX to reflowing semantic HTML with lit-docx-html, replace the rendering of any node, and get an outline and the bold or underlined passages.

`@quirekit/lit-docx-html` renders DOCX as HTML that reflows to the screen width. It suits Word-authored content shown
on websites or in apps, such as user agreements, privacy policies and announcements. It does not paginate or
reproduce page size, fonts or paragraph spacing; the Preview of the [Quick start](/en) example is rendered by it.

```bash
pnpm add @quirekit/lit-docx-html
```

## Convert to HTML

`docxToHtml` runs in browsers and in Node. Converting at build time lets pages ship static HTML without loading
the parser in the browser:

```ts
import { readFile } from "node:fs/promises";
import { docxToHtml } from "@quirekit/lit-docx-html";

const { html, outline, emphasis } = await docxToHtml(new Uint8Array(await readFile("agreement.docx")));
```

- `html`: the body as an HTML fragment. Text and attributes are escaped; links keep only `http`, `https`, `mailto`
  and in-page anchors, and images keep only embedded raster data.
- `outline`: headings and clauses with anchor, level, number text (such as `1.1`) and text, for a table of contents.
- `emphasis`: bold or underlined passages with the anchor of their clause. Agreements usually mark terms that need
  special attention this way, so they can be listed together.
- `title`: the document title, taken from the first centered line before any clause or the first heading.

## Clause numbers

Paragraphs numbered by Word get their number text when rendered. Many agreements type the numbers instead, such as
`第三条`, `一、`, `（一）`, `4.1`, `(1)` or `①`, or `Section 2`; these are recognized as clauses too, enter the outline
and get anchors, while the text stays as written.

- Levels follow the order in which each numbering style first appears: when `一、` comes before `第一条`, articles nest
  under sections, and the other way round. Word numbering and typed numbers are ranked together.
- A style used only once in the document (such as `1.5 times spacing`) is not a clause, and paragraphs inside tables never are.
- `outlineDepth` sets how many levels the outline lists, 2 by default; `typedClauses: false` turns typed numbers off.

Bold or underlined text counts toward `emphasis` only when at least 8 letters remain after removing the clause number
and `{{…}}` placeholders, so bold clause numbers and underlined blanks do not enter the key terms.

## Custom rendering

`renderers` replaces the default rendering by node type and `markRenderers` by mark type. A renderer returns a tree
of `{ tag, attrs, children }` and strings; `null` omits the node and `undefined` uses the default:

```ts
const { html } = await docxToHtml(bytes, {
  renderers: {
    // Numbered clauses become sections with a number badge
    paragraph: (node, context) => context.number
      ? { tag: "section", attrs: { class: "clause", id: context.anchor },
          children: [{ tag: "b", children: [context.number] }, ...context.children()] }
      : undefined,
    // Hide tables
    table: () => null,
  },
  markRenderers: {
    // Mark all bold text as key terms
    bold: (_mark, content) => ({ tag: "mark", attrs: { class: "key-term" }, children: content }),
  },
});
```

The context provides `children()` (rendered children), `fallback()` (the default rendering), `number`, `anchor` and the
ancestors. See the [LitAST node model](/en/guide/litast) for node types and attributes. Parsed LitAST can go straight
to `renderLitDocx` and be serialized with `toHtml`; parse with `numberingMode: "flat-numbered-paragraphs"` so number
text follows Word's rules.

## In Vue

```vue
<script setup lang="ts">
import { ref } from "vue";
import { LitDocxView } from "@quirekit/lit-docx-html/vue";

defineProps<{ bytes: Uint8Array }>();
const agreed = ref(false);
</script>

<template>
  <LitDocxView :source="bytes" @rendered="result => console.log(result.outline)" @read-end="agreed = true" />
</template>
```

`source` accepts DOCX bytes or LitAST, and `options` takes the custom renderers above. `read-end` fires the first time
the end of the document enters the viewport, for “agree after reading” checkboxes.

## Theme and static pages

`@quirekit/lit-docx-html/styles.css` is the default theme. It applies to the `.lit-docx` container, exposes colors and
fonts as CSS variables (such as `--lit-docx-accent` and `--lit-docx-font`), follows the system dark mode and can be
fixed with `data-theme="light"` or `"dark"`. The root element of `LitDocxView` is `.lit-docx`.

At build time a command generates a complete page:

```bash
npx lit-docx-html agreement.docx -o agreement.html --toc --key-terms --outline agreement.json
```

`--toc` adds the table of contents, `--key-terms` the key terms, `--fragment` outputs only the body, `--lang en` switches
the interface text, and `--outline` saves the outline and key terms as JSON. In code, `renderHtmlPage`, `renderToc` and
`renderKeyTerms` compose the same page.

## Compare two versions

After an agreement changes, users usually need to know what changed. `compareDocx` compares paragraphs, headings and
table rows of two versions and returns additions, removals and modifications with their clause:

```ts
import { compareDocx } from "@quirekit/lit-docx-html";

const changes = await compareDocx(oldBytes, newBytes);
// [{ kind: "added", clause: { number: "Section 2", text: "Personal data" }, after: "…" },
//  { kind: "modified", clause: { number: "Section 3", text: "Services" }, before: "…billed yearly.", after: "…billed monthly." }]
```

Typed clause numbers are ignored when matching, so inserting a clause does not mark every renumbered clause after it
as modified. Adjacent removed and added text whose similarity reaches `similarity` (0.4 by default) becomes one
modification. Removals carry the clause of the old version; other changes carry the clause of the new version.

## License

`@quirekit/lit-docx-html` is a QuireKit commercial component under the same license terms as lit-docx.
