node-pdf2img 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,520 @@
1
+ # node-pdf2img
2
+
3
+ High-performance PDF to image converter using PDFium native renderer + Sharp image encoding.
4
+
5
+ [![npm version](https://badge.fury.io/js/node-pdf2img.svg)](https://badge.fury.io/js/node-pdf2img)
6
+ [![Build Status](https://github.com/sigma-2026/node-pdf2img/workflows/Build%20and%20Release/badge.svg)](https://github.com/sigma-2026/node-pdf2img/actions)
7
+
8
+ ## 特性
9
+
10
+ - **原生性能**:使用 PDFium C++ 库通过 Rust 绑定实现高性能 PDF 渲染
11
+ - **Sharp 编码**:使用 libvips 的 Sharp 库进行高效图像编码
12
+ - **多线程处理**:使用 piscina 线程池,充分利用多核 CPU 并行处理
13
+ - **零拷贝文件读取**:原生模块直接读取文件路径,避免 Node.js 堆内存占用
14
+ - **异步 I/O**:主线程负责协调和 I/O,工作线程负责 CPU 密集型任务
15
+ - **并发控制**:文件写入和 COS 上传使用 p-limit 控制并发,避免资源耗尽
16
+ - **多种输入源**:支持本地文件、URL 或 Buffer
17
+ - **多种输出目标**:支持本地文件、Buffer 或腾讯云 COS
18
+ - **多种输出格式**:支持 WebP、PNG、JPG 格式
19
+ - **CLI 和 API**:支持命令行使用或作为 Node.js 模块引用
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ # Install as project dependency (for API usage)
25
+ npm install node-pdf2img
26
+
27
+ # Install globally (for CLI usage)
28
+ npm install -g node-pdf2img
29
+ ```
30
+
31
+ > 💡 **本地开发**: 如果你想直接使用预编译的 native 模块进行本地开发,请查看 [LOCAL_DEV.md](../../LOCAL_DEV.md) 了解更多。
32
+
33
+ ## CLI 使用
34
+
35
+ ```bash
36
+ # 基本用法 - 转换所有页面(默认 WebP 格式)
37
+ pdf2img document.pdf -o ./output
38
+
39
+ # 转换指定页面
40
+ pdf2img document.pdf -p 1,2,3 -o ./output
41
+
42
+ # 从 URL 转换
43
+ pdf2img https://example.com/document.pdf -o ./output
44
+
45
+ # 自定义质量和宽度
46
+ pdf2img document.pdf -q 90 -w 2560 -o ./output
47
+
48
+ # 输出 PNG 格式
49
+ pdf2img document.pdf -f png -o ./output
50
+
51
+ # 输出 JPG 格式
52
+ pdf2img document.pdf -f jpg -q 85 -o ./output
53
+
54
+ # 显示 PDF 信息
55
+ pdf2img document.pdf --info
56
+
57
+ # 详细输出
58
+ pdf2img document.pdf -o ./output -v
59
+
60
+ # 上传到腾讯云 COS(需先配置环境变量)
61
+ pdf2img document.pdf --cos --cos-prefix images/doc-123
62
+ ```
63
+
64
+ ### CLI 选项
65
+
66
+ | 选项 | 说明 | 默认值 |
67
+ |------|------|--------|
68
+ | `-o, --output <dir>` | 输出目录(本地模式) | `./output` |
69
+ | `-p, --pages <pages>` | 页码(逗号分隔) | 全部页面 |
70
+ | `-w, --width <width>` | 目标渲染宽度(像素) | `1280` |
71
+ | `-q, --quality <quality>` | 图片质量(0-100,用于 webp/jpg) | `80` |
72
+ | `-f, --format <format>` | 输出格式:webp, png, jpg | `webp` |
73
+ | `--prefix <prefix>` | 输出文件名前缀 | `page` |
74
+ | `--info` | 仅显示 PDF 信息 | |
75
+ | `--version-info` | 显示渲染器版本 | |
76
+ | `-v, --verbose` | 详细输出 | |
77
+ | `--cos` | 上传到腾讯云 COS | |
78
+ | `--cos-prefix <prefix>` | COS key 前缀 | |
79
+
80
+ ### COS 上传配置
81
+
82
+ CLI 支持通过环境变量配置 COS 上传参数:
83
+
84
+ ```bash
85
+ # 设置环境变量
86
+ export COS_SECRET_ID=your-secret-id
87
+ export COS_SECRET_KEY=your-secret-key
88
+ export COS_BUCKET=your-bucket-name
89
+ export COS_REGION=ap-guangzhou
90
+
91
+ # 使用 --cos 选项上传
92
+ pdf2img document.pdf --cos --cos-prefix images/doc-123
93
+ ```
94
+
95
+ 也可以通过命令行参数指定(不推荐,敏感信息会暴露在命令行历史中):
96
+
97
+ ```bash
98
+ pdf2img document.pdf --cos \
99
+ --cos-secret-id xxx \
100
+ --cos-secret-key xxx \
101
+ --cos-bucket xxx \
102
+ --cos-region ap-guangzhou \
103
+ --cos-prefix images/doc-123
104
+ ```
105
+
106
+ ## API 使用
107
+
108
+ ### 基本用法
109
+
110
+ ```javascript
111
+ import { convert, getPageCount, isAvailable } from 'node-pdf2img';
112
+
113
+ // 检查渲染器是否可用
114
+ if (!isAvailable()) {
115
+ console.error('原生渲染器不可用');
116
+ process.exit(1);
117
+ }
118
+
119
+ // 转换 PDF 为图片(返回 Buffer)
120
+ const result = await convert('./document.pdf');
121
+ console.log(`转换了 ${result.renderedPages} 页`);
122
+
123
+ for (const page of result.pages) {
124
+ console.log(`第 ${page.pageNum} 页: ${page.width}x${page.height}`);
125
+ // page.buffer 包含图片数据
126
+ }
127
+ ```
128
+
129
+ ### 保存到文件
130
+
131
+ ```javascript
132
+ const result = await convert('./document.pdf', {
133
+ outputType: 'file',
134
+ outputDir: './output',
135
+ prefix: 'doc',
136
+ });
137
+
138
+ for (const page of result.pages) {
139
+ console.log(`已保存: ${page.outputPath}`);
140
+ }
141
+ ```
142
+
143
+ ### 指定输出格式
144
+
145
+ ```javascript
146
+ // 输出 PNG 格式
147
+ const result = await convert('./document.pdf', {
148
+ format: 'png',
149
+ outputType: 'file',
150
+ outputDir: './output',
151
+ });
152
+
153
+ // 输出 JPG 格式,指定质量
154
+ const result = await convert('./document.pdf', {
155
+ format: 'jpg',
156
+ jpeg: { quality: 85 },
157
+ outputType: 'file',
158
+ outputDir: './output',
159
+ });
160
+
161
+ // 输出 WebP 格式,指定质量和编码方法
162
+ const result = await convert('./document.pdf', {
163
+ format: 'webp',
164
+ webp: { quality: 80, method: 4 },
165
+ outputType: 'file',
166
+ outputDir: './output',
167
+ });
168
+ ```
169
+
170
+ ### 转换指定页面
171
+
172
+ ```javascript
173
+ const result = await convert('./document.pdf', {
174
+ pages: [1, 2, 3],
175
+ outputType: 'file',
176
+ outputDir: './output',
177
+ });
178
+ ```
179
+
180
+ ### 自定义渲染选项
181
+
182
+ ```javascript
183
+ const result = await convert('./document.pdf', {
184
+ targetWidth: 2560,
185
+ format: 'webp',
186
+ webp: { quality: 90, method: 6 },
187
+ outputType: 'file',
188
+ outputDir: './output',
189
+ });
190
+ ```
191
+
192
+ ### 从 URL 转换
193
+
194
+ ```javascript
195
+ // 自动下载到临时文件后渲染
196
+ const result = await convert('https://example.com/document.pdf', {
197
+ outputType: 'file',
198
+ outputDir: './output',
199
+ });
200
+ ```
201
+
202
+ ### 上传到腾讯云 COS
203
+
204
+ ```javascript
205
+ const result = await convert('./document.pdf', {
206
+ outputType: 'cos',
207
+ format: 'webp',
208
+ cos: {
209
+ secretId: 'your-secret-id',
210
+ secretKey: 'your-secret-key',
211
+ bucket: 'your-bucket',
212
+ region: 'ap-guangzhou',
213
+ },
214
+ cosKeyPrefix: 'pdf-images/doc-123',
215
+ });
216
+
217
+ for (const page of result.pages) {
218
+ console.log(`已上传: ${page.cosKey}`);
219
+ }
220
+ ```
221
+
222
+ ### 获取页数
223
+
224
+ ```javascript
225
+ // 异步版本(推荐)
226
+ const pageCount = await getPageCount('./document.pdf');
227
+ console.log(`PDF 共 ${pageCount} 页`);
228
+
229
+ // 同步版本(已废弃,保持向后兼容)
230
+ import { getPageCountSync } from 'node-pdf2img';
231
+ const pageCount = getPageCountSync('./document.pdf');
232
+ ```
233
+
234
+ ### 线程池管理
235
+
236
+ ```javascript
237
+ import { getThreadPoolStats, destroyThreadPool } from 'node-pdf2img';
238
+
239
+ // 获取线程池统计信息
240
+ const stats = getThreadPoolStats();
241
+ console.log(`工作线程: ${stats.workers}`);
242
+ console.log(`已完成任务: ${stats.completed}`);
243
+ console.log(`线程利用率: ${(stats.utilization * 100).toFixed(1)}%`);
244
+
245
+ // 应用关闭时销毁线程池
246
+ await destroyThreadPool();
247
+ ```
248
+
249
+ ## API 参考
250
+
251
+ ### `convert(input, options?)`
252
+
253
+ PDF 转图片。
254
+
255
+ **参数:**
256
+ - `input` (string | Buffer):PDF 文件路径、URL 或 Buffer
257
+ - `options` (object):转换选项
258
+ - `pages` (number[]):要转换的页码(1-based),空数组表示全部
259
+ - `outputType` ('file' | 'buffer' | 'cos'):输出类型(默认:'buffer')
260
+ - `outputDir` (string):输出目录('file' 类型时必需)
261
+ - `prefix` (string):文件名前缀(默认:'page')
262
+ - `format` ('webp' | 'png' | 'jpg'):输出格式(默认:'webp')
263
+ - `webp` (object):WebP 编码选项
264
+ - `quality` (number):质量 0-100(默认:80)
265
+ - `method` (number):编码方法 0-6(默认:4,0最快6最慢)
266
+ - `jpeg` (object):JPEG 编码选项
267
+ - `quality` (number):质量 0-100(默认:85)
268
+ - `png` (object):PNG 编码选项
269
+ - `compressionLevel` (number):压缩级别 0-9(默认:6)
270
+ - `cos` (object):COS 配置('cos' 类型时必需)
271
+ - `cosKeyPrefix` (string):COS key 前缀
272
+ - `targetWidth` (number):目标渲染宽度(默认:1280)
273
+ - `concurrency` (number):文件/上传并发数
274
+
275
+ **返回:** Promise<ConvertResult>
276
+
277
+ ### `getPageCount(input)`
278
+
279
+ 获取 PDF 页数(异步)。
280
+
281
+ **参数:**
282
+ - `input` (string | Buffer):PDF 文件路径或 Buffer
283
+
284
+ **返回:** Promise<number>
285
+
286
+ ### `getPageCountSync(input)`
287
+
288
+ 获取 PDF 页数(同步,已废弃)。
289
+
290
+ **参数:**
291
+ - `input` (string | Buffer):PDF 文件路径或 Buffer
292
+
293
+ **返回:** number
294
+
295
+ ### `isAvailable()`
296
+
297
+ 检查原生渲染器是否可用。
298
+
299
+ **返回:** boolean
300
+
301
+ ### `getVersion()`
302
+
303
+ 获取原生渲染器版本信息。
304
+
305
+ **返回:** string
306
+
307
+ ### `getThreadPoolStats()`
308
+
309
+ 获取线程池统计信息。
310
+
311
+ **返回:** object
312
+ - `initialized` (boolean):线程池是否已初始化
313
+ - `workers` (number):工作线程数
314
+ - `completed` (number):已完成任务数
315
+ - `utilization` (number):线程利用率 (0-1)
316
+
317
+ ### `destroyThreadPool()`
318
+
319
+ 销毁线程池,释放工作线程资源。
320
+
321
+ **返回:** Promise<void>
322
+
323
+ ## 环境变量
324
+
325
+ | 变量 | 说明 | 默认值 |
326
+ |------|------|--------|
327
+ | `TARGET_RENDER_WIDTH` | 默认渲染宽度 | `1280` |
328
+ | `OUTPUT_FORMAT` | 默认输出格式 | `webp` |
329
+ | `NATIVE_STREAM_THRESHOLD` | 流式加载文件大小阈值 | `5MB` |
330
+ | `RANGE_REQUEST_TIMEOUT` | 分片请求超时 | `25000` |
331
+ | `DOWNLOAD_TIMEOUT` | 文件下载超时 | `60000` |
332
+ | `PDF2IMG_THREAD_COUNT` | 工作线程数 | CPU 核心数 |
333
+ | `PDF2IMG_DEBUG` | 启用调试日志 | `false` |
334
+
335
+ ## 性能测试
336
+
337
+ 测试环境:Linux x64,32 核 CPU,渲染宽度 1280px
338
+
339
+ ### 本地文件渲染(前 10 页)
340
+
341
+ | 文件 | 大小 | 渲染页 | WebP | PNG | JPG |
342
+ |------|------|--------|------|-----|-----|
343
+ | 通行费电子发票-1.pdf | 39.1 KB | 1 | 123 ms | 101 ms | 177 ms |
344
+ | 发票.pdf | 76.8 KB | 1 | 111 ms | 107 ms | 165 ms |
345
+ | 股权转让协议书 (2).pdf | 593.2 KB | 3 | 294 ms | 275 ms | 350 ms |
346
+ | 1M.pdf | 992.5 KB | 10 | 698 ms | 532 ms | 1.31 s |
347
+ | DJI 用户手册.pdf | 2.8 MB | 10 | 541 ms | 529 ms | 616 ms |
348
+ | 大图内存性能素材.pdf | 7.6 MB | 10 | 2.05 s | 2.08 s | 2.13 s |
349
+ | 10M.pdf | 8.8 MB | 10 | 628 ms | 600 ms | 695 ms |
350
+ | ISO_32000-2.pdf | 16.5 MB | 10 | 677 ms | 620 ms | 946 ms |
351
+ | 四年级数学.pdf | 20.9 MB | 10 | 1.04 s | 1.09 s | 1.05 s |
352
+ | Rust语言圣经.pdf | 34.7 MB | 10 | 996 ms | 956 ms | 1.03 s |
353
+ | 50M.pdf | 55.3 MB | 10 | 1.57 s | 1.58 s | 1.59 s |
354
+ | 80M.pdf | 77.9 MB | 10 | 488 ms | 509 ms | 633 ms |
355
+
356
+ ### URL 下载渲染(前 10 页)
357
+
358
+ | 文件 | 大小 | 渲染页 | WebP | PNG | JPG |
359
+ |------|------|--------|------|-----|-----|
360
+ | 发票.pdf | 76.8 KB | 1 | 122 ms | 106 ms | 172 ms |
361
+ | 1M.pdf | 992.5 KB | 10 | 770 ms | 577 ms | 1.36 s |
362
+ | DJI 用户手册.pdf | 2.8 MB | 10 | 607 ms | 576 ms | 687 ms |
363
+ | 10M.pdf | 8.8 MB | 10 | 666 ms | 678 ms | 720 ms |
364
+ | ISO_32000-2.pdf | 16.5 MB | 10 | 748 ms | 677 ms | 938 ms |
365
+ | Rust语言圣经.pdf | 34.7 MB | 10 | 1.08 s | 1.03 s | 1.17 s |
366
+ | 50M.pdf | 55.3 MB | 10 | 1.73 s | 1.89 s | 1.73 s |
367
+ | 80M.pdf | 77.9 MB | 10 | 699 ms | 792 ms | 889 ms |
368
+
369
+ **性能说明:**
370
+ - 架构:PDFium 渲染 + Sharp 编码(piscina 线程池)
371
+ - 线程数:自动使用 CPU 核心数(可通过 `PDF2IMG_THREAD_COUNT` 调整)
372
+ - PNG 格式通常最快(无损压缩,编码简单)
373
+ - WebP 格式文件最小(高压缩率)
374
+ - JPG 格式需要 RGBA→RGB 转换
375
+
376
+ ## 架构设计
377
+
378
+ ```
379
+ ┌─────────────────────────────────────────────────────────────┐
380
+ │ 主线程 (Main Thread) │
381
+ │ - 接收用户请求 convert(input, options) │
382
+ │ - 初始 I/O:读取文件信息、下载远程文件 │
383
+ │ - 任务分发:为每一页创建任务并提交到线程池 │
384
+ │ - 结果收集:等待所有工作线程完成 │
385
+ │ - 最终 I/O:保存文件或上传 COS │
386
+ └─────────────────────────────────────────────────────────────┘
387
+
388
+
389
+ ┌─────────────────────────────────────────────────────────────┐
390
+ │ piscina 线程池 (Worker Pool) │
391
+ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
392
+ │ │ Worker │ │ Worker │ │ Worker │ │ Worker │ ... │
393
+ │ │ Thread │ │ Thread │ │ Thread │ │ Thread │ │
394
+ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
395
+ │ │ │ │ │ │
396
+ │ ▼ ▼ ▼ ▼ │
397
+ │ ┌─────────────────────────────────────────────────────┐ │
398
+ │ │ 每个工作线程处理单页任务 │ │
399
+ │ │ 1. PDFium 渲染 PDF 页面 → 原始 RGBA 位图 │ │
400
+ │ │ 2. Sharp 编码位图 → WebP/PNG/JPG │ │
401
+ │ │ 3. 返回编码后的 Buffer │ │
402
+ │ └─────────────────────────────────────────────────────┘ │
403
+ └─────────────────────────────────────────────────────────────┘
404
+ ```
405
+
406
+ ## 系统要求
407
+
408
+ - Node.js >= 18.0.0
409
+ - 支持平台:
410
+ - Linux x64 (glibc)
411
+ - Linux arm64 (glibc)
412
+ - macOS x64 (Intel)
413
+ - macOS arm64 (Apple Silicon)
414
+ - Windows x64
415
+
416
+ ### 原生模块安装
417
+
418
+ `node-pdf2img` 依赖 `node-pdf2img-native` 原生模块。安装时会自动:
419
+ 1. **优先**下载对应平台的预编译二进制文件
420
+ 2. **降级**如果预编译文件不可用,则在本地编译(需要 Rust + C++ 编译工具链)
421
+
422
+ 大多数情况下会使用预编译版本,安装快速。如果需要本地编译,请确保已安装:
423
+ - Rust 工具链
424
+ - C++ 编译器(GCC/Clang/MSVC)
425
+ - Make(Linux/macOS)或 Ninja(Windows)
426
+
427
+ ## 多平台构建说明
428
+
429
+ 本项目使用 Rust + NAPI-RS 构建原生模块,通过 GitHub Actions 自动构建和发布所有平台版本。
430
+
431
+ ### 支持的平台
432
+
433
+ | 平台 | 架构 | 构建状态 |
434
+ |------|------|----------|
435
+ | Linux | x64 | ✅ GitHub Actions |
436
+ | Linux | arm64 | ✅ GitHub Actions (交叉编译) |
437
+ | macOS | x64 | ✅ GitHub Actions |
438
+ | macOS | arm64 | ✅ GitHub Actions |
439
+ | Windows | x64 | ✅ GitHub Actions |
440
+
441
+ ### 自动构建流程
442
+
443
+ 推送到以下分支会自动触发 GitHub Actions 构建:
444
+ - `master` / `main`: 正式版本,发布到 latest 标签
445
+ - `beta/*`: 测试版本,发布到 beta 标签
446
+ - `next`: 大版本预览,发布到 next 标签
447
+ - 标签 `v*`: 正式发布版本
448
+
449
+ GitHub Actions 会:
450
+ 1. 为所有 5 个平台交叉编译原生模块
451
+ 2. 将编译产物合并到 `node-pdf2img-native` 包
452
+ 3. 发布两个 npm 包:
453
+ - `node-pdf2img-native`: 原生渲染器包
454
+ - `node-pdf2img`: 主包
455
+
456
+ ### 手动构建(开发调试)
457
+
458
+ 如需在本地构建特定平台的原生模块:
459
+
460
+ **Linux x64**:
461
+ ```bash
462
+ cd packages/native-renderer
463
+ pnpm install
464
+ pnpm run build
465
+ # 产物:pdf-renderer.linux-x64-gnu.node, libpdfium.so
466
+ ```
467
+
468
+ **macOS x64 (Intel)**:
469
+ ```bash
470
+ cd packages/native-renderer
471
+ pnpm install
472
+ pnpm run build
473
+ # 产物:pdf-renderer.darwin-x64.node, libpdfium.dylib
474
+ ```
475
+
476
+ **macOS arm64 (Apple Silicon)**:
477
+ ```bash
478
+ cd packages/native-renderer
479
+ pnpm install
480
+ pnpm run build
481
+ # 产物:pdf-renderer.darwin-arm64.node, libpdfium.dylib
482
+ ```
483
+
484
+ **Windows x64**:
485
+ ```powershell
486
+ cd packages\native-renderer
487
+ pnpm install
488
+ pnpm run build
489
+ # 产物:pdf-renderer.win32-x64-msvc.node, pdfium.dll
490
+ ```
491
+
492
+ ### 项目结构
493
+
494
+ ```
495
+ pdf2img/
496
+ ├── packages/
497
+ │ ├── pdf2img/ # 主包
498
+ │ │ ├── src/
499
+ │ │ ├── bin/
500
+ │ │ └── package.json
501
+ │ └── native-renderer/ # 原生渲染器包
502
+ │ ├── src/ # Rust 源代码
503
+ │ ├── index.js # JavaScript 绑定
504
+ │ ├── package.json
505
+ │ └── Cargo.toml
506
+ ├── .github/workflows/
507
+ │ └── build-and-release.yml # CI/CD 配置
508
+ └── pnpm-workspace.yaml
509
+ ```
510
+
511
+ ### 发布流程
512
+
513
+ 1. **开发分支提交**: 推送到 `beta/*` 或 `next` 分支
514
+ 2. **自动构建**: GitHub Actions 为所有平台编译
515
+ 3. **自动发布**: 发布到 npm 对应的 tag(beta/next)
516
+ 4. **正式发布**: 合并到 `master` 或打 tag `v*`,发布到 latest
517
+
518
+ ## 许可证
519
+
520
+ MIT