@naphatjm/cdh-thermal-printer 1.0.0 → 1.0.1
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 +147 -12
- package/dist/index.d.ts +187 -0
- package/dist/thermal-printer.es.js +62 -29
- package/dist/thermal-printer.es.js.map +1 -0
- package/dist/thermal-printer.umd.js +4 -2
- package/dist/thermal-printer.umd.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ A JavaScript library for controlling ESC/POS thermal printers with support for T
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm install cdh-thermal-printer
|
|
8
|
+
npm install @naphatjm/cdh-thermal-printer
|
|
9
9
|
```
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
@@ -13,9 +13,10 @@ npm install cdh-thermal-printer
|
|
|
13
13
|
### Basic Setup
|
|
14
14
|
|
|
15
15
|
```javascript
|
|
16
|
-
import { ThermalPrinter } from "cdh-thermal-printer";
|
|
16
|
+
import { ThermalPrinter } from "@naphatjm/cdh-thermal-printer";
|
|
17
17
|
|
|
18
18
|
const printer = new ThermalPrinter("http://localhost:9123", 384);
|
|
19
|
+
// 384 is papar width
|
|
19
20
|
|
|
20
21
|
printer
|
|
21
22
|
.init()
|
|
@@ -27,6 +28,87 @@ printer
|
|
|
27
28
|
.print("Printer Name");
|
|
28
29
|
```
|
|
29
30
|
|
|
31
|
+
### Get Available Printers
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
const printers = await ThermalPrinter.getPrinters("http://localhost:9123");
|
|
35
|
+
console.log(printers);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Response Example:**
|
|
39
|
+
|
|
40
|
+
```json
|
|
41
|
+
["EPSON TM-58 (Model 109)", "Star Micronics TSP100", "Generic ESC/POS Printer"]
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Print Receipt (Full Example)
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
const printer = new ThermalPrinter("http://localhost:9123", 384);
|
|
48
|
+
|
|
49
|
+
// Get available printers
|
|
50
|
+
const printers = await ThermalPrinter.getPrinters();
|
|
51
|
+
const printerName = printers[0];
|
|
52
|
+
|
|
53
|
+
// Build receipt
|
|
54
|
+
printer
|
|
55
|
+
.clear()
|
|
56
|
+
.init()
|
|
57
|
+
.align(1)
|
|
58
|
+
.bold(true)
|
|
59
|
+
.line("ร้านค้าทดสอบ")
|
|
60
|
+
.bold(false)
|
|
61
|
+
.divider("-", 32)
|
|
62
|
+
.line("รายการสินค้า:")
|
|
63
|
+
.line("1. ข้าวมันไก่ 50.-")
|
|
64
|
+
.line("2. ชานมไข่มุก 35.-")
|
|
65
|
+
.divider("-", 32)
|
|
66
|
+
.align(2)
|
|
67
|
+
.line("รวม: 85.-")
|
|
68
|
+
.align(1)
|
|
69
|
+
.newline(2)
|
|
70
|
+
.line("ขอบคุณค่ะ")
|
|
71
|
+
.feed(3)
|
|
72
|
+
.cut();
|
|
73
|
+
|
|
74
|
+
// Send to printer
|
|
75
|
+
await printer.print(printerName);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Print Image (QR Code, Logo, etc.)
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
const printer = new ThermalPrinter("http://localhost:9123", 384);
|
|
82
|
+
|
|
83
|
+
// Create canvas and draw image
|
|
84
|
+
const canvas = document.createElement("canvas");
|
|
85
|
+
const ctx = canvas.getContext("2d");
|
|
86
|
+
|
|
87
|
+
canvas.width = 384;
|
|
88
|
+
canvas.height = 200;
|
|
89
|
+
|
|
90
|
+
// White background
|
|
91
|
+
ctx.fillStyle = "white";
|
|
92
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
93
|
+
|
|
94
|
+
// Draw image (e.g., QR code from img tag)
|
|
95
|
+
const qrImg = document.getElementById("qrcode");
|
|
96
|
+
ctx.drawImage(qrImg, 150, 20, 100, 100);
|
|
97
|
+
|
|
98
|
+
// Get image data and print
|
|
99
|
+
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
100
|
+
|
|
101
|
+
// Send to printer
|
|
102
|
+
const printers = await ThermalPrinter.getPrinters();
|
|
103
|
+
printer
|
|
104
|
+
.init()
|
|
105
|
+
.align(1)
|
|
106
|
+
.image(imageData) // Print the image
|
|
107
|
+
.feed(2)
|
|
108
|
+
.cut()
|
|
109
|
+
.print(printers[0]);
|
|
110
|
+
```
|
|
111
|
+
|
|
30
112
|
## ⚠️ Important: Font Setup for Thai Text
|
|
31
113
|
|
|
32
114
|
When using `printThaiText()`, you **must** load the Sarabun font in your HTML file:
|
|
@@ -39,7 +121,7 @@ When using `printThaiText()`, you **must** load the Sarabun font in your HTML fi
|
|
|
39
121
|
|
|
40
122
|
Without this link, the printer will use the default system font instead of Sarabun.
|
|
41
123
|
|
|
42
|
-
## API
|
|
124
|
+
## API Reference
|
|
43
125
|
|
|
44
126
|
### Constructor
|
|
45
127
|
|
|
@@ -50,16 +132,69 @@ new ThermalPrinter((driverApiUrl = "http://localhost:9123"), (width = 384));
|
|
|
50
132
|
- `driverApiUrl`: URL of the thermal printer driver API
|
|
51
133
|
- `width`: Printer width in dots (384 = 58mm, 576 = 80mm)
|
|
52
134
|
|
|
53
|
-
### Methods
|
|
135
|
+
### Instance Methods
|
|
54
136
|
|
|
55
|
-
|
|
137
|
+
#### Text & Formatting
|
|
138
|
+
|
|
139
|
+
- `init()` - Initialize printer (ESC @)
|
|
56
140
|
- `align(alignment)` - Set text alignment (0=Left, 1=Center, 2=Right)
|
|
57
|
-
- `bold(enable)` - Enable/disable bold text
|
|
58
|
-
- `
|
|
59
|
-
- `
|
|
141
|
+
- `bold(enable)` - Enable/disable bold text (true/false)
|
|
142
|
+
- `line(text)` - Add line of text with newline
|
|
143
|
+
- `newline(count)` - Add blank lines (default: 1)
|
|
144
|
+
- `divider(char, width)` - Add separator line (e.g., `divider("-", 32)`)
|
|
145
|
+
|
|
146
|
+
#### Paper & Output
|
|
147
|
+
|
|
148
|
+
- `feed(lines)` - Feed paper by specified lines (default: 1)
|
|
149
|
+
- `cut(partial)` - Cut paper (true=partial, false=full cut)
|
|
150
|
+
- `beep(times, duration)` - Beep speaker (times: count, duration: milliseconds)
|
|
151
|
+
- `drawerKick()` - Open cash drawer
|
|
152
|
+
|
|
153
|
+
#### Image & Printing
|
|
154
|
+
|
|
60
155
|
- `image(imageData)` - Print image from canvas ImageData
|
|
61
|
-
- `printThaiText(text, fontSize)` - Print Thai text (
|
|
62
|
-
- `
|
|
156
|
+
- `printThaiText(text, fontSize)` - Print Thai text with automatic image rendering (default fontSize: 22px)
|
|
157
|
+
- `raw(bytes)` - Send raw byte array
|
|
158
|
+
|
|
159
|
+
#### Buffer Management
|
|
160
|
+
|
|
161
|
+
- `clear()` - Clear all buffered commands
|
|
63
162
|
- `getBuffer()` - Get current buffer as Uint8Array
|
|
64
|
-
- `print(printerName)` - Send
|
|
65
|
-
|
|
163
|
+
- `print(printerName)` - Send buffered commands to printer
|
|
164
|
+
|
|
165
|
+
### Static Methods
|
|
166
|
+
|
|
167
|
+
- `static async getPrinters(apiUrl)` - Get list of available printers
|
|
168
|
+
|
|
169
|
+
**Returns:** Array of printer names (strings)
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
const printers = await ThermalPrinter.getPrinters("http://localhost:9123");
|
|
173
|
+
// ["EPSON TM-58", "Star Micronics TSP100", ...]
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Fluent API
|
|
177
|
+
|
|
178
|
+
All methods return `this`, allowing for method chaining:
|
|
179
|
+
|
|
180
|
+
```javascript
|
|
181
|
+
printer
|
|
182
|
+
.init()
|
|
183
|
+
.align(1)
|
|
184
|
+
.bold(true)
|
|
185
|
+
.line("Header")
|
|
186
|
+
.bold(false)
|
|
187
|
+
.divider()
|
|
188
|
+
.feed(2)
|
|
189
|
+
.cut();
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## Requirements
|
|
193
|
+
|
|
194
|
+
- Modern browser with Canvas API support
|
|
195
|
+
- Active connection to thermal printer driver API (default: http://localhost:9123)
|
|
196
|
+
- Sarabun font loaded for Thai text rendering
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thermal Printer Library for ESC/POS printers
|
|
3
|
+
* Supports Thai text rendering and image printing
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Print response type
|
|
8
|
+
*/
|
|
9
|
+
export interface PrintResponse {
|
|
10
|
+
success: boolean;
|
|
11
|
+
message?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* ThermalPrinter Service
|
|
16
|
+
* Fluent API for controlling thermal printer with ESC/POS commands
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```javascript
|
|
20
|
+
* const printer = new ThermalPrinter("http://localhost:9123", 384);
|
|
21
|
+
* printer
|
|
22
|
+
* .init()
|
|
23
|
+
* .align(1)
|
|
24
|
+
* .printThaiText("สวัสดี")
|
|
25
|
+
* .feed(2)
|
|
26
|
+
* .cut()
|
|
27
|
+
* .print("Printer Name");
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export class ThermalPrinter {
|
|
31
|
+
/**
|
|
32
|
+
* Initialize ThermalPrinter instance
|
|
33
|
+
* @param driverApiUrl - URL of the thermal printer driver API (default: http://localhost:9123)
|
|
34
|
+
* @param width - Printer width in dots (384 = 58mm, 576 = 80mm, default: 384)
|
|
35
|
+
*/
|
|
36
|
+
constructor(driverApiUrl?: string, width?: number);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Driver API URL
|
|
40
|
+
*/
|
|
41
|
+
readonly driverApi: string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Printer width in dots
|
|
45
|
+
*/
|
|
46
|
+
readonly printerWidth: number;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Internal command buffer
|
|
50
|
+
*/
|
|
51
|
+
readonly buffer: number[];
|
|
52
|
+
|
|
53
|
+
// --- Basic Commands ---
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Initialize printer (ESC @)
|
|
57
|
+
* @returns this for method chaining
|
|
58
|
+
*/
|
|
59
|
+
init(): ThermalPrinter;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Set text alignment
|
|
63
|
+
* @param alignment - 0=Left, 1=Center, 2=Right (default: 1)
|
|
64
|
+
* @returns this for method chaining
|
|
65
|
+
*/
|
|
66
|
+
align(alignment?: 0 | 1 | 2): ThermalPrinter;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Enable or disable bold text
|
|
70
|
+
* @param enable - true to enable, false to disable (default: true)
|
|
71
|
+
* @returns this for method chaining
|
|
72
|
+
*/
|
|
73
|
+
bold(enable?: boolean): ThermalPrinter;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Feed paper by specified lines
|
|
77
|
+
* @param lines - Number of lines to feed (default: 1)
|
|
78
|
+
* @returns this for method chaining
|
|
79
|
+
*/
|
|
80
|
+
feed(lines?: number): ThermalPrinter;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Cut paper
|
|
84
|
+
* @param partial - true for partial cut, false for full cut (default: false)
|
|
85
|
+
* @returns this for method chaining
|
|
86
|
+
*/
|
|
87
|
+
cut(partial?: boolean): ThermalPrinter;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Add line of text with newline
|
|
91
|
+
* @param text - Text to print (default: "")
|
|
92
|
+
* @returns this for method chaining
|
|
93
|
+
*/
|
|
94
|
+
line(text?: string): ThermalPrinter;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Add blank lines
|
|
98
|
+
* @param count - Number of blank lines to add (default: 1)
|
|
99
|
+
* @returns this for method chaining
|
|
100
|
+
*/
|
|
101
|
+
newline(count?: number): ThermalPrinter;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Add divider line
|
|
105
|
+
* @param char - Character to repeat (default: "-")
|
|
106
|
+
* @param width - Width of divider in characters (default: 32)
|
|
107
|
+
* @returns this for method chaining
|
|
108
|
+
*/
|
|
109
|
+
divider(char?: string, width?: number): ThermalPrinter;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Beep speaker
|
|
113
|
+
* @param times - Number of beeps (default: 1)
|
|
114
|
+
* @param duration - Duration of beep in milliseconds (default: 100)
|
|
115
|
+
* @returns this for method chaining
|
|
116
|
+
*/
|
|
117
|
+
beep(times?: number, duration?: number): ThermalPrinter;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Open cash drawer
|
|
121
|
+
* @returns this for method chaining
|
|
122
|
+
*/
|
|
123
|
+
drawerKick(): ThermalPrinter;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Send raw byte array
|
|
127
|
+
* @param bytes - Byte array to send
|
|
128
|
+
* @returns this for method chaining
|
|
129
|
+
*/
|
|
130
|
+
raw(bytes: number[]): ThermalPrinter;
|
|
131
|
+
|
|
132
|
+
// --- Image & Thai Text Logic ---
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Print image from canvas ImageData
|
|
136
|
+
* @param imageData - Canvas ImageData object
|
|
137
|
+
* @returns this for method chaining
|
|
138
|
+
*/
|
|
139
|
+
image(imageData: ImageData): ThermalPrinter;
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Print Thai text (automatically renders as image)
|
|
143
|
+
*
|
|
144
|
+
* ⚠️ REQUIRED: Sarabun font must be loaded in your HTML file:
|
|
145
|
+
* ```html
|
|
146
|
+
* <link href="https://fonts.googleapis.com/css2?family=Sarabun:wght@400;700&display=swap" rel="stylesheet">
|
|
147
|
+
* ```
|
|
148
|
+
*
|
|
149
|
+
* @param text - Thai text to print
|
|
150
|
+
* @param fontSize - Font size in pixels (default: 22)
|
|
151
|
+
* @returns this for method chaining
|
|
152
|
+
*/
|
|
153
|
+
printThaiText(text: string, fontSize?: number): ThermalPrinter;
|
|
154
|
+
|
|
155
|
+
// --- Execution ---
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Clear all buffered commands
|
|
159
|
+
* @returns this for method chaining
|
|
160
|
+
*/
|
|
161
|
+
clear(): ThermalPrinter;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get compiled binary data
|
|
165
|
+
* @returns Uint8Array of buffered commands
|
|
166
|
+
*/
|
|
167
|
+
getBuffer(): Uint8Array;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Print to specific printer
|
|
171
|
+
* @param printerName - Name of printer to print to (optional, will use first available if not provided)
|
|
172
|
+
* @returns Promise resolving to print response
|
|
173
|
+
* @throws Error if printer not found or print failed
|
|
174
|
+
*/
|
|
175
|
+
print(printerName?: string): Promise<PrintResponse>;
|
|
176
|
+
|
|
177
|
+
// --- Static Methods ---
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get list of available printers
|
|
181
|
+
* @param apiUrl - Driver API URL (default: http://localhost:9123)
|
|
182
|
+
* @returns Promise resolving to array of printer names
|
|
183
|
+
*/
|
|
184
|
+
static getPrinters(apiUrl?: string): Promise<string[]>;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export {};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
const
|
|
1
|
+
const i = {
|
|
2
|
+
ESC: 27,
|
|
3
|
+
GS: 29,
|
|
2
4
|
LF: 10,
|
|
3
5
|
// Commands
|
|
4
6
|
INIT: [27, 64],
|
|
@@ -10,44 +12,74 @@ const o = {
|
|
|
10
12
|
// Formatting
|
|
11
13
|
BOLD_ON: [27, 69, 1],
|
|
12
14
|
BOLD_OFF: [27, 69, 0],
|
|
13
|
-
ALIGN_LEFT: [27, 97, 0]
|
|
15
|
+
ALIGN_LEFT: [27, 97, 0],
|
|
16
|
+
ALIGN_CENTER: [27, 97, 1],
|
|
17
|
+
ALIGN_RIGHT: [27, 97, 2],
|
|
18
|
+
// Cash Drawer
|
|
19
|
+
DRAWER_KICK: [27, 112, 0, 12, 120],
|
|
20
|
+
// Beep (ESC B - default 100ms)
|
|
21
|
+
BEEP: (f = 100) => [27, 66, f]
|
|
14
22
|
};
|
|
15
|
-
function p(
|
|
16
|
-
const e =
|
|
23
|
+
function p(f) {
|
|
24
|
+
const e = f.width, t = f.height, r = f.data, s = e / 8;
|
|
17
25
|
let n = [];
|
|
18
|
-
n.push(29, 118, 48, 0), n.push(
|
|
19
|
-
for (let
|
|
20
|
-
for (let c = 0; c <
|
|
21
|
-
let
|
|
22
|
-
for (let
|
|
23
|
-
const a = (
|
|
24
|
-
|
|
26
|
+
n.push(29, 118, 48, 0), n.push(s % 256, Math.floor(s / 256)), n.push(t % 256, Math.floor(t / 256));
|
|
27
|
+
for (let o = 0; o < t; o++)
|
|
28
|
+
for (let c = 0; c < s; c++) {
|
|
29
|
+
let u = 0;
|
|
30
|
+
for (let h = 0; h < 8; h++) {
|
|
31
|
+
const a = (o * e + c * 8 + h) * 4;
|
|
32
|
+
r[a] + r[a + 1] + r[a + 2] < 380 && (u |= 1 << 7 - h);
|
|
25
33
|
}
|
|
26
|
-
n.push(
|
|
34
|
+
n.push(u);
|
|
27
35
|
}
|
|
28
36
|
return n;
|
|
29
37
|
}
|
|
30
|
-
class
|
|
38
|
+
class l {
|
|
31
39
|
constructor(e = "http://localhost:9123", t = 384) {
|
|
32
40
|
this.driverApi = e, this.printerWidth = t, this.buffer = [];
|
|
33
41
|
}
|
|
34
42
|
// --- Basic Commands ---
|
|
35
43
|
init() {
|
|
36
|
-
return this.buffer.push(...
|
|
44
|
+
return this.buffer.push(...i.INIT), this;
|
|
37
45
|
}
|
|
38
46
|
align(e = 1) {
|
|
39
|
-
const t = [...
|
|
47
|
+
const t = [...i.ALIGN_LEFT];
|
|
40
48
|
return t[2] = e, this.buffer.push(...t), this;
|
|
41
49
|
}
|
|
42
50
|
bold(e = !0) {
|
|
43
|
-
return this.buffer.push(...e ?
|
|
51
|
+
return this.buffer.push(...e ? i.BOLD_ON : i.BOLD_OFF), this;
|
|
44
52
|
}
|
|
45
53
|
feed(e = 1) {
|
|
46
|
-
for (let t = 0; t < e; t++) this.buffer.push(
|
|
54
|
+
for (let t = 0; t < e; t++) this.buffer.push(i.LF);
|
|
47
55
|
return this;
|
|
48
56
|
}
|
|
49
57
|
cut(e = !1) {
|
|
50
|
-
return this.buffer.push(...e ?
|
|
58
|
+
return this.buffer.push(...e ? i.CUT_PARTIAL : i.CUT_FULL), this;
|
|
59
|
+
}
|
|
60
|
+
line(e = "") {
|
|
61
|
+
const r = new TextEncoder().encode(e + `
|
|
62
|
+
`);
|
|
63
|
+
return this.buffer.push(...r), this;
|
|
64
|
+
}
|
|
65
|
+
newline(e = 1) {
|
|
66
|
+
for (let t = 0; t < e; t++)
|
|
67
|
+
this.buffer.push(i.LF);
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
divider(e = "-", t = 32) {
|
|
71
|
+
return this.line(e.repeat(t)), this;
|
|
72
|
+
}
|
|
73
|
+
beep(e = 1, t = 100) {
|
|
74
|
+
for (let r = 0; r < e; r++)
|
|
75
|
+
this.buffer.push(...i.BEEP(t));
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
drawerKick() {
|
|
79
|
+
return this.buffer.push(...i.DRAWER_KICK), this;
|
|
80
|
+
}
|
|
81
|
+
raw(e) {
|
|
82
|
+
return this.buffer.push(...e), this;
|
|
51
83
|
}
|
|
52
84
|
// --- Image & Thai Text Logic ---
|
|
53
85
|
image(e) {
|
|
@@ -68,13 +100,13 @@ class x {
|
|
|
68
100
|
printThaiText(e, t = 22) {
|
|
69
101
|
if (typeof document > "u")
|
|
70
102
|
return console.error("Browser environment required for printThaiText"), this;
|
|
71
|
-
const
|
|
72
|
-
`),
|
|
73
|
-
|
|
74
|
-
let
|
|
75
|
-
for (let
|
|
76
|
-
|
|
77
|
-
const a =
|
|
103
|
+
const r = document.createElement("canvas"), s = r.getContext("2d"), n = "'Sarabun', sans-serif", o = t + 12, c = e.split(`
|
|
104
|
+
`), u = c.length * o + 20;
|
|
105
|
+
r.width = this.printerWidth, r.height = u, s.fillStyle = "white", s.fillRect(0, 0, r.width, r.height), s.fillStyle = "black", s.font = `${t}px ${n}`, s.textBaseline = "top";
|
|
106
|
+
let h = 10;
|
|
107
|
+
for (let x of c)
|
|
108
|
+
s.fillText(x, 10, h), h += o;
|
|
109
|
+
const a = s.getImageData(0, 0, this.printerWidth, u);
|
|
78
110
|
return this.image(a);
|
|
79
111
|
}
|
|
80
112
|
// --- Execution ---
|
|
@@ -86,14 +118,14 @@ class x {
|
|
|
86
118
|
}
|
|
87
119
|
async print(e) {
|
|
88
120
|
if (!e) {
|
|
89
|
-
const t = await
|
|
121
|
+
const t = await l.getPrinters(this.driverApi);
|
|
90
122
|
if (t.length > 0) e = t[0];
|
|
91
123
|
else throw new Error("No printer found.");
|
|
92
124
|
}
|
|
93
125
|
try {
|
|
94
|
-
const t = this.getBuffer(),
|
|
126
|
+
const t = this.getBuffer(), r = encodeURIComponent(e);
|
|
95
127
|
if (!(await fetch(
|
|
96
|
-
`${this.driverApi}/print?printer=${
|
|
128
|
+
`${this.driverApi}/print?printer=${r}`,
|
|
97
129
|
{
|
|
98
130
|
method: "POST",
|
|
99
131
|
headers: { "Content-Type": "application/octet-stream" },
|
|
@@ -115,5 +147,6 @@ class x {
|
|
|
115
147
|
}
|
|
116
148
|
}
|
|
117
149
|
export {
|
|
118
|
-
|
|
150
|
+
l as ThermalPrinter
|
|
119
151
|
};
|
|
152
|
+
//# sourceMappingURL=thermal-printer.es.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thermal-printer.es.js","sources":["../src/constants.js","../src/utils.js","../src/index.js"],"sourcesContent":["// src/constants.js\r\nexport const CMD = {\r\n ESC: 0x1b,\r\n GS: 0x1d,\r\n LF: 0x0a,\r\n\r\n // Commands\r\n INIT: [0x1b, 0x40], // Initialize printer\r\n CUT_FULL: [0x1d, 0x56, 0x42, 0x00], // Full cut\r\n CUT_PARTIAL: [0x1d, 0x56, 0x41, 0x00], // Partial cut\r\n\r\n // Formatting\r\n BOLD_ON: [0x1b, 0x45, 0x01],\r\n BOLD_OFF: [0x1b, 0x45, 0x00],\r\n\r\n ALIGN_LEFT: [0x1b, 0x61, 0x00],\r\n ALIGN_CENTER: [0x1b, 0x61, 0x01],\r\n ALIGN_RIGHT: [0x1b, 0x61, 0x02],\r\n\r\n // Cash Drawer\r\n DRAWER_KICK: [0x1b, 0x70, 0x00, 0x0c, 0x78],\r\n\r\n // Beep (ESC B - default 100ms)\r\n BEEP: (duration = 100) => [0x1b, 0x42, duration],\r\n};\r\n","// src/utils.js\r\n\r\n/**\r\n * แปลงข้อมูลภาพ (ImageData) เป็นคำสั่ง ESC/POS GS v 0\r\n * @param {ImageData} imageData\r\n * @returns {number[]} Array ของ Byte คำสั่ง\r\n */\r\nexport function convertCanvasToEscPos(imageData) {\r\n const width = imageData.width;\r\n const height = imageData.height;\r\n const pixels = imageData.data;\r\n\r\n // 1 Byte = 8 pixels แนวนอน\r\n const xBytes = width / 8;\r\n\r\n let command = [];\r\n\r\n // Header: GS v 0 (Raster Bit Image)\r\n command.push(0x1d, 0x76, 0x30, 0x00);\r\n\r\n // บอกขนาดภาพ (Little Endian)\r\n command.push(xBytes % 256, Math.floor(xBytes / 256)); // xL, xH\r\n command.push(height % 256, Math.floor(height / 256)); // yL, yH\r\n\r\n // Loop ทุก Pixel แปลงเป็น Bit\r\n for (let y = 0; y < height; y++) {\r\n for (let x = 0; x < xBytes; x++) {\r\n let byte = 0;\r\n for (let bit = 0; bit < 8; bit++) {\r\n // คำนวณตำแหน่ง Pixel ใน Array (RGBA = 4 ช่องต่อ 1 pixel)\r\n const pxIndex = (y * width + x * 8 + bit) * 4;\r\n\r\n // เช็คความสว่าง (R+G+B)\r\n // ถ้าค่าต่ำกว่า 380 (ค่อนข้างมืด) ให้ถือเป็นสีดำ (Bit=1)\r\n const brightness =\r\n pixels[pxIndex] + pixels[pxIndex + 1] + pixels[pxIndex + 2];\r\n if (brightness < 380) {\r\n byte |= 1 << (7 - bit);\r\n }\r\n }\r\n command.push(byte);\r\n }\r\n }\r\n\r\n return command;\r\n}\r\n","// src/index.js\r\nimport { CMD } from \"./constants\";\r\nimport { convertCanvasToEscPos } from \"./utils\";\r\n\r\n/**\r\n * Thermal Printer Library for ESC/POS printers\r\n *\r\n * IMPORTANT: When using printThaiText(), you must load the Sarabun font in your HTML:\r\n * <link href=\"https://fonts.googleapis.com/css2?family=Sarabun:wght@400;700&display=swap\" rel=\"stylesheet\">\r\n *\r\n * Without this link, the printer will use the default system font instead of Sarabun.\r\n */\r\nexport class ThermalPrinter {\r\n constructor(driverApiUrl = \"http://localhost:9123\", width = 384) {\r\n this.driverApi = driverApiUrl;\r\n this.printerWidth = width; // 384 dots = 58mm, 576 dots = 80mm\r\n this.buffer = [];\r\n }\r\n\r\n // --- Basic Commands ---\r\n\r\n init() {\r\n this.buffer.push(...CMD.INIT);\r\n return this;\r\n }\r\n\r\n align(alignment = 1) {\r\n // 0=Left, 1=Center, 2=Right\r\n const cmd = [...CMD.ALIGN_LEFT];\r\n cmd[2] = alignment;\r\n this.buffer.push(...cmd);\r\n return this;\r\n }\r\n\r\n bold(enable = true) {\r\n this.buffer.push(...(enable ? CMD.BOLD_ON : CMD.BOLD_OFF));\r\n return this;\r\n }\r\n\r\n feed(lines = 1) {\r\n for (let i = 0; i < lines; i++) this.buffer.push(CMD.LF);\r\n return this;\r\n }\r\n\r\n cut(partial = false) {\r\n this.buffer.push(...(partial ? CMD.CUT_PARTIAL : CMD.CUT_FULL));\r\n return this;\r\n }\r\n\r\n line(text = \"\") {\r\n const encoder = new TextEncoder();\r\n const bytes = encoder.encode(text + \"\\n\");\r\n this.buffer.push(...bytes);\r\n return this;\r\n }\r\n\r\n newline(count = 1) {\r\n for (let i = 0; i < count; i++) {\r\n this.buffer.push(CMD.LF);\r\n }\r\n return this;\r\n }\r\n\r\n divider(char = \"-\", width = 32) {\r\n this.line(char.repeat(width));\r\n return this;\r\n }\r\n\r\n beep(times = 1, duration = 100) {\r\n for (let i = 0; i < times; i++) {\r\n this.buffer.push(...CMD.BEEP(duration));\r\n }\r\n return this;\r\n }\r\n\r\n drawerKick() {\r\n this.buffer.push(...CMD.DRAWER_KICK);\r\n return this;\r\n }\r\n\r\n raw(bytes) {\r\n this.buffer.push(...bytes);\r\n return this;\r\n }\r\n\r\n // --- Image & Thai Text Logic ---\r\n\r\n image(imageData) {\r\n const bytes = convertCanvasToEscPos(imageData);\r\n this.buffer.push(...bytes);\r\n return this;\r\n }\r\n\r\n /**\r\n * พิมพ์ข้อความภาษาไทย (Render เป็นรูปภาพอัตโนมัติ)\r\n *\r\n * ⚠️ REQUIRED: Sarabun font must be loaded in your HTML file:\r\n * <link href=\"https://fonts.googleapis.com/css2?family=Sarabun:wght@400;700&display=swap\" rel=\"stylesheet\">\r\n *\r\n * Without this link, the system will use the default font instead.\r\n *\r\n * @param {string} text - Thai text to print\r\n * @param {number} fontSize - Font size in pixels (default: 22)\r\n */\r\n printThaiText(text, fontSize = 22) {\r\n if (typeof document === \"undefined\") {\r\n console.error(\"Browser environment required for printThaiText\");\r\n return this;\r\n }\r\n\r\n const canvas = document.createElement(\"canvas\");\r\n const ctx = canvas.getContext(\"2d\");\r\n\r\n // ตั้งค่า Font\r\n const fontFamily = \"'Sarabun', sans-serif\";\r\n const lineHeight = fontSize + 12;\r\n const lines = text.split(\"\\n\");\r\n\r\n // คำนวณความสูง\r\n const height = lines.length * lineHeight + 20;\r\n\r\n canvas.width = this.printerWidth;\r\n canvas.height = height;\r\n\r\n // วาดพื้นหลังขาว\r\n ctx.fillStyle = \"white\";\r\n ctx.fillRect(0, 0, canvas.width, canvas.height);\r\n\r\n // วาดข้อความ\r\n ctx.fillStyle = \"black\";\r\n ctx.font = `${fontSize}px ${fontFamily}`;\r\n ctx.textBaseline = \"top\";\r\n\r\n let y = 10;\r\n for (let line of lines) {\r\n ctx.fillText(line, 10, y);\r\n y += lineHeight;\r\n }\r\n\r\n const imageData = ctx.getImageData(0, 0, this.printerWidth, height);\r\n return this.image(imageData);\r\n }\r\n\r\n // --- Execution ---\r\n\r\n clear() {\r\n this.buffer = [];\r\n return this;\r\n }\r\n\r\n getBuffer() {\r\n return new Uint8Array(this.buffer);\r\n }\r\n\r\n async print(printerName) {\r\n // ถ้าไม่ส่งชื่อมา ให้ลองดึงตัวแรก\r\n if (!printerName) {\r\n const printers = await ThermalPrinter.getPrinters(this.driverApi);\r\n if (printers.length > 0) printerName = printers[0];\r\n else throw new Error(\"No printer found.\");\r\n }\r\n\r\n try {\r\n const bufferData = this.getBuffer();\r\n const encodedName = encodeURIComponent(printerName);\r\n\r\n const res = await fetch(\r\n `${this.driverApi}/print?printer=${encodedName}`,\r\n {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/octet-stream\" },\r\n body: bufferData,\r\n },\r\n );\r\n\r\n if (!res.ok) throw new Error(\"Driver Error\");\r\n\r\n this.clear();\r\n return { success: true };\r\n } catch (e) {\r\n console.error(\"Print Failed:\", e);\r\n throw e;\r\n }\r\n }\r\n\r\n // --- Static Methods ---\r\n\r\n static async getPrinters(apiUrl = \"http://localhost:9123\") {\r\n try {\r\n const res = await fetch(`${apiUrl}/printers`);\r\n return await res.json();\r\n } catch (e) {\r\n console.error(\"Connection Error:\", e);\r\n return [];\r\n }\r\n }\r\n}\r\n"],"names":["CMD","duration","convertCanvasToEscPos","imageData","width","height","pixels","xBytes","command","y","x","byte","bit","pxIndex","ThermalPrinter","driverApiUrl","alignment","cmd","enable","lines","i","partial","text","bytes","count","char","times","fontSize","canvas","ctx","fontFamily","lineHeight","line","printerName","printers","bufferData","encodedName","e","apiUrl"],"mappings":"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"}
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
(function(h,
|
|
2
|
-
`)
|
|
1
|
+
(function(h,i){typeof exports=="object"&&typeof module<"u"?i(exports):typeof define=="function"&&define.amd?define(["exports"],i):(h=typeof globalThis<"u"?globalThis:h||self,i(h.ThermalPrinter={}))})(this,(function(h){"use strict";const i={ESC:27,GS:29,LF:10,INIT:[27,64],CUT_FULL:[29,86,66,0],CUT_PARTIAL:[29,86,65,0],BOLD_ON:[27,69,1],BOLD_OFF:[27,69,0],ALIGN_LEFT:[27,97,0],ALIGN_CENTER:[27,97,1],ALIGN_RIGHT:[27,97,2],DRAWER_KICK:[27,112,0,12,120],BEEP:(u=100)=>[27,66,u]};function p(u){const e=u.width,t=u.height,r=u.data,n=e/8;let s=[];s.push(29,118,48,0),s.push(n%256,Math.floor(n/256)),s.push(t%256,Math.floor(t/256));for(let f=0;f<t;f++)for(let c=0;c<n;c++){let l=0;for(let o=0;o<8;o++){const a=(f*e+c*8+o)*4;r[a]+r[a+1]+r[a+2]<380&&(l|=1<<7-o)}s.push(l)}return s}class x{constructor(e="http://localhost:9123",t=384){this.driverApi=e,this.printerWidth=t,this.buffer=[]}init(){return this.buffer.push(...i.INIT),this}align(e=1){const t=[...i.ALIGN_LEFT];return t[2]=e,this.buffer.push(...t),this}bold(e=!0){return this.buffer.push(...e?i.BOLD_ON:i.BOLD_OFF),this}feed(e=1){for(let t=0;t<e;t++)this.buffer.push(i.LF);return this}cut(e=!1){return this.buffer.push(...e?i.CUT_PARTIAL:i.CUT_FULL),this}line(e=""){const r=new TextEncoder().encode(e+`
|
|
2
|
+
`);return this.buffer.push(...r),this}newline(e=1){for(let t=0;t<e;t++)this.buffer.push(i.LF);return this}divider(e="-",t=32){return this.line(e.repeat(t)),this}beep(e=1,t=100){for(let r=0;r<e;r++)this.buffer.push(...i.BEEP(t));return this}drawerKick(){return this.buffer.push(...i.DRAWER_KICK),this}raw(e){return this.buffer.push(...e),this}image(e){const t=p(e);return this.buffer.push(...t),this}printThaiText(e,t=22){if(typeof document>"u")return console.error("Browser environment required for printThaiText"),this;const r=document.createElement("canvas"),n=r.getContext("2d"),s="'Sarabun', sans-serif",f=t+12,c=e.split(`
|
|
3
|
+
`),l=c.length*f+20;r.width=this.printerWidth,r.height=l,n.fillStyle="white",n.fillRect(0,0,r.width,r.height),n.fillStyle="black",n.font=`${t}px ${s}`,n.textBaseline="top";let o=10;for(let d of c)n.fillText(d,10,o),o+=f;const a=n.getImageData(0,0,this.printerWidth,l);return this.image(a)}clear(){return this.buffer=[],this}getBuffer(){return new Uint8Array(this.buffer)}async print(e){if(!e){const t=await x.getPrinters(this.driverApi);if(t.length>0)e=t[0];else throw new Error("No printer found.")}try{const t=this.getBuffer(),r=encodeURIComponent(e);if(!(await fetch(`${this.driverApi}/print?printer=${r}`,{method:"POST",headers:{"Content-Type":"application/octet-stream"},body:t})).ok)throw new Error("Driver Error");return this.clear(),{success:!0}}catch(t){throw console.error("Print Failed:",t),t}}static async getPrinters(e="http://localhost:9123"){try{return await(await fetch(`${e}/printers`)).json()}catch(t){return console.error("Connection Error:",t),[]}}}h.ThermalPrinter=x,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"})}));
|
|
4
|
+
//# sourceMappingURL=thermal-printer.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thermal-printer.umd.js","sources":["../src/constants.js","../src/utils.js","../src/index.js"],"sourcesContent":["// src/constants.js\r\nexport const CMD = {\r\n ESC: 0x1b,\r\n GS: 0x1d,\r\n LF: 0x0a,\r\n\r\n // Commands\r\n INIT: [0x1b, 0x40], // Initialize printer\r\n CUT_FULL: [0x1d, 0x56, 0x42, 0x00], // Full cut\r\n CUT_PARTIAL: [0x1d, 0x56, 0x41, 0x00], // Partial cut\r\n\r\n // Formatting\r\n BOLD_ON: [0x1b, 0x45, 0x01],\r\n BOLD_OFF: [0x1b, 0x45, 0x00],\r\n\r\n ALIGN_LEFT: [0x1b, 0x61, 0x00],\r\n ALIGN_CENTER: [0x1b, 0x61, 0x01],\r\n ALIGN_RIGHT: [0x1b, 0x61, 0x02],\r\n\r\n // Cash Drawer\r\n DRAWER_KICK: [0x1b, 0x70, 0x00, 0x0c, 0x78],\r\n\r\n // Beep (ESC B - default 100ms)\r\n BEEP: (duration = 100) => [0x1b, 0x42, duration],\r\n};\r\n","// src/utils.js\r\n\r\n/**\r\n * แปลงข้อมูลภาพ (ImageData) เป็นคำสั่ง ESC/POS GS v 0\r\n * @param {ImageData} imageData\r\n * @returns {number[]} Array ของ Byte คำสั่ง\r\n */\r\nexport function convertCanvasToEscPos(imageData) {\r\n const width = imageData.width;\r\n const height = imageData.height;\r\n const pixels = imageData.data;\r\n\r\n // 1 Byte = 8 pixels แนวนอน\r\n const xBytes = width / 8;\r\n\r\n let command = [];\r\n\r\n // Header: GS v 0 (Raster Bit Image)\r\n command.push(0x1d, 0x76, 0x30, 0x00);\r\n\r\n // บอกขนาดภาพ (Little Endian)\r\n command.push(xBytes % 256, Math.floor(xBytes / 256)); // xL, xH\r\n command.push(height % 256, Math.floor(height / 256)); // yL, yH\r\n\r\n // Loop ทุก Pixel แปลงเป็น Bit\r\n for (let y = 0; y < height; y++) {\r\n for (let x = 0; x < xBytes; x++) {\r\n let byte = 0;\r\n for (let bit = 0; bit < 8; bit++) {\r\n // คำนวณตำแหน่ง Pixel ใน Array (RGBA = 4 ช่องต่อ 1 pixel)\r\n const pxIndex = (y * width + x * 8 + bit) * 4;\r\n\r\n // เช็คความสว่าง (R+G+B)\r\n // ถ้าค่าต่ำกว่า 380 (ค่อนข้างมืด) ให้ถือเป็นสีดำ (Bit=1)\r\n const brightness =\r\n pixels[pxIndex] + pixels[pxIndex + 1] + pixels[pxIndex + 2];\r\n if (brightness < 380) {\r\n byte |= 1 << (7 - bit);\r\n }\r\n }\r\n command.push(byte);\r\n }\r\n }\r\n\r\n return command;\r\n}\r\n","// src/index.js\r\nimport { CMD } from \"./constants\";\r\nimport { convertCanvasToEscPos } from \"./utils\";\r\n\r\n/**\r\n * Thermal Printer Library for ESC/POS printers\r\n *\r\n * IMPORTANT: When using printThaiText(), you must load the Sarabun font in your HTML:\r\n * <link href=\"https://fonts.googleapis.com/css2?family=Sarabun:wght@400;700&display=swap\" rel=\"stylesheet\">\r\n *\r\n * Without this link, the printer will use the default system font instead of Sarabun.\r\n */\r\nexport class ThermalPrinter {\r\n constructor(driverApiUrl = \"http://localhost:9123\", width = 384) {\r\n this.driverApi = driverApiUrl;\r\n this.printerWidth = width; // 384 dots = 58mm, 576 dots = 80mm\r\n this.buffer = [];\r\n }\r\n\r\n // --- Basic Commands ---\r\n\r\n init() {\r\n this.buffer.push(...CMD.INIT);\r\n return this;\r\n }\r\n\r\n align(alignment = 1) {\r\n // 0=Left, 1=Center, 2=Right\r\n const cmd = [...CMD.ALIGN_LEFT];\r\n cmd[2] = alignment;\r\n this.buffer.push(...cmd);\r\n return this;\r\n }\r\n\r\n bold(enable = true) {\r\n this.buffer.push(...(enable ? CMD.BOLD_ON : CMD.BOLD_OFF));\r\n return this;\r\n }\r\n\r\n feed(lines = 1) {\r\n for (let i = 0; i < lines; i++) this.buffer.push(CMD.LF);\r\n return this;\r\n }\r\n\r\n cut(partial = false) {\r\n this.buffer.push(...(partial ? CMD.CUT_PARTIAL : CMD.CUT_FULL));\r\n return this;\r\n }\r\n\r\n line(text = \"\") {\r\n const encoder = new TextEncoder();\r\n const bytes = encoder.encode(text + \"\\n\");\r\n this.buffer.push(...bytes);\r\n return this;\r\n }\r\n\r\n newline(count = 1) {\r\n for (let i = 0; i < count; i++) {\r\n this.buffer.push(CMD.LF);\r\n }\r\n return this;\r\n }\r\n\r\n divider(char = \"-\", width = 32) {\r\n this.line(char.repeat(width));\r\n return this;\r\n }\r\n\r\n beep(times = 1, duration = 100) {\r\n for (let i = 0; i < times; i++) {\r\n this.buffer.push(...CMD.BEEP(duration));\r\n }\r\n return this;\r\n }\r\n\r\n drawerKick() {\r\n this.buffer.push(...CMD.DRAWER_KICK);\r\n return this;\r\n }\r\n\r\n raw(bytes) {\r\n this.buffer.push(...bytes);\r\n return this;\r\n }\r\n\r\n // --- Image & Thai Text Logic ---\r\n\r\n image(imageData) {\r\n const bytes = convertCanvasToEscPos(imageData);\r\n this.buffer.push(...bytes);\r\n return this;\r\n }\r\n\r\n /**\r\n * พิมพ์ข้อความภาษาไทย (Render เป็นรูปภาพอัตโนมัติ)\r\n *\r\n * ⚠️ REQUIRED: Sarabun font must be loaded in your HTML file:\r\n * <link href=\"https://fonts.googleapis.com/css2?family=Sarabun:wght@400;700&display=swap\" rel=\"stylesheet\">\r\n *\r\n * Without this link, the system will use the default font instead.\r\n *\r\n * @param {string} text - Thai text to print\r\n * @param {number} fontSize - Font size in pixels (default: 22)\r\n */\r\n printThaiText(text, fontSize = 22) {\r\n if (typeof document === \"undefined\") {\r\n console.error(\"Browser environment required for printThaiText\");\r\n return this;\r\n }\r\n\r\n const canvas = document.createElement(\"canvas\");\r\n const ctx = canvas.getContext(\"2d\");\r\n\r\n // ตั้งค่า Font\r\n const fontFamily = \"'Sarabun', sans-serif\";\r\n const lineHeight = fontSize + 12;\r\n const lines = text.split(\"\\n\");\r\n\r\n // คำนวณความสูง\r\n const height = lines.length * lineHeight + 20;\r\n\r\n canvas.width = this.printerWidth;\r\n canvas.height = height;\r\n\r\n // วาดพื้นหลังขาว\r\n ctx.fillStyle = \"white\";\r\n ctx.fillRect(0, 0, canvas.width, canvas.height);\r\n\r\n // วาดข้อความ\r\n ctx.fillStyle = \"black\";\r\n ctx.font = `${fontSize}px ${fontFamily}`;\r\n ctx.textBaseline = \"top\";\r\n\r\n let y = 10;\r\n for (let line of lines) {\r\n ctx.fillText(line, 10, y);\r\n y += lineHeight;\r\n }\r\n\r\n const imageData = ctx.getImageData(0, 0, this.printerWidth, height);\r\n return this.image(imageData);\r\n }\r\n\r\n // --- Execution ---\r\n\r\n clear() {\r\n this.buffer = [];\r\n return this;\r\n }\r\n\r\n getBuffer() {\r\n return new Uint8Array(this.buffer);\r\n }\r\n\r\n async print(printerName) {\r\n // ถ้าไม่ส่งชื่อมา ให้ลองดึงตัวแรก\r\n if (!printerName) {\r\n const printers = await ThermalPrinter.getPrinters(this.driverApi);\r\n if (printers.length > 0) printerName = printers[0];\r\n else throw new Error(\"No printer found.\");\r\n }\r\n\r\n try {\r\n const bufferData = this.getBuffer();\r\n const encodedName = encodeURIComponent(printerName);\r\n\r\n const res = await fetch(\r\n `${this.driverApi}/print?printer=${encodedName}`,\r\n {\r\n method: \"POST\",\r\n headers: { \"Content-Type\": \"application/octet-stream\" },\r\n body: bufferData,\r\n },\r\n );\r\n\r\n if (!res.ok) throw new Error(\"Driver Error\");\r\n\r\n this.clear();\r\n return { success: true };\r\n } catch (e) {\r\n console.error(\"Print Failed:\", e);\r\n throw e;\r\n }\r\n }\r\n\r\n // --- Static Methods ---\r\n\r\n static async getPrinters(apiUrl = \"http://localhost:9123\") {\r\n try {\r\n const res = await fetch(`${apiUrl}/printers`);\r\n return await res.json();\r\n } catch (e) {\r\n console.error(\"Connection Error:\", e);\r\n return [];\r\n }\r\n }\r\n}\r\n"],"names":["CMD","duration","convertCanvasToEscPos","imageData","width","height","pixels","xBytes","command","y","x","byte","bit","pxIndex","ThermalPrinter","driverApiUrl","alignment","cmd","enable","lines","i","partial","text","bytes","count","char","times","fontSize","canvas","ctx","fontFamily","lineHeight","line","printerName","printers","bufferData","encodedName","e","apiUrl"],"mappings":"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"}
|