# Word 结构化原生生成

`fsys.openXml.docx.builder` 将普通 aardio 内容模型生成可编辑的 DOCX。它不解析 HTML，不调用 Office、MSXML 或 .NET；通过公共 `fsys.openXml.writer` 写包、检查写入结果并原子提交。

适合程序组织报告、段落、编号和简单表格。Markdown 仍使用 `string.markdown.toDocx`：Markdown → 有限 HTML → 本生成器。不是完整 Word 排版引擎，也不是已有 Word 文档的编辑器。

## 最小示例

```aardio
import fsys.openXml.docx.builder;
var document = {
    blocks = [
        {style="Heading1";text="工作报告"},
        {runs=[{text="结论：";bold=true},{text="正文与格式分开组织。"}]},
        {kind="table";rows=[["项目","数值"],["计数",0],["完成",false]]}
    ]
};
var path,err,report = fsys.openXml.docx.builder.write("/report.docx",document,{
    title = "工作报告";
    author = "aardio";
});
if(!path) return null,err;
return path,report;
```

- `write(path,document,options)`：成功返回完整路径、null、report；失败返回 null、err，已经完成构建时第三返回值仍保留报告。扩展名必须是 `.docx`。
- `buildParts(document,options)`：返回 `{name,data}` 部件数组、null、report；不进行文件 IO。可直接交给公共 writer。**与 HTML 转换器旧接口返回的“部件名→XML”表不同。**
- `normalizeOptions(options)`：返回合并并检查后的新表，不修改输入或 defaults，供适配层复用公共纸张/字体规则。
- 已导入 `fsys.openXml.docx` 时，也可用 `.write` 便捷入口；只做生成时独立导入 builder，避免加载读取器。

`document` 只包含 `blocks` 和可选的 `numbering`。内容数组必须连续，空数组使用 `[]`，不要用 null 作为占位元素。未知内容字段、未知块/运行类型、无效编号/书签引用会返回错误，不静默丢弃。

## 段落与文字

`blocks` 的普通字符串、数值、布尔值简写为一个段落；表对象省略 `kind` 时也表示段落。显式写法为 `{kind="paragraph";...}`。

| 字段 | 含义 |
|---|---|
| `text` / `runs` | 二选一。text 是字符串/数值/布尔值；runs 是格式化片段数组。未指定时为空段落 |
| `style` | Normal、Heading1…Heading6、CodeBlock。标题是带大纲级别的真实 Word 样式 |
| `align` | left、center、right、justify（兼容 Word 的 both） |
| `left/hanging` | 左缩进/悬挂量，非负整数，单位 twips |
| `spaceBefore/spaceAfter` | 段前/段后间距，twips；0 保留 |
| `lineSpacing` | 自动行距倍数 |
| `keepNext/keepLines` | 与下段同页/段中不分页，布尔值 |
| `quote/rule` | 简单左侧引用线/底部分隔线，使用公共边框颜色；同时设置时采用底线 |
| `numId` | 引用 document.numbering 的 1 基索引；不是直接填写显示的编号 |
| `list` | 无编号续段的紧凑段后间距；编号段落也自动采用紧凑间距 |

文字中的空格原样保留，不经过 HTML 实体解码。一个 text 内的 CRLF、LF、CR 转为软换行，TAB 转为制表符；要生成多个段落，使用多个 blocks。

`0`、`false`、空字符串分别生成 `0`、`false` 和空文字。XML 1.0 禁止的控制字符及 U+FFFE/U+FFFF 替换为 U+FFFD；非法 UTF-8 返回错误。标题和作者沿用 XML 文本的换行归一化规则。

### 格式化运行

```aardio
import fsys.openXml.docx.builder;
var document = {blocks=[
    {runs=[
        {text="普通 "},
        {text="加粗";bold=true;color=0x0969DA},
        {text=" 标记";background=0xFFF8C5},
        {kind="tab"},
        {text="下一列"},
        {kind="break"},
        {text="软换行后的文字"}
    ]},
    {runs=[{kind="break";breakType="page"}]},
    {style="Heading1";text="新的一页"}
]};
return fsys.openXml.docx.builder.buildParts(document);
```

运行的 `kind` 默认为 text，另支持 tab、break、bookmark。breakType 默认为 line，也可为 page；不分页计算，只写入显式分页符。

文字运行可设置：

- `font/eastAsia`：字体名；eastAsia 必须与 font 一起指定，省略则使用相同字体。不嵌入字体。
- `fontSize`：磅，0.5～1638，输出到半磅精度。
- `bold/italic/strike/underline/noProof`：布尔值；false 显式取消对应格式，不被假值兜底吞掉。
- `color/background`：0xRRGGBB；黑色 0 合法，background=false 不写底色。
- `vert`：superscript、subscript、baseline。
- `style`：目前只支持 Hyperlink；设置 hyperlink 时默认采用此字符样式。
- `hyperlink`：见下文。

## 编号、书签与超链接

```aardio
import fsys.openXml.docx.builder;
var document = {
    numbering = [
        {kind="decimal";start=3;left=480},
        {kind="bullet";marker="•";left=960}
    ];
    blocks = [
        {style="Heading1";runs=[{kind="bookmark";name="Intro";id=1},{text="目录目标"}]},
        {text="第三步";numId=1},
        {text="第四步";numId=1},
        {text="子项";numId=2},
        {runs=[
            {text="返回标题";hyperlink={anchor="Intro"}},
            {text=" aardio";hyperlink="https://www.aardio.com/"}
        ]}
    ]
};
return fsys.openXml.docx.builder.buildParts(document);
```

- 每个编号定义是独立的单级列表。kind 为 decimal 或 bullet，start 默认 1，范围为 0～2147483647 的整数；不接受负起点。left 默认 480 twips，marker 默认 `%1.` 或 `•`；可用 font 指定编号字体。
- 同一 numId 的段落按序连续编号。重新起号需再建一个定义；嵌套层级用独立定义与不同缩进表示，不模拟完整多级章节编号。
- 编号段落缺省使用定义的 left、240 twips 的 hanging。显式填写可覆盖，0 也保留。
- bookmark 是零长度书签；id 为 0～2147483647 的整数，name 是至多 40 个字母/数字/下划线组成、不能以数字开头的名称。同一文档中 id 和 name 均须唯一。
- 内部链接支持前向引用，但目标必须实际存在。外链仅允许 http、https、mailto；同一地址复用关系，不访问 URL、不下载文件、不执行脚本。地址中的保留字符建议按 URI 规则编码。

## 表格

```aardio
import fsys.openXml.docx.builder;
var document = {blocks=[{
    kind="table";
    widths=[3000,5000];
    cellMargins={left=120;right=120;top=80;bottom=80};
    rows=[
        {header=true;cells=[
            {background=0xF6F8FA;blocks=[{runs=[{text="项目";bold=true}]}]},
            {background=0xF6F8FA;blocks=[{runs=[{text="说明";bold=true}]}]}
        ]},
        ["数值",0],
        ["状态",false],
        ["详情",{blocks=[{text="第一段"},{text="第二段";align="right"}]}]
    ]
}]};
return fsys.openXml.docx.builder.buildParts(document);
```

- rows 至少一行，1～63 列。每行可为单元格数组，或 `{header=true;cells=[...]}`；header 写入跨页重复表头标记，不自动设置粗体/底色。
- 单元格可为字符串/数值/布尔值，或 `{blocks=[...];background=...}`；blocks 支持嵌套表格。生成器保证每个单元格以段落结束。
- 不齐行按最大列数补空单元格。数组内部不能留 null 洞；要留空请用空字符串或 `{blocks=[]}`。
- widths 为正整数列宽数组，单位 twips；省略则均分当前可用宽度。显式宽度总和不能超出页面或父单元格；不自动计算文字排版或按内容撑开列。
- left 指定整个表格的左缩进；cellMargins 默认四边 80 twips，可单独覆盖，0 合法。左右边距之和必须小于列宽。
- borderColor 默认使用 options.colors.border，边框为简单 0.5 磅实线。
- 本入口暂不支持跨行/跨列合并。HTML 转换器继续按原策略将合并或过宽表格展开为文字，不把这一降级策略放进 Word 内核。

## 公共选项与保存

- 字体：font=Microsoft YaHei、monoFont=Consolas、symbolFont=Segoe UI Symbol；fontSize=11、codeFontSize=10（磅）。
- 行距：lineSpacing/codeLineSpacing=1.15；正文默认段后 160 twips，表格/列表默认段后 60 twips。
- 纸张：pageWidth=11906、pageHeight=16838（A4，twips）；margins 四边 1440；landscape=true 时将纵向宽高交换。
- 1 磅 = 20 twips。页宽、页高、页边距不是屏幕像素，不随显示器 DPI 改变。
- colors 包含 text、muted、link、border、codeBackground、codeText、inlineCode、inlineCodeBackground、tableHeaderBackground、mark，颜色均为 0xRRGGBB。inlineCodeBackground=false 可关闭 HTML 适配器的行内代码底色。
- title/author 为核心属性。overwrite 默认 false；true 仅表示明确允许替换目标，不会把旧文件先清空。
- writer 的 level、maxEntries、maxPartBytes、maxWriteBytes、protectPaths 可透传。默认每部件 16 MiB、总写入 128 MiB；失败不留下半成品目标。buildParts 只构建内存部件，写包限额由 write 阶段检查。

`defaults` 可修改，但与 string.html.toDocx 的默认表独立；后者与 string.markdown.toDocx 仍共用同一个表。

## 边界

当前只生成一个正文节、预定义样式与上述内容。没有页眉页脚、图片嵌入、跨格合并、浮动图形、修订、自动目录、公式/域计算、字体嵌入或既有 DOCX 编辑。内容模型拒绝这些未支持字段；不会通过 altChunk 把 HTML 留给 Word 临时转换。

report 包含 paragraphCount、tableCount、listCount、warnings。段落统计包括内核自动补充的必需空段落，listCount 是编号定义数，warnings 当前为空；不是页数/分页预检。HTML 包装继续返回它原来的计数和降级报告，不混用两种统计口径。

文档嵌套最多 128 层，内容节点最多 200000；这不是完整的解析内存上限。实际字体、换行和分页由阅读软件决定。需要修改已有文档或高保真渲染时，可继续使用 Office/WPS COM。
