@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 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
- - `init()` - Initialize printer
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
- - `feed(lines)` - Feed paper by specified lines
59
- - `cut(partial)` - Cut paper (partial or full)
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 (automatically renders as image)
62
- - `clear()` - Clear buffer
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 buffer to printer
65
- - `static getPrinters(apiUrl)` - Get list of available printers
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
@@ -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 o = {
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(u) {
16
- const e = u.width, t = u.height, s = u.data, r = e / 8;
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(r % 256, Math.floor(r / 256)), n.push(t % 256, Math.floor(t / 256));
19
- for (let h = 0; h < t; h++)
20
- for (let c = 0; c < r; c++) {
21
- let f = 0;
22
- for (let i = 0; i < 8; i++) {
23
- const a = (h * e + c * 8 + i) * 4;
24
- s[a] + s[a + 1] + s[a + 2] < 380 && (f |= 1 << 7 - i);
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(f);
34
+ n.push(u);
27
35
  }
28
36
  return n;
29
37
  }
30
- class x {
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(...o.INIT), this;
44
+ return this.buffer.push(...i.INIT), this;
37
45
  }
38
46
  align(e = 1) {
39
- const t = [...o.ALIGN_LEFT];
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 ? o.BOLD_ON : o.BOLD_OFF), this;
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(o.LF);
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 ? o.CUT_PARTIAL : o.CUT_FULL), this;
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 s = document.createElement("canvas"), r = s.getContext("2d"), n = "'Sarabun', sans-serif", h = t + 12, c = e.split(`
72
- `), f = c.length * h + 20;
73
- s.width = this.printerWidth, s.height = f, r.fillStyle = "white", r.fillRect(0, 0, s.width, s.height), r.fillStyle = "black", r.font = `${t}px ${n}`, r.textBaseline = "top";
74
- let i = 10;
75
- for (let l of c)
76
- r.fillText(l, 10, i), i += h;
77
- const a = r.getImageData(0, 0, this.printerWidth, f);
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 x.getPrinters(this.driverApi);
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(), s = encodeURIComponent(e);
126
+ const t = this.getBuffer(), r = encodeURIComponent(e);
95
127
  if (!(await fetch(
96
- `${this.driverApi}/print?printer=${s}`,
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
- x as ThermalPrinter
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,n){typeof exports=="object"&&typeof module<"u"?n(exports):typeof define=="function"&&define.amd?define(["exports"],n):(h=typeof globalThis<"u"?globalThis:h||self,n(h.ThermalPrinter={}))})(this,(function(h){"use strict";const n={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]};function x(d){const e=d.width,t=d.height,i=d.data,r=e/8;let s=[];s.push(29,118,48,0),s.push(r%256,Math.floor(r/256)),s.push(t%256,Math.floor(t/256));for(let c=0;c<t;c++)for(let f=0;f<r;f++){let u=0;for(let o=0;o<8;o++){const l=(c*e+f*8+o)*4;i[l]+i[l+1]+i[l+2]<380&&(u|=1<<7-o)}s.push(u)}return s}class a{constructor(e="http://localhost:9123",t=384){this.driverApi=e,this.printerWidth=t,this.buffer=[]}init(){return this.buffer.push(...n.INIT),this}align(e=1){const t=[...n.ALIGN_LEFT];return t[2]=e,this.buffer.push(...t),this}bold(e=!0){return this.buffer.push(...e?n.BOLD_ON:n.BOLD_OFF),this}feed(e=1){for(let t=0;t<e;t++)this.buffer.push(n.LF);return this}cut(e=!1){return this.buffer.push(...e?n.CUT_PARTIAL:n.CUT_FULL),this}image(e){const t=x(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 i=document.createElement("canvas"),r=i.getContext("2d"),s="'Sarabun', sans-serif",c=t+12,f=e.split(`
2
- `),u=f.length*c+20;i.width=this.printerWidth,i.height=u,r.fillStyle="white",r.fillRect(0,0,i.width,i.height),r.fillStyle="black",r.font=`${t}px ${s}`,r.textBaseline="top";let o=10;for(let p of f)r.fillText(p,10,o),o+=c;const l=r.getImageData(0,0,this.printerWidth,u);return this.image(l)}clear(){return this.buffer=[],this}getBuffer(){return new Uint8Array(this.buffer)}async print(e){if(!e){const t=await a.getPrinters(this.driverApi);if(t.length>0)e=t[0];else throw new Error("No printer found.")}try{const t=this.getBuffer(),i=encodeURIComponent(e);if(!(await fetch(`${this.driverApi}/print?printer=${i}`,{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=a,Object.defineProperty(h,Symbol.toStringTag,{value:"Module"})}));
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":"uOACO,MAAMA,EAAM,CACf,IAAK,GACL,GAAI,GACJ,GAAI,GAGJ,KAAM,CAAC,GAAM,EAAI,EACjB,SAAU,CAAC,GAAM,GAAM,GAAM,CAAI,EACjC,YAAa,CAAC,GAAM,GAAM,GAAM,CAAI,EAGpC,QAAS,CAAC,GAAM,GAAM,CAAI,EAC1B,SAAU,CAAC,GAAM,GAAM,CAAI,EAE3B,WAAY,CAAC,GAAM,GAAM,CAAI,EAC7B,aAAc,CAAC,GAAM,GAAM,CAAI,EAC/B,YAAa,CAAC,GAAM,GAAM,CAAI,EAG9B,YAAa,CAAC,GAAM,IAAM,EAAM,GAAM,GAAI,EAG1C,KAAM,CAACC,EAAW,MAAQ,CAAC,GAAM,GAAMA,CAAQ,CACnD,ECjBO,SAASC,EAAsBC,EAAW,CAC7C,MAAMC,EAAQD,EAAU,MAClBE,EAASF,EAAU,OACnBG,EAASH,EAAU,KAGnBI,EAASH,EAAQ,EAEvB,IAAII,EAAU,CAAA,EAGdA,EAAQ,KAAK,GAAM,IAAM,GAAM,CAAI,EAGnCA,EAAQ,KAAKD,EAAS,IAAK,KAAK,MAAMA,EAAS,GAAG,CAAC,EACnDC,EAAQ,KAAKH,EAAS,IAAK,KAAK,MAAMA,EAAS,GAAG,CAAC,EAGnD,QAASI,EAAI,EAAGA,EAAIJ,EAAQI,IACxB,QAASC,EAAI,EAAGA,EAAIH,EAAQG,IAAK,CAC7B,IAAIC,EAAO,EACX,QAASC,EAAM,EAAGA,EAAM,EAAGA,IAAO,CAE9B,MAAMC,GAAWJ,EAAIL,EAAQM,EAAI,EAAIE,GAAO,EAKxCN,EAAOO,CAAO,EAAIP,EAAOO,EAAU,CAAC,EAAIP,EAAOO,EAAU,CAAC,EAC7C,MACbF,GAAQ,GAAM,EAAIC,EAE1B,CACAJ,EAAQ,KAAKG,CAAI,CACrB,CAGJ,OAAOH,CACX,CCjCO,MAAMM,CAAe,CACxB,YAAYC,EAAe,wBAAyBX,EAAQ,IAAK,CAC7D,KAAK,UAAYW,EACjB,KAAK,aAAeX,EACpB,KAAK,OAAS,EAClB,CAIA,MAAO,CACH,YAAK,OAAO,KAAK,GAAGJ,EAAI,IAAI,EACrB,IACX,CAEA,MAAMgB,EAAY,EAAG,CAEjB,MAAMC,EAAM,CAAC,GAAGjB,EAAI,UAAU,EAC9B,OAAAiB,EAAI,CAAC,EAAID,EACT,KAAK,OAAO,KAAK,GAAGC,CAAG,EAChB,IACX,CAEA,KAAKC,EAAS,GAAM,CAChB,YAAK,OAAO,KAAK,GAAIA,EAASlB,EAAI,QAAUA,EAAI,QAAS,EAClD,IACX,CAEA,KAAKmB,EAAQ,EAAG,CACZ,QAASC,EAAI,EAAGA,EAAID,EAAOC,IAAK,KAAK,OAAO,KAAKpB,EAAI,EAAE,EACvD,OAAO,IACX,CAEA,IAAIqB,EAAU,GAAO,CACjB,YAAK,OAAO,KAAK,GAAIA,EAAUrB,EAAI,YAAcA,EAAI,QAAS,EACvD,IACX,CAEA,KAAKsB,EAAO,GAAI,CAEZ,MAAMC,EADU,IAAI,cACE,OAAOD,EAAO;AAAA,CAAI,EACxC,YAAK,OAAO,KAAK,GAAGC,CAAK,EAClB,IACX,CAEA,QAAQC,EAAQ,EAAG,CACf,QAASJ,EAAI,EAAGA,EAAII,EAAOJ,IACvB,KAAK,OAAO,KAAKpB,EAAI,EAAE,EAE3B,OAAO,IACX,CAEA,QAAQyB,EAAO,IAAKrB,EAAQ,GAAI,CAC5B,YAAK,KAAKqB,EAAK,OAAOrB,CAAK,CAAC,EACrB,IACX,CAEA,KAAKsB,EAAQ,EAAGzB,EAAW,IAAK,CAC5B,QAASmB,EAAI,EAAGA,EAAIM,EAAON,IACvB,KAAK,OAAO,KAAK,GAAGpB,EAAI,KAAKC,CAAQ,CAAC,EAE1C,OAAO,IACX,CAEA,YAAa,CACT,YAAK,OAAO,KAAK,GAAGD,EAAI,WAAW,EAC5B,IACX,CAEA,IAAIuB,EAAO,CACP,YAAK,OAAO,KAAK,GAAGA,CAAK,EAClB,IACX,CAIA,MAAMpB,EAAW,CACb,MAAMoB,EAAQrB,EAAsBC,CAAS,EAC7C,YAAK,OAAO,KAAK,GAAGoB,CAAK,EAClB,IACX,CAaA,cAAcD,EAAMK,EAAW,GAAI,CAC/B,GAAI,OAAO,SAAa,IACpB,eAAQ,MAAM,gDAAgD,EACvD,KAGX,MAAMC,EAAS,SAAS,cAAc,QAAQ,EACxCC,EAAMD,EAAO,WAAW,IAAI,EAG5BE,EAAa,wBACbC,EAAaJ,EAAW,GACxBR,EAAQG,EAAK,MAAM;AAAA,CAAI,EAGvBjB,EAASc,EAAM,OAASY,EAAa,GAE3CH,EAAO,MAAQ,KAAK,aACpBA,EAAO,OAASvB,EAGhBwB,EAAI,UAAY,QAChBA,EAAI,SAAS,EAAG,EAAGD,EAAO,MAAOA,EAAO,MAAM,EAG9CC,EAAI,UAAY,QAChBA,EAAI,KAAO,GAAGF,CAAQ,MAAMG,CAAU,GACtCD,EAAI,aAAe,MAEnB,IAAIpB,EAAI,GACR,QAASuB,KAAQb,EACbU,EAAI,SAASG,EAAM,GAAIvB,CAAC,EACxBA,GAAKsB,EAGT,MAAM5B,EAAY0B,EAAI,aAAa,EAAG,EAAG,KAAK,aAAcxB,CAAM,EAClE,OAAO,KAAK,MAAMF,CAAS,CAC/B,CAIA,OAAQ,CACJ,YAAK,OAAS,GACP,IACX,CAEA,WAAY,CACR,OAAO,IAAI,WAAW,KAAK,MAAM,CACrC,CAEA,MAAM,MAAM8B,EAAa,CAErB,GAAI,CAACA,EAAa,CACd,MAAMC,EAAW,MAAMpB,EAAe,YAAY,KAAK,SAAS,EAChE,GAAIoB,EAAS,OAAS,EAAGD,EAAcC,EAAS,CAAC,MAC5C,OAAM,IAAI,MAAM,mBAAmB,CAC5C,CAEA,GAAI,CACA,MAAMC,EAAa,KAAK,YAClBC,EAAc,mBAAmBH,CAAW,EAWlD,GAAI,EATQ,MAAM,MACd,GAAG,KAAK,SAAS,kBAAkBG,CAAW,GAC9C,CACI,OAAQ,OACR,QAAS,CAAE,eAAgB,0BAA0B,EACrD,KAAMD,CAC1B,CACA,GAEqB,GAAI,MAAM,IAAI,MAAM,cAAc,EAE3C,YAAK,MAAK,EACH,CAAE,QAAS,GACtB,OAASE,EAAG,CACR,cAAQ,MAAM,gBAAiBA,CAAC,EAC1BA,CACV,CACJ,CAIA,aAAa,YAAYC,EAAS,wBAAyB,CACvD,GAAI,CAEA,OAAO,MADK,MAAM,MAAM,GAAGA,CAAM,WAAW,GAC3B,MACrB,OAASD,EAAG,CACR,eAAQ,MAAM,oBAAqBA,CAAC,EAC7B,EACX,CACJ,CACJ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naphatjm/cdh-thermal-printer",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Thai Thermal Printer Library via Local Driver",
5
5
  "type": "module",
6
6
  "files": [