screenitshot 0.6.0 → 0.7.5
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/dist/cli.js +69 -18
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -7
- package/dist/index.js.map +1 -1
- package/dist/renderer.d.ts +1 -1
- package/dist/renderer.d.ts.map +1 -1
- package/dist/renderer.js +24 -23
- package/dist/renderer.js.map +1 -1
- package/dist/types.d.ts +8 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +57 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +72 -17
- package/src/index.ts +15 -9
- package/src/renderer.ts +36 -29
- package/src/types.ts +64 -2
package/dist/cli.js
CHANGED
|
@@ -1,34 +1,90 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from 'commander';
|
|
3
|
-
import { screenshot } from './index.js';
|
|
4
|
-
import { readFile } from 'fs/promises';
|
|
5
|
-
import { resolve, dirname } from 'path';
|
|
3
|
+
import { screenshot, detectFormat } from './index.js';
|
|
4
|
+
import { readFile, writeFile, access } from 'fs/promises';
|
|
5
|
+
import { resolve, dirname, basename, extname, join } from 'path';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
8
|
const __dirname = dirname(__filename);
|
|
9
9
|
const packageJson = JSON.parse(await readFile(resolve(__dirname, '../package.json'), 'utf-8'));
|
|
10
|
+
/**
|
|
11
|
+
* Generate a unique output path using macOS-style duplicate naming.
|
|
12
|
+
* If basePath exists, returns basePath with ' (1)', ' (2)', etc. suffix.
|
|
13
|
+
* Example: document.png -> document (1).png -> document (2).png
|
|
14
|
+
*/
|
|
15
|
+
async function getUniqueOutputPath(basePath) {
|
|
16
|
+
try {
|
|
17
|
+
await access(basePath);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// File doesn't exist, use the base path
|
|
21
|
+
return basePath;
|
|
22
|
+
}
|
|
23
|
+
const dir = dirname(basePath);
|
|
24
|
+
const ext = extname(basePath);
|
|
25
|
+
const stem = basename(basePath, ext);
|
|
26
|
+
let counter = 1;
|
|
27
|
+
// eslint-disable-next-line no-constant-condition
|
|
28
|
+
while (true) {
|
|
29
|
+
const newPath = join(dir, `${stem} (${counter})${ext}`);
|
|
30
|
+
try {
|
|
31
|
+
await access(newPath);
|
|
32
|
+
counter++;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return newPath;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
10
39
|
const program = new Command();
|
|
11
40
|
program
|
|
12
41
|
.name('screenitshot')
|
|
13
42
|
.description('Convert various file formats to high-quality screenshots')
|
|
14
43
|
.version(packageJson.version)
|
|
15
44
|
.argument('<input>', 'Input file path')
|
|
16
|
-
.argument('[output]', 'Output image path')
|
|
17
45
|
.option('-f, --format <format>', 'Output image format (png, jpeg, webp)', 'png')
|
|
18
|
-
.option('-w, --width <width>', 'Viewport width'
|
|
19
|
-
.option('-
|
|
46
|
+
.option('-w, --width <width>', 'Viewport width')
|
|
47
|
+
.option('-H, --height <height>', 'Viewport height')
|
|
48
|
+
.option('-o, --output <path>', 'Output file path (default: same folder as input)')
|
|
20
49
|
.option('-p, --page <page>', 'Page number for multi-page documents', '1')
|
|
21
|
-
.action(async (input,
|
|
50
|
+
.action(async (input, options) => {
|
|
22
51
|
try {
|
|
52
|
+
// Check input file exists and detect format
|
|
53
|
+
let inputData;
|
|
54
|
+
try {
|
|
55
|
+
inputData = await readFile(input);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
console.error(`Error: Input file not found: ${input}`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
const inputFormat = await detectFormat(input);
|
|
62
|
+
if (inputFormat === 'unknown') {
|
|
63
|
+
console.error(`Error: Unsupported file format: ${input}`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
// Determine output path
|
|
67
|
+
let outputPath;
|
|
68
|
+
if (options.output) {
|
|
69
|
+
outputPath = options.output;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
const dir = dirname(input);
|
|
73
|
+
const stem = basename(input, extname(input));
|
|
74
|
+
const baseOutput = join(dir, `${stem}.${options.format}`);
|
|
75
|
+
outputPath = await getUniqueOutputPath(baseOutput);
|
|
76
|
+
}
|
|
23
77
|
console.log(`Converting ${input}...`);
|
|
24
|
-
const result = await screenshot(
|
|
25
|
-
output,
|
|
78
|
+
const result = await screenshot(inputData, inputFormat, {
|
|
26
79
|
format: options.format,
|
|
27
|
-
width: parseInt(options.width),
|
|
28
|
-
height: parseInt(options.height),
|
|
80
|
+
width: options.width ? parseInt(options.width) : undefined,
|
|
81
|
+
height: options.height ? parseInt(options.height) : undefined,
|
|
29
82
|
page: parseInt(options.page),
|
|
83
|
+
fileName: basename(input),
|
|
30
84
|
});
|
|
31
|
-
|
|
85
|
+
// Write output to file
|
|
86
|
+
await writeFile(outputPath, result.data);
|
|
87
|
+
console.log(`✓ Screenshot saved to ${outputPath}`);
|
|
32
88
|
console.log(` Renderer: ${result.renderer}`);
|
|
33
89
|
console.log(` Format: ${result.format}`);
|
|
34
90
|
console.log(` Size: ${result.width}x${result.height}`);
|
|
@@ -36,16 +92,11 @@ program
|
|
|
36
92
|
catch (error) {
|
|
37
93
|
const err = error;
|
|
38
94
|
// User-friendly error messages
|
|
39
|
-
if (err.message.includes('
|
|
40
|
-
console.error(`Error: Input file not found: ${input}`);
|
|
41
|
-
}
|
|
42
|
-
else if (err.message.includes('Unsupported file format')) {
|
|
95
|
+
if (err.message.includes('Unknown format')) {
|
|
43
96
|
console.error(`Error: ${err.message}`);
|
|
44
|
-
console.error('Supported formats: PDF');
|
|
45
97
|
}
|
|
46
98
|
else if (err.message.includes('No template available')) {
|
|
47
99
|
console.error(`Error: Format not yet supported`);
|
|
48
|
-
console.error('Currently supported: PDF');
|
|
49
100
|
}
|
|
50
101
|
else if (err.message.includes('page')) {
|
|
51
102
|
console.error(`Error: Invalid page number or page not found`);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
import type { ScreenshotOptions, ScreenshotResult } from './types.js';
|
|
2
|
-
export { type ScreenshotOptions, type ScreenshotResult, type FileFormat } from './types.js';
|
|
3
|
-
export
|
|
2
|
+
export { type ScreenshotOptions, type ScreenshotResult, type FileFormat, resolveFormat } from './types.js';
|
|
3
|
+
export { detectFormat } from './detector.js';
|
|
4
|
+
/**
|
|
5
|
+
* Convert input data to a screenshot image.
|
|
6
|
+
*
|
|
7
|
+
* @param input - Input data: Buffer for documents, or URL string for 'url' format
|
|
8
|
+
* @param inputFormat - Input format as slug (e.g., 'pdf') or MIME type (e.g., 'application/pdf')
|
|
9
|
+
* @param options - Optional screenshot options (format, width, height, page, fileName)
|
|
10
|
+
* @returns ScreenshotResult with image data buffer and dimensions
|
|
11
|
+
*/
|
|
12
|
+
export declare function screenshot(input: Buffer | string, inputFormat: string, options?: ScreenshotOptions): Promise<ScreenshotResult>;
|
|
4
13
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGtE,OAAO,EAAE,KAAK,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,KAAK,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3G,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,KAAK,EAAE,MAAM,GAAG,MAAM,EACtB,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC,CAK3B"}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import { detectFormat } from './detector.js';
|
|
2
1
|
import { Renderer } from './renderer.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
import { resolveFormat } from './types.js';
|
|
3
|
+
export { resolveFormat } from './types.js';
|
|
4
|
+
export { detectFormat } from './detector.js';
|
|
5
|
+
/**
|
|
6
|
+
* Convert input data to a screenshot image.
|
|
7
|
+
*
|
|
8
|
+
* @param input - Input data: Buffer for documents, or URL string for 'url' format
|
|
9
|
+
* @param inputFormat - Input format as slug (e.g., 'pdf') or MIME type (e.g., 'application/pdf')
|
|
10
|
+
* @param options - Optional screenshot options (format, width, height, page, fileName)
|
|
11
|
+
* @returns ScreenshotResult with image data buffer and dimensions
|
|
12
|
+
*/
|
|
13
|
+
export async function screenshot(input, inputFormat, options = {}) {
|
|
14
|
+
const format = resolveFormat(inputFormat);
|
|
8
15
|
const renderer = new Renderer();
|
|
9
|
-
return await renderer.render(
|
|
16
|
+
return await renderer.render(input, format, options);
|
|
10
17
|
}
|
|
11
18
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,EAAkE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3G,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,KAAsB,EACtB,WAAmB,EACnB,UAA6B,EAAE;IAE/B,MAAM,MAAM,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;IAE1C,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC;IAChC,OAAO,MAAM,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AACvD,CAAC"}
|
package/dist/renderer.d.ts
CHANGED
|
@@ -2,6 +2,6 @@ import type { FileFormat, ScreenshotOptions, ScreenshotResult } from './types.js
|
|
|
2
2
|
export declare class Renderer {
|
|
3
3
|
private getTemplatePath;
|
|
4
4
|
private injectDataIntoPage;
|
|
5
|
-
render(
|
|
5
|
+
render(input: Buffer | string, format: FileFormat, options?: ScreenshotOptions): Promise<ScreenshotResult>;
|
|
6
6
|
}
|
|
7
7
|
//# sourceMappingURL=renderer.d.ts.map
|
package/dist/renderer.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAKlF,qBAAa,QAAQ;IACnB,OAAO,CAAC,eAAe;YA4BT,kBAAkB;IAe1B,MAAM,CACV,KAAK,EAAE,MAAM,GAAG,MAAM,EACtB,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC;CA6J7B"}
|
package/dist/renderer.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { chromium } from 'playwright';
|
|
2
|
-
import {
|
|
3
|
-
import { resolve, dirname, basename } from 'path';
|
|
2
|
+
import { resolve, dirname } from 'path';
|
|
4
3
|
import { fileURLToPath } from 'url';
|
|
5
4
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
5
|
const __dirname = dirname(__filename);
|
|
@@ -40,13 +39,12 @@ export class Renderer {
|
|
|
40
39
|
globalThis.fileName = fName;
|
|
41
40
|
}, { fileBase64, pageNum: pageNumber, fName: fileName });
|
|
42
41
|
}
|
|
43
|
-
async render(
|
|
44
|
-
const {
|
|
42
|
+
async render(input, format, options = {}) {
|
|
43
|
+
const { format: imageFormat = 'png', width, height, page: pageNumber = 1, fileName = '', } = options;
|
|
45
44
|
// Use small initial viewport - content will determine final size
|
|
46
45
|
// For formats like XLSX, large viewport causes table to expand to fill it
|
|
47
46
|
const initialWidth = width || 800;
|
|
48
47
|
const initialHeight = height || 600;
|
|
49
|
-
const outputPath = output || inputPath.replace(/\.[^.]+$/, `.${imageFormat}`);
|
|
50
48
|
// Launch browser
|
|
51
49
|
const browser = await chromium.launch({
|
|
52
50
|
headless: true,
|
|
@@ -60,40 +58,44 @@ export class Renderer {
|
|
|
60
58
|
});
|
|
61
59
|
// Special handling for URL format - navigate directly to the URL
|
|
62
60
|
if (format === 'url') {
|
|
63
|
-
//
|
|
64
|
-
const
|
|
65
|
-
|
|
61
|
+
// Input should be URL string (or buffer containing URL)
|
|
62
|
+
const url = Buffer.isBuffer(input)
|
|
63
|
+
? input.toString('utf-8').trim()
|
|
64
|
+
: input.trim();
|
|
66
65
|
// Set a reasonable viewport for webpage screenshots
|
|
67
66
|
const webWidth = width || 1280;
|
|
68
67
|
const webHeight = height || 800;
|
|
69
68
|
await page.setViewportSize({ width: webWidth, height: webHeight });
|
|
70
69
|
// Navigate to URL and wait for network idle
|
|
71
70
|
await page.goto(url, { waitUntil: 'networkidle' });
|
|
72
|
-
// Take screenshot
|
|
73
|
-
await page.screenshot({
|
|
74
|
-
path: outputPath,
|
|
71
|
+
// Take screenshot to buffer
|
|
72
|
+
const screenshotData = await page.screenshot({
|
|
75
73
|
type: imageFormat,
|
|
76
74
|
fullPage: false,
|
|
77
75
|
});
|
|
78
76
|
await browser.close();
|
|
79
77
|
return {
|
|
80
|
-
|
|
78
|
+
data: screenshotData,
|
|
81
79
|
format: imageFormat,
|
|
82
80
|
width: webWidth * deviceScaleFactor,
|
|
83
81
|
height: webHeight * deviceScaleFactor,
|
|
84
82
|
renderer: format,
|
|
85
83
|
};
|
|
86
84
|
}
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
85
|
+
// Encode input as base64
|
|
86
|
+
let fileBase64;
|
|
87
|
+
if (Buffer.isBuffer(input)) {
|
|
88
|
+
fileBase64 = input.toString('base64');
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
// String input for non-URL formats is treated as text content
|
|
92
|
+
fileBase64 = Buffer.from(input, 'utf-8').toString('base64');
|
|
93
|
+
}
|
|
94
|
+
// Inject data before loading template
|
|
92
95
|
await this.injectDataIntoPage(page, fileBase64, pageNumber, fileName);
|
|
93
96
|
// Load template
|
|
94
97
|
const templatePath = this.getTemplatePath(format);
|
|
95
98
|
await page.goto(`file://${templatePath}`);
|
|
96
|
-
// Wait for render complete and get metadata
|
|
97
99
|
const metadata = await page.evaluate(async () => {
|
|
98
100
|
const renderComplete = globalThis.renderComplete;
|
|
99
101
|
if (!renderComplete) {
|
|
@@ -105,6 +107,7 @@ export class Renderer {
|
|
|
105
107
|
// Check if we need to clip (for EPUB content cropping)
|
|
106
108
|
const clipX = metadata.clipX;
|
|
107
109
|
const clipY = metadata.clipY;
|
|
110
|
+
let screenshotData;
|
|
108
111
|
if (clipX !== undefined && clipY !== undefined) {
|
|
109
112
|
// Resize viewport to ensure clip area is fully visible
|
|
110
113
|
const requiredWidth = clipX + metadata.width;
|
|
@@ -116,8 +119,7 @@ export class Renderer {
|
|
|
116
119
|
// Wait for layout to stabilize
|
|
117
120
|
await page.waitForTimeout(100);
|
|
118
121
|
// Use clip to capture just the content area
|
|
119
|
-
await page.screenshot({
|
|
120
|
-
path: outputPath,
|
|
122
|
+
screenshotData = await page.screenshot({
|
|
121
123
|
type: imageFormat,
|
|
122
124
|
clip: {
|
|
123
125
|
x: clipX,
|
|
@@ -136,8 +138,7 @@ export class Renderer {
|
|
|
136
138
|
// Wait for layout to stabilize after viewport resize
|
|
137
139
|
await page.waitForTimeout(100);
|
|
138
140
|
// Take screenshot at exact rendered size
|
|
139
|
-
await page.screenshot({
|
|
140
|
-
path: outputPath,
|
|
141
|
+
screenshotData = await page.screenshot({
|
|
141
142
|
type: imageFormat,
|
|
142
143
|
fullPage: false,
|
|
143
144
|
});
|
|
@@ -147,7 +148,7 @@ export class Renderer {
|
|
|
147
148
|
const actualWidth = metadata.width * deviceScaleFactor;
|
|
148
149
|
const actualHeight = metadata.height * deviceScaleFactor;
|
|
149
150
|
return {
|
|
150
|
-
|
|
151
|
+
data: screenshotData,
|
|
151
152
|
format: imageFormat,
|
|
152
153
|
width: actualWidth,
|
|
153
154
|
height: actualHeight,
|
package/dist/renderer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer.js","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAa,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"renderer.js","sourceRoot":"","sources":["../src/renderer.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export interface ScreenshotOptions {
|
|
2
|
-
output?: string;
|
|
3
2
|
format?: 'png' | 'jpeg' | 'webp';
|
|
4
3
|
width?: number;
|
|
5
4
|
height?: number;
|
|
6
5
|
page?: number;
|
|
6
|
+
fileName?: string;
|
|
7
7
|
}
|
|
8
8
|
export interface ScreenshotResult {
|
|
9
|
-
|
|
9
|
+
data: Buffer;
|
|
10
10
|
format: string;
|
|
11
11
|
width: number;
|
|
12
12
|
height: number;
|
|
@@ -20,4 +20,10 @@ export interface RenderMetadata {
|
|
|
20
20
|
scale: number;
|
|
21
21
|
}
|
|
22
22
|
export type FileFormat = 'pdf' | 'epub' | 'docx' | 'xlsx' | 'pptx' | 'md' | 'html' | 'csv' | 'rtf' | 'ipynb' | 'tex' | 'code' | 'url' | 'mmd' | 'geojson' | 'gpx' | 'unknown';
|
|
23
|
+
export declare const MIME_TO_FORMAT: Record<string, FileFormat>;
|
|
24
|
+
export declare const SLUG_TO_FORMAT: Record<string, FileFormat>;
|
|
25
|
+
/**
|
|
26
|
+
* Resolve a format string (MIME type or slug) to a FileFormat.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveFormat(formatStr: string): FileFormat;
|
|
23
29
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,SAAS,GAAG,KAAK,GAAG,SAAS,CAAC;AAG9K,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAgBrD,CAAC;AAGF,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAsBrD,CAAC;AAEF;;GAEG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,CAa3D"}
|
package/dist/types.js
CHANGED
|
@@ -1,2 +1,58 @@
|
|
|
1
|
-
|
|
1
|
+
// Mapping from MIME types to FileFormat
|
|
2
|
+
export const MIME_TO_FORMAT = {
|
|
3
|
+
'application/pdf': 'pdf',
|
|
4
|
+
'application/epub+zip': 'epub',
|
|
5
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',
|
|
6
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx',
|
|
7
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'pptx',
|
|
8
|
+
'text/markdown': 'md',
|
|
9
|
+
'text/html': 'html',
|
|
10
|
+
'text/csv': 'csv',
|
|
11
|
+
'application/rtf': 'rtf',
|
|
12
|
+
'text/rtf': 'rtf',
|
|
13
|
+
'application/x-ipynb+json': 'ipynb',
|
|
14
|
+
'application/x-tex': 'tex',
|
|
15
|
+
'text/x-tex': 'tex',
|
|
16
|
+
'application/geo+json': 'geojson',
|
|
17
|
+
'application/gpx+xml': 'gpx',
|
|
18
|
+
};
|
|
19
|
+
// Mapping from slug names to FileFormat
|
|
20
|
+
export const SLUG_TO_FORMAT = {
|
|
21
|
+
'pdf': 'pdf',
|
|
22
|
+
'epub': 'epub',
|
|
23
|
+
'docx': 'docx',
|
|
24
|
+
'xlsx': 'xlsx',
|
|
25
|
+
'pptx': 'pptx',
|
|
26
|
+
'md': 'md',
|
|
27
|
+
'markdown': 'md',
|
|
28
|
+
'html': 'html',
|
|
29
|
+
'htm': 'html',
|
|
30
|
+
'csv': 'csv',
|
|
31
|
+
'rtf': 'rtf',
|
|
32
|
+
'ipynb': 'ipynb',
|
|
33
|
+
'jupyter': 'ipynb',
|
|
34
|
+
'tex': 'tex',
|
|
35
|
+
'latex': 'tex',
|
|
36
|
+
'code': 'code',
|
|
37
|
+
'url': 'url',
|
|
38
|
+
'mmd': 'mmd',
|
|
39
|
+
'mermaid': 'mmd',
|
|
40
|
+
'geojson': 'geojson',
|
|
41
|
+
'gpx': 'gpx',
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Resolve a format string (MIME type or slug) to a FileFormat.
|
|
45
|
+
*/
|
|
46
|
+
export function resolveFormat(formatStr) {
|
|
47
|
+
// Try slug first (more common)
|
|
48
|
+
const lowerFormat = formatStr.toLowerCase();
|
|
49
|
+
if (lowerFormat in SLUG_TO_FORMAT) {
|
|
50
|
+
return SLUG_TO_FORMAT[lowerFormat];
|
|
51
|
+
}
|
|
52
|
+
// Try MIME type
|
|
53
|
+
if (formatStr in MIME_TO_FORMAT) {
|
|
54
|
+
return MIME_TO_FORMAT[formatStr];
|
|
55
|
+
}
|
|
56
|
+
throw new Error(`Unknown format: ${formatStr}. Use a slug (e.g., 'pdf') or MIME type (e.g., 'application/pdf')`);
|
|
57
|
+
}
|
|
2
58
|
//# sourceMappingURL=types.js.map
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA0BA,wCAAwC;AACxC,MAAM,CAAC,MAAM,cAAc,GAA+B;IACxD,iBAAiB,EAAE,KAAK;IACxB,sBAAsB,EAAE,MAAM;IAC9B,yEAAyE,EAAE,MAAM;IACjF,mEAAmE,EAAE,MAAM;IAC3E,2EAA2E,EAAE,MAAM;IACnF,eAAe,EAAE,IAAI;IACrB,WAAW,EAAE,MAAM;IACnB,UAAU,EAAE,KAAK;IACjB,iBAAiB,EAAE,KAAK;IACxB,UAAU,EAAE,KAAK;IACjB,0BAA0B,EAAE,OAAO;IACnC,mBAAmB,EAAE,KAAK;IAC1B,YAAY,EAAE,KAAK;IACnB,sBAAsB,EAAE,SAAS;IACjC,qBAAqB,EAAE,KAAK;CAC7B,CAAC;AAEF,wCAAwC;AACxC,MAAM,CAAC,MAAM,cAAc,GAA+B;IACxD,KAAK,EAAE,KAAK;IACZ,MAAM,EAAE,MAAM;IACd,MAAM,EAAE,MAAM;IACd,MAAM,EAAE,MAAM;IACd,MAAM,EAAE,MAAM;IACd,IAAI,EAAE,IAAI;IACV,UAAU,EAAE,IAAI;IAChB,MAAM,EAAE,MAAM;IACd,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,KAAK;IACZ,OAAO,EAAE,OAAO;IAChB,SAAS,EAAE,OAAO;IAClB,KAAK,EAAE,KAAK;IACZ,OAAO,EAAE,KAAK;IACd,MAAM,EAAE,MAAM;IACd,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,KAAK;IACZ,SAAS,EAAE,KAAK;IAChB,SAAS,EAAE,SAAS;IACpB,KAAK,EAAE,KAAK;CACb,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,SAAiB;IAC7C,+BAA+B;IAC/B,MAAM,WAAW,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,WAAW,IAAI,cAAc,EAAE,CAAC;QAClC,OAAO,cAAc,CAAC,WAAW,CAAC,CAAC;IACrC,CAAC;IAED,gBAAgB;IAChB,IAAI,SAAS,IAAI,cAAc,EAAE,CAAC;QAChC,OAAO,cAAc,CAAC,SAAS,CAAC,CAAC;IACnC,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,mBAAmB,SAAS,mEAAmE,CAAC,CAAC;AACnH,CAAC"}
|
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
|
-
import { screenshot } from './index.js';
|
|
5
|
-
import { readFile } from 'fs/promises';
|
|
6
|
-
import { resolve, dirname } from 'path';
|
|
4
|
+
import { screenshot, detectFormat } from './index.js';
|
|
5
|
+
import { readFile, writeFile, access } from 'fs/promises';
|
|
6
|
+
import { resolve, dirname, basename, extname, join } from 'path';
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
8
|
|
|
9
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -13,6 +13,36 @@ const packageJson = JSON.parse(
|
|
|
13
13
|
await readFile(resolve(__dirname, '../package.json'), 'utf-8')
|
|
14
14
|
);
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Generate a unique output path using macOS-style duplicate naming.
|
|
18
|
+
* If basePath exists, returns basePath with ' (1)', ' (2)', etc. suffix.
|
|
19
|
+
* Example: document.png -> document (1).png -> document (2).png
|
|
20
|
+
*/
|
|
21
|
+
async function getUniqueOutputPath(basePath: string): Promise<string> {
|
|
22
|
+
try {
|
|
23
|
+
await access(basePath);
|
|
24
|
+
} catch {
|
|
25
|
+
// File doesn't exist, use the base path
|
|
26
|
+
return basePath;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const dir = dirname(basePath);
|
|
30
|
+
const ext = extname(basePath);
|
|
31
|
+
const stem = basename(basePath, ext);
|
|
32
|
+
|
|
33
|
+
let counter = 1;
|
|
34
|
+
// eslint-disable-next-line no-constant-condition
|
|
35
|
+
while (true) {
|
|
36
|
+
const newPath = join(dir, `${stem} (${counter})${ext}`);
|
|
37
|
+
try {
|
|
38
|
+
await access(newPath);
|
|
39
|
+
counter++;
|
|
40
|
+
} catch {
|
|
41
|
+
return newPath;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
16
46
|
const program = new Command();
|
|
17
47
|
|
|
18
48
|
program
|
|
@@ -20,24 +50,53 @@ program
|
|
|
20
50
|
.description('Convert various file formats to high-quality screenshots')
|
|
21
51
|
.version(packageJson.version)
|
|
22
52
|
.argument('<input>', 'Input file path')
|
|
23
|
-
.argument('[output]', 'Output image path')
|
|
24
53
|
.option('-f, --format <format>', 'Output image format (png, jpeg, webp)', 'png')
|
|
25
|
-
.option('-w, --width <width>', 'Viewport width'
|
|
26
|
-
.option('-
|
|
54
|
+
.option('-w, --width <width>', 'Viewport width')
|
|
55
|
+
.option('-H, --height <height>', 'Viewport height')
|
|
56
|
+
.option('-o, --output <path>', 'Output file path (default: same folder as input)')
|
|
27
57
|
.option('-p, --page <page>', 'Page number for multi-page documents', '1')
|
|
28
|
-
.action(async (input: string,
|
|
58
|
+
.action(async (input: string, options: { format: 'png' | 'jpeg' | 'webp'; width?: string; height?: string; output?: string; page: string }) => {
|
|
29
59
|
try {
|
|
60
|
+
// Check input file exists and detect format
|
|
61
|
+
let inputData: Buffer;
|
|
62
|
+
try {
|
|
63
|
+
inputData = await readFile(input);
|
|
64
|
+
} catch {
|
|
65
|
+
console.error(`Error: Input file not found: ${input}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const inputFormat = await detectFormat(input);
|
|
70
|
+
if (inputFormat === 'unknown') {
|
|
71
|
+
console.error(`Error: Unsupported file format: ${input}`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Determine output path
|
|
76
|
+
let outputPath: string;
|
|
77
|
+
if (options.output) {
|
|
78
|
+
outputPath = options.output;
|
|
79
|
+
} else {
|
|
80
|
+
const dir = dirname(input);
|
|
81
|
+
const stem = basename(input, extname(input));
|
|
82
|
+
const baseOutput = join(dir, `${stem}.${options.format}`);
|
|
83
|
+
outputPath = await getUniqueOutputPath(baseOutput);
|
|
84
|
+
}
|
|
85
|
+
|
|
30
86
|
console.log(`Converting ${input}...`);
|
|
31
87
|
|
|
32
|
-
const result = await screenshot(
|
|
33
|
-
output,
|
|
88
|
+
const result = await screenshot(inputData, inputFormat, {
|
|
34
89
|
format: options.format,
|
|
35
|
-
width: parseInt(options.width),
|
|
36
|
-
height: parseInt(options.height),
|
|
90
|
+
width: options.width ? parseInt(options.width) : undefined,
|
|
91
|
+
height: options.height ? parseInt(options.height) : undefined,
|
|
37
92
|
page: parseInt(options.page),
|
|
93
|
+
fileName: basename(input),
|
|
38
94
|
});
|
|
39
95
|
|
|
40
|
-
|
|
96
|
+
// Write output to file
|
|
97
|
+
await writeFile(outputPath, result.data);
|
|
98
|
+
|
|
99
|
+
console.log(`✓ Screenshot saved to ${outputPath}`);
|
|
41
100
|
console.log(` Renderer: ${result.renderer}`);
|
|
42
101
|
console.log(` Format: ${result.format}`);
|
|
43
102
|
console.log(` Size: ${result.width}x${result.height}`);
|
|
@@ -45,14 +104,10 @@ program
|
|
|
45
104
|
const err = error as Error;
|
|
46
105
|
|
|
47
106
|
// User-friendly error messages
|
|
48
|
-
if (err.message.includes('
|
|
49
|
-
console.error(`Error: Input file not found: ${input}`);
|
|
50
|
-
} else if (err.message.includes('Unsupported file format')) {
|
|
107
|
+
if (err.message.includes('Unknown format')) {
|
|
51
108
|
console.error(`Error: ${err.message}`);
|
|
52
|
-
console.error('Supported formats: PDF');
|
|
53
109
|
} else if (err.message.includes('No template available')) {
|
|
54
110
|
console.error(`Error: Format not yet supported`);
|
|
55
|
-
console.error('Currently supported: PDF');
|
|
56
111
|
} else if (err.message.includes('page')) {
|
|
57
112
|
console.error(`Error: Invalid page number or page not found`);
|
|
58
113
|
// Show stack trace in debug mode
|
package/src/index.ts
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
import { detectFormat } from './detector.js';
|
|
2
1
|
import { Renderer } from './renderer.js';
|
|
3
2
|
import type { ScreenshotOptions, ScreenshotResult } from './types.js';
|
|
3
|
+
import { resolveFormat } from './types.js';
|
|
4
4
|
|
|
5
|
-
export { type ScreenshotOptions, type ScreenshotResult, type FileFormat } from './types.js';
|
|
5
|
+
export { type ScreenshotOptions, type ScreenshotResult, type FileFormat, resolveFormat } from './types.js';
|
|
6
|
+
export { detectFormat } from './detector.js';
|
|
6
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Convert input data to a screenshot image.
|
|
10
|
+
*
|
|
11
|
+
* @param input - Input data: Buffer for documents, or URL string for 'url' format
|
|
12
|
+
* @param inputFormat - Input format as slug (e.g., 'pdf') or MIME type (e.g., 'application/pdf')
|
|
13
|
+
* @param options - Optional screenshot options (format, width, height, page, fileName)
|
|
14
|
+
* @returns ScreenshotResult with image data buffer and dimensions
|
|
15
|
+
*/
|
|
7
16
|
export async function screenshot(
|
|
8
|
-
|
|
17
|
+
input: Buffer | string,
|
|
18
|
+
inputFormat: string,
|
|
9
19
|
options: ScreenshotOptions = {}
|
|
10
20
|
): Promise<ScreenshotResult> {
|
|
11
|
-
const format =
|
|
12
|
-
|
|
13
|
-
if (format === 'unknown') {
|
|
14
|
-
throw new Error(`Unsupported file format: ${inputPath}`);
|
|
15
|
-
}
|
|
21
|
+
const format = resolveFormat(inputFormat);
|
|
16
22
|
|
|
17
23
|
const renderer = new Renderer();
|
|
18
|
-
return await renderer.render(
|
|
24
|
+
return await renderer.render(input, format, options);
|
|
19
25
|
}
|
package/src/renderer.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { chromium, type Page } from 'playwright';
|
|
2
|
-
import {
|
|
3
|
-
import { resolve, dirname, basename } from 'path';
|
|
2
|
+
import { resolve, dirname } from 'path';
|
|
4
3
|
import { fileURLToPath } from 'url';
|
|
5
4
|
import type { FileFormat, ScreenshotOptions, ScreenshotResult } from './types.js';
|
|
6
5
|
|
|
@@ -45,23 +44,23 @@ export class Renderer {
|
|
|
45
44
|
// Inject data into page globals before template loads
|
|
46
45
|
await page.addInitScript(({ fileBase64: fb64, pageNum, fName }: { fileBase64: string; pageNum: number; fName: string }) => {
|
|
47
46
|
// Override the placeholder values
|
|
48
|
-
(globalThis as
|
|
49
|
-
(globalThis as
|
|
50
|
-
(globalThis as
|
|
47
|
+
(globalThis as unknown as Record<string, unknown>).fileBase64 = fb64;
|
|
48
|
+
(globalThis as unknown as Record<string, unknown>).pageNumber = pageNum;
|
|
49
|
+
(globalThis as unknown as Record<string, unknown>).fileName = fName;
|
|
51
50
|
}, { fileBase64, pageNum: pageNumber, fName: fileName });
|
|
52
51
|
}
|
|
53
52
|
|
|
54
53
|
async render(
|
|
55
|
-
|
|
54
|
+
input: Buffer | string,
|
|
56
55
|
format: FileFormat,
|
|
57
56
|
options: ScreenshotOptions = {}
|
|
58
57
|
): Promise<ScreenshotResult> {
|
|
59
58
|
const {
|
|
60
|
-
output,
|
|
61
59
|
format: imageFormat = 'png',
|
|
62
60
|
width,
|
|
63
61
|
height,
|
|
64
62
|
page: pageNumber = 1,
|
|
63
|
+
fileName = '',
|
|
65
64
|
} = options;
|
|
66
65
|
|
|
67
66
|
// Use small initial viewport - content will determine final size
|
|
@@ -69,8 +68,6 @@ export class Renderer {
|
|
|
69
68
|
const initialWidth = width || 800;
|
|
70
69
|
const initialHeight = height || 600;
|
|
71
70
|
|
|
72
|
-
const outputPath = output || inputPath.replace(/\.[^.]+$/, `.${imageFormat}`);
|
|
73
|
-
|
|
74
71
|
// Launch browser
|
|
75
72
|
const browser = await chromium.launch({
|
|
76
73
|
headless: true,
|
|
@@ -87,9 +84,10 @@ export class Renderer {
|
|
|
87
84
|
|
|
88
85
|
// Special handling for URL format - navigate directly to the URL
|
|
89
86
|
if (format === 'url') {
|
|
90
|
-
//
|
|
91
|
-
const
|
|
92
|
-
|
|
87
|
+
// Input should be URL string (or buffer containing URL)
|
|
88
|
+
const url = Buffer.isBuffer(input)
|
|
89
|
+
? input.toString('utf-8').trim()
|
|
90
|
+
: input.trim();
|
|
93
91
|
|
|
94
92
|
// Set a reasonable viewport for webpage screenshots
|
|
95
93
|
const webWidth = width || 1280;
|
|
@@ -99,9 +97,8 @@ export class Renderer {
|
|
|
99
97
|
// Navigate to URL and wait for network idle
|
|
100
98
|
await page.goto(url, { waitUntil: 'networkidle' });
|
|
101
99
|
|
|
102
|
-
// Take screenshot
|
|
103
|
-
await page.screenshot({
|
|
104
|
-
path: outputPath,
|
|
100
|
+
// Take screenshot to buffer
|
|
101
|
+
const screenshotData = await page.screenshot({
|
|
105
102
|
type: imageFormat as 'png' | 'jpeg',
|
|
106
103
|
fullPage: false,
|
|
107
104
|
});
|
|
@@ -109,7 +106,7 @@ export class Renderer {
|
|
|
109
106
|
await browser.close();
|
|
110
107
|
|
|
111
108
|
return {
|
|
112
|
-
|
|
109
|
+
data: screenshotData,
|
|
113
110
|
format: imageFormat,
|
|
114
111
|
width: webWidth * deviceScaleFactor,
|
|
115
112
|
height: webHeight * deviceScaleFactor,
|
|
@@ -117,12 +114,16 @@ export class Renderer {
|
|
|
117
114
|
};
|
|
118
115
|
}
|
|
119
116
|
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
117
|
+
// Encode input as base64
|
|
118
|
+
let fileBase64: string;
|
|
119
|
+
if (Buffer.isBuffer(input)) {
|
|
120
|
+
fileBase64 = input.toString('base64');
|
|
121
|
+
} else {
|
|
122
|
+
// String input for non-URL formats is treated as text content
|
|
123
|
+
fileBase64 = Buffer.from(input, 'utf-8').toString('base64');
|
|
124
|
+
}
|
|
123
125
|
|
|
124
|
-
// Inject data before loading template
|
|
125
|
-
const fileName = basename(inputPath);
|
|
126
|
+
// Inject data before loading template
|
|
126
127
|
await this.injectDataIntoPage(page, fileBase64, pageNumber, fileName);
|
|
127
128
|
|
|
128
129
|
// Load template
|
|
@@ -130,8 +131,14 @@ export class Renderer {
|
|
|
130
131
|
await page.goto(`file://${templatePath}`);
|
|
131
132
|
|
|
132
133
|
// Wait for render complete and get metadata
|
|
134
|
+
interface PageMetadata {
|
|
135
|
+
width: number;
|
|
136
|
+
height: number;
|
|
137
|
+
clipX?: number;
|
|
138
|
+
clipY?: number;
|
|
139
|
+
}
|
|
133
140
|
const metadata = await page.evaluate(async () => {
|
|
134
|
-
const renderComplete = (globalThis as
|
|
141
|
+
const renderComplete = (globalThis as unknown as Record<string, unknown>).renderComplete as Promise<PageMetadata> | undefined;
|
|
135
142
|
|
|
136
143
|
if (!renderComplete) {
|
|
137
144
|
throw new Error('window.renderComplete not found');
|
|
@@ -142,8 +149,10 @@ export class Renderer {
|
|
|
142
149
|
});
|
|
143
150
|
|
|
144
151
|
// Check if we need to clip (for EPUB content cropping)
|
|
145
|
-
const clipX =
|
|
146
|
-
const clipY =
|
|
152
|
+
const clipX = metadata.clipX;
|
|
153
|
+
const clipY = metadata.clipY;
|
|
154
|
+
|
|
155
|
+
let screenshotData: Buffer;
|
|
147
156
|
|
|
148
157
|
if (clipX !== undefined && clipY !== undefined) {
|
|
149
158
|
// Resize viewport to ensure clip area is fully visible
|
|
@@ -158,8 +167,7 @@ export class Renderer {
|
|
|
158
167
|
await page.waitForTimeout(100);
|
|
159
168
|
|
|
160
169
|
// Use clip to capture just the content area
|
|
161
|
-
await page.screenshot({
|
|
162
|
-
path: outputPath,
|
|
170
|
+
screenshotData = await page.screenshot({
|
|
163
171
|
type: imageFormat as 'png' | 'jpeg',
|
|
164
172
|
clip: {
|
|
165
173
|
x: clipX,
|
|
@@ -179,8 +187,7 @@ export class Renderer {
|
|
|
179
187
|
await page.waitForTimeout(100);
|
|
180
188
|
|
|
181
189
|
// Take screenshot at exact rendered size
|
|
182
|
-
await page.screenshot({
|
|
183
|
-
path: outputPath,
|
|
190
|
+
screenshotData = await page.screenshot({
|
|
184
191
|
type: imageFormat as 'png' | 'jpeg',
|
|
185
192
|
fullPage: false,
|
|
186
193
|
});
|
|
@@ -193,7 +200,7 @@ export class Renderer {
|
|
|
193
200
|
const actualHeight = metadata.height * deviceScaleFactor;
|
|
194
201
|
|
|
195
202
|
return {
|
|
196
|
-
|
|
203
|
+
data: screenshotData,
|
|
197
204
|
format: imageFormat,
|
|
198
205
|
width: actualWidth,
|
|
199
206
|
height: actualHeight,
|
package/src/types.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
export interface ScreenshotOptions {
|
|
2
|
-
output?: string;
|
|
3
2
|
format?: 'png' | 'jpeg' | 'webp';
|
|
4
3
|
width?: number;
|
|
5
4
|
height?: number;
|
|
6
5
|
page?: number;
|
|
6
|
+
fileName?: string;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export interface ScreenshotResult {
|
|
10
|
-
|
|
10
|
+
data: Buffer;
|
|
11
11
|
format: string;
|
|
12
12
|
width: number;
|
|
13
13
|
height: number;
|
|
@@ -23,3 +23,65 @@ export interface RenderMetadata {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export type FileFormat = 'pdf' | 'epub' | 'docx' | 'xlsx' | 'pptx' | 'md' | 'html' | 'csv' | 'rtf' | 'ipynb' | 'tex' | 'code' | 'url' | 'mmd' | 'geojson' | 'gpx' | 'unknown';
|
|
26
|
+
|
|
27
|
+
// Mapping from MIME types to FileFormat
|
|
28
|
+
export const MIME_TO_FORMAT: Record<string, FileFormat> = {
|
|
29
|
+
'application/pdf': 'pdf',
|
|
30
|
+
'application/epub+zip': 'epub',
|
|
31
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',
|
|
32
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx',
|
|
33
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'pptx',
|
|
34
|
+
'text/markdown': 'md',
|
|
35
|
+
'text/html': 'html',
|
|
36
|
+
'text/csv': 'csv',
|
|
37
|
+
'application/rtf': 'rtf',
|
|
38
|
+
'text/rtf': 'rtf',
|
|
39
|
+
'application/x-ipynb+json': 'ipynb',
|
|
40
|
+
'application/x-tex': 'tex',
|
|
41
|
+
'text/x-tex': 'tex',
|
|
42
|
+
'application/geo+json': 'geojson',
|
|
43
|
+
'application/gpx+xml': 'gpx',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// Mapping from slug names to FileFormat
|
|
47
|
+
export const SLUG_TO_FORMAT: Record<string, FileFormat> = {
|
|
48
|
+
'pdf': 'pdf',
|
|
49
|
+
'epub': 'epub',
|
|
50
|
+
'docx': 'docx',
|
|
51
|
+
'xlsx': 'xlsx',
|
|
52
|
+
'pptx': 'pptx',
|
|
53
|
+
'md': 'md',
|
|
54
|
+
'markdown': 'md',
|
|
55
|
+
'html': 'html',
|
|
56
|
+
'htm': 'html',
|
|
57
|
+
'csv': 'csv',
|
|
58
|
+
'rtf': 'rtf',
|
|
59
|
+
'ipynb': 'ipynb',
|
|
60
|
+
'jupyter': 'ipynb',
|
|
61
|
+
'tex': 'tex',
|
|
62
|
+
'latex': 'tex',
|
|
63
|
+
'code': 'code',
|
|
64
|
+
'url': 'url',
|
|
65
|
+
'mmd': 'mmd',
|
|
66
|
+
'mermaid': 'mmd',
|
|
67
|
+
'geojson': 'geojson',
|
|
68
|
+
'gpx': 'gpx',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Resolve a format string (MIME type or slug) to a FileFormat.
|
|
73
|
+
*/
|
|
74
|
+
export function resolveFormat(formatStr: string): FileFormat {
|
|
75
|
+
// Try slug first (more common)
|
|
76
|
+
const lowerFormat = formatStr.toLowerCase();
|
|
77
|
+
if (lowerFormat in SLUG_TO_FORMAT) {
|
|
78
|
+
return SLUG_TO_FORMAT[lowerFormat];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Try MIME type
|
|
82
|
+
if (formatStr in MIME_TO_FORMAT) {
|
|
83
|
+
return MIME_TO_FORMAT[formatStr];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
throw new Error(`Unknown format: ${formatStr}. Use a slug (e.g., 'pdf') or MIME type (e.g., 'application/pdf')`);
|
|
87
|
+
}
|