report-to-pdf — Markdown 报告转精美 PDF
确定性、脚本驱动的 Markdown 转 PDF 渲染器。它解析一个受限的“报告”Markdown 子集,使用一个自研的小型块解析器(不依赖外部 Markdown 库),并通过 fpdf2 进行排版,其视觉约定与 ColeteOnline Magento 模块的 build-user-guide.py 生成器相同——包括封面页、页眉/页脚、目录、强调色块标题、带阴影的代码块、带边框/斑马条纹的表格。对于相同的输入,布局逻辑在多次运行之间始终保持不变:相同的报告输入,每次生成的外观完全一致的 PDF——渲染循环中没有 LLM 参与。
何时使用
- "把这个 markdown 评论变成一份给导师的 PDF。"
- "把我的报告导出为精美的 PDF。"
- "我希望每次看起来都一样,而不是每次运行都被 LLM 重新格式化。"
- 任何包含标题、列表、代码块或表格的
.md报告/文章,需要一份客户/导师/品牌质量级别的 PDF 版本。
如何运行
python3 <skill-dir>/scripts/build_report_pdf.py <input.md> [选项]
(<skill-dir> 是本技能的基础目录,即包含此 SKILL.md 的文件夹。)
选项:
-o / --output <路径>— 输出 PDF 路径(默认:在源文件旁边生成<input>.pdf)。--logo <路径>— 封面页上的可选 Logo 图片(PNG/WebP/JPEG,任何 Pillow 能打开的格式)。可以重复该标志以添加多个 Logo,它们会并排绘制在一行中(也可通过 frontmatter 的logo: a.png, b.png设置,路径相对于.md文件)。--title,--subtitle,--version,--vendor,--author— 覆盖对应的 frontmatter 值(见下文);CLI 标志优先级更高。--cover center|left— 封面页布局。两种模式下,整个标题块(Logo、标题、副标题、作者、版本)都垂直居中;center(默认)同时水平居中,left则左对齐。也可通过 frontmatter 的cover:设置。--no-header— 省略运行页眉及其预留的空间,使每一页的内容从相同的上边距开始(也可通过 frontmatter 的header: false设置)。--accent "R,G,B"— 覆盖封面页顶部条和章节标题强调色条使用的强调色(默认220,50,50,ColeteOnline 红色)。--dark— 使用深色主题渲染,而不是默认的浅色主题(也可通过 frontmatter 的theme: dark设置;如果同时存在,--dark优先)。详见“深色主题”。
如果任何导入失败,脚本会通过 pip install --user 自动安装 fpdf2、Pillow 和 Pygments,模仿参考脚本的 Pillow 自动安装。这三个库在开发过程中都已验证可导入,并且脚本已从头到尾运行过(详见“测试”部分)。
它期望的 Markdown 约定
可选的 frontmatter 块位于文件最顶部:
---
title: 我的报告标题
subtitle: 一行副标题
version: 1.0
vendor: ColeteOnline SRL
accent: 220,50,50
---
所有键都是可选的,并有合理的回退:
title— 回退到正文中找到的第一个# H1标题,如果也没有,则回退到输入文件名。subtitle— 回退到空(从封面页省略)。version— 回退到今天的日期(YYYY-MM-DD);设置version: none以隐藏版本行。vendor— 回退到空(如果未设置,封面页页脚将省略版权行)。author— 作者姓名,显示在封面页的副标题下方(例如author: 张三, 李四)。如果未设置则省略。cover—center(默认)或left;参见上面的--cover。header—false省略运行页眉;参见--no-header。justify—true使正文文本、标题和表格单元格两端对齐;默认是左对齐(对齐文本会在短的自动换行行上拉伸单词间距,例如两行标题)。accent— 回退到 ColeteOnline 品牌红色220,50,50。
正文 Markdown,以及每个元素如何渲染:
| Markdown | 渲染效果 |
|---|---|
# 标题 | 文件中第一个 # 仅用作封面页标题(被消耗,不会在正文中重复)。任何后续的 # 都是一个真正的主要标题(见下文)。 |
# 主要章节(第一个 # 之后) | **主要标题。**始终另起一页(除非光标已经在空白页顶部),大的彩色左侧条,粗体 16pt,黑色文本——并生成目录条目。仅用于真正的顶级分区;每个标题都会占用一页。 |
## 子节 | **子标题。**粗体 13pt,强调色文本,带有一个短的强调色下划线(在视觉上与上下两层都不同)——不强制分页,仅继续流动。仍会获得目录条目。有自己的“避免标题出现在页面最底部”的检查。 |
### 子子节(以及 ####+) | **第三级标题。**粗体 12pt,中性灰色,全宽细规则线,不分页,没有目录条目(保持目录的可扫描性)。 |
| 段落文本 | 两端对齐的正文段落,10pt。 |
- 项目 / * 项目 | 无序列表(•)。 |
1. 项目 | 有序列表。 |
```语言 ... ``` | 带阴影、有边框的等宽代码块;语言标签(如果存在)会作为块上方的小斜体标签打印,并驱动真实的 Pygments 语法高亮——见下文。 |
| a | b | 带有 --- 分隔行 | 带边框的表格,表头行粗体,表体行斑马条纹,单元格自动换行,列宽相等。 |
--- / *** / ___ 单独一行 | 垂直分隔符(不是分页符)。 |
<br> 单独一行 | 一个显式的空白正文行(5.5mm)。堆叠多个可获得更多空间。渲染器在页面顶部不会自动添加间隙,因此当您需要额外空间时,请使用此标记。 |
<!-- pagebreak --> 单独一行 | 开始新页面(如果已在页面顶部,则无操作)。 |
 单独一行 | 居中、带边框的图片,按比例缩放以适应;相对路径相对于 .md 文件的文件夹解析。支持 PNG/JPEG/WebP 和 SVG(见“图形、框和数学公式”)。宽度低于 900 像素的纯栅格图像被限制在约 130x105mm;一个图形(图像后跟标题)、任何 SVG 以及宽度 900 像素或以上的任何图像可以使用完整的文本宽度(最大高度 120mm)。如果图形无法容纳在页面剩余空间中,它会被缩小以适应(最多缩小到其高度的 70%),而不是跳到下一页并留下空白。 |
*图形: ...*(紧跟在图像后的整个斜体行) | 图像的标题:小号、居中、斜体,与图像保持在同一页上。替代文本永远不会打印。 |
> 文本 行 | 定义框:每个块引用一个带边框、浅阴影的框(粗体术语和内联数学可以在其中工作;> 行后面没有任何内容会在同一框中开始一个新段落)。 |
$...$ | 内联数学,转换为 Unicode 文本(希腊字母、≤ ≥ × ·、上/下标、\frac{a}{b} -> (a)/b、\sqrt、\text{}、重音符号)。像 $5 这样的价格不会被视为数学。 |
$$...$$(一行,或单独几行) | 显示数学,来自第一个可用的数学后端的居中图像(见“图形、框和数学公式”)。 |
| 空行后缩进 4 个空格 | 没有语言标签的代码块(笔记本导出的单元格输出以此方式打印)。列表后不适用。 |
**粗体** | 真正的粗体文本,通过 fpdf2 内置的 markdown=True 渲染器实现。 |
[文本](url) | 一个真实的、可点击的 PDF 链接注释——只显示文本,不显示原始 URL——以独特的链接颜色样式显示。 |
*斜体* / _斜体_ | 渲染为斜体(内部会转换为 fpdf2 的本地 __italic__ 形式)。 |
`代码` | 内联代码反引号会被去除——不支持等宽内联,渲染为纯文本。 |
标题、段落、无序列表、有序列表项和表格单元格都通过 fpdf2 的 markdown=True 路径渲染,因此粗体和 [文本](url) 链接在所有出现文本的地方都有效,包括标题和表格单元格内部。围栏代码块是唯一的例外——其内容完全按字面渲染(不解析 markdown),因此包含字面 **、__ 或 []() 的代码示例不会被误解。
图形、框和数学公式
旨在让笔记本的 markdown 伴随文件(nbconvert 输出)直接渲染:report-to-pdf notebook.md 替代了单独的基于 Chrome 的 PDF 生成步骤。
SVG 图像。  有效。fpdf2 自身的 SVG 绘制会跳过 <marker> 箭头、滤镜和遮罩,使用核心字体(无希腊字母)绘制文本,并且对没有 fill 的文本进行错误着色,因此每个 SVG 都被光栅化为 2.5 倍大小并嵌入为 PNG,依次使用:无头 Chrome/Edge(自动查找;设置 REPORT_PDF_BROWSER 为浏览器路径以覆盖),然后是 cairosvg(如果可导入)。如果两者都不可用,SVG 将由 fpdf2 本地绘制,并发出警告说明可能不完整。光栅化过程在每次运行中会被缓存。
显示数学后端(--math auto|latex|mathtext|unicode,或 frontmatter 的 math:;默认 auto)。脚本会打印使用了哪个后端。
latex:当pdflatex、xelatex或tectonic在 PATH 中(以及pdftocairo、PyMuPDF 或dvisvgm用于生成图像),或latex+dvipng时,使用真正的 LaTeX。每个公式都以standalone、amsmath、amssymb和xcolor以 12pt 编译,然后裁剪为文本颜色的透明 300 dpi PNG。安装:MiKTeX(Windows)、TeX Live(apt install texlive-latex-base texlive-latex-extra poppler-utils,macOSbrew install --cask mactex-no-gui),或tectonic(单个二进制文件,按需下载包;conda install -c conda-forge tectonic或scoop install tectonic;已在 Windows 上端到端测试,使用 PyMuPDF 作为图像转换器)。mathtext:matplotlib 的 LaTeX 子集(分数、求和、希腊字母、\text{}映射为\mathrm)。不需要 TeX,只需要 matplotlib。不支持aligned等环境,会降级处理。unicode:与内联数学相同的文本转换,居中显示。
如果某个后端缺失或失败(例如公式 TeX 拒绝),则会静默降级到下一个后端;--math latex 仍然以相同的方式降级。
测试:python tests/run_checks.py(数学转换、解析器、SVG 路由、模拟 PATH 的后端回退、tests/fixture.md 的端到端构建)。渲染一个伴随文件:python scripts/build_report_pdf.py notebook.md -o notebook.pdf。
语法高亮
围栏代码块使用真实的 Pygments 词法分析器进行标记化(不是自制的正则表达式高亮器——代码正是那种小型即席解析器容易出错的东西:字符串转义、嵌套注释、模板字面量)。栅栏上的语言标签(```typescript,```sql,```python,...)选择词法分析器;少数常见别名会被标准化(ts/tsx → typescript,js/jsx → javascript,sh/shell/zsh → bash,yml → yaml,prisma → graphql——最接近的内置词法分析器,在实践中匹配良好,因为 Prisma 的 @attribute(...) 语法和 GraphQL 的 @directive(...) 语法几乎相同地标记化)。
高亮是一种奖励,绝不是渲染代码块的硬性要求——缺少语言标签、Pygments 无法识别的语言(即使在别名表之后),或 Pygments 本身不可导入,都会回退到未高亮的确切行为:整个块使用一种纯色,不会崩溃,不会丢失内容。
调色板(针对代码块浅灰色背景的可读性选择,不绑定任何特定的 Pygments 主题):
| 令牌类别 | 颜色 |
|---|---|
关键字(interface、async、const、NOT NULL、类型关键字如 string/number) | 品红色 |
| 字符串 | 绿色 |
| 数字 | 棕/橙色 |
| 注释 | 灰色 |
函数/类名、装饰器/属性(@map、@relation) | 蓝色/棕色 |
内置函数(Promise 等) | 青色 |
| 其他所有(标识符、标点、运算符) | 基础代码颜色 |
在调色板中没有精确条目的令牌类型,会通过向上遍历 Pygments 的令牌类型层次结构(例如 Keyword.Declaration 没有自己的条目,但它的父级 Keyword 有)直到找到匹配项——裸的 Token 根始终匹配,因此这永远不会无法解析为某种颜色。
等宽缩小以适应逻辑(代码行太宽超出页面时,会统一缩小整个块的字体而不是溢出)不受高亮影响——它测量纯字符宽度,而字符宽度不会根据令牌最终绘制成什么颜色而改变。
深色主题
传递 --dark,或在 frontmatter 中设置 theme: dark,以使用深色主题渲染整个文档,而不是默认的浅色主题。文档中的每种颜色都会切换到深色主题的对应颜色——不仅仅是代码块:
- 页面背景本身填充为深色(fpdf2 没有原生的“页面背景颜色”设置,这是通过在
header()钩子中填充整个页面来实现的,在任何其他内容绘制于其之上之前——包括封面页)。 - 正文文本、标题(所有三个层级)、静默/页脚文本、规则线、表格斑马条纹以及运行页眉/页脚都使用独立的深色模式颜色集,而不仅仅是反转浅色模式的颜色。
- 语法高亮使用其自己的深色模式调色板(选择更明亮的粉/绿/蓝色以与深色代码背景形成对比),而不是浅色模式颜色的变暗版本。
- 链接颜色(包括
[文本](url)markdown 链接和目录的跳转页面链接)切换到在深色背景上保持可读性的更浅的蓝色。
--accent 颜色(顶部条、主要标题左侧条和副标题文本/下划线)在两个主题中均按原样使用——如果您计划在两个主题中生成相同的报告,请选择一个在白色和接近黑色的背景上都有效的颜色;否则,每个主题单独覆盖 --accent。
目录
回填的目录(见上面的“如何渲染”)
…
[为翻译截断]