# 渲染为 HTML

用 lit-docx-html 把 DOCX 渲染成随屏幕重排的语义 HTML，自定义任意节点的渲染，并取得目录与加粗、下划线的提示文字。

`@quirekit/lit-docx-html` 把 DOCX 渲染成随屏幕宽度重排的 HTML，适合在网页或 App 中展示由 Word 编写的内容，
例如用户协议、隐私政策和公告。它不分页，也不还原页面尺寸、字体和段落间距；[快速开始](/)中示例的“预览”就由它渲染。

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

## 转换为 HTML

`docxToHtml` 在浏览器和 Node 中都可运行。在构建时转换，页面直接输出静态 HTML，浏览器不必加载解析器：

```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`：正文的 HTML 片段。文字与属性都已转义；链接只保留 `http`、`https`、`mailto` 与页内锚点，图片只保留内嵌的位图。
- `outline`：标题与条款，含锚点、层级、编号文字（如“第一条”“1.1”）和正文，可用来生成目录。
- `emphasis`：加粗或下划线的文字及其所在条款的锚点。协议通常用这种格式标出需要特别提示的条款，可以汇总展示。
- `title`：文档标题，取条款开始前第一行居中的文字或第一个标题。

## 条款编号

Word 自动编号的段落会在渲染时加上编号文字。很多协议的编号是直接输入的文字，例如“第三条”“一、”“（一）”“4.1”“（1）”“①”，
这些也会被识别为条款，进入目录并获得锚点，文字本身保持原样。

- 层级按各种编号写法在文档中第一次出现的先后决定：先出现“一、”再出现“第一条”时，条在章之下；反之亦然。
  Word 编号与手工编号一起参与排序。
- 某种写法在全文只出现一次时（如“1.5 倍行距”）不当作条款；表格内的段落不当作条款。
- `outlineDepth` 控制目录列到第几级，默认 2；`typedClauses: false` 关闭手工编号识别。

加粗或下划线只有在去掉条款号与 `{{…}}` 占位符后仍有至少 8 个文字时才计入 `emphasis`，加粗的条款号和下划线填空不会混入重点条款。

## 自定义渲染

`renderers` 按节点类型、`markRenderers` 按标记类型替换默认渲染。渲染函数返回 `{ tag, attrs, children }`
与文字组成的树；返回 `null` 省略该节点，返回 `undefined` 使用默认渲染：

```ts
const { html } = await docxToHtml(bytes, {
  renderers: {
    // 编号条款渲染成带编号徽标的 section
    paragraph: (node, context) => context.number
      ? { tag: "section", attrs: { class: "clause", id: context.anchor },
          children: [{ tag: "b", children: [context.number] }, ...context.children()] }
      : undefined,
    // 不展示表格
    table: () => null,
  },
  markRenderers: {
    // 加粗文字统一标成重点
    bold: (_mark, content) => ({ tag: "mark", attrs: { class: "key-term" }, children: content }),
  },
});
```

渲染上下文提供 `children()`（已渲染的子节点）、`fallback()`（默认渲染结果）、`number`、`anchor` 与祖先节点。
节点类型与属性见 [LitAST 节点模型](/guide/litast)。已解析的 LitAST 可以直接交给 `renderLitDocx`，再用 `toHtml`
序列化；解析时请使用 `numberingMode: "flat-numbered-paragraphs"`，编号文字才能按 Word 的规则计算。

## 在 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` 接受 DOCX 字节或 LitAST；`options` 传入与上面相同的自定义渲染。`read-end` 在文档末尾第一次进入视口时触发，
可用于“阅读完毕后才能勾选同意”。

## 主题与静态页面

包内的 `@quirekit/lit-docx-html/styles.css` 是默认主题：作用于 `.lit-docx` 容器，颜色与字体都是 CSS 变量（如
`--lit-docx-accent`、`--lit-docx-font`），跟随系统深色模式，也可用 `data-theme="light"` 或 `"dark"` 固定。
`LitDocxView` 的根元素就是 `.lit-docx`。

构建时可以直接用命令生成完整页面：

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

`--toc` 加入目录，`--key-terms` 加入重点条款，`--fragment` 只输出正文片段，`--lang en` 切换英文界面文字，
`--outline` 另存目录与重点条款的 JSON。在代码中可用 `renderHtmlPage`、`renderToc`、`renderKeyTerms` 组合同样的页面。

## 对比两个版本

协议更新后通常要告知用户改了什么。`compareDocx` 按段落、标题和表格行对比两个版本，返回新增、删除与修改，并标出所在条款：

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

const changes = await compareDocx(oldBytes, newBytes);
// [{ kind: "added", clause: { number: "第二条", text: "个人信息" }, after: "…" },
//  { kind: "modified", clause: { number: "第三条", text: "服务内容" }, before: "…按年收取。", after: "…按月收取。" }]
```

匹配时忽略手工输入的条款号，中间插入一条后，后面整体顺延的“第五条”“第六条”不会被当成修改。相邻的删除与新增
文字相似度达到 `similarity`（默认 0.4）时合并为一处修改。删除项标注旧版本中的条款，其余标注新版本中的条款。

## 许可

`@quirekit/lit-docx-html` 与 lit-docx 一样是 QuireKit 商业组件，适用相同的授权条款。
