# PDF Lecture Notes Zh

> 把中文 PDF 课件（大学课程讲义/幻灯片）提炼为结构化中文 Markdown 学习笔记，裁剪并嵌入完整图例。当用户提供课件 PDF 并要求提炼笔记、导出 Markdown、整理讲义、做章节笔记或嵌入图例时使用。

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

---


# 中文 PDF 课件 → Markdown 学习笔记

把课程 PDF 课件转化为结构化、可直接复习的中文 Markdown 笔记；图例（流程图、结构图、示意图等）裁剪为完整图片，嵌入对应知识点下方。

## 适用条件

| 条件 | 说明 |
| --- | --- |
| PDF 有文本层 | 文本提取依赖 `pdfplumber`；纯扫描件需先 OCR，不适用本流程 |
| 图例由图形对象构成 | 位图（images）或矢量（curves/lines/rects）均可，两者裁剪路径不同，见第 3 步 |
| 页面尺寸动态读取 | 常见 960×540、720×405、1280×720，用 `page.width/height` 读取，不要硬编码 |

## 环境准备

```bash
python3 -m venv .venv

# Linux / macOS
.venv/bin/pip install -r requirements.txt
# Windows (PowerShell)
# .venv\Scripts\pip install -r requirements.txt
```

依赖共三个：`pdfplumber`（文本与图形对象坐标）、`pypdfium2`（整页渲染）、`Pillow`（裁剪与像素统计）。不需要 numpy，逐行/逐列像素统计用 PIL 的 `histogram()` 即可。

## 工作流

```
- [ ] 1. 提取文本 + 渲染页面 + 诊断固定角标
- [ ] 2. 挑选图例页
- [ ] 3. 裁剪图例（figures.json 驱动）
- [ ] 4. 目视复核裁剪结果
- [ ] 5. 撰写 Markdown 笔记
- [ ] 6. 校验图片引用
- [ ] 7. 清理中间文件
```

### 第 1 步：提取文本 + 渲染页面

```bash
python scripts/extract_and_render.py 课件.pdf -o _tmp --scale 3 --diagnose-deco
```

产出：

- `_tmp/text_raw.txt`：每页文本 + `images/curves/rects/lines` 计数，计数高的页大概率含图例；
- `_tmp/pages/pNN.png`：整页渲染图（scale=3，保证裁剪后仍清晰）；
- `--diagnose-deco`：找出"在多数页面相同坐标重复出现"的图形对象（页眉校名、页脚校徽这类固定角标），直接输出可粘进 `figures.json` 各项 `deco` 字段的 bbox 列表。

PPT 型课件通常有两个固定角标（页眉 + 页脚），诊断结果要全部填入 `deco`；漏填会使 auto 模式的裁剪边界一路扩到页边。

PPT 类课件的文本抽取顺序常常错乱（标题与正文穿插），需结合渲染图判断真实版面。

### 第 2 步：挑选图例页

只截真图。以下情况不截：

- 纯文字页（包括带项目符号的要点罗列）；
- 与正文文字完全重复的内容；
- 目录页、章节分隔页、参考文献页（除非含图）。

值得截的：流程图、结构图、语法树、状态机、时序图、架构图、示意图、带标注的代码对比图、表格化的映射关系图。

同一张图在多页重复出现时只截一次；同一页有多个独立图例时分开裁剪保存，不要合并成一张。

### 第 3 步：裁剪图例

核心原则：**宁可截大，不可截破**。裁剪过紧导致标题、箭头、边框、图注被切掉，是最常见的返工原因。不要用手工比例坐标直接下刀。

先区分图例类型，再选对应模式。判据：打开该页 `page.images`，看是否存在面积占比 >10% 的单张大图（PPT 导出型课件典型 15%~40%）：

- 有 → 整块嵌入位图，用 `mode: "bbox"`，直接取该图例自身的 image bbox（加 2~4pt 内边距）；
- 没有，图形由大量小对象组成 → 矢量线条 + 文字标签绘制，用 `mode: "auto"`。

编写裁剪配置（完整示例见 [examples/figures.example.json](examples/figures.example.json)），然后执行：

```bash
python scripts/crop_figures.py 课件.pdf figures.json -o figures --scale 3
```

```json
[
  {"page": 19, "name": "fig_b", "mode": "auto", "margin": 40,
   "deco": [[17.8, 20.2, 103.4, 60.7], [599.3, 368.4, 697.0, 399.6]]},
  {"page": 3,  "name": "fig_a", "mode": "bbox", "box": [173, 68, 524, 221]},
  {"page": 29, "name": "fig_c", "mode": "bbox", "box": [58, 206, 650, 386],
   "masks": [
     {"box": [608.5, 366, 650, 386], "fill": "bg"},
     {"box": [601, 382, 608.5, 386], "fill": [0, 112, 192]}
   ]}
]
```

#### mode: "auto" 的算法

实现见 `scripts/crop_figures.py` 的 `auto_bbox()`，确定性流程：

1. 种子 bbox = 全部图形对象的联合（images/curves/lines/rects 全部纳入，不只 images——树图、自动机、流程图常由矢量线条 + 文字标签绘制）。并入前排除三类对象：
   - `deco` 中的固定角标；
   - 面积 >70% 页面的大背景框；
   - 通栏装饰线（一维 ≤8pt 且另一维横跨 >60% 页面，如标题下划线、页脚色带）。装饰线面积接近 0，面积阈值挡不住，一旦并入会把裁剪框拉到接近整页，必须在求并集之前剔除。
2. 迭代吸收相邻文字：与 bbox 膨胀 6pt 相交的 word 全部并入，循环到不动点（图例旁注、轴标、箭头文字在这一步保住）。
3. 外扩 30~40pt 缓冲，clamp 到页面边界；通栏装饰线与不重叠的角标作为护栏压住外扩边界（把角标排除出种子框并不能阻止它进入最终裁剪范围）。角标与图例重叠时脚本打印 `[警告]`，改用 `masks` 局部遮盖。
4. 截断词吸收到不动点：任何 word 与裁剪框相交但未被完全包含，则并入并外扩 4pt，循环直到零截断。只做一轮不够——扩张边界后可能切到新的词。

无图像查看能力时，上述程序化保证（矢量全包含 + 文字零截断）即完整性依据；能目视时仍应逐张复核（第 4 步）。

#### mode: "bbox" 的边界确定

整块位图不要套用"联合 + 40pt 外扩"：外扩会把邻近的公式小图框进来，而公式是 image 不是 word，截断词校验管不到它，结果图里出现半个公式。

精确切边用墨迹扫描（第 6 步 `--ink` / `--col`）：逐行/逐列统计暗像素，找出图例墨迹的真实起止位置，再与邻近文字的行区间比对。例如：图例墨迹止于 330.3pt，下方图注 word 的 `top` 是 339.4pt，则取 332pt 作下边界，既完整又不带半个字。

#### masks：角标压住图例时的局部遮盖

固定校徽可能压住图例一角。不要整体裁窄（会切掉图例另一侧的独立图元），改为局部遮盖：

- 遮盖矩形不得进入图例本体，否则会啃掉边框。先用列扫描量出图例本体右边界与校徽墨迹起始列（例如方框右边框在 607.0~608.0pt、校徽墨迹起于 608.3pt，则遮盖从 608.5pt 起），确认遮盖区内没有图例内容；
- `fill: "bg"` 取图中出现次数最多的颜色作为页面背景色（这类课件背景常是 `(242, 242, 242)` 而非纯白，比固定采样某个像素稳）；
- 校徽压在彩色方框边框上时，用 `fill: [r, g, b]` 以该边框色补一小块，视觉上自然融合；
- 遮盖后复扫该区域，剩余深色像素应能确认属于图例本身（如箭头笔尖），否则继续修。

#### 命名

`figures/fig_<语义名>.png`，英文小写下划线，名字体现图例内容。

### 第 4 步：目视复核裁剪结果

当前工具链能查看图片时（能看图时用满这一能力，成本远低于返工）：

- 先看整页渲染图（`_tmp/pages/pXX.png`）确认图例与邻近文字的真实版面关系；
- 逐张打开裁剪结果，确认：四周无半个字、无断掉的边框/箭头、无混入的校徽或正文残片；
- 顺带核准公式：这类课件的公式多为图片，纯文本抽取常丢失或乱序（`∑` 结构散架、下标错位），关键公式必须对照渲染页确认后再写入笔记；
- 发现问题按 `bbox` 模式重裁或用 `masks` 遮盖，不要带问题交付。

### 第 5 步：撰写 Markdown 笔记

结构与风格要求：

- 一级标题：`课程名 · 第X章 章节名 · 学习笔记`；
- 开头用 `>` 引用块注明来源与笔记定位，附目录（锚点链接）；
- 标题层级合理，章节编号清晰；
- 优先引用课件原文（中文）概括，不要擅自改写术语；
- 重点、难点加粗，并用 `>` 引用块附加解释与注解（"难点提示 / 注"）；
- 善用表格做对比归纳；
- 图例放在对应知识点正下方，用相对路径 `![说明](figures/xxx.png)`；
- 章末附「重点回顾与易混淆点」与「课后自检清单」（`- [ ]` 形式）。

### 第 6 步：校验图片引用

```bash
python scripts/check_figures.py 笔记.md           # 路径与可加载性校验
python scripts/check_figures.py 笔记.md --edges   # 四边墨迹检测（推荐，必跑）
```

第一句校验笔记引用的图片全部存在且能被 PIL 加载（0 缺失才通过），并列出 `figures/` 下未被引用的孤儿文件。

**`--edges` 是客观完整性判据，不能只靠目视复核**：它检查每张图**四边最外 3px 内是否有墨迹**，四边全 0 才算干净。边缘有墨迹 = 要么混入了页外元素（正文列、校徽、页脚色带），要么图例内容被裁掉了。

> ⚠️ 为什么必须跑：看图工具（Read 等）可能**返回缓存的旧图**——重新裁剪后立刻看图，看到的可能仍是上一版，于是把已修好的图误判为"还有问题"、或把没修好的图误判为"已经好了"。`--edges` 直接读磁盘像素，不受缓存影响。

墨迹扫描（bbox 模式定边界、masks 遮盖后复查时使用）：

```bash
python scripts/check_figures.py 笔记.md --ink figures/fig_a.png --y0 69 --scale 3   # 逐行：找图例墨迹下界
python scripts/check_figures.py 笔记.md --col figures/fig_a.png --x0 58 --scale 3   # 逐列：找右边界 / 校徽起始列
```

**用 `--ink` 找上下边界时，要扫出"连续空白行区间"再取边界值**，不要凭肉眼估——正文行之间、图注与正文之间通常都只有 **4~8pt** 的空白带。定位步骤：先扫一遍打印逐行墨迹，找到 `dark=0` 的连续行区间，取该空白带**偏上**的位置作为 `y1`（保住图注、避开正文）。

### 第 7 步：清理中间文件

删除 `_tmp/` 等全部中间产物，只保留最终 Markdown 与 `figures/`。不要删除用户的历史产出或原始课件，那些不是临时文件。

## 常见问题

| 问题 | 处理 |
|----|------|
| 裁剪过紧、内容截断 | 用 auto 模式的零截断算法；宁可截大，不可截破 |
| 只按 images bbox 裁，矢量图被切掉大半 | 种子框必须纳入 curves/lines/rects |
| 裁剪框被拉到接近整页 | 通栏装饰线混入种子框（面积接近 0，面积阈值挡不住）；auto 模式已自动剔除 |
| 裁剪边界扩到页边、混入校徽/页眉 | `deco` 未填全；重跑 `--diagnose-deco`，通常有两个角标 |
| 外扩把邻近公式小图截进来半个 | 整块位图型图例不要套联合 + 外扩，改用 bbox 模式 |
| 图例底部与下方正文重叠 1~2pt，裁出半个字 | 用逐行墨迹扫描找图例墨迹真实下界，与正文 word 的 `top` 比对后定边界 |
| 遮盖矩形压进图例本体、啃掉边框 | 遮盖只能从图例本体边界之外起笔，先用列扫描量出确切列 |
| 吸收只做一轮，扩张后又切到新词 | 吸收必须循环到不动点 |
| PPT 文本抽取顺序错乱、公式丢失 | 结合渲染图判断版面，关键公式对照渲染页核准 |
| 截了纯文字页或与正文重复的图 | 执行第 2 步"只截真图"标准 |
| 图片路径用了绝对路径 | 用 `figures/xxx.png` 相对路径，便于笔记整体移动 |
| 只靠目视复核、被看图工具的缓存旧图骗了 | 重裁后立刻看图可能看到上一版 → 必须跑 `check_figures.py --edges` 做客观检测 |
| 裁剪框底部混进页脚色带（横贯页面的彩色带） | 页脚带通常在 y ≥ 399.5pt（页高 405）→ `y1` 取 ≤ 398；量墨迹时也要排除 y≥399 的行 |
| 图注与正文行分不清，`y1` 取错带进半行正文 | 逐行墨迹统计找**连续空白行区间**再取边界；两者之间常只有 4~8pt 空白，不能凭肉眼估 |
| 测量 ROI 给窄了，读数贴在 ROI 边上却当成真边界 | 读数与 ROI 边界相差 <2pt 说明还没测到真边界，放宽 ROI 重测 |
| 忘了清理 `_tmp/` | 第 7 步强制检查 |

## 仓库结构

```
.
├── SKILL.md                    # 本文件：完整工作流（agent 加载此文件即可工作）
├── README.md                   # 面向人的介绍与快速开始
├── requirements.txt            # pdfplumber / pypdfium2 / Pillow
├── examples/
│   └── figures.example.json    # 裁剪配置示例（auto / bbox / masks）
└── scripts/
    ├── extract_and_render.py   # 第 1 步：抽文本 + 图形统计 + 整页渲染 + 角标诊断
    ├── crop_figures.py         # 第 3 步：配置驱动的图例裁剪
    └── check_figures.py        # 第 6 步：引用校验 + 墨迹扫描
```

