PDF 读/写/转/OCR 一体化。触发场景:总结 PDF、读文本或表格、填 AcroForm 表单、渲染页面为图、合并/拆分/水印/旋转/加密、创建新 PDF(发票/报告)、PDF ↔ docx/md/txt 互转。典型说法:“总结这份 PDF”、“填一下这个 PDF 表单”、“把几个 PDF 合一起”、“PDF 加水印”、“PDF 转 Word”、“扫描件 OCR”、“看看 PDF 第 3 页长什么样”。用户提到 .pdf 文件、要产出 PDF、或要对 PDF 做任何改动都加载本技能。
76
95%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
读用 PdfInspect;写 / 改 / 创建一律用 JsSandbox 跑 Node 脚本调用 pdf-lib / pdfkit / pdf-parse 等预装库;格式互转用 DocConvert;扫描件走 CloudImageUnderstand。
| 工具 | 做什么 | 只读 |
|---|---|---|
PdfInspect | 读:summary / text / tables / form-fields / form-structure / images / annotations / render | 是 |
JsSandbox | 所有写操作:创建新 PDF、合并、水印、旋转、填表、加密…… 用 pdf-lib / pdfkit 代码实现 | 否 |
DocConvert | 格式互转:pdf ↔ docx / html / md / txt / xlsx | 否 |
CloudImageUnderstand | 扫描 PDF 的 OCR 入口(云端,会扣积分) | 否 |
加载(两步,缺一不可):
LoadSkill pdf-skill—— 仅把本技能文档拉进上下文。ToolSearch(query: "select:PdfInspect,JsSandbox,DocConvert")—— 激活工具 schema。
Read对 .pdf 只走轻量摘要;任何分析/总结/填/改/转/OCR PDF 都走PdfInspect+JsSandbox。
PdfInspect 是唯一入口不知道 PDF 长啥样先 summary:
PdfInspect { action: "summary", filePath: "…" }返回 pageCount / fileSize / characterCount / hasForm / encryption / textType,按 textType:
| textType | 下一步 |
|---|---|
extractable | PdfInspect(text) 直接取全文 |
scanned / cid-encoded | PdfInspect(render, pageRange) → 再调 CloudImageUnderstand 对每张 PNG OCR |
isEncrypted: true | 走下面 JsSandbox pdf-lib 解密,或 PdfInspect(password) |
大 PDF 必传 pageRange(如 "1-5"),否则触发截断。
AcroForm 表单:PdfInspect(form-fields) 拿 checkedValue / radioOptions 再填。
JsSandbox + pdf-lib / pdfkit首选 pdf-lib(编辑已有 PDF / 合并 / 水印 / 旋转 / 填表 / 解密);
从零生成复杂布局用 pdfkit(矢量 + 文字 + 图)。
pdfkit)import PDFDocument from 'pdfkit'
import fs from 'node:fs'
const doc = new PDFDocument({ size: 'A4', margin: 48 })
doc.pipe(fs.createWriteStream('invoice.pdf'))
doc.fontSize(22).text('INVOICE', { align: 'right' })
doc.moveDown()
doc.fontSize(10).text('Acme Corp', { align: 'right' })
doc.text('Invoice #2026-042 Date: 2026-04-20', { align: 'right' })
doc.moveDown()
// 简易表格
const rows = [
['Widget', 1, 50.00],
['Gadget', 2, 30.00],
]
doc.fontSize(12).text('Description Qty Unit Total')
doc.moveTo(48, doc.y).lineTo(547, doc.y).stroke()
let total = 0
for (const [name, qty, unit] of rows) {
const line = qty * unit
total += line
doc.text(`${name.padEnd(14)} ${qty} $${unit.toFixed(2)} $${line.toFixed(2)}`)
}
doc.moveDown().fontSize(14).text(`Total: $${total.toFixed(2)}`, { align: 'right' })
doc.end()
await new Promise(r => doc.on('end', r))
console.log('invoice.pdf written')CJK 提示:pdfkit 默认字体不含中文字形。若要出中文,先用
doc.registerFont('cn', '/path/to/NotoSansCJK.ttf')再doc.font('cn')。系统 fallback 字体不保证覆盖。
CONFIDENTIAL 水印(pdf-lib)import { PDFDocument, rgb, degrees, StandardFonts } from 'pdf-lib'
import fs from 'node:fs/promises'
const src = await fs.readFile(process.argv[2] ?? 'input.pdf')
const pdf = await PDFDocument.load(src)
const font = await pdf.embedFont(StandardFonts.HelveticaBold)
for (const page of pdf.getPages()) {
const { width, height } = page.getSize()
page.drawText('CONFIDENTIAL', {
x: width / 2 - 140,
y: height / 2,
size: 64,
font,
color: rgb(0.9, 0.1, 0.1),
opacity: 0.25,
rotate: degrees(-30),
})
}
const out = await pdf.save()
await fs.writeFile('watermarked.pdf', out)
console.log(`watermarked ${pdf.getPageCount()} pages`)pdf-lib)import { PDFDocument } from 'pdf-lib'
import fs from 'node:fs/promises'
const merged = await PDFDocument.create()
for (const p of ['a.pdf', 'b.pdf', 'c.pdf']) {
const src = await PDFDocument.load(await fs.readFile(p))
const pages = await merged.copyPages(src, src.getPageIndices())
pages.forEach(pg => merged.addPage(pg))
}
await fs.writeFile('merged.pdf', await merged.save())
console.log(`merged → ${merged.getPageCount()} pages`)PdfInspect(form-fields) 拿 checkedValue)import { PDFDocument } from 'pdf-lib'
import fs from 'node:fs/promises'
const pdf = await PDFDocument.load(await fs.readFile('form.pdf'))
const form = pdf.getForm()
form.getTextField('full_name').setText('张三')
form.getCheckBox('agree').check() // 若 PdfInspect 给出 checkedValue='Y',用 setValue('Y')
form.getRadioGroup('gender').select('M')
// form.flatten() // 想锁表单就 flatten
await fs.writeFile('filled.pdf', await pdf.save())
console.log('form filled')// 解密
import { PDFDocument } from 'pdf-lib'
const pdf = await PDFDocument.load(buf, { password: 'secret', ignoreEncryption: false })
await fs.writeFile('unlocked.pdf', await pdf.save())
// 旋转第 1 页 90°
pdf.getPage(0).setRotation(degrees(90))
// 抽出第 3、5 页到新 PDF
const out = await PDFDocument.create()
const [p3, p5] = await out.copyPages(pdf, [2, 4])
out.addPage(p3); out.addPage(p5)PdfInspect(summary) // textType == 'scanned'
PdfInspect(render, pageRange) // 得到 [<asset>/render-p1.png, ...]
CloudImageUnderstand(image=<上面每张 png>, question='精确识别并提取中文文字')DocConvert(from="pdf", to="docx", sourcePath="…") # PDF → Word 编辑
DocConvert(from="docx", to="pdf", sourcePath="…") # Word → PDF 分发DocConvert 比 JsSandbox + pdf-lib 快且排版忠实;互转优先它。
| 症状 | 原因 | 处理 |
|---|---|---|
pdfkit 中文乱码 / 方块 | 默认字体不含 CJK 字形 | 先 registerFont('cn', '/path/to/NotoSansCJK.ttf') |
pdf-lib 加载报 EncryptedPDFError | 文件加密 | PDFDocument.load(buf, { password: '…' }) |
| 水印覆盖不均 | PDF 页面大小不同 | 循环 page.getSize() 按页算 x/y |
| 合并后字体变样 | 原 PDF 嵌字体不完整 | 用 pdf-lib 的 useObjectStreams: false 重写 |
TypeError: Cannot read properties of undefined (reading 'save') | pdfkit 没有 doc.save() / doc.restore()(那是 Canvas 2D API) | 颜色/字号状态直接用 doc.fillColor() / doc.fontSize() 切换即可,不需要保存-恢复栈。最终文档落盘用 doc.pipe(fs.createWriteStream(...)); doc.end() |
修脚本:JsSandbox(action="edit-and-run", scriptPath=<上次的>, edits=[{find,replace}]) 只传改动点。
a1ab5be
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.