@bobfrankston/brother-label 1.0.12 → 1.0.13

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/api.d.ts CHANGED
@@ -26,6 +26,7 @@ export interface PrintOptions {
26
26
  basePath?: string;
27
27
  aspect?: string;
28
28
  qrLabel?: string;
29
+ textHeight?: string;
29
30
  }
30
31
  export interface PrintResult {
31
32
  image: Buffer;
package/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["api.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAYH,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAC5C,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,UAAU,CAAC;AAEnD,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,MAAM,WAAW,YAAY;IAEzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,EAAE,CAAC,EAAE,MAAM,CAAC;IAGZ,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IACxB,KAAK,EAAE,MAAM,CAAC;CACjB;AA0BD,wBAAgB,SAAS,IAAI,aAAa,CAczC;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAI9D;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAGD,wBAAsB,YAAY,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAuB3D;AAsVD,wBAAsB,MAAM,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAInE;AAED,wBAAsB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAMvE"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["api.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAYH,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAC5C,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,UAAU,CAAC;AAEnD,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,MAAM,WAAW,YAAY;IAEzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,EAAE,CAAC,EAAE,MAAM,CAAC;IAGZ,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IACxB,KAAK,EAAE,MAAM,CAAC;CACjB;AA0BD,wBAAgB,SAAS,IAAI,aAAa,CAczC;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,IAAI,CAI9D;AAED,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAGD,wBAAsB,YAAY,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAuB3D;AA8WD,wBAAsB,MAAM,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAInE;AAED,wBAAsB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAMvE"}
package/api.js CHANGED
@@ -129,10 +129,35 @@ function resolveSettings(options) {
129
129
  printer: options.printer ?? config.defaultPrinter ?? DEFAULT_PRINTER,
130
130
  };
131
131
  }
132
+ // Parse text height spec to pixels
133
+ function parseTextHeight(spec, tapeHeightPx) {
134
+ const trimmed = spec.trim().toLowerCase();
135
+ if (trimmed.endsWith("%")) {
136
+ const pct = parseFloat(trimmed.slice(0, -1));
137
+ if (isNaN(pct) || pct <= 0)
138
+ throw new Error(`Invalid text height: ${spec}`);
139
+ return Math.round(tapeHeightPx * pct / 100);
140
+ }
141
+ if (trimmed.endsWith("mm")) {
142
+ const mm = parseFloat(trimmed.slice(0, -2));
143
+ if (isNaN(mm) || mm <= 0)
144
+ throw new Error(`Invalid text height: ${spec}`);
145
+ return Math.round(mm * PRINT_DPI / 25.4);
146
+ }
147
+ if (trimmed.endsWith("in")) {
148
+ const inches = parseFloat(trimmed.slice(0, -2));
149
+ if (isNaN(inches) || inches <= 0)
150
+ throw new Error(`Invalid text height: ${spec}`);
151
+ return Math.round(inches * PRINT_DPI);
152
+ }
153
+ throw new Error(`Invalid text height: ${spec}. Use "12mm", ".5in", or "50%"`);
154
+ }
132
155
  // Render text to image buffer
133
- async function renderText(text, tape) {
156
+ async function renderText(text, tape, textHeightSpec) {
134
157
  const tapeSize = TAPE_SIZES[tape];
135
- const targetHeight = tapeSize.height;
158
+ const targetHeight = textHeightSpec
159
+ ? Math.min(parseTextHeight(textHeightSpec, tapeSize.height), tapeSize.height)
160
+ : tapeSize.height;
136
161
  const lines = text.split("\\n").join("\n");
137
162
  // Load largest font for quality
138
163
  const fontDir = path.join(path.dirname(fileURLToPath(import.meta.url)), "node_modules/@jimp/plugin-print/fonts/open-sans");
@@ -153,11 +178,12 @@ async function renderText(text, tape) {
153
178
  const finalHeight = Math.round(imgHeight * scale);
154
179
  tempImage.resize({ w: finalWidth, h: finalHeight });
155
180
  // Create final image with padding
156
- const hPadding = Math.round(targetHeight * 0.2);
181
+ const canvasHeight = tapeSize.height;
182
+ const hPadding = Math.round(canvasHeight * 0.2);
157
183
  const outputWidth = finalWidth + hPadding * 2;
158
- const image = new Jimp({ width: outputWidth, height: targetHeight, color: 0xffffffff });
184
+ const image = new Jimp({ width: outputWidth, height: canvasHeight, color: 0xffffffff });
159
185
  const xOffset = hPadding;
160
- const yOffset = Math.round((targetHeight - finalHeight) / 2);
186
+ const yOffset = Math.round((canvasHeight - finalHeight) / 2);
161
187
  image.composite(tempImage, xOffset, yOffset);
162
188
  return image.getBuffer("image/png");
163
189
  }
@@ -218,11 +244,11 @@ async function renderQr(data, tape, labelText) {
218
244
  async function renderContent(options, tape) {
219
245
  const tapeSize = TAPE_SIZES[tape];
220
246
  if (options.text !== undefined) {
221
- return renderText(options.text, tape);
247
+ return renderText(options.text, tape, options.textHeight);
222
248
  }
223
249
  if (options.textFile !== undefined) {
224
250
  const text = fs.readFileSync(options.textFile, "utf-8");
225
- return renderText(text, tape);
251
+ return renderText(text, tape, options.textHeight);
226
252
  }
227
253
  if (options.html !== undefined) {
228
254
  const { width, height } = getHtmlDimensions(tapeSize.height, options.aspect);
package/api.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.js","sourceRoot":"","sources":["api.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * Brother Label Printer API\r\n * Programmatic interface for printing labels on Brother P-touch printers\r\n */\r\n\r\nimport * as fs from \"fs\";\r\nimport * as path from \"path\";\r\nimport { fileURLToPath } from \"url\";\r\nimport * as os from \"os\";\r\nimport { renderHtmlFile, renderHtmlString, closeBrowser } from \"./render.js\";\r\nimport { Jimp, loadFont, measureText, measureTextHeight } from \"jimp\";\r\nimport { spawn } from \"child_process\";\r\nimport QRCode from \"qrcode\";\r\n\r\n// Types\r\nexport type TapeSize = 6 | 9 | 12 | 18 | 24;\r\nexport type Orientation = \"landscape\" | \"portrait\";\r\n\r\nexport interface PrinterConfig {\r\n defaultTape?: TapeSize;\r\n defaultPrinter?: string;\r\n}\r\n\r\nexport interface PrinterInfo {\r\n name: string;\r\n}\r\n\r\nexport interface PrintOptions {\r\n // Content (exactly one required)\r\n text?: string;\r\n html?: string;\r\n htmlPath?: string;\r\n textFile?: string;\r\n imagePath?: string;\r\n imageBuffer?: Buffer;\r\n qr?: string; // QR code data to encode\r\n\r\n // Settings\r\n tape?: TapeSize;\r\n printer?: string;\r\n orientation?: Orientation;\r\n length?: number; // explicit length in mm\r\n basePath?: string; // base path for inline HTML resources\r\n aspect?: string; // width:height ratio for HTML (e.g., \"3.5:2\")\r\n qrLabel?: string; // optional text label beside QR code\r\n}\r\n\r\nexport interface PrintResult {\r\n image: Buffer;\r\n}\r\n\r\n// Constants\r\nconst CONFIG_PATH = path.join(os.homedir(), \".brother-label.json\");\r\nconst DEFAULT_PRINTER = \"Brother PT-P710BT\";\r\nconst PRINT_DPI = 300; // High resolution for quality output\r\n\r\n// Tape sizes: height is printable area in pixels at 300 DPI\r\nconst TAPE_SIZES: Record<TapeSize, { width: number; height: number }> = {\r\n 6: { width: 1137, height: 56 }, // 3.79\" x 0.19\" @ 300 DPI\r\n 9: { width: 1137, height: 84 }, // 3.79\" x 0.28\" @ 300 DPI\r\n 12: { width: 1137, height: 113 }, // 3.79\" x 0.38\" @ 300 DPI\r\n 18: { width: 1137, height: 169 }, // 3.79\" x 0.56\" @ 300 DPI\r\n 24: { width: 1137, height: 213 }, // 3.79\" x 0.71\" @ 300 DPI\r\n};\r\n\r\n// Brother CustomMediaSize names and widths in microns\r\nconst MEDIA_OPTIONS: Record<TapeSize, { name: string; width: number }> = {\r\n 6: { name: \"CustomMediaSize257\", width: 5900 },\r\n 9: { name: \"CustomMediaSize258\", width: 9000 },\r\n 12: { name: \"CustomMediaSize259\", width: 11900 },\r\n 18: { name: \"CustomMediaSize260\", width: 18100 },\r\n 24: { name: \"CustomMediaSize261\", width: 24000 },\r\n};\r\n\r\n// Config functions\r\nexport function getConfig(): PrinterConfig {\r\n try {\r\n if (fs.existsSync(CONFIG_PATH)) {\r\n const raw = JSON.parse(fs.readFileSync(CONFIG_PATH, \"utf-8\"));\r\n // Handle legacy format where tape was stored as \"24mm\" string\r\n if (typeof raw.defaultTape === \"string\") {\r\n raw.defaultTape = parseInt(raw.defaultTape.replace(\"mm\", \"\"), 10) as TapeSize;\r\n }\r\n return raw;\r\n }\r\n } catch {\r\n // Ignore config errors\r\n }\r\n return {};\r\n}\r\n\r\nexport function setConfig(config: Partial<PrinterConfig>): void {\r\n const current = getConfig();\r\n const merged = { ...current, ...config };\r\n fs.writeFileSync(CONFIG_PATH, JSON.stringify(merged, null, 2));\r\n}\r\n\r\nexport function getConfigPath(): string {\r\n return CONFIG_PATH;\r\n}\r\n\r\n// Printer listing\r\nexport async function listPrinters(): Promise<PrinterInfo[]> {\r\n return new Promise((resolve, reject) => {\r\n const ps = spawn(\"powershell\", [\r\n \"-Command\",\r\n \"Get-Printer | Where-Object { $_.Name -like '*Brother*' -or $_.Name -like '*PT*' -or $_.Name -like '*QL*' } | Select-Object -ExpandProperty Name\"\r\n ], { stdio: \"pipe\" });\r\n\r\n let stdout = \"\";\r\n let stderr = \"\";\r\n ps.stdout.on(\"data\", (data) => { stdout += data.toString(); });\r\n ps.stderr.on(\"data\", (data) => { stderr += data.toString(); });\r\n\r\n ps.on(\"close\", (code) => {\r\n if (code !== 0) {\r\n reject(new Error(`Failed to list printers: ${stderr}`));\r\n return;\r\n }\r\n const printers = stdout.trim().split(\"\\n\")\r\n .filter(p => p.trim())\r\n .map(name => ({ name: name.trim() }));\r\n resolve(printers);\r\n });\r\n });\r\n}\r\n\r\n// Calculate HTML viewport dimensions from tape height and optional aspect ratio\r\nfunction getHtmlDimensions(tapeHeight: number, aspect?: string): { width?: number; height: number } {\r\n if (!aspect) {\r\n // Auto-detect width from content\r\n return { width: undefined, height: tapeHeight };\r\n }\r\n\r\n // Parse aspect ratio \"width:height\" or \"width/height\" (e.g., \"3.5:2\" or \"3.5/2\")\r\n const separator = aspect.includes(\"/\") ? \"/\" : \":\";\r\n const parts = aspect.split(separator);\r\n if (parts.length !== 2) {\r\n throw new Error(`Invalid aspect ratio: ${aspect}. Use format \"width:height\" or \"width/height\"`);\r\n }\r\n const aspectWidth = parseFloat(parts[0]);\r\n const aspectHeight = parseFloat(parts[1]);\r\n if (isNaN(aspectWidth) || isNaN(aspectHeight) || aspectHeight === 0) {\r\n throw new Error(`Invalid aspect ratio: ${aspect}. Use format \"width:height\" (e.g., \"3.5:2\")`);\r\n }\r\n\r\n // Scale so height fits tape, width is proportional\r\n const height = tapeHeight;\r\n const width = Math.round(tapeHeight * (aspectWidth / aspectHeight));\r\n return { width, height };\r\n}\r\n\r\n// Validation\r\nfunction validateOptions(options: PrintOptions): void {\r\n const contentFields = [\r\n options.text,\r\n options.html,\r\n options.htmlPath,\r\n options.textFile,\r\n options.imagePath,\r\n options.imageBuffer,\r\n options.qr,\r\n ].filter(f => f !== undefined);\r\n\r\n if (contentFields.length === 0) {\r\n throw new Error(\"No content provided. Specify one of: text, html, htmlPath, textFile, imagePath, imageBuffer, qr\");\r\n }\r\n if (contentFields.length > 1) {\r\n throw new Error(\"Multiple content options provided. Specify exactly one.\");\r\n }\r\n\r\n if (options.tape !== undefined && !TAPE_SIZES[options.tape]) {\r\n throw new Error(`Invalid tape size: ${options.tape}. Valid sizes: 6, 9, 12, 18, 24`);\r\n }\r\n}\r\n\r\n// Resolve effective settings from options + config + defaults\r\nfunction resolveSettings(options: PrintOptions): { tape: TapeSize; printer: string } {\r\n const config = getConfig();\r\n return {\r\n tape: options.tape ?? config.defaultTape ?? 24,\r\n printer: options.printer ?? config.defaultPrinter ?? DEFAULT_PRINTER,\r\n };\r\n}\r\n\r\n// Render text to image buffer\r\nasync function renderText(text: string, tape: TapeSize): Promise<Buffer> {\r\n const tapeSize = TAPE_SIZES[tape];\r\n const targetHeight = tapeSize.height;\r\n const lines = text.split(\"\\\\n\").join(\"\\n\");\r\n\r\n // Load largest font for quality\r\n const fontDir = path.join(path.dirname(fileURLToPath(import.meta.url)), \"node_modules/@jimp/plugin-print/fonts/open-sans\");\r\n const fontPath = path.join(fontDir, \"open-sans-128-black\", \"open-sans-128-black.fnt\");\r\n const font = await loadFont(fontPath);\r\n\r\n // Measure text\r\n const textWidth = measureText(font, lines);\r\n const textHeight = measureTextHeight(font, lines, textWidth + 100);\r\n\r\n // Create temp image for text\r\n const padding = 10;\r\n const imgWidth = textWidth + padding * 2;\r\n const imgHeight = textHeight + padding * 2;\r\n\r\n const tempImage = new Jimp({ width: imgWidth, height: imgHeight, color: 0xffffffff });\r\n tempImage.print({ font, x: padding, y: padding, text: lines });\r\n\r\n // Scale to fit tape height\r\n const scale = targetHeight / imgHeight;\r\n const finalWidth = Math.round(imgWidth * scale);\r\n const finalHeight = Math.round(imgHeight * scale);\r\n tempImage.resize({ w: finalWidth, h: finalHeight });\r\n\r\n // Create final image with padding\r\n const hPadding = Math.round(targetHeight * 0.2);\r\n const outputWidth = finalWidth + hPadding * 2;\r\n const image = new Jimp({ width: outputWidth, height: targetHeight, color: 0xffffffff });\r\n\r\n const xOffset = hPadding;\r\n const yOffset = Math.round((targetHeight - finalHeight) / 2);\r\n image.composite(tempImage, xOffset, yOffset);\r\n\r\n return image.getBuffer(\"image/png\");\r\n}\r\n\r\n// Render QR code to image buffer\r\nasync function renderQr(data: string, tape: TapeSize, labelText?: string): Promise<Buffer> {\r\n const tapeSize = TAPE_SIZES[tape];\r\n const targetHeight = tapeSize.height;\r\n const qrSize = Math.floor(targetHeight * 0.95);\r\n\r\n // Margins in pixels at PRINT_DPI (3mm left, 4mm right)\r\n const pxPerMm = PRINT_DPI / 25.4;\r\n const leftMargin = Math.round(3 * pxPerMm);\r\n const rightMargin = Math.round(4 * pxPerMm);\r\n\r\n // Generate QR code at high resolution\r\n const qrBuffer = await QRCode.toBuffer(data, {\r\n type: \"png\",\r\n width: qrSize,\r\n margin: 0,\r\n errorCorrectionLevel: \"M\",\r\n color: { dark: \"#000000\", light: \"#ffffff\" },\r\n });\r\n\r\n const qrImage = await Jimp.read(qrBuffer);\r\n\r\n if (!labelText) {\r\n // QR only\r\n const outputWidth = leftMargin + qrSize + rightMargin;\r\n const image = new Jimp({ width: outputWidth, height: targetHeight, color: 0xffffffff });\r\n const yOffset = Math.floor((targetHeight - qrSize) / 2);\r\n image.composite(qrImage, leftMargin, yOffset);\r\n return image.getBuffer(\"image/png\");\r\n }\r\n\r\n // QR + text label\r\n const fontDir = path.join(path.dirname(fileURLToPath(import.meta.url)), \"node_modules/@jimp/plugin-print/fonts/open-sans\");\r\n const fontPath = path.join(fontDir, \"open-sans-64-black\", \"open-sans-64-black.fnt\");\r\n const font = await loadFont(fontPath);\r\n\r\n const textWidth = measureText(font, labelText);\r\n const textHeight = measureTextHeight(font, labelText, textWidth + 50);\r\n\r\n // Scale text to fit beside QR\r\n const maxTextHeight = targetHeight * 0.8;\r\n const textScale = Math.min(1, maxTextHeight / textHeight);\r\n const scaledTextWidth = Math.round(textWidth * textScale);\r\n const scaledTextHeight = Math.round(textHeight * textScale);\r\n\r\n // Create text image\r\n const textImg = new Jimp({ width: textWidth + 20, height: textHeight + 20, color: 0xffffffff });\r\n textImg.print({ font, x: 10, y: 10, text: labelText });\r\n if (textScale < 1) {\r\n textImg.resize({ w: scaledTextWidth, h: scaledTextHeight });\r\n }\r\n\r\n // Compose: QR on left, text on right\r\n const gap = Math.floor(targetHeight * 0.15);\r\n const outputWidth = leftMargin + qrSize + gap + scaledTextWidth + rightMargin;\r\n const image = new Jimp({ width: outputWidth, height: targetHeight, color: 0xffffffff });\r\n\r\n const qrY = Math.floor((targetHeight - qrSize) / 2);\r\n image.composite(qrImage, leftMargin, qrY);\r\n\r\n const textY = Math.floor((targetHeight - scaledTextHeight) / 2);\r\n image.composite(textImg, leftMargin + qrSize + gap, textY);\r\n\r\n return image.getBuffer(\"image/png\");\r\n}\r\n\r\n// Render content to image buffer\r\nasync function renderContent(options: PrintOptions, tape: TapeSize): Promise<Buffer> {\r\n const tapeSize = TAPE_SIZES[tape];\r\n\r\n if (options.text !== undefined) {\r\n return renderText(options.text, tape);\r\n }\r\n\r\n if (options.textFile !== undefined) {\r\n const text = fs.readFileSync(options.textFile, \"utf-8\");\r\n return renderText(text, tape);\r\n }\r\n\r\n if (options.html !== undefined) {\r\n const { width, height } = getHtmlDimensions(tapeSize.height, options.aspect);\r\n const buffer = await renderHtmlString(options.html, { width, height, tapeMm: tape }, options.basePath);\r\n await closeBrowser();\r\n return buffer;\r\n }\r\n\r\n if (options.htmlPath !== undefined) {\r\n const { width, height } = getHtmlDimensions(tapeSize.height, options.aspect);\r\n const buffer = await renderHtmlFile(options.htmlPath, { width, height, tapeMm: tape });\r\n await closeBrowser();\r\n return buffer;\r\n }\r\n\r\n if (options.imagePath !== undefined) {\r\n return fs.readFileSync(options.imagePath);\r\n }\r\n\r\n if (options.imageBuffer !== undefined) {\r\n return options.imageBuffer;\r\n }\r\n\r\n if (options.qr !== undefined) {\r\n return renderQr(options.qr, tape, options.qrLabel);\r\n }\r\n\r\n throw new Error(\"No content to render\");\r\n}\r\n\r\n// Print single image buffer to printer\r\nasync function printBuffer(imageBuffer: Buffer, printer: string, tape: TapeSize): Promise<void> {\r\n const media = MEDIA_OPTIONS[tape];\r\n\r\n const tempPath = path.join(os.tmpdir(), `label-${Date.now()}.png`);\r\n fs.writeFileSync(tempPath, imageBuffer);\r\n\r\n try {\r\n await new Promise<void>((resolve, reject) => {\r\n const psScript = `\r\nAdd-Type -AssemblyName System.Drawing\r\nAdd-Type -AssemblyName System.Printing\r\nAdd-Type -AssemblyName ReachFramework\r\nAdd-Type -AssemblyName PresentationCore\r\n\r\n$img = [System.Drawing.Image]::FromFile('${tempPath.replace(/\\\\/g, \"\\\\\\\\\")}')\r\n\r\n$DPI = ${PRINT_DPI}\r\n$MICRONS_PER_INCH = 25400\r\n\r\n$labelLengthMicrons = [int]($img.Width / $DPI * $MICRONS_PER_INCH) + 3000 # +2mm offset +1mm buffer\r\n$tapeWidthMicrons = ${media.width}\r\n\r\n$imgWidthWpf = $img.Width / $DPI * 96\r\n$imgHeightWpf = $img.Height / $DPI * 96\r\n\r\n$ticketXml = @\"\r\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<psf:PrintTicket xmlns:psf=\"http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework\"\r\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" version=\"1\"\r\n xmlns:psk=\"http://schemas.microsoft.com/windows/2003/08/printing/printschemakeywords\"\r\n xmlns:ns0001=\"http://schemas.brother.info/mfc/printing/2006/11/printschemakeywords\">\r\n <psf:Feature name=\"psk:PageMediaSize\">\r\n <psf:Option name=\"ns0001:${media.name}\">\r\n <psf:ScoredProperty name=\"psk:MediaSizeWidth\">\r\n <psf:Value xsi:type=\"xsd:integer\">${media.width}</psf:Value>\r\n </psf:ScoredProperty>\r\n <psf:ScoredProperty name=\"psk:MediaSizeHeight\">\r\n <psf:ParameterRef name=\"psk:PageMediaSizeMediaSizeHeight\" />\r\n </psf:ScoredProperty>\r\n </psf:Option>\r\n </psf:Feature>\r\n <psf:ParameterInit name=\"psk:PageMediaSizeMediaSizeHeight\">\r\n <psf:Value xsi:type=\"xsd:integer\">$labelLengthMicrons</psf:Value>\r\n </psf:ParameterInit>\r\n <psf:Feature name=\"psk:PageOrientation\">\r\n <psf:Option name=\"psk:Landscape\" />\r\n </psf:Feature>\r\n <psf:Feature name=\"ns0001:PageRollFeedToEndOfSheet\">\r\n <psf:Option name=\"ns0001:FeedToImageEdge\" />\r\n </psf:Feature>\r\n <psf:Feature name=\"psk:PageMediaType\">\r\n <psf:Option name=\"psk:Label\" />\r\n </psf:Feature>\r\n <psf:Feature name=\"ns0001:JobRollCutAtEndOfJob\">\r\n <psf:Option name=\"ns0001:Cut\" />\r\n </psf:Feature>\r\n</psf:PrintTicket>\r\n\"@\r\n\r\n$xmlBytes = [System.Text.Encoding]::UTF8.GetBytes($ticketXml)\r\n$memStream = New-Object System.IO.MemoryStream(,$xmlBytes)\r\n$ticket = New-Object System.Printing.PrintTicket($memStream)\r\n\r\n$server = New-Object System.Printing.LocalPrintServer\r\n$queue = $server.GetPrintQueue('${printer}')\r\n\r\n$xpsPath = [System.IO.Path]::GetTempFileName() + \".xps\"\r\n\r\n$pageWidth = $labelLengthMicrons / $MICRONS_PER_INCH * 96\r\n$pageHeight = $tapeWidthMicrons / $MICRONS_PER_INCH * 96\r\n\r\n$package = [System.IO.Packaging.Package]::Open($xpsPath, [System.IO.FileMode]::Create)\r\n$xpsDoc = New-Object System.Windows.Xps.Packaging.XpsDocument($package)\r\n$writer = [System.Windows.Xps.Packaging.XpsDocument]::CreateXpsDocumentWriter($xpsDoc)\r\n\r\n$visual = New-Object System.Windows.Media.DrawingVisual\r\n$dc = $visual.RenderOpen()\r\n\r\n$bitmapImg = New-Object System.Windows.Media.Imaging.BitmapImage\r\n$bitmapImg.BeginInit()\r\n$bitmapImg.UriSource = New-Object System.Uri('${tempPath.replace(/\\\\/g, \"\\\\\\\\\")}')\r\n$bitmapImg.EndInit()\r\n\r\n$yOffset = ($pageHeight - $imgHeightWpf) / 2\r\n$xOffset = 2 / 25.4 * 96 # 2mm left margin in WPF units\r\n$rect = New-Object System.Windows.Rect($xOffset, $yOffset, $imgWidthWpf, $imgHeightWpf)\r\n$dc.DrawImage($bitmapImg, $rect)\r\n$dc.Close()\r\n\r\n$writer.Write($visual, $ticket)\r\n\r\n$xpsDoc.Close()\r\n$package.Close()\r\n$img.Dispose()\r\n\r\n$xpsDocForPrint = New-Object System.Windows.Xps.Packaging.XpsDocument($xpsPath, [System.IO.FileAccess]::Read)\r\n$seq = $xpsDocForPrint.GetFixedDocumentSequence()\r\n\r\n$xpsWriter = [System.Printing.PrintQueue]::CreateXpsDocumentWriter($queue)\r\n$xpsWriter.Write($seq, $ticket)\r\n\r\n$xpsDocForPrint.Close()\r\nRemove-Item $xpsPath -ErrorAction SilentlyContinue\r\n`;\r\n\r\n const ps = spawn(\"powershell\", [\"-Command\", psScript], { stdio: \"pipe\" });\r\n\r\n let stdout = \"\";\r\n let stderr = \"\";\r\n ps.stdout.on(\"data\", (data) => { stdout += data.toString(); });\r\n ps.stderr.on(\"data\", (data) => { stderr += data.toString(); });\r\n\r\n ps.on(\"close\", (code) => {\r\n if (code === 0) {\r\n resolve();\r\n } else {\r\n reject(new Error(`Print failed: ${stderr}`));\r\n }\r\n });\r\n\r\n ps.on(\"error\", (err) => {\r\n reject(new Error(`Failed to print: ${err.message}`));\r\n });\r\n });\r\n } finally {\r\n if (fs.existsSync(tempPath)) {\r\n fs.unlinkSync(tempPath);\r\n }\r\n }\r\n}\r\n\r\n// Main API functions\r\nexport async function render(options: PrintOptions): Promise<Buffer> {\r\n validateOptions(options);\r\n const { tape } = resolveSettings(options);\r\n return renderContent(options, tape);\r\n}\r\n\r\nexport async function print(options: PrintOptions): Promise<PrintResult> {\r\n validateOptions(options);\r\n const { tape, printer } = resolveSettings(options);\r\n const image = await renderContent(options, tape);\r\n await printBuffer(image, printer, tape);\r\n return { image };\r\n}\r\n"]}
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["api.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * Brother Label Printer API\r\n * Programmatic interface for printing labels on Brother P-touch printers\r\n */\r\n\r\nimport * as fs from \"fs\";\r\nimport * as path from \"path\";\r\nimport { fileURLToPath } from \"url\";\r\nimport * as os from \"os\";\r\nimport { renderHtmlFile, renderHtmlString, closeBrowser } from \"./render.js\";\r\nimport { Jimp, loadFont, measureText, measureTextHeight } from \"jimp\";\r\nimport { spawn } from \"child_process\";\r\nimport QRCode from \"qrcode\";\r\n\r\n// Types\r\nexport type TapeSize = 6 | 9 | 12 | 18 | 24;\r\nexport type Orientation = \"landscape\" | \"portrait\";\r\n\r\nexport interface PrinterConfig {\r\n defaultTape?: TapeSize;\r\n defaultPrinter?: string;\r\n}\r\n\r\nexport interface PrinterInfo {\r\n name: string;\r\n}\r\n\r\nexport interface PrintOptions {\r\n // Content (exactly one required)\r\n text?: string;\r\n html?: string;\r\n htmlPath?: string;\r\n textFile?: string;\r\n imagePath?: string;\r\n imageBuffer?: Buffer;\r\n qr?: string; // QR code data to encode\r\n\r\n // Settings\r\n tape?: TapeSize;\r\n printer?: string;\r\n orientation?: Orientation;\r\n length?: number; // explicit length in mm\r\n basePath?: string; // base path for inline HTML resources\r\n aspect?: string; // width:height ratio for HTML (e.g., \"3.5:2\")\r\n qrLabel?: string; // optional text label beside QR code\r\n textHeight?: string; // text height: \"12mm\", \".5in\", \"50%\" (of tape height)\r\n}\r\n\r\nexport interface PrintResult {\r\n image: Buffer;\r\n}\r\n\r\n// Constants\r\nconst CONFIG_PATH = path.join(os.homedir(), \".brother-label.json\");\r\nconst DEFAULT_PRINTER = \"Brother PT-P710BT\";\r\nconst PRINT_DPI = 300; // High resolution for quality output\r\n\r\n// Tape sizes: height is printable area in pixels at 300 DPI\r\nconst TAPE_SIZES: Record<TapeSize, { width: number; height: number }> = {\r\n 6: { width: 1137, height: 56 }, // 3.79\" x 0.19\" @ 300 DPI\r\n 9: { width: 1137, height: 84 }, // 3.79\" x 0.28\" @ 300 DPI\r\n 12: { width: 1137, height: 113 }, // 3.79\" x 0.38\" @ 300 DPI\r\n 18: { width: 1137, height: 169 }, // 3.79\" x 0.56\" @ 300 DPI\r\n 24: { width: 1137, height: 213 }, // 3.79\" x 0.71\" @ 300 DPI\r\n};\r\n\r\n// Brother CustomMediaSize names and widths in microns\r\nconst MEDIA_OPTIONS: Record<TapeSize, { name: string; width: number }> = {\r\n 6: { name: \"CustomMediaSize257\", width: 5900 },\r\n 9: { name: \"CustomMediaSize258\", width: 9000 },\r\n 12: { name: \"CustomMediaSize259\", width: 11900 },\r\n 18: { name: \"CustomMediaSize260\", width: 18100 },\r\n 24: { name: \"CustomMediaSize261\", width: 24000 },\r\n};\r\n\r\n// Config functions\r\nexport function getConfig(): PrinterConfig {\r\n try {\r\n if (fs.existsSync(CONFIG_PATH)) {\r\n const raw = JSON.parse(fs.readFileSync(CONFIG_PATH, \"utf-8\"));\r\n // Handle legacy format where tape was stored as \"24mm\" string\r\n if (typeof raw.defaultTape === \"string\") {\r\n raw.defaultTape = parseInt(raw.defaultTape.replace(\"mm\", \"\"), 10) as TapeSize;\r\n }\r\n return raw;\r\n }\r\n } catch {\r\n // Ignore config errors\r\n }\r\n return {};\r\n}\r\n\r\nexport function setConfig(config: Partial<PrinterConfig>): void {\r\n const current = getConfig();\r\n const merged = { ...current, ...config };\r\n fs.writeFileSync(CONFIG_PATH, JSON.stringify(merged, null, 2));\r\n}\r\n\r\nexport function getConfigPath(): string {\r\n return CONFIG_PATH;\r\n}\r\n\r\n// Printer listing\r\nexport async function listPrinters(): Promise<PrinterInfo[]> {\r\n return new Promise((resolve, reject) => {\r\n const ps = spawn(\"powershell\", [\r\n \"-Command\",\r\n \"Get-Printer | Where-Object { $_.Name -like '*Brother*' -or $_.Name -like '*PT*' -or $_.Name -like '*QL*' } | Select-Object -ExpandProperty Name\"\r\n ], { stdio: \"pipe\" });\r\n\r\n let stdout = \"\";\r\n let stderr = \"\";\r\n ps.stdout.on(\"data\", (data) => { stdout += data.toString(); });\r\n ps.stderr.on(\"data\", (data) => { stderr += data.toString(); });\r\n\r\n ps.on(\"close\", (code) => {\r\n if (code !== 0) {\r\n reject(new Error(`Failed to list printers: ${stderr}`));\r\n return;\r\n }\r\n const printers = stdout.trim().split(\"\\n\")\r\n .filter(p => p.trim())\r\n .map(name => ({ name: name.trim() }));\r\n resolve(printers);\r\n });\r\n });\r\n}\r\n\r\n// Calculate HTML viewport dimensions from tape height and optional aspect ratio\r\nfunction getHtmlDimensions(tapeHeight: number, aspect?: string): { width?: number; height: number } {\r\n if (!aspect) {\r\n // Auto-detect width from content\r\n return { width: undefined, height: tapeHeight };\r\n }\r\n\r\n // Parse aspect ratio \"width:height\" or \"width/height\" (e.g., \"3.5:2\" or \"3.5/2\")\r\n const separator = aspect.includes(\"/\") ? \"/\" : \":\";\r\n const parts = aspect.split(separator);\r\n if (parts.length !== 2) {\r\n throw new Error(`Invalid aspect ratio: ${aspect}. Use format \"width:height\" or \"width/height\"`);\r\n }\r\n const aspectWidth = parseFloat(parts[0]);\r\n const aspectHeight = parseFloat(parts[1]);\r\n if (isNaN(aspectWidth) || isNaN(aspectHeight) || aspectHeight === 0) {\r\n throw new Error(`Invalid aspect ratio: ${aspect}. Use format \"width:height\" (e.g., \"3.5:2\")`);\r\n }\r\n\r\n // Scale so height fits tape, width is proportional\r\n const height = tapeHeight;\r\n const width = Math.round(tapeHeight * (aspectWidth / aspectHeight));\r\n return { width, height };\r\n}\r\n\r\n// Validation\r\nfunction validateOptions(options: PrintOptions): void {\r\n const contentFields = [\r\n options.text,\r\n options.html,\r\n options.htmlPath,\r\n options.textFile,\r\n options.imagePath,\r\n options.imageBuffer,\r\n options.qr,\r\n ].filter(f => f !== undefined);\r\n\r\n if (contentFields.length === 0) {\r\n throw new Error(\"No content provided. Specify one of: text, html, htmlPath, textFile, imagePath, imageBuffer, qr\");\r\n }\r\n if (contentFields.length > 1) {\r\n throw new Error(\"Multiple content options provided. Specify exactly one.\");\r\n }\r\n\r\n if (options.tape !== undefined && !TAPE_SIZES[options.tape]) {\r\n throw new Error(`Invalid tape size: ${options.tape}. Valid sizes: 6, 9, 12, 18, 24`);\r\n }\r\n}\r\n\r\n// Resolve effective settings from options + config + defaults\r\nfunction resolveSettings(options: PrintOptions): { tape: TapeSize; printer: string } {\r\n const config = getConfig();\r\n return {\r\n tape: options.tape ?? config.defaultTape ?? 24,\r\n printer: options.printer ?? config.defaultPrinter ?? DEFAULT_PRINTER,\r\n };\r\n}\r\n\r\n// Parse text height spec to pixels\r\nfunction parseTextHeight(spec: string, tapeHeightPx: number): number {\r\n const trimmed = spec.trim().toLowerCase();\r\n if (trimmed.endsWith(\"%\")) {\r\n const pct = parseFloat(trimmed.slice(0, -1));\r\n if (isNaN(pct) || pct <= 0) throw new Error(`Invalid text height: ${spec}`);\r\n return Math.round(tapeHeightPx * pct / 100);\r\n }\r\n if (trimmed.endsWith(\"mm\")) {\r\n const mm = parseFloat(trimmed.slice(0, -2));\r\n if (isNaN(mm) || mm <= 0) throw new Error(`Invalid text height: ${spec}`);\r\n return Math.round(mm * PRINT_DPI / 25.4);\r\n }\r\n if (trimmed.endsWith(\"in\")) {\r\n const inches = parseFloat(trimmed.slice(0, -2));\r\n if (isNaN(inches) || inches <= 0) throw new Error(`Invalid text height: ${spec}`);\r\n return Math.round(inches * PRINT_DPI);\r\n }\r\n throw new Error(`Invalid text height: ${spec}. Use \"12mm\", \".5in\", or \"50%\"`);\r\n}\r\n\r\n// Render text to image buffer\r\nasync function renderText(text: string, tape: TapeSize, textHeightSpec?: string): Promise<Buffer> {\r\n const tapeSize = TAPE_SIZES[tape];\r\n const targetHeight = textHeightSpec\r\n ? Math.min(parseTextHeight(textHeightSpec, tapeSize.height), tapeSize.height)\r\n : tapeSize.height;\r\n const lines = text.split(\"\\\\n\").join(\"\\n\");\r\n\r\n // Load largest font for quality\r\n const fontDir = path.join(path.dirname(fileURLToPath(import.meta.url)), \"node_modules/@jimp/plugin-print/fonts/open-sans\");\r\n const fontPath = path.join(fontDir, \"open-sans-128-black\", \"open-sans-128-black.fnt\");\r\n const font = await loadFont(fontPath);\r\n\r\n // Measure text\r\n const textWidth = measureText(font, lines);\r\n const textHeight = measureTextHeight(font, lines, textWidth + 100);\r\n\r\n // Create temp image for text\r\n const padding = 10;\r\n const imgWidth = textWidth + padding * 2;\r\n const imgHeight = textHeight + padding * 2;\r\n\r\n const tempImage = new Jimp({ width: imgWidth, height: imgHeight, color: 0xffffffff });\r\n tempImage.print({ font, x: padding, y: padding, text: lines });\r\n\r\n // Scale to fit tape height\r\n const scale = targetHeight / imgHeight;\r\n const finalWidth = Math.round(imgWidth * scale);\r\n const finalHeight = Math.round(imgHeight * scale);\r\n tempImage.resize({ w: finalWidth, h: finalHeight });\r\n\r\n // Create final image with padding\r\n const canvasHeight = tapeSize.height;\r\n const hPadding = Math.round(canvasHeight * 0.2);\r\n const outputWidth = finalWidth + hPadding * 2;\r\n const image = new Jimp({ width: outputWidth, height: canvasHeight, color: 0xffffffff });\r\n\r\n const xOffset = hPadding;\r\n const yOffset = Math.round((canvasHeight - finalHeight) / 2);\r\n image.composite(tempImage, xOffset, yOffset);\r\n\r\n return image.getBuffer(\"image/png\");\r\n}\r\n\r\n// Render QR code to image buffer\r\nasync function renderQr(data: string, tape: TapeSize, labelText?: string): Promise<Buffer> {\r\n const tapeSize = TAPE_SIZES[tape];\r\n const targetHeight = tapeSize.height;\r\n const qrSize = Math.floor(targetHeight * 0.95);\r\n\r\n // Margins in pixels at PRINT_DPI (3mm left, 4mm right)\r\n const pxPerMm = PRINT_DPI / 25.4;\r\n const leftMargin = Math.round(3 * pxPerMm);\r\n const rightMargin = Math.round(4 * pxPerMm);\r\n\r\n // Generate QR code at high resolution\r\n const qrBuffer = await QRCode.toBuffer(data, {\r\n type: \"png\",\r\n width: qrSize,\r\n margin: 0,\r\n errorCorrectionLevel: \"M\",\r\n color: { dark: \"#000000\", light: \"#ffffff\" },\r\n });\r\n\r\n const qrImage = await Jimp.read(qrBuffer);\r\n\r\n if (!labelText) {\r\n // QR only\r\n const outputWidth = leftMargin + qrSize + rightMargin;\r\n const image = new Jimp({ width: outputWidth, height: targetHeight, color: 0xffffffff });\r\n const yOffset = Math.floor((targetHeight - qrSize) / 2);\r\n image.composite(qrImage, leftMargin, yOffset);\r\n return image.getBuffer(\"image/png\");\r\n }\r\n\r\n // QR + text label\r\n const fontDir = path.join(path.dirname(fileURLToPath(import.meta.url)), \"node_modules/@jimp/plugin-print/fonts/open-sans\");\r\n const fontPath = path.join(fontDir, \"open-sans-64-black\", \"open-sans-64-black.fnt\");\r\n const font = await loadFont(fontPath);\r\n\r\n const textWidth = measureText(font, labelText);\r\n const textHeight = measureTextHeight(font, labelText, textWidth + 50);\r\n\r\n // Scale text to fit beside QR\r\n const maxTextHeight = targetHeight * 0.8;\r\n const textScale = Math.min(1, maxTextHeight / textHeight);\r\n const scaledTextWidth = Math.round(textWidth * textScale);\r\n const scaledTextHeight = Math.round(textHeight * textScale);\r\n\r\n // Create text image\r\n const textImg = new Jimp({ width: textWidth + 20, height: textHeight + 20, color: 0xffffffff });\r\n textImg.print({ font, x: 10, y: 10, text: labelText });\r\n if (textScale < 1) {\r\n textImg.resize({ w: scaledTextWidth, h: scaledTextHeight });\r\n }\r\n\r\n // Compose: QR on left, text on right\r\n const gap = Math.floor(targetHeight * 0.15);\r\n const outputWidth = leftMargin + qrSize + gap + scaledTextWidth + rightMargin;\r\n const image = new Jimp({ width: outputWidth, height: targetHeight, color: 0xffffffff });\r\n\r\n const qrY = Math.floor((targetHeight - qrSize) / 2);\r\n image.composite(qrImage, leftMargin, qrY);\r\n\r\n const textY = Math.floor((targetHeight - scaledTextHeight) / 2);\r\n image.composite(textImg, leftMargin + qrSize + gap, textY);\r\n\r\n return image.getBuffer(\"image/png\");\r\n}\r\n\r\n// Render content to image buffer\r\nasync function renderContent(options: PrintOptions, tape: TapeSize): Promise<Buffer> {\r\n const tapeSize = TAPE_SIZES[tape];\r\n\r\n if (options.text !== undefined) {\r\n return renderText(options.text, tape, options.textHeight);\r\n }\r\n\r\n if (options.textFile !== undefined) {\r\n const text = fs.readFileSync(options.textFile, \"utf-8\");\r\n return renderText(text, tape, options.textHeight);\r\n }\r\n\r\n if (options.html !== undefined) {\r\n const { width, height } = getHtmlDimensions(tapeSize.height, options.aspect);\r\n const buffer = await renderHtmlString(options.html, { width, height, tapeMm: tape }, options.basePath);\r\n await closeBrowser();\r\n return buffer;\r\n }\r\n\r\n if (options.htmlPath !== undefined) {\r\n const { width, height } = getHtmlDimensions(tapeSize.height, options.aspect);\r\n const buffer = await renderHtmlFile(options.htmlPath, { width, height, tapeMm: tape });\r\n await closeBrowser();\r\n return buffer;\r\n }\r\n\r\n if (options.imagePath !== undefined) {\r\n return fs.readFileSync(options.imagePath);\r\n }\r\n\r\n if (options.imageBuffer !== undefined) {\r\n return options.imageBuffer;\r\n }\r\n\r\n if (options.qr !== undefined) {\r\n return renderQr(options.qr, tape, options.qrLabel);\r\n }\r\n\r\n throw new Error(\"No content to render\");\r\n}\r\n\r\n// Print single image buffer to printer\r\nasync function printBuffer(imageBuffer: Buffer, printer: string, tape: TapeSize): Promise<void> {\r\n const media = MEDIA_OPTIONS[tape];\r\n\r\n const tempPath = path.join(os.tmpdir(), `label-${Date.now()}.png`);\r\n fs.writeFileSync(tempPath, imageBuffer);\r\n\r\n try {\r\n await new Promise<void>((resolve, reject) => {\r\n const psScript = `\r\nAdd-Type -AssemblyName System.Drawing\r\nAdd-Type -AssemblyName System.Printing\r\nAdd-Type -AssemblyName ReachFramework\r\nAdd-Type -AssemblyName PresentationCore\r\n\r\n$img = [System.Drawing.Image]::FromFile('${tempPath.replace(/\\\\/g, \"\\\\\\\\\")}')\r\n\r\n$DPI = ${PRINT_DPI}\r\n$MICRONS_PER_INCH = 25400\r\n\r\n$labelLengthMicrons = [int]($img.Width / $DPI * $MICRONS_PER_INCH) + 3000 # +2mm offset +1mm buffer\r\n$tapeWidthMicrons = ${media.width}\r\n\r\n$imgWidthWpf = $img.Width / $DPI * 96\r\n$imgHeightWpf = $img.Height / $DPI * 96\r\n\r\n$ticketXml = @\"\r\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<psf:PrintTicket xmlns:psf=\"http://schemas.microsoft.com/windows/2003/08/printing/printschemaframework\"\r\n xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" version=\"1\"\r\n xmlns:psk=\"http://schemas.microsoft.com/windows/2003/08/printing/printschemakeywords\"\r\n xmlns:ns0001=\"http://schemas.brother.info/mfc/printing/2006/11/printschemakeywords\">\r\n <psf:Feature name=\"psk:PageMediaSize\">\r\n <psf:Option name=\"ns0001:${media.name}\">\r\n <psf:ScoredProperty name=\"psk:MediaSizeWidth\">\r\n <psf:Value xsi:type=\"xsd:integer\">${media.width}</psf:Value>\r\n </psf:ScoredProperty>\r\n <psf:ScoredProperty name=\"psk:MediaSizeHeight\">\r\n <psf:ParameterRef name=\"psk:PageMediaSizeMediaSizeHeight\" />\r\n </psf:ScoredProperty>\r\n </psf:Option>\r\n </psf:Feature>\r\n <psf:ParameterInit name=\"psk:PageMediaSizeMediaSizeHeight\">\r\n <psf:Value xsi:type=\"xsd:integer\">$labelLengthMicrons</psf:Value>\r\n </psf:ParameterInit>\r\n <psf:Feature name=\"psk:PageOrientation\">\r\n <psf:Option name=\"psk:Landscape\" />\r\n </psf:Feature>\r\n <psf:Feature name=\"ns0001:PageRollFeedToEndOfSheet\">\r\n <psf:Option name=\"ns0001:FeedToImageEdge\" />\r\n </psf:Feature>\r\n <psf:Feature name=\"psk:PageMediaType\">\r\n <psf:Option name=\"psk:Label\" />\r\n </psf:Feature>\r\n <psf:Feature name=\"ns0001:JobRollCutAtEndOfJob\">\r\n <psf:Option name=\"ns0001:Cut\" />\r\n </psf:Feature>\r\n</psf:PrintTicket>\r\n\"@\r\n\r\n$xmlBytes = [System.Text.Encoding]::UTF8.GetBytes($ticketXml)\r\n$memStream = New-Object System.IO.MemoryStream(,$xmlBytes)\r\n$ticket = New-Object System.Printing.PrintTicket($memStream)\r\n\r\n$server = New-Object System.Printing.LocalPrintServer\r\n$queue = $server.GetPrintQueue('${printer}')\r\n\r\n$xpsPath = [System.IO.Path]::GetTempFileName() + \".xps\"\r\n\r\n$pageWidth = $labelLengthMicrons / $MICRONS_PER_INCH * 96\r\n$pageHeight = $tapeWidthMicrons / $MICRONS_PER_INCH * 96\r\n\r\n$package = [System.IO.Packaging.Package]::Open($xpsPath, [System.IO.FileMode]::Create)\r\n$xpsDoc = New-Object System.Windows.Xps.Packaging.XpsDocument($package)\r\n$writer = [System.Windows.Xps.Packaging.XpsDocument]::CreateXpsDocumentWriter($xpsDoc)\r\n\r\n$visual = New-Object System.Windows.Media.DrawingVisual\r\n$dc = $visual.RenderOpen()\r\n\r\n$bitmapImg = New-Object System.Windows.Media.Imaging.BitmapImage\r\n$bitmapImg.BeginInit()\r\n$bitmapImg.UriSource = New-Object System.Uri('${tempPath.replace(/\\\\/g, \"\\\\\\\\\")}')\r\n$bitmapImg.EndInit()\r\n\r\n$yOffset = ($pageHeight - $imgHeightWpf) / 2\r\n$xOffset = 2 / 25.4 * 96 # 2mm left margin in WPF units\r\n$rect = New-Object System.Windows.Rect($xOffset, $yOffset, $imgWidthWpf, $imgHeightWpf)\r\n$dc.DrawImage($bitmapImg, $rect)\r\n$dc.Close()\r\n\r\n$writer.Write($visual, $ticket)\r\n\r\n$xpsDoc.Close()\r\n$package.Close()\r\n$img.Dispose()\r\n\r\n$xpsDocForPrint = New-Object System.Windows.Xps.Packaging.XpsDocument($xpsPath, [System.IO.FileAccess]::Read)\r\n$seq = $xpsDocForPrint.GetFixedDocumentSequence()\r\n\r\n$xpsWriter = [System.Printing.PrintQueue]::CreateXpsDocumentWriter($queue)\r\n$xpsWriter.Write($seq, $ticket)\r\n\r\n$xpsDocForPrint.Close()\r\nRemove-Item $xpsPath -ErrorAction SilentlyContinue\r\n`;\r\n\r\n const ps = spawn(\"powershell\", [\"-Command\", psScript], { stdio: \"pipe\" });\r\n\r\n let stdout = \"\";\r\n let stderr = \"\";\r\n ps.stdout.on(\"data\", (data) => { stdout += data.toString(); });\r\n ps.stderr.on(\"data\", (data) => { stderr += data.toString(); });\r\n\r\n ps.on(\"close\", (code) => {\r\n if (code === 0) {\r\n resolve();\r\n } else {\r\n reject(new Error(`Print failed: ${stderr}`));\r\n }\r\n });\r\n\r\n ps.on(\"error\", (err) => {\r\n reject(new Error(`Failed to print: ${err.message}`));\r\n });\r\n });\r\n } finally {\r\n if (fs.existsSync(tempPath)) {\r\n fs.unlinkSync(tempPath);\r\n }\r\n }\r\n}\r\n\r\n// Main API functions\r\nexport async function render(options: PrintOptions): Promise<Buffer> {\r\n validateOptions(options);\r\n const { tape } = resolveSettings(options);\r\n return renderContent(options, tape);\r\n}\r\n\r\nexport async function print(options: PrintOptions): Promise<PrintResult> {\r\n validateOptions(options);\r\n const { tape, printer } = resolveSettings(options);\r\n const image = await renderContent(options, tape);\r\n await printBuffer(image, printer, tape);\r\n return { image };\r\n}\r\n"]}
package/api.ts CHANGED
@@ -43,6 +43,7 @@ export interface PrintOptions {
43
43
  basePath?: string; // base path for inline HTML resources
44
44
  aspect?: string; // width:height ratio for HTML (e.g., "3.5:2")
45
45
  qrLabel?: string; // optional text label beside QR code
46
+ textHeight?: string; // text height: "12mm", ".5in", "50%" (of tape height)
46
47
  }
47
48
 
48
49
  export interface PrintResult {
@@ -183,10 +184,33 @@ function resolveSettings(options: PrintOptions): { tape: TapeSize; printer: stri
183
184
  };
184
185
  }
185
186
 
187
+ // Parse text height spec to pixels
188
+ function parseTextHeight(spec: string, tapeHeightPx: number): number {
189
+ const trimmed = spec.trim().toLowerCase();
190
+ if (trimmed.endsWith("%")) {
191
+ const pct = parseFloat(trimmed.slice(0, -1));
192
+ if (isNaN(pct) || pct <= 0) throw new Error(`Invalid text height: ${spec}`);
193
+ return Math.round(tapeHeightPx * pct / 100);
194
+ }
195
+ if (trimmed.endsWith("mm")) {
196
+ const mm = parseFloat(trimmed.slice(0, -2));
197
+ if (isNaN(mm) || mm <= 0) throw new Error(`Invalid text height: ${spec}`);
198
+ return Math.round(mm * PRINT_DPI / 25.4);
199
+ }
200
+ if (trimmed.endsWith("in")) {
201
+ const inches = parseFloat(trimmed.slice(0, -2));
202
+ if (isNaN(inches) || inches <= 0) throw new Error(`Invalid text height: ${spec}`);
203
+ return Math.round(inches * PRINT_DPI);
204
+ }
205
+ throw new Error(`Invalid text height: ${spec}. Use "12mm", ".5in", or "50%"`);
206
+ }
207
+
186
208
  // Render text to image buffer
187
- async function renderText(text: string, tape: TapeSize): Promise<Buffer> {
209
+ async function renderText(text: string, tape: TapeSize, textHeightSpec?: string): Promise<Buffer> {
188
210
  const tapeSize = TAPE_SIZES[tape];
189
- const targetHeight = tapeSize.height;
211
+ const targetHeight = textHeightSpec
212
+ ? Math.min(parseTextHeight(textHeightSpec, tapeSize.height), tapeSize.height)
213
+ : tapeSize.height;
190
214
  const lines = text.split("\\n").join("\n");
191
215
 
192
216
  // Load largest font for quality
@@ -213,12 +237,13 @@ async function renderText(text: string, tape: TapeSize): Promise<Buffer> {
213
237
  tempImage.resize({ w: finalWidth, h: finalHeight });
214
238
 
215
239
  // Create final image with padding
216
- const hPadding = Math.round(targetHeight * 0.2);
240
+ const canvasHeight = tapeSize.height;
241
+ const hPadding = Math.round(canvasHeight * 0.2);
217
242
  const outputWidth = finalWidth + hPadding * 2;
218
- const image = new Jimp({ width: outputWidth, height: targetHeight, color: 0xffffffff });
243
+ const image = new Jimp({ width: outputWidth, height: canvasHeight, color: 0xffffffff });
219
244
 
220
245
  const xOffset = hPadding;
221
- const yOffset = Math.round((targetHeight - finalHeight) / 2);
246
+ const yOffset = Math.round((canvasHeight - finalHeight) / 2);
222
247
  image.composite(tempImage, xOffset, yOffset);
223
248
 
224
249
  return image.getBuffer("image/png");
@@ -295,12 +320,12 @@ async function renderContent(options: PrintOptions, tape: TapeSize): Promise<Buf
295
320
  const tapeSize = TAPE_SIZES[tape];
296
321
 
297
322
  if (options.text !== undefined) {
298
- return renderText(options.text, tape);
323
+ return renderText(options.text, tape, options.textHeight);
299
324
  }
300
325
 
301
326
  if (options.textFile !== undefined) {
302
327
  const text = fs.readFileSync(options.textFile, "utf-8");
303
- return renderText(text, tape);
328
+ return renderText(text, tape, options.textHeight);
304
329
  }
305
330
 
306
331
  if (options.html !== undefined) {
package/cli.js CHANGED
@@ -28,6 +28,9 @@ function buildPrintOptions(input, opts) {
28
28
  if (opts.aspect) {
29
29
  options.aspect = opts.aspect;
30
30
  }
31
+ if (opts.height) {
32
+ options.textHeight = opts.height;
33
+ }
31
34
  // Explicit type flags override auto-detection
32
35
  if (opts.text) {
33
36
  options.text = input;
@@ -80,6 +83,7 @@ program
80
83
  .option("-p, --printer <name>", "Printer name")
81
84
  .option("-o, --output <file>", "Save to file instead of printing (png, jpg, bmp)")
82
85
  .option("-a, --aspect <ratio>", "Aspect ratio width:height for HTML (e.g., 3.5:2)")
86
+ .option("-H, --height <size>", "Text height: 12mm, .5in, or 50% (of tape height)")
83
87
  .option("--text", "Force input as literal text")
84
88
  .option("--html", "Force input as HTML file path")
85
89
  .option("--image", "Force input as image file path")
package/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\r\n/**\r\n * Brother Label Printer CLI\r\n * Thin wrapper around the API\r\n */\r\n\r\nimport * as fs from \"fs\";\r\nimport * as path from \"path\";\r\nimport { program } from \"commander\";\r\nimport {\r\n print,\r\n render,\r\n getConfig,\r\n setConfig,\r\n getConfigPath,\r\n listPrinters,\r\n TapeSize,\r\n PrintOptions,\r\n} from \"./api.js\";\r\n\r\nconst VALID_TAPES: TapeSize[] = [6, 9, 12, 18, 24];\r\n\r\nfunction parseTape(value: string): TapeSize {\r\n const num = parseInt(value.replace(\"mm\", \"\"), 10) as TapeSize;\r\n if (!VALID_TAPES.includes(num)) {\r\n throw new Error(`Invalid tape size: ${value}. Valid: 6, 9, 12, 18, 24`);\r\n }\r\n return num;\r\n}\r\n\r\n// Detect content type from input string\r\nfunction buildPrintOptions(input: string, opts: { tape?: string; printer?: string; text?: boolean; html?: boolean; image?: boolean; aspect?: string }): PrintOptions {\r\n const options: PrintOptions = {};\r\n\r\n // Parse tape option\r\n if (opts.tape) {\r\n options.tape = parseTape(opts.tape);\r\n }\r\n if (opts.printer) {\r\n options.printer = opts.printer;\r\n }\r\n if (opts.aspect) {\r\n options.aspect = opts.aspect;\r\n }\r\n\r\n // Explicit type flags override auto-detection\r\n if (opts.text) {\r\n options.text = input;\r\n return options;\r\n }\r\n if (opts.html) {\r\n options.htmlPath = path.resolve(input);\r\n return options;\r\n }\r\n if (opts.image) {\r\n options.imagePath = path.resolve(input);\r\n return options;\r\n }\r\n\r\n // Auto-detect content type\r\n const lower = input.toLowerCase();\r\n if (lower.endsWith(\".html\") || lower.endsWith(\".htm\")) {\r\n options.htmlPath = path.resolve(input);\r\n } else if (lower.endsWith(\".txt\")) {\r\n options.textFile = path.resolve(input);\r\n } else if (lower.endsWith(\".png\") || lower.endsWith(\".jpg\") || lower.endsWith(\".jpeg\") || lower.endsWith(\".bmp\") || lower.endsWith(\".gif\")) {\r\n options.imagePath = path.resolve(input);\r\n } else if (fs.existsSync(input)) {\r\n // Exists but unknown extension - try to detect\r\n const ext = path.extname(lower);\r\n if (ext === \".html\" || ext === \".htm\") {\r\n options.htmlPath = path.resolve(input);\r\n } else {\r\n // Assume text file\r\n options.textFile = path.resolve(input);\r\n }\r\n } else {\r\n // Treat as literal text\r\n options.text = input;\r\n }\r\n\r\n return options;\r\n}\r\n\r\nprogram\r\n .name(\"brother-print\")\r\n .description(\"Print labels on Brother P-touch printers\")\r\n .version(\"1.0.0\");\r\n\r\n// Default command - print\r\nprogram\r\n .argument(\"[input]\", \"Text to print, or path to file (auto-detects type)\")\r\n .option(\"-t, --tape <size>\", \"Tape size: 6, 9, 12, 18, 24 (mm)\")\r\n .option(\"-p, --printer <name>\", \"Printer name\")\r\n .option(\"-o, --output <file>\", \"Save to file instead of printing (png, jpg, bmp)\")\r\n .option(\"-a, --aspect <ratio>\", \"Aspect ratio width:height for HTML (e.g., 3.5:2)\")\r\n .option(\"--text\", \"Force input as literal text\")\r\n .option(\"--html\", \"Force input as HTML file path\")\r\n .option(\"--image\", \"Force input as image file path\")\r\n .action(async (input: string | undefined, opts) => {\r\n if (!input) {\r\n program.help();\r\n return;\r\n }\r\n\r\n try {\r\n const options = buildPrintOptions(input, opts);\r\n\r\n if (opts.output) {\r\n // Render only, save to file\r\n const buffer = await render(options);\r\n fs.writeFileSync(opts.output, buffer);\r\n console.log(`Saved to ${opts.output}`);\r\n } else {\r\n // Print\r\n const result = await print(options);\r\n const tape = options.tape ?? getConfig().defaultTape ?? 24;\r\n console.log(`Printed on ${tape}mm tape`);\r\n }\r\n } catch (err) {\r\n console.error(`Error: ${(err as Error).message}`);\r\n process.exit(1);\r\n }\r\n });\r\n\r\n// List printers\r\nprogram\r\n .command(\"list\")\r\n .description(\"List available Brother printers\")\r\n .action(async () => {\r\n try {\r\n const printers = await listPrinters();\r\n if (printers.length === 0) {\r\n console.log(\"No Brother printers found\");\r\n } else {\r\n console.log(\"Brother printers:\");\r\n printers.forEach(p => console.log(` ${p.name}`));\r\n }\r\n } catch (err) {\r\n console.error(`Error: ${(err as Error).message}`);\r\n process.exit(1);\r\n }\r\n });\r\n\r\n// Config\r\nprogram\r\n .command(\"config\")\r\n .description(\"Show or set configuration\")\r\n .option(\"-t, --tape <size>\", \"Set default tape size: 6, 9, 12, 18, 24 (mm)\")\r\n .option(\"-p, --printer <name>\", \"Set default printer name\")\r\n .action((opts) => {\r\n try {\r\n if (!opts.tape && !opts.printer) {\r\n // Show config\r\n const config = getConfig();\r\n console.log(\"Configuration:\");\r\n console.log(` File: ${getConfigPath()}`);\r\n console.log(` Default tape: ${config.defaultTape ? config.defaultTape + \"mm\" : \"(not set)\"}`);\r\n console.log(` Default printer: ${config.defaultPrinter ?? \"(not set)\"}`);\r\n console.log(\"\\nValid tape sizes: 6, 9, 12, 18, 24\");\r\n return;\r\n }\r\n\r\n if (opts.tape) {\r\n const tape = parseTape(opts.tape);\r\n setConfig({ defaultTape: tape });\r\n console.log(`Default tape set to: ${tape}mm`);\r\n }\r\n\r\n if (opts.printer) {\r\n setConfig({ defaultPrinter: opts.printer });\r\n console.log(`Default printer set to: ${opts.printer}`);\r\n }\r\n } catch (err) {\r\n console.error(`Error: ${(err as Error).message}`);\r\n process.exit(1);\r\n }\r\n });\r\n\r\nprogram.parse();\r\n"]}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\r\n/**\r\n * Brother Label Printer CLI\r\n * Thin wrapper around the API\r\n */\r\n\r\nimport * as fs from \"fs\";\r\nimport * as path from \"path\";\r\nimport { program } from \"commander\";\r\nimport {\r\n print,\r\n render,\r\n getConfig,\r\n setConfig,\r\n getConfigPath,\r\n listPrinters,\r\n TapeSize,\r\n PrintOptions,\r\n} from \"./api.js\";\r\n\r\nconst VALID_TAPES: TapeSize[] = [6, 9, 12, 18, 24];\r\n\r\nfunction parseTape(value: string): TapeSize {\r\n const num = parseInt(value.replace(\"mm\", \"\"), 10) as TapeSize;\r\n if (!VALID_TAPES.includes(num)) {\r\n throw new Error(`Invalid tape size: ${value}. Valid: 6, 9, 12, 18, 24`);\r\n }\r\n return num;\r\n}\r\n\r\n// Detect content type from input string\r\nfunction buildPrintOptions(input: string, opts: { tape?: string; printer?: string; text?: boolean; html?: boolean; image?: boolean; aspect?: string; height?: string }): PrintOptions {\r\n const options: PrintOptions = {};\r\n\r\n // Parse tape option\r\n if (opts.tape) {\r\n options.tape = parseTape(opts.tape);\r\n }\r\n if (opts.printer) {\r\n options.printer = opts.printer;\r\n }\r\n if (opts.aspect) {\r\n options.aspect = opts.aspect;\r\n }\r\n if (opts.height) {\r\n options.textHeight = opts.height;\r\n }\r\n\r\n // Explicit type flags override auto-detection\r\n if (opts.text) {\r\n options.text = input;\r\n return options;\r\n }\r\n if (opts.html) {\r\n options.htmlPath = path.resolve(input);\r\n return options;\r\n }\r\n if (opts.image) {\r\n options.imagePath = path.resolve(input);\r\n return options;\r\n }\r\n\r\n // Auto-detect content type\r\n const lower = input.toLowerCase();\r\n if (lower.endsWith(\".html\") || lower.endsWith(\".htm\")) {\r\n options.htmlPath = path.resolve(input);\r\n } else if (lower.endsWith(\".txt\")) {\r\n options.textFile = path.resolve(input);\r\n } else if (lower.endsWith(\".png\") || lower.endsWith(\".jpg\") || lower.endsWith(\".jpeg\") || lower.endsWith(\".bmp\") || lower.endsWith(\".gif\")) {\r\n options.imagePath = path.resolve(input);\r\n } else if (fs.existsSync(input)) {\r\n // Exists but unknown extension - try to detect\r\n const ext = path.extname(lower);\r\n if (ext === \".html\" || ext === \".htm\") {\r\n options.htmlPath = path.resolve(input);\r\n } else {\r\n // Assume text file\r\n options.textFile = path.resolve(input);\r\n }\r\n } else {\r\n // Treat as literal text\r\n options.text = input;\r\n }\r\n\r\n return options;\r\n}\r\n\r\nprogram\r\n .name(\"brother-print\")\r\n .description(\"Print labels on Brother P-touch printers\")\r\n .version(\"1.0.0\");\r\n\r\n// Default command - print\r\nprogram\r\n .argument(\"[input]\", \"Text to print, or path to file (auto-detects type)\")\r\n .option(\"-t, --tape <size>\", \"Tape size: 6, 9, 12, 18, 24 (mm)\")\r\n .option(\"-p, --printer <name>\", \"Printer name\")\r\n .option(\"-o, --output <file>\", \"Save to file instead of printing (png, jpg, bmp)\")\r\n .option(\"-a, --aspect <ratio>\", \"Aspect ratio width:height for HTML (e.g., 3.5:2)\")\r\n .option(\"-H, --height <size>\", \"Text height: 12mm, .5in, or 50% (of tape height)\")\r\n .option(\"--text\", \"Force input as literal text\")\r\n .option(\"--html\", \"Force input as HTML file path\")\r\n .option(\"--image\", \"Force input as image file path\")\r\n .action(async (input: string | undefined, opts) => {\r\n if (!input) {\r\n program.help();\r\n return;\r\n }\r\n\r\n try {\r\n const options = buildPrintOptions(input, opts);\r\n\r\n if (opts.output) {\r\n // Render only, save to file\r\n const buffer = await render(options);\r\n fs.writeFileSync(opts.output, buffer);\r\n console.log(`Saved to ${opts.output}`);\r\n } else {\r\n // Print\r\n const result = await print(options);\r\n const tape = options.tape ?? getConfig().defaultTape ?? 24;\r\n console.log(`Printed on ${tape}mm tape`);\r\n }\r\n } catch (err) {\r\n console.error(`Error: ${(err as Error).message}`);\r\n process.exit(1);\r\n }\r\n });\r\n\r\n// List printers\r\nprogram\r\n .command(\"list\")\r\n .description(\"List available Brother printers\")\r\n .action(async () => {\r\n try {\r\n const printers = await listPrinters();\r\n if (printers.length === 0) {\r\n console.log(\"No Brother printers found\");\r\n } else {\r\n console.log(\"Brother printers:\");\r\n printers.forEach(p => console.log(` ${p.name}`));\r\n }\r\n } catch (err) {\r\n console.error(`Error: ${(err as Error).message}`);\r\n process.exit(1);\r\n }\r\n });\r\n\r\n// Config\r\nprogram\r\n .command(\"config\")\r\n .description(\"Show or set configuration\")\r\n .option(\"-t, --tape <size>\", \"Set default tape size: 6, 9, 12, 18, 24 (mm)\")\r\n .option(\"-p, --printer <name>\", \"Set default printer name\")\r\n .action((opts) => {\r\n try {\r\n if (!opts.tape && !opts.printer) {\r\n // Show config\r\n const config = getConfig();\r\n console.log(\"Configuration:\");\r\n console.log(` File: ${getConfigPath()}`);\r\n console.log(` Default tape: ${config.defaultTape ? config.defaultTape + \"mm\" : \"(not set)\"}`);\r\n console.log(` Default printer: ${config.defaultPrinter ?? \"(not set)\"}`);\r\n console.log(\"\\nValid tape sizes: 6, 9, 12, 18, 24\");\r\n return;\r\n }\r\n\r\n if (opts.tape) {\r\n const tape = parseTape(opts.tape);\r\n setConfig({ defaultTape: tape });\r\n console.log(`Default tape set to: ${tape}mm`);\r\n }\r\n\r\n if (opts.printer) {\r\n setConfig({ defaultPrinter: opts.printer });\r\n console.log(`Default printer set to: ${opts.printer}`);\r\n }\r\n } catch (err) {\r\n console.error(`Error: ${(err as Error).message}`);\r\n process.exit(1);\r\n }\r\n });\r\n\r\nprogram.parse();\r\n"]}
package/cli.ts CHANGED
@@ -29,7 +29,7 @@ function parseTape(value: string): TapeSize {
29
29
  }
30
30
 
31
31
  // Detect content type from input string
32
- function buildPrintOptions(input: string, opts: { tape?: string; printer?: string; text?: boolean; html?: boolean; image?: boolean; aspect?: string }): PrintOptions {
32
+ function buildPrintOptions(input: string, opts: { tape?: string; printer?: string; text?: boolean; html?: boolean; image?: boolean; aspect?: string; height?: string }): PrintOptions {
33
33
  const options: PrintOptions = {};
34
34
 
35
35
  // Parse tape option
@@ -42,6 +42,9 @@ function buildPrintOptions(input: string, opts: { tape?: string; printer?: strin
42
42
  if (opts.aspect) {
43
43
  options.aspect = opts.aspect;
44
44
  }
45
+ if (opts.height) {
46
+ options.textHeight = opts.height;
47
+ }
45
48
 
46
49
  // Explicit type flags override auto-detection
47
50
  if (opts.text) {
@@ -94,6 +97,7 @@ program
94
97
  .option("-p, --printer <name>", "Printer name")
95
98
  .option("-o, --output <file>", "Save to file instead of printing (png, jpg, bmp)")
96
99
  .option("-a, --aspect <ratio>", "Aspect ratio width:height for HTML (e.g., 3.5:2)")
100
+ .option("-H, --height <size>", "Text height: 12mm, .5in, or 50% (of tape height)")
97
101
  .option("--text", "Force input as literal text")
98
102
  .option("--html", "Force input as HTML file path")
99
103
  .option("--image", "Force input as image file path")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/brother-label",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "API and CLI for printing labels on Brother P-touch printers",
5
5
  "type": "module",
6
6
  "main": "index.js",