@bobfrankston/brother-label 1.0.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/cli.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Brother Label Printer CLI
4
+ * Thin wrapper around the API
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=cli.d.ts.map
package/cli.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["cli.ts"],"names":[],"mappings":";AACA;;;GAGG"}
package/cli.js ADDED
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Brother Label Printer CLI
4
+ * Thin wrapper around the API
5
+ */
6
+ import * as fs from "fs";
7
+ import * as path from "path";
8
+ import { program } from "commander";
9
+ import { print, render, getConfig, setConfig, getConfigPath, listPrinters, } from "./api.js";
10
+ const VALID_TAPES = [6, 9, 12, 18, 24];
11
+ function parseTape(value) {
12
+ const num = parseInt(value.replace("mm", ""), 10);
13
+ if (!VALID_TAPES.includes(num)) {
14
+ throw new Error(`Invalid tape size: ${value}. Valid: 6, 9, 12, 18, 24`);
15
+ }
16
+ return num;
17
+ }
18
+ // Detect content type from input string
19
+ function buildPrintOptions(input, opts) {
20
+ const options = {};
21
+ // Parse tape option
22
+ if (opts.tape) {
23
+ options.tape = parseTape(opts.tape);
24
+ }
25
+ if (opts.printer) {
26
+ options.printer = opts.printer;
27
+ }
28
+ if (opts.aspect) {
29
+ options.aspect = opts.aspect;
30
+ }
31
+ // Explicit type flags override auto-detection
32
+ if (opts.text) {
33
+ options.text = input;
34
+ return options;
35
+ }
36
+ if (opts.html) {
37
+ options.htmlPath = path.resolve(input);
38
+ return options;
39
+ }
40
+ if (opts.image) {
41
+ options.imagePath = path.resolve(input);
42
+ return options;
43
+ }
44
+ // Auto-detect content type
45
+ const lower = input.toLowerCase();
46
+ if (lower.endsWith(".html") || lower.endsWith(".htm")) {
47
+ options.htmlPath = path.resolve(input);
48
+ }
49
+ else if (lower.endsWith(".txt")) {
50
+ options.textFile = path.resolve(input);
51
+ }
52
+ else if (lower.endsWith(".png") || lower.endsWith(".jpg") || lower.endsWith(".jpeg") || lower.endsWith(".bmp") || lower.endsWith(".gif")) {
53
+ options.imagePath = path.resolve(input);
54
+ }
55
+ else if (fs.existsSync(input)) {
56
+ // Exists but unknown extension - try to detect
57
+ const ext = path.extname(lower);
58
+ if (ext === ".html" || ext === ".htm") {
59
+ options.htmlPath = path.resolve(input);
60
+ }
61
+ else {
62
+ // Assume text file
63
+ options.textFile = path.resolve(input);
64
+ }
65
+ }
66
+ else {
67
+ // Treat as literal text
68
+ options.text = input;
69
+ }
70
+ return options;
71
+ }
72
+ program
73
+ .name("brother-print")
74
+ .description("Print labels on Brother P-touch printers")
75
+ .version("1.0.0");
76
+ // Default command - print
77
+ program
78
+ .argument("[input]", "Text to print, or path to file (auto-detects type)")
79
+ .option("-t, --tape <size>", "Tape size: 6, 9, 12, 18, 24 (mm)")
80
+ .option("-p, --printer <name>", "Printer name")
81
+ .option("-o, --output <file>", "Save to file instead of printing (png, jpg, bmp)")
82
+ .option("-a, --aspect <ratio>", "Aspect ratio width:height for HTML (e.g., 3.5:2)")
83
+ .option("--text", "Force input as literal text")
84
+ .option("--html", "Force input as HTML file path")
85
+ .option("--image", "Force input as image file path")
86
+ .action(async (input, opts) => {
87
+ if (!input) {
88
+ program.help();
89
+ return;
90
+ }
91
+ try {
92
+ const options = buildPrintOptions(input, opts);
93
+ if (opts.output) {
94
+ // Render only, save to file
95
+ const buffer = await render(options);
96
+ fs.writeFileSync(opts.output, buffer);
97
+ console.log(`Saved to ${opts.output}`);
98
+ }
99
+ else {
100
+ // Print
101
+ const result = await print(options);
102
+ const tape = options.tape ?? getConfig().defaultTape ?? 24;
103
+ console.log(`Printed on ${tape}mm tape`);
104
+ }
105
+ }
106
+ catch (err) {
107
+ console.error(`Error: ${err.message}`);
108
+ process.exit(1);
109
+ }
110
+ });
111
+ // List printers
112
+ program
113
+ .command("list")
114
+ .description("List available Brother printers")
115
+ .action(async () => {
116
+ try {
117
+ const printers = await listPrinters();
118
+ if (printers.length === 0) {
119
+ console.log("No Brother printers found");
120
+ }
121
+ else {
122
+ console.log("Brother printers:");
123
+ printers.forEach(p => console.log(` ${p.name}`));
124
+ }
125
+ }
126
+ catch (err) {
127
+ console.error(`Error: ${err.message}`);
128
+ process.exit(1);
129
+ }
130
+ });
131
+ // Config
132
+ program
133
+ .command("config")
134
+ .description("Show or set configuration")
135
+ .option("-t, --tape <size>", "Set default tape size: 6, 9, 12, 18, 24 (mm)")
136
+ .option("-p, --printer <name>", "Set default printer name")
137
+ .action((opts) => {
138
+ try {
139
+ if (!opts.tape && !opts.printer) {
140
+ // Show config
141
+ const config = getConfig();
142
+ console.log("Configuration:");
143
+ console.log(` File: ${getConfigPath()}`);
144
+ console.log(` Default tape: ${config.defaultTape ? config.defaultTape + "mm" : "(not set)"}`);
145
+ console.log(` Default printer: ${config.defaultPrinter ?? "(not set)"}`);
146
+ console.log("\nValid tape sizes: 6, 9, 12, 18, 24");
147
+ return;
148
+ }
149
+ if (opts.tape) {
150
+ const tape = parseTape(opts.tape);
151
+ setConfig({ defaultTape: tape });
152
+ console.log(`Default tape set to: ${tape}mm`);
153
+ }
154
+ if (opts.printer) {
155
+ setConfig({ defaultPrinter: opts.printer });
156
+ console.log(`Default printer set to: ${opts.printer}`);
157
+ }
158
+ }
159
+ catch (err) {
160
+ console.error(`Error: ${err.message}`);
161
+ process.exit(1);
162
+ }
163
+ });
164
+ program.parse();
165
+ //# sourceMappingURL=cli.js.map
package/cli.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["cli.ts"],"names":[],"mappings":"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"}
package/cli.ts ADDED
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Brother Label Printer CLI
4
+ * Thin wrapper around the API
5
+ */
6
+
7
+ import * as fs from "fs";
8
+ import * as path from "path";
9
+ import { program } from "commander";
10
+ import {
11
+ print,
12
+ render,
13
+ getConfig,
14
+ setConfig,
15
+ getConfigPath,
16
+ listPrinters,
17
+ TapeSize,
18
+ PrintOptions,
19
+ } from "./api.js";
20
+
21
+ const VALID_TAPES: TapeSize[] = [6, 9, 12, 18, 24];
22
+
23
+ function parseTape(value: string): TapeSize {
24
+ const num = parseInt(value.replace("mm", ""), 10) as TapeSize;
25
+ if (!VALID_TAPES.includes(num)) {
26
+ throw new Error(`Invalid tape size: ${value}. Valid: 6, 9, 12, 18, 24`);
27
+ }
28
+ return num;
29
+ }
30
+
31
+ // Detect content type from input string
32
+ function buildPrintOptions(input: string, opts: { tape?: string; printer?: string; text?: boolean; html?: boolean; image?: boolean; aspect?: string }): PrintOptions {
33
+ const options: PrintOptions = {};
34
+
35
+ // Parse tape option
36
+ if (opts.tape) {
37
+ options.tape = parseTape(opts.tape);
38
+ }
39
+ if (opts.printer) {
40
+ options.printer = opts.printer;
41
+ }
42
+ if (opts.aspect) {
43
+ options.aspect = opts.aspect;
44
+ }
45
+
46
+ // Explicit type flags override auto-detection
47
+ if (opts.text) {
48
+ options.text = input;
49
+ return options;
50
+ }
51
+ if (opts.html) {
52
+ options.htmlPath = path.resolve(input);
53
+ return options;
54
+ }
55
+ if (opts.image) {
56
+ options.imagePath = path.resolve(input);
57
+ return options;
58
+ }
59
+
60
+ // Auto-detect content type
61
+ const lower = input.toLowerCase();
62
+ if (lower.endsWith(".html") || lower.endsWith(".htm")) {
63
+ options.htmlPath = path.resolve(input);
64
+ } else if (lower.endsWith(".txt")) {
65
+ options.textFile = path.resolve(input);
66
+ } else if (lower.endsWith(".png") || lower.endsWith(".jpg") || lower.endsWith(".jpeg") || lower.endsWith(".bmp") || lower.endsWith(".gif")) {
67
+ options.imagePath = path.resolve(input);
68
+ } else if (fs.existsSync(input)) {
69
+ // Exists but unknown extension - try to detect
70
+ const ext = path.extname(lower);
71
+ if (ext === ".html" || ext === ".htm") {
72
+ options.htmlPath = path.resolve(input);
73
+ } else {
74
+ // Assume text file
75
+ options.textFile = path.resolve(input);
76
+ }
77
+ } else {
78
+ // Treat as literal text
79
+ options.text = input;
80
+ }
81
+
82
+ return options;
83
+ }
84
+
85
+ program
86
+ .name("brother-print")
87
+ .description("Print labels on Brother P-touch printers")
88
+ .version("1.0.0");
89
+
90
+ // Default command - print
91
+ program
92
+ .argument("[input]", "Text to print, or path to file (auto-detects type)")
93
+ .option("-t, --tape <size>", "Tape size: 6, 9, 12, 18, 24 (mm)")
94
+ .option("-p, --printer <name>", "Printer name")
95
+ .option("-o, --output <file>", "Save to file instead of printing (png, jpg, bmp)")
96
+ .option("-a, --aspect <ratio>", "Aspect ratio width:height for HTML (e.g., 3.5:2)")
97
+ .option("--text", "Force input as literal text")
98
+ .option("--html", "Force input as HTML file path")
99
+ .option("--image", "Force input as image file path")
100
+ .action(async (input: string | undefined, opts) => {
101
+ if (!input) {
102
+ program.help();
103
+ return;
104
+ }
105
+
106
+ try {
107
+ const options = buildPrintOptions(input, opts);
108
+
109
+ if (opts.output) {
110
+ // Render only, save to file
111
+ const buffer = await render(options);
112
+ fs.writeFileSync(opts.output, buffer);
113
+ console.log(`Saved to ${opts.output}`);
114
+ } else {
115
+ // Print
116
+ const result = await print(options);
117
+ const tape = options.tape ?? getConfig().defaultTape ?? 24;
118
+ console.log(`Printed on ${tape}mm tape`);
119
+ }
120
+ } catch (err) {
121
+ console.error(`Error: ${(err as Error).message}`);
122
+ process.exit(1);
123
+ }
124
+ });
125
+
126
+ // List printers
127
+ program
128
+ .command("list")
129
+ .description("List available Brother printers")
130
+ .action(async () => {
131
+ try {
132
+ const printers = await listPrinters();
133
+ if (printers.length === 0) {
134
+ console.log("No Brother printers found");
135
+ } else {
136
+ console.log("Brother printers:");
137
+ printers.forEach(p => console.log(` ${p.name}`));
138
+ }
139
+ } catch (err) {
140
+ console.error(`Error: ${(err as Error).message}`);
141
+ process.exit(1);
142
+ }
143
+ });
144
+
145
+ // Config
146
+ program
147
+ .command("config")
148
+ .description("Show or set configuration")
149
+ .option("-t, --tape <size>", "Set default tape size: 6, 9, 12, 18, 24 (mm)")
150
+ .option("-p, --printer <name>", "Set default printer name")
151
+ .action((opts) => {
152
+ try {
153
+ if (!opts.tape && !opts.printer) {
154
+ // Show config
155
+ const config = getConfig();
156
+ console.log("Configuration:");
157
+ console.log(` File: ${getConfigPath()}`);
158
+ console.log(` Default tape: ${config.defaultTape ? config.defaultTape + "mm" : "(not set)"}`);
159
+ console.log(` Default printer: ${config.defaultPrinter ?? "(not set)"}`);
160
+ console.log("\nValid tape sizes: 6, 9, 12, 18, 24");
161
+ return;
162
+ }
163
+
164
+ if (opts.tape) {
165
+ const tape = parseTape(opts.tape);
166
+ setConfig({ defaultTape: tape });
167
+ console.log(`Default tape set to: ${tape}mm`);
168
+ }
169
+
170
+ if (opts.printer) {
171
+ setConfig({ defaultPrinter: opts.printer });
172
+ console.log(`Default printer set to: ${opts.printer}`);
173
+ }
174
+ } catch (err) {
175
+ console.error(`Error: ${(err as Error).message}`);
176
+ process.exit(1);
177
+ }
178
+ });
179
+
180
+ program.parse();
package/index.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Brother Label Printer API
3
+ * @module @bobfrankston/brother-label
4
+ */
5
+ export { TapeSize, Orientation, PrinterConfig, PrinterInfo, PrintOptions, PrintResult, print, render, getConfig, setConfig, getConfigPath, listPrinters, } from "./api.js";
6
+ //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAEH,QAAQ,EACR,WAAW,EACX,aAAa,EACb,WAAW,EACX,YAAY,EACZ,WAAW,EAEX,KAAK,EACL,MAAM,EACN,SAAS,EACT,SAAS,EACT,aAAa,EACb,YAAY,GACf,MAAM,UAAU,CAAC"}
package/index.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Brother Label Printer API
3
+ * @module @bobfrankston/brother-label
4
+ */
5
+ export {
6
+ // Functions
7
+ print, render, getConfig, setConfig, getConfigPath, listPrinters, } from "./api.js";
8
+ //# sourceMappingURL=index.js.map
package/index.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO;AAQH,YAAY;AACZ,KAAK,EACL,MAAM,EACN,SAAS,EACT,SAAS,EACT,aAAa,EACb,YAAY,GACf,MAAM,UAAU,CAAC"}
package/index.ts ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Brother Label Printer API
3
+ * @module @bobfrankston/brother-label
4
+ */
5
+
6
+ export {
7
+ // Types
8
+ TapeSize,
9
+ Orientation,
10
+ PrinterConfig,
11
+ PrinterInfo,
12
+ PrintOptions,
13
+ PrintResult,
14
+ // Functions
15
+ print,
16
+ render,
17
+ getConfig,
18
+ setConfig,
19
+ getConfigPath,
20
+ listPrinters,
21
+ } from "./api.js";
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@bobfrankston/brother-label",
3
+ "version": "1.0.1",
4
+ "description": "API and CLI for printing labels on Brother P-touch printers",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "types": "index.d.ts",
8
+ "bin": {
9
+ "brother-print": "./cli.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "types": "./index.d.ts",
14
+ "import": "./index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "*.js",
19
+ "*.js.map",
20
+ "*.d.ts",
21
+ "*.d.ts.map",
22
+ "*.ts",
23
+ "tsconfig.json",
24
+ "README.md"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsc",
28
+ "prepublishOnly": "npm run build",
29
+ "start": "node cli.js"
30
+ },
31
+ "keywords": [
32
+ "brother",
33
+ "label",
34
+ "printer",
35
+ "p-touch",
36
+ "pt-p710bt",
37
+ "qrcode"
38
+ ],
39
+ "author": "Bob Frankston",
40
+ "license": "MIT",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/BobFrankston/brother-label.git"
44
+ },
45
+ "dependencies": {
46
+ "commander": "^12.0.0",
47
+ "jimp": "^1.6.0",
48
+ "puppeteer": "^23.11.1",
49
+ "qrcode": "^1.5.4"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^20.0.0",
53
+ "@types/qrcode": "^1.5.6",
54
+ "typescript": "^5.0.0"
55
+ }
56
+ }
package/render.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * HTML to bitmap rendering module
3
+ * Standalone utility for converting HTML to PNG images
4
+ */
5
+ export interface RenderOptions {
6
+ width: number;
7
+ height: number;
8
+ deviceScaleFactor?: number;
9
+ keepScale?: boolean;
10
+ }
11
+ /**
12
+ * Close the shared browser instance
13
+ */
14
+ export declare function closeBrowser(): Promise<void>;
15
+ /**
16
+ * Render HTML file to PNG buffer
17
+ * Processes <img qr="..."> tags to inline base64 QR codes
18
+ */
19
+ export declare function renderHtmlFile(htmlPath: string, options: RenderOptions): Promise<Buffer>;
20
+ /**
21
+ * Render HTML string to PNG buffer
22
+ * Processes <img qr="..."> tags to inline base64 QR codes via DOM
23
+ * @param html - HTML content string
24
+ * @param options - Render dimensions
25
+ * @param basePath - Optional base path for resolving relative resources
26
+ */
27
+ export declare function renderHtmlString(html: string, options: RenderOptions, basePath?: string): Promise<Buffer>;
28
+ /**
29
+ * Render HTML from URL to PNG buffer
30
+ * Processes <img qr="..."> tags to inline base64 QR codes via DOM
31
+ */
32
+ export declare function renderHtmlUrl(url: string, options: RenderOptions): Promise<Buffer>;
33
+ /**
34
+ * Render HTML file and save to PNG file
35
+ */
36
+ export declare function renderHtmlToFile(htmlPath: string, outputPath: string, options: RenderOptions): Promise<void>;
37
+ export declare const renderHtml: typeof renderHtmlFile;
38
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["render.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAQH,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAC;CACvB;AA6ED;;GAEG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAKlD;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAM9F;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgC/G;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAyBxF;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAClC,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,aAAa,GACvB,OAAO,CAAC,IAAI,CAAC,CAGf;AAGD,eAAO,MAAM,UAAU,uBAAiB,CAAC"}
package/render.js ADDED
@@ -0,0 +1,170 @@
1
+ /**
2
+ * HTML to bitmap rendering module
3
+ * Standalone utility for converting HTML to PNG images
4
+ */
5
+ import puppeteer from "puppeteer";
6
+ import * as path from "path";
7
+ import * as fs from "fs";
8
+ import { Jimp } from "jimp";
9
+ import QRCode from "qrcode";
10
+ let browserInstance = null;
11
+ const DEFAULT_SCALE = 3; // Render at 3x for quality, then scale down
12
+ /**
13
+ * Generate QR code data URLs for all qr attributes found in page
14
+ * Returns map of qr data -> data URL
15
+ */
16
+ async function generateQrDataUrls(qrValues) {
17
+ const map = new Map();
18
+ for (const data of qrValues) {
19
+ if (!map.has(data)) {
20
+ const dataUrl = await QRCode.toDataURL(data, {
21
+ type: "image/png",
22
+ margin: 0,
23
+ errorCorrectionLevel: "M",
24
+ color: { dark: "#000000", light: "#ffffff" },
25
+ });
26
+ map.set(data, dataUrl);
27
+ }
28
+ }
29
+ return map;
30
+ }
31
+ /**
32
+ * Process <img qr="..."> elements in page via DOM manipulation
33
+ */
34
+ async function processQrElements(page) {
35
+ // Get all qr attribute values from the page
36
+ const qrValues = await page.evaluate(() => {
37
+ const imgs = document.querySelectorAll("img[qr]");
38
+ return Array.from(imgs).map(img => img.getAttribute("qr") || "");
39
+ });
40
+ if (qrValues.length === 0)
41
+ return;
42
+ // Generate QR codes
43
+ const qrMap = await generateQrDataUrls(qrValues);
44
+ // Convert map to object for passing to page context
45
+ const qrUrls = {};
46
+ qrMap.forEach((url, data) => { qrUrls[data] = url; });
47
+ // Replace qr attributes with src in the DOM
48
+ await page.evaluate((urls) => {
49
+ const imgs = document.querySelectorAll("img[qr]");
50
+ imgs.forEach(img => {
51
+ const qrData = img.getAttribute("qr");
52
+ if (qrData && urls[qrData]) {
53
+ img.setAttribute("src", urls[qrData]);
54
+ img.removeAttribute("qr");
55
+ }
56
+ });
57
+ }, qrUrls);
58
+ }
59
+ /**
60
+ * Scale image buffer down to target dimensions
61
+ */
62
+ async function scaleDown(buffer, targetWidth, targetHeight) {
63
+ const image = await Jimp.read(buffer);
64
+ image.resize({ w: targetWidth, h: targetHeight });
65
+ return image.getBuffer("image/png");
66
+ }
67
+ /**
68
+ * Get or create a shared browser instance for better performance
69
+ */
70
+ async function getBrowser() {
71
+ if (!browserInstance || !browserInstance.connected) {
72
+ browserInstance = await puppeteer.launch({ headless: true });
73
+ }
74
+ return browserInstance;
75
+ }
76
+ /**
77
+ * Close the shared browser instance
78
+ */
79
+ export async function closeBrowser() {
80
+ if (browserInstance) {
81
+ await browserInstance.close();
82
+ browserInstance = null;
83
+ }
84
+ }
85
+ /**
86
+ * Render HTML file to PNG buffer
87
+ * Processes <img qr="..."> tags to inline base64 QR codes
88
+ */
89
+ export async function renderHtmlFile(htmlPath, options) {
90
+ const absolutePath = path.resolve(htmlPath);
91
+ if (!fs.existsSync(absolutePath)) {
92
+ throw new Error(`HTML file not found: ${absolutePath}`);
93
+ }
94
+ return renderHtmlUrl(`file://${absolutePath}`, options);
95
+ }
96
+ /**
97
+ * Render HTML string to PNG buffer
98
+ * Processes <img qr="..."> tags to inline base64 QR codes via DOM
99
+ * @param html - HTML content string
100
+ * @param options - Render dimensions
101
+ * @param basePath - Optional base path for resolving relative resources
102
+ */
103
+ export async function renderHtmlString(html, options, basePath) {
104
+ const browser = await getBrowser();
105
+ const page = await browser.newPage();
106
+ try {
107
+ await page.setViewport({
108
+ width: options.width,
109
+ height: options.height,
110
+ deviceScaleFactor: options.deviceScaleFactor ?? 3,
111
+ });
112
+ if (basePath) {
113
+ // Set base URL for relative resource resolution
114
+ const baseUrl = `file://${path.resolve(basePath)}/`;
115
+ await page.goto(baseUrl, { waitUntil: "domcontentloaded" });
116
+ await page.setContent(html, { waitUntil: "networkidle0" });
117
+ }
118
+ else {
119
+ await page.setContent(html, { waitUntil: "networkidle0" });
120
+ }
121
+ // Process <img qr="..."> elements
122
+ await processQrElements(page);
123
+ const pngBuffer = await page.screenshot({ type: "png" });
124
+ const scaleFactor = options.deviceScaleFactor ?? DEFAULT_SCALE;
125
+ if (scaleFactor > 1 && !options.keepScale) {
126
+ return scaleDown(Buffer.from(pngBuffer), options.width, options.height);
127
+ }
128
+ return Buffer.from(pngBuffer);
129
+ }
130
+ finally {
131
+ await page.close();
132
+ }
133
+ }
134
+ /**
135
+ * Render HTML from URL to PNG buffer
136
+ * Processes <img qr="..."> tags to inline base64 QR codes via DOM
137
+ */
138
+ export async function renderHtmlUrl(url, options) {
139
+ const browser = await getBrowser();
140
+ const page = await browser.newPage();
141
+ try {
142
+ await page.setViewport({
143
+ width: options.width,
144
+ height: options.height,
145
+ deviceScaleFactor: options.deviceScaleFactor ?? 3,
146
+ });
147
+ await page.goto(url, { waitUntil: "networkidle0" });
148
+ // Process <img qr="..."> elements
149
+ await processQrElements(page);
150
+ const pngBuffer = await page.screenshot({ type: "png" });
151
+ const scaleFactor = options.deviceScaleFactor ?? DEFAULT_SCALE;
152
+ if (scaleFactor > 1 && !options.keepScale) {
153
+ return scaleDown(Buffer.from(pngBuffer), options.width, options.height);
154
+ }
155
+ return Buffer.from(pngBuffer);
156
+ }
157
+ finally {
158
+ await page.close();
159
+ }
160
+ }
161
+ /**
162
+ * Render HTML file and save to PNG file
163
+ */
164
+ export async function renderHtmlToFile(htmlPath, outputPath, options) {
165
+ const buffer = await renderHtmlFile(htmlPath, options);
166
+ fs.writeFileSync(outputPath, buffer);
167
+ }
168
+ // Legacy export for compatibility
169
+ export const renderHtml = renderHtmlFile;
170
+ //# sourceMappingURL=render.js.map