node-pdf2img 0.1.8 → 0.1.9
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 +95 -9
- package/bin/cli.js +36 -20
- package/package.json +8 -3
- package/src/core/config.js +9 -0
- package/src/core/converter.js +69 -19
- package/src/core/output-handler.js +3 -0
- package/src/core/renderer.js +71 -6
- package/src/index.d.ts +182 -49
- package/src/index.js +18 -4
- package/src/renderers/pdfjs.js +867 -0
package/README.md
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
# node-pdf2img
|
|
2
2
|
|
|
3
|
-
高性能 PDF
|
|
3
|
+
高性能 PDF 转图片工具,支持 PDFium 原生渲染器和 PDF.js 渲染器。
|
|
4
4
|
|
|
5
5
|
[](https://badge.fury.io/js/node-pdf2img)
|
|
6
6
|
|
|
7
7
|
## 特性
|
|
8
8
|
|
|
9
|
-
-
|
|
9
|
+
- **双渲染引擎**:PDFium 原生渲染(高性能)+ PDF.js(纯 JavaScript,无需原生依赖)
|
|
10
|
+
- **高性能**:PDFium 多线程并行处理,整体快 1.7x
|
|
11
|
+
- **流式渲染**:URL 输入支持 HTTP Range 请求,大文件无需完整下载
|
|
10
12
|
- **多种格式**:支持 WebP、PNG、JPG 输出
|
|
11
13
|
- **多种输入**:支持本地文件、URL、Buffer
|
|
12
14
|
- **多种输出**:支持本地文件、Buffer、腾讯云 COS
|
|
13
15
|
- **CLI + API**:命令行工具和 Node.js 模块双模式
|
|
16
|
+
- **广泛兼容**:支持 GLIBC 2.17+ (CentOS 7/RHEL 7+)
|
|
14
17
|
|
|
15
18
|
## 安装
|
|
16
19
|
|
|
@@ -25,14 +28,20 @@ npm install -g node-pdf2img
|
|
|
25
28
|
## 系统要求
|
|
26
29
|
|
|
27
30
|
- Node.js >= 18.0.0
|
|
28
|
-
- 支持平台:
|
|
31
|
+
- 支持平台:
|
|
32
|
+
- Linux x64/arm64 (GLIBC 2.17+,兼容 CentOS 7/RHEL 7+)
|
|
33
|
+
- macOS x64/arm64
|
|
34
|
+
- Windows x64
|
|
29
35
|
|
|
30
36
|
## CLI 使用
|
|
31
37
|
|
|
32
38
|
```bash
|
|
33
|
-
# 基本用法 -
|
|
39
|
+
# 基本用法 - 转换所有页面(默认使用 PDFium)
|
|
34
40
|
pdf2img document.pdf -o ./output
|
|
35
41
|
|
|
42
|
+
# 使用 PDF.js 渲染器
|
|
43
|
+
pdf2img document.pdf -o ./output -r pdfjs
|
|
44
|
+
|
|
36
45
|
# 转换指定页面
|
|
37
46
|
pdf2img document.pdf -p 1,2,3 -o ./output
|
|
38
47
|
|
|
@@ -51,6 +60,9 @@ pdf2img document.pdf -f jpg -q 85 -o ./output
|
|
|
51
60
|
# 显示 PDF 信息
|
|
52
61
|
pdf2img document.pdf --info
|
|
53
62
|
|
|
63
|
+
# 显示渲染器版本信息
|
|
64
|
+
pdf2img --version-info
|
|
65
|
+
|
|
54
66
|
# 上传到腾讯云 COS
|
|
55
67
|
pdf2img document.pdf --cos --cos-prefix images/doc-123
|
|
56
68
|
```
|
|
@@ -64,8 +76,10 @@ pdf2img document.pdf --cos --cos-prefix images/doc-123
|
|
|
64
76
|
| `-w, --width <width>` | 渲染宽度(像素) | `1920` |
|
|
65
77
|
| `-q, --quality <quality>` | 图片质量(0-100) | `100` |
|
|
66
78
|
| `-f, --format <format>` | 输出格式:webp, png, jpg | `webp` |
|
|
79
|
+
| `-r, --renderer <renderer>` | 渲染器:pdfium, pdfjs | `pdfium` |
|
|
67
80
|
| `--prefix <prefix>` | 文件名前缀 | `page` |
|
|
68
81
|
| `--info` | 仅显示 PDF 信息 | |
|
|
82
|
+
| `--version-info` | 显示渲染器版本信息 | |
|
|
69
83
|
| `-v, --verbose` | 详细输出 | |
|
|
70
84
|
| `--cos` | 上传到腾讯云 COS | |
|
|
71
85
|
| `--cos-prefix <prefix>` | COS key 前缀 | |
|
|
@@ -88,7 +102,7 @@ pdf2img document.pdf --cos --cos-prefix images/doc-123
|
|
|
88
102
|
### 基本用法
|
|
89
103
|
|
|
90
104
|
```javascript
|
|
91
|
-
import { convert, getPageCount, isAvailable } from 'node-pdf2img';
|
|
105
|
+
import { convert, getPageCount, isAvailable, RendererType } from 'node-pdf2img';
|
|
92
106
|
|
|
93
107
|
// 检查渲染器是否可用
|
|
94
108
|
if (!isAvailable()) {
|
|
@@ -96,7 +110,7 @@ if (!isAvailable()) {
|
|
|
96
110
|
process.exit(1);
|
|
97
111
|
}
|
|
98
112
|
|
|
99
|
-
// 转换 PDF 为图片(返回 Buffer)
|
|
113
|
+
// 转换 PDF 为图片(返回 Buffer,默认使用 PDFium)
|
|
100
114
|
const result = await convert('./document.pdf');
|
|
101
115
|
console.log(`转换了 ${result.renderedPages} 页`);
|
|
102
116
|
|
|
@@ -106,6 +120,24 @@ for (const page of result.pages) {
|
|
|
106
120
|
}
|
|
107
121
|
```
|
|
108
122
|
|
|
123
|
+
### 使用 PDF.js 渲染器
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
import { convert, RendererType, isPdfjsAvailable } from 'node-pdf2img';
|
|
127
|
+
|
|
128
|
+
// 检查 PDF.js 渲染器是否可用
|
|
129
|
+
if (isPdfjsAvailable()) {
|
|
130
|
+
const result = await convert('./document.pdf', {
|
|
131
|
+
renderer: RendererType.PDFJS, // 或直接使用 'pdfjs'
|
|
132
|
+
});
|
|
133
|
+
console.log(`使用 PDF.js 转换了 ${result.renderedPages} 页`);
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
> **渲染器选择建议**:
|
|
138
|
+
> - **PDFium**(默认):高性能,适合大多数场景,整体快 1.7x
|
|
139
|
+
> - **PDF.js**:纯 JavaScript,无需原生依赖,适合超大文件(>50MB)或无法使用原生模块的环境
|
|
140
|
+
|
|
109
141
|
### 保存到文件
|
|
110
142
|
|
|
111
143
|
```javascript
|
|
@@ -200,8 +232,15 @@ const result = await convert('https://example.com/document.pdf', {
|
|
|
200
232
|
outputType: 'file',
|
|
201
233
|
outputDir: './output',
|
|
202
234
|
});
|
|
235
|
+
|
|
236
|
+
// 查看流式渲染统计(仅 URL 输入时存在)
|
|
237
|
+
if (result.streamStats) {
|
|
238
|
+
console.log(`流式渲染: 缓存命中 ${result.streamStats.cacheHits} 次`);
|
|
239
|
+
}
|
|
203
240
|
```
|
|
204
241
|
|
|
242
|
+
> **流式渲染**:对于大于 2MB 的远程 PDF,会自动使用 HTTP Range 请求按需获取数据,避免完整下载。小于 2MB 的文件会直接下载后渲染,减少 Range 请求开销。
|
|
243
|
+
|
|
205
244
|
### 上传到腾讯云 COS
|
|
206
245
|
|
|
207
246
|
```javascript
|
|
@@ -256,6 +295,7 @@ PDF 转图片。
|
|
|
256
295
|
- `prefix` (string):文件名前缀,默认 'page'
|
|
257
296
|
- `format` ('webp' | 'png' | 'jpg'):输出格式,默认 'webp'
|
|
258
297
|
- `targetWidth` (number):渲染宽度,默认 1280
|
|
298
|
+
- `renderer` ('pdfium' | 'pdfjs'):渲染器,默认 'pdfium'
|
|
259
299
|
- `webp` (object):WebP 编码选项
|
|
260
300
|
- `quality` (number):质量 0-100,默认 80
|
|
261
301
|
- `jpeg` (object):JPEG 编码选项
|
|
@@ -276,15 +316,30 @@ PDF 转图片。
|
|
|
276
316
|
|
|
277
317
|
**返回:** Promise\<number\>
|
|
278
318
|
|
|
279
|
-
### `isAvailable()`
|
|
319
|
+
### `isAvailable(renderer?)`
|
|
320
|
+
|
|
321
|
+
检查渲染器是否可用。
|
|
322
|
+
|
|
323
|
+
**参数:**
|
|
324
|
+
- `renderer` ('pdfium' | 'pdfjs'):可选,指定检查的渲染器,默认检查 PDFium
|
|
325
|
+
|
|
326
|
+
**返回:** boolean
|
|
327
|
+
|
|
328
|
+
### `isPdfjsAvailable()`
|
|
280
329
|
|
|
281
|
-
|
|
330
|
+
检查 PDF.js 渲染器是否可用。
|
|
282
331
|
|
|
283
332
|
**返回:** boolean
|
|
284
333
|
|
|
285
334
|
### `getVersion()`
|
|
286
335
|
|
|
287
|
-
|
|
336
|
+
获取 PDFium 渲染器版本信息。
|
|
337
|
+
|
|
338
|
+
**返回:** string
|
|
339
|
+
|
|
340
|
+
### `getPdfjsVersion()`
|
|
341
|
+
|
|
342
|
+
获取 PDF.js 渲染器版本信息。
|
|
288
343
|
|
|
289
344
|
**返回:** string
|
|
290
345
|
|
|
@@ -299,8 +354,39 @@ PDF 转图片。
|
|
|
299
354
|
| 变量 | 说明 | 默认值 |
|
|
300
355
|
|------|------|--------|
|
|
301
356
|
| `PDF2IMG_THREAD_COUNT` | 工作线程数 | CPU 核心数 |
|
|
357
|
+
| `PDF2IMG_RENDERER` | 默认渲染器 (pdfium/pdfjs) | `pdfium` |
|
|
302
358
|
| `PDF2IMG_DEBUG` | 启用调试日志 | `false` |
|
|
303
359
|
|
|
360
|
+
## 渲染器对比
|
|
361
|
+
|
|
362
|
+
| 特性 | PDFium | PDF.js |
|
|
363
|
+
|------|--------|--------|
|
|
364
|
+
| 性能 | ⚡ 快 1.7x | 普通 |
|
|
365
|
+
| 依赖 | 原生模块 | 纯 JavaScript |
|
|
366
|
+
| 大文件 (>50MB) | 普通 | ⚡ 更快 |
|
|
367
|
+
| 兼容性 | Linux/macOS/Windows | 所有平台 |
|
|
368
|
+
| 流式渲染 | ✓ | ✓ |
|
|
369
|
+
|
|
370
|
+
## 架构
|
|
371
|
+
|
|
372
|
+
模块化设计,职责分离:
|
|
373
|
+
|
|
374
|
+
```
|
|
375
|
+
src/
|
|
376
|
+
├── core/
|
|
377
|
+
│ ├── converter.js # 主 API 入口
|
|
378
|
+
│ ├── renderer.js # PDF 渲染逻辑(流式/下载/本地)
|
|
379
|
+
│ ├── thread-pool.js # 线程池管理
|
|
380
|
+
│ ├── downloader.js # 远程文件下载
|
|
381
|
+
│ ├── output-handler.js # 文件保存和 COS 上传
|
|
382
|
+
│ └── config.js # 配置常量
|
|
383
|
+
├── renderers/
|
|
384
|
+
│ ├── native.js # PDFium 原生渲染器
|
|
385
|
+
│ └── pdfjs.js # PDF.js 渲染器(分片加载)
|
|
386
|
+
└── utils/
|
|
387
|
+
└── logger.js # 日志工具
|
|
388
|
+
```
|
|
389
|
+
|
|
304
390
|
## 许可证
|
|
305
391
|
|
|
306
392
|
MIT
|
package/bin/cli.js
CHANGED
|
@@ -12,8 +12,13 @@
|
|
|
12
12
|
* pdf2img document.pdf -p 1,2,3 -o ./output
|
|
13
13
|
* pdf2img document.pdf --quality 90 --width 1920 -o ./output
|
|
14
14
|
* pdf2img document.pdf --format png -o ./output # 输出 PNG 格式
|
|
15
|
+
* pdf2img document.pdf --renderer pdfjs -o ./output # 使用 PDF.js 渲染器
|
|
15
16
|
* pdf2img document.pdf --cos --cos-prefix images/doc # 上传到 COS
|
|
16
17
|
*
|
|
18
|
+
* 渲染器:
|
|
19
|
+
* pdfium - PDFium 原生渲染器(默认,高性能)
|
|
20
|
+
* pdfjs - PDF.js 渲染器(纯 JavaScript,无需原生依赖)
|
|
21
|
+
*
|
|
17
22
|
* COS 环境变量:
|
|
18
23
|
* COS_SECRET_ID - 腾讯云 SecretId
|
|
19
24
|
* COS_SECRET_KEY - 腾讯云 SecretKey
|
|
@@ -42,10 +47,10 @@ try {
|
|
|
42
47
|
|
|
43
48
|
// 处理 --version-info 选项(在解析前检查)
|
|
44
49
|
if (process.argv.includes('--version-info')) {
|
|
45
|
-
const { isAvailable, getVersion } = await import('../src/index.js');
|
|
50
|
+
const { isAvailable, getVersion, RendererType } = await import('../src/index.js');
|
|
46
51
|
console.log(`pdf2img v${pkg.version}`);
|
|
47
|
-
console.log(
|
|
48
|
-
console.log(
|
|
52
|
+
console.log(`PDFium 渲染器: ${isAvailable(RendererType.PDFIUM) ? getVersion(RendererType.PDFIUM) : '不可用'}`);
|
|
53
|
+
console.log(`PDF.js 渲染器: ${isAvailable(RendererType.PDFJS) ? '可用' : '不可用'}`);
|
|
49
54
|
process.exit(0);
|
|
50
55
|
}
|
|
51
56
|
|
|
@@ -59,6 +64,7 @@ program
|
|
|
59
64
|
.option('-w, --width <width>', '目标渲染宽度(像素)', '1920')
|
|
60
65
|
.option('-q, --quality <quality>', '图片质量(0-100,用于 webp/jpg)', '100')
|
|
61
66
|
.option('-f, --format <format>', '输出格式:webp, png, jpg', 'webp')
|
|
67
|
+
.option('-r, --renderer <renderer>', '渲染器:pdfium(默认)或 pdfjs', 'pdfium')
|
|
62
68
|
.option('--prefix <prefix>', '输出文件名前缀', 'page')
|
|
63
69
|
.option('--info', '仅显示 PDF 信息(页数)')
|
|
64
70
|
.option('--version-info', '显示原生渲染器版本')
|
|
@@ -77,20 +83,31 @@ program
|
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
// 动态导入主模块
|
|
80
|
-
const { convert, getPageCount, isAvailable, getVersion } = await import('../src/index.js');
|
|
86
|
+
const { convert, getPageCount, isAvailable, getVersion, RendererType } = await import('../src/index.js');
|
|
81
87
|
|
|
82
88
|
// 显示版本信息
|
|
83
89
|
if (options.versionInfo) {
|
|
84
90
|
console.log(`pdf2img v${pkg.version}`);
|
|
85
|
-
console.log(
|
|
86
|
-
console.log(
|
|
91
|
+
console.log(`PDFium 渲染器: ${isAvailable(RendererType.PDFIUM) ? getVersion(RendererType.PDFIUM) : '不可用'}`);
|
|
92
|
+
console.log(`PDF.js 渲染器: ${isAvailable(RendererType.PDFJS) ? '可用' : '不可用'}`);
|
|
87
93
|
return;
|
|
88
94
|
}
|
|
89
95
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
console.error(
|
|
96
|
+
// 验证渲染器
|
|
97
|
+
const renderer = options.renderer.toLowerCase();
|
|
98
|
+
if (renderer !== 'pdfium' && renderer !== 'pdfjs') {
|
|
99
|
+
console.error(`错误:不支持的渲染器 "${options.renderer}"。支持的渲染器:pdfium, pdfjs`);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 检查渲染器可用性
|
|
104
|
+
if (!isAvailable(renderer)) {
|
|
105
|
+
if (renderer === 'pdfium') {
|
|
106
|
+
console.error('错误:PDFium 原生渲染器不可用。');
|
|
107
|
+
console.error('请确保 PDFium 库已正确安装,或使用 --renderer pdfjs 切换到 PDF.js 渲染器。');
|
|
108
|
+
} else {
|
|
109
|
+
console.error('错误:PDF.js 渲染器不可用。');
|
|
110
|
+
}
|
|
94
111
|
process.exit(1);
|
|
95
112
|
}
|
|
96
113
|
|
|
@@ -111,17 +128,15 @@ program
|
|
|
111
128
|
|
|
112
129
|
// 仅显示 PDF 信息
|
|
113
130
|
if (options.info) {
|
|
114
|
-
if (isUrl) {
|
|
115
|
-
console.error('错误:--info 选项仅支持本地文件');
|
|
116
|
-
process.exit(1);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
131
|
try {
|
|
120
|
-
const pageCount = getPageCount(input);
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
132
|
+
const pageCount = await getPageCount(input, { renderer });
|
|
133
|
+
if (!isUrl) {
|
|
134
|
+
const stat = fs.statSync(input);
|
|
135
|
+
console.log(`文件: ${path.basename(input)}`);
|
|
136
|
+
console.log(`大小: ${(stat.size / 1024 / 1024).toFixed(2)} MB`);
|
|
137
|
+
}
|
|
124
138
|
console.log(`页数: ${pageCount}`);
|
|
139
|
+
console.log(`渲染器: ${renderer}`);
|
|
125
140
|
} catch (err) {
|
|
126
141
|
console.error(`错误: ${err.message}`);
|
|
127
142
|
process.exit(1);
|
|
@@ -143,6 +158,7 @@ program
|
|
|
143
158
|
targetWidth: parseInt(options.width, 10),
|
|
144
159
|
quality: parseInt(options.quality, 10),
|
|
145
160
|
format: format,
|
|
161
|
+
renderer: renderer,
|
|
146
162
|
};
|
|
147
163
|
|
|
148
164
|
// COS 模式
|
|
@@ -195,7 +211,7 @@ program
|
|
|
195
211
|
|
|
196
212
|
const duration = Date.now() - startTime;
|
|
197
213
|
|
|
198
|
-
spinner.succeed(`${modeText}完成 ${result.renderedPages}/${result.numPages} 页,格式: ${result.format.toUpperCase()},耗时 ${duration}ms`);
|
|
214
|
+
spinner.succeed(`${modeText}完成 ${result.renderedPages}/${result.numPages} 页,格式: ${result.format.toUpperCase()},渲染器: ${result.renderer},耗时 ${duration}ms`);
|
|
199
215
|
|
|
200
216
|
// 显示结果
|
|
201
217
|
if (options.cos) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-pdf2img",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "High-performance PDF to image converter using PDFium native renderer",
|
|
3
|
+
"version": "0.1.9",
|
|
4
|
+
"description": "High-performance PDF to image converter using PDFium native renderer or PDF.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"types": "./src/index.d.ts",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"image",
|
|
30
30
|
"converter",
|
|
31
31
|
"pdfium",
|
|
32
|
+
"pdfjs",
|
|
32
33
|
"webp",
|
|
33
34
|
"native",
|
|
34
35
|
"cli"
|
|
@@ -53,9 +54,13 @@
|
|
|
53
54
|
"dotenv": "^16.0.3",
|
|
54
55
|
"ora": "^8.0.0",
|
|
55
56
|
"p-limit": "^7.2.0",
|
|
57
|
+
"pdfjs-dist": "^4.0.0",
|
|
56
58
|
"piscina": "^5.1.4",
|
|
57
59
|
"sharp": "^0.33.0",
|
|
58
|
-
"node-pdf2img-native": "^1.1.
|
|
60
|
+
"node-pdf2img-native": "^1.1.10"
|
|
61
|
+
},
|
|
62
|
+
"optionalDependencies": {
|
|
63
|
+
"canvas": "^2.11.0"
|
|
59
64
|
},
|
|
60
65
|
"devDependencies": {
|
|
61
66
|
"@types/node": "^20.0.0"
|
package/src/core/config.js
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
* PDF2IMG 配置
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
// ==================== 渲染器类型 ====================
|
|
6
|
+
export const RendererType = {
|
|
7
|
+
PDFIUM: 'pdfium', // PDFium 原生渲染器(默认,高性能)
|
|
8
|
+
PDFJS: 'pdfjs', // PDF.js 渲染器(纯 JavaScript,无需原生依赖)
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// 默认渲染器
|
|
12
|
+
export const DEFAULT_RENDERER = process.env.PDF2IMG_RENDERER || RendererType.PDFIUM;
|
|
13
|
+
|
|
5
14
|
// ==================== 渲染配置 ====================
|
|
6
15
|
export const RENDER_CONFIG = {
|
|
7
16
|
// 目标渲染宽度(像素)
|
package/src/core/converter.js
CHANGED
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
* - 主线程:负责 I/O、任务分发、结果收集
|
|
8
8
|
* - 工作线程池:负责 CPU 密集型任务(PDFium 渲染 + Sharp 编码)
|
|
9
9
|
*
|
|
10
|
+
* 渲染器支持:
|
|
11
|
+
* - pdfium: PDFium 原生渲染器(默认,高性能)
|
|
12
|
+
* - pdfjs: PDF.js 渲染器(纯 JavaScript,无需原生依赖)
|
|
13
|
+
*
|
|
10
14
|
* 性能优化:
|
|
11
15
|
* - 使用 piscina 线程池,充分利用多核 CPU
|
|
12
16
|
* - 异步文件 I/O,不阻塞事件循环
|
|
@@ -16,12 +20,13 @@
|
|
|
16
20
|
|
|
17
21
|
import fs from 'fs';
|
|
18
22
|
import { createLogger } from '../utils/logger.js';
|
|
19
|
-
import { RENDER_CONFIG, SUPPORTED_FORMATS } from './config.js';
|
|
23
|
+
import { RENDER_CONFIG, SUPPORTED_FORMATS, RendererType, DEFAULT_RENDERER } from './config.js';
|
|
20
24
|
import * as nativeRenderer from '../renderers/native.js';
|
|
25
|
+
import * as pdfjsRenderer from '../renderers/pdfjs.js';
|
|
21
26
|
import { getThreadCount, getThreadPoolStats, destroyThreadPool } from './thread-pool.js';
|
|
22
27
|
import { downloadToTempFile } from './downloader.js';
|
|
23
28
|
import { saveToFiles, uploadToCos, DEFAULT_CONCURRENCY } from './output-handler.js';
|
|
24
|
-
import { InputType, detectInputType, renderPages } from './renderer.js';
|
|
29
|
+
import { InputType, detectInputType, renderPages, getRendererType } from './renderer.js';
|
|
25
30
|
|
|
26
31
|
const logger = createLogger('Converter');
|
|
27
32
|
|
|
@@ -48,6 +53,7 @@ export { InputType };
|
|
|
48
53
|
* @param {string} [options.prefix='page'] - 输出文件名前缀
|
|
49
54
|
* @param {string} [options.format='webp'] - 输出格式:'webp'、'png'、'jpg'
|
|
50
55
|
* @param {number} [options.quality] - 图片质量(0-100,用于 webp 和 jpg)
|
|
56
|
+
* @param {string} [options.renderer='pdfium'] - 渲染器:'pdfium'(默认)或 'pdfjs'
|
|
51
57
|
* @param {Object} [options.webp] - WebP 编码配置
|
|
52
58
|
* @param {number} [options.webp.quality] - WebP 质量(0-100,默认 80)
|
|
53
59
|
* @param {number} [options.webp.method] - WebP 编码方法(0-6,默认 4,0最快6最慢)
|
|
@@ -70,6 +76,7 @@ export async function convert(input, options = {}) {
|
|
|
70
76
|
outputDir,
|
|
71
77
|
prefix = 'page',
|
|
72
78
|
format = RENDER_CONFIG.OUTPUT_FORMAT,
|
|
79
|
+
renderer = DEFAULT_RENDERER,
|
|
73
80
|
cos: cosConfig,
|
|
74
81
|
cosKeyPrefix = `pdf2img/${Date.now()}`,
|
|
75
82
|
concurrency,
|
|
@@ -82,9 +89,13 @@ export async function convert(input, options = {}) {
|
|
|
82
89
|
throw new Error(`Unsupported format: ${format}. Supported formats: ${SUPPORTED_FORMATS.join(', ')}`);
|
|
83
90
|
}
|
|
84
91
|
|
|
92
|
+
// 获取实际使用的渲染器
|
|
93
|
+
const actualRenderer = getRendererType({ renderer });
|
|
94
|
+
logger.debug(`Renderer: ${actualRenderer}`);
|
|
95
|
+
|
|
85
96
|
// 检查渲染器可用性
|
|
86
|
-
if (!nativeRenderer.isNativeAvailable()) {
|
|
87
|
-
throw new Error('Native renderer is not available. Please ensure PDFium library is installed.');
|
|
97
|
+
if (actualRenderer === RendererType.PDFIUM && !nativeRenderer.isNativeAvailable()) {
|
|
98
|
+
throw new Error('Native renderer is not available. Please ensure PDFium library is installed or use renderer: "pdfjs".');
|
|
88
99
|
}
|
|
89
100
|
|
|
90
101
|
// 检测输入类型
|
|
@@ -101,6 +112,7 @@ export async function convert(input, options = {}) {
|
|
|
101
112
|
pngCompression: renderOptions.png?.compressionLevel,
|
|
102
113
|
targetWidth: renderOptions.targetWidth,
|
|
103
114
|
detectScan: renderOptions.detectScan,
|
|
115
|
+
renderer: actualRenderer,
|
|
104
116
|
};
|
|
105
117
|
|
|
106
118
|
// 使用线程池渲染页面
|
|
@@ -186,6 +198,7 @@ export async function convert(input, options = {}) {
|
|
|
186
198
|
numPages: result.numPages,
|
|
187
199
|
renderedPages: outputResult.filter(p => p.success).length,
|
|
188
200
|
format: normalizedFormat,
|
|
201
|
+
renderer: result.renderer || actualRenderer,
|
|
189
202
|
pages: outputResult,
|
|
190
203
|
timing: {
|
|
191
204
|
total: Date.now() - startTime,
|
|
@@ -204,9 +217,34 @@ export async function convert(input, options = {}) {
|
|
|
204
217
|
* 获取 PDF 页数(异步版本)
|
|
205
218
|
*
|
|
206
219
|
* @param {string|Buffer} input - PDF 输入(文件路径、URL 或 Buffer)
|
|
220
|
+
* @param {Object} [options] - 选项
|
|
221
|
+
* @param {string} [options.renderer] - 渲染器:'pdfium' 或 'pdfjs'
|
|
207
222
|
* @returns {Promise<number>} 页数
|
|
208
223
|
*/
|
|
209
|
-
export async function getPageCount(input) {
|
|
224
|
+
export async function getPageCount(input, options = {}) {
|
|
225
|
+
const rendererType = getRendererType(options);
|
|
226
|
+
|
|
227
|
+
// 使用 PDF.js 渲染器
|
|
228
|
+
if (rendererType === RendererType.PDFJS) {
|
|
229
|
+
if (Buffer.isBuffer(input)) {
|
|
230
|
+
return pdfjsRenderer.getPageCount(input);
|
|
231
|
+
}
|
|
232
|
+
if (typeof input === 'string') {
|
|
233
|
+
if (input.startsWith('http://') || input.startsWith('https://')) {
|
|
234
|
+
// URL 输入需要下载
|
|
235
|
+
const tempFile = await downloadToTempFile(input);
|
|
236
|
+
try {
|
|
237
|
+
return pdfjsRenderer.getPageCountFromFile(tempFile);
|
|
238
|
+
} finally {
|
|
239
|
+
try { await fs.promises.unlink(tempFile); } catch {}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return pdfjsRenderer.getPageCountFromFile(input);
|
|
243
|
+
}
|
|
244
|
+
throw new Error('Invalid input: must be a file path, URL, or Buffer');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 使用 PDFium 渲染器
|
|
210
248
|
if (!nativeRenderer.isNativeAvailable()) {
|
|
211
249
|
throw new Error('Native renderer is not available');
|
|
212
250
|
}
|
|
@@ -216,23 +254,15 @@ export async function getPageCount(input) {
|
|
|
216
254
|
}
|
|
217
255
|
|
|
218
256
|
if (typeof input === 'string') {
|
|
219
|
-
// 检查是否是 URL
|
|
220
257
|
if (input.startsWith('http://') || input.startsWith('https://')) {
|
|
221
|
-
// URL 输入:下载到临时文件后获取页数
|
|
222
258
|
const tempFile = await downloadToTempFile(input);
|
|
223
259
|
try {
|
|
224
260
|
return nativeRenderer.getPageCountFromFile(tempFile);
|
|
225
261
|
} finally {
|
|
226
|
-
|
|
227
|
-
try {
|
|
228
|
-
await fs.promises.unlink(tempFile);
|
|
229
|
-
} catch {
|
|
230
|
-
// 忽略清理错误
|
|
231
|
-
}
|
|
262
|
+
try { await fs.promises.unlink(tempFile); } catch {}
|
|
232
263
|
}
|
|
233
264
|
}
|
|
234
265
|
|
|
235
|
-
// 本地文件路径
|
|
236
266
|
try {
|
|
237
267
|
await fs.promises.access(input, fs.constants.R_OK);
|
|
238
268
|
} catch {
|
|
@@ -268,17 +298,37 @@ export function getPageCountSync(input) {
|
|
|
268
298
|
|
|
269
299
|
/**
|
|
270
300
|
* 检查渲染器是否可用
|
|
301
|
+
*
|
|
302
|
+
* @param {string} [renderer] - 渲染器类型:'pdfium' 或 'pdfjs'
|
|
303
|
+
* @returns {boolean} 是否可用
|
|
271
304
|
*/
|
|
272
|
-
export function isAvailable() {
|
|
273
|
-
|
|
305
|
+
export function isAvailable(renderer) {
|
|
306
|
+
if (renderer === RendererType.PDFJS) {
|
|
307
|
+
return pdfjsRenderer.isPdfjsAvailable();
|
|
308
|
+
}
|
|
309
|
+
if (renderer === RendererType.PDFIUM) {
|
|
310
|
+
return nativeRenderer.isNativeAvailable();
|
|
311
|
+
}
|
|
312
|
+
// 默认检查 pdfium,如果不可用则检查 pdfjs
|
|
313
|
+
return nativeRenderer.isNativeAvailable() || pdfjsRenderer.isPdfjsAvailable();
|
|
274
314
|
}
|
|
275
315
|
|
|
276
316
|
/**
|
|
277
317
|
* 获取版本信息
|
|
318
|
+
*
|
|
319
|
+
* @param {string} [renderer] - 渲染器类型
|
|
320
|
+
* @returns {string} 版本信息
|
|
278
321
|
*/
|
|
279
|
-
export function getVersion() {
|
|
280
|
-
|
|
322
|
+
export function getVersion(renderer) {
|
|
323
|
+
if (renderer === RendererType.PDFJS) {
|
|
324
|
+
return pdfjsRenderer.getPdfjsVersion();
|
|
325
|
+
}
|
|
326
|
+
if (nativeRenderer.isNativeAvailable()) {
|
|
327
|
+
return nativeRenderer.getVersion();
|
|
328
|
+
}
|
|
329
|
+
return pdfjsRenderer.getPdfjsVersion();
|
|
281
330
|
}
|
|
282
331
|
|
|
283
|
-
//
|
|
332
|
+
// 重新导出渲染器类型和线程池相关函数
|
|
333
|
+
export { RendererType };
|
|
284
334
|
export { getThreadPoolStats, destroyThreadPool };
|
|
@@ -162,6 +162,9 @@ export async function uploadToCos(pages, cosConfig, keyPrefix, format = 'webp',
|
|
|
162
162
|
const cos = new COS({
|
|
163
163
|
SecretId: cosConfig.secretId,
|
|
164
164
|
SecretKey: cosConfig.secretKey,
|
|
165
|
+
Protocol: cosConfig.protocol || 'https:',
|
|
166
|
+
ServiceDomain: cosConfig.serviceDomain,
|
|
167
|
+
Domain: cosConfig.domain,
|
|
165
168
|
});
|
|
166
169
|
|
|
167
170
|
const ext = getExtension(format);
|
package/src/core/renderer.js
CHANGED
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
* PDF 渲染模块
|
|
3
3
|
*
|
|
4
4
|
* 负责 PDF 页面的渲染逻辑,支持本地文件、Buffer 和 URL 输入
|
|
5
|
+
* 支持两种渲染器:
|
|
6
|
+
* - pdfium: PDFium 原生渲染器(默认,高性能)
|
|
7
|
+
* - pdfjs: PDF.js 渲染器(纯 JavaScript,无需原生依赖)
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
10
|
import fs from 'fs';
|
|
8
11
|
import { createLogger } from '../utils/logger.js';
|
|
12
|
+
import { RendererType, DEFAULT_RENDERER } from './config.js';
|
|
9
13
|
import * as nativeRenderer from '../renderers/native.js';
|
|
14
|
+
import * as pdfjsRenderer from '../renderers/pdfjs.js';
|
|
10
15
|
import { getThreadPool, getThreadCount } from './thread-pool.js';
|
|
11
16
|
import { getRemoteFileSize, downloadToTempFile } from './downloader.js';
|
|
12
17
|
|
|
@@ -46,9 +51,30 @@ export function detectInputType(input) {
|
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
/**
|
|
49
|
-
*
|
|
54
|
+
* 获取当前使用的渲染器类型
|
|
50
55
|
*
|
|
51
|
-
*
|
|
56
|
+
* @param {Object} options - 选项
|
|
57
|
+
* @returns {string} 渲染器类型
|
|
58
|
+
*/
|
|
59
|
+
export function getRendererType(options = {}) {
|
|
60
|
+
const renderer = options.renderer || DEFAULT_RENDERER;
|
|
61
|
+
|
|
62
|
+
// 验证渲染器类型
|
|
63
|
+
if (renderer === RendererType.PDFJS) {
|
|
64
|
+
return RendererType.PDFJS;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 默认使用 pdfium,如果不可用则回退到 pdfjs
|
|
68
|
+
if (!nativeRenderer.isNativeAvailable()) {
|
|
69
|
+
logger.warn('PDFium 渲染器不可用,回退到 PDF.js');
|
|
70
|
+
return RendererType.PDFJS;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return RendererType.PDFIUM;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 使用指定渲染器渲染 PDF 页面
|
|
52
78
|
*
|
|
53
79
|
* @param {string|Buffer} input - 输入
|
|
54
80
|
* @param {string} inputType - 输入类型
|
|
@@ -58,16 +84,52 @@ export function detectInputType(input) {
|
|
|
58
84
|
*/
|
|
59
85
|
export async function renderPages(input, inputType, pages, options) {
|
|
60
86
|
const startTime = Date.now();
|
|
61
|
-
|
|
62
|
-
|
|
87
|
+
const rendererType = getRendererType(options);
|
|
88
|
+
|
|
89
|
+
logger.debug(`Using renderer: ${rendererType}`);
|
|
90
|
+
|
|
91
|
+
if (rendererType === RendererType.PDFJS) {
|
|
92
|
+
return renderPagesWithPdfjs(input, inputType, pages, options, startTime);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 使用 pdfium 渲染器
|
|
63
96
|
if (inputType === InputType.URL) {
|
|
64
97
|
return renderPagesFromUrl(input, pages, options, startTime);
|
|
65
98
|
}
|
|
66
|
-
|
|
67
|
-
// 本地文件或 Buffer 输入:使用线程池渲染
|
|
99
|
+
|
|
68
100
|
return renderPagesFromLocal(input, inputType, pages, options, startTime);
|
|
69
101
|
}
|
|
70
102
|
|
|
103
|
+
/**
|
|
104
|
+
* 使用 PDF.js 渲染器渲染 PDF 页面
|
|
105
|
+
*/
|
|
106
|
+
async function renderPagesWithPdfjs(input, inputType, pages, options, startTime) {
|
|
107
|
+
let result;
|
|
108
|
+
|
|
109
|
+
if (inputType === InputType.URL) {
|
|
110
|
+
result = await pdfjsRenderer.renderFromUrl(input, pages, options);
|
|
111
|
+
} else if (inputType === InputType.BUFFER) {
|
|
112
|
+
result = await pdfjsRenderer.renderFromBuffer(input, pages, options);
|
|
113
|
+
} else {
|
|
114
|
+
result = await pdfjsRenderer.renderFromFile(input, pages, options);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!result.success) {
|
|
118
|
+
throw new Error(result.error || 'PDF.js 渲染失败');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
success: true,
|
|
123
|
+
numPages: result.numPages,
|
|
124
|
+
pages: result.pages,
|
|
125
|
+
totalTime: Date.now() - startTime,
|
|
126
|
+
renderTime: result.renderTime || result.pages.reduce((sum, p) => sum + (p.renderTime || 0), 0),
|
|
127
|
+
encodeTime: result.pages.reduce((sum, p) => sum + (p.encodeTime || 0), 0),
|
|
128
|
+
renderer: RendererType.PDFJS,
|
|
129
|
+
streamStats: result.streamStats,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
71
133
|
/**
|
|
72
134
|
* 从 URL 渲染 PDF 页面(流式)
|
|
73
135
|
*
|
|
@@ -97,6 +159,7 @@ async function renderPagesFromUrl(url, pages, options, startTime) {
|
|
|
97
159
|
renderTime: result.pages.reduce((sum, p) => sum + (p.renderTime || 0), 0),
|
|
98
160
|
encodeTime: result.pages.reduce((sum, p) => sum + (p.encodeTime || 0), 0),
|
|
99
161
|
streamStats: result.streamStats,
|
|
162
|
+
renderer: RendererType.PDFIUM,
|
|
100
163
|
};
|
|
101
164
|
} catch (err) {
|
|
102
165
|
// 流式渲染失败,回退到下载后渲染
|
|
@@ -145,6 +208,7 @@ async function renderPagesWithDownload(url, pages, options, startTime) {
|
|
|
145
208
|
totalTime: Date.now() - startTime,
|
|
146
209
|
renderTime: results.reduce((sum, p) => sum + (p.renderTime || 0), 0),
|
|
147
210
|
encodeTime: results.reduce((sum, p) => sum + (p.encodeTime || 0), 0),
|
|
211
|
+
renderer: RendererType.PDFIUM,
|
|
148
212
|
};
|
|
149
213
|
} finally {
|
|
150
214
|
try {
|
|
@@ -220,5 +284,6 @@ async function renderPagesFromLocal(input, inputType, pages, options, startTime)
|
|
|
220
284
|
totalTime: Date.now() - startTime,
|
|
221
285
|
renderTime: results.reduce((sum, p) => sum + (p.renderTime || 0), 0),
|
|
222
286
|
encodeTime: results.reduce((sum, p) => sum + (p.encodeTime || 0), 0),
|
|
287
|
+
renderer: RendererType.PDFIUM,
|
|
223
288
|
};
|
|
224
289
|
}
|