@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/LICENSE +21 -0
- package/README.md +580 -59
- package/dist/index.d.ts +45 -14
- package/dist/thermal-printer.es.js +117 -76
- package/dist/thermal-printer.es.js.map +1 -1
- package/dist/thermal-printer.umd.js +4 -4
- package/dist/thermal-printer.umd.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
[
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
271
|
+
## ImageDataLike Interface
|
|
170
272
|
|
|
171
|
-
|
|
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
|
-
|
|
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((
|
|
687
|
+
new ThermalPrinter((width = 384));
|
|
187
688
|
```
|
|
188
689
|
|
|
189
|
-
- `
|
|
190
|
-
|
|
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
|
-
- `
|
|
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(
|
|
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
|
-
**
|
|
773
|
+
**Example:**
|
|
249
774
|
|
|
250
775
|
```javascript
|
|
251
|
-
const printers = await ThermalPrinter.getPrinters(
|
|
252
|
-
// [
|
|
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
|
-
##
|
|
810
|
+
## Environment Notes
|
|
272
811
|
|
|
273
|
-
###
|
|
812
|
+
### Client-side (Browser)
|
|
274
813
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
###
|
|
820
|
+
### Server-side (Node.js)
|
|
281
821
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
##
|
|
828
|
+
## License
|
|
306
829
|
|
|
307
|
-
|
|
308
|
-
- Active connection to thermal printer driver API (default: http://localhost:9123)
|
|
309
|
-
- Sarabun font loaded in HTML for Thai text rendering
|
|
830
|
+
MIT
|