@bobfrankston/brother-label 1.1.14 → 1.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -18
- package/api.d.ts +19 -0
- package/api.d.ts.map +1 -1
- package/api.js +80 -11
- package/api.js.map +1 -1
- package/api.ts +84 -11
- package/cli.js +33 -49
- package/cli.js.map +1 -1
- package/cli.ts +31 -49
- package/index.d.ts +1 -1
- package/index.d.ts.map +1 -1
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/index.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,22 +10,30 @@ npm install -g @bobfrankston/brother-label
|
|
|
10
10
|
|
|
11
11
|
## Command Line Usage
|
|
12
12
|
|
|
13
|
+
Bare arguments are always label **content**; commands always take a leading dash
|
|
14
|
+
(`-list`, `-status`, `-config`). That separation is deliberate -- a mistyped
|
|
15
|
+
command (`-lsit`) is an unrecognized flag and errors, whereas a bare word can
|
|
16
|
+
only ever be something to print. A bare word that collides with a command name
|
|
17
|
+
is refused rather than guessed at.
|
|
18
|
+
|
|
13
19
|
### Print text
|
|
14
20
|
```bash
|
|
15
|
-
brother-print
|
|
16
|
-
brother-print
|
|
21
|
+
brother-print "Hello World"
|
|
22
|
+
brother-print "Line 1
|
|
23
|
+
Line 2" # Multi-line
|
|
17
24
|
```
|
|
18
25
|
|
|
19
|
-
### Print QR code
|
|
26
|
+
### Print a QR code
|
|
20
27
|
```bash
|
|
21
|
-
brother-print qr "https://example.com"
|
|
22
|
-
brother-print qr "https://example.com" -
|
|
23
|
-
brother-print qr "any data" -o qr.png
|
|
28
|
+
brother-print -qr "https://example.com"
|
|
29
|
+
brother-print -qr "https://example.com" -text "My Site" # QR plus a text segment
|
|
30
|
+
brother-print -qr "any data" -o qr.png # Save instead of printing
|
|
24
31
|
```
|
|
25
32
|
|
|
26
33
|
### Print HTML
|
|
27
34
|
```bash
|
|
28
|
-
brother-print
|
|
35
|
+
brother-print label.html # File type is auto-detected
|
|
36
|
+
brother-print -html label.html # Force HTML
|
|
29
37
|
```
|
|
30
38
|
|
|
31
39
|
#### Inline QR codes in HTML
|
|
@@ -39,26 +47,31 @@ Use `<img qr="data">` to embed QR codes directly in HTML - they're converted to
|
|
|
39
47
|
Combine multiple `-text` and `-qr` segments on a single label, printed side-by-side in order:
|
|
40
48
|
```bash
|
|
41
49
|
brother-print -text "Hello" -qr "https://example.com"
|
|
42
|
-
brother-print -text "Top
|
|
43
|
-
|
|
50
|
+
brother-print -text "Top
|
|
51
|
+
Bottom" -qr "https://example.com" -text "More text"
|
|
52
|
+
brother-print -text "Label" -qr "data" -o combined.png
|
|
44
53
|
```
|
|
45
54
|
|
|
46
|
-
### Print image
|
|
55
|
+
### Print an image
|
|
47
56
|
```bash
|
|
48
|
-
brother-print
|
|
57
|
+
brother-print photo.png # Auto-detected
|
|
58
|
+
brother-print -image photo.png # Force image
|
|
49
59
|
```
|
|
50
60
|
|
|
51
61
|
### Preview (save without printing)
|
|
62
|
+
`-o <file>` writes the rendered PNG instead of sending it to the printer:
|
|
52
63
|
```bash
|
|
53
|
-
brother-print
|
|
64
|
+
brother-print "Test Label" -o preview.png
|
|
54
65
|
```
|
|
55
66
|
|
|
56
|
-
###
|
|
67
|
+
### Commands
|
|
57
68
|
```bash
|
|
58
|
-
brother-print
|
|
59
|
-
brother-print
|
|
60
|
-
brother-print
|
|
61
|
-
brother-print
|
|
69
|
+
brother-print -list # List Brother printers and status
|
|
70
|
+
brother-print -list -mdns # ...also discover network printers
|
|
71
|
+
brother-print -status -p "Brother PT-D610BT" # Show one printer's status and queue
|
|
72
|
+
brother-print -config # Show current defaults
|
|
73
|
+
brother-print -config -tape 12 # Set default tape size
|
|
74
|
+
brother-print -config -p "Brother PT-P710BT" # Set default printer
|
|
62
75
|
```
|
|
63
76
|
|
|
64
77
|
### Print clipboard contents
|
|
@@ -92,7 +105,27 @@ Single-hyphen long options are supported: `-text` works the same as `--text`, `-
|
|
|
92
105
|
|
|
93
106
|
### Offline handling
|
|
94
107
|
|
|
95
|
-
By default `print()` waits for the printer to come online if it's asleep. Use `-no-wait` to fail immediately, or `-timeout <secs>` to bound the wait. Use `brother-print status` to check the current online state. The wait reports any other Brother printers that are online as alternatives.
|
|
108
|
+
By default `print()` waits for the printer to come online if it's asleep. Use `-no-wait` to fail immediately, or `-timeout <secs>` to bound the wait. Use `brother-print -status` to check the current online state. The wait reports any other Brother printers that are online as alternatives.
|
|
109
|
+
|
|
110
|
+
### Stuck queues
|
|
111
|
+
|
|
112
|
+
A Windows print queue keeps accepting jobs long after the printer stops taking them -- an unplugged USB printer gets flagged `WorkOffline` and swallows jobs silently, and a queue pointed at a Bluetooth COM port that no longer exists leaves the failed job parked in the queue forever. Every new label then lands behind the wedged one and nothing prints.
|
|
113
|
+
|
|
114
|
+
`status`, `list`, and the `-p <printer>` info dump all report the queue contents, marking any job that will not clear on its own with `[STUCK]`:
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
$ brother-print -p "Brother PT-D610BT"
|
|
118
|
+
Printer: Brother PT-D610BT
|
|
119
|
+
Port: COM8:
|
|
120
|
+
Status: offline (Error, Other error)
|
|
121
|
+
Queue: 1 job(s)
|
|
122
|
+
#7 "Xps Document" -- Error, Printing, Retained [STUCK]
|
|
123
|
+
Warning: Brother PT-D610BT: 1 stuck job in the queue (oldest: "Xps Document", Error, Printing, Retained, 39m old) -- clear the queue or nothing new will print.
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The same warning is emitted before printing, so submitting a label to a backed-up queue says so rather than appearing to succeed. Clearing a wedged job usually needs the port to work again, or an elevated `Restart-Service Spooler`.
|
|
127
|
+
|
|
128
|
+
A job counts as stuck when Windows reports `Error`, `Blocked`, `Offline`, `PaperOut`, `UserIntervention`, `Deleting`, or `Paused`, or when an unprinted job has sat in the queue for more than a minute.
|
|
96
129
|
|
|
97
130
|
### QL series printers (DK die-cut / continuous labels)
|
|
98
131
|
|
package/api.d.ts
CHANGED
|
@@ -35,7 +35,26 @@ export type { Segment };
|
|
|
35
35
|
export declare function getConfig(): PrinterConfig;
|
|
36
36
|
export declare function setConfig(config: Partial<PrinterConfig>): void;
|
|
37
37
|
export declare function getConfigPath(): string;
|
|
38
|
+
/**
|
|
39
|
+
* The queue used when a caller names no printer: the configured default, else
|
|
40
|
+
* the built-in fallback. Exported so callers can NAME the queue they are about
|
|
41
|
+
* to use instead of saying "the default".
|
|
42
|
+
* Claude Code 2026-08-24.
|
|
43
|
+
*/
|
|
44
|
+
export declare function getDefaultPrinter(): string;
|
|
38
45
|
export declare function listPrinters(): Promise<PrinterInfo[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Resolve a user-supplied printer value to an installed Windows queue name. Accepts:
|
|
48
|
+
* #N 1-based index into the list shown by `brother-label list`
|
|
49
|
+
* <exact> Full queue name (case-sensitive match shortcut)
|
|
50
|
+
* <substr> Case-insensitive substring against name or port (e.g. "ql820",
|
|
51
|
+
* "172.20.1.165", "BRW4CD577B59B6A"). Errors if ambiguous.
|
|
52
|
+
*
|
|
53
|
+
* Claude Code 2026-08-24: moved here from cli.ts so other apps that let the user
|
|
54
|
+
* name a printer (iplabels) resolve names the same way rather than each rolling
|
|
55
|
+
* its own matching.
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolvePrinter(value: string): Promise<string>;
|
|
39
58
|
/**
|
|
40
59
|
* Auto-detect tape size. Tries the live printer status first, then the
|
|
41
60
|
* Windows driver's default print ticket (see detectTape for details).
|
package/api.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAgBH,OAAO,KAAK,EACR,YAAY,EACZ,YAAY,IAAI,gBAAgB,EAChC,cAAc,IAAI,kBAAkB,EAMpC,OAAO,EAEV,MAAM,0BAA0B,CAAC;AAIlC,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAC5C,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,UAAU,CAAC;AAEnD,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC1B,WAAW,CAAC,EAAE,QAAQ,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,WAAW;IACxB,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,iFAAiF;AACjF,MAAM,WAAW,YAAa,SAAQ,IAAI,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACjE,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAY,6CAA6C;IACzE,WAAW,CAAC,EAAE,WAAW,CAAC,CAAE,kDAAkD;IAC9E,MAAM,CAAC,EAAE,MAAM,CAAC,CAAY,uCAAuC;CACtE;AAED,MAAM,WAAW,cAAe,SAAQ,IAAI,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrE,IAAI,CAAC,EAAE,QAAQ,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IACxB,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,YAAY,EAAE,OAAO,EAAE,CAAC;AA8BxB,wBAAgB,SAAS,IAAI,aAAa,CAMzC;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAE9D;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAID,wBAAsB,YAAY,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAG3D;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAG5E;
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["api.ts"],"names":[],"mappings":"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"}
|
package/api.js
CHANGED
|
@@ -48,11 +48,74 @@ export function setConfig(config) {
|
|
|
48
48
|
export function getConfigPath() {
|
|
49
49
|
return CONFIG_PATH;
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* The queue used when a caller names no printer: the configured default, else
|
|
53
|
+
* the built-in fallback. Exported so callers can NAME the queue they are about
|
|
54
|
+
* to use instead of saying "the default".
|
|
55
|
+
* Claude Code 2026-08-24.
|
|
56
|
+
*/
|
|
57
|
+
export function getDefaultPrinter() {
|
|
58
|
+
return getConfig().defaultPrinter ?? DEFAULT_PRINTER;
|
|
59
|
+
}
|
|
51
60
|
/* ----- Printer listing (backward-compat shape: { name }[]) ----- */
|
|
52
61
|
export async function listPrinters() {
|
|
53
62
|
const ps = await coreListPrinters(DEFAULT_PRINTER_PATTERN);
|
|
54
63
|
return ps.map(p => ({ name: p.name }));
|
|
55
64
|
}
|
|
65
|
+
/** Mark an error as user-actionable so cli.ts prints it inline instead of
|
|
66
|
+
* dumping to a log file. The cli's catch block duck-types on `isUserError`. */
|
|
67
|
+
function userError(msg) {
|
|
68
|
+
const e = new Error(msg);
|
|
69
|
+
e.isUserError = true; // No typed shape for this; cli.ts duck-types the flag
|
|
70
|
+
return e;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Resolve a user-supplied printer value to an installed Windows queue name. Accepts:
|
|
74
|
+
* #N 1-based index into the list shown by `brother-label list`
|
|
75
|
+
* <exact> Full queue name (case-sensitive match shortcut)
|
|
76
|
+
* <substr> Case-insensitive substring against name or port (e.g. "ql820",
|
|
77
|
+
* "172.20.1.165", "BRW4CD577B59B6A"). Errors if ambiguous.
|
|
78
|
+
*
|
|
79
|
+
* Claude Code 2026-08-24: moved here from cli.ts so other apps that let the user
|
|
80
|
+
* name a printer (iplabels) resolve names the same way rather than each rolling
|
|
81
|
+
* its own matching.
|
|
82
|
+
*/
|
|
83
|
+
export async function resolvePrinter(value) {
|
|
84
|
+
const all = await coreListPrinters(DEFAULT_PRINTER_PATTERN);
|
|
85
|
+
const sorted = [...all].sort((a, b) => a.name.localeCompare(b.name));
|
|
86
|
+
if (sorted.length === 0) {
|
|
87
|
+
throw userError(`No Brother printers installed. Run 'brother-label list' to verify.`);
|
|
88
|
+
}
|
|
89
|
+
const idx = value.match(/^#(\d+)$/);
|
|
90
|
+
if (idx) {
|
|
91
|
+
const n = parseInt(idx[1], 10);
|
|
92
|
+
if (n < 1 || n > sorted.length) {
|
|
93
|
+
throw userError(`${value}: only ${sorted.length} printer(s) installed (use 'brother-label list').`);
|
|
94
|
+
}
|
|
95
|
+
return sorted[n - 1].name;
|
|
96
|
+
}
|
|
97
|
+
const exact = sorted.find(p => p.name === value);
|
|
98
|
+
if (exact)
|
|
99
|
+
return exact.name;
|
|
100
|
+
const lower = value.toLowerCase();
|
|
101
|
+
const norm = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
102
|
+
const target = norm(value);
|
|
103
|
+
const matches = sorted.filter(p => {
|
|
104
|
+
const name = p.name.toLowerCase();
|
|
105
|
+
const port = (p.portName || "").toLowerCase();
|
|
106
|
+
if (name.includes(lower) || port.includes(lower))
|
|
107
|
+
return true;
|
|
108
|
+
// Normalized fallback: "ql820" matches "Brother QL-820NWB"
|
|
109
|
+
return norm(p.name).includes(target) || norm(p.portName || "").includes(target);
|
|
110
|
+
});
|
|
111
|
+
if (matches.length === 1)
|
|
112
|
+
return matches[0].name;
|
|
113
|
+
if (matches.length === 0) {
|
|
114
|
+
throw userError(`No printer matches '${value}'. Run 'brother-label list' to see available printers.`);
|
|
115
|
+
}
|
|
116
|
+
const names = matches.map(p => p.name).join(", ");
|
|
117
|
+
throw userError(`'${value}' is ambiguous — matches: ${names}`);
|
|
118
|
+
}
|
|
56
119
|
/**
|
|
57
120
|
* Auto-detect tape size. Tries the live printer status first, then the
|
|
58
121
|
* Windows driver's default print ticket (see detectTape for details).
|
|
@@ -67,8 +130,7 @@ export async function detectTapeSize(printerName) {
|
|
|
67
130
|
* this is just the driver's saved preference, not what's physically loaded.
|
|
68
131
|
*/
|
|
69
132
|
async function detectTapeSizeFromDriver(printerName) {
|
|
70
|
-
const
|
|
71
|
-
const printer = printerName ?? config.defaultPrinter ?? DEFAULT_PRINTER;
|
|
133
|
+
const printer = printerName ?? getDefaultPrinter();
|
|
72
134
|
const script = `
|
|
73
135
|
$ErrorActionPreference = 'SilentlyContinue'
|
|
74
136
|
Add-Type -AssemblyName System.Printing | Out-Null
|
|
@@ -121,8 +183,7 @@ function withTimeout(p, ms) {
|
|
|
121
183
|
* unsupported port. Never throws.
|
|
122
184
|
*/
|
|
123
185
|
export async function queryTapeStatus(printerName) {
|
|
124
|
-
const
|
|
125
|
-
const printer = printerName ?? config.defaultPrinter ?? DEFAULT_PRINTER;
|
|
186
|
+
const printer = printerName ?? getDefaultPrinter();
|
|
126
187
|
const usbPid = Object.entries(PT_USB_PIDS)
|
|
127
188
|
.find(([model]) => printer.toUpperCase().includes(model))?.[1] ?? "";
|
|
128
189
|
const script = String.raw `
|
|
@@ -290,11 +351,11 @@ class BrotherPrinterImpl {
|
|
|
290
351
|
return coreListPrinters(DEFAULT_PRINTER_PATTERN);
|
|
291
352
|
}
|
|
292
353
|
async getStatus(printerName) {
|
|
293
|
-
const printer = printerName ??
|
|
354
|
+
const printer = printerName ?? getDefaultPrinter();
|
|
294
355
|
return getPrinterStatus(printer);
|
|
295
356
|
}
|
|
296
357
|
async waitOnline(printerName, opts = {}) {
|
|
297
|
-
const printer = printerName ??
|
|
358
|
+
const printer = printerName ?? getDefaultPrinter();
|
|
298
359
|
return coreWaitOnline(printer, opts, DEFAULT_PRINTER_PATTERN);
|
|
299
360
|
}
|
|
300
361
|
async render(opts) {
|
|
@@ -304,7 +365,7 @@ class BrotherPrinterImpl {
|
|
|
304
365
|
}
|
|
305
366
|
async print(opts) {
|
|
306
367
|
validateContent(opts);
|
|
307
|
-
const printer = opts.printer ??
|
|
368
|
+
const printer = opts.printer ?? getDefaultPrinter();
|
|
308
369
|
const tape = await this.resolveTape(opts.media);
|
|
309
370
|
const image = await resolveContent(opts, TAPE_SIZES[tape].height, tape);
|
|
310
371
|
await ensureOnline(printer, opts);
|
|
@@ -316,7 +377,7 @@ class BrotherPrinterImpl {
|
|
|
316
377
|
return coreRenderSegments(segments, TAPE_SIZES[tape].height, opts.textHeight, opts.space);
|
|
317
378
|
}
|
|
318
379
|
async printSegments(segments, opts = {}) {
|
|
319
|
-
const printer = opts.printer ??
|
|
380
|
+
const printer = opts.printer ?? getDefaultPrinter();
|
|
320
381
|
const tape = await this.resolveTape(opts.media);
|
|
321
382
|
const image = await coreRenderSegments(segments, TAPE_SIZES[tape].height, opts.textHeight, opts.space);
|
|
322
383
|
await ensureOnline(printer, opts);
|
|
@@ -378,13 +439,21 @@ export async function printSegments(segments, options) {
|
|
|
378
439
|
/* ----- Brother XPS print path (kept here, Brother-specific) ----- */
|
|
379
440
|
async function ensureOnline(printer, opts) {
|
|
380
441
|
const status = await getPrinterStatus(printer);
|
|
381
|
-
|
|
442
|
+
const queueNote = status.queueWarning ? " " + status.queueWarning : "";
|
|
443
|
+
if (status.online) {
|
|
444
|
+
/* A backed-up queue matters even when the printer is reachable: the
|
|
445
|
+
* label about to be submitted lands behind whatever is already wedged,
|
|
446
|
+
* so nothing comes out of the printer. The offline paths below carry
|
|
447
|
+
* the same note in their own message, so only log it here. */
|
|
448
|
+
if (status.queueWarning && opts.log)
|
|
449
|
+
opts.log(`[brother-label] ${status.queueWarning}`);
|
|
382
450
|
return;
|
|
451
|
+
}
|
|
383
452
|
if (opts.wait === false) {
|
|
384
|
-
throw new Error(`Printer "${printer}" is offline (${status.statusText}${status.error ? ", " + status.error : ""})
|
|
453
|
+
throw new Error(`Printer "${printer}" is offline (${status.statusText}${status.error ? ", " + status.error : ""}).${queueNote} Use wait:true to wait, or pick another printer.`);
|
|
385
454
|
}
|
|
386
455
|
if (opts.log)
|
|
387
|
-
opts.log(`[brother-label] ${printer} offline (${status.statusText}); waiting
|
|
456
|
+
opts.log(`[brother-label] ${printer} offline (${status.statusText}); waiting...${queueNote}`);
|
|
388
457
|
await coreWaitOnline(printer, {
|
|
389
458
|
timeoutMs: opts.waitTimeoutMs,
|
|
390
459
|
intervalMs: opts.waitIntervalMs,
|
package/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["api.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Brother Label Printer API\n * Programmatic interface for printing labels on Brother P-touch printers.\n *\n * Implements the unified LabelPrinter interface from @bobfrankston/label-core.\n * All printer-agnostic logic (rendering, segments, clipboard, CLI primitives,\n * online-wait) lives in label-core. This file holds the Brother-specific bits:\n * TAPE_SIZES catalog, MEDIA_OPTIONS (CustomMediaSize* names), XPS PrintTicket\n * builder with the Brother namespace, and detectTapeSize.\n */\n\nimport * as fs from \"fs\";\nimport * as os from \"os\";\nimport * as path from \"path\";\nimport {\n validateContent,\n resolveContent,\n renderSegments as coreRenderSegments,\n listPrinters as coreListPrinters,\n getPrinterStatus,\n waitOnline as coreWaitOnline,\n runScriptOrThrow,\n readJsonConfig,\n mergeJsonConfig,\n} from \"@bobfrankston/label-core\";\nimport type {\n LabelPrinter,\n PrintOptions as CorePrintOptions,\n SegmentOptions as CoreSegmentOptions,\n PrintResult as CorePrintResult,\n PrinterStatus,\n PrinterInfo as CorePrinterInfo,\n PrinterConfig as CorePrinterConfig,\n MediaInfo,\n Segment,\n WaitOptions,\n} from \"@bobfrankston/label-core\";\n\n/* ----- Brother-specific types ----- */\n\nexport type TapeSize = 6 | 9 | 12 | 18 | 24;\nexport type Orientation = \"landscape\" | \"portrait\";\n\n/** Backward-compat: brother-label's config kept the `defaultTape` name. */\nexport interface PrinterConfig {\n defaultTape?: TapeSize;\n defaultPrinter?: string;\n}\n\nexport interface PrinterInfo {\n name: string;\n}\n\n/** Brother-specific PrintOptions: same as core but with tape alias for media. */\nexport interface PrintOptions extends Omit<CorePrintOptions, \"media\"> {\n tape?: TapeSize; /** Tape size in mm — alias for core.media */\n orientation?: Orientation; /** Reserved (Brother PT prints landscape only) */\n length?: number; /** Reserved — explicit length in mm */\n}\n\nexport interface SegmentOptions extends Omit<CoreSegmentOptions, \"media\"> {\n tape?: TapeSize;\n}\n\nexport interface PrintResult {\n image: Buffer;\n}\n\nexport type { Segment };\n\n/* ----- Constants ----- */\n\nconst CONFIG_PATH = path.join(os.homedir(), \".brother-label.json\");\nconst DEFAULT_PRINTER = \"Brother PT-P710BT\";\nconst DEFAULT_PRINTER_PATTERN = /brother|^pt-|^ql-/i;\nconst PRINT_DPI = 300;\nconst VALID_TAPES: TapeSize[] = [6, 9, 12, 18, 24];\n\n/** Tape sizes: width in pixels @ 300 DPI = continuous direction; height = perpendicular. */\nconst TAPE_SIZES: Record<TapeSize, { width: number; height: number }> = {\n 6: { width: 1137, height: 56 },\n 9: { width: 1137, height: 84 },\n 12: { width: 1137, height: 113 },\n 18: { width: 1137, height: 169 },\n 24: { width: 1137, height: 213 },\n};\n\n/** Brother CustomMediaSize names + tape widths in microns (XPS PrintTicket). */\nconst MEDIA_OPTIONS: Record<TapeSize, { name: string; width: number }> = {\n 6: { name: \"CustomMediaSize257\", width: 5900 },\n 9: { name: \"CustomMediaSize258\", width: 9000 },\n 12: { name: \"CustomMediaSize259\", width: 11900 },\n 18: { name: \"CustomMediaSize260\", width: 18100 },\n 24: { name: \"CustomMediaSize261\", width: 24000 },\n};\n\n/* ----- Config (backward-compat — note \"defaultTape\" stays in JSON) ----- */\n\nexport function getConfig(): PrinterConfig {\n const raw: any = readJsonConfig(CONFIG_PATH);\n if (typeof raw.defaultTape === \"string\") {\n raw.defaultTape = parseInt(raw.defaultTape.replace(\"mm\", \"\"), 10) as TapeSize;\n }\n return raw;\n}\n\nexport function setConfig(config: Partial<PrinterConfig>): void {\n mergeJsonConfig(CONFIG_PATH, config);\n}\n\nexport function getConfigPath(): string {\n return CONFIG_PATH;\n}\n\n/* ----- Printer listing (backward-compat shape: { name }[]) ----- */\n\nexport async function listPrinters(): Promise<PrinterInfo[]> {\n const ps = await coreListPrinters(DEFAULT_PRINTER_PATTERN);\n return ps.map(p => ({ name: p.name }));\n}\n\n/**\n * Auto-detect tape size. Tries the live printer status first, then the\n * Windows driver's default print ticket (see detectTape for details).\n */\nexport async function detectTapeSize(printerName?: string): Promise<TapeSize> {\n const d = await detectTape(printerName);\n return d?.tape ?? null;\n}\n\n/**\n * Read the tape size the Windows driver's default PrintTicket claims.\n * Brother's driver embeds a CustomMediaSize name in the ticket XML — but\n * this is just the driver's saved preference, not what's physically loaded.\n */\nasync function detectTapeSizeFromDriver(printerName?: string): Promise<TapeSize> {\n const config = getConfig();\n const printer = printerName ?? config.defaultPrinter ?? DEFAULT_PRINTER;\n const script = `\n$ErrorActionPreference = 'SilentlyContinue'\nAdd-Type -AssemblyName System.Printing | Out-Null\ntry {\n $server = New-Object System.Printing.LocalPrintServer\n $queue = $server.GetPrintQueue('${printer.replace(/'/g, \"''\")}')\n $ticket = $queue.DefaultPrintTicket\n $stream = $ticket.GetXmlStream()\n $reader = New-Object System.IO.StreamReader($stream)\n Write-Output $reader.ReadToEnd()\n $reader.Close()\n} catch {\n Write-Output \"ERROR\"\n}\n`;\n try {\n const out = await runScriptOrThrow(script);\n for (const [size, media] of Object.entries(MEDIA_OPTIONS)) {\n if (out.includes(media.name)) return parseInt(size, 10) as TapeSize;\n }\n } catch { /* fall through */ }\n return null;\n}\n\n/* ----- Live tape status (raw P-touch protocol) ----- */\n\n/**\n * USB product IDs for PT models whose queue sits on a spooler USB port.\n * The spooler is write-only, so the live status request needs the usbprint\n * device interface opened directly (CreateFile on the \\\\?\\USB#... path).\n * Bluetooth models (PT-D610BT) need no entry — their queue's PortName is a\n * COM port, which is bidirectional as-is.\n */\nconst PT_USB_PIDS: Record<string, string> = {\n \"PT-P710BT\": \"20AF\",\n};\n\n/** Parsed ESC i S status reply (32 bytes; byte 10 = cassette width in mm). */\nexport interface TapeStatus {\n widthMm: number; /** 0 = no cassette loaded */\n tape: TapeSize | null; /** widthMm when it maps to a supported size */\n mediaType: number; /** 0x01 laminated, 0x03 non-laminated, 0x11 heat-shrink */\n raw: string; /** all 32 status bytes as hex */\n}\n\nfunction withTimeout<T>(p: Promise<T>, ms: number): Promise<T> {\n return Promise.race([\n p,\n new Promise<never>((_, reject) => {\n const t = setTimeout(() => reject(new Error(\"timeout\")), ms);\n t.unref();\n }),\n ]);\n}\n\n/**\n * Query the printer itself for the loaded cassette via the raw P-touch\n * status request (100×0x00 invalidate, ESC @, ESC i S → 32-byte reply).\n * Returns null when the printer is off, busy printing, or on an\n * unsupported port. Never throws.\n */\nexport async function queryTapeStatus(printerName?: string): Promise<TapeStatus | null> {\n const config = getConfig();\n const printer = printerName ?? config.defaultPrinter ?? DEFAULT_PRINTER;\n const usbPid = Object.entries(PT_USB_PIDS)\n .find(([model]) => printer.toUpperCase().includes(model))?.[1] ?? \"\";\n const script = String.raw`\n$ErrorActionPreference = 'Stop'\ntry { $port = (Get-Printer -Name '${printer.replace(/'/g, \"''\")}').PortName }\ncatch { Write-Output 'FAIL no such queue'; exit 0 }\n\n$req = New-Object byte[] 105\n$req[100] = 0x1B; $req[101] = 0x40 # ESC @ initialize\n$req[102] = 0x1B; $req[103] = 0x69; $req[104] = 0x53 # ESC i S status request\n\nif ($port -match '^COM\\d+') {\n try {\n $sp = New-Object System.IO.Ports.SerialPort ($port.TrimEnd(':'))\n $sp.ReadTimeout = 3000; $sp.WriteTimeout = 2000\n $sp.Open()\n try {\n $sp.Write($req, 0, $req.Length)\n $buf = New-Object byte[] 32\n $total = 0\n while ($total -lt 32) {\n $n = $sp.Read($buf, $total, 32 - $total)\n if ($n -le 0) { break }\n $total += $n\n }\n if ($total -lt 32) { Write-Output 'FAIL short serial read'; exit 0 }\n Write-Output ('OK ' + (($buf | ForEach-Object { $_.ToString('X2') }) -join ' '))\n } finally { $sp.Close() }\n } catch { Write-Output \"FAIL $($_.Exception.Message)\" }\n exit 0\n}\nif ($port -notmatch '^USB') { Write-Output \"FAIL unsupported port $port\"; exit 0 }\n\n# usbprint device interfaces live under this class GUID; stale (unplugged)\n# entries persist in the registry, so try each match until one opens.\n$classKey = 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\DeviceClasses\\{28d78fad-5a12-11d1-ae5b-0000f803a8c2}'\n$pidFilter = '${usbPid}'\n$paths = @(Get-ChildItem $classKey | ForEach-Object {\n $leaf = Split-Path $_.Name -Leaf\n if ($leaf -match 'VID_04F9' -and ($pidFilter -eq '' -or $leaf -match \"PID_$pidFilter\")) {\n $leaf -replace '^##\\?#', '\\\\?\\'\n }\n})\nif ($paths.Count -eq 0) { Write-Output 'FAIL no usbprint interface'; exit 0 }\n\nAdd-Type -TypeDefinition @'\nusing System;\nusing System.IO;\nusing System.Runtime.InteropServices;\nusing System.Threading.Tasks;\nusing Microsoft.Win32.SafeHandles;\n\npublic static class BrotherRawUsb {\n [DllImport(\"kernel32.dll\", CharSet = CharSet.Unicode, SetLastError = true)]\n static extern SafeFileHandle CreateFile(string fileName, uint access, uint share,\n IntPtr security, uint creation, uint flags, IntPtr template);\n\n public static byte[] QueryStatus(string devPath, byte[] request) {\n SafeFileHandle h = CreateFile(devPath, 0xC0000000u /*GENERIC_RW*/, 0, IntPtr.Zero,\n 3 /*OPEN_EXISTING*/, 0, IntPtr.Zero);\n if (h.IsInvalid) throw new IOException(\"open failed: \" + Marshal.GetLastWin32Error());\n using (FileStream fs = new FileStream(h, FileAccess.ReadWrite)) {\n fs.Write(request, 0, request.Length);\n fs.Flush();\n byte[] buf = new byte[32];\n int total = 0;\n while (total < 32) {\n Task<int> t = fs.ReadAsync(buf, total, 32 - total);\n if (!t.Wait(3000)) throw new IOException(\"status read timeout\");\n if (t.Result <= 0) throw new IOException(\"read returned \" + t.Result);\n total += t.Result;\n }\n return buf;\n }\n }\n}\n'@\n\nforeach ($p in $paths) {\n try {\n $buf = [BrotherRawUsb]::QueryStatus($p, $req)\n Write-Output ('OK ' + (($buf | ForEach-Object { $_.ToString('X2') }) -join ' '))\n exit 0\n } catch { }\n}\nWrite-Output 'FAIL no device answered'\n`;\n try {\n const out = await withTimeout(runScriptOrThrow(script), 10000);\n const m = out.match(/^OK ((?:[0-9A-F]{2} ?){32})/m);\n if (!m) return null;\n const bytes = m[1].trim().split(/\\s+/).map(h => parseInt(h, 16));\n if (bytes[0] !== 0x80) return null; /* print-head mark: sanity check */\n const widthMm = bytes[10];\n const tape = VALID_TAPES.includes(widthMm as TapeSize) ? widthMm as TapeSize : null;\n return { widthMm, tape, mediaType: bytes[11], raw: m[1].trim() };\n } catch {\n return null;\n }\n}\n\nexport interface DetectedTape {\n tape: TapeSize;\n source: \"printer\" | \"driver\";\n}\n\n/**\n * Detect the loaded tape: live printer status first (authoritative — reads\n * the cassette sensor), falling back to the Windows driver's default print\n * ticket, which is only the driver's saved preference and may be stale.\n * A live reply of \"no cassette\" returns null rather than falling back.\n */\nexport async function detectTape(printerName?: string): Promise<DetectedTape | null> {\n const live = await queryTapeStatus(printerName);\n if (live) {\n return live.tape ? { tape: live.tape, source: \"printer\" } : null;\n }\n const t = await detectTapeSizeFromDriver(printerName);\n return t ? { tape: t, source: \"driver\" } : null;\n}\n\n/* ----- Brother LabelPrinter implementation ----- */\n\nclass BrotherPrinterImpl implements LabelPrinter {\n readonly driverName = \"brother\";\n\n getConfig(): CorePrinterConfig {\n const c = getConfig();\n return {\n defaultPrinter: c.defaultPrinter,\n defaultMedia: c.defaultTape !== undefined ? String(c.defaultTape) : undefined,\n };\n }\n setConfig(config: Partial<CorePrinterConfig>): void {\n const update: Partial<PrinterConfig> = {};\n if (config.defaultPrinter !== undefined) update.defaultPrinter = config.defaultPrinter;\n if (config.defaultMedia !== undefined) {\n const t = parseInt(config.defaultMedia, 10) as TapeSize;\n if (VALID_TAPES.includes(t)) update.defaultTape = t;\n }\n setConfig(update);\n }\n getConfigPath(): string {\n return CONFIG_PATH;\n }\n\n listMedia(): MediaInfo[] {\n return VALID_TAPES.map(t => ({\n id: String(t),\n name: `${t}mm tape`,\n widthPx: TAPE_SIZES[t].height, /** \"media width\" (perpendicular to feed) */\n lengthPx: 0, /** continuous tape — length determined by content */\n continuous: true,\n }));\n }\n getMedia(id: string): MediaInfo {\n const t = parseInt(id.replace(\"mm\", \"\"), 10) as TapeSize;\n if (!VALID_TAPES.includes(t)) return null;\n return {\n id: String(t), name: `${t}mm tape`,\n widthPx: TAPE_SIZES[t].height, lengthPx: 0, continuous: true,\n };\n }\n async detectMedia(printerName?: string): Promise<MediaInfo> {\n const t = await detectTapeSize(printerName);\n return t ? this.getMedia(String(t)) : null;\n }\n\n async listPrinters(): Promise<CorePrinterInfo[]> {\n return coreListPrinters(DEFAULT_PRINTER_PATTERN);\n }\n async getStatus(printerName?: string): Promise<PrinterStatus> {\n const printer = printerName ?? this.getConfig().defaultPrinter ?? DEFAULT_PRINTER;\n return getPrinterStatus(printer);\n }\n async waitOnline(printerName?: string, opts: WaitOptions = {}): Promise<PrinterStatus> {\n const printer = printerName ?? this.getConfig().defaultPrinter ?? DEFAULT_PRINTER;\n return coreWaitOnline(printer, opts, DEFAULT_PRINTER_PATTERN);\n }\n\n async render(opts: CorePrintOptions): Promise<Buffer> {\n validateContent(opts);\n const tape = await this.resolveTape(opts.media);\n return resolveContent(opts, TAPE_SIZES[tape].height, tape);\n }\n\n async print(opts: CorePrintOptions): Promise<CorePrintResult> {\n validateContent(opts);\n const printer = opts.printer ?? this.getConfig().defaultPrinter ?? DEFAULT_PRINTER;\n const tape = await this.resolveTape(opts.media);\n const image = await resolveContent(opts, TAPE_SIZES[tape].height, tape);\n await ensureOnline(printer, opts);\n await printBuffer(image, printer, tape);\n return { image, media: this.getMedia(String(tape)), printer };\n }\n\n async renderSegments(segments: Segment[], opts: CoreSegmentOptions = {}): Promise<Buffer> {\n const tape = await this.resolveTape(opts.media);\n return coreRenderSegments(segments, TAPE_SIZES[tape].height, opts.textHeight, opts.space);\n }\n async printSegments(segments: Segment[], opts: CoreSegmentOptions = {}): Promise<CorePrintResult> {\n const printer = opts.printer ?? this.getConfig().defaultPrinter ?? DEFAULT_PRINTER;\n const tape = await this.resolveTape(opts.media);\n const image = await coreRenderSegments(segments, TAPE_SIZES[tape].height, opts.textHeight, opts.space);\n await ensureOnline(printer, opts);\n await printBuffer(image, printer, tape);\n return { image, media: this.getMedia(String(tape)), printer };\n }\n\n private async resolveTape(media?: string): Promise<TapeSize> {\n if (media !== undefined) {\n const t = parseInt(media.replace(\"mm\", \"\"), 10) as TapeSize;\n if (!VALID_TAPES.includes(t)) throw new Error(`Invalid tape size: ${media}. Valid: ${VALID_TAPES.join(\", \")}`);\n return t;\n }\n const cfg = getConfig();\n if (cfg.defaultTape) return cfg.defaultTape;\n const detected = await detectTapeSize();\n return detected ?? 24;\n }\n}\n\n/** Public Brother singleton (implements LabelPrinter). */\nexport const brotherPrinter: LabelPrinter = new BrotherPrinterImpl();\n\n/* ----- Backward-compatible function exports ----- */\n\n/**\n * Translate brother-label PrintOptions (with tape) to core (with media).\n */\nfunction toCore(opts: PrintOptions): CorePrintOptions {\n const out: CorePrintOptions = { ...opts };\n if (opts.tape !== undefined && out.media === undefined) {\n out.media = String(opts.tape);\n }\n delete (out as any).tape;\n return out;\n}\nfunction toCoreSeg(opts: SegmentOptions = {}): CoreSegmentOptions {\n const out: CoreSegmentOptions = { ...opts };\n if (opts.tape !== undefined && out.media === undefined) out.media = String(opts.tape);\n delete (out as any).tape;\n return out;\n}\n\nexport async function render(options: PrintOptions): Promise<Buffer> {\n return brotherPrinter.render(toCore(options));\n}\n\nexport async function print(options: PrintOptions): Promise<PrintResult> {\n const r = await brotherPrinter.print(toCore(options));\n return { image: r.image };\n}\n\nexport async function renderSegments(segments: Segment[], tape?: TapeSize, textHeight?: string, space?: string): Promise<Buffer> {\n return brotherPrinter.renderSegments(segments, {\n media: tape !== undefined ? String(tape) : undefined,\n textHeight, space,\n });\n}\n\nexport async function printSegments(segments: Segment[], options?: SegmentOptions): Promise<PrintResult> {\n const r = await brotherPrinter.printSegments(segments, toCoreSeg(options));\n return { image: r.image };\n}\n\n/* ----- Brother XPS print path (kept here, Brother-specific) ----- */\n\nasync function ensureOnline(printer: string, opts: { wait?: boolean; waitTimeoutMs?: number; waitIntervalMs?: number; onWaiting?: CorePrintOptions[\"onWaiting\"]; log?: CorePrintOptions[\"log\"] }): Promise<void> {\n const status = await getPrinterStatus(printer);\n if (status.online) return;\n if (opts.wait === false) {\n throw new Error(`Printer \"${printer}\" is offline (${status.statusText}${status.error ? \", \" + status.error : \"\"}). Use wait:true to wait, or pick another printer.`);\n }\n if (opts.log) opts.log(`[brother-label] ${printer} offline (${status.statusText}); waiting...`);\n await coreWaitOnline(printer, {\n timeoutMs: opts.waitTimeoutMs,\n intervalMs: opts.waitIntervalMs,\n onWaiting: opts.onWaiting,\n log: opts.log,\n }, DEFAULT_PRINTER_PATTERN);\n}\n\n/**\n * Print a single image buffer using the Brother XPS PrintTicket path.\n * Builds an XPS document with a custom CustomMediaSize for the tape, then\n * sends it to the print queue.\n */\nasync function printBuffer(imageBuffer: Buffer, printer: string, tape: TapeSize): Promise<void> {\n const media = MEDIA_OPTIONS[tape];\n const tempPath = path.join(os.tmpdir(), `label-${Date.now()}.png`);\n fs.writeFileSync(tempPath, imageBuffer);\n const psPath = tempPath.replace(/\\\\/g, \"\\\\\\\\\");\n\n const script = `\n$ErrorActionPreference = 'Stop'\nAdd-Type -AssemblyName System.Drawing\nAdd-Type -AssemblyName System.Printing\nAdd-Type -AssemblyName ReachFramework\nAdd-Type -AssemblyName PresentationCore\n\n$img = [System.Drawing.Image]::FromFile('${psPath}')\n\n$DPI = ${PRINT_DPI}\n$MICRONS_PER_INCH = 25400\n\n$labelLengthMicrons = [int]($img.Width / $DPI * $MICRONS_PER_INCH) + 3000\n$tapeWidthMicrons = ${media.width}\n\n$imgWidthWpf = $img.Width / $DPI * 96\n$imgHeightWpf = $img.Height / $DPI * 96\n\n$ticketXml = @\"\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<psf:PrintTicket xmlns:psf=\"http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" version=\"1\"\n xmlns:psk=\"http://schemas.microsoft.com/windows/2003/08/printing/printschemakeywords\"\n xmlns:ns0001=\"http://schemas.brother.info/mfc/printing/2006/11/printschemakeywords\">\n <psf:Feature name=\"psk:PageMediaSize\">\n <psf:Option name=\"ns0001:${media.name}\">\n <psf:ScoredProperty name=\"psk:MediaSizeWidth\">\n <psf:Value xsi:type=\"xsd:integer\">${media.width}</psf:Value>\n </psf:ScoredProperty>\n <psf:ScoredProperty name=\"psk:MediaSizeHeight\">\n <psf:ParameterRef name=\"psk:PageMediaSizeMediaSizeHeight\" />\n </psf:ScoredProperty>\n </psf:Option>\n </psf:Feature>\n <psf:ParameterInit name=\"psk:PageMediaSizeMediaSizeHeight\">\n <psf:Value xsi:type=\"xsd:integer\">$labelLengthMicrons</psf:Value>\n </psf:ParameterInit>\n <psf:Feature name=\"psk:PageOrientation\">\n <psf:Option name=\"psk:Landscape\" />\n </psf:Feature>\n <psf:Feature name=\"ns0001:PageRollFeedToEndOfSheet\">\n <psf:Option name=\"ns0001:FeedToImageEdge\" />\n </psf:Feature>\n <psf:Feature name=\"psk:PageMediaType\">\n <psf:Option name=\"psk:Label\" />\n </psf:Feature>\n <psf:Feature name=\"ns0001:JobRollCutAtEndOfJob\">\n <psf:Option name=\"ns0001:Cut\" />\n </psf:Feature>\n</psf:PrintTicket>\n\"@\n\n$xmlBytes = [System.Text.Encoding]::UTF8.GetBytes($ticketXml)\n$memStream = New-Object System.IO.MemoryStream(,$xmlBytes)\n$ticket = New-Object System.Printing.PrintTicket($memStream)\n\n$server = New-Object System.Printing.LocalPrintServer\n$queue = $server.GetPrintQueue('${printer.replace(/'/g, \"''\")}')\n\n$xpsPath = [System.IO.Path]::GetTempFileName() + \".xps\"\n\n$pageWidth = $labelLengthMicrons / $MICRONS_PER_INCH * 96\n$pageHeight = $tapeWidthMicrons / $MICRONS_PER_INCH * 96\n\n$package = [System.IO.Packaging.Package]::Open($xpsPath, [System.IO.FileMode]::Create)\n$xpsDoc = New-Object System.Windows.Xps.Packaging.XpsDocument($package)\n$writer = [System.Windows.Xps.Packaging.XpsDocument]::CreateXpsDocumentWriter($xpsDoc)\n\n$visual = New-Object System.Windows.Media.DrawingVisual\n$dc = $visual.RenderOpen()\n\n$bitmapImg = New-Object System.Windows.Media.Imaging.BitmapImage\n$bitmapImg.BeginInit()\n$bitmapImg.UriSource = New-Object System.Uri('${psPath}')\n$bitmapImg.EndInit()\n\n$yOffset = ($pageHeight - $imgHeightWpf) / 2\n$xOffset = 2 / 25.4 * 96\n$rect = New-Object System.Windows.Rect($xOffset, $yOffset, $imgWidthWpf, $imgHeightWpf)\n$dc.DrawImage($bitmapImg, $rect)\n$dc.Close()\n\n$writer.Write($visual, $ticket)\n\n$xpsDoc.Close()\n$package.Close()\n$img.Dispose()\n\n$xpsDocForPrint = New-Object System.Windows.Xps.Packaging.XpsDocument($xpsPath, [System.IO.FileAccess]::Read)\n$seq = $xpsDocForPrint.GetFixedDocumentSequence()\n\n$xpsWriter = [System.Printing.PrintQueue]::CreateXpsDocumentWriter($queue)\n$xpsWriter.Write($seq, $ticket)\n\n$xpsDocForPrint.Close()\nRemove-Item $xpsPath -ErrorAction SilentlyContinue\n`;\n try {\n await runScriptOrThrow(script);\n } finally {\n if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EACH,eAAe,EACf,cAAc,EACd,cAAc,IAAI,kBAAkB,EACpC,YAAY,IAAI,gBAAgB,EAChC,gBAAgB,EAChB,UAAU,IAAI,cAAc,EAC5B,gBAAgB,EAChB,cAAc,EACd,eAAe,GAClB,MAAM,0BAA0B,CAAC;AA8ClC,2BAA2B;AAE3B,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,qBAAqB,CAAC,CAAC;AACnE,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAC5C,MAAM,uBAAuB,GAAG,oBAAoB,CAAC;AACrD,MAAM,SAAS,GAAG,GAAG,CAAC;AACtB,MAAM,WAAW,GAAe,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;AAEnD,4FAA4F;AAC5F,MAAM,UAAU,GAAwD;IACpE,CAAC,EAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE;IAC/B,CAAC,EAAG,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE;IAC/B,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;IAChC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;IAChC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;CACnC,CAAC;AAEF,gFAAgF;AAChF,MAAM,aAAa,GAAsD;IACrE,CAAC,EAAG,EAAE,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,IAAI,EAAE;IAC/C,CAAC,EAAG,EAAE,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,IAAI,EAAE;IAC/C,EAAE,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,KAAK,EAAE;IAChD,EAAE,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,KAAK,EAAE;IAChD,EAAE,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,KAAK,EAAE,KAAK,EAAE;CACnD,CAAC;AAEF,6EAA6E;AAE7E,MAAM,UAAU,SAAS;IACrB,MAAM,GAAG,GAAQ,cAAc,CAAC,WAAW,CAAC,CAAC;IAC7C,IAAI,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;QACtC,GAAG,CAAC,WAAW,GAAG,QAAQ,CAAC,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,CAAa,CAAC;IAClF,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,MAA8B;IACpD,eAAe,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,aAAa;IACzB,OAAO,WAAW,CAAC;AACvB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB;IAC7B,OAAO,SAAS,EAAE,CAAC,cAAc,IAAI,eAAe,CAAC;AACzD,CAAC;AAED,qEAAqE;AAErE,MAAM,CAAC,KAAK,UAAU,YAAY;IAC9B,MAAM,EAAE,GAAG,MAAM,gBAAgB,CAAC,uBAAuB,CAAC,CAAC;IAC3D,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;AAC3C,CAAC;AAED;gFACgF;AAChF,SAAS,SAAS,CAAC,GAAW;IAC1B,MAAM,CAAC,GAAG,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC;IACxB,CAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAG,sDAAsD;IACvF,OAAO,CAAC,CAAC;AACb,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,KAAa;IAC9C,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,uBAAuB,CAAC,CAAC;IAC5D,MAAM,MAAM,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IAErE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,SAAS,CAAC,oEAAoE,CAAC,CAAC;IAC1F,CAAC;IAED,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACpC,IAAI,GAAG,EAAE,CAAC;QACN,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;YAC7B,MAAM,SAAS,CAAC,GAAG,KAAK,UAAU,MAAM,CAAC,MAAM,mDAAmD,CAAC,CAAC;QACxG,CAAC;QACD,OAAO,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9B,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC;IACjD,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC,IAAI,CAAC;IAE7B,MAAM,KAAK,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;IAClC,MAAM,IAAI,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IACtE,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IAC3B,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;QAC9B,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QAC9C,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAC9D,2DAA2D;QAC3D,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpF,CAAC,CAAC,CAAC;IACH,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACjD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,SAAS,CAAC,uBAAuB,KAAK,wDAAwD,CAAC,CAAC;IAC1G,CAAC;IACD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClD,MAAM,SAAS,CAAC,IAAI,KAAK,6BAA6B,KAAK,EAAE,CAAC,CAAC;AACnE,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,WAAoB;IACrD,MAAM,CAAC,GAAG,MAAM,UAAU,CAAC,WAAW,CAAC,CAAC;IACxC,OAAO,CAAC,EAAE,IAAI,IAAI,IAAI,CAAC;AAC3B,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,wBAAwB,CAAC,WAAoB;IACxD,MAAM,OAAO,GAAG,WAAW,IAAI,iBAAiB,EAAE,CAAC;IACnD,MAAM,MAAM,GAAG;;;;;sCAKmB,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC;;;;;;;;;CAShE,CAAC;IACE,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,CAAC;QAC3C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,CAAC;YACxD,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;gBAAE,OAAO,QAAQ,CAAC,IAAI,EAAE,EAAE,CAAa,CAAC;QACxE,CAAC;IACL,CAAC;IAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;IAC9B,OAAO,IAAI,CAAC;AAChB,CAAC;AAED,yDAAyD;AAEzD;;;;;;GAMG;AACH,MAAM,WAAW,GAA2B;IACxC,WAAW,EAAE,MAAM;CACtB,CAAC;AAUF,SAAS,WAAW,CAAI,CAAa,EAAE,EAAU;IAC7C,OAAO,OAAO,CAAC,IAAI,CAAC;QAChB,CAAC;QACD,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE;YAC7B,MAAM,CAAC,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC7D,CAAC,CAAC,KAAK,EAAE,CAAC;QACd,CAAC,CAAC;KACL,CAAC,CAAC;AACP,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,WAAoB;IACtD,MAAM,OAAO,GAAG,WAAW,IAAI,iBAAiB,EAAE,CAAC;IACnD,MAAM,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC;SACrC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACzE,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAA;;oCAEO,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gBAgC/C,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkDrB,CAAC;IACE,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC;QAC/D,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;QACpD,IAAI,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QACpB,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QACjE,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC,CAAG,mCAAmC;QACzE,MAAM,OAAO,GAAG,KAAK,CAAC,EAAE,CAAC,CAAC;QAC1B,MAAM,IAAI,GAAG,WAAW,CAAC,QAAQ,CAAC,OAAmB,CAAC,CAAC,CAAC,CAAC,OAAmB,CAAC,CAAC,CAAC,IAAI,CAAC;QACpF,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC;AAOD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,WAAoB;IACjD,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,WAAW,CAAC,CAAC;IAChD,IAAI,IAAI,EAAE,CAAC;QACP,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACrE,CAAC;IACD,MAAM,CAAC,GAAG,MAAM,wBAAwB,CAAC,WAAW,CAAC,CAAC;IACtD,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACpD,CAAC;AAED,qDAAqD;AAErD,MAAM,kBAAkB;IACX,UAAU,GAAG,SAAS,CAAC;IAEhC,SAAS;QACL,MAAM,CAAC,GAAG,SAAS,EAAE,CAAC;QACtB,OAAO;YACH,cAAc,EAAE,CAAC,CAAC,cAAc;YAChC,YAAY,EAAE,CAAC,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,SAAS;SAChF,CAAC;IACN,CAAC;IACD,SAAS,CAAC,MAAkC;QACxC,MAAM,MAAM,GAA2B,EAAE,CAAC;QAC1C,IAAI,MAAM,CAAC,cAAc,KAAK,SAAS;YAAE,MAAM,CAAC,cAAc,GAAG,MAAM,CAAC,cAAc,CAAC;QACvF,IAAI,MAAM,CAAC,YAAY,KAAK,SAAS,EAAE,CAAC;YACpC,MAAM,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,CAAa,CAAC;YACxD,IAAI,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAAE,MAAM,CAAC,WAAW,GAAG,CAAC,CAAC;QACxD,CAAC;QACD,SAAS,CAAC,MAAM,CAAC,CAAC;IACtB,CAAC;IACD,aAAa;QACT,OAAO,WAAW,CAAC;IACvB,CAAC;IAED,SAAS;QACL,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACzB,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;YACb,IAAI,EAAE,GAAG,CAAC,SAAS;YACnB,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,EAAK,4CAA4C;YAC9E,QAAQ,EAAE,CAAC,EAAwB,qDAAqD;YACxF,UAAU,EAAE,IAAI;SACnB,CAAC,CAAC,CAAC;IACR,CAAC;IACD,QAAQ,CAAC,EAAU;QACf,MAAM,CAAC,GAAG,QAAQ,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,CAAa,CAAC;QACzD,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QAC1C,OAAO;YACH,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,SAAS;YAClC,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,IAAI;SAC/D,CAAC;IACN,CAAC;IACD,KAAK,CAAC,WAAW,CAAC,WAAoB;QAClC,MAAM,CAAC,GAAG,MAAM,cAAc,CAAC,WAAW,CAAC,CAAC;QAC5C,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,YAAY;QACd,OAAO,gBAAgB,CAAC,uBAAuB,CAAC,CAAC;IACrD,CAAC;IACD,KAAK,CAAC,SAAS,CAAC,WAAoB;QAChC,MAAM,OAAO,GAAG,WAAW,IAAI,iBAAiB,EAAE,CAAC;QACnD,OAAO,gBAAgB,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IACD,KAAK,CAAC,UAAU,CAAC,WAAoB,EAAE,OAAoB,EAAE;QACzD,MAAM,OAAO,GAAG,WAAW,IAAI,iBAAiB,EAAE,CAAC;QACnD,OAAO,cAAc,CAAC,OAAO,EAAE,IAAI,EAAE,uBAAuB,CAAC,CAAC;IAClE,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,IAAsB;QAC/B,eAAe,CAAC,IAAI,CAAC,CAAC;QACtB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChD,OAAO,cAAc,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC/D,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAAsB;QAC9B,eAAe,CAAC,IAAI,CAAC,CAAC;QACtB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,iBAAiB,EAAE,CAAC;QACpD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChD,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QACxE,MAAM,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAClC,MAAM,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QACxC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;IAClE,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,QAAmB,EAAE,OAA2B,EAAE;QACnE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChD,OAAO,kBAAkB,CAAC,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9F,CAAC;IACD,KAAK,CAAC,aAAa,CAAC,QAAmB,EAAE,OAA2B,EAAE;QAClE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,iBAAiB,EAAE,CAAC;QACpD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAChD,MAAM,KAAK,GAAG,MAAM,kBAAkB,CAAC,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACvG,MAAM,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAClC,MAAM,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QACxC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;IAClE,CAAC;IAEO,KAAK,CAAC,WAAW,CAAC,KAAc;QACpC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACtB,MAAM,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,CAAa,CAAC;YAC5D,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,KAAK,YAAY,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC/G,OAAO,CAAC,CAAC;QACb,CAAC;QACD,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;QACxB,IAAI,GAAG,CAAC,WAAW;YAAE,OAAO,GAAG,CAAC,WAAW,CAAC;QAC5C,MAAM,QAAQ,GAAG,MAAM,cAAc,EAAE,CAAC;QACxC,OAAO,QAAQ,IAAI,EAAE,CAAC;IAC1B,CAAC;CACJ;AAED,0DAA0D;AAC1D,MAAM,CAAC,MAAM,cAAc,GAAiB,IAAI,kBAAkB,EAAE,CAAC;AAErE,sDAAsD;AAEtD;;GAEG;AACH,SAAS,MAAM,CAAC,IAAkB;IAC9B,MAAM,GAAG,GAAqB,EAAE,GAAG,IAAI,EAAE,CAAC;IAC1C,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QACrD,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IACD,OAAQ,GAAW,CAAC,IAAI,CAAC;IACzB,OAAO,GAAG,CAAC;AACf,CAAC;AACD,SAAS,SAAS,CAAC,OAAuB,EAAE;IACxC,MAAM,GAAG,GAAuB,EAAE,GAAG,IAAI,EAAE,CAAC;IAC5C,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,GAAG,CAAC,KAAK,KAAK,SAAS;QAAE,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtF,OAAQ,GAAW,CAAC,IAAI,CAAC;IACzB,OAAO,GAAG,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,OAAqB;IAC9C,OAAO,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;AAClD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,OAAqB;IAC7C,MAAM,CAAC,GAAG,MAAM,cAAc,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IACtD,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,QAAmB,EAAE,IAAe,EAAE,UAAmB,EAAE,KAAc;IAC1G,OAAO,cAAc,CAAC,cAAc,CAAC,QAAQ,EAAE;QAC3C,KAAK,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;QACpD,UAAU,EAAE,KAAK;KACpB,CAAC,CAAC;AACP,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAmB,EAAE,OAAwB;IAC7E,MAAM,CAAC,GAAG,MAAM,cAAc,CAAC,aAAa,CAAC,QAAQ,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3E,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;AAC9B,CAAC;AAED,sEAAsE;AAEtE,KAAK,UAAU,YAAY,CAAC,OAAe,EAAE,IAAmJ;IAC5L,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,SAAS,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;IACvE,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAChB;;;sEAG8D;QAC9D,IAAI,MAAM,CAAC,YAAY,IAAI,IAAI,CAAC,GAAG;YAAE,IAAI,CAAC,GAAG,CAAC,mBAAmB,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC;QACxF,OAAO;IACX,CAAC;IACD,IAAI,IAAI,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,YAAY,OAAO,iBAAiB,MAAM,CAAC,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,SAAS,kDAAkD,CAAC,CAAC;IACrL,CAAC;IACD,IAAI,IAAI,CAAC,GAAG;QAAE,IAAI,CAAC,GAAG,CAAC,mBAAmB,OAAO,aAAa,MAAM,CAAC,UAAU,gBAAgB,SAAS,EAAE,CAAC,CAAC;IAC5G,MAAM,cAAc,CAAC,OAAO,EAAE;QAC1B,SAAS,EAAE,IAAI,CAAC,aAAa;QAC7B,UAAU,EAAE,IAAI,CAAC,cAAc;QAC/B,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,GAAG,EAAE,IAAI,CAAC,GAAG;KAChB,EAAE,uBAAuB,CAAC,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,WAAW,CAAC,WAAmB,EAAE,OAAe,EAAE,IAAc;IAC3E,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,SAAS,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACnE,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAE/C,MAAM,MAAM,GAAG;;;;;;;2CAOwB,MAAM;;SAExC,SAAS;;;;sBAII,KAAK,CAAC,KAAK;;;;;;;;;;;;+BAYF,KAAK,CAAC,IAAI;;4CAEG,KAAK,CAAC,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCA8BrB,OAAO,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC;;;;;;;;;;;;;;;;gDAgBb,MAAM;;;;;;;;;;;;;;;;;;;;;;;CAuBrD,CAAC;IACE,IAAI,CAAC;QACD,MAAM,gBAAgB,CAAC,MAAM,CAAC,CAAC;IACnC,CAAC;YAAS,CAAC;QACP,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACzD,CAAC;AACL,CAAC","sourcesContent":["/**\n * Brother Label Printer API\n * Programmatic interface for printing labels on Brother P-touch printers.\n *\n * Implements the unified LabelPrinter interface from @bobfrankston/label-core.\n * All printer-agnostic logic (rendering, segments, clipboard, CLI primitives,\n * online-wait) lives in label-core. This file holds the Brother-specific bits:\n * TAPE_SIZES catalog, MEDIA_OPTIONS (CustomMediaSize* names), XPS PrintTicket\n * builder with the Brother namespace, and detectTapeSize.\n */\n\nimport * as fs from \"fs\";\nimport * as os from \"os\";\nimport * as path from \"path\";\nimport {\n validateContent,\n resolveContent,\n renderSegments as coreRenderSegments,\n listPrinters as coreListPrinters,\n getPrinterStatus,\n waitOnline as coreWaitOnline,\n runScriptOrThrow,\n readJsonConfig,\n mergeJsonConfig,\n} from \"@bobfrankston/label-core\";\nimport type {\n LabelPrinter,\n PrintOptions as CorePrintOptions,\n SegmentOptions as CoreSegmentOptions,\n PrintResult as CorePrintResult,\n PrinterStatus,\n PrinterInfo as CorePrinterInfo,\n PrinterConfig as CorePrinterConfig,\n MediaInfo,\n Segment,\n WaitOptions,\n} from \"@bobfrankston/label-core\";\n\n/* ----- Brother-specific types ----- */\n\nexport type TapeSize = 6 | 9 | 12 | 18 | 24;\nexport type Orientation = \"landscape\" | \"portrait\";\n\n/** Backward-compat: brother-label's config kept the `defaultTape` name. */\nexport interface PrinterConfig {\n defaultTape?: TapeSize;\n defaultPrinter?: string;\n}\n\nexport interface PrinterInfo {\n name: string;\n}\n\n/** Brother-specific PrintOptions: same as core but with tape alias for media. */\nexport interface PrintOptions extends Omit<CorePrintOptions, \"media\"> {\n tape?: TapeSize; /** Tape size in mm — alias for core.media */\n orientation?: Orientation; /** Reserved (Brother PT prints landscape only) */\n length?: number; /** Reserved — explicit length in mm */\n}\n\nexport interface SegmentOptions extends Omit<CoreSegmentOptions, \"media\"> {\n tape?: TapeSize;\n}\n\nexport interface PrintResult {\n image: Buffer;\n}\n\nexport type { Segment };\n\n/* ----- Constants ----- */\n\nconst CONFIG_PATH = path.join(os.homedir(), \".brother-label.json\");\nconst DEFAULT_PRINTER = \"Brother PT-P710BT\";\nconst DEFAULT_PRINTER_PATTERN = /brother|^pt-|^ql-/i;\nconst PRINT_DPI = 300;\nconst VALID_TAPES: TapeSize[] = [6, 9, 12, 18, 24];\n\n/** Tape sizes: width in pixels @ 300 DPI = continuous direction; height = perpendicular. */\nconst TAPE_SIZES: Record<TapeSize, { width: number; height: number }> = {\n 6: { width: 1137, height: 56 },\n 9: { width: 1137, height: 84 },\n 12: { width: 1137, height: 113 },\n 18: { width: 1137, height: 169 },\n 24: { width: 1137, height: 213 },\n};\n\n/** Brother CustomMediaSize names + tape widths in microns (XPS PrintTicket). */\nconst MEDIA_OPTIONS: Record<TapeSize, { name: string; width: number }> = {\n 6: { name: \"CustomMediaSize257\", width: 5900 },\n 9: { name: \"CustomMediaSize258\", width: 9000 },\n 12: { name: \"CustomMediaSize259\", width: 11900 },\n 18: { name: \"CustomMediaSize260\", width: 18100 },\n 24: { name: \"CustomMediaSize261\", width: 24000 },\n};\n\n/* ----- Config (backward-compat — note \"defaultTape\" stays in JSON) ----- */\n\nexport function getConfig(): PrinterConfig {\n const raw: any = readJsonConfig(CONFIG_PATH);\n if (typeof raw.defaultTape === \"string\") {\n raw.defaultTape = parseInt(raw.defaultTape.replace(\"mm\", \"\"), 10) as TapeSize;\n }\n return raw;\n}\n\nexport function setConfig(config: Partial<PrinterConfig>): void {\n mergeJsonConfig(CONFIG_PATH, config);\n}\n\nexport function getConfigPath(): string {\n return CONFIG_PATH;\n}\n\n/**\n * The queue used when a caller names no printer: the configured default, else\n * the built-in fallback. Exported so callers can NAME the queue they are about\n * to use instead of saying \"the default\".\n * Claude Code 2026-08-24.\n */\nexport function getDefaultPrinter(): string {\n return getConfig().defaultPrinter ?? DEFAULT_PRINTER;\n}\n\n/* ----- Printer listing (backward-compat shape: { name }[]) ----- */\n\nexport async function listPrinters(): Promise<PrinterInfo[]> {\n const ps = await coreListPrinters(DEFAULT_PRINTER_PATTERN);\n return ps.map(p => ({ name: p.name }));\n}\n\n/** Mark an error as user-actionable so cli.ts prints it inline instead of\n * dumping to a log file. The cli's catch block duck-types on `isUserError`. */\nfunction userError(msg: string): Error {\n const e = new Error(msg);\n (e as any).isUserError = true; // No typed shape for this; cli.ts duck-types the flag\n return e;\n}\n\n/**\n * Resolve a user-supplied printer value to an installed Windows queue name. Accepts:\n * #N 1-based index into the list shown by `brother-label list`\n * <exact> Full queue name (case-sensitive match shortcut)\n * <substr> Case-insensitive substring against name or port (e.g. \"ql820\",\n * \"172.20.1.165\", \"BRW4CD577B59B6A\"). Errors if ambiguous.\n *\n * Claude Code 2026-08-24: moved here from cli.ts so other apps that let the user\n * name a printer (iplabels) resolve names the same way rather than each rolling\n * its own matching.\n */\nexport async function resolvePrinter(value: string): Promise<string> {\n const all = await coreListPrinters(DEFAULT_PRINTER_PATTERN);\n const sorted = [...all].sort((a, b) => a.name.localeCompare(b.name));\n\n if (sorted.length === 0) {\n throw userError(`No Brother printers installed. Run 'brother-label list' to verify.`);\n }\n\n const idx = value.match(/^#(\\d+)$/);\n if (idx) {\n const n = parseInt(idx[1], 10);\n if (n < 1 || n > sorted.length) {\n throw userError(`${value}: only ${sorted.length} printer(s) installed (use 'brother-label list').`);\n }\n return sorted[n - 1].name;\n }\n\n const exact = sorted.find(p => p.name === value);\n if (exact) return exact.name;\n\n const lower = value.toLowerCase();\n const norm = (s: string) => s.toLowerCase().replace(/[^a-z0-9]/g, \"\");\n const target = norm(value);\n const matches = sorted.filter(p => {\n const name = p.name.toLowerCase();\n const port = (p.portName || \"\").toLowerCase();\n if (name.includes(lower) || port.includes(lower)) return true;\n // Normalized fallback: \"ql820\" matches \"Brother QL-820NWB\"\n return norm(p.name).includes(target) || norm(p.portName || \"\").includes(target);\n });\n if (matches.length === 1) return matches[0].name;\n if (matches.length === 0) {\n throw userError(`No printer matches '${value}'. Run 'brother-label list' to see available printers.`);\n }\n const names = matches.map(p => p.name).join(\", \");\n throw userError(`'${value}' is ambiguous — matches: ${names}`);\n}\n\n/**\n * Auto-detect tape size. Tries the live printer status first, then the\n * Windows driver's default print ticket (see detectTape for details).\n */\nexport async function detectTapeSize(printerName?: string): Promise<TapeSize> {\n const d = await detectTape(printerName);\n return d?.tape ?? null;\n}\n\n/**\n * Read the tape size the Windows driver's default PrintTicket claims.\n * Brother's driver embeds a CustomMediaSize name in the ticket XML — but\n * this is just the driver's saved preference, not what's physically loaded.\n */\nasync function detectTapeSizeFromDriver(printerName?: string): Promise<TapeSize> {\n const printer = printerName ?? getDefaultPrinter();\n const script = `\n$ErrorActionPreference = 'SilentlyContinue'\nAdd-Type -AssemblyName System.Printing | Out-Null\ntry {\n $server = New-Object System.Printing.LocalPrintServer\n $queue = $server.GetPrintQueue('${printer.replace(/'/g, \"''\")}')\n $ticket = $queue.DefaultPrintTicket\n $stream = $ticket.GetXmlStream()\n $reader = New-Object System.IO.StreamReader($stream)\n Write-Output $reader.ReadToEnd()\n $reader.Close()\n} catch {\n Write-Output \"ERROR\"\n}\n`;\n try {\n const out = await runScriptOrThrow(script);\n for (const [size, media] of Object.entries(MEDIA_OPTIONS)) {\n if (out.includes(media.name)) return parseInt(size, 10) as TapeSize;\n }\n } catch { /* fall through */ }\n return null;\n}\n\n/* ----- Live tape status (raw P-touch protocol) ----- */\n\n/**\n * USB product IDs for PT models whose queue sits on a spooler USB port.\n * The spooler is write-only, so the live status request needs the usbprint\n * device interface opened directly (CreateFile on the \\\\?\\USB#... path).\n * Bluetooth models (PT-D610BT) need no entry — their queue's PortName is a\n * COM port, which is bidirectional as-is.\n */\nconst PT_USB_PIDS: Record<string, string> = {\n \"PT-P710BT\": \"20AF\",\n};\n\n/** Parsed ESC i S status reply (32 bytes; byte 10 = cassette width in mm). */\nexport interface TapeStatus {\n widthMm: number; /** 0 = no cassette loaded */\n tape: TapeSize | null; /** widthMm when it maps to a supported size */\n mediaType: number; /** 0x01 laminated, 0x03 non-laminated, 0x11 heat-shrink */\n raw: string; /** all 32 status bytes as hex */\n}\n\nfunction withTimeout<T>(p: Promise<T>, ms: number): Promise<T> {\n return Promise.race([\n p,\n new Promise<never>((_, reject) => {\n const t = setTimeout(() => reject(new Error(\"timeout\")), ms);\n t.unref();\n }),\n ]);\n}\n\n/**\n * Query the printer itself for the loaded cassette via the raw P-touch\n * status request (100×0x00 invalidate, ESC @, ESC i S → 32-byte reply).\n * Returns null when the printer is off, busy printing, or on an\n * unsupported port. Never throws.\n */\nexport async function queryTapeStatus(printerName?: string): Promise<TapeStatus | null> {\n const printer = printerName ?? getDefaultPrinter();\n const usbPid = Object.entries(PT_USB_PIDS)\n .find(([model]) => printer.toUpperCase().includes(model))?.[1] ?? \"\";\n const script = String.raw`\n$ErrorActionPreference = 'Stop'\ntry { $port = (Get-Printer -Name '${printer.replace(/'/g, \"''\")}').PortName }\ncatch { Write-Output 'FAIL no such queue'; exit 0 }\n\n$req = New-Object byte[] 105\n$req[100] = 0x1B; $req[101] = 0x40 # ESC @ initialize\n$req[102] = 0x1B; $req[103] = 0x69; $req[104] = 0x53 # ESC i S status request\n\nif ($port -match '^COM\\d+') {\n try {\n $sp = New-Object System.IO.Ports.SerialPort ($port.TrimEnd(':'))\n $sp.ReadTimeout = 3000; $sp.WriteTimeout = 2000\n $sp.Open()\n try {\n $sp.Write($req, 0, $req.Length)\n $buf = New-Object byte[] 32\n $total = 0\n while ($total -lt 32) {\n $n = $sp.Read($buf, $total, 32 - $total)\n if ($n -le 0) { break }\n $total += $n\n }\n if ($total -lt 32) { Write-Output 'FAIL short serial read'; exit 0 }\n Write-Output ('OK ' + (($buf | ForEach-Object { $_.ToString('X2') }) -join ' '))\n } finally { $sp.Close() }\n } catch { Write-Output \"FAIL $($_.Exception.Message)\" }\n exit 0\n}\nif ($port -notmatch '^USB') { Write-Output \"FAIL unsupported port $port\"; exit 0 }\n\n# usbprint device interfaces live under this class GUID; stale (unplugged)\n# entries persist in the registry, so try each match until one opens.\n$classKey = 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\DeviceClasses\\{28d78fad-5a12-11d1-ae5b-0000f803a8c2}'\n$pidFilter = '${usbPid}'\n$paths = @(Get-ChildItem $classKey | ForEach-Object {\n $leaf = Split-Path $_.Name -Leaf\n if ($leaf -match 'VID_04F9' -and ($pidFilter -eq '' -or $leaf -match \"PID_$pidFilter\")) {\n $leaf -replace '^##\\?#', '\\\\?\\'\n }\n})\nif ($paths.Count -eq 0) { Write-Output 'FAIL no usbprint interface'; exit 0 }\n\nAdd-Type -TypeDefinition @'\nusing System;\nusing System.IO;\nusing System.Runtime.InteropServices;\nusing System.Threading.Tasks;\nusing Microsoft.Win32.SafeHandles;\n\npublic static class BrotherRawUsb {\n [DllImport(\"kernel32.dll\", CharSet = CharSet.Unicode, SetLastError = true)]\n static extern SafeFileHandle CreateFile(string fileName, uint access, uint share,\n IntPtr security, uint creation, uint flags, IntPtr template);\n\n public static byte[] QueryStatus(string devPath, byte[] request) {\n SafeFileHandle h = CreateFile(devPath, 0xC0000000u /*GENERIC_RW*/, 0, IntPtr.Zero,\n 3 /*OPEN_EXISTING*/, 0, IntPtr.Zero);\n if (h.IsInvalid) throw new IOException(\"open failed: \" + Marshal.GetLastWin32Error());\n using (FileStream fs = new FileStream(h, FileAccess.ReadWrite)) {\n fs.Write(request, 0, request.Length);\n fs.Flush();\n byte[] buf = new byte[32];\n int total = 0;\n while (total < 32) {\n Task<int> t = fs.ReadAsync(buf, total, 32 - total);\n if (!t.Wait(3000)) throw new IOException(\"status read timeout\");\n if (t.Result <= 0) throw new IOException(\"read returned \" + t.Result);\n total += t.Result;\n }\n return buf;\n }\n }\n}\n'@\n\nforeach ($p in $paths) {\n try {\n $buf = [BrotherRawUsb]::QueryStatus($p, $req)\n Write-Output ('OK ' + (($buf | ForEach-Object { $_.ToString('X2') }) -join ' '))\n exit 0\n } catch { }\n}\nWrite-Output 'FAIL no device answered'\n`;\n try {\n const out = await withTimeout(runScriptOrThrow(script), 10000);\n const m = out.match(/^OK ((?:[0-9A-F]{2} ?){32})/m);\n if (!m) return null;\n const bytes = m[1].trim().split(/\\s+/).map(h => parseInt(h, 16));\n if (bytes[0] !== 0x80) return null; /* print-head mark: sanity check */\n const widthMm = bytes[10];\n const tape = VALID_TAPES.includes(widthMm as TapeSize) ? widthMm as TapeSize : null;\n return { widthMm, tape, mediaType: bytes[11], raw: m[1].trim() };\n } catch {\n return null;\n }\n}\n\nexport interface DetectedTape {\n tape: TapeSize;\n source: \"printer\" | \"driver\";\n}\n\n/**\n * Detect the loaded tape: live printer status first (authoritative — reads\n * the cassette sensor), falling back to the Windows driver's default print\n * ticket, which is only the driver's saved preference and may be stale.\n * A live reply of \"no cassette\" returns null rather than falling back.\n */\nexport async function detectTape(printerName?: string): Promise<DetectedTape | null> {\n const live = await queryTapeStatus(printerName);\n if (live) {\n return live.tape ? { tape: live.tape, source: \"printer\" } : null;\n }\n const t = await detectTapeSizeFromDriver(printerName);\n return t ? { tape: t, source: \"driver\" } : null;\n}\n\n/* ----- Brother LabelPrinter implementation ----- */\n\nclass BrotherPrinterImpl implements LabelPrinter {\n readonly driverName = \"brother\";\n\n getConfig(): CorePrinterConfig {\n const c = getConfig();\n return {\n defaultPrinter: c.defaultPrinter,\n defaultMedia: c.defaultTape !== undefined ? String(c.defaultTape) : undefined,\n };\n }\n setConfig(config: Partial<CorePrinterConfig>): void {\n const update: Partial<PrinterConfig> = {};\n if (config.defaultPrinter !== undefined) update.defaultPrinter = config.defaultPrinter;\n if (config.defaultMedia !== undefined) {\n const t = parseInt(config.defaultMedia, 10) as TapeSize;\n if (VALID_TAPES.includes(t)) update.defaultTape = t;\n }\n setConfig(update);\n }\n getConfigPath(): string {\n return CONFIG_PATH;\n }\n\n listMedia(): MediaInfo[] {\n return VALID_TAPES.map(t => ({\n id: String(t),\n name: `${t}mm tape`,\n widthPx: TAPE_SIZES[t].height, /** \"media width\" (perpendicular to feed) */\n lengthPx: 0, /** continuous tape — length determined by content */\n continuous: true,\n }));\n }\n getMedia(id: string): MediaInfo {\n const t = parseInt(id.replace(\"mm\", \"\"), 10) as TapeSize;\n if (!VALID_TAPES.includes(t)) return null;\n return {\n id: String(t), name: `${t}mm tape`,\n widthPx: TAPE_SIZES[t].height, lengthPx: 0, continuous: true,\n };\n }\n async detectMedia(printerName?: string): Promise<MediaInfo> {\n const t = await detectTapeSize(printerName);\n return t ? this.getMedia(String(t)) : null;\n }\n\n async listPrinters(): Promise<CorePrinterInfo[]> {\n return coreListPrinters(DEFAULT_PRINTER_PATTERN);\n }\n async getStatus(printerName?: string): Promise<PrinterStatus> {\n const printer = printerName ?? getDefaultPrinter();\n return getPrinterStatus(printer);\n }\n async waitOnline(printerName?: string, opts: WaitOptions = {}): Promise<PrinterStatus> {\n const printer = printerName ?? getDefaultPrinter();\n return coreWaitOnline(printer, opts, DEFAULT_PRINTER_PATTERN);\n }\n\n async render(opts: CorePrintOptions): Promise<Buffer> {\n validateContent(opts);\n const tape = await this.resolveTape(opts.media);\n return resolveContent(opts, TAPE_SIZES[tape].height, tape);\n }\n\n async print(opts: CorePrintOptions): Promise<CorePrintResult> {\n validateContent(opts);\n const printer = opts.printer ?? getDefaultPrinter();\n const tape = await this.resolveTape(opts.media);\n const image = await resolveContent(opts, TAPE_SIZES[tape].height, tape);\n await ensureOnline(printer, opts);\n await printBuffer(image, printer, tape);\n return { image, media: this.getMedia(String(tape)), printer };\n }\n\n async renderSegments(segments: Segment[], opts: CoreSegmentOptions = {}): Promise<Buffer> {\n const tape = await this.resolveTape(opts.media);\n return coreRenderSegments(segments, TAPE_SIZES[tape].height, opts.textHeight, opts.space);\n }\n async printSegments(segments: Segment[], opts: CoreSegmentOptions = {}): Promise<CorePrintResult> {\n const printer = opts.printer ?? getDefaultPrinter();\n const tape = await this.resolveTape(opts.media);\n const image = await coreRenderSegments(segments, TAPE_SIZES[tape].height, opts.textHeight, opts.space);\n await ensureOnline(printer, opts);\n await printBuffer(image, printer, tape);\n return { image, media: this.getMedia(String(tape)), printer };\n }\n\n private async resolveTape(media?: string): Promise<TapeSize> {\n if (media !== undefined) {\n const t = parseInt(media.replace(\"mm\", \"\"), 10) as TapeSize;\n if (!VALID_TAPES.includes(t)) throw new Error(`Invalid tape size: ${media}. Valid: ${VALID_TAPES.join(\", \")}`);\n return t;\n }\n const cfg = getConfig();\n if (cfg.defaultTape) return cfg.defaultTape;\n const detected = await detectTapeSize();\n return detected ?? 24;\n }\n}\n\n/** Public Brother singleton (implements LabelPrinter). */\nexport const brotherPrinter: LabelPrinter = new BrotherPrinterImpl();\n\n/* ----- Backward-compatible function exports ----- */\n\n/**\n * Translate brother-label PrintOptions (with tape) to core (with media).\n */\nfunction toCore(opts: PrintOptions): CorePrintOptions {\n const out: CorePrintOptions = { ...opts };\n if (opts.tape !== undefined && out.media === undefined) {\n out.media = String(opts.tape);\n }\n delete (out as any).tape;\n return out;\n}\nfunction toCoreSeg(opts: SegmentOptions = {}): CoreSegmentOptions {\n const out: CoreSegmentOptions = { ...opts };\n if (opts.tape !== undefined && out.media === undefined) out.media = String(opts.tape);\n delete (out as any).tape;\n return out;\n}\n\nexport async function render(options: PrintOptions): Promise<Buffer> {\n return brotherPrinter.render(toCore(options));\n}\n\nexport async function print(options: PrintOptions): Promise<PrintResult> {\n const r = await brotherPrinter.print(toCore(options));\n return { image: r.image };\n}\n\nexport async function renderSegments(segments: Segment[], tape?: TapeSize, textHeight?: string, space?: string): Promise<Buffer> {\n return brotherPrinter.renderSegments(segments, {\n media: tape !== undefined ? String(tape) : undefined,\n textHeight, space,\n });\n}\n\nexport async function printSegments(segments: Segment[], options?: SegmentOptions): Promise<PrintResult> {\n const r = await brotherPrinter.printSegments(segments, toCoreSeg(options));\n return { image: r.image };\n}\n\n/* ----- Brother XPS print path (kept here, Brother-specific) ----- */\n\nasync function ensureOnline(printer: string, opts: { wait?: boolean; waitTimeoutMs?: number; waitIntervalMs?: number; onWaiting?: CorePrintOptions[\"onWaiting\"]; log?: CorePrintOptions[\"log\"] }): Promise<void> {\n const status = await getPrinterStatus(printer);\n const queueNote = status.queueWarning ? \" \" + status.queueWarning : \"\";\n if (status.online) {\n /* A backed-up queue matters even when the printer is reachable: the\n * label about to be submitted lands behind whatever is already wedged,\n * so nothing comes out of the printer. The offline paths below carry\n * the same note in their own message, so only log it here. */\n if (status.queueWarning && opts.log) opts.log(`[brother-label] ${status.queueWarning}`);\n return;\n }\n if (opts.wait === false) {\n throw new Error(`Printer \"${printer}\" is offline (${status.statusText}${status.error ? \", \" + status.error : \"\"}).${queueNote} Use wait:true to wait, or pick another printer.`);\n }\n if (opts.log) opts.log(`[brother-label] ${printer} offline (${status.statusText}); waiting...${queueNote}`);\n await coreWaitOnline(printer, {\n timeoutMs: opts.waitTimeoutMs,\n intervalMs: opts.waitIntervalMs,\n onWaiting: opts.onWaiting,\n log: opts.log,\n }, DEFAULT_PRINTER_PATTERN);\n}\n\n/**\n * Print a single image buffer using the Brother XPS PrintTicket path.\n * Builds an XPS document with a custom CustomMediaSize for the tape, then\n * sends it to the print queue.\n */\nasync function printBuffer(imageBuffer: Buffer, printer: string, tape: TapeSize): Promise<void> {\n const media = MEDIA_OPTIONS[tape];\n const tempPath = path.join(os.tmpdir(), `label-${Date.now()}.png`);\n fs.writeFileSync(tempPath, imageBuffer);\n const psPath = tempPath.replace(/\\\\/g, \"\\\\\\\\\");\n\n const script = `\n$ErrorActionPreference = 'Stop'\nAdd-Type -AssemblyName System.Drawing\nAdd-Type -AssemblyName System.Printing\nAdd-Type -AssemblyName ReachFramework\nAdd-Type -AssemblyName PresentationCore\n\n$img = [System.Drawing.Image]::FromFile('${psPath}')\n\n$DPI = ${PRINT_DPI}\n$MICRONS_PER_INCH = 25400\n\n$labelLengthMicrons = [int]($img.Width / $DPI * $MICRONS_PER_INCH) + 3000\n$tapeWidthMicrons = ${media.width}\n\n$imgWidthWpf = $img.Width / $DPI * 96\n$imgHeightWpf = $img.Height / $DPI * 96\n\n$ticketXml = @\"\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<psf:PrintTicket xmlns:psf=\"http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework\"\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" version=\"1\"\n xmlns:psk=\"http://schemas.microsoft.com/windows/2003/08/printing/printschemakeywords\"\n xmlns:ns0001=\"http://schemas.brother.info/mfc/printing/2006/11/printschemakeywords\">\n <psf:Feature name=\"psk:PageMediaSize\">\n <psf:Option name=\"ns0001:${media.name}\">\n <psf:ScoredProperty name=\"psk:MediaSizeWidth\">\n <psf:Value xsi:type=\"xsd:integer\">${media.width}</psf:Value>\n </psf:ScoredProperty>\n <psf:ScoredProperty name=\"psk:MediaSizeHeight\">\n <psf:ParameterRef name=\"psk:PageMediaSizeMediaSizeHeight\" />\n </psf:ScoredProperty>\n </psf:Option>\n </psf:Feature>\n <psf:ParameterInit name=\"psk:PageMediaSizeMediaSizeHeight\">\n <psf:Value xsi:type=\"xsd:integer\">$labelLengthMicrons</psf:Value>\n </psf:ParameterInit>\n <psf:Feature name=\"psk:PageOrientation\">\n <psf:Option name=\"psk:Landscape\" />\n </psf:Feature>\n <psf:Feature name=\"ns0001:PageRollFeedToEndOfSheet\">\n <psf:Option name=\"ns0001:FeedToImageEdge\" />\n </psf:Feature>\n <psf:Feature name=\"psk:PageMediaType\">\n <psf:Option name=\"psk:Label\" />\n </psf:Feature>\n <psf:Feature name=\"ns0001:JobRollCutAtEndOfJob\">\n <psf:Option name=\"ns0001:Cut\" />\n </psf:Feature>\n</psf:PrintTicket>\n\"@\n\n$xmlBytes = [System.Text.Encoding]::UTF8.GetBytes($ticketXml)\n$memStream = New-Object System.IO.MemoryStream(,$xmlBytes)\n$ticket = New-Object System.Printing.PrintTicket($memStream)\n\n$server = New-Object System.Printing.LocalPrintServer\n$queue = $server.GetPrintQueue('${printer.replace(/'/g, \"''\")}')\n\n$xpsPath = [System.IO.Path]::GetTempFileName() + \".xps\"\n\n$pageWidth = $labelLengthMicrons / $MICRONS_PER_INCH * 96\n$pageHeight = $tapeWidthMicrons / $MICRONS_PER_INCH * 96\n\n$package = [System.IO.Packaging.Package]::Open($xpsPath, [System.IO.FileMode]::Create)\n$xpsDoc = New-Object System.Windows.Xps.Packaging.XpsDocument($package)\n$writer = [System.Windows.Xps.Packaging.XpsDocument]::CreateXpsDocumentWriter($xpsDoc)\n\n$visual = New-Object System.Windows.Media.DrawingVisual\n$dc = $visual.RenderOpen()\n\n$bitmapImg = New-Object System.Windows.Media.Imaging.BitmapImage\n$bitmapImg.BeginInit()\n$bitmapImg.UriSource = New-Object System.Uri('${psPath}')\n$bitmapImg.EndInit()\n\n$yOffset = ($pageHeight - $imgHeightWpf) / 2\n$xOffset = 2 / 25.4 * 96\n$rect = New-Object System.Windows.Rect($xOffset, $yOffset, $imgWidthWpf, $imgHeightWpf)\n$dc.DrawImage($bitmapImg, $rect)\n$dc.Close()\n\n$writer.Write($visual, $ticket)\n\n$xpsDoc.Close()\n$package.Close()\n$img.Dispose()\n\n$xpsDocForPrint = New-Object System.Windows.Xps.Packaging.XpsDocument($xpsPath, [System.IO.FileAccess]::Read)\n$seq = $xpsDocForPrint.GetFixedDocumentSequence()\n\n$xpsWriter = [System.Printing.PrintQueue]::CreateXpsDocumentWriter($queue)\n$xpsWriter.Write($seq, $ticket)\n\n$xpsDocForPrint.Close()\nRemove-Item $xpsPath -ErrorAction SilentlyContinue\n`;\n try {\n await runScriptOrThrow(script);\n } finally {\n if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath);\n }\n}\n"]}
|