256 lines
8.2 KiB
Markdown
256 lines
8.2 KiB
Markdown
---
|
||
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/<slug>/phase4/final.md \
|
||
--manifest projects/<slug>/manifest.json \
|
||
--output projects/<slug>/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/<slug>/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/<slug>/phase4/figures/fig_01_market_size.png', dpi=300, bbox_inches='tight')
|
||
```
|
||
|
||
然后在 final.md 里用标准 Markdown 引用:
|
||
```markdown
|
||

|
||
```
|
||
|
||
模板会自动:
|
||
- 按 Markdown 解析图片
|
||
- 用 `caption` 样式渲染标题
|
||
- 图表居中,宽度适配页宽
|
||
|
||
---
|
||
|
||
## 七、Markdown → ReportLab 的支持范围
|
||
|
||
模板支持以下 Markdown 元素:
|
||
|
||
| Markdown | ReportLab 渲染 |
|
||
|---|---|
|
||
| `# 标题` | h1(章标题,自动分页) |
|
||
| `## 标题` | h2(section,不分页) |
|
||
| `### 标题` | h3(sub-section) |
|
||
| `**粗体**` | `<b>` inline |
|
||
| `*斜体*` | `<i>` inline |
|
||
| `` `代码` `` | 等宽字体 inline |
|
||
| `> 引文` | `quote` 样式块 |
|
||
| `- 列表项` / `1. 项` | 项目符号列表 |
|
||
| `表格`(\| \| \|) | ReportLab Table,自动列宽 |
|
||
| `` | 图片 + 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/<slug>/phase4/final.md 存在
|
||
3. 检查配置:projects/<slug>/manifest.json 有必需字段
|
||
4. 执行:
|
||
python3 .opencode/templates/report-template.py \
|
||
--input projects/<slug>/phase4/final.md \
|
||
--manifest projects/<slug>/manifest.json \
|
||
--output projects/<slug>/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", ...]`,未来版本会处理。
|