# 生成 DOCX

用手写节点或 docx 创作辅助函数构造 LitAST，再用 generateDocx 输出完整的 DOCX 字节；附单位约定。

```ts
generateDocx(ast: LitAstNode): Promise<Uint8Array>
```

`generateDocx` 没有其他参数。它根据 LitAST 重新构建整个 DOCX 包，返回完整文件的字节；不会在某个已有文件上局部修改。

## 输出字节

```ts
// Node
await writeFile("out.docx", await generateDocx(ast));

// 浏览器：生成下载链接
const bytes = await generateDocx(ast);
const url = URL.createObjectURL(new Blob([bytes], {
  type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
}));
```

## 手写节点

LitAST 节点是普通对象，根节点是 `document`：

```ts
const ast = {
  type: "document",
  children: [
    {
      type: "paragraph",
      children: [
        { type: "text", text: "Hello " },
        { type: "text", text: "world", marks: [{ type: "bold" }] },
      ],
    },
  ],
};
```

这份文档生成后再用 `parseDocx` 读回，得到的 `children` 与上面完全一致。

## 用 `docx` 辅助函数

`docx` 是一个分组的辅助对象，返回的就是上面这样的节点，只是写起来更短，并带有类型检查：

| 分组 | 内容 |
| --- | --- |
| `docx.blocks` | `paragraph`、`heading`、`blockquote`、`codeBlock`、`pageBreak`、`callout` |
| `docx.inline` | `text`、`link`、`ruby` |
| `docx.marks` | `bold`、`highlight`、`link`、`textStyle` |
| `docx.tables` | `table`、`row`、`cell`、`shadedCell` |
| `docx.styles` | `paragraphProperties`、`spacing`、`indentation`、`shading`、`border`、`runProperties` |

辅助函数没有覆盖所有节点。根节点 `document`、图片 `image`、分节 `section`、列表 `list` 需要手写，二者可以自由混用。斜体等没有辅助函数的标记直接写 `{ type: "italic" }`。

```ts
docx.blocks.paragraph({
  children: [
    docx.inline.text("重要", { marks: [docx.marks.bold(), docx.marks.textStyle({ color: "C00000", fontSize: "14pt" })] }),
    docx.inline.text("：详见"),
    docx.inline.link("说明", { href: "https://example.com" }),
  ],
  properties: docx.styles.paragraphProperties({
    textAlign: "center",
    spacing: docx.styles.spacing({ after: 120 }),
  }),
});
```

## 拼音指南

`docx.inline.ruby` 生成原生 `w:ruby`。`base` 是正文，`annotation` 是注音；两层可以
分别设置文字格式。读音由调用者提供，lit-docx 不包含自动拼音词典。

```ts
const guide = docx.inline.ruby({
  marks: [docx.marks.textStyle({ fontSize: "12pt" })],
  base: [docx.inline.text("汉", {
    marks: [docx.marks.textStyle({ fontSize: "12pt" })],
  })],
  annotation: [docx.inline.text("hàn", {
    marks: [docx.marks.textStyle({ fontFamily: "Arial", fontSize: "6pt" })],
  })],
  rubyAlign: "center",
  hps: 12,
  hpsRaise: 24,
  hpsBaseText: 24,
  lid: "zh-CN",
});
const bytes = await generateDocx({
  type: "document",
  children: [docx.blocks.paragraph({ children: [guide] })],
});
```

这五个属性在新建时都需显式提供。`hps`、`hpsRaise`、`hpsBaseText` 的数字单位是半磅；
`hpsRaise` 是文件中的抬升量，**不是** Word 对话框“拼音文字距离”的直接数值。
本例来自 Word 16.109.2 的 12pt 正文、6pt 注音样本，不代表通用字体的自动间距算法。
外层 `marks` 的字号、基文字号与 `hpsBaseText` 保持一致；参考字号不会替代实际文字格式。
对齐值有 `center`、`left`、`right`、`distributeLetter`、`distributeSpace`、`rightVertical`。

解析后的节点使用 `children` 存正文、`attrs.rubyText` 存注音。可修改文字和以上属性后再生成；
未知或未改的来源属性按[往返规则](/guide/round-trip#拼音指南)保留。超链接应放在整个 ruby
节点的 `marks` 上；不能把 `docx.inline.link` 直接放进任一文字层。

## 表格

表格的层级是 `table > tableRow > tableCell > 段落`。单元格里至少要有一个块级节点，用辅助函数创建时会自动补一个空段落。

```ts
docx.tables.table({
  columns: [3000, 3000],
  rows: [
    docx.tables.row({ isHeader: true, cells: [docx.tables.cell({ children: [docx.blocks.paragraph({ children: [docx.inline.text("地区")] })] })] }),
  ],
});
```

## 图片、分节与页眉页脚

- **图片**：`{ type: "image", attrs: { src, width, height } }`，`src` 是图片的 Data URL，宽高单位是像素（按 96 DPI 换算）。
- **分节**：`section` 是 `document.children` 里的一个兄弟节点，表示它**之前**的内容到此结束一节，并携带这一节的页面设置。
- **页眉页脚**：作为 `section` 的子节点，`header` 用 `attrs.headerType`，`footer` 用 `attrs.footerType`，取值为 `"default"`、`"first"` 或 `"even"`。

[快速开始](/)里的报告示例同时用到了这三者。

## 单位约定

| 数值 | 单位 |
| --- | --- |
| 页面宽高、页边距、段落缩进、段前段后间距、表格列宽 | twips，即 1/20 磅；1 英寸 = 1440，1 厘米 ≈ 567 |
| 图片宽高 | 像素，按 96 DPI 换算 |
| `textStyle` 的 `fontSize` | `"12pt"` 这样的字符串，或以磅为单位的数字 |
| ruby 的 `hps`、`hpsRaise`、`hpsBaseText` | 数字为半磅，或保留 OOXML 显式单位字符串 |
| 颜色 | 6 位十六进制 RGB，`"C00000"` 与 `"#C00000"` 均可；解析结果带 `#` |

A4 纸为 `pageWidth: 11906, pageHeight: 16838`，US Letter 为 `12240 × 15840`。

## 新文档会自动补齐的部件

从零生成时没有原始的 `styles.xml`、`numbering.xml` 等部件，生成器会按文档中实际用到的内容补齐编号、批注、脚注尾注等部件。从已有文件读进来的 LitAST 则会写回 `ast.meta.rawXmlFiles` 中保存的原始部件，见[往返与保留](/guide/round-trip)。
