【应用】ClawPDF 完全指南:安装、部署与使用

基于 PDFium WebAssembly 打造的轻量级 PDF 处理库,设计目标极简 ------ 零外部依赖,既可以在 Node.js 后端运行,也能直接在浏览器中工作。它赋予你强大的 PDF 解析能力:提取文本、渲染页面、输出 PNG 图片,而且不需要安装任何原生 Canvas 包,也没有繁琐的 postinstall 脚本。

概述

ClawPDF 是 openclaw 团队开发的开源项目,旨在提供一个纯粹、安全、高效 的 PDF 处理工具。它的核心是 PDFium(Chrome 浏览器内置的 PDF 渲染引擎),通过 WebAssembly 编译后,可在任何现代 JavaScript 环境中运行。

✨ 核心亮点

  • 🚀 零依赖 -- 无需安装 cairopangocanvas 等原生库
  • 🌐 跨平台 -- 完美支持 Node.js 22+ 和所有现代浏览器
  • 🔒 安全 -- WASM 沙箱运行,不暴露文件系统(浏览器环境)
  • 高性能 -- 基于 PDFium 原生渲染,RGBA 位图直接输出
  • 🖼️ 内置 PNG 编码器 -- 无需额外库即可生成压缩 PNG
  • 🧩 清晰的生命周期管理 -- 支持 Symbol.disposeasyncDispose,避免内存泄漏

安装

使用 npm 一键安装:

bash 复制代码
npm install clawpdf

系统要求

  • Node.js 22+(ESM-only 模块,不支持 CommonJS require)
  • 现代浏览器(支持 WebAssembly 和 CompressionStream

安装完成后,你即可在代码中引入,同时也获得了命令行工具 clawpdf


核心概念:引擎与文档

ClawPDF 的架构,核心组件的关系:
#mermaid-svg-zV1OR2v06vtLRYEm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zV1OR2v06vtLRYEm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zV1OR2v06vtLRYEm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zV1OR2v06vtLRYEm .error-icon{fill:#552222;}#mermaid-svg-zV1OR2v06vtLRYEm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zV1OR2v06vtLRYEm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zV1OR2v06vtLRYEm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zV1OR2v06vtLRYEm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zV1OR2v06vtLRYEm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zV1OR2v06vtLRYEm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zV1OR2v06vtLRYEm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zV1OR2v06vtLRYEm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zV1OR2v06vtLRYEm .marker.cross{stroke:#333333;}#mermaid-svg-zV1OR2v06vtLRYEm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zV1OR2v06vtLRYEm p{margin:0;}#mermaid-svg-zV1OR2v06vtLRYEm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zV1OR2v06vtLRYEm .cluster-label text{fill:#333;}#mermaid-svg-zV1OR2v06vtLRYEm .cluster-label span{color:#333;}#mermaid-svg-zV1OR2v06vtLRYEm .cluster-label span p{background-color:transparent;}#mermaid-svg-zV1OR2v06vtLRYEm .label text,#mermaid-svg-zV1OR2v06vtLRYEm span{fill:#333;color:#333;}#mermaid-svg-zV1OR2v06vtLRYEm .node rect,#mermaid-svg-zV1OR2v06vtLRYEm .node circle,#mermaid-svg-zV1OR2v06vtLRYEm .node ellipse,#mermaid-svg-zV1OR2v06vtLRYEm .node polygon,#mermaid-svg-zV1OR2v06vtLRYEm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zV1OR2v06vtLRYEm .rough-node .label text,#mermaid-svg-zV1OR2v06vtLRYEm .node .label text,#mermaid-svg-zV1OR2v06vtLRYEm .image-shape .label,#mermaid-svg-zV1OR2v06vtLRYEm .icon-shape .label{text-anchor:middle;}#mermaid-svg-zV1OR2v06vtLRYEm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zV1OR2v06vtLRYEm .rough-node .label,#mermaid-svg-zV1OR2v06vtLRYEm .node .label,#mermaid-svg-zV1OR2v06vtLRYEm .image-shape .label,#mermaid-svg-zV1OR2v06vtLRYEm .icon-shape .label{text-align:center;}#mermaid-svg-zV1OR2v06vtLRYEm .node.clickable{cursor:pointer;}#mermaid-svg-zV1OR2v06vtLRYEm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zV1OR2v06vtLRYEm .arrowheadPath{fill:#333333;}#mermaid-svg-zV1OR2v06vtLRYEm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zV1OR2v06vtLRYEm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zV1OR2v06vtLRYEm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zV1OR2v06vtLRYEm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zV1OR2v06vtLRYEm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zV1OR2v06vtLRYEm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zV1OR2v06vtLRYEm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zV1OR2v06vtLRYEm .cluster text{fill:#333;}#mermaid-svg-zV1OR2v06vtLRYEm .cluster span{color:#333;}#mermaid-svg-zV1OR2v06vtLRYEm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-zV1OR2v06vtLRYEm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zV1OR2v06vtLRYEm rect.text{fill:none;stroke-width:0;}#mermaid-svg-zV1OR2v06vtLRYEm .icon-shape,#mermaid-svg-zV1OR2v06vtLRYEm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zV1OR2v06vtLRYEm .icon-shape p,#mermaid-svg-zV1OR2v06vtLRYEm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zV1OR2v06vtLRYEm .icon-shape .label rect,#mermaid-svg-zV1OR2v06vtLRYEm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zV1OR2v06vtLRYEm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zV1OR2v06vtLRYEm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zV1OR2v06vtLRYEm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 环境
open()
page(n)
text()
render()
png()
依赖
🔧 PDFium WebAssembly

(底层引擎)
📦 PdfEngine

WASM 实例 + 资源池
📄 PdfDocument

PDF 文件句柄
📑 PdfPage

单页操作
📝 提取文本
🎨 RGBA 位图
🖼️ PNG 图片

  • PdfEngine:持有一个 WASM 实例,负责管理内存和资源。它可以打开多个文档,是服务端复用的核心。
  • PdfDocument:代表一个已加载的 PDF 文件,提供页面访问、元数据读取和全文提取。
  • PdfPage :代表文档中的一页(页码从 1 开始),可渲染为 RGBA 位图或直接编码为 PNG。

两种打开方式

方式 适用场景 生命周期管理
openPdf(...) 脚本、一次性任务 自动创建私有引擎,文档销毁时引擎一并销毁
createEngine() + engine.open() 服务器常驻进程、批量处理 引擎长期存活,文档需手动销毁(或通过 await using 自动释放)

一次性使用(推荐)

ts 复制代码
import { openPdf } from "clawpdf";

await using pdf = await openPdf("report.pdf");
console.log(pdf.pageCount); // 页面总数
// 当离开作用域时,pdf 自动销毁,私有引擎也随之释放

服务器复用引擎

ts 复制代码
import { createEngine } from "clawpdf";

await using engine = await createEngine(); // 引擎在应用生命周期内复用
const pdf = await engine.open(pdfBytes);
try {
  console.log(pdf.text());
} finally {
  pdf.destroy(); // 必须手动释放文档内存
}
// engine 会在作用域结束时自动销毁(因为使用了 await using)

💡 最佳实践 :在服务端,保持一个 engine 实例存活,每次请求通过 engine.open() 打开新文档,处理完成后立即 destroy() 文档。这样既避免了反复初始化 WASM 的开销,又防止了内存泄漏。


加载 PDF

openPdf()engine.open() 都接受多种输入类型:

输入类型 说明
Uint8Array / ArrayBuffer 内存中的 PDF 字节数据
string(Node.js) 本地文件路径,从磁盘读取
string(浏览器) 必须是 URL (如 "https://example.com/doc.pdf"
URL 对象 任何环境下的 HTTP/HTTPS URL
Blob 浏览器环境,通过 arrayBuffer() 读取

远程请求配置

ts 复制代码
await using pdf = await openPdf("https://example.com/doc.pdf", {
  fetchTimeoutMs: 10_000,          // 10 秒超时,0 表示不限制
  signal: controller.signal,       // 使用 AbortController 取消请求
});

⚠️ 在浏览器中,如果传入一个看起来像路径的字符串(如 "./local.pdf"),会抛出 PdfFormatError,因为浏览器无法直接读取文件系统。请使用 <input type="file"> 获取 File 对象,或通过 fetch 获取远程资源。


文本提取

所有页码均从 1 开始计数。ClawPDF 提取的文本顺序遵循 PDFium 的内部排序,可能与视觉阅读顺序不一致(尤其是复杂排版的 PDF),但大多数场景下足够使用。

单页提取

ts 复制代码
const firstPageText = pdf.page(1).text();

多页提取

ts 复制代码
const text = pdf.text({
  maxPages: 5,            // 最多处理 5 页(从第1页开始)
  pages: [1, 3, 4],       // 指定页码列表(优先级高于 maxPages)
  maxChars: 200_000,      // 最大字符数,超出则截断并标记 truncated.text = true
});
  • 若同时指定 pagesmaxPages,则 pages 优先,maxPages 被忽略。
  • 无效页码(超出文档范围)会抛出 PdfPageRangeError
  • maxChars 默认值为 200,000,有效防止超大 PDF 产生的文本数据撑爆内存或 AI 调用限额。

页面渲染

通过 pdf.page(n).render(options) 将页面渲染为 RGBA 位图,返回 { width, height, rgba }

尺寸控制(四选一)

参数 说明 示例
dpi 以 72 DPI 为基准缩放,默认 96 { dpi: 144 } → 2 倍大小
scale 直接缩放比例,1 表示 72 DPI { scale: 1.5 }
width 目标像素宽度,高度自动按比例 { width: 800 }
height 目标像素高度,宽度自动按比例 { height: 600 }

如果不提供任何尺寸参数,默认使用 { dpi: 96 }禁止同时指定多个 ,否则会抛出 PdfError

渲染选项

选项 默认值 说明
background "white" 背景色:"white""transparent"
forms false 是否渲染 AcroForm 表单控件(如输入框、按钮)
rotate 0 额外旋转角度:090180270(在页面自带旋转基础上叠加)

示例

ts 复制代码
const rendered = pdf.page(1).render({
  dpi: 144,
  forms: true,
  background: "transparent",
  rotate: 90,        // 顺时针旋转 90 度
});
console.log(rendered.width, rendered.height);
console.log(rendered.rgba.byteLength); // 始终 = width * height * 4

🚨 渲染尺寸有硬上限 maxRenderPixels(引擎配置项,默认 10,000,000 像素)。若超出预算,会抛出 PdfBudgetError


PNG 输出

ClawPDF 内置纯 JavaScript PNG 编码器,Node.js 和浏览器通用,无需安装 sharpcanvas

异步 PNG(推荐)

ts 复制代码
import { writeFile } from "node:fs/promises";
const png = await pdf.page(1).png({ dpi: 144, forms: true });
await writeFile("page-1.png", png);

异步版本使用 node:zlib(Node)或 CompressionStream(浏览器)进行压缩,输出文件小。

同步 PNG

ts 复制代码
const png = pdf.page(1).pngSync({ scale: 2 });

同步版本仅使用存储的 zlib 块(不重新压缩),生成的文件较大,但适用于不允许异步操作的严格场景。

独立编码 RGBA

如果你已有 { rgba, width, height } 数据,可以直接调用 encodePng

ts 复制代码
import { encodePng } from "clawpdf";

// 压缩(异步)
const compressed = await encodePng(rgba, { width, height });

// 不压缩(同步)
const stored = encodePng(rgba, { width, height, compress: false });

智能提取回退(extractPdf)

extractPdf 是专门为 AI 应用设计的高级助手函数 ,它遵循 "先文本,后图片" 的策略,确保你总能获得有意义的内容,无论是纯文本 PDF 还是扫描件。

快速上手

ts 复制代码
import { extractPdf } from "clawpdf";

const result = await extractPdf("report.pdf", {
  mode: "auto",                // 自动模式
  maxPages: 20,
  minTextChars: 200,           // 文本阈值
  image: {
    dpi: 96,
    maxPixels: 4_000_000,
    maxDimension: 10_000,
    forms: true,
  },
});

四种工作模式

模式 行为
auto 默认 。先提取文本,若总字符数 < minTextChars,则渲染对应页为图片
text 仅提取文本(不渲染图片)
images 仅渲染图片(不提取文本)
both 同时提取文本和渲染图片(所有页面)

auto 模式流程图

#mermaid-svg-WycowgiWCGW5EoWI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WycowgiWCGW5EoWI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WycowgiWCGW5EoWI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WycowgiWCGW5EoWI .error-icon{fill:#552222;}#mermaid-svg-WycowgiWCGW5EoWI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WycowgiWCGW5EoWI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WycowgiWCGW5EoWI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WycowgiWCGW5EoWI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WycowgiWCGW5EoWI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WycowgiWCGW5EoWI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WycowgiWCGW5EoWI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WycowgiWCGW5EoWI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WycowgiWCGW5EoWI .marker.cross{stroke:#333333;}#mermaid-svg-WycowgiWCGW5EoWI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WycowgiWCGW5EoWI p{margin:0;}#mermaid-svg-WycowgiWCGW5EoWI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-WycowgiWCGW5EoWI .cluster-label text{fill:#333;}#mermaid-svg-WycowgiWCGW5EoWI .cluster-label span{color:#333;}#mermaid-svg-WycowgiWCGW5EoWI .cluster-label span p{background-color:transparent;}#mermaid-svg-WycowgiWCGW5EoWI .label text,#mermaid-svg-WycowgiWCGW5EoWI span{fill:#333;color:#333;}#mermaid-svg-WycowgiWCGW5EoWI .node rect,#mermaid-svg-WycowgiWCGW5EoWI .node circle,#mermaid-svg-WycowgiWCGW5EoWI .node ellipse,#mermaid-svg-WycowgiWCGW5EoWI .node polygon,#mermaid-svg-WycowgiWCGW5EoWI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WycowgiWCGW5EoWI .rough-node .label text,#mermaid-svg-WycowgiWCGW5EoWI .node .label text,#mermaid-svg-WycowgiWCGW5EoWI .image-shape .label,#mermaid-svg-WycowgiWCGW5EoWI .icon-shape .label{text-anchor:middle;}#mermaid-svg-WycowgiWCGW5EoWI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WycowgiWCGW5EoWI .rough-node .label,#mermaid-svg-WycowgiWCGW5EoWI .node .label,#mermaid-svg-WycowgiWCGW5EoWI .image-shape .label,#mermaid-svg-WycowgiWCGW5EoWI .icon-shape .label{text-align:center;}#mermaid-svg-WycowgiWCGW5EoWI .node.clickable{cursor:pointer;}#mermaid-svg-WycowgiWCGW5EoWI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WycowgiWCGW5EoWI .arrowheadPath{fill:#333333;}#mermaid-svg-WycowgiWCGW5EoWI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WycowgiWCGW5EoWI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WycowgiWCGW5EoWI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WycowgiWCGW5EoWI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WycowgiWCGW5EoWI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WycowgiWCGW5EoWI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WycowgiWCGW5EoWI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WycowgiWCGW5EoWI .cluster text{fill:#333;}#mermaid-svg-WycowgiWCGW5EoWI .cluster span{color:#333;}#mermaid-svg-WycowgiWCGW5EoWI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WycowgiWCGW5EoWI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WycowgiWCGW5EoWI rect.text{fill:none;stroke-width:0;}#mermaid-svg-WycowgiWCGW5EoWI .icon-shape,#mermaid-svg-WycowgiWCGW5EoWI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WycowgiWCGW5EoWI .icon-shape p,#mermaid-svg-WycowgiWCGW5EoWI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WycowgiWCGW5EoWI .icon-shape .label rect,#mermaid-svg-WycowgiWCGW5EoWI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WycowgiWCGW5EoWI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WycowgiWCGW5EoWI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WycowgiWCGW5EoWI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是





开始 extractPdf
提取指定页面的文本
文本长度 >= minTextChars?
返回文本结果, 不渲染图片
开始渲染页面为 PNG
是否超过图片预算?

maxPixels / maxDimension
停止渲染, 返回已生成的图片和文本
继续渲染下一页
所有页面渲染完成?
返回全部文本 + 图片

提取选项详解

选项 默认值 说明
pages --- 要处理的页码列表(1-based),未指定则从第1页开始
maxPages 20 最大处理页数(若未指定 pages
minTextChars 200 触发图片回退的文本阈值(仅 auto 模式)
maxTextChars 200_000 文本输出上限,超出则截断
password --- PDF 用户密码
engine --- 可选的外部引擎实例(复用)
image.dpi 96 回退图片的 DPI
image.maxPixels 4_000_000 所有回退图片的总像素预算
image.maxDimension 10_000 单张图片的最大宽或高(像素)
image.forms true 是否渲染表单控件

返回结果结构

ts 复制代码
type ExtractResult = {
  text: string;                  // 提取的文本
  images: Array<{               // 渲染的图片列表
    page: number;
    width: number;
    height: number;
    bytes: Uint8Array;          // PNG 字节
    mimeType: "image/png";
  }>;
  pagesProcessed: number[];     // 实际处理的页码
  truncated: {                  // 是否因预算限制而截断
    text: boolean;
    images: boolean;
  };
};

适配器(方便对接 AI 模型)

ClawPDF 提供了两个内置适配器,方便你将结果直接用于多模态模型:

ts 复制代码
import { toDataUrls, toMessageContent } from "clawpdf/adapters";

// 转为 Data URL 数组(可用于浏览器 img 标签)
const urls = toDataUrls(result);

// 转为 Anthropic/OpenAI 风格的消息内容块
const content = toMessageContent(result);
// 输出: [{ type: "text", text: "..." }, { type: "image", source: { data: "...", media_type: "image/png" } }]

密码保护的 PDF

ClawPDF 支持打开标准密码加密的 PDF(AES-128 或 AES-256)。

ts 复制代码
// 文档 API
await using pdf = await openPdf("secret.pdf", { password: "myPassword" });

// extractPdf 同样支持
const result = await extractPdf("secret.pdf", {
  password: "myPassword",
  minTextChars: 200,
});

密码错误或缺失会抛出 PdfPasswordError;若 PDF 使用了不支持的加密算法(如某些专有加密),则抛出 PdfSecurityError

🔐 安全建议 :在脚本或 CLI 中,避免将密码写在命令行中(会被 shell 历史记录)。推荐使用 --password-file 选项(见下文 CLI 部分)。


命令行工具(CLI)

安装 clawpdf 后,会自动注册 clawpdf 命令,适合快速测试、脚本集成或一次性转换。

提取文本(默认行为)

bash 复制代码
clawpdf report.pdf                    # 输出文本到 stdout
cat report.pdf | clawpdf -            # 从管道读取(- 表示 stdin)

JSON 格式输出(含图片 Base64)

bash 复制代码
clawpdf report.pdf --json
clawpdf extract report.pdf --mode both --pages 1,3-5 --json

JSON 结构同 ExtractResult,其中图片的 bytes 被替换为 base64 字符串。

渲染单页为 PNG

bash 复制代码
clawpdf render report.pdf --page 1 > page.png
clawpdf render report.pdf --page 1 -o page.png
clawpdf render report.pdf --page 1 --inline auto   # 终端内联显示(支持 Kitty/iTerm2)

常用 CLI 参数

参数 说明
`--mode auto text
--pages 1,3-5 选择页码(1-based,支持范围)
--max-pages N 最大页数(默认 20)
--max-text-chars N 文本输出上限
--min-text-chars N 触发图片回退的文本阈值
--dpi N / --scale N 图片 DPI 或缩放
--max-pixels N / --max-dimension N 图片预算限制
--forms / --no-forms 是否渲染表单控件
--password <value> 密码(不安全,避免使用)
--password-file <path> 从文件读取密码(推荐)
--output-dir <dir> 将回退图片写入 page-N.png 文件
`--inline auto kitty

退出码

含义
0 成功
1 运行时错误(提取/渲染失败)
2 无效参数或用法
3 无法读取或解析为 PDF
4 密码缺失或错误
5 渲染或提取预算超限

在浏览器中使用

浏览器环境需使用专用入口 clawpdf/browser,它通过 import.meta.url 预置了打包好的 WASM 文件路径,无需额外配置。

ts 复制代码
import { openPdf } from "clawpdf/browser";

// 假设从 <input type="file"> 获取 File 对象
const fileInput = document.getElementById("pdfInput") as HTMLInputElement;
const file = fileInput.files[0];
await using pdf = await openPdf(file);
console.log(pdf.text({ maxPages: 3 }));

自定义 WASM 路径

若需要从 CDN 或自己的服务器加载 WASM,可以传入 wasmUrl

ts 复制代码
import { createEngine } from "clawpdf/browser";

await using engine = await createEngine({
  wasmUrl: "/assets/pdfium.esm.wasm",
});
const pdf = await engine.open(file);

自定义实例化

对于特殊环境(如 Service Worker 或 Deno),可提供 instantiateWasm 钩子:

ts 复制代码
await using engine = await createEngine({
  instantiateWasm(imports, receiveInstance) {
    // 使用 WebAssembly.instantiate 或自定义加载逻辑
    WebAssembly.instantiateStreaming(fetch("/pdfium.wasm"), imports)
      .then(result => receiveInstance(result.instance));
  },
});

⚠️ 注意 :浏览器中 openPdf 的字符串输入必须是 URL,路径字符串(如 "/local.pdf")会抛出错误。如需加载本地文件,请使用 FileBlob


错误类型一览

所有公共 API 错误均继承自 PdfError,便于统一捕获。

错误类 触发条件
PdfError 所有错误的基类
PdfPasswordError 密码缺失或错误
PdfFormatError 输入无效、路径不存在、获取失败、PDF 格式损坏
PdfSecurityError 不支持的 PDF 安全处理器(如某些专有加密)
PdfPageRangeError 请求的页码超出文档范围
PdfBudgetError 渲染像素预算或文本字符预算超限
PdfDestroyedError 在文档或引擎销毁后调用方法

页面属性

PdfPage 对象暴露了以下只读属性:

属性 说明
index 页码(从 1 开始)
width 页面宽度(PDF 单位,1/72 英寸)
height 页面高度(PDF 单位)
rotation 页面自带的旋转角度(0, 90, 180, 270)

服务端部署最佳实践

在 Node.js 生产环境中,建议如下模式:

ts 复制代码
import { createEngine } from "clawpdf";

// 应用启动时创建引擎(全局单例)
const engine = await createEngine({
  maxRenderPixels: 8_000_000,  // 根据服务器内存调整
});

// 处理请求的控制器
async function handlePdfRequest(input: Buffer | Uint8Array) {
  const pdf = await engine.open(input);
  try {
    // 尝试提取文本
    const text = pdf.text({ maxPages: 10 });
    if (text.length >= 200) {
      return { text }; // 文本充足,无需渲染图片
    }
    // 文本不足,回退到图片
    const png = await pdf.page(1).png({ dpi: 144 });
    return { text, images: [png] };
  } finally {
    pdf.destroy(); // 必须释放文档内存
  }
}

// 优雅关闭时销毁引擎(如果使用 await using 则自动处理)
// 若未使用 await using,需在退出前调用 engine.destroy()

关键点

  • ✅ 全局复用 engine,避免重复加载 WASM。
  • ✅ 每个请求打开新文档,处理完后立即 destroy()
  • ✅ 使用 try/finallyawait using 确保资源释放。
  • ✅ 根据可用内存调整 maxRenderPixels,防止 OOM。

API 快速参考

导出函数

ts 复制代码
export {
  createEngine,          // 创建引擎实例
  encodePng,             // 独立 PNG 编码
  extractPdf,            // 高级提取助手
  openPdf,               // 快速打开(自管理引擎)
  releaseExtractEngine,  // 释放 extractPdf 内部缓存的引擎(用于测试)
  PdfError,
  PdfPasswordError,
  PdfFormatError,
  PdfSecurityError,
  PdfPageRangeError,
  PdfBudgetError,
  PdfDestroyedError,
  PDFIUM_RELEASE,        // 当前 PDFium 版本
  PDFIUM_WASM_SHA256,    // WASM 文件校验和
};

核心类型

  • PdfInputUint8Array | ArrayBuffer | string | URL | Blob
  • PdfEngine:引擎实例
  • PdfDocument:文档实例
  • PdfPage:页面实例
  • RenderOptions:渲染选项
  • ExtractOptions:提取选项
  • ExtractResult:提取结果
  • PdfImage:图片数据

故障排除与性能建议

常见问题

问题 可能原因 解决方案
PdfFormatError 输入不是有效的 PDF 文件 检查文件是否损坏,或路径是否正确
PdfPasswordError 需要密码但未提供,或密码错误 提供正确密码,或确认 PDF 是否加密
PdfBudgetError 渲染像素或文本字符超出限制 降低 DPI/缩放,或增加 maxChars / maxPixels
内存占用过高 同时打开过多文档未释放 确保每个文档处理完后调用 destroy()
浏览器跨域问题 通过 fetch 加载 PDF 时跨域 配置 CORS 或使用同源 URL

性能优化小贴士

  • 🧠 复用引擎 :服务端环境中,createEngine 开销较大(加载 WASM 约 1~2 秒),务必全局复用。
  • 📄 及时销毁文档 :文档对象占用内存包含所有页面数据,处理完立即 destroy()
  • 🖼️ 控制图片输出 :使用 maxPixelsmaxDimension 防止渲染超大图片(如工程图纸)。
  • 📊 文本提取优先 :对于文本型 PDF,优先使用 text() 而非渲染,速度更快且内存友好。
  • ⏱️ 合理设置超时:远程 PDF 读取默认 30 秒,可根据网络情况调整。
相关推荐
Leslie1651 小时前
柱子只进栈一次为何能算最大矩形:单调栈逐步可视化
人工智能
两万五千个小时1 小时前
DeepSeek Harness 的动力引擎:Agent Loop 是怎么转起来的
人工智能·程序员·架构
AI轻职场1 小时前
#DeepSeek、Qwen、GLM 都在卷 Coding,Java 开发者真正应该学什么?
人工智能
Leslie1651 小时前
注意力不是全连接层换名字:多头自注意力的张量实验
人工智能
Postkarte不想说话1 小时前
vLLM自定义对话模板
人工智能
具身AGI1 小时前
宇树打新:物理AI 国产的「本体」先跑通了商业化
人工智能
Json____1 小时前
AI内容创作平台项目源码
人工智能·ai·agent·内容创作·wwwoop.com
Jay-r1 小时前
DeepSeek Harness 极简上手:装好、玩熟、让它自己长新能力
人工智能·windows·ai·github·ai编程·deepseek·harness
CodeBlog-star1 小时前
LLM能力与边界:多模态、幻觉、上下文窗口及开源模型对比
人工智能·python·开源·llm
COOLMO研究AI1 小时前
Python 如何实现 AI API 的动态路由与多通道负载均衡:多账号与多供应商的高可用调度
人工智能·python·负载均衡