--- name: pdf-reportlab description: 用 ReportLab 生成专业中文 PDF 研究报告。集中样式管理、紧凑分页(widows/orphans/keepWithNext)、颜色层次、封面保密标识、页眉页脚简洁化。基于 9MW1911 综合战略报告的编排参考。 --- # ReportLab 中文 PDF 模板使用指南(v0.5) ## 一、为什么是 ReportLab - **完全可控**:每个字号、行距、颜色由代码说了算,不被引擎意外改动 - **中文字体一次搞定**:`pdfmetrics.registerFont` 注册后全局可用,子集嵌入 PDF,无授权问题 - **分页规则精确**:支持 widows/orphans、keepWithNext、splitByRow 等避免孤行/寡行 - **图表嵌入高质量**:matplotlib 300 DPI PNG,速度快 - **样式集中在 StyleSheet**:避免字号不一 --- ## 二、模板入口 脚本:`.opencode/templates/report-template.py` 调用: ```bash uv run python .opencode/templates/report-template.py \ --input projects//phase4/final.md \ --manifest projects//manifest.json \ --output projects//phase4/final.pdf \ --fonts-dir .opencode/templates/fonts ``` 首次使用前: ```bash bash .opencode/templates/fonts/download-fonts.sh ``` --- ## 三、字体注册(7 个字重) ``` SrcSerif-Regular 思源宋体 Regular 正文 SrcSerif-Bold 思源宋体 Bold 正文粗体 SrcSans-Light 思源黑体 Light 页眉页脚 SrcSans-Medium 思源黑体 Medium 三级标题/图表 SrcSans-Bold 思源黑体 Bold 一二级标题 SrcSans-Heavy 思源黑体 Heavy 封面大标题 Kai 霞鹜文楷 Regular 摘要/引文 ``` --- ## 四、样式表(StyleSheet,集中管理) | 样式名 | 字体 | 字号 | 行高 | 颜色 | 备注 | |---|---|---|---|---|---| | `body` | SrcSerif | 10.5 | 18 | `#1a1a1a` | 正文,首行缩进 21pt | | `body-bold` | SrcSerif-Bold | 10.5 | 18 | `#1a1a1a` | 行内加粗 | | `h1` | SrcSans-Bold | 18 | 28 | `#1e3a8a` | 章标题,前强制分页 | | `h2` | SrcSans-Bold | 14 | 22 | `#2c5282` | 节标题 | | `h3` | SrcSans-Medium | 12 | 18 | `#374151` | 小节标题 | | `quote` | Kai | 10.5 | 18 | `#4b5563` | 引文 | | `caption` | SrcSans-Medium | 9 | 13 | `#6b7280` | 图表标题 | | `footnote` | SrcSerif | 9 | 13 | `#374151` | 脚注/参考文献 | | `header-footer` | SrcSans-Light | 8 | 12 | `#9ca3af` | 页眉页脚 | | `cover-title` | SrcSans-Heavy | 28 | 40 | `#0f172a` | 封面主标题 | | `cover-subtitle` | SrcSans-Medium | 15 | 24 | `#475569` | 封面副标题 | | `cover-confidential` | SrcSans-Bold | 11 | 16 | `#dc2626` | 封面保密标识(红色)| | `cover-meta` | SrcSerif | 11 | 18 | `#334155` | 封面元信息 | | `summary` | SrcSerif | 11 | 20 | `#1a1a1a` | 执行摘要 | --- ## 五、分页规则(关键升级点) ### 章(h1) - `pageBreakBefore=1`(每章新起一页) - `keepWithNext=1`(标题和下一段不分离) ### 节(h2) - **禁止单独触发分页** - `keepWithNext=1`(标题紧跟内容) ### 小节(h3) - 同 h2,`keepWithNext=1` ### 段落 - `widows=2, orphans=2`(避免寡行/孤行) - 每段之间 `spaceBefore=6, spaceAfter=6` ### 表格 - `TableStyle` 中启用 `splitByRow=True`(长表格按行分页) - `repeatRows=1`(表头在分页后重复) - 单元格 padding 统一 4-6pt ### 摘要/术语表/目录/参考文献 - 每个独占起始页(用 `PageBreak`) --- ## 六、封面模板(参考 9MW1911) 封面独立一页,无页眉页脚,布局: ``` (上 30% 空白) 主标题(cover-title,黑色,居中) 副标题(cover-subtitle,深灰,居中) (中部 40% 空白) [机密 | 仅供 XX 内部决策使用] (cover-confidential,红色,居中) (下部 20%) 类型:研究类 作者:Deep Research 系统 / <公司名> 编制日期:YYYY 年 M 月 版本:v1.0 ``` 字段来源: - `manifest.report_title` / `report_subtitle` - `manifest.confidentiality` (如 "机密 | 仅供迈威生物内部决策使用") - `manifest.author` / `manifest.date` / `manifest.version` --- ## 七、页眉页脚(简洁化) ### 页眉 左边:报告简称(从 `manifest.report_title` 取前 15 字) 右边:章节名(动态,从当前 h1 内容取) 底线:`#e5e7eb` 浅灰分隔线 ### 页脚 居中:页码(格式 `— X —`) 字号:8pt,`#9ca3af` 浅灰 ### 特殊页 - 封面:无页眉页脚 - 免责声明:无页眉,仅页脚 - 其他(摘要/术语表/目录/正文/附录/参考文献):有页眉页脚 --- ## 八、报告结构(11 件套) ReportLab 按以下顺序组装: 1. **封面**(cover PageTemplate) 2. **免责声明**(normal PageTemplate,单页) 3. **Executive Summary / 执行摘要**(summary PageTemplate,1-2 页) 4. **Abstract / 摘要**(normal,单页) 5. **Glossary / 术语表**(normal) 6. **Table of Contents / 目录**(TOC,自动生成) 7. **正文各章**(normal,每章 h1 强制分页) 8. **结论与建议**(正文的一部分) 9. **附录**(normal) 10. **参考文献**(bibliography PageTemplate,footnote 样式) 11. **版本信息**(normal,单页) --- ## 九、Markdown 支持范围 | Markdown | ReportLab 渲染 | |---|---| | `# 第 X 章 ...` | h1(新起一页,深蓝色)| | `## X.Y ...` | h2(节,蓝色,不分页)| | `### X.Y.Z ...` | h3(小节,深灰)| | `**粗体**` | inline `` | | `*斜体*` | inline `` | | `` `代码` `` | 等宽字体 | | `> 引文` | quote 样式(楷体,浅底色)| | `- 项` / `1. 项` | 项目符号列表 | | 表格 `\| \| \|` | Table,自动列宽 + splitByRow | | `![caption](path)` | 图片 + caption 样式 | | `[src_001]` | 上标引用 | | `---` | 分页符 | **不支持**:HTML 标签、数学公式、代码块高亮。 --- ## 十、图表嵌入 不用 ReportLab 原生绘图,全部预渲染为 PNG: ```python import matplotlib.pyplot as plt import matplotlib.font_manager as fm font_path = '.opencode/templates/fonts/SourceHanSansSC-Medium.otf' fm.fontManager.addfont(font_path) plt.rcParams['font.family'] = 'Source Han Sans SC' plt.rcParams['axes.unicode_minus'] = False fig, ax = plt.subplots(figsize=(6, 4), dpi=300) # ... plt.savefig('projects//phase4/figures/fig_01_XXX.png', dpi=300, bbox_inches='tight') ``` Markdown 中引用:`![图 1-1:2020-2025 GLP-1 市场规模(数据来源:[src_042])](figures/fig_01_XXX.png)` --- ## 十一、manifest 必需字段 ```json { "slug": "...", "report_title": "自研 O-糖苷酶立项可行性研究报告", "report_subtitle": "对标 NEB 与 Merck 经典产品的技术路径、IP 壁垒与差异化战略", "confidentiality": "机密 | 仅供内部决策使用", "author": "Deep Research 系统", "date": "2026-04-21", "version": "1.0", "type": "研究类", "disclaimer": "本报告基于公开信息与 AI 辅助研究生成,仅供参考..." } ``` --- ## 十二、常见坑与对策 | 坑 | 对策 | |---|---| | 中文字号不一 | 集中 StyleSheet,不在 Paragraph 里 inline 改 fontSize | | 行距太挤 | 行高 = 字号 × 1.5~1.7 | | 换行断错 | `wordWrap='CJK'` 必设 | | 表格被截断 | `splitByRow=True, repeatRows=1` | | 标题孤行(页末一个标题后直接新页)| `keepWithNext=1` | | 段落寡行(末段只剩一行在下页)| `widows=2, orphans=2` | | 图片变形 | 先 matplotlib 出 300 DPI PNG,再 `Image(path, width=..., kind="proportional")` | | 生成慢 | matplotlib 图表预渲染,不要在 PDF 生成阶段现算 | | 参考文献丢失 | dr-reporter 出稿前检查:读 citations.md,确认内容已写入 final.md | --- ## 十三、dr-reporter 调用流程 ``` 1. 环境检查 - 字体 ≥6 个 OTF 文件 - final.md 存在 - manifest.json 有必需字段 2. 回填参考文献(关键步骤,修复 v0.4 的 bug) - 读 citations.md 内容 - 在 final.md 中找到"## 参考文献"段落 - 如果段落内容是占位符(如 "[由 dr-reporter 自动生成]" 或为空) → 替换为 citations.md 的完整内容 - 写回 final.md 3. 图表检查 - 扫描 final.md 中所有 ![...](path) 图片引用 - 验证每个 path 在 figures/ 目录下存在 - 缺失图片 → 警告并继续,但汇报中注明 4. 生成 PDF uv run python .opencode/templates/report-template.py \ --input projects//phase4/final.md \ --manifest projects//manifest.json \ --output projects//phase4/final.pdf \ --fonts-dir .opencode/templates/fonts 5. 生成 DOCX(可选) pandoc projects//phase4/final.md \ --from markdown --to docx \ --output projects//phase4/final.docx \ --toc --toc-depth=3 6. 验证 - PDF 文件大小 > 500KB(太小说明字体没嵌) - PDF 页数合理(按字数估算) - "参考文献"章节存在且非空 7. 汇报 ``` --- ## 十四、输出卫生检查(必跑) 生成 PDF 前: ```bash # 加载 skill:output-hygiene python3 .opencode/templates/hygiene_check.py projects//phase4/final.md ``` 禁止词残留 → 抛回 dr-polisher 再润色一轮。