# Typeset

> 把 Markdown 生成排版规范的中文 .docx（合同、协议、服务确认单、方案书、正式函件等）。凡是要交付一份中文 Word 文档——尤其是要盖章签字、要发给客户或对方法务的——都用这个 skill，即使用户只说"帮我写份合同""转成 Word""给我个 docx""做个协议"而没提排版。它解决的是 pandoc 默认输出拿去当中文正式文书会很难看的问题：青蓝色不加粗的标题、Letter 纸型、Aptos 西文字体、表格被分页劈成两半、签章区甲乙方分到两页。也用于修正已有中文 docx 的版式，或需要 A4 / 宋体 / 1.5 倍行距 / 页眉横线 / 页码 / 封面页 / 签章区这类中文公文版式要求时。

- Skill: `cerul-ai/typeset` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add cerul-ai/typeset`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cerul-ai/typeset/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- Author: cerul-ai (https://skillmd.com/u/cerul-ai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/cerul-ai/typeset

---


# 中文正式文书的 .docx 生成

## 这个 skill 解决什么

`pandoc x.md -o x.docx` 能跑通，但产物是给英文博客用的：标题 `#0F4761` 青蓝色、20/16/14pt **且不加粗**，Letter 纸型，西文 Aptos。拿去做中文合同，对方法务一眼就觉得不正式。

更麻烦的是三类只有"看了才知道"的问题：表格跨页被劈成两半、条款标题孤零零留在页底而正文在下一页、签章区甲方在上一页乙方在下一页。这些在生成时不报错，在 Word 里打开才看得见。

所以这个 skill 的核心不只是参数，还有**一定要把每页渲染成图片看一眼**的工作流。

## 工作流

```bash
# 1. 写 markdown（模板见 templates/contract.md）
# 2. 生成
python3 scripts/build.py 合同.md
# 3. 检查：结构 lint + 转 PDF + 逐页渲染
python3 scripts/verify.py 合同.docx --render
# 4. 真的去看渲染出来的 jpg —— 至少看封面、表格页、签章页
```

第 4 步不能省。前三步全部通过、文档在 Word 里能打开，版面依然可能很难看。看图发现问题后回到 markdown 或 `build.py` 改，重跑。

`verify.py` 的版面体检会标出"偏空的页"，那通常意味着有东西被强行推到了下一页——可能是对的（附件另起页），也可能是表格放不下。看图确认。

### 两个渲染器，各验各的

**LibreOffice 匹配不到「宋体 / 黑体」这类中文字体名**（即使系统里装着），会回退到 Arial Unicode MS 之类。替换字的度量不同，连**页数都会显著变化**——同一份合同实测 Word 出 17 页、LibreOffice 出 27 页，差 59%。

所以不能只用一个渲染器：

| 渲染器 | 解析字体 | 适合验什么 | 不适合 |
| --- | --- | --- | --- |
| **LibreOffice** | 回退（Arial Unicode MS） | 结构机制：表格行有没有被劈开、签章区有没有散、内容有无丢失、XML 有无问题 | **页数、分页位置、留白节奏** |
| **Microsoft Word** | 正确（SimSun / SimHei） | 最终版面与字体外观 | 自动化——GUI 应用，弹对话框就卡住 |

Word 之所以权威，是因为它**自带 SimSun / SimHei**，和对方 Windows Word 看到的一致。

实用节奏：**改稿循环用 LibreOffice**（快、无干扰、能抓结构错误），**定稿前在 Word 里开一次**确认真实分页与字体。

`verify.py` 会自动检查字体有没有被替换，并据此告诉你这次的渲染图能信到什么程度。

## markdown 怎么写

YAML frontmatter 提供封面信息：

```markdown
---
title: 技术服务合同
party_a: 【甲方全称】
party_b: 【乙方全称】
party_a_label: 甲方（客户）
party_b_label: 乙方（服务方）
style: A
---

@@COVER@@

## 第 1 条 定义

1.1 **服务**：……

@@SIGNATURE@@
```

三个占位符：

| 占位符 | 作用 |
| --- | --- |
| `@@COVER@@` | 封面（自动分页到正文） |
| `@@SIGNATURE@@` | 签章区，可出现多次（正文一处、附件一处） |
| `@@PAGEBREAK@@` | 手动分页，用于让长表格独占一页 |

## 四套封面与签章方案

用 `style:` 或 `--style` 选。差别只在封面、签章区、页眉横线，正文版式相同。

| 方案 | 封面 | 签章区 | 页眉横线 |
| --- | --- | --- | --- |
| **A** 复刻参考版 | 编号靠右 / 甲乙左对齐 / 日期下沉 | 竖排，仅标签留白供盖章 | 有 |
| **B** 严格对齐版 | 无框表格做标签-值两列 | 无框表格甲乙并排 | 有 |
| **C** 公文庄重版 | 信息块加外框，标题 22pt | 竖排 + 独立盖章区提示 | 有 |
| **D** 现代简洁版 | 左对齐块 + 细线包夹 | 竖排 + 行内（盖章）标注 | 无 |

拿不准就用 A。它最像国内公司常见的合同范式，对方法务看着眼熟、审得快。

`--all` 一次出四份，把封面页渲染出来给人挑。

## 中文合同的几个惯例

这些不是排版细节，是**不写就显得外行**的东西：

- **封面靠不同对齐区分信息组**：合同编号靠右、缔约方靠左且互相对齐、签订日期单独下沉。全部居中会把这种区分抹平。
- **签章区在正文之后另起，竖排堆叠**（甲方组、乙方组上下分开），不是并排表格——盖章需要留白。
- **`（本行以下无正文，仅供签章之用）`** 这句要有，防止在空白处补写内容。
- **占位符用 `【】`**，不用 `[]` 或下划线。待议的数字也用 `【10】`。
- **页脚要有"第 X 页 / 共 Y 页"**，防抽换页。`build.py` 用 PAGE/NUMPAGES 域自动生成。

更多见 `references/contract-zh.md`。

## 排版参数

正文宋体 12pt、1.5 倍行距；标题黑体纯黑加粗 16/14/12pt；A4，上下 2.54cm、左右 3.00cm。

完整参数与取值依据见 `references/house-style.md`。要改整体风格（比如换仿宋、换字号）就改 `scripts/build.py` 顶部的常量块，那里集中了全部度量。

## 分页控制

Word 里控制分页的四个开关，`build.py` 已经预置，写 markdown 时按需要用：

| 场景 | 机制 | 怎么用 |
| --- | --- | --- |
| 表格行被劈成两半 | `cantSplit` | 自动，所有表格已开 |
| 单行孤行落在页首页尾 | `widowControl` | 自动，全局已开 |
| 引导句留在页底、列表在下一页 | `keepNext` | 自动（列表前一段自动套 LeadIn） |
| 某条款不能被劈开 | `keepLines` | 手动套 `::: {custom-style="Together"}` |
| 长表格要独占一页 | 分页符 | 表格前加 `@@PAGEBREAK@@` |

价格、赔偿上限、责任范围这类条款建议套 `Together`——被分页切成两半会显得很不专业，也容易在传阅中被误读。

```markdown
::: {custom-style="Together"}
**本条单价适用条件**：单次充值不低于人民币【　　】元……
:::
```

## 表格列宽

pandoc 的管道表列宽由**分隔行的连字符数量**决定，不是内容。默认全等宽，长内容列会挤成多行而短列一片空白：

```markdown
| 里程碑 | 时间 | 内容 | 付款 |
| ---------- | --------- | -------------------------- | -------- |
```

按各列实际内容量分配连字符。中文全角字在 10.5pt 下约 210 twips，正文宽 8504 twips——照这个估每列需要多少。

## 两个 pandoc 陷阱

**`- (a) 文字` 会变成空项目符号 + 嵌套列表。** pandoc 的 `fancy_lists` 把 `(a)` 当成有序列表标记，于是外层项目符号是空的、真正的内容缩进两层。`build.py` 会自动把这类行转成转义括号 + 悬挂缩进样式。手写 OpenXML 时要记得转义成 `\(a\)`。

**智能引号会把中文引号变成两个右引号。** `"通道存在"` 经 pandoc 的 smart 扩展后可能渲染成 `”通道存在”`——开引号也是右引号。直接在 markdown 里写中文引号 `“ ”` 最省事。

## OpenXML 的硬约束

如果要手写或修改 OpenXML（而不只是调 `build.py` 的常量），先读 `references/openxml-gotchas.md`。最容易踩的是：

**`<w:pPr>` 和 `<w:style>` 的子元素顺序由 XSD 强制。** 顺序写错，Word 打开会报"文档已损坏"，而且报错信息完全不提元素顺序，极难 debug。`build.py` 的 `ppr()` 按正确顺序拼装，`verify.py` 的 lint 会检查产物。

常见顺序错误：`spacing` 放在了 `keepNext` 前面；`tblPr` 放在了 `pPr` 前面；`jc` 放在了 `ind` 前面。

## 依赖

```bash
brew install pandoc poppler
brew install --cask libreoffice     # 渲染检查用，强烈建议装
```

`pandoc` 必需。`poppler` 提供 `pdftotext` / `pdftoppm`，做版面体检和渲染。

**自动化转换首选 LibreOffice**——headless、不抢屏、不会卡。`verify.py` 没找到它时会回退到调用 Microsoft Word（macOS，AppleScript），那条路能出正确字体但不可靠：Word 是 GUI 应用，弹任何对话框都会把转换卡死，脚本既看不见也关不掉。

两个都装最好：LibreOffice 跑循环，Word 做定稿确认（见上文"两个渲染器"）。

## 参考文件

- `references/house-style.md` — 完整排版参数、取值依据、四套方案的设计说明
- `references/openxml-gotchas.md` — XSD 元素顺序、分页控制、页眉页脚注入、字体主题
- `references/contract-zh.md` — 中文合同的结构与惯例（封面、条款编号、签章、附件）
- `templates/contract.md` — 可直接改的合同骨架

