node-pdf2img 0.1.5 → 0.1.7
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 +26 -0
- package/package.json +2 -2
- package/src/core/converter.js +75 -60
- package/src/index.d.ts +2 -2
package/README.md
CHANGED
|
@@ -157,6 +157,32 @@ const result = await convert('./document.pdf', {
|
|
|
157
157
|
});
|
|
158
158
|
```
|
|
159
159
|
|
|
160
|
+
### pages 参数说明
|
|
161
|
+
|
|
162
|
+
`pages` 参数控制要转换的页面:
|
|
163
|
+
|
|
164
|
+
- **空数组 `[]` 或不传**:转换所有页面
|
|
165
|
+
- **指定页码数组**:只转换指定页面(1-based)
|
|
166
|
+
|
|
167
|
+
```javascript
|
|
168
|
+
// 转换所有页面(推荐)
|
|
169
|
+
await convert('./document.pdf', { pages: [] });
|
|
170
|
+
|
|
171
|
+
// 只转换第 1、3、5 页
|
|
172
|
+
await convert('./document.pdf', { pages: [1, 3, 5] });
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
> **最佳实践**:如果需要转换所有页面,直接使用 `convert()` 并传空 `pages` 数组,**不要**先调用 `getPageCount()` 获取页数再传入。这样可以避免 URL 输入时重复下载 PDF 文件。
|
|
176
|
+
>
|
|
177
|
+
> ```javascript
|
|
178
|
+
> // ❌ 不推荐:URL 会被下载两次
|
|
179
|
+
> const pageCount = await getPageCount(url);
|
|
180
|
+
> const result = await convert(url, { pages: Array.from({length: pageCount}, (_, i) => i + 1) });
|
|
181
|
+
>
|
|
182
|
+
> // ✅ 推荐:直接转换所有页面
|
|
183
|
+
> const result = await convert(url, { pages: [] });
|
|
184
|
+
> ```
|
|
185
|
+
|
|
160
186
|
### 自定义渲染宽度
|
|
161
187
|
|
|
162
188
|
```javascript
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-pdf2img",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "High-performance PDF to image converter using PDFium native renderer",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"p-limit": "^7.2.0",
|
|
56
56
|
"piscina": "^5.1.4",
|
|
57
57
|
"sharp": "^0.33.0",
|
|
58
|
-
"node-pdf2img-native": "^1.1.
|
|
58
|
+
"node-pdf2img-native": "^1.1.8"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@types/node": "^20.0.0"
|
package/src/core/converter.js
CHANGED
|
@@ -424,6 +424,59 @@ export async function convert(input, options = {}) {
|
|
|
424
424
|
// 使用线程池渲染页面
|
|
425
425
|
const result = await renderPages(input, inputType, pages, encodeOptions);
|
|
426
426
|
|
|
427
|
+
// 恢复 Buffer 类型
|
|
428
|
+
// Piscina 跨线程传递时 Buffer 可能被序列化为普通对象 { type: 'Buffer', data: [...] }
|
|
429
|
+
const normalizedPages = result.pages.map(page => {
|
|
430
|
+
if (!page.success || !page.buffer) {
|
|
431
|
+
return {
|
|
432
|
+
pageNum: page.pageNum,
|
|
433
|
+
width: page.width,
|
|
434
|
+
height: page.height,
|
|
435
|
+
success: false,
|
|
436
|
+
buffer: null,
|
|
437
|
+
error: page.error || 'Render failed',
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
let buffer = page.buffer;
|
|
442
|
+
if (!Buffer.isBuffer(buffer)) {
|
|
443
|
+
try {
|
|
444
|
+
if (buffer && typeof buffer === 'object') {
|
|
445
|
+
if (buffer.type === 'Buffer' && Array.isArray(buffer.data)) {
|
|
446
|
+
buffer = Buffer.from(buffer.data);
|
|
447
|
+
} else if (buffer.data && ArrayBuffer.isView(buffer.data)) {
|
|
448
|
+
buffer = Buffer.from(buffer.data);
|
|
449
|
+
} else if (ArrayBuffer.isView(buffer)) {
|
|
450
|
+
buffer = Buffer.from(buffer);
|
|
451
|
+
} else {
|
|
452
|
+
buffer = Buffer.from(buffer);
|
|
453
|
+
}
|
|
454
|
+
} else {
|
|
455
|
+
throw new Error(`Cannot convert ${typeof buffer} to Buffer`);
|
|
456
|
+
}
|
|
457
|
+
} catch (e) {
|
|
458
|
+
logger.error(`Buffer type mismatch: ${typeof page.buffer}, conversion failed: ${e.message}`);
|
|
459
|
+
return {
|
|
460
|
+
pageNum: page.pageNum,
|
|
461
|
+
width: page.width,
|
|
462
|
+
height: page.height,
|
|
463
|
+
success: false,
|
|
464
|
+
buffer: null,
|
|
465
|
+
error: `Invalid buffer type returned from worker: ${e.message}`,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return {
|
|
471
|
+
pageNum: page.pageNum,
|
|
472
|
+
width: page.width,
|
|
473
|
+
height: page.height,
|
|
474
|
+
success: true,
|
|
475
|
+
buffer,
|
|
476
|
+
size: buffer.length,
|
|
477
|
+
};
|
|
478
|
+
});
|
|
479
|
+
|
|
427
480
|
// 处理输出
|
|
428
481
|
let outputResult;
|
|
429
482
|
|
|
@@ -431,72 +484,17 @@ export async function convert(input, options = {}) {
|
|
|
431
484
|
if (!outputDir) {
|
|
432
485
|
throw new Error('outputDir is required when outputType is "file"');
|
|
433
486
|
}
|
|
434
|
-
outputResult = await saveToFiles(
|
|
487
|
+
outputResult = await saveToFiles(normalizedPages, outputDir, prefix, normalizedFormat, concurrency);
|
|
435
488
|
|
|
436
489
|
} else if (outputType === OutputType.COS) {
|
|
437
490
|
if (!cosConfig) {
|
|
438
491
|
throw new Error('cos config is required when outputType is "cos"');
|
|
439
492
|
}
|
|
440
|
-
outputResult = await uploadToCos(
|
|
493
|
+
outputResult = await uploadToCos(normalizedPages, cosConfig, cosKeyPrefix, normalizedFormat, concurrency);
|
|
441
494
|
|
|
442
495
|
} else {
|
|
443
496
|
// 返回 Buffer
|
|
444
|
-
outputResult =
|
|
445
|
-
if (!page.success || !page.buffer) {
|
|
446
|
-
return {
|
|
447
|
-
pageNum: page.pageNum,
|
|
448
|
-
width: page.width,
|
|
449
|
-
height: page.height,
|
|
450
|
-
success: false,
|
|
451
|
-
buffer: null,
|
|
452
|
-
error: page.error || 'Render failed',
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// 确保 buffer 是 Buffer 类型
|
|
457
|
-
// Piscina 跨线程传递时 Buffer 可能被序列化为普通对象
|
|
458
|
-
let buffer = page.buffer;
|
|
459
|
-
if (!Buffer.isBuffer(buffer)) {
|
|
460
|
-
// 尝试从序列化的对象恢复 Buffer
|
|
461
|
-
try {
|
|
462
|
-
if (buffer && typeof buffer === 'object') {
|
|
463
|
-
// 可能是 { type: 'Buffer', data: [...] } 格式
|
|
464
|
-
if (buffer.type === 'Buffer' && Array.isArray(buffer.data)) {
|
|
465
|
-
buffer = Buffer.from(buffer.data);
|
|
466
|
-
} else if (buffer.data && ArrayBuffer.isView(buffer.data)) {
|
|
467
|
-
buffer = Buffer.from(buffer.data);
|
|
468
|
-
} else if (ArrayBuffer.isView(buffer)) {
|
|
469
|
-
// Uint8Array 等 TypedArray
|
|
470
|
-
buffer = Buffer.from(buffer);
|
|
471
|
-
} else {
|
|
472
|
-
// 最后尝试直接转换
|
|
473
|
-
buffer = Buffer.from(buffer);
|
|
474
|
-
}
|
|
475
|
-
} else {
|
|
476
|
-
throw new Error(`Cannot convert ${typeof buffer} to Buffer`);
|
|
477
|
-
}
|
|
478
|
-
} catch (e) {
|
|
479
|
-
logger.error(`Buffer type mismatch: ${typeof page.buffer}, conversion failed: ${e.message}`);
|
|
480
|
-
return {
|
|
481
|
-
pageNum: page.pageNum,
|
|
482
|
-
width: page.width,
|
|
483
|
-
height: page.height,
|
|
484
|
-
success: false,
|
|
485
|
-
buffer: null,
|
|
486
|
-
error: `Invalid buffer type returned from worker: ${e.message}`,
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
return {
|
|
492
|
-
pageNum: page.pageNum,
|
|
493
|
-
width: page.width,
|
|
494
|
-
height: page.height,
|
|
495
|
-
success: true,
|
|
496
|
-
buffer,
|
|
497
|
-
size: buffer.length,
|
|
498
|
-
};
|
|
499
|
-
}).sort((a, b) => a.pageNum - b.pageNum);
|
|
497
|
+
outputResult = normalizedPages.sort((a, b) => a.pageNum - b.pageNum);
|
|
500
498
|
}
|
|
501
499
|
|
|
502
500
|
return {
|
|
@@ -519,7 +517,7 @@ export async function convert(input, options = {}) {
|
|
|
519
517
|
/**
|
|
520
518
|
* 获取 PDF 页数(异步版本)
|
|
521
519
|
*
|
|
522
|
-
* @param {string|Buffer} input - PDF
|
|
520
|
+
* @param {string|Buffer} input - PDF 输入(文件路径、URL 或 Buffer)
|
|
523
521
|
* @returns {Promise<number>} 页数
|
|
524
522
|
*/
|
|
525
523
|
export async function getPageCount(input) {
|
|
@@ -532,6 +530,23 @@ export async function getPageCount(input) {
|
|
|
532
530
|
}
|
|
533
531
|
|
|
534
532
|
if (typeof input === 'string') {
|
|
533
|
+
// 检查是否是 URL
|
|
534
|
+
if (input.startsWith('http://') || input.startsWith('https://')) {
|
|
535
|
+
// URL 输入:下载到临时文件后获取页数
|
|
536
|
+
const tempFile = await downloadToTempFile(input);
|
|
537
|
+
try {
|
|
538
|
+
return nativeRenderer.getPageCountFromFile(tempFile);
|
|
539
|
+
} finally {
|
|
540
|
+
// 清理临时文件
|
|
541
|
+
try {
|
|
542
|
+
await fs.promises.unlink(tempFile);
|
|
543
|
+
} catch {
|
|
544
|
+
// 忽略清理错误
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// 本地文件路径
|
|
535
550
|
try {
|
|
536
551
|
await fs.promises.access(input, fs.constants.R_OK);
|
|
537
552
|
} catch {
|
|
@@ -540,7 +555,7 @@ export async function getPageCount(input) {
|
|
|
540
555
|
return nativeRenderer.getPageCountFromFile(input);
|
|
541
556
|
}
|
|
542
557
|
|
|
543
|
-
throw new Error('Invalid input: must be a file path or Buffer');
|
|
558
|
+
throw new Error('Invalid input: must be a file path, URL, or Buffer');
|
|
544
559
|
}
|
|
545
560
|
|
|
546
561
|
/**
|
package/src/index.d.ts
CHANGED
|
@@ -92,10 +92,10 @@ export function convert(input: string | Buffer, options?: ConvertOptions): Promi
|
|
|
92
92
|
/**
|
|
93
93
|
* 获取 PDF 页数
|
|
94
94
|
*
|
|
95
|
-
* @param input - PDF
|
|
95
|
+
* @param input - PDF 文件路径、URL 或 Buffer
|
|
96
96
|
* @returns 页数
|
|
97
97
|
*/
|
|
98
|
-
export function getPageCount(input: string | Buffer): number
|
|
98
|
+
export function getPageCount(input: string | Buffer): Promise<number>;
|
|
99
99
|
|
|
100
100
|
/**
|
|
101
101
|
* 检查原生渲染器是否可用
|