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 CHANGED
@@ -1,16 +1,19 @@
1
1
  # node-pdf2img
2
2
 
3
- 高性能 PDF 转图片工具,基于 PDFium 原生渲染器 + Sharp 图像编码。
3
+ 高性能 PDF 转图片工具,支持 PDFium 原生渲染器和 PDF.js 渲染器。
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/node-pdf2img.svg)](https://badge.fury.io/js/node-pdf2img)
6
6
 
7
7
  ## 特性
8
8
 
9
- - **高性能**:使用 PDFium 原生渲染,多线程并行处理
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
- - 支持平台:Linux x64/arm64、macOS x64/arm64、Windows x64
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(`原生渲染器: ${getVersion()}`);
48
- console.log(`可用: ${isAvailable() ? '' : ''}`);
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(`原生渲染器: ${getVersion()}`);
86
- console.log(`可用: ${isAvailable() ? '' : ''}`);
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
- // 检查 native renderer
91
- if (!isAvailable()) {
92
- console.error('错误:原生渲染器不可用。');
93
- console.error('请确保 PDFium 库已正确安装。');
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
- const stat = fs.statSync(input);
122
- console.log(`文件: ${path.basename(input)}`);
123
- console.log(`大小: ${(stat.size / 1024 / 1024).toFixed(2)} MB`);
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.8",
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.9"
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"
@@ -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
  // 目标渲染宽度(像素)
@@ -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
- return nativeRenderer.isNativeAvailable();
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
- return nativeRenderer.getVersion();
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);
@@ -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
- * 使用线程池渲染 PDF 页面
54
+ * 获取当前使用的渲染器类型
50
55
  *
51
- * 主线程负责协调,工作线程负责 CPU 密集型任务
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
- // URL 输入:优先使用流式渲染
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
  }