@naphatjm/cdh-thermal-printer 1.0.4 → 1.0.6

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
@@ -15,8 +15,9 @@ npm install @naphatjm/cdh-thermal-printer
15
15
  ```javascript
16
16
  import { ThermalPrinter } from "@naphatjm/cdh-thermal-printer";
17
17
 
18
- const printer = new ThermalPrinter("http://localhost:9123", 384);
19
- // 384 is papar width
18
+ // No need to specify driver URL - auto-discovers on localhost:9123-9130
19
+ const printer = new ThermalPrinter(384);
20
+ // 384 is paper width (58mm)
20
21
 
21
22
  printer
22
23
  .init()
@@ -31,24 +32,44 @@ printer
31
32
  ### Get Available Printers
32
33
 
33
34
  ```javascript
34
- const printers = await ThermalPrinter.getPrinters("http://localhost:9123");
35
+ // Auto-discovers driver automatically
36
+ const printers = await ThermalPrinter.getPrinters();
35
37
  console.log(printers);
36
38
  ```
37
39
 
38
40
  **Response Example:**
39
41
 
40
42
  ```json
41
- ["EPSON TM-58 (Model 109)", "Star Micronics TSP100", "Generic ESC/POS Printer"]
43
+ [
44
+ {
45
+ "name": "thermal printer",
46
+ "driver": "Generic / Text Only",
47
+ "port": "USB001",
48
+ "status_text": "Ready"
49
+ },
50
+ {
51
+ "name": "HP LaserJet M1530 MFP Series PCL 6",
52
+ "driver": "HP LaserJet M1530 MFP Series PCL 6",
53
+ "port": "HPLaserJetM1536dnfMFP",
54
+ "status_text": "Ready"
55
+ },
56
+ {
57
+ "name": "Microsoft Print to PDF",
58
+ "driver": "Microsoft Print To PDF",
59
+ "port": "PORTPROMPT:",
60
+ "status_text": "Ready"
61
+ }
62
+ ]
42
63
  ```
43
64
 
44
65
  ### Print Receipt (Full Example)
45
66
 
46
67
  ```javascript
47
- const printer = new ThermalPrinter("http://localhost:9123", 384);
68
+ const printer = new ThermalPrinter(384);
48
69
 
49
- // Get available printers
70
+ // Get available printers (auto-discovers driver)
50
71
  const printers = await ThermalPrinter.getPrinters();
51
- const printerName = printers[0];
72
+ const printerName = printers[0].name;
52
73
 
53
74
  // Build receipt
54
75
  printer
@@ -71,14 +92,14 @@ printer
71
92
  .feed(3)
72
93
  .cut();
73
94
 
74
- // Send to printer
95
+ // Send to printer (auto-discovers driver)
75
96
  await printer.print(printerName);
76
97
  ```
77
98
 
78
99
  ### Print Image (QR Code, Logo, etc.)
79
100
 
80
101
  ```javascript
81
- const printer = new ThermalPrinter("http://localhost:9123", 384);
102
+ const printer = new ThermalPrinter(384);
82
103
 
83
104
  // Create canvas and draw image
84
105
  const canvas = document.createElement("canvas");
@@ -98,7 +119,7 @@ ctx.drawImage(qrImg, 150, 20, 100, 100);
98
119
  // Get image data and print
99
120
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
100
121
 
101
- // Send to printer
122
+ // Send to printer (auto-discovers driver)
102
123
  const printers = await ThermalPrinter.getPrinters();
103
124
  printer
104
125
  .init()
@@ -106,14 +127,95 @@ printer
106
127
  .image(imageData) // Print the image
107
128
  .feed(2)
108
129
  .cut()
109
- .print(printers[0]);
130
+ .print(printers[0].name);
131
+ ```
132
+
133
+ ### Print QR Code (Advanced Example)
134
+
135
+ ```javascript
136
+ import QRCode from "qrcode";
137
+
138
+ const printer = new ThermalPrinter(384);
139
+ const canvas = document.createElement("canvas");
140
+
141
+ // Generate QR code (any size, will be auto-padded)
142
+ await QRCode.toCanvas(canvas, "https://example.com", {
143
+ width: 300, // ✨ Auto-pads to 304px (multiple of 8)
144
+ margin: 2,
145
+ color: {
146
+ dark: "#000000",
147
+ light: "#FFFFFF",
148
+ },
149
+ });
150
+
151
+ // Extract image data
152
+ const imageData = canvas
153
+ .getContext("2d")
154
+ ?.getImageData(0, 0, canvas.width, canvas.height);
155
+
156
+ // Send to printer (auto-discovers driver)
157
+ const printers = await ThermalPrinter.getPrinters();
158
+ printer
159
+ .init()
160
+ .align(1)
161
+ .image(imageData) // ← Handles any width automatically
162
+ .feed(2)
163
+ .cut()
164
+ .print(printers[0].name);
165
+ ```
166
+
167
+ ## ⚠️ Important: Font Setup for Thai Text
168
+
169
+ When using `printThaiText()` or `textToImageData()`, you **must** load the Sarabun font in your HTML file:
170
+
171
+ ```html
172
+ <link
173
+ href="https://fonts.googleapis.com/css2?family=Sarabun:wght@400;700&display=swap"
174
+ rel="stylesheet" />
175
+ ```
176
+
177
+ Without this link, the printer will use the default system font instead of Sarabun.
178
+
179
+ ## How Auto-Discovery Works
180
+
181
+ The library automatically scans for the printer driver on ports 9123-9130:
182
+
183
+ ```javascript
184
+ const printer = new ThermalPrinter(384);
185
+
186
+ // First call to print() triggers auto-discovery
187
+ // Scans localhost:9123 → 9130 for CDH-Driver service
188
+ // Caches the discovered URL for future calls
189
+ await printer.print("Printer Name");
190
+
191
+ // Subsequent calls reuse the cached URL
192
+ await printer.print("Another Printer");
193
+
194
+ // Reconnects automatically if connection is lost
110
195
  ```
111
196
 
112
- ### Convert Thai Text to Image (Client-side Helper)
197
+ **Manual Discovery (if needed):**
198
+
199
+ ```javascript
200
+ const printer = new ThermalPrinter(384);
201
+
202
+ // Explicitly find driver
203
+ const found = await printer.findDriver();
204
+ if (!found) {
205
+ console.error(
206
+ "Driver not found. Please run the printer driver application.",
207
+ );
208
+ }
209
+
210
+ // Now proceed with printing
211
+ await printer.print("Printer Name");
212
+ ```
213
+
214
+ ## Converting Thai Text (Client-side Helper)
113
215
 
114
216
  For **server-side printing** with Thai text, use the `textToImageData()` utility function to render text on the client, then send the image data to your server API.
115
217
 
116
- #### Client-side (React/Next.js):
218
+ ### Client-side (React/Next.js):
117
219
 
118
220
  ```javascript
119
221
  import { textToImageData } from "@naphatjm/cdh-thermal-printer";
@@ -136,7 +238,7 @@ const response = await fetch("/api/print", {
136
238
  });
137
239
  ```
138
240
 
139
- #### Server-side (Next.js API Route):
241
+ ### Server-side (Next.js API Route):
140
242
 
141
243
  ```javascript
142
244
  // pages/api/print.js
@@ -154,7 +256,7 @@ export default function handler(req, res) {
154
256
  height: imageData.height,
155
257
  };
156
258
 
157
- // Print using server-side library
259
+ // Print using server-side library (auto-discovers driver)
158
260
  const printer = new ThermalPrinter();
159
261
  try {
160
262
  printer.init().image(imageDataLike).feed(3).cut().print(printerName);
@@ -166,28 +268,428 @@ export default function handler(req, res) {
166
268
  }
167
269
  ```
168
270
 
169
- ## ⚠️ Important: Font Setup for Thai Text
271
+ ## ImageDataLike Interface
170
272
 
171
- When using `printThaiText()` or `textToImageData()`, you **must** load the Sarabun font in your HTML file:
273
+ For **server-side usage**, the `image()` method accepts an object that matches this structure instead of requiring the native ImageData:
274
+
275
+ ```typescript
276
+ interface ImageDataLike {
277
+ data: Uint8ClampedArray | number[];
278
+ width: number;
279
+ height: number;
280
+ }
281
+ ```
282
+
283
+ **Why?** Node.js doesn't have the `ImageData` constructor, so use a plain object instead:
284
+
285
+ ```javascript
286
+ const imageDataLike = {
287
+ data: new Uint8ClampedArray([...pixelData]),
288
+ width: 384,
289
+ height: 100,
290
+ };
291
+
292
+ printer.image(imageDataLike).print("Printer Name");
293
+ ```
294
+
295
+ ## Complete Next.js Example
296
+
297
+ ### Step 1: Load Font in Layout
298
+
299
+ ```html
300
+ <!-- app/layout.tsx -->
301
+ <head>
302
+ <link
303
+ href="https://fonts.googleapis.com/css2?family=Sarabun:wght@400;700&display=swap"
304
+ rel="stylesheet" />
305
+ </head>
306
+ ```
307
+
308
+ ### Step 2: Client Page Component
309
+
310
+ ```typescript
311
+ // app/print/page.tsx
312
+ "use client";
313
+ import { textToImageData } from "@naphatjm/cdh-thermal-printer";
314
+ import { useState } from "react";
315
+
316
+ export default function PrintPage() {
317
+ const [loading, setLoading] = useState(false);
318
+
319
+ const handlePrint = async (e: React.FormEvent<HTMLFormElement>) => {
320
+ e.preventDefault();
321
+ setLoading(true);
322
+
323
+ const text = (e.currentTarget.elements.namedItem("text") as HTMLInputElement).value;
324
+
325
+ try {
326
+ // 1. Client-side: Convert Thai text to image
327
+ const imageData = textToImageData(text, 384, 22);
328
+
329
+ // 2. Send to API
330
+ const response = await fetch("/api/print", {
331
+ method: "POST",
332
+ headers: { "Content-Type": "application/json" },
333
+ body: JSON.stringify({
334
+ imageData: {
335
+ data: Array.from(imageData.data),
336
+ width: imageData.width,
337
+ height: imageData.height,
338
+ },
339
+ printerName: "EPSON TM-58",
340
+ }),
341
+ });
342
+
343
+ const result = await response.json();
344
+ alert(result.message || "✅ Print successful");
345
+ } catch (error) {
346
+ alert(`❌ Error: ${error instanceof Error ? error.message : "Unknown error"}`);
347
+ } finally {
348
+ setLoading(false);
349
+ }
350
+ };
351
+
352
+ return (
353
+ <div style={{ padding: "20px" }}>
354
+ <h1>🖨️ Print Thai Text</h1>
355
+ <form onSubmit={handlePrint}>
356
+ <textarea
357
+ name="text"
358
+ placeholder="Enter Thai text to print..."
359
+ defaultValue="สวัสดีครับ"
360
+ style={{ width: "100%", height: "100px" }}
361
+ />
362
+ <br />
363
+ <button type="submit" disabled={loading}>
364
+ {loading ? "⏳ Printing..." : "🖨️ Print"}
365
+ </button>
366
+ </form>
367
+ </div>
368
+ );
369
+ }
370
+ ```
371
+
372
+ ### Step 3: API Route Handler
373
+
374
+ ```typescript
375
+ // app/api/print/route.ts
376
+ import { ThermalPrinter } from "@naphatjm/cdh-thermal-printer";
377
+ import { NextRequest, NextResponse } from "next/server";
378
+
379
+ export async function POST(req: NextRequest) {
380
+ try {
381
+ const { imageData, printerName } = await req.json();
382
+
383
+ // Convert JSON to ImageDataLike
384
+ const imageDataLike = {
385
+ data: new Uint8ClampedArray(imageData.data),
386
+ width: imageData.width,
387
+ height: imageData.height,
388
+ };
389
+
390
+ // Use server-side printer
391
+ const printer = new ThermalPrinter();
392
+ printer.init().image(imageDataLike).feed(3).cut();
393
+
394
+ await printer.print(printerName);
395
+
396
+ return NextResponse.json({ message: "✅ Print successful" });
397
+ } catch (error) {
398
+ console.error("Print error:", error);
399
+ return NextResponse.json(
400
+ { error: error instanceof Error ? error.message : "Unknown error" },
401
+ { status: 500 },
402
+ );
403
+ }
404
+ }
405
+ ```
406
+
407
+ ## Customization Options
408
+
409
+ ### What You Can Customize
410
+
411
+ | Feature | Parameter/Method | Options | Default |
412
+ | --------------------- | ---------------------------------------- | -------------------------------- | --------------------- |
413
+ | **Paper Width** | Constructor `width` | 384 (58mm) / 576 (80mm) / custom | 384 |
414
+ | **API URL** | Constructor `driverApiUrl` | Any URL | http://localhost:9123 |
415
+ | **Font Size (Thai)** | `printThaiText(text, fontSize)` | Any number (px) | 22 |
416
+ | **Font Size (Image)** | `textToImageData(text, width, fontSize)` | Any number (px) | 22 |
417
+ | **Canvas Width** | `textToImageData(text, width)` | Any number (px) | 384 |
418
+ | **Text Alignment** | `align(alignment)` | 0=Left, 1=Center, 2=Right | 1 (Center) |
419
+ | **Bold Text** | `bold(enable)` | true / false | true |
420
+ | **Paper Feed** | `feed(lines)` | Any number | 1 |
421
+ | **Cut Type** | `cut(partial)` | true=Partial, false=Full | false (Full) |
422
+ | **Divider Style** | `divider(char, width)` | Any char + count | "-", 32 |
423
+ | **Custom Text** | `line(text)` | Any text | "" |
424
+ | **Blank Lines** | `newline(count)` | Any number | 1 |
425
+ | **Beep Sound** | `beep(times, duration)` | times + ms | 1, 100ms |
426
+ | **Custom Image** | `image(imageData)` | ImageData / ImageDataLike | - |
427
+ | **Raw Commands** | `raw(bytes)` | ESC/POS bytes | - |
428
+
429
+ ### What's Fixed (For Compatibility)
430
+
431
+ | Feature | Value | Reason |
432
+ | ------------------------------ | ---------------- | ------------------------- |
433
+ | **Thai Font** | Sarabun only | Consistency & readability |
434
+ | **Line Height** | fontSize + 12px | Standard spacing |
435
+ | **Pixel Brightness Threshold** | 380 | Black/White conversion |
436
+ | **Cash Drawer Pins** | 0x00, 0x0c, 0x78 | ESC/POS standard |
437
+
438
+ ### Examples of Customization
439
+
440
+ #### Custom Paper Width (80mm)
441
+
442
+ ```javascript
443
+ const printer = new ThermalPrinter(576); // 80mm instead of 58mm (default 384)
444
+
445
+ printer.init().line("Wide receipt").feed(2).cut().print("Printer");
446
+ ```
447
+
448
+ #### Custom Font Size for Thai Text
449
+
450
+ ```javascript
451
+ const printer = new ThermalPrinter();
452
+
453
+ printer
454
+ .init()
455
+ .bold(true)
456
+ .printThaiText("ขนาดใหญ่", 28) // 28px instead of default 22px
457
+ .bold(false)
458
+ .divider("*", 40) // Custom divider
459
+ .feed(3)
460
+ .cut()
461
+ .print("Printer");
462
+ ```
463
+
464
+ #### Custom Divider and Alignment
465
+
466
+ ```javascript
467
+ const printer = new ThermalPrinter();
468
+
469
+ printer
470
+ .init()
471
+ .align(2) // Right align
472
+ .divider("=", 30) // Different style
473
+ .line("Item 1 100.-")
474
+ .line("Item 2 200.-")
475
+ .divider("-", 20) // Different width
476
+ .align(1) // Center align
477
+ .line("Total: 300.-")
478
+ .feed(5)
479
+ .cut(true) // Partial cut instead of full
480
+ .print("Printer");
481
+ ```
482
+
483
+ #### Mixed Customizations
484
+
485
+ ```javascript
486
+ const printer = new ThermalPrinter(576); // 80mm (auto-discovers driver)
487
+
488
+ printer
489
+ .init()
490
+ .align(1) // Center
491
+ .bold(true)
492
+ .printThaiText("ร้านค้า XYZ", 26) // Bigger font
493
+ .bold(false)
494
+ .divider("=", 50)
495
+ .line("พิมพ์บันทึก")
496
+ .divider("-", 40)
497
+ .line("สินค้า 1 150.-")
498
+ .line("สินค้า 2 250.-")
499
+ .line("สินค้า 3 100.-")
500
+ .divider("-", 40)
501
+ .align(2) // Right align total
502
+ .bold(true)
503
+ .line("รวม: 500.-")
504
+ .bold(false)
505
+ .align(1)
506
+ .newline(2)
507
+ .line("ขอบคุณที่มาใช้บริการ")
508
+ .feed(4)
509
+ .cut(true) // Partial cut
510
+ .beep(1, 200) // Single beep
511
+ .print("Receipt Printer");
512
+ ```
513
+
514
+ ## Usage Patterns
515
+
516
+ ### Pattern 1: Client-side Direct Printing
517
+
518
+ ```javascript
519
+ // Simple, all processing on client
520
+ printer.init().printThaiText("สวัสดี").feed(2).cut().print("Printer Name");
521
+ ```
522
+
523
+ ### Pattern 2: Client Render → Server Print (Recommended for production)
524
+
525
+ ```javascript
526
+ // 1. Client renders
527
+ const imageData = textToImageData("สวัสดี", 384, 22);
528
+
529
+ // 2. Send to server
530
+ await fetch("/api/print", { body: JSON.stringify(imageData) });
531
+
532
+ // 3. Server prints (more secure & flexible)
533
+ printer.image(imageDataLike).print("Printer Name");
534
+ ```
535
+
536
+ ### Pattern 3: Custom Image Printing
537
+
538
+ ```javascript
539
+ // Use with QR codes, logos, or custom images
540
+ const canvas = document.createElement("canvas");
541
+ const ctx = canvas.getContext("2d");
542
+ // ... draw custom content ...
543
+ const imageData = ctx.getImageData(0, 0, 384, 200);
544
+
545
+ printer.image(imageData).print("Printer Name");
546
+ ```
547
+
548
+ ## Usage Matrix (Quick Reference)
549
+
550
+ | Scenario | Method | Location | Example |
551
+ | ---------------------- | ------------------------------- | ------------------------------ | ------------------------------------ |
552
+ | **English text** | `line()` | Server or Client | `printer.line("Hello")` |
553
+ | **Thai text (direct)** | `printThaiText()` | Client only | `printer.printThaiText("สวัสดี")` |
554
+ | **Thai text (API)** | `textToImageData()` + `image()` | Client converts, Server prints | See Complete Example |
555
+ | **Image/QR Code** | `image()` | Server or Client | `printer.image(imageData)` |
556
+ | **Get printers** | `getPrinters()` | Server or Client | `await ThermalPrinter.getPrinters()` |
557
+
558
+ ## ⚠️ Common Issues & Troubleshooting
559
+
560
+ ### Issue: "Browser environment required for printThaiText"
561
+
562
+ **Problem:** Using `printThaiText()` on server-side (Node.js)
563
+
564
+ **Solution:** Use `textToImageData()` on client instead
565
+
566
+ ```javascript
567
+ // ❌ WRONG - Server can't use printThaiText
568
+ printer.printThaiText("สวัสดี");
569
+
570
+ // ✅ RIGHT - Client converts, server prints
571
+ const imageData = textToImageData("สวัสดี");
572
+ // ... send to server ...
573
+ printer.image(imageDataLike);
574
+ ```
575
+
576
+ ### Issue: Only "@" symbols print instead of Thai text
577
+
578
+ **Problem:** Sending Thai text directly without rendering to image
579
+
580
+ **Solution:** Always convert Thai text to image first
581
+
582
+ ```javascript
583
+ // ❌ WRONG
584
+ printer.line("สวัสดี"); // Will print as gibberish
585
+
586
+ // ✅ RIGHT
587
+ printer.printThaiText("สวัสดี"); // Client-side
588
+ // OR
589
+ const imageData = textToImageData("สวัสดี"); // Convert first
590
+ ```
591
+
592
+ ### Issue: Thai text looks like default font, not Sarabun
593
+
594
+ **Problem:** Sarabun font not loaded in HTML
595
+
596
+ **Solution:** Add font link to your HTML `<head>`
172
597
 
173
598
  ```html
599
+ <!-- ✅ Add this to your HTML -->
174
600
  <link
175
601
  href="https://fonts.googleapis.com/css2?family=Sarabun:wght@400;700&display=swap"
176
602
  rel="stylesheet" />
177
603
  ```
178
604
 
179
- Without this link, the printer will use the default system font instead of Sarabun.
605
+ ### Issue: "ImageData is not defined" in Node.js
606
+
607
+ **Problem:** Trying to use browser ImageData in Node.js
608
+
609
+ **Solution:** Use ImageDataLike object instead
610
+
611
+ ```javascript
612
+ // ❌ WRONG - ImageData doesn't exist in Node.js
613
+ const img = new ImageData(...);
614
+
615
+ // ✅ RIGHT - Use plain object
616
+ const imageDataLike = {
617
+ data: new Uint8ClampedArray([...]),
618
+ width: 384,
619
+ height: 100,
620
+ };
621
+ printer.image(imageDataLike);
622
+ ```
623
+
624
+ ### Issue: QR Code prints blurry or corrupted
625
+
626
+ **Problem:** QR code width not compatible with thermal printer (not multiple of 8 pixels)
627
+
628
+ **Solution:** Library auto-pads any width - no need to manually adjust
629
+
630
+ ```javascript
631
+ // ✅ Works - Any size QR code
632
+ const qrCanvas = document.createElement("canvas");
633
+ await QRCode.toCanvas(qrCanvas, "https://example.com", { width: 300 }); // 300px QR
634
+ const imageData = qrCanvas.getContext("2d")?.getImageData(0, 0, 300, 300);
635
+ printer.image(imageData); // Auto-pads to 304px (38 bytes × 8 = 304px)
636
+
637
+ // ✅ Also works
638
+ await QRCode.toCanvas(qrCanvas, "https://example.com", { width: 256 }); // 256px QR
639
+ // Auto-pads to 256px (already multiple of 8)
640
+ ```
641
+
642
+ **How it works:**
643
+
644
+ - QR 300px → padded to 304px (38 bytes)
645
+ - QR 256px → padded to 256px (32 bytes, already aligned)
646
+ - QR 210px → padded to 216px (27 bytes)
647
+
648
+ ### Issue: QR Code prints as solid black or white square
649
+
650
+ **Problem:** Image brightness threshold doesn't match QR code contrast
651
+
652
+ **Solution:** QR codes use pure black (0) and pure white (255), threshold of 382 is perfect for them
653
+
654
+ ```javascript
655
+ // This should work out-of-the-box
656
+ const imageData = canvas
657
+ .getContext("2d")
658
+ ?.getImageData(0, 0, canvas.width, canvas.height);
659
+ printer.image(imageData); // Uses default threshold 382 (0-765 scale)
660
+
661
+ // If custom image needs adjustment (not typical)
662
+ // Advanced: Can be customized in convertCanvasToEscPos() function
663
+ ```
664
+
665
+ ### Issue: TextToImageData throws error on server
666
+
667
+ **Problem:** `textToImageData()` requires browser Canvas API
668
+
669
+ **Solution:** Always call `textToImageData()` on client-side only
670
+
671
+ ```javascript
672
+ // ❌ WRONG - Server has no Canvas
673
+ // (server-side code)
674
+ const imageData = textToImageData("สวัสดี");
675
+
676
+ // ✅ RIGHT - Client calls it
677
+ // (client-side React component)
678
+ const imageData = textToImageData("สวัสดี");
679
+ await fetch("/api/print", { body: JSON.stringify(imageData) });
680
+ ```
180
681
 
181
682
  ## API Reference
182
683
 
183
684
  ### Constructor
184
685
 
185
686
  ```javascript
186
- new ThermalPrinter((driverApiUrl = "http://localhost:9123"), (width = 384));
687
+ new ThermalPrinter((width = 384));
187
688
  ```
188
689
 
189
- - `driverApiUrl`: URL of the thermal printer driver API
190
- - `width`: Printer width in dots (384 = 58mm, 576 = 80mm)
690
+ - `width`: Printer width in dots (384 = 58mm, 576 = 80mm, default: 384)
691
+
692
+ The driver URL is **auto-discovered** on ports 9123-9130 when first needed.
191
693
 
192
694
  ### Utility Functions
193
695
 
@@ -232,6 +734,17 @@ printer.image(imageData).print();
232
734
  #### Image & Printing
233
735
 
234
736
  - `image(imageData)` - Print image from canvas ImageData or ImageDataLike object
737
+ - **Auto-adjusts width:** Any image width is automatically padded to multiple of 8 pixels
738
+ - **QR Code friendly:** Supports any QR code size (300px, 256px, etc.)
739
+ - **Smart brightness detection:** Uses configurable threshold (default: 382) to convert to black/white
740
+ - **Supports:** QR codes, logos, barcodes, custom graphics
741
+
742
+ ```javascript
743
+ // Works with any image size
744
+ const imageData = canvas.getContext("2d")?.getImageData(0, 0, 300, 300); // 300px QR
745
+ printer.image(imageData); // Auto-pads to 304px
746
+ ```
747
+
235
748
  - `printThaiText(text, fontSize)` - Print Thai text with automatic image rendering (client-side only, default fontSize: 22px)
236
749
  - `raw(bytes)` - Send raw byte array
237
750
 
@@ -239,17 +752,43 @@ printer.image(imageData).print();
239
752
 
240
753
  - `clear()` - Clear all buffered commands
241
754
  - `getBuffer()` - Get current buffer as Uint8Array
242
- - `print(printerName)` - Send buffered commands to printer
755
+ - `findDriver()` - Auto-discover printer driver on localhost:9123-9130, caches result
756
+ - `print(printerName)` - Send buffered commands to printer (triggers auto-discovery if needed)
243
757
 
244
758
  ### Static Methods
245
759
 
246
- - `static async getPrinters(apiUrl)` - Get list of available printers
760
+ - `static async getPrinters(overrideUrl)` - Get list of available printers from Windows/system
761
+
762
+ **Returns:** Array of PrinterInfo objects
763
+
764
+ ```typescript
765
+ interface PrinterInfo {
766
+ name: string;
767
+ driver: string;
768
+ port: string;
769
+ status_text: string;
770
+ }
771
+ ```
247
772
 
248
- **Returns:** Array of printer names (strings)
773
+ **Example:**
249
774
 
250
775
  ```javascript
251
- const printers = await ThermalPrinter.getPrinters("http://localhost:9123");
252
- // ["EPSON TM-58", "Star Micronics TSP100", ...]
776
+ const printers = await ThermalPrinter.getPrinters();
777
+ // [
778
+ // { name: "thermal printer", driver: "Generic / Text Only", port: "USB001", status_text: "Ready" },
779
+ // { name: "HP LaserJet M1530 MFP Series PCL 6", driver: "HP LaserJet M1530 MFP Series PCL 6", ... },
780
+ // ...
781
+ // ]
782
+
783
+ // Filter for thermal printers only
784
+ const thermalPrinters = printers.filter((p) =>
785
+ p.name.toLowerCase().includes("thermal"),
786
+ );
787
+
788
+ // Or with override URL (for custom server)
789
+ const printers = await ThermalPrinter.getPrinters(
790
+ "http://custom-driver:9123",
791
+ );
253
792
  ```
254
793
 
255
794
  ## Fluent API
@@ -268,42 +807,24 @@ printer
268
807
  .cut();
269
808
  ```
270
809
 
271
- ## Usage Patterns
810
+ ## Environment Notes
272
811
 
273
- ### Pattern 1: Client-side Direct Printing
812
+ ### Client-side (Browser)
274
813
 
275
- ```javascript
276
- // Simple, all processing on client
277
- printer.init().printThaiText("สวัสดี").feed(2).cut().print("Printer Name");
278
- ```
814
+ ✅ Has Canvas API
815
+ Has DOM (document, window)
816
+ ✅ Has Fonts (Sarabun)
817
+ ✅ Has ImageData constructor
818
+ **Use:** `printThaiText()`, `textToImageData()`
279
819
 
280
- ### Pattern 2: Client Render → Server Print (Recommended for production)
820
+ ### Server-side (Node.js)
281
821
 
282
- ```javascript
283
- // 1. Client renders
284
- const imageData = textToImageData("สวัสดี", 384, 22);
285
-
286
- // 2. Send to server
287
- await fetch("/api/print", { body: JSON.stringify(imageData) });
288
-
289
- // 3. Server prints (more secure & flexible)
290
- printer.image(imageDataLike).print("Printer Name");
291
- ```
292
-
293
- ### Pattern 3: Custom Image Printing
294
-
295
- ```javascript
296
- // Use with QR codes, logos, or custom images
297
- const canvas = document.createElement("canvas");
298
- const ctx = canvas.getContext("2d");
299
- // ... draw custom content ...
300
- const imageData = ctx.getImageData(0, 0, 384, 200);
301
-
302
- printer.image(imageData).print("Printer Name");
303
- ```
822
+ ❌ No Canvas API
823
+ No DOM
824
+ No Fonts
825
+ ❌ No ImageData constructor
826
+ **Use:** `image()` with ImageDataLike, `image()` with image data from client
304
827
 
305
- ## Requirements
828
+ ## License
306
829
 
307
- - Modern browser with Canvas API support (for `printThaiText()` and `textToImageData()`)
308
- - Active connection to thermal printer driver API (default: http://localhost:9123)
309
- - Sarabun font loaded in HTML for Thai text rendering
830
+ MIT