> ## Documentation Index
> Fetch the complete documentation index at: https://docs.profy.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# PDF 文档

> profy-pdf — 两条正交路径：WeasyPrint 生成 PDF（强制）与 pypdf/qpdf 处理已有 PDF，另含 kami 精排引擎

# PDF 文档（profy-pdf）

PDF 能力分两条互不相干的路径，**先分清你在哪条路上**，后面的一切都取决于此：

| 你要做的事                          | 走哪条  | 用什么                                 |
| ------------------------------ | ---- | ----------------------------------- |
| 生成新 PDF                        | 创建路径 | **WeasyPrint**（HTML + CSS → PDF），强制 |
| 读取 / 合并 / 拆分 / 填表 / OCR 已有 PDF | 处理路径 | pypdf、pdfplumber、qpdf、pdftotext     |

## 激活方式

`profy-pdf` 是 `user_selectable: false`——**自动可用，不需要勾选**。

```json theme={null}
{ "id": "profy-pdf", "version": "1.1.0", "activation": { "user_selectable": false } }
```

`contracts` 只有 `skills: ["skills/"]`，**没有 tools**。这个插件带**两个**技能：`builtin/pdf`（通用处理）与 `builtin/kami`（精排引擎），后者单独一节讲。

## 创建路径：WeasyPrint 是唯一选择

<Warning>
  **绝不使用 reportlab、fpdf2 或任何基于坐标绘制的库生成 PDF。** 这类库要求手工摆放每个元素的坐标，产出质量差且几乎无法维护——改一句话，后面所有元素的位置都要重算。
</Warning>

WeasyPrint 的路子是写 HTML + CSS 再渲染成 PDF，于是排版由 CSS 布局引擎负责，你得到的是**声明式排版**而不是手工定位。

```python theme={null}
from weasyprint import HTML

html_content = """
<!DOCTYPE html>
<html>
<head>
<style>
  @page { size: A4; margin: 2cm; }
  body { font-family: 'Noto Sans CJK SC', sans-serif; font-size: 12pt; line-height: 1.6; }
  h1 { color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 8px; }
  table { width: 100%; border-collapse: collapse; margin: 1em 0; }
  th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
  th { background: #3498db; color: white; }
  tr:nth-child(even) { background: #f8f9fa; }
</style>
</head>
<body>
  <h1>季度销售报告</h1>
  <p>本报告涵盖 2024 年第四季度业绩数据。</p>
  <table>
    <tr><th>产品</th><th>销量</th><th>收入</th></tr>
    <tr><td>产品 A</td><td>1,200</td><td>¥360,000</td></tr>
  </table>
</body>
</html>
"""

HTML(string=html_content).write_pdf("report.pdf")
```

### 页眉页脚与分页

`@page` 规则是 PDF 特有的 CSS 能力，浏览器里用不上但这里很关键：

```python theme={null}
html = """
<style>
  @page {
    size: A4;
    margin: 2.5cm 2cm;
    @top-center { content: "公司机密"; font-size: 9pt; color: #999; }
    @bottom-right { content: "第 " counter(page) " 页，共 " counter(pages) " 页"; font-size: 9pt; }
  }
  .page-break { page-break-after: always; }
</style>
<h1>封面</h1>
<div class="page-break"></div>
<h2>第一章</h2>
"""
HTML(string=html).write_pdf("multi-page.pdf")
```

`counter(page)` / `counter(pages)` 由渲染器在分页后填充——这是手工坐标方案给不了的东西。

### 排版要点

* 复杂布局用 CSS Grid / Flexbox（卡片、多栏）
* 页面尺寸、页边距、页眉页脚全走 `@page`
* 分页控制用 `page-break-before` / `page-break-after`
* **中文用 `font-family: 'Noto Sans CJK SC', sans-serif`**（沙箱已预装）
* 品牌报告：设主色，用 CSS 背景形状

### 什么时候不用 WeasyPrint

| 场景            | 改用                    |
| ------------- | --------------------- |
| 填已有 PDF 表单    | pypdf（见技能里的 FORMS.md） |
| 合并 / 拆分已有 PDF | pypdf、qpdf            |
| 提取文字 / 表格     | pdfplumber            |

## 处理路径：命令行工具

### pdftotext（poppler-utils）

```bash theme={null}
pdftotext input.pdf output.txt                 # 提取文字
pdftotext -layout input.pdf output.txt         # 保留版面
pdftotext -f 1 -l 5 input.pdf output.txt       # 只取 1-5 页
```

`-layout` 很重要：不带它，多栏排版会被读成交错的乱序文本。

### qpdf

```bash theme={null}
qpdf --empty --pages file1.pdf file2.pdf -- merged.pdf   # 合并
qpdf input.pdf --pages . 1-5 -- pages1-5.pdf             # 拆分
qpdf input.pdf output.pdf --rotate=+90:1                 # 第 1 页旋转 90°
qpdf --password=mypassword --decrypt in.pdf out.pdf      # 去密码
```

### pdftk（若可用）

```bash theme={null}
pdftk file1.pdf file2.pdf cat output merged.pdf
pdftk input.pdf burst
pdftk input.pdf rotate 1east output rotated.pdf
```

### 表单处理

技能自带一组表单脚本：`extract_form_field_info.py` / `extract_form_structure.py` / `check_fillable_fields.py` / `fill_fillable_fields.py` / `fill_pdf_form_with_annotations.py`，以及验证用的 `check_bounding_boxes.py` / `create_validation_image.py` / `convert_pdf_to_images.py`。

流程是**先探测再填**：不同 PDF 的表单字段命名毫无规律，直接猜字段名必错，所以先 extract 出真实字段结构。

### 扫描件 OCR

扫描版 PDF 里没有文字层，`pdftotext` 提取出来是空的。需要 `pytesseract` + `pdf2image` 走 OCR。

## kami · 紙：精排引擎

`builtin/kami` 是 pdf 插件里的第二个技能，专门做**有设计感的交付物**：暖米色纸面、墨蓝强调色、衬线主导的层级、紧凑的编辑节奏。

### 九种文档类型

| 用户说                              | 文档类型          |
| -------------------------------- | ------------- |
| one-pager / 方案 / 执行摘要            | One-Pager     |
| 白皮书 / 长文 / 年度总结 / 技术报告           | Long Doc      |
| 正式信件 / 辞职信 / 推荐信 / memo          | Letter        |
| 作品集 / case studies               | Portfolio     |
| 简历 / CV / 履歴書                    | Resume        |
| slides / 演示                      | Slides        |
| 个股研报 / 估值分析 / investment memo    | Equity Report |
| 更新日志 / changelog / release notes | Changelog     |
| 落地页 / 官网 / 产品页                   | Landing Page  |

选型靠决策树而不是问用户，只有两类真的都合适时才反问：

| 信号                    | 定为            |
| --------------------- | ------------- |
| 篇幅未知                  | **先问几页**，再分类  |
| ≤1 页 + 投资人/招聘方/高管受众   | one-pager     |
| ≤1 页 + 正式往来函件         | letter        |
| 1.5–2 页 + 履历叙事 + 项目要点 | resume        |
| 3–6 页 + 项目展示 + 视觉密集   | portfolio     |
| 6–15 页 + 持续论证 + 视觉密度低 | long-doc      |
| 演示流 + 讲者支撑 + 每页一个论断   | slides        |
| 财务指标看板 + 论点 + 价格或风险视角 | equity-report |
| 逐版本记录                 | changelog     |
| 产品展示 + 定价 + 截图 + FAQ  | landing-page  |

<Info>
  **Landing Page 不产出 PDF**——它是屏幕优先的交互模板，含画廊轮播、首屏入场动画、响应式断点（880px / 480px）与 `prefers-reduced-motion` 支持，交付物是可直接托管的 `.html`。
</Info>

### 字体

| 语言 | 主字体                                       | 回退链                                                                                         |
| -- | ----------------------------------------- | ------------------------------------------------------------------------------------------- |
| 中文 | TsangerJinKai02-W04（正文）+ W05（标题，真粗体）      | Source Han Serif SC → Noto Serif CJK SC → Songti SC → STSong → Georgia                      |
| 英文 | Charter（正文与标题同一字体，`--sans: var(--serif)`） | Georgia → Palatino → Times New Roman                                                        |
| 日文 | 尽力支持，无专用模板                                | YuMincho → Hiragino Mincho ProN → Noto Serif CJK JP → Source Han Serif JP → TsangerJinKai02 |

中文字体是商用字体。构建中文文档前先跑一次字体自愈脚本：

```bash theme={null}
bash scripts/ensure-fonts.sh
```

它会依次尝试多个 CDN，带重试与体积校验；全部失败时提示改用 Source Han Serif SC 兜底。

<Warning>
  日文目前走 CJK 模板路径，没有专用 `-ja` 模板。交付前必须**人工确认断行、标点节奏与强调字重**——这三项是中日排版差异最容易出问题的地方。
</Warning>

### 模糊反馈处理

用户说"看着不对""太挤了""不够优雅"时，技能规定**不许猜**，而要带着当前数值反问。这条设计的价值在于把一次主观争论变成一次参数调整——"行高现在是 1.6，要调到 1.8 吗"比"我再改改"有效得多。

## 边界与失败态

* **生成 PDF 只有 WeasyPrint 一条路**，坐标绘制库被明确禁止。
* **扫描件必须走 OCR**，否则文字提取是空的。
* **表单字段必须先探测**，直接猜字段名会静默填不进去。
* **kami 的中文字体是商用字体**，不随分发包携带，靠 `ensure-fonts.sh` 现取。
* **Landing Page 没有 PDF 输出**，别指望它导出 PDF。

### 排错

| 症状               | 原因                                 | 处理                                                 |
| ---------------- | ---------------------------------- | -------------------------------------------------- |
| PDF 里中文是方框或缺字    | 没指定 CJK 字体，或 kami 字体没就位            | 设 `'Noto Sans CJK SC'`；kami 场景先跑 `ensure-fonts.sh` |
| `pdftotext` 输出为空 | 扫描件无文字层                            | 走 pytesseract OCR                                  |
| 提取的多栏文字顺序错乱      | 没加 `-layout`                       | 加上 `-layout` 重跑                                    |
| 页码显示成字面量而不是数字    | 没用 `counter(page)` 或写在了 `@page` 之外 | 页码只能在 `@page` 的 margin box 里生成                     |
| 表单填完打开还是空        | 字段名猜错                              | 先 `extract_form_field_info.py` 拿真实字段名              |
| 分页位置不对           | 用了错误的分页属性或被容器阻断                    | 用 `page-break-after: always`，确认没有被 `overflow` 容器包住 |

## 验证你的产出

1. 生成后转成图片逐页看：`convert_pdf_to_images.py`。**光看代码看不出排版事故。**
2. 中文文档搜一遍有没有方框字符——这是字体没生效最典型的症状。
3. 多页文档翻到最后一页，确认 `共 N 页` 的 N 是真实页数而不是字面量。

## 相关页面

<CardGroup cols={2}>
  <Card title="办公文档总览" href="/zh/documentation/capabilities/office-documents">
    四类文档能力的定位与选型
  </Card>

  <Card title="演示文稿" href="/zh/documentation/capabilities/office-pptx">
    需要 PPT 而非 PDF 时走这条
  </Card>
</CardGroup>

<Note>
  核对日期 2026-08-11。来源：`services/agent-runtime/src/plugins/builtin/pdf/plugin.json`、`skills/pdf/SKILL.md`、`skills/kami/SKILL.md`、`skills/pdf/scripts/`、`skills/kami/scripts/ensure-fonts.sh`。
</Note>
