@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 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 print "Hello World"
16
- brother-print print "Line 1\\nLine 2" # Multi-line
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" -l "My Site" # With text label
23
- brother-print qr "any data" -o qr.png # Save to file
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 html label.html
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\nBottom" -qr "https://example.com" -text "More text"
43
- brother-print -t "Label" -q "data" -o combined.png
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 image photo.png
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 preview "Test Label" -o preview.png
64
+ brother-print "Test Label" -o preview.png
54
65
  ```
55
66
 
56
- ### Configuration
67
+ ### Commands
57
68
  ```bash
58
- brother-print config --show # Show current config
59
- brother-print config -s 12 # Set default tape size
60
- brother-print config -p "Brother PT-P710BT" # Set default printer
61
- brother-print list # List Brother printers
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;AA+CD,8EAA8E;AAC9E,MAAM,WAAW,UAAU;IACvB,OAAO,EAAE,MAAM,CAAC,CAAU,6BAA6B;IACvD,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC,CAAI,+CAA+C;IACzE,SAAS,EAAE,MAAM,CAAC,CAAQ,2DAA2D;IACrF,GAAG,EAAE,MAAM,CAAC,CAAc,iCAAiC;CAC9D;AAYD;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAsGtF;AAED,MAAM,WAAW,YAAY;IACzB,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,SAAS,GAAG,QAAQ,CAAC;CAChC;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAOnF;AAuGD,0DAA0D;AAC1D,eAAO,MAAM,cAAc,EAAE,YAAuC,CAAC;AAsBrE,wBAAsB,MAAM,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAEnE;AAED,wBAAsB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAGvE;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,CAAC,EAAE,QAAQ,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK/H;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC,CAGvG"}
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 config = getConfig();
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 config = getConfig();
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 ?? this.getConfig().defaultPrinter ?? DEFAULT_PRINTER;
354
+ const printer = printerName ?? getDefaultPrinter();
294
355
  return getPrinterStatus(printer);
295
356
  }
296
357
  async waitOnline(printerName, opts = {}) {
297
- const printer = printerName ?? this.getConfig().defaultPrinter ?? DEFAULT_PRINTER;
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 ?? this.getConfig().defaultPrinter ?? DEFAULT_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 ?? this.getConfig().defaultPrinter ?? DEFAULT_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
- if (status.online)
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 : ""}). Use wait:true to wait, or pick another printer.`);
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":"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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/**\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"]}