screenitshot 0.5.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +63 -18
- package/dist/cli.js.map +1 -1
- package/dist/detector.d.ts.map +1 -1
- package/dist/detector.js +88 -0
- package/dist/detector.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 +58 -17
- package/dist/renderer.js.map +1 -1
- package/dist/types.d.ts +10 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +57 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/src/cli.ts +67 -17
- package/src/detector.ts +88 -0
- package/src/index.ts +15 -9
- package/src/renderer.ts +76 -22
- package/src/types.ts +66 -3
package/dist/cli.js
CHANGED
|
@@ -1,50 +1,95 @@
|
|
|
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')
|
|
20
48
|
.option('-p, --page <page>', 'Page number for multi-page documents', '1')
|
|
21
|
-
.action(async (input,
|
|
49
|
+
.action(async (input, options) => {
|
|
22
50
|
try {
|
|
51
|
+
// Check input file exists and detect format
|
|
52
|
+
let inputData;
|
|
53
|
+
try {
|
|
54
|
+
inputData = await readFile(input);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
console.error(`Error: Input file not found: ${input}`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
const inputFormat = await detectFormat(input);
|
|
61
|
+
if (inputFormat === 'unknown') {
|
|
62
|
+
console.error(`Error: Unsupported file format: ${input}`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
// Determine output path (same folder as input, with unique name)
|
|
66
|
+
const dir = dirname(input);
|
|
67
|
+
const stem = basename(input, extname(input));
|
|
68
|
+
const baseOutput = join(dir, `${stem}.${options.format}`);
|
|
69
|
+
const outputPath = await getUniqueOutputPath(baseOutput);
|
|
23
70
|
console.log(`Converting ${input}...`);
|
|
24
|
-
const result = await screenshot(
|
|
25
|
-
output,
|
|
71
|
+
const result = await screenshot(inputData, inputFormat, {
|
|
26
72
|
format: options.format,
|
|
27
|
-
width: parseInt(options.width),
|
|
28
|
-
height: parseInt(options.height),
|
|
73
|
+
width: options.width ? parseInt(options.width) : undefined,
|
|
74
|
+
height: options.height ? parseInt(options.height) : undefined,
|
|
29
75
|
page: parseInt(options.page),
|
|
76
|
+
fileName: basename(input),
|
|
30
77
|
});
|
|
31
|
-
|
|
78
|
+
// Write output to file
|
|
79
|
+
await writeFile(outputPath, result.data);
|
|
80
|
+
console.log(`✓ Screenshot saved to ${outputPath}`);
|
|
81
|
+
console.log(` Renderer: ${result.renderer}`);
|
|
32
82
|
console.log(` Format: ${result.format}`);
|
|
33
83
|
console.log(` Size: ${result.width}x${result.height}`);
|
|
34
84
|
}
|
|
35
85
|
catch (error) {
|
|
36
86
|
const err = error;
|
|
37
87
|
// User-friendly error messages
|
|
38
|
-
if (err.message.includes('
|
|
39
|
-
console.error(`Error: Input file not found: ${input}`);
|
|
40
|
-
}
|
|
41
|
-
else if (err.message.includes('Unsupported file format')) {
|
|
88
|
+
if (err.message.includes('Unknown format')) {
|
|
42
89
|
console.error(`Error: ${err.message}`);
|
|
43
|
-
console.error('Supported formats: PDF');
|
|
44
90
|
}
|
|
45
91
|
else if (err.message.includes('No template available')) {
|
|
46
92
|
console.error(`Error: Format not yet supported`);
|
|
47
|
-
console.error('Currently supported: PDF');
|
|
48
93
|
}
|
|
49
94
|
else if (err.message.includes('page')) {
|
|
50
95
|
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/detector.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../src/detector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,
|
|
1
|
+
{"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../src/detector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAoHxE"}
|
package/dist/detector.js
CHANGED
|
@@ -9,6 +9,94 @@ export async function detectFormat(filePath) {
|
|
|
9
9
|
'.docx': 'docx',
|
|
10
10
|
'.xlsx': 'xlsx',
|
|
11
11
|
'.pptx': 'pptx',
|
|
12
|
+
'.md': 'md',
|
|
13
|
+
'.markdown': 'md',
|
|
14
|
+
'.html': 'html',
|
|
15
|
+
'.htm': 'html',
|
|
16
|
+
'.csv': 'csv',
|
|
17
|
+
'.tsv': 'csv',
|
|
18
|
+
'.rtf': 'rtf',
|
|
19
|
+
'.ipynb': 'ipynb',
|
|
20
|
+
'.tex': 'tex',
|
|
21
|
+
'.latex': 'tex',
|
|
22
|
+
// Source code extensions
|
|
23
|
+
'.js': 'code',
|
|
24
|
+
'.jsx': 'code',
|
|
25
|
+
'.ts': 'code',
|
|
26
|
+
'.tsx': 'code',
|
|
27
|
+
'.py': 'code',
|
|
28
|
+
'.rb': 'code',
|
|
29
|
+
'.java': 'code',
|
|
30
|
+
'.c': 'code',
|
|
31
|
+
'.cpp': 'code',
|
|
32
|
+
'.cc': 'code',
|
|
33
|
+
'.cxx': 'code',
|
|
34
|
+
'.h': 'code',
|
|
35
|
+
'.hpp': 'code',
|
|
36
|
+
'.cs': 'code',
|
|
37
|
+
'.go': 'code',
|
|
38
|
+
'.rs': 'code',
|
|
39
|
+
'.swift': 'code',
|
|
40
|
+
'.kt': 'code',
|
|
41
|
+
'.kts': 'code',
|
|
42
|
+
'.scala': 'code',
|
|
43
|
+
'.php': 'code',
|
|
44
|
+
'.sh': 'code',
|
|
45
|
+
'.bash': 'code',
|
|
46
|
+
'.zsh': 'code',
|
|
47
|
+
'.fish': 'code',
|
|
48
|
+
'.ps1': 'code',
|
|
49
|
+
'.sql': 'code',
|
|
50
|
+
'.json': 'code',
|
|
51
|
+
'.yaml': 'code',
|
|
52
|
+
'.yml': 'code',
|
|
53
|
+
'.xml': 'code',
|
|
54
|
+
'.css': 'code',
|
|
55
|
+
'.scss': 'code',
|
|
56
|
+
'.sass': 'code',
|
|
57
|
+
'.less': 'code',
|
|
58
|
+
'.vue': 'code',
|
|
59
|
+
'.svelte': 'code',
|
|
60
|
+
'.r': 'code',
|
|
61
|
+
'.lua': 'code',
|
|
62
|
+
'.perl': 'code',
|
|
63
|
+
'.pl': 'code',
|
|
64
|
+
'.ex': 'code',
|
|
65
|
+
'.exs': 'code',
|
|
66
|
+
'.erl': 'code',
|
|
67
|
+
'.hs': 'code',
|
|
68
|
+
'.ml': 'code',
|
|
69
|
+
'.fs': 'code',
|
|
70
|
+
'.fsx': 'code',
|
|
71
|
+
'.clj': 'code',
|
|
72
|
+
'.cljs': 'code',
|
|
73
|
+
'.dart': 'code',
|
|
74
|
+
'.zig': 'code',
|
|
75
|
+
'.nim': 'code',
|
|
76
|
+
'.v': 'code',
|
|
77
|
+
'.toml': 'code',
|
|
78
|
+
'.ini': 'code',
|
|
79
|
+
'.conf': 'code',
|
|
80
|
+
'.graphql': 'code',
|
|
81
|
+
'.gql': 'code',
|
|
82
|
+
'.proto': 'code',
|
|
83
|
+
'.tf': 'code',
|
|
84
|
+
'.hcl': 'code',
|
|
85
|
+
'.asm': 'code',
|
|
86
|
+
'.s': 'code',
|
|
87
|
+
'.diff': 'code',
|
|
88
|
+
'.patch': 'code',
|
|
89
|
+
'.mdx': 'code',
|
|
90
|
+
'.astro': 'code',
|
|
91
|
+
// URL file extension
|
|
92
|
+
'.url': 'url',
|
|
93
|
+
// Mermaid diagram extension
|
|
94
|
+
'.mmd': 'mmd',
|
|
95
|
+
'.mermaid': 'mmd',
|
|
96
|
+
// GeoJSON extension
|
|
97
|
+
'.geojson': 'geojson',
|
|
98
|
+
// GPX extension
|
|
99
|
+
'.gpx': 'gpx',
|
|
12
100
|
};
|
|
13
101
|
if (ext in extensionMap) {
|
|
14
102
|
return extensionMap[ext];
|
package/dist/detector.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detector.js","sourceRoot":"","sources":["../src/detector.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAG/B,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAgB;IACjD,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IAE5C,kCAAkC;IAClC,MAAM,YAAY,GAA+B;QAC/C,MAAM,EAAE,KAAK;QACb,OAAO,EAAE,MAAM;QACf,OAAO,EAAE,MAAM;QACf,OAAO,EAAE,MAAM;QACf,OAAO,EAAE,MAAM;
|
|
1
|
+
{"version":3,"file":"detector.js","sourceRoot":"","sources":["../src/detector.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,5 +1,4 @@
|
|
|
1
1
|
import { chromium } from 'playwright';
|
|
2
|
-
import { readFile } from 'fs/promises';
|
|
3
2
|
import { resolve, dirname } from 'path';
|
|
4
3
|
import { fileURLToPath } from 'url';
|
|
5
4
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -12,6 +11,17 @@ export class Renderer {
|
|
|
12
11
|
docx: resolve(__dirname, '../templates/docx.html'),
|
|
13
12
|
xlsx: resolve(__dirname, '../templates/xlsx.html'),
|
|
14
13
|
pptx: resolve(__dirname, '../templates/pptx.html'),
|
|
14
|
+
md: resolve(__dirname, '../templates/md.html'),
|
|
15
|
+
html: resolve(__dirname, '../templates/html.html'),
|
|
16
|
+
csv: resolve(__dirname, '../templates/csv.html'),
|
|
17
|
+
rtf: resolve(__dirname, '../templates/rtf.html'),
|
|
18
|
+
ipynb: resolve(__dirname, '../templates/ipynb.html'),
|
|
19
|
+
tex: resolve(__dirname, '../templates/tex.html'),
|
|
20
|
+
code: resolve(__dirname, '../templates/code.html'),
|
|
21
|
+
url: resolve(__dirname, '../templates/url.html'),
|
|
22
|
+
mmd: resolve(__dirname, '../templates/mmd.html'),
|
|
23
|
+
geojson: resolve(__dirname, '../templates/geojson.html'),
|
|
24
|
+
gpx: resolve(__dirname, '../templates/gpx.html'),
|
|
15
25
|
unknown: '',
|
|
16
26
|
};
|
|
17
27
|
const path = templateMap[format];
|
|
@@ -20,21 +30,21 @@ export class Renderer {
|
|
|
20
30
|
}
|
|
21
31
|
return path;
|
|
22
32
|
}
|
|
23
|
-
async injectDataIntoPage(page, fileBase64, pageNumber = 1) {
|
|
33
|
+
async injectDataIntoPage(page, fileBase64, pageNumber = 1, fileName = '') {
|
|
24
34
|
// Inject data into page globals before template loads
|
|
25
|
-
await page.addInitScript(({ fileBase64: fb64, pageNum }) => {
|
|
35
|
+
await page.addInitScript(({ fileBase64: fb64, pageNum, fName }) => {
|
|
26
36
|
// Override the placeholder values
|
|
27
37
|
globalThis.fileBase64 = fb64;
|
|
28
38
|
globalThis.pageNumber = pageNum;
|
|
29
|
-
|
|
39
|
+
globalThis.fileName = fName;
|
|
40
|
+
}, { fileBase64, pageNum: pageNumber, fName: fileName });
|
|
30
41
|
}
|
|
31
|
-
async render(
|
|
32
|
-
const {
|
|
42
|
+
async render(input, format, options = {}) {
|
|
43
|
+
const { format: imageFormat = 'png', width, height, page: pageNumber = 1, fileName = '', } = options;
|
|
33
44
|
// Use small initial viewport - content will determine final size
|
|
34
45
|
// For formats like XLSX, large viewport causes table to expand to fill it
|
|
35
46
|
const initialWidth = width || 800;
|
|
36
47
|
const initialHeight = height || 600;
|
|
37
|
-
const outputPath = output || inputPath.replace(/\.[^.]+$/, `.${imageFormat}`);
|
|
38
48
|
// Launch browser
|
|
39
49
|
const browser = await chromium.launch({
|
|
40
50
|
headless: true,
|
|
@@ -46,15 +56,46 @@ export class Renderer {
|
|
|
46
56
|
viewport: { width: initialWidth, height: initialHeight },
|
|
47
57
|
deviceScaleFactor,
|
|
48
58
|
});
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
59
|
+
// Special handling for URL format - navigate directly to the URL
|
|
60
|
+
if (format === 'url') {
|
|
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();
|
|
65
|
+
// Set a reasonable viewport for webpage screenshots
|
|
66
|
+
const webWidth = width || 1280;
|
|
67
|
+
const webHeight = height || 800;
|
|
68
|
+
await page.setViewportSize({ width: webWidth, height: webHeight });
|
|
69
|
+
// Navigate to URL and wait for network idle
|
|
70
|
+
await page.goto(url, { waitUntil: 'networkidle' });
|
|
71
|
+
// Take screenshot to buffer
|
|
72
|
+
const screenshotData = await page.screenshot({
|
|
73
|
+
type: imageFormat,
|
|
74
|
+
fullPage: false,
|
|
75
|
+
});
|
|
76
|
+
await browser.close();
|
|
77
|
+
return {
|
|
78
|
+
data: screenshotData,
|
|
79
|
+
format: imageFormat,
|
|
80
|
+
width: webWidth * deviceScaleFactor,
|
|
81
|
+
height: webHeight * deviceScaleFactor,
|
|
82
|
+
renderer: format,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
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
|
+
}
|
|
52
94
|
// Inject data before loading template
|
|
53
|
-
await this.injectDataIntoPage(page, fileBase64, pageNumber);
|
|
95
|
+
await this.injectDataIntoPage(page, fileBase64, pageNumber, fileName);
|
|
54
96
|
// Load template
|
|
55
97
|
const templatePath = this.getTemplatePath(format);
|
|
56
98
|
await page.goto(`file://${templatePath}`);
|
|
57
|
-
// Wait for render complete and get metadata
|
|
58
99
|
const metadata = await page.evaluate(async () => {
|
|
59
100
|
const renderComplete = globalThis.renderComplete;
|
|
60
101
|
if (!renderComplete) {
|
|
@@ -66,6 +107,7 @@ export class Renderer {
|
|
|
66
107
|
// Check if we need to clip (for EPUB content cropping)
|
|
67
108
|
const clipX = metadata.clipX;
|
|
68
109
|
const clipY = metadata.clipY;
|
|
110
|
+
let screenshotData;
|
|
69
111
|
if (clipX !== undefined && clipY !== undefined) {
|
|
70
112
|
// Resize viewport to ensure clip area is fully visible
|
|
71
113
|
const requiredWidth = clipX + metadata.width;
|
|
@@ -77,8 +119,7 @@ export class Renderer {
|
|
|
77
119
|
// Wait for layout to stabilize
|
|
78
120
|
await page.waitForTimeout(100);
|
|
79
121
|
// Use clip to capture just the content area
|
|
80
|
-
await page.screenshot({
|
|
81
|
-
path: outputPath,
|
|
122
|
+
screenshotData = await page.screenshot({
|
|
82
123
|
type: imageFormat,
|
|
83
124
|
clip: {
|
|
84
125
|
x: clipX,
|
|
@@ -97,8 +138,7 @@ export class Renderer {
|
|
|
97
138
|
// Wait for layout to stabilize after viewport resize
|
|
98
139
|
await page.waitForTimeout(100);
|
|
99
140
|
// Take screenshot at exact rendered size
|
|
100
|
-
await page.screenshot({
|
|
101
|
-
path: outputPath,
|
|
141
|
+
screenshotData = await page.screenshot({
|
|
102
142
|
type: imageFormat,
|
|
103
143
|
fullPage: false,
|
|
104
144
|
});
|
|
@@ -108,10 +148,11 @@ export class Renderer {
|
|
|
108
148
|
const actualWidth = metadata.width * deviceScaleFactor;
|
|
109
149
|
const actualHeight = metadata.height * deviceScaleFactor;
|
|
110
150
|
return {
|
|
111
|
-
|
|
151
|
+
data: screenshotData,
|
|
112
152
|
format: imageFormat,
|
|
113
153
|
width: actualWidth,
|
|
114
154
|
height: actualHeight,
|
|
155
|
+
renderer: format,
|
|
115
156
|
};
|
|
116
157
|
}
|
|
117
158
|
catch (error) {
|
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,15 +1,16 @@
|
|
|
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;
|
|
13
|
+
renderer: string;
|
|
13
14
|
}
|
|
14
15
|
export interface RenderMetadata {
|
|
15
16
|
width: number;
|
|
@@ -18,5 +19,11 @@ export interface RenderMetadata {
|
|
|
18
19
|
pageNumber: number;
|
|
19
20
|
scale: number;
|
|
20
21
|
}
|
|
21
|
-
export type FileFormat = 'pdf' | 'epub' | 'docx' | 'xlsx' | 'pptx' | 'unknown';
|
|
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;
|
|
22
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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "screenitshot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Convert various file formats to high-quality screenshots",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"type": "module",
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc && npm run copy-templates",
|
|
13
|
-
"copy-templates": "mkdir -p templates && cp ../render/dist
|
|
13
|
+
"copy-templates": "mkdir -p templates && cp ../render/dist/*.html templates/",
|
|
14
14
|
"dev": "tsc --watch",
|
|
15
15
|
"start": "npm run build && node dist/cli.js",
|
|
16
16
|
"lint": "eslint \"src/**/*.ts\"",
|
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,38 +50,58 @@ 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')
|
|
27
56
|
.option('-p, --page <page>', 'Page number for multi-page documents', '1')
|
|
28
|
-
.action(async (input: string,
|
|
57
|
+
.action(async (input: string, options: { format: 'png' | 'jpeg' | 'webp'; width?: string; height?: string; page: string }) => {
|
|
29
58
|
try {
|
|
59
|
+
// Check input file exists and detect format
|
|
60
|
+
let inputData: Buffer;
|
|
61
|
+
try {
|
|
62
|
+
inputData = await readFile(input);
|
|
63
|
+
} catch {
|
|
64
|
+
console.error(`Error: Input file not found: ${input}`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const inputFormat = await detectFormat(input);
|
|
69
|
+
if (inputFormat === 'unknown') {
|
|
70
|
+
console.error(`Error: Unsupported file format: ${input}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Determine output path (same folder as input, with unique name)
|
|
75
|
+
const dir = dirname(input);
|
|
76
|
+
const stem = basename(input, extname(input));
|
|
77
|
+
const baseOutput = join(dir, `${stem}.${options.format}`);
|
|
78
|
+
const outputPath = await getUniqueOutputPath(baseOutput);
|
|
79
|
+
|
|
30
80
|
console.log(`Converting ${input}...`);
|
|
31
81
|
|
|
32
|
-
const result = await screenshot(
|
|
33
|
-
output,
|
|
82
|
+
const result = await screenshot(inputData, inputFormat, {
|
|
34
83
|
format: options.format,
|
|
35
|
-
width: parseInt(options.width),
|
|
36
|
-
height: parseInt(options.height),
|
|
84
|
+
width: options.width ? parseInt(options.width) : undefined,
|
|
85
|
+
height: options.height ? parseInt(options.height) : undefined,
|
|
37
86
|
page: parseInt(options.page),
|
|
87
|
+
fileName: basename(input),
|
|
38
88
|
});
|
|
39
89
|
|
|
40
|
-
|
|
90
|
+
// Write output to file
|
|
91
|
+
await writeFile(outputPath, result.data);
|
|
92
|
+
|
|
93
|
+
console.log(`✓ Screenshot saved to ${outputPath}`);
|
|
94
|
+
console.log(` Renderer: ${result.renderer}`);
|
|
41
95
|
console.log(` Format: ${result.format}`);
|
|
42
96
|
console.log(` Size: ${result.width}x${result.height}`);
|
|
43
97
|
} catch (error) {
|
|
44
98
|
const err = error as Error;
|
|
45
99
|
|
|
46
100
|
// User-friendly error messages
|
|
47
|
-
if (err.message.includes('
|
|
48
|
-
console.error(`Error: Input file not found: ${input}`);
|
|
49
|
-
} else if (err.message.includes('Unsupported file format')) {
|
|
101
|
+
if (err.message.includes('Unknown format')) {
|
|
50
102
|
console.error(`Error: ${err.message}`);
|
|
51
|
-
console.error('Supported formats: PDF');
|
|
52
103
|
} else if (err.message.includes('No template available')) {
|
|
53
104
|
console.error(`Error: Format not yet supported`);
|
|
54
|
-
console.error('Currently supported: PDF');
|
|
55
105
|
} else if (err.message.includes('page')) {
|
|
56
106
|
console.error(`Error: Invalid page number or page not found`);
|
|
57
107
|
// Show stack trace in debug mode
|
package/src/detector.ts
CHANGED
|
@@ -12,6 +12,94 @@ export async function detectFormat(filePath: string): Promise<FileFormat> {
|
|
|
12
12
|
'.docx': 'docx',
|
|
13
13
|
'.xlsx': 'xlsx',
|
|
14
14
|
'.pptx': 'pptx',
|
|
15
|
+
'.md': 'md',
|
|
16
|
+
'.markdown': 'md',
|
|
17
|
+
'.html': 'html',
|
|
18
|
+
'.htm': 'html',
|
|
19
|
+
'.csv': 'csv',
|
|
20
|
+
'.tsv': 'csv',
|
|
21
|
+
'.rtf': 'rtf',
|
|
22
|
+
'.ipynb': 'ipynb',
|
|
23
|
+
'.tex': 'tex',
|
|
24
|
+
'.latex': 'tex',
|
|
25
|
+
// Source code extensions
|
|
26
|
+
'.js': 'code',
|
|
27
|
+
'.jsx': 'code',
|
|
28
|
+
'.ts': 'code',
|
|
29
|
+
'.tsx': 'code',
|
|
30
|
+
'.py': 'code',
|
|
31
|
+
'.rb': 'code',
|
|
32
|
+
'.java': 'code',
|
|
33
|
+
'.c': 'code',
|
|
34
|
+
'.cpp': 'code',
|
|
35
|
+
'.cc': 'code',
|
|
36
|
+
'.cxx': 'code',
|
|
37
|
+
'.h': 'code',
|
|
38
|
+
'.hpp': 'code',
|
|
39
|
+
'.cs': 'code',
|
|
40
|
+
'.go': 'code',
|
|
41
|
+
'.rs': 'code',
|
|
42
|
+
'.swift': 'code',
|
|
43
|
+
'.kt': 'code',
|
|
44
|
+
'.kts': 'code',
|
|
45
|
+
'.scala': 'code',
|
|
46
|
+
'.php': 'code',
|
|
47
|
+
'.sh': 'code',
|
|
48
|
+
'.bash': 'code',
|
|
49
|
+
'.zsh': 'code',
|
|
50
|
+
'.fish': 'code',
|
|
51
|
+
'.ps1': 'code',
|
|
52
|
+
'.sql': 'code',
|
|
53
|
+
'.json': 'code',
|
|
54
|
+
'.yaml': 'code',
|
|
55
|
+
'.yml': 'code',
|
|
56
|
+
'.xml': 'code',
|
|
57
|
+
'.css': 'code',
|
|
58
|
+
'.scss': 'code',
|
|
59
|
+
'.sass': 'code',
|
|
60
|
+
'.less': 'code',
|
|
61
|
+
'.vue': 'code',
|
|
62
|
+
'.svelte': 'code',
|
|
63
|
+
'.r': 'code',
|
|
64
|
+
'.lua': 'code',
|
|
65
|
+
'.perl': 'code',
|
|
66
|
+
'.pl': 'code',
|
|
67
|
+
'.ex': 'code',
|
|
68
|
+
'.exs': 'code',
|
|
69
|
+
'.erl': 'code',
|
|
70
|
+
'.hs': 'code',
|
|
71
|
+
'.ml': 'code',
|
|
72
|
+
'.fs': 'code',
|
|
73
|
+
'.fsx': 'code',
|
|
74
|
+
'.clj': 'code',
|
|
75
|
+
'.cljs': 'code',
|
|
76
|
+
'.dart': 'code',
|
|
77
|
+
'.zig': 'code',
|
|
78
|
+
'.nim': 'code',
|
|
79
|
+
'.v': 'code',
|
|
80
|
+
'.toml': 'code',
|
|
81
|
+
'.ini': 'code',
|
|
82
|
+
'.conf': 'code',
|
|
83
|
+
'.graphql': 'code',
|
|
84
|
+
'.gql': 'code',
|
|
85
|
+
'.proto': 'code',
|
|
86
|
+
'.tf': 'code',
|
|
87
|
+
'.hcl': 'code',
|
|
88
|
+
'.asm': 'code',
|
|
89
|
+
'.s': 'code',
|
|
90
|
+
'.diff': 'code',
|
|
91
|
+
'.patch': 'code',
|
|
92
|
+
'.mdx': 'code',
|
|
93
|
+
'.astro': 'code',
|
|
94
|
+
// URL file extension
|
|
95
|
+
'.url': 'url',
|
|
96
|
+
// Mermaid diagram extension
|
|
97
|
+
'.mmd': 'mmd',
|
|
98
|
+
'.mermaid': 'mmd',
|
|
99
|
+
// GeoJSON extension
|
|
100
|
+
'.geojson': 'geojson',
|
|
101
|
+
// GPX extension
|
|
102
|
+
'.gpx': 'gpx',
|
|
15
103
|
};
|
|
16
104
|
|
|
17
105
|
if (ext in extensionMap) {
|
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,5 +1,4 @@
|
|
|
1
1
|
import { chromium, type Page } from 'playwright';
|
|
2
|
-
import { readFile } from 'fs/promises';
|
|
3
2
|
import { resolve, dirname } from 'path';
|
|
4
3
|
import { fileURLToPath } from 'url';
|
|
5
4
|
import type { FileFormat, ScreenshotOptions, ScreenshotResult } from './types.js';
|
|
@@ -15,6 +14,17 @@ export class Renderer {
|
|
|
15
14
|
docx: resolve(__dirname, '../templates/docx.html'),
|
|
16
15
|
xlsx: resolve(__dirname, '../templates/xlsx.html'),
|
|
17
16
|
pptx: resolve(__dirname, '../templates/pptx.html'),
|
|
17
|
+
md: resolve(__dirname, '../templates/md.html'),
|
|
18
|
+
html: resolve(__dirname, '../templates/html.html'),
|
|
19
|
+
csv: resolve(__dirname, '../templates/csv.html'),
|
|
20
|
+
rtf: resolve(__dirname, '../templates/rtf.html'),
|
|
21
|
+
ipynb: resolve(__dirname, '../templates/ipynb.html'),
|
|
22
|
+
tex: resolve(__dirname, '../templates/tex.html'),
|
|
23
|
+
code: resolve(__dirname, '../templates/code.html'),
|
|
24
|
+
url: resolve(__dirname, '../templates/url.html'),
|
|
25
|
+
mmd: resolve(__dirname, '../templates/mmd.html'),
|
|
26
|
+
geojson: resolve(__dirname, '../templates/geojson.html'),
|
|
27
|
+
gpx: resolve(__dirname, '../templates/gpx.html'),
|
|
18
28
|
unknown: '',
|
|
19
29
|
};
|
|
20
30
|
|
|
@@ -28,27 +38,29 @@ export class Renderer {
|
|
|
28
38
|
private async injectDataIntoPage(
|
|
29
39
|
page: Page,
|
|
30
40
|
fileBase64: string,
|
|
31
|
-
pageNumber: number = 1
|
|
41
|
+
pageNumber: number = 1,
|
|
42
|
+
fileName: string = ''
|
|
32
43
|
): Promise<void> {
|
|
33
44
|
// Inject data into page globals before template loads
|
|
34
|
-
await page.addInitScript(({ fileBase64: fb64, pageNum }: { fileBase64: string; pageNum: number }) => {
|
|
45
|
+
await page.addInitScript(({ fileBase64: fb64, pageNum, fName }: { fileBase64: string; pageNum: number; fName: string }) => {
|
|
35
46
|
// Override the placeholder values
|
|
36
|
-
(globalThis as
|
|
37
|
-
(globalThis as
|
|
38
|
-
|
|
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;
|
|
50
|
+
}, { fileBase64, pageNum: pageNumber, fName: fileName });
|
|
39
51
|
}
|
|
40
52
|
|
|
41
53
|
async render(
|
|
42
|
-
|
|
54
|
+
input: Buffer | string,
|
|
43
55
|
format: FileFormat,
|
|
44
56
|
options: ScreenshotOptions = {}
|
|
45
57
|
): Promise<ScreenshotResult> {
|
|
46
58
|
const {
|
|
47
|
-
output,
|
|
48
59
|
format: imageFormat = 'png',
|
|
49
60
|
width,
|
|
50
61
|
height,
|
|
51
62
|
page: pageNumber = 1,
|
|
63
|
+
fileName = '',
|
|
52
64
|
} = options;
|
|
53
65
|
|
|
54
66
|
// Use small initial viewport - content will determine final size
|
|
@@ -56,8 +68,6 @@ export class Renderer {
|
|
|
56
68
|
const initialWidth = width || 800;
|
|
57
69
|
const initialHeight = height || 600;
|
|
58
70
|
|
|
59
|
-
const outputPath = output || inputPath.replace(/\.[^.]+$/, `.${imageFormat}`);
|
|
60
|
-
|
|
61
71
|
// Launch browser
|
|
62
72
|
const browser = await chromium.launch({
|
|
63
73
|
headless: true,
|
|
@@ -72,20 +82,63 @@ export class Renderer {
|
|
|
72
82
|
deviceScaleFactor,
|
|
73
83
|
});
|
|
74
84
|
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
85
|
+
// Special handling for URL format - navigate directly to the URL
|
|
86
|
+
if (format === 'url') {
|
|
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();
|
|
91
|
+
|
|
92
|
+
// Set a reasonable viewport for webpage screenshots
|
|
93
|
+
const webWidth = width || 1280;
|
|
94
|
+
const webHeight = height || 800;
|
|
95
|
+
await page.setViewportSize({ width: webWidth, height: webHeight });
|
|
96
|
+
|
|
97
|
+
// Navigate to URL and wait for network idle
|
|
98
|
+
await page.goto(url, { waitUntil: 'networkidle' });
|
|
99
|
+
|
|
100
|
+
// Take screenshot to buffer
|
|
101
|
+
const screenshotData = await page.screenshot({
|
|
102
|
+
type: imageFormat as 'png' | 'jpeg',
|
|
103
|
+
fullPage: false,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
await browser.close();
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
data: screenshotData,
|
|
110
|
+
format: imageFormat,
|
|
111
|
+
width: webWidth * deviceScaleFactor,
|
|
112
|
+
height: webHeight * deviceScaleFactor,
|
|
113
|
+
renderer: format,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
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
|
+
}
|
|
78
125
|
|
|
79
126
|
// Inject data before loading template
|
|
80
|
-
await this.injectDataIntoPage(page, fileBase64, pageNumber);
|
|
127
|
+
await this.injectDataIntoPage(page, fileBase64, pageNumber, fileName);
|
|
81
128
|
|
|
82
129
|
// Load template
|
|
83
130
|
const templatePath = this.getTemplatePath(format);
|
|
84
131
|
await page.goto(`file://${templatePath}`);
|
|
85
132
|
|
|
86
133
|
// Wait for render complete and get metadata
|
|
134
|
+
interface PageMetadata {
|
|
135
|
+
width: number;
|
|
136
|
+
height: number;
|
|
137
|
+
clipX?: number;
|
|
138
|
+
clipY?: number;
|
|
139
|
+
}
|
|
87
140
|
const metadata = await page.evaluate(async () => {
|
|
88
|
-
const renderComplete = (globalThis as
|
|
141
|
+
const renderComplete = (globalThis as unknown as Record<string, unknown>).renderComplete as Promise<PageMetadata> | undefined;
|
|
89
142
|
|
|
90
143
|
if (!renderComplete) {
|
|
91
144
|
throw new Error('window.renderComplete not found');
|
|
@@ -96,8 +149,10 @@ export class Renderer {
|
|
|
96
149
|
});
|
|
97
150
|
|
|
98
151
|
// Check if we need to clip (for EPUB content cropping)
|
|
99
|
-
const clipX =
|
|
100
|
-
const clipY =
|
|
152
|
+
const clipX = metadata.clipX;
|
|
153
|
+
const clipY = metadata.clipY;
|
|
154
|
+
|
|
155
|
+
let screenshotData: Buffer;
|
|
101
156
|
|
|
102
157
|
if (clipX !== undefined && clipY !== undefined) {
|
|
103
158
|
// Resize viewport to ensure clip area is fully visible
|
|
@@ -112,8 +167,7 @@ export class Renderer {
|
|
|
112
167
|
await page.waitForTimeout(100);
|
|
113
168
|
|
|
114
169
|
// Use clip to capture just the content area
|
|
115
|
-
await page.screenshot({
|
|
116
|
-
path: outputPath,
|
|
170
|
+
screenshotData = await page.screenshot({
|
|
117
171
|
type: imageFormat as 'png' | 'jpeg',
|
|
118
172
|
clip: {
|
|
119
173
|
x: clipX,
|
|
@@ -133,8 +187,7 @@ export class Renderer {
|
|
|
133
187
|
await page.waitForTimeout(100);
|
|
134
188
|
|
|
135
189
|
// Take screenshot at exact rendered size
|
|
136
|
-
await page.screenshot({
|
|
137
|
-
path: outputPath,
|
|
190
|
+
screenshotData = await page.screenshot({
|
|
138
191
|
type: imageFormat as 'png' | 'jpeg',
|
|
139
192
|
fullPage: false,
|
|
140
193
|
});
|
|
@@ -147,10 +200,11 @@ export class Renderer {
|
|
|
147
200
|
const actualHeight = metadata.height * deviceScaleFactor;
|
|
148
201
|
|
|
149
202
|
return {
|
|
150
|
-
|
|
203
|
+
data: screenshotData,
|
|
151
204
|
format: imageFormat,
|
|
152
205
|
width: actualWidth,
|
|
153
206
|
height: actualHeight,
|
|
207
|
+
renderer: format,
|
|
154
208
|
};
|
|
155
209
|
} catch (error) {
|
|
156
210
|
await browser.close();
|
package/src/types.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
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;
|
|
14
|
+
renderer: string;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export interface RenderMetadata {
|
|
@@ -21,4 +22,66 @@ export interface RenderMetadata {
|
|
|
21
22
|
scale: number;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
export type FileFormat = 'pdf' | 'epub' | 'docx' | 'xlsx' | 'pptx' | 'unknown';
|
|
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
|
+
}
|