# node-pdf2img
High-performance PDF to image converter using PDFium native renderer + Sharp image encoding.
[](https://badge.fury.io/js/node-pdf2img)
[](https://github.com/your-username/node-pdf2img/actions)
## 特性
- **原生性能**:使用 PDFium C++ 库通过 Rust 绑定实现高性能 PDF 渲染
- **Sharp 编码**:使用 libvips 的 Sharp 库进行高效图像编码
- **多线程处理**:使用 piscina 线程池,充分利用多核 CPU 并行处理
- **零拷贝文件读取**:原生模块直接读取文件路径,避免 Node.js 堆内存占用
- **异步 I/O**:主线程负责协调和 I/O,工作线程负责 CPU 密集型任务
- **并发控制**:文件写入和 COS 上传使用 p-limit 控制并发,避免资源耗尽
- **多种输入源**:支持本地文件、URL 或 Buffer
- **多种输出目标**:支持本地文件、Buffer 或腾讯云 COS
- **多种输出格式**:支持 WebP、PNG、JPG 格式
- **CLI 和 API**:支持命令行使用或作为 Node.js 模块引用
## Installation
```bash
# Install as project dependency (for API usage)
npm install node-pdf2img
# Install globally (for CLI usage)
npm install -g node-pdf2img
```
## CLI 使用
```bash
# 基本用法 - 转换所有页面(默认 WebP 格式)
pdf2img document.pdf -o ./output
# 转换指定页面
pdf2img document.pdf -p 1,2,3 -o ./output
# 从 URL 转换
pdf2img https://example.com/document.pdf -o ./output
# 自定义质量和宽度
pdf2img document.pdf -q 90 -w 2560 -o ./output
# 输出 PNG 格式
pdf2img document.pdf -f png -o ./output
# 输出 JPG 格式
pdf2img document.pdf -f jpg -q 85 -o ./output
# 显示 PDF 信息
pdf2img document.pdf --info
# 详细输出
pdf2img document.pdf -o ./output -v
# 上传到腾讯云 COS(需先配置环境变量)
pdf2img document.pdf --cos --cos-prefix images/doc-123
```
### CLI 选项
| 选项 | 说明 | 默认值 |
|------|------|--------|
| `-o, --output
` | 输出目录(本地模式) | `./output` |
| `-p, --pages ` | 页码(逗号分隔) | 全部页面 |
| `-w, --width ` | 目标渲染宽度(像素) | `1280` |
| `-q, --quality ` | 图片质量(0-100,用于 webp/jpg) | `80` |
| `-f, --format ` | 输出格式:webp, png, jpg | `webp` |
| `--prefix ` | 输出文件名前缀 | `page` |
| `--info` | 仅显示 PDF 信息 | |
| `--version-info` | 显示渲染器版本 | |
| `-v, --verbose` | 详细输出 | |
| `--cos` | 上传到腾讯云 COS | |
| `--cos-prefix ` | COS key 前缀 | |
### COS 上传配置
CLI 支持通过环境变量配置 COS 上传参数:
```bash
# 设置环境变量
export COS_SECRET_ID=your-secret-id
export COS_SECRET_KEY=your-secret-key
export COS_BUCKET=your-bucket-name
export COS_REGION=ap-guangzhou
# 使用 --cos 选项上传
pdf2img document.pdf --cos --cos-prefix images/doc-123
```
也可以通过命令行参数指定(不推荐,敏感信息会暴露在命令行历史中):
```bash
pdf2img document.pdf --cos \
--cos-secret-id xxx \
--cos-secret-key xxx \
--cos-bucket xxx \
--cos-region ap-guangzhou \
--cos-prefix images/doc-123
```
## API 使用
### 基本用法
```javascript
import { convert, getPageCount, isAvailable } from '@tencent/pdf2img';
// 检查渲染器是否可用
if (!isAvailable()) {
console.error('原生渲染器不可用');
process.exit(1);
}
// 转换 PDF 为图片(返回 Buffer)
const result = await convert('./document.pdf');
console.log(`转换了 ${result.renderedPages} 页`);
for (const page of result.pages) {
console.log(`第 ${page.pageNum} 页: ${page.width}x${page.height}`);
// page.buffer 包含图片数据
}
```
### 保存到文件
```javascript
const result = await convert('./document.pdf', {
outputType: 'file',
outputDir: './output',
prefix: 'doc',
});
for (const page of result.pages) {
console.log(`已保存: ${page.outputPath}`);
}
```
### 指定输出格式
```javascript
// 输出 PNG 格式
const result = await convert('./document.pdf', {
format: 'png',
outputType: 'file',
outputDir: './output',
});
// 输出 JPG 格式,指定质量
const result = await convert('./document.pdf', {
format: 'jpg',
jpeg: { quality: 85 },
outputType: 'file',
outputDir: './output',
});
// 输出 WebP 格式,指定质量和编码方法
const result = await convert('./document.pdf', {
format: 'webp',
webp: { quality: 80, method: 4 },
outputType: 'file',
outputDir: './output',
});
```
### 转换指定页面
```javascript
const result = await convert('./document.pdf', {
pages: [1, 2, 3],
outputType: 'file',
outputDir: './output',
});
```
### 自定义渲染选项
```javascript
const result = await convert('./document.pdf', {
targetWidth: 2560,
format: 'webp',
webp: { quality: 90, method: 6 },
outputType: 'file',
outputDir: './output',
});
```
### 从 URL 转换
```javascript
// 自动下载到临时文件后渲染
const result = await convert('https://example.com/document.pdf', {
outputType: 'file',
outputDir: './output',
});
```
### 上传到腾讯云 COS
```javascript
const result = await convert('./document.pdf', {
outputType: 'cos',
format: 'webp',
cos: {
secretId: 'your-secret-id',
secretKey: 'your-secret-key',
bucket: 'your-bucket',
region: 'ap-guangzhou',
},
cosKeyPrefix: 'pdf-images/doc-123',
});
for (const page of result.pages) {
console.log(`已上传: ${page.cosKey}`);
}
```
### 获取页数
```javascript
// 异步版本(推荐)
const pageCount = await getPageCount('./document.pdf');
console.log(`PDF 共 ${pageCount} 页`);
// 同步版本(已废弃,保持向后兼容)
import { getPageCountSync } from '@tencent/pdf2img';
const pageCount = getPageCountSync('./document.pdf');
```
### 线程池管理
```javascript
import { getThreadPoolStats, destroyThreadPool } from '@tencent/pdf2img';
// 获取线程池统计信息
const stats = getThreadPoolStats();
console.log(`工作线程: ${stats.workers}`);
console.log(`已完成任务: ${stats.completed}`);
console.log(`线程利用率: ${(stats.utilization * 100).toFixed(1)}%`);
// 应用关闭时销毁线程池
await destroyThreadPool();
```
## API 参考
### `convert(input, options?)`
PDF 转图片。
**参数:**
- `input` (string | Buffer):PDF 文件路径、URL 或 Buffer
- `options` (object):转换选项
- `pages` (number[]):要转换的页码(1-based),空数组表示全部
- `outputType` ('file' | 'buffer' | 'cos'):输出类型(默认:'buffer')
- `outputDir` (string):输出目录('file' 类型时必需)
- `prefix` (string):文件名前缀(默认:'page')
- `format` ('webp' | 'png' | 'jpg'):输出格式(默认:'webp')
- `webp` (object):WebP 编码选项
- `quality` (number):质量 0-100(默认:80)
- `method` (number):编码方法 0-6(默认:4,0最快6最慢)
- `jpeg` (object):JPEG 编码选项
- `quality` (number):质量 0-100(默认:85)
- `png` (object):PNG 编码选项
- `compressionLevel` (number):压缩级别 0-9(默认:6)
- `cos` (object):COS 配置('cos' 类型时必需)
- `cosKeyPrefix` (string):COS key 前缀
- `targetWidth` (number):目标渲染宽度(默认:1280)
- `concurrency` (number):文件/上传并发数
**返回:** Promise
### `getPageCount(input)`
获取 PDF 页数(异步)。
**参数:**
- `input` (string | Buffer):PDF 文件路径或 Buffer
**返回:** Promise
### `getPageCountSync(input)`
获取 PDF 页数(同步,已废弃)。
**参数:**
- `input` (string | Buffer):PDF 文件路径或 Buffer
**返回:** number
### `isAvailable()`
检查原生渲染器是否可用。
**返回:** boolean
### `getVersion()`
获取原生渲染器版本信息。
**返回:** string
### `getThreadPoolStats()`
获取线程池统计信息。
**返回:** object
- `initialized` (boolean):线程池是否已初始化
- `workers` (number):工作线程数
- `completed` (number):已完成任务数
- `utilization` (number):线程利用率 (0-1)
### `destroyThreadPool()`
销毁线程池,释放工作线程资源。
**返回:** Promise
## 环境变量
| 变量 | 说明 | 默认值 |
|------|------|--------|
| `TARGET_RENDER_WIDTH` | 默认渲染宽度 | `1280` |
| `OUTPUT_FORMAT` | 默认输出格式 | `webp` |
| `NATIVE_STREAM_THRESHOLD` | 流式加载文件大小阈值 | `5MB` |
| `RANGE_REQUEST_TIMEOUT` | 分片请求超时 | `25000` |
| `DOWNLOAD_TIMEOUT` | 文件下载超时 | `60000` |
| `PDF2IMG_THREAD_COUNT` | 工作线程数 | CPU 核心数 |
| `PDF2IMG_DEBUG` | 启用调试日志 | `false` |
## 性能测试
测试环境:Linux x64,32 核 CPU,渲染宽度 1280px
### 本地文件渲染(前 10 页)
| 文件 | 大小 | 渲染页 | WebP | PNG | JPG |
|------|------|--------|------|-----|-----|
| 通行费电子发票-1.pdf | 39.1 KB | 1 | 123 ms | 101 ms | 177 ms |
| 发票.pdf | 76.8 KB | 1 | 111 ms | 107 ms | 165 ms |
| 股权转让协议书 (2).pdf | 593.2 KB | 3 | 294 ms | 275 ms | 350 ms |
| 1M.pdf | 992.5 KB | 10 | 698 ms | 532 ms | 1.31 s |
| DJI 用户手册.pdf | 2.8 MB | 10 | 541 ms | 529 ms | 616 ms |
| 大图内存性能素材.pdf | 7.6 MB | 10 | 2.05 s | 2.08 s | 2.13 s |
| 10M.pdf | 8.8 MB | 10 | 628 ms | 600 ms | 695 ms |
| ISO_32000-2.pdf | 16.5 MB | 10 | 677 ms | 620 ms | 946 ms |
| 四年级数学.pdf | 20.9 MB | 10 | 1.04 s | 1.09 s | 1.05 s |
| Rust语言圣经.pdf | 34.7 MB | 10 | 996 ms | 956 ms | 1.03 s |
| 50M.pdf | 55.3 MB | 10 | 1.57 s | 1.58 s | 1.59 s |
| 80M.pdf | 77.9 MB | 10 | 488 ms | 509 ms | 633 ms |
### URL 下载渲染(前 10 页)
| 文件 | 大小 | 渲染页 | WebP | PNG | JPG |
|------|------|--------|------|-----|-----|
| 发票.pdf | 76.8 KB | 1 | 122 ms | 106 ms | 172 ms |
| 1M.pdf | 992.5 KB | 10 | 770 ms | 577 ms | 1.36 s |
| DJI 用户手册.pdf | 2.8 MB | 10 | 607 ms | 576 ms | 687 ms |
| 10M.pdf | 8.8 MB | 10 | 666 ms | 678 ms | 720 ms |
| ISO_32000-2.pdf | 16.5 MB | 10 | 748 ms | 677 ms | 938 ms |
| Rust语言圣经.pdf | 34.7 MB | 10 | 1.08 s | 1.03 s | 1.17 s |
| 50M.pdf | 55.3 MB | 10 | 1.73 s | 1.89 s | 1.73 s |
| 80M.pdf | 77.9 MB | 10 | 699 ms | 792 ms | 889 ms |
**性能说明:**
- 架构:PDFium 渲染 + Sharp 编码(piscina 线程池)
- 线程数:自动使用 CPU 核心数(可通过 `PDF2IMG_THREAD_COUNT` 调整)
- PNG 格式通常最快(无损压缩,编码简单)
- WebP 格式文件最小(高压缩率)
- JPG 格式需要 RGBA→RGB 转换
## 架构设计
```
┌─────────────────────────────────────────────────────────────┐
│ 主线程 (Main Thread) │
│ - 接收用户请求 convert(input, options) │
│ - 初始 I/O:读取文件信息、下载远程文件 │
│ - 任务分发:为每一页创建任务并提交到线程池 │
│ - 结果收集:等待所有工作线程完成 │
│ - 最终 I/O:保存文件或上传 COS │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ piscina 线程池 (Worker Pool) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Worker │ │ Worker │ │ Worker │ │ Worker │ ... │
│ │ Thread │ │ Thread │ │ Thread │ │ Thread │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
│ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 每个工作线程处理单页任务 │ │
│ │ 1. PDFium 渲染 PDF 页面 → 原始 RGBA 位图 │ │
│ │ 2. Sharp 编码位图 → WebP/PNG/JPG │ │
│ │ 3. 返回编码后的 Buffer │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## 系统要求
- Node.js >= 18.0.0
- 支持平台:
- Linux x64 (glibc)
- Linux arm64 (glibc)
- macOS x64 (Intel)
- macOS arm64 (Apple Silicon)
- Windows x64
PDFium 库已随包一起分发,无需额外安装。
## 多平台构建说明
本项目使用 Rust + NAPI-RS 构建原生模块,支持以下平台:
| 平台 | 架构 | 构建状态 |
|------|------|----------|
| Linux | x64 | ✅ Orange CI |
| Linux | arm64 | ⚠️ OCI 交叉编译 |
| macOS | x64 | ⚠️ 需要手动构建 |
| macOS | arm64 | ⚠️ 需要手动构建 |
| Windows | x64 | ⚠️ 需要手动构建 |
### 构建流程
#### 1. Linux 平台(OCI 自动构建)
推送到以下分支会自动触发 Orange CI 构建:
- `master` / `main`: 正式版本
- `beta/*`: 测试版本
- `next`: 大版本预览
OCI 会构建 Linux x64 和 arm64 版本。
#### 2. macOS / Windows 平台(手动构建)
由于 Orange CI 没有 macOS/Windows 构建机,需要手动构建:
**macOS x64 (Intel)**:
```bash
cd packages/native-renderer
npm install
npm run build
# 产物:pdf-renderer.darwin-x64.node, libpdfium.dylib
```
**macOS arm64 (Apple Silicon)**:
```bash
cd packages/native-renderer
npm install
npm run build
# 产物:pdf-renderer.darwin-arm64.node, libpdfium.dylib
```
**Windows x64**:
```powershell
cd packages\native-renderer
npm install
npm run build
# 产物:pdf-renderer.win32-x64-msvc.node, pdfium.dll
```
#### 3. 合并所有平台产物
将各平台构建产物放入 `packages/native-renderer/` 目录:
```
packages/native-renderer/
├── index.js
├── index.d.ts
├── package.json
├── pdf-renderer.linux-x64-gnu.node # Linux x64
├── pdf-renderer.linux-arm64-gnu.node # Linux arm64
├── pdf-renderer.darwin-x64.node # macOS x64
├── pdf-renderer.darwin-arm64.node # macOS arm64
├── pdf-renderer.win32-x64-msvc.node # Windows x64
├── libpdfium.so # Linux PDFium
├── libpdfium.dylib # macOS PDFium
└── pdfium.dll # Windows PDFium
```
#### 4. 发布包
提交并推送代码到发布分支:
```bash
git add packages/native-renderer/
git commit -m "feat: update native modules for all platforms"
git push origin master # 或 beta/* 分支
```
Orange CI 会自动发布 npm 包。
## 许可证
MIT