# Cn PDF Report Typeset

> 中文报告做成 PDF，常见翻车三件套：**字体乱码、表格错位、字号小到客户在手机上根本看不清**。这套是交付级的排版标准加可直接跑的模板。 交付标准（按「客户在手机上看」倒推）： - **大字号**：正文 ≥16pt、表格 ≥15pt、H2 ≥17pt、大标题 ≥30pt——不是 12pt 那种印出来都费劲的 - **紧凑不注水**：只在封面后分页一次，正文自然流动，禁止每章强制分页撑页数 - **表格智能换行**：单元格用 Paragraph 加中文断行，长内容不溢出页面 - **页面均衡**：生成后自查每页字符数，避免一页只有两行 - A4 竖排、边距 18-20mm、直接打印可用 附：微软雅黑系统字体直接调用（不用下载字体文件）、封面/页脚页码/核心结论框模板、**排版自检脚本**（自动查字号是否达标、有没有多余分页）。 适用：可行性研究报告、尽调报告、项目分析、任何要交付给客户的中文正式文档。纯本地 Python，零 API Key。 触发词：做成PDF、生成PDF、中文PDF、报告排版、雅黑、乱码、表格溢出、手机看不清、可研报告、交付文档。

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

---

# 中文专业PDF报告生成

## 🚨 用户两次纠正的血泪教训（2026-08-20，最高优先级，禁止再犯）
1. **紧凑排版**：严禁"一点点内容就一页"。禁止每个章节加 PageBreak 撑页数——只在封面后加一个 PageBreak，正文让它自然流动。之前6页的内容压到4页才对。
2. **大字号（手机可读，客户在手机看）**：正文≥16pt、表格≥15pt、H2≥17pt、H1≥21pt、标题≥30pt。14pt正文/12pt表格太小，客户手机看不清，用户会当场批评。
3. 这两条在 memory 排版标准里也写着，但执行时仍会偷懒用 14pt/12pt + 每章分页——**生成脚本前必须自查字号常量和 PageBreak 数量**。

## ⭐ 推荐排版标准（PDF/Word 通用）

**这套排版逻辑是标准**，以后所有 PDF 和 Word 文档都参照执行：
1. **A4竖排**（210×297mm）——不用横排/其他尺寸
2. **大字号，手机可读**：正文≥14pt、表格≥12.5pt、H1≥20pt、标题≥32pt
3. **表格智能换行不溢出**：单元格用 Paragraph + wordWrap="CJK"
4. **页面内容均衡**：不用强制分页，KeepTogether 智能分块，生成后检查每页字符数（除封面/收尾页外，各页应均衡）
5. **排版紧凑**：边距 18-20mm、表格 padding 3-4、行距紧凑，直接打印可用

Word 文档同逻辑：A4竖排、正文≥小四/四号（12-14pt）、表格列宽自适应不溢出、页面均衡。

## 触发条件
- 用户/客户要求"把结论做成PDF"、"发PDF上来"、"可研给我"、"结论做出来发我"
- 需要交付专业排版的中文分析报告（封面 + 表格 + 页脚 + 核心结论框）
- 参考实例：某制造业企业尽调报告（5页）

## 环境与安装
- 用 hermes venv 解释器：`python`
- 首次安装：`$V -m pip install reportlab`（本机已装 5.0.0）
- 字体：Windows 系统自带微软雅黑，**无需下载字体文件**
  - `C:/Windows/Fonts/msyh.ttc`（常规，subfontIndex=0）
  - `C:/Windows/Fonts/msyhbd.ttc`（粗体）
  - `C:/Windows/Fonts/msyhl.ttc`（细体）

## 标准工作流
0. **⚠️ 交付铁律（2026-09-02 用户明示，最高优先级）**：PDF 生成后必须 `MEDIA:` 发到飞书聊天框，绝不能只存知识库 / 只给路径 / 只发要点。用户原话："PDF文档你要发到飞书聊天框这里来，要不然我怎么看到呢？就是要形成铁律一样的东西。"
   - 用户在手机端看，点飞书消息里的文件才看得到；存进 `02-项目成果/` 只是备份，**不是交付**。
   - 交付格式：`<你的输出目录>/<文件名>.pdf`（绝对路径）。
   - 发完 PDF 附核心结论要点（30秒版）。先发飞书，再存知识库复盘。
1. **取内容**：session_search 查历史分析——用户常对附件回"结论做成PDF"，先找回之前出过的研判/尽调内容，不要重写
2. **写脚本**：复制 `templates/reportlab_chinese_template.py`，只替换内容区（封面文字、章节、表格数据）
3. **生成**：python 运行 → 输出到 Obsidian `Hermes工作成果/02-项目成果/`（命名：<主题>项目可行性尽调与分析报告.pdf）
4. **验证**：pymupdf 打开读 get_text，确认页数 / 中文渲染 / 末页结论存在
5. **交付**：回复 `MEDIA:D:\...pdf` 路径 + 核心结论要点（30秒版），不啰嗦

## ⭐ 推荐配色（金色系，最高优先级）

**默认采用金土系配色：避开水（蓝/黑）与火（红/紫）色调。所有 PDF 一律用金土系配色，禁止再用蓝色。颜色变量见下方 palette。**

- **章节大标题 H1**：金属金底 + 白字（`backColor=#C9A227`，`textColor=white`，`borderPadding=(6,8,6,8)`）——金底白字，不是蓝色文字。
- **表格表头**：金属金底 + 白字（`header_bg=#C9A227`，`FONTNAME=MSYHBD`）——金底白字。
- **正文**：纯黑 `#1f1f1f`。
- **副题 / 分隔线**：深土金 `#B8860B`（去红色）。
- **代码块**：土金 `#8a6d1f`（去蓝色）。
- **斑马纹**：浅米金 `#FBF3DE`（暖调，不是灰白）。
- **网格线**：浅土金 `#D9C9A3`。

### 金色系 palette（reportlab 用）
```python
C_DARK=colors.HexColor("#1f1f1f")   # 正文近黑
C_RED=colors.HexColor("#B8860B")    # 强调/副题-深土金
C_BLUE=colors.HexColor("#C9A227")   # 表头/标题底-金属金
C_LGRAY=colors.HexColor("#FBF3DE")  # 斑马纹-浅米金
C_MGRAY=colors.HexColor("#D9C9A3")  # 网格线-浅土金
C_WHITE=colors.white
```
> 行内强调 `S_CODE=st(..., textColor=colors.HexColor("#8a6d1f"))`；行内 `<span color='#B8860B'>`（不用 `#c0392b` 红色）。

## 排版要点（模板已封装）
- 封面页：大标题 + 金色副题 + 日期/来源 + 核心结论框（深土金底白字表）
- 正文：H1 章节（金属金底白字 MSYHBD 21pt）+ H2 小节（17pt）+ 正文 **15pt**（2026-08-18 用户指示：**手机端可读，字号必须大**）
- 表格：`make_table()` 表头金属金底白字 + 斑马纹（ROWBACKGROUNDS）+ grid + repeatRows=1，**表格字号 13pt**
- 页脚：onPage 回调画报告名 + 页码
- 强调：Paragraph 支持 `<b>` / `<span color='#B8860B'>` 行内标记
- 长报告分章节：PageBreak 分隔封面与正文

**⚠️ 字号铁律（2026-08-18 用户强调）**：PDF 是给手机看的，**正文≥15pt、表格≥13pt、H1≥21pt、标题≥34pt**。原模板 10.5pt/9.3pt 太小，手机看不清——**所有新报告必须用大字号**。字号大导致页数变多是正常的（如5页→8页）。

## ⭐ 智能排版铁律（2026-08-18 用户强调，打印友好）**页面内容必须均衡**——不能某页只有200字符、某页挤800字符。方法：
1. **少用 PageBreak**：只在封面后强制分页，正文让它自然流动
2. **用 KeepTogether 包裹"标题+内容"**：`keep(H2标题, 表格/段落)` 防止表格被拆到两页、防止标题孤悬页底
3. **封面独立一页**：封面元素 + 一个 PageBreak
4. **每章用小节自然分隔**：H2标题 + 表格/要点，不强制每章换页
5. **验证均衡度**：生成后用 pymupdf 检查每页字符数——正常报告除封面/收尾页外，各页字符数应接近（如600-900字符），差异过大就要调整
6. **排版紧凑**：边距 20mm/16mm（不要过大），表格 padding 4-5，行距 leading=22（14pt正文），减少空白浪费

## 坑（PITFALLS）
- **⛔ 禁止两端对齐（TA_JUSTIFY）——字间距被拉宽（2026-09-03 用户最在意的手机可读问题）**：reportlab 的 `TA_JUSTIFY` 会对中文段落**强制把每行拉伸到右边界**，某行字少时字间距被撑得极宽、极难看。**所有正文/列表/段落一律用 `TA_LEFT` 左对齐**。
  - 错误：`b=dict(..., alignment=TA_JUSTIFY, ...)`
  - 正确：`b=dict(..., alignment=TA_LEFT, ...)`
  - 排查：正文/列表样式若继承 `st()` 且 `alignment=TA_JUSTIFY`，必改左对齐。
- **⛔ 封面内容溢出成空白孤页（2026-09-03）**：封面元素（主标题+副题+说明+核心一句话）必须全部压在第1页内，**绝不能让封面内容被推到第2页形成"一小段字+大片空白"**。封面内容过多时删掉大段引言、压缩列表间距，让它在1页内放完。每章之间只保留封面后一个 PageBreak，正文自然流动。
- **⛔ 字号/边距要同频调整**：放大字号后，边距要同步收窄（本会话 22mm→17mm）才能让每页装更多、不产生孤页。字号 17pt 正文 + 17mm 边距是实测平衡点；表格字号 14.5pt。
- **⛔ KeepTogether 包"自定义对齐小表"会导致页高误判→孤页（2026-09-06 实测）**：用 KeepTogether 包裹"标题+正文+自定义两列对齐表(field_table)"+价格时，reportlab 对该块的**高度估算不准确**，常把整块推到下一页，造成上一页只剩"提示句/表尾一行"变成几十字符的孤页（本会话第2页只有76字符）。**修复**：① 首屏"服务一览表+引言"用 KeepTogether 包整个 head_block 并**压缩表格字号/行距**确保能在第1页放完；② 自建段渲染后用 pymupdf 逐页看字符数，**<120字符的页就是孤页**，立即压缩前页表格/删多余提示句。
  - **区分（2026-09-10 实测）：只包"表格本身"是准且该做的；包"标题+正文+表+价格"大块才容易误判。** 让表格工厂函数统一 `return KeepTogether(t)`（表格整体不跨页），配合**只保留封面后一个 PageBreak**，实测把 7 页（第5页仅 77 字）修成 6 页、每页 301/451/548/407/551/240 字。**表格被分页切断、切出的 1–2 行独占一页，是孤页的头号成因**；发现"某页只有表头/末行"先查这个。
- **⛔ 两列"标签+内容"对齐表的标签列不能太窄——中文标签会折行成孤字（2026-09-06）**：用两列对齐表做"能做什么/做到多深/您能得到什么"时，标签列若太窄（如 32mm），"您能得到什么"会折成"您能得到什"+"么"，孤字极难看。**修复**：标签列宽度要 > 最长标签单行放置（40mm 左右），或把标签改短（"您能得到什么"→"带来什么"）。
- **⛔ 长句末尾的"。"会单独折行成孤儿标点（2026-09-06）**："做到多深/带来什么"这类长字段，整句末尾的"。"在换行时被甩到下一行开头。**修复**：字段值较长、有折行风险时**去掉末尾句号**（或用规避短句），宁可不要句号也不留孤点。
- **⛔ "定制技能"要写对口径（2026-09-06 用户纠正）**：不要把"定制/定做skills"误写成"定时任务"。客户版统一用"专属技能定制"并口语化解释"技能=一件AI能照着干的具体本事"（见 enterprise-ai-service-blueprint）。
- `.ttc` 字体必须 `subfontIndex=0`，否则报错或乱码
- 加粗必须注册 MSYHBD 并设置 fontName——`<b>` 标签在无粗体字体注册时静默不加粗
- **`<` `>` 的转义要分两种情况（2026-09-10 实测踩坑）**：正文里的**裸尖括号**（如"精度<10mm"）才转义成 `&lt;`/`&gt;`；而 `<b>`、`<span color=...>` 是**内联标记**，必须原样传给 Paragraph 才生效。
  - ⛔ 致命写法：单元格文本一律 `.replace('<','&lt;')` 全转义 → 表格里的加粗会**原样显示成 `<b>字样`**（本会话第2页整列都印成 `<b>点名支持…`，交付前才发现）。
  - ✅ 正确：表格单元格**只转义裸 `&`**（`v.replace('&','&amp;')`），保留内联标签；内容里的裸尖括号由调用方自己写 `&lt;`。
  - 自检：交付前抓全文，`'<b>' in text` 或 `'</b>' in text` 为真即说明标签被当文字渲染了，必须回查 make_table 的转义。
- 表格列宽总和 ≤ A4 可用宽度（210mm - 左右边距），否则溢出换行难看
- **表格中文溢出（2026-08-18 重要修复）**：reportlab 的 Table 直接放长中文**不会自动换行**，会溢出到右边！**必须用 Paragraph 包裹单元格** + `wordWrap="CJK"`。已在 make_table 内置 `_cell_style()` 处理——所有单元格自动换行。**验证**：用 pymupdf 查 `get_text('words')` 的 x2 是否越过内容区右边界。
  - **阈值必须按实际边距算，不能写死 540pt**（2026-09-10 实测）：右边界 = `A4[0] - rightMargin`（A4 宽 595.3pt；16mm 边距 → 549.9pt，17mm → 547pt）。写死 540 会把**正常换行**误报成溢出，白折腾。
  - **必须排除页脚**：onPage 画的"第 N 页"永远超出内容区，判定时要按文本过滤掉（如 `w[4] != '页'`），否则每次都有假警报。
  - 判定示例：`[w for w in page.get_text('words') if w[2] > page.rect.width - rightMargin + 1 and w[4] != '页']`
- **A4竖排铁律（用户强调）**：所有PDF必须 A4 竖排（210×297mm），pagesize=A4 默认就是竖排，不要改 landscape
- 交付前必须用 pymupdf 验证一次（页数/中文/乱码/**无溢出**）——验证通过才发
- **⛔ 写生成脚本时，Python 字符串一律用单引号定界（2026-09-10 实测）**：工具链会把中文引号 “ ” 规范化成半角 `"`，字符串外层若用双引号定界，正文里的引号会**提前闭合字面量** → 报 `SyntaxError: invalid character '＋' (U+FF0B)` 之类（报错点看着像全角字符非法，真因是引号截断，会误导排查方向）。正文里的中文引号要么改用 `「」`，要么整条字符串用 `'...'` 包。
- **⛔ 绝不要"边写边读"同一个文件**：`open(f,'w').write(open(f,encoding='utf-8').read() + add)` 会先把文件**截断成 0 字节**再读，结果是原文全丢、只剩追加段（本会话知识库笔记从 13KB 缩成 3.8KB，差点交了残档）。追加内容时先 `txt = open(f).read()` 存变量，再 `open(f,'w').write(txt + add)`；或直接用 patch 工具改。
- 文件 >100KB 通常正常（嵌入字体子集）

## 验证命令
```bash
$V -c "import pymupdf; d=pymupdf.open(r'D:\...\报告.pdf'); print(len(d)); print(d[0].get_text()[:120]); print('末页含结论:', '结论' in d[-1].get_text())"
```

## 关联
- 内容框架：`feasibility-study-writing`、`dual-lens-business-analysis`（双光研判出报告前先加载）
- 报告审查铁律：严谨报告出稿前执行 `report-reflection-review`
- PPT 场景：`ultimate-ppt-master`
- 模板：`templates/reportlab_chinese_template.py`（旧）；<b>`templates/reportlab_gold_template.py`（推荐）</b>：金色系 + 左对齐 + 17pt大字 + 17mm紧凑边距 + 无孤页。已修 `make_table()` 内联标签处理（表格单元格内 `<b>`/`<span>` 现在真正生效，不再被转义成字面文字），并内置生成后自检注释。

---

## 🙋 关于作者

**九品锦锂e** ｜ 把踩过的坑封装成"拿来就能跑"的 skill，不写教科书。这个 skill 是我自己每天在用的版本。

**微信：ly5419495**（加时备注「SkillHub」，我优先通过）
**公众号：初五Agent**（微信搜一搜，复盘和方法都写在那儿，不加微信也能读）

我另外做的几个能直接跑的工具，都放在这个货架页（复制到浏览器打开）：
https://skillpay.alipay.com/public/jiupinjinlie

用的时候卡住了、或者有别的场景想让我封装成 skill，按上面任意方式找我就行。

![九品锦锂e 微信二维码](https://jinli-vault-1372591613.cos.ap-guangzhou.myqcloud.com/skillhub/hook-wechat-jiupinjinlie.png)

