--- name: pdf-reportlab description: 用 ReportLab 生成专业中文 PDF 研究报告。包含思源宋体/黑体+霞鹜文楷的字体注册、集中样式管理、封面/目录/正文/参考文献多页模板、matplotlib 图表嵌入。dr-reporter 用于 Phase 4 出 PDF 稿;也可被用户直接调用渲染单章。 --- # ReportLab 中文 PDF 模板使用指南 ## 一、为什么是 ReportLab - **完全可控**:每个字号、行距、缩进都是代码说了算,不像 CSS/LaTeX 会被引擎意外改变 - **中文字体一次搞定**:`pdfmetrics.registerFont` 注册后全局可用,子集嵌入 PDF,分发无忧 - **速度快**:纯 Python,30,000 字报告 3-5 秒出稿(matplotlib 图表预渲染后) - **图表质量高**:matplotlib 生成 300 DPI PNG 嵌入,比 LaTeX 的 pgfplots 快得多 - **样式集中**:用 `StyleSheet` 管理,避免你之前碰到的"中文字号不一"问题 --- ## 二、项目模板入口 模板脚本:`.opencode/templates/report-template.py` 调用方式: ```bash python3 .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 ``` --- ## 三、字体注册(模板已封装,此处仅说明原理) ```python from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont # 思源宋体 = 正文 pdfmetrics.registerFont(TTFont('SrcSerif', 'fonts/SourceHanSerifSC-Regular.otf')) pdfmetrics.registerFont(TTFont('SrcSerif-Bold', 'fonts/SourceHanSerifSC-Bold.otf')) pdfmetrics.registerFontFamily('SrcSerif', normal='SrcSerif', bold='SrcSerif-Bold') # 思源黑体 = 标题/UI pdfmetrics.registerFont(TTFont('SrcSans-Light', 'fonts/SourceHanSansSC-Light.otf')) pdfmetrics.registerFont(TTFont('SrcSans-Medium', 'fonts/SourceHanSansSC-Medium.otf')) pdfmetrics.registerFont(TTFont('SrcSans-Bold', 'fonts/SourceHanSansSC-Bold.otf')) pdfmetrics.registerFont(TTFont('SrcSans-Heavy', 'fonts/SourceHanSansSC-Heavy.otf')) # 霞鹜文楷 = 引文/摘要 pdfmetrics.registerFont(TTFont('Kai', 'fonts/LXGWWenKai-Regular.ttf')) ``` **关键**:`TTFont` 虽然类名含 "TT",但也接受 `.otf`(OpenType),别犹豫。 --- ## 四、样式表(集中管理,避免字号不一) 所有样式集中在模板的 `build_styles()` 函数: | 样式名 | 字体 | 字号 | 行高 | 用途 | |---|---|---|---|---| | `body` | SrcSerif | 10.5 | 18 | 正文 | | `body-bold` | SrcSerif-Bold | 10.5 | 18 | 术语 | | `h1` | SrcSans-Bold | 18 | 28 | 章标题 | | `h2` | SrcSans-Bold | 14 | 22 | section 标题 | | `h3` | SrcSans-Medium | 12 | 18 | sub-section | | `quote` | Kai | 10.5 | 18 | 引文、摘要 | | `caption` | SrcSans-Medium | 9 | 13 | 图表标题 | | `footnote` | SrcSerif | 9 | 13 | 脚注/参考文献 | | `header-footer` | SrcSans-Light | 8 | 12 | 页眉页脚 | | `cover-title` | SrcSans-Heavy | 32 | 42 | 封面大标题 | **行高 = 字号 × 1.5~1.7**,不要用默认值。 --- ## 五、报告 11 件套结构 模板会按以下顺序生成页面: 1. **封面页**(`PageTemplate: cover`) - 主标题:`cover-title` - 副标题:`h2` - 作者、日期:`body` - 单独版心,无页眉页脚 2. **免责声明**(`PageTemplate: normal`) - 固定模板,来源 manifest.json 的 `disclaimer` 字段 3. **执行摘要**(Executive Summary) - `quote` 样式,1-2 页 - 来源 final.md 的 `## 摘要` 段 4. **术语表** - 两列表格,术语+解释 - 来源 final.md 的 `## 术语表` 段 5. **目录** - 自动从 h1/h2 生成,支持超链接 6. **主体正文** - 来源 final.md 的各 `## 第 N 章 ...` 段 - 页眉:左=主题缩写 / 右=章节名 - 页脚:居中页码 7. **结论与建议** - final.md 的最后一章 8. **参考文献** - 来源 `projects//phase4/citations.bib` 或 `sources.jsonl` - 按引用顺序编号,GB/T 7714 格式 - `footnote` 样式 9. **附录 A:数据表**(可选) 10. **附录 B:方法论说明**(可选) 11. **版本信息** - 生成时间、版本号、生成者(dr-reporter)、字数统计 --- ## 六、图表嵌入规范 **不要用 ReportLab 原生绘图**,全部预渲染为 PNG: ```python # 在 dr-analyst / dr-reporter 阶段,用 matplotlib 出图 import matplotlib.pyplot as plt import matplotlib.font_manager as fm # 注册中文字体给 matplotlib 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=150) # ...绘图代码 plt.savefig('projects//phase4/figures/fig_01_market_size.png', dpi=300, bbox_inches='tight') ``` 然后在 final.md 里用标准 Markdown 引用: ```markdown ![图 1:2020-2025 GLP-1 市场规模](figures/fig_01_market_size.png) ``` 模板会自动: - 按 Markdown 解析图片 - 用 `caption` 样式渲染标题 - 图表居中,宽度适配页宽 --- ## 七、Markdown → ReportLab 的支持范围 模板支持以下 Markdown 元素: | Markdown | ReportLab 渲染 | |---|---| | `# 标题` | h1(章标题,自动分页) | | `## 标题` | h2(section,不分页) | | `### 标题` | h3(sub-section) | | `**粗体**` | `` inline | | `*斜体*` | `` inline | | `` `代码` `` | 等宽字体 inline | | `> 引文` | `quote` 样式块 | | `- 列表项` / `1. 项` | 项目符号列表 | | `表格`(\| \| \|) | ReportLab Table,自动列宽 | | `![caption](path)` | 图片 + caption | | `[src_001]` | 上标引用链接到参考文献 | | `---` | 分页符(`PageBreak`) | **不支持**(请在 Markdown 里避免): - HTML 标签(除少数 inline) - 数学公式(后续可加 matplotlib 渲染) - 代码块高亮(只保留等宽显示) --- ## 八、manifest.json 的必需字段 ```json { "slug": "glp1-obesity-2026", "topic": "GLP-1 减重药物竞争格局与投资机会", "subtitle": "2026 年产业深度研究", "author": "Deep Research 系统 v0.1", "date": "2026-04-20", "type": "研究类", "version": "1.0", "disclaimer": "本报告基于公开信息与 AI 辅助研究生成,仅供参考,不构成投资建议。", "cover_theme": "blue" } ``` --- ## 九、常见坑与对策 | 坑 | 对策 | |---|---| | 中文字号不一 | **集中 StyleSheet**,不在 Paragraph 里 inline 改 fontSize | | 行距太挤 | 行高 = 字号 × 1.5~1.7,不要用默认 | | 换行断错 | `wordWrap='CJK'` 必设 | | 字体子集缺字 | 用完整版思源字体(非 subset 精简版) | | 图片变形 | 先 matplotlib 出 300 DPI PNG,再 `Image(path, width=..., height=...)` | | 页眉页脚重叠 | 用 `BaseDocTemplate` + `PageTemplate`,`Frame` 的 margin 留足 | | 英文中文混排间距怪 | 思源系列自带 CJK metrics,间距会自适应,一般不用额外处理 | | 生成慢 | matplotlib 图表预渲染,不要在 PDF 生成阶段现算 | --- ## 十、调用流程(dr-reporter 阶段) ``` 1. 检查字体:ls .opencode/templates/fonts/*.otf | wc -l ≥ 6 2. 检查输入:projects//phase4/final.md 存在 3. 检查配置:projects//manifest.json 有必需字段 4. 执行: python3 .opencode/templates/report-template.py \ --input projects//phase4/final.md \ --manifest projects//manifest.json \ --output projects//phase4/final.pdf 5. 验证: - PDF 打得开 - 文件大小 > 500KB(太小说明字体没嵌) - 页数合理(30,000 字约 60-80 页) 6. 汇报:输出路径、页数、文件大小 ``` --- ## 十一、MVP 阶段注意 目前(MVP)`report-template.py` 是**基础版**,支持: - 思源字体注册 - 标题 / 正文 / 引文 / 表格 / 图片 - 简单封面 + 目录 - 参考文献自动编号 **暂未实现**(Phase 4 能力阶段补齐): - 自动书签/大纲(PDF navigation pane) - 交叉引用("见第 3 章"自动跳转) - 复杂页眉(左右对称排版) - 附录 B 自动生成(方法论模板) 如需上述功能,在 manifest.json 里标 `"template_features": ["bookmarks", "xref", ...]`,未来版本会处理。