PDF 技能
根据结构化的规格创建 PDF,构建和填写 AcroForm 表单(含布局 lint 和视觉叠加层),提取文本/表格/元数据,合并/拆分/旋转/水印/图章页面,导出页面图像,管理元数据和附件,以及加密/解密——使用 pypdf、reportlab 和 pdfplumber。两个吸收的功能位于 references/ 中(在相关任务之前请阅读对应的文件):
- 扫描/纯图像 PDF 和 OCR(pymupdf 快速路径、marker-pdf 质量路径、
scripts/extract_pymupdf.py+scripts/extract_marker.py):references/ocr-extraction.md - 通过自然语言提示编辑现有 PDF 中的文本(nano-pdf CLI):
references/nano-pdf-editing.md
何时使用
- 生成报告、发票或多页文档作为 PDF。
- 根据 JSON 规格构建可填写的 AcroForm(文本/复选框/单选/下拉框),先对布局进行 lint。
- 从 PDF 中提取文本、表格(JSON/CSV)、元数据或表单字段值。
- 对 PDF 进行合并、拆分、旋转、提取页面子集、添加水印、在特定坐标处加盖文本/图像、添加书签或压缩。
- 将页面导出为 PNG 用于视觉检查或 OCR 交接;设置/清除文档元数据;添加/提取文件附件。
- 填写或扁平化 AcroForm 表单;使用密码加密或解密。
- 不适用于扫描/纯图像 PDF(请使用
references/ocr-extraction.md)且不适用于像素完美的 HTML 转 PDF 渲染(请使用无头浏览器)。
前置条件
- Python 3.10+ 并安装
pypdf、reportlab、pdfplumber:python -m pip install pypdf reportlab pdfplumber - 可选,用于页面栅格化(
pdf_page_image.py、叠加渲染):python -m pip install pypdfium2,或在 PATH 中包含 poppler 的pdftoppm。当两者都不存在时,脚本会依次尝试 pypdfium2 → pdftoppm 并报告{"rendered": false, "missing": [...]}(退出码 0)。 - 每个辅助脚本都会惰性地检查导入,并在缺少依赖时打印安装提示。
如何运行
所有辅助程序都位于 scripts/ 目录下,是 argparse CLI——请使用 terminal 工具运行;每个都支持 --help。它们以 UTF-8 格式严格读写 JSON,将 JSON 结果打印到 stdout,并在失败时以非零退出。
python scripts/pdf_create.py spec.json -o out.pdf # 从 JSON 规格构建 PDF
python scripts/pdf_make_form.py formspec.json -o form.pdf # 从 JSON 规格构建可填写 AcroForm
python scripts/pdf_form_layout.py formspec.json # 在构建前对表单布局进行 lint
python scripts/pdf_form_layout.py formspec.json --render-overlay boxes.png [--pdf form.pdf]
python scripts/pdf_read.py doc.pdf --text # 每页文本(JSON)
python scripts/pdf_read.py doc.pdf --tables --csv-dir t/ # 将表格导出为 JSON + CSV 文件
python scripts/pdf_read.py doc.pdf --meta # 元数据、页面尺寸、加密/扫描标志
python scripts/pdf_read.py form.pdf --fields # 表单字段:名称、类型、值
python scripts/pdf_merge.py a.pdf b.pdf -o merged.pdf [--bookmarks]
python scripts/pdf_split.py doc.pdf --pages 1-3,7 -o part.pdf [--rotate 90]
python scripts/pdf_fill_form.py form.pdf --fields-json values.json -o filled.pdf [--flatten]
python scripts/pdf_secure.py doc.pdf --encrypt -o enc.pdf --user-password your-password
python scripts/pdf_secure.py enc.pdf --decrypt -o dec.pdf --password your-password
python scripts/pdf_watermark.py doc.pdf --stamp mark.pdf -o stamped.pdf [--under]
python scripts/pdf_stamp.py doc.pdf -o out.pdf --text "DRAFT" --x 150 --y 400 \
--font-size 60 --rotation 45 --opacity 0.3 --color "#cc0000" [--pages 1-3]
python scripts/pdf_stamp.py doc.pdf -o out.pdf --image sig.png --x 400 --y 60 --width 120
python scripts/pdf_page_image.py doc.pdf --pages 1-3 --dpi 150 --out-dir imgs/
python scripts/pdf_meta.py doc.pdf --set-meta --title "T" --author "A" -o out.pdf
python scripts/pdf_meta.py doc.pdf --attach data.csv -o out.pdf
python scripts/pdf_meta.py doc.pdf --list-attachments | --extract-attachments dir/
快速参考
| 任务 | 工具 | 命令 / API |
|---|---|---|
| 创建文档(标题、表格、图像) | reportlab platypus | pdf_create.py spec.json -o out.pdf |
| 构建可填写表单 | reportlab acroForm | pdf_make_form.py formspec.json -o form.pdf |
| Lint 表单布局 / 叠加图像 | pure python + PIL | pdf_form_layout.py formspec.json [--render-overlay o.png] |
| 每页文本 | pdfplumber | pdf_read.py f.pdf --text |
| 表格 → JSON/CSV | pdfplumber | pdf_read.py f.pdf --tables |
| 元数据 / 尺寸 / 加密 / 扫描 | pypdf + pdfplumber | pdf_read.py f.pdf --meta |
| 合并(+ 大纲) | pypdf | pdf_merge.py a.pdf b.pdf -o m.pdf |
| 拆分 / 提取 / 旋转 | pypdf | pdf_split.py f.pdf --pages 2-5 --rotate 90 |
| 列出 / 填写 / 扁平化表单 | pypdf | pdf_read.py --fields、pdf_fill_form.py |
| 加密 / 解密(AES-256) | pypdf | pdf_secure.py --encrypt/--decrypt |
| 水印 / 加盖 PDF 页面 | pypdf | pdf_watermark.py f.pdf --stamp w.pdf |
| 在坐标处加盖文本/图像 | reportlab + pypdf | pdf_stamp.py f.pdf --text "Sign here" --x 400 --y 60 |
| 页面 → PNG(审查 / OCR 交接) | pypdfium2 或 pdftoppm | pdf_page_image.py f.pdf --pages 1-3 --out-dir imgs/ |
| 设置/清除元数据、附件 | pypdf | pdf_meta.py --set-meta / --attach / --extract-attachments |
| 压缩内容流 | pypdf | pdf_split.py f.pdf --pages 1-N --compress |
流程
- 检查。 运行
pdf_read.py file.pdf --meta。检查encrypted(如果为真,先用pdf_secure.py --decrypt解密)和likely_scanned_pages。如果页面是纯图像的,先用pdf_page_image.py --pages <scanned> --dpi 300 --out-dir imgs/导出,然后将 PNG 交给references/ocr-extraction.md技能——不要将空文本报告为“无内容”。 - 创建。 用
write_file编写 JSON 规格(元素:heading、paragraph、table、image、pagebreak;可选的title/author元数据;页码会自动添加),然后运行pdf_create.py。如果布局很重要,用vision_analyze在渲染的页面图像上直观验证。 - 提取。
--text输出每页字符串的 JSON 列表;--tables输出每页的行数组,并且也可以生成 CSV 文件。用read_file读取结果;绝不要直接查看二进制 PDF。 - 操作。
pdf_merge.py连接文件,可以为每个源文件添加一个书签;pdf_split.py处理页面范围(从 1 开始,例如1-3,5,9-)、以 90° 为步长的旋转以及--compress。通过准备单页图章 PDF(例如通过pdf_create.py)并用pdf_watermark.py叠加来添加水印;对于单行图章(“在此签名”、对角线 DRAFT、角落标签),使用pdf_stamp.py在明确坐标处添加文本或图像。 - 构建表单。 编写一个表单规格 JSON(字段包含以 PDF 点为单位的
label_box/entry_box——参见references/forms.md),用pdf_form_layout.py进行 lint 并修复所有报告的问题,可选择用vision_analyze审查--render-overlayPNG,然后用pdf_make_form.py构建,并用pdf_read.py --fields确认。 - 填写表单。 列出字段(
--fields)以了解确切的名称和类型,用write_file编写 UTF-8 JSON 格式的{"FieldName": "value"}(复选框接受true/false;单选/选择值必须匹配字段的导出选项),然后运行pdf_fill_form.py。重新用--fields读取以确认值已正确填写。 - 元数据和附件。
pdf_meta.py --set-meta写入标题/作者/主题/关键词(DocInfo);--clear-meta清除它们;--attach/--list-attachments/--extract-attachments处理嵌入文件的往返。 - 安全。 使用不同的用户/所有者密码和 AES-256 加密。要移除已知密码,
--decrypt会写入未加密的副本。 - 验证(见下文)再报告成功。
陷阱
- 扫描 PDF:空的
extract_text()加上页面图像意味着没有文本层。转到references/ocr-extraction.md;不要编造文本。 - 扁平化限制:
pdf_fill_form.py --flatten使用 pypdf 的扁平化支持,将小部件外观转换为页面内容。对于纯文本字段和复选框是可靠的,但可能会丢弃或错误渲染特殊的小部件(富文本、自定义外观流、某些单选组)。用vision_analyze直观验证扁平化输出;对于要求严格的扁平化,使用外部渲染器(例如 Ghostscript 或pdftoppm+ 重组)作为后备方案。 - NeedAppearances:填写后,查看器仅在存在外观流时才渲染值。填充脚本会设置 AcroForm 的
NeedAppearances标志,以便合规的查看器重新生成它们;一些最小查看器会忽略它——如果显示保真度重要,请扁平化。 - 非拉丁表单值:值被正确存储(UTF-16),但字段的默认字体可能缺少字形,因此查看器可能显示空白,即使数据往返正常。用
--fields验证,而不仅仅是视觉验证。 - 压缩预期:
--compress仅对内容流进行 deflate 操作。通常节省 0-20%;对于以图像或已压缩流为主的 PDF 无效。它不能替代图像降采样(这是 Ghostscript 的领域)。 - 权限标志不强制执行:所有者密码权限位(禁止打印、禁止复制)是查看器可能遵守的礼貌请求;任何库(包括 pypdf)都可以读取和剥离它们。只有用户密码通过加密才能实际保护内容。永远不要把权限标志当作安全措施。
- 表格提取是启发式的:pdfplumber 根据分割线/文字对齐检测表格;无边框或合并单元格的表格可能需要
table_settings调整或手动清理。 - 页面索引:辅助 CLI 采用从 1 开始的页面;pypdf API 是从 0 开始的。脚本会进行转换——不要重复转换。
- 旋转的图章文本提取:pdfplumber 的行分组会打乱旋转的字形(45° 的“DRAFT”会提取为散乱的字母);用
pypdf的extract_text()或渲染图像来验证旋转的图章。 - 单选组:reportlab 每个组需要 ≥2 个
radio()小部件,填充需要斜杠导出的值("/red"),并且对于单选,扁平化保真度最差——参见references/forms.md。 - 元数据范围:
pdf_meta.py仅写入经典的 DocInfo 字典;嵌入的 XMP 元数据(如果有)保持不变,在某些查看器中可能显示不同的值。 - PDF/A 超出范围:pypdf/reportlab 无法生成或验证符合标准的 PDF/A。如果需要存档合规性,请通过
terminal工具运行 Ghostscript(例如gs -dPDFA=2 -dPDFACompatibilityPolicy=1 -sColorConversionStrategy=UseDeviceIndependentColor -sDEVICE=pdfwrite -o out.pdf in.pdf以及合适的 ICC 配置文件)并使用 veraPDF 进行验证——两者都是外部安装,结果仍需要验证,而不是假设。 - 旋转必须是 90 的倍数;加密输入必须在任何其他操作之前解密。
验证
- 创建/合并/拆分后:
pdf_read.py out.pdf --meta——确认page_count,以及旋转后的每页rotation。 - 提取后:检查 JSON 非空,并抽查已知的字符串或单元格。
- 表单设计循环:
pdf_form_layout.py spec.json必须退出码为 0;然后--render-overlay boxes.png --pdf form.pdf并用vision_analyze审查 PNG(红色 = 带字段名称的输入框,蓝色 = 标签框),询问重叠、未对齐以及标签与字段分离的问题。迭代规格 → lint → 叠加,直到干净。 - 构建表单后:
pdf_read.py form.pdf --fields列出每个规格字段,并具有正确的类型和选项。 - 填写表单后:
pdf_read.py filled.pdf --fields并比较值(精确匹配,包括非 ASCII 字符)。 - 加盖图章后:重新提取文本(旋转的图章使用 pypdf)或用
pdf_page_image.py渲染页面并用vision_analyze检查。 - 元数据/附件编辑后:
pdf_read.py --meta/pdf_meta.py --list-attachments,然后重新提取附件进行字节比较。 - 加密后:
--meta显示"encrypted": true且无密码打开会失败;解密后,文本提取与原始内容一致。 - 对于任何视觉内容(水印、扁平化表单),进行渲染并用
vision_analyze检查。