@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/dist/index.d.ts CHANGED
@@ -11,6 +11,16 @@ export interface PrintResponse {
11
11
  message?: string;
12
12
  }
13
13
 
14
+ /**
15
+ * Printer information from Windows/system
16
+ */
17
+ export interface PrinterInfo {
18
+ name: string;
19
+ driver: string;
20
+ port: string;
21
+ status_text: string;
22
+ }
23
+
14
24
  /**
15
25
  * Convert Thai text to ImageData (Client-side only)
16
26
  *
@@ -56,33 +66,33 @@ export function textToImageData(
56
66
  ): ImageData;
57
67
 
58
68
  /**
59
- * ThermalPrinter Service
69
+ * ThermalPrinter Service with Auto-Discovery
60
70
  * Fluent API for controlling thermal printer with ESC/POS commands
71
+ * Automatically discovers printer driver on ports 9123-9130
61
72
  *
62
73
  * @example
63
74
  * ```javascript
64
- * const printer = new ThermalPrinter("http://localhost:9123", 384);
75
+ * const printer = new ThermalPrinter(384); // 384 dots = 58mm (default)
65
76
  * printer
66
77
  * .init()
67
78
  * .align(1)
68
79
  * .printThaiText("สวัสดี")
69
80
  * .feed(2)
70
81
  * .cut()
71
- * .print("Printer Name");
82
+ * .print("Printer Name"); // Auto-discovers driver
72
83
  * ```
73
84
  */
74
85
  export class ThermalPrinter {
75
86
  /**
76
- * Initialize ThermalPrinter instance
77
- * @param driverApiUrl - URL of the thermal printer driver API (default: http://localhost:9123)
87
+ * Initialize ThermalPrinter instance with auto-discovery
78
88
  * @param width - Printer width in dots (384 = 58mm, 576 = 80mm, default: 384)
79
89
  */
80
- constructor(driverApiUrl?: string, width?: number);
90
+ constructor(width?: number);
81
91
 
82
92
  /**
83
- * Driver API URL
93
+ * Driver API URL (null until driver is discovered)
84
94
  */
85
- readonly driverApi: string;
95
+ driverApi: string | null;
86
96
 
87
97
  /**
88
98
  * Printer width in dots
@@ -211,21 +221,42 @@ export class ThermalPrinter {
211
221
  getBuffer(): Uint8Array;
212
222
 
213
223
  /**
214
- * Print to specific printer
224
+ * Auto-discover printer driver on localhost:9123-9130
225
+ * Performs health check and verifies it's a CDH-Driver service
226
+ * Caches discovered URL for subsequent calls
227
+ *
228
+ * @returns Promise<boolean> - true if driver found, false otherwise
229
+ * @throws Error if driver not found after scanning all ports
230
+ */
231
+ findDriver(): Promise<boolean>;
232
+
233
+ /**
234
+ * Print to specific printer (auto-discovers driver if needed)
215
235
  * @param printerName - Name of printer to print to (optional, will use first available if not provided)
216
236
  * @returns Promise resolving to print response
217
- * @throws Error if printer not found or print failed
237
+ * @throws Error if driver not found or print failed
218
238
  */
219
239
  print(printerName?: string): Promise<PrintResponse>;
220
240
 
221
241
  // --- Static Methods ---
222
242
 
223
243
  /**
224
- * Get list of available printers
225
- * @param apiUrl - Driver API URL (default: http://localhost:9123)
226
- * @returns Promise resolving to array of printer names
244
+ * Get list of available printers from Windows/system
245
+ * Auto-discovers driver if URL not provided
246
+ * @param overrideUrl - Driver API URL (optional, will auto-discover if not provided)
247
+ * @returns Promise resolving to array of PrinterInfo objects
248
+ *
249
+ * @example
250
+ * ```javascript
251
+ * const printers = await ThermalPrinter.getPrinters();
252
+ * // [
253
+ * // { name: "thermal printer", driver: "Generic / Text Only", port: "USB001", status_text: "Ready" },
254
+ * // { name: "HP LaserJet M1530 MFP Series PCL 6", ... },
255
+ * // ...
256
+ * // ]
257
+ * ```
227
258
  */
228
- static getPrinters(apiUrl?: string): Promise<string[]>;
259
+ static getPrinters(overrideUrl?: string): Promise<PrinterInfo[]>;
229
260
  }
230
261
 
231
262
  export {};
@@ -1,4 +1,4 @@
1
- const s = {
1
+ const o = {
2
2
  ESC: 27,
3
3
  GS: 29,
4
4
  LF: 10,
@@ -18,86 +18,91 @@ const s = {
18
18
  // Cash Drawer
19
19
  DRAWER_KICK: [27, 112, 0, 12, 120],
20
20
  // Beep (ESC B - default 100ms)
21
- BEEP: (a = 100) => [27, 66, a]
21
+ BEEP: (l = 100) => [27, 66, l]
22
22
  };
23
- function d(a) {
24
- const e = a.width, t = a.height, r = a.data, n = e / 8;
25
- let o = [];
26
- o.push(29, 118, 48, 0), o.push(n % 256, Math.floor(n / 256)), o.push(t % 256, Math.floor(t / 256));
27
- for (let h = 0; h < t; h++)
28
- for (let c = 0; c < n; c++) {
23
+ function b(l, t = 382) {
24
+ const r = l.width, e = l.height, n = l.data, s = Math.ceil(r / 8) * 8 / 8;
25
+ let i = [];
26
+ i.push(29, 118, 48, 0), i.push(s % 256, Math.floor(s / 256)), i.push(e % 256, Math.floor(e / 256));
27
+ for (let h = 0; h < e; h++)
28
+ for (let c = 0; c < s; c++) {
29
29
  let f = 0;
30
- for (let i = 0; i < 8; i++) {
31
- const l = (h * e + c * 8 + i) * 4;
32
- r[l] + r[l + 1] + r[l + 2] < 380 && (f |= 1 << 7 - i);
30
+ for (let u = 0; u < 8; u++) {
31
+ const p = c * 8 + u;
32
+ if (p >= r)
33
+ continue;
34
+ const d = (h * r + p) * 4;
35
+ n[d] + // Red
36
+ n[d + 1] + // Green
37
+ n[d + 2] < t && (f |= 1 << 7 - u);
33
38
  }
34
- o.push(f);
39
+ i.push(f);
35
40
  }
36
- return o;
41
+ return i;
37
42
  }
38
- function p(a, e = 384, t = 22) {
43
+ function g(l, t = 384, r = 22) {
39
44
  if (typeof document > "u")
40
45
  throw new Error(
41
46
  "textToImageData() requires browser environment with Canvas API"
42
47
  );
43
- const r = document.createElement("canvas"), n = r.getContext("2d"), o = "'Sarabun', sans-serif", h = t + 12, c = a.split(`
44
- `), f = c.length * h + 20;
45
- r.width = e, r.height = f, n.fillStyle = "white", n.fillRect(0, 0, r.width, r.height), n.fillStyle = "black", n.font = `${t}px ${o}`, n.textBaseline = "top";
46
- let i = 10;
47
- for (let l of c)
48
- n.fillText(l, 10, i), i += h;
49
- return n.getImageData(0, 0, e, f);
48
+ const e = document.createElement("canvas"), n = e.getContext("2d"), a = "'Sarabun', sans-serif", s = r + 12, i = l.split(`
49
+ `), h = i.length * s + 20;
50
+ e.width = t, e.height = h, n.fillStyle = "white", n.fillRect(0, 0, e.width, e.height), n.fillStyle = "black", n.font = `${r}px ${a}`, n.textBaseline = "top";
51
+ let c = 10;
52
+ for (let f of i)
53
+ n.fillText(f, 10, c), c += s;
54
+ return n.getImageData(0, 0, t, h);
50
55
  }
51
56
  class x {
52
- constructor(e = "http://localhost:9123", t = 384) {
53
- this.driverApi = e, this.printerWidth = t, this.buffer = [];
57
+ constructor(t = 384) {
58
+ this.driverApi = null, this.printerWidth = t, this.buffer = [];
54
59
  }
55
60
  // --- Basic Commands ---
56
61
  init() {
57
- return this.buffer.push(...s.INIT), this;
62
+ return this.buffer.push(...o.INIT), this;
58
63
  }
59
- align(e = 1) {
60
- const t = [...s.ALIGN_LEFT];
61
- return t[2] = e, this.buffer.push(...t), this;
64
+ align(t = 1) {
65
+ const r = [...o.ALIGN_LEFT];
66
+ return r[2] = t, this.buffer.push(...r), this;
62
67
  }
63
- bold(e = !0) {
64
- return this.buffer.push(...e ? s.BOLD_ON : s.BOLD_OFF), this;
68
+ bold(t = !0) {
69
+ return this.buffer.push(...t ? o.BOLD_ON : o.BOLD_OFF), this;
65
70
  }
66
- feed(e = 1) {
67
- for (let t = 0; t < e; t++) this.buffer.push(s.LF);
71
+ feed(t = 1) {
72
+ for (let r = 0; r < t; r++) this.buffer.push(o.LF);
68
73
  return this;
69
74
  }
70
- cut(e = !1) {
71
- return this.buffer.push(...e ? s.CUT_PARTIAL : s.CUT_FULL), this;
75
+ cut(t = !1) {
76
+ return this.buffer.push(...t ? o.CUT_PARTIAL : o.CUT_FULL), this;
72
77
  }
73
- line(e = "") {
74
- const r = new TextEncoder().encode(e + `
78
+ line(t = "") {
79
+ const e = new TextEncoder().encode(t + `
75
80
  `);
76
- return this.buffer.push(...r), this;
81
+ return this.buffer.push(...e), this;
77
82
  }
78
- newline(e = 1) {
79
- for (let t = 0; t < e; t++)
80
- this.buffer.push(s.LF);
83
+ newline(t = 1) {
84
+ for (let r = 0; r < t; r++)
85
+ this.buffer.push(o.LF);
81
86
  return this;
82
87
  }
83
- divider(e = "-", t = 32) {
84
- return this.line(e.repeat(t)), this;
88
+ divider(t = "-", r = 32) {
89
+ return this.line(t.repeat(r)), this;
85
90
  }
86
- beep(e = 1, t = 100) {
87
- for (let r = 0; r < e; r++)
88
- this.buffer.push(...s.BEEP(t));
91
+ beep(t = 1, r = 100) {
92
+ for (let e = 0; e < t; e++)
93
+ this.buffer.push(...o.BEEP(r));
89
94
  return this;
90
95
  }
91
96
  drawerKick() {
92
- return this.buffer.push(...s.DRAWER_KICK), this;
97
+ return this.buffer.push(...o.DRAWER_KICK), this;
93
98
  }
94
- raw(e) {
95
- return this.buffer.push(...e), this;
99
+ raw(t) {
100
+ return this.buffer.push(...t), this;
96
101
  }
97
102
  // --- Image & Thai Text Logic ---
98
- image(e) {
99
- const t = d(e);
100
- return this.buffer.push(...t), this;
103
+ image(t) {
104
+ const r = b(t);
105
+ return this.buffer.push(...r), this;
101
106
  }
102
107
  /**
103
108
  * พิมพ์ข้อความภาษาไทย (Render เป็นรูปภาพอัตโนมัติ)
@@ -110,17 +115,17 @@ class x {
110
115
  * @param {string} text - Thai text to print
111
116
  * @param {number} fontSize - Font size in pixels (default: 22)
112
117
  */
113
- printThaiText(e, t = 22) {
118
+ printThaiText(t, r = 22) {
114
119
  if (typeof document > "u")
115
120
  return console.error("Browser environment required for printThaiText"), this;
116
- const r = document.createElement("canvas"), n = r.getContext("2d"), o = "'Sarabun', sans-serif", h = t + 12, c = e.split(`
117
- `), f = c.length * h + 20;
118
- r.width = this.printerWidth, r.height = f, n.fillStyle = "white", n.fillRect(0, 0, r.width, r.height), n.fillStyle = "black", n.font = `${t}px ${o}`, n.textBaseline = "top";
119
- let i = 10;
120
- for (let u of c)
121
- n.fillText(u, 10, i), i += h;
122
- const l = n.getImageData(0, 0, this.printerWidth, f);
123
- return this.image(l);
121
+ const e = document.createElement("canvas"), n = e.getContext("2d"), a = "'Sarabun', sans-serif", s = r + 12, i = t.split(`
122
+ `), h = i.length * s + 20;
123
+ e.width = this.printerWidth, e.height = h, n.fillStyle = "white", n.fillRect(0, 0, e.width, e.height), n.fillStyle = "black", n.font = `${r}px ${a}`, n.textBaseline = "top";
124
+ let c = 10;
125
+ for (let u of i)
126
+ n.fillText(u, 10, c), c += s;
127
+ const f = n.getImageData(0, 0, this.printerWidth, h);
128
+ return this.image(f);
124
129
  }
125
130
  // --- Execution ---
126
131
  clear() {
@@ -129,38 +134,74 @@ class x {
129
134
  getBuffer() {
130
135
  return new Uint8Array(this.buffer);
131
136
  }
132
- async print(e) {
133
- if (!e) {
134
- const t = await x.getPrinters(this.driverApi);
135
- if (t.length > 0) e = t[0];
136
- else throw new Error("No printer found.");
137
+ // --- 🔥 Auto Discovery & Print Logic 🔥 ---
138
+ /**
139
+ * วิ่งหา Driver ใน Port 9123-9130
140
+ * (Lazy Mode: เรียกใช้เมื่อจำเป็น)
141
+ */
142
+ async findDriver() {
143
+ if (this.driverApi)
144
+ try {
145
+ if ((await fetch(`${this.driverApi}/health`)).ok) return !0;
146
+ } catch {
147
+ console.warn("Driver connection lost, rescanning..."), this.driverApi = null;
148
+ }
149
+ for (let t = 9123; t <= 9130; t++) {
150
+ const r = `http://localhost:${t}`;
151
+ try {
152
+ const e = new AbortController(), n = setTimeout(() => e.abort(), 100), a = await fetch(`${r}/health`, {
153
+ signal: e.signal
154
+ });
155
+ if (clearTimeout(n), a.ok && (await a.json()).service === "CDH-Driver")
156
+ return this.driverApi = r, console.log(`✅ Driver found at: ${r}`), !0;
157
+ } catch {
158
+ continue;
159
+ }
160
+ }
161
+ return console.error("❌ CDH Driver not found (Is .exe running?)"), !1;
162
+ }
163
+ async print(t) {
164
+ if (!await this.findDriver())
165
+ throw new Error(
166
+ "Cannot connect to Printer Driver. Please run the application."
167
+ );
168
+ if (!t) {
169
+ const e = await x.getPrinters(this.driverApi);
170
+ if (e.length > 0) t = e[0].name;
171
+ else throw new Error("No printer found in Windows settings.");
137
172
  }
138
173
  try {
139
- const t = this.getBuffer(), r = encodeURIComponent(e);
174
+ const e = this.getBuffer(), n = encodeURIComponent(t);
140
175
  if (!(await fetch(
141
- `${this.driverApi}/print?printer=${r}`,
176
+ `${this.driverApi}/print?printer=${n}`,
142
177
  {
143
178
  method: "POST",
144
179
  headers: { "Content-Type": "application/octet-stream" },
145
- body: t
180
+ body: e
146
181
  }
147
- )).ok) throw new Error("Driver Error");
182
+ )).ok) throw new Error("Driver returned error");
148
183
  return this.clear(), { success: !0 };
149
- } catch (t) {
150
- throw console.error("Print Failed:", t), t;
184
+ } catch (e) {
185
+ throw console.error("Print Failed:", e), e;
151
186
  }
152
187
  }
153
- // --- Static Methods ---
154
- static async getPrinters(e = "http://localhost:9123") {
188
+ // Static Method: ต้องสร้าง instance ชั่วคราวไปหา driver
189
+ static async getPrinters(t = null) {
190
+ let r = t;
191
+ if (!r) {
192
+ const e = new x();
193
+ if (await e.findDriver()) r = e.driverApi;
194
+ else return [];
195
+ }
155
196
  try {
156
- return await (await fetch(`${e}/printers`)).json();
157
- } catch (t) {
158
- return console.error("Connection Error:", t), [];
197
+ return await (await fetch(`${r}/printers`)).json();
198
+ } catch (e) {
199
+ return console.error("Get Printers Failed:", e), [];
159
200
  }
160
201
  }
161
202
  }
162
203
  export {
163
204
  x as ThermalPrinter,
164
- p as textToImageData
205
+ g as textToImageData
165
206
  };
166
207
  //# sourceMappingURL=thermal-printer.es.js.map
@@ -1 +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/**\r\n * แปลงข้อความเป็น ImageData (Client-side only)\r\n * ต้องใช้ใน Browser environment เท่านั้น\r\n *\r\n * ⚠️ REQUIRED: Sarabun font must be loaded 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 * @param {string} text - Thai text to convert\r\n * @param {number} width - Canvas width in pixels (default: 384)\r\n * @param {number} fontSize - Font size in pixels (default: 22)\r\n * @returns {ImageData} Canvas ImageData object\r\n * @throws {Error} If not in browser environment\r\n *\r\n * @example\r\n * // Client-side (React/Next.js)\r\n * const imageData = textToImageData(\"สวัสดี\", 384, 22);\r\n * await fetch('/api/print', {\r\n * body: JSON.stringify({\r\n * data: Array.from(imageData.data),\r\n * width: imageData.width,\r\n * height: imageData.height,\r\n * })\r\n * });\r\n */\r\nexport function textToImageData(text, width = 384, fontSize = 22) {\r\n // ตรวจสอบว่าเป็น Browser environment\r\n if (typeof document === \"undefined\") {\r\n throw new Error(\r\n \"textToImageData() requires browser environment with Canvas API\",\r\n );\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 = width;\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 // ดึง ImageData\r\n return ctx.getImageData(0, 0, width, height);\r\n}\r\n","// src/index.js\r\nimport { CMD } from \"./constants\";\r\nimport { convertCanvasToEscPos, textToImageData } from \"./utils\";\r\n\r\n/**\r\n * Thermal Printer Library for ESC/POS printers\r\n *\r\n * IMPORTANT: When using printThaiText() or textToImageData(), 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 { textToImageData } from \"./utils\";\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","textToImageData","text","fontSize","canvas","ctx","fontFamily","lineHeight","lines","line","ThermalPrinter","driverApiUrl","alignment","cmd","enable","i","partial","bytes","count","char","times","printerName","printers","bufferData","encodedName","e","apiUrl"],"mappings":"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"}
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 * รองรับภาพ QR Code, Logo, Thai Text เป็นต้น\r\n *\r\n * @param {ImageData|Object} imageData - ImageData object หรือ ImageDataLike {data, width, height}\r\n * @param {number} brightnessThreshold - ค่าเกณฑ์สำหรับแยกสีดำ/ขาว (0-765, default: 382)\r\n * @returns {number[]} Array ของ Byte คำสั่ง ESC/POS\r\n *\r\n * @example\r\n * // QR Code (300x300px)\r\n * const qrCanvas = document.createElement('canvas');\r\n * await QRCode.toCanvas(qrCanvas, 'https://example.com', { width: 300 });\r\n * const imageData = qrCanvas.getContext('2d').getImageData(0, 0, 300, 300);\r\n * const bytes = convertCanvasToEscPos(imageData);\r\n */\r\nexport function convertCanvasToEscPos(imageData, brightnessThreshold = 382) {\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 แนวนอน (pad ให้ได้ width ที่หารด้วย 8 ลงตัว)\r\n const paddedWidth = Math.ceil(width / 8) * 8;\r\n const xBytes = paddedWidth / 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 const pixelX = x * 8 + bit;\r\n\r\n // ถ้าเกินขอบภาพ ให้เป็นสีขาว (Bit=0)\r\n if (pixelX >= width) {\r\n continue;\r\n }\r\n\r\n // คำนวณตำแหน่ง Pixel ใน Array (RGBA = 4 ช่องต่อ 1 pixel)\r\n const pxIndex = (y * width + pixelX) * 4;\r\n\r\n // เช็คความสว่าง: R+G+B (ระหว่าง 0-765)\r\n // ถ้าค่าต่ำกว่า threshold ให้ถือเป็นสีดำ (Bit=1)\r\n const brightness =\r\n pixels[pxIndex] + // Red\r\n pixels[pxIndex + 1] + // Green\r\n pixels[pxIndex + 2]; // Blue\r\n // (ไม่ใช้ Alpha channel)\r\n\r\n if (brightness < brightnessThreshold) {\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/**\r\n * แปลงข้อความเป็น ImageData (Client-side only)\r\n * ต้องใช้ใน Browser environment เท่านั้น\r\n *\r\n * ⚠️ REQUIRED: Sarabun font must be loaded 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 * @param {string} text - Thai text to convert\r\n * @param {number} width - Canvas width in pixels (default: 384)\r\n * @param {number} fontSize - Font size in pixels (default: 22)\r\n * @returns {ImageData} Canvas ImageData object\r\n * @throws {Error} If not in browser environment\r\n *\r\n * @example\r\n * // Client-side (React/Next.js)\r\n * const imageData = textToImageData(\"สวัสดี\", 384, 22);\r\n * await fetch('/api/print', {\r\n * body: JSON.stringify({\r\n * data: Array.from(imageData.data),\r\n * width: imageData.width,\r\n * height: imageData.height,\r\n * })\r\n * });\r\n */\r\nexport function textToImageData(text, width = 384, fontSize = 22) {\r\n // ตรวจสอบว่าเป็น Browser environment\r\n if (typeof document === \"undefined\") {\r\n throw new Error(\r\n \"textToImageData() requires browser environment with Canvas API\",\r\n );\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 = width;\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 // ดึง ImageData\r\n return ctx.getImageData(0, 0, width, height);\r\n}\r\n","// src/index.js\r\nimport { CMD } from \"./constants\";\r\nimport { convertCanvasToEscPos, textToImageData } from \"./utils\";\r\n\r\n/**\r\n * Thermal Printer Library for ESC/POS printers\r\n *\r\n * IMPORTANT: When using printThaiText() or textToImageData(), 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 { textToImageData } from \"./utils\";\r\nexport class ThermalPrinter {\r\n constructor(width = 384) {\r\n this.driverApi = null; // เริ่มต้นยังไม่มี URL\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 // --- 🔥 Auto Discovery & Print Logic 🔥 ---\r\n\r\n /**\r\n * วิ่งหา Driver ใน Port 9123-9130\r\n * (Lazy Mode: เรียกใช้เมื่อจำเป็น)\r\n */\r\n async findDriver() {\r\n // 1. ถ้ามี URL เดิมอยู่ ลองเช็คว่ายังอยู่ไหม (Ping)\r\n if (this.driverApi) {\r\n try {\r\n const res = await fetch(`${this.driverApi}/health`);\r\n if (res.ok) return true; // ยังอยู่ดี\r\n } catch (e) {\r\n console.warn(\"Driver connection lost, rescanning...\");\r\n this.driverApi = null;\r\n }\r\n }\r\n\r\n // 2. ถ้าไม่มี หรือหลุด ให้วนหาใหม่\r\n for (let port = 9123; port <= 9130; port++) {\r\n const url = `http://localhost:${port}`;\r\n try {\r\n // ตั้ง Timeout สั้นๆ (100ms) เพื่อความเร็ว\r\n const controller = new AbortController();\r\n const timeoutId = setTimeout(() => controller.abort(), 100);\r\n\r\n const res = await fetch(`${url}/health`, {\r\n signal: controller.signal,\r\n });\r\n clearTimeout(timeoutId);\r\n\r\n if (res.ok) {\r\n const data = await res.json();\r\n // เช็ค Signature ว่าใช่ Driver ของเราไหม\r\n if (data.service === \"CDH-Driver\") {\r\n this.driverApi = url;\r\n console.log(`✅ Driver found at: ${url}`);\r\n return true;\r\n }\r\n }\r\n } catch (e) {\r\n // Port ไม่ว่าง หรือไม่ใช่ของเรา -> ข้าม\r\n continue;\r\n }\r\n }\r\n console.error(\"❌ CDH Driver not found (Is .exe running?)\");\r\n return false;\r\n }\r\n\r\n async print(printerName) {\r\n // 1. หา Driver ก่อน (Lazy Discovery)\r\n const found = await this.findDriver();\r\n if (!found) {\r\n throw new Error(\r\n \"Cannot connect to Printer Driver. Please run the application.\",\r\n );\r\n }\r\n\r\n // 2. ถ้าไม่ระบุชื่อ Printer ให้ดึงตัวแรก\r\n if (!printerName) {\r\n const printers = await ThermalPrinter.getPrinters(this.driverApi);\r\n if (printers.length > 0) printerName = printers[0].name;\r\n else throw new Error(\"No printer found in Windows settings.\");\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 returned 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 Method: ต้องสร้าง instance ชั่วคราวไปหา driver\r\n static async getPrinters(overrideUrl = null) {\r\n let apiUrl = overrideUrl;\r\n\r\n if (!apiUrl) {\r\n const temp = new ThermalPrinter();\r\n const found = await temp.findDriver();\r\n if (found) apiUrl = temp.driverApi;\r\n else return [];\r\n }\r\n\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(\"Get Printers Failed:\", e);\r\n return [];\r\n }\r\n }\r\n}\r\n"],"names":["CMD","duration","convertCanvasToEscPos","imageData","brightnessThreshold","width","height","pixels","xBytes","command","y","x","byte","bit","pixelX","pxIndex","textToImageData","text","fontSize","canvas","ctx","fontFamily","lineHeight","lines","line","ThermalPrinter","alignment","cmd","enable","i","partial","bytes","count","char","times","port","url","controller","timeoutId","res","printerName","printers","bufferData","encodedName","overrideUrl","apiUrl","temp"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- (function(a,i){typeof exports=="object"&&typeof module<"u"?i(exports):typeof define=="function"&&define.amd?define(["exports"],i):(a=typeof globalThis<"u"?globalThis:a||self,i(a.ThermalPrinter={}))})(this,(function(a){"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:(l=100)=>[27,66,l]};function p(l){const e=l.width,t=l.height,r=l.data,n=e/8;let o=[];o.push(29,118,48,0),o.push(n%256,Math.floor(n/256)),o.push(t%256,Math.floor(t/256));for(let h=0;h<t;h++)for(let c=0;c<n;c++){let f=0;for(let s=0;s<8;s++){const u=(h*e+c*8+s)*4;r[u]+r[u+1]+r[u+2]<380&&(f|=1<<7-s)}o.push(f)}return o}function b(l,e=384,t=22){if(typeof document>"u")throw new Error("textToImageData() requires browser environment with Canvas API");const r=document.createElement("canvas"),n=r.getContext("2d"),o="'Sarabun', sans-serif",h=t+12,c=l.split(`
2
- `),f=c.length*h+20;r.width=e,r.height=f,n.fillStyle="white",n.fillRect(0,0,r.width,r.height),n.fillStyle="black",n.font=`${t}px ${o}`,n.textBaseline="top";let s=10;for(let u of c)n.fillText(u,10,s),s+=h;return n.getImageData(0,0,e,f)}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+`
3
- `);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"),o="'Sarabun', sans-serif",h=t+12,c=e.split(`
4
- `),f=c.length*h+20;r.width=this.printerWidth,r.height=f,n.fillStyle="white",n.fillRect(0,0,r.width,r.height),n.fillStyle="black",n.font=`${t}px ${o}`,n.textBaseline="top";let s=10;for(let d of c)n.fillText(d,10,s),s+=h;const u=n.getImageData(0,0,this.printerWidth,f);return this.image(u)}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),[]}}}a.ThermalPrinter=x,a.textToImageData=b,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(f,i){typeof exports=="object"&&typeof module<"u"?i(exports):typeof define=="function"&&define.amd?define(["exports"],i):(f=typeof globalThis<"u"?globalThis:f||self,i(f.ThermalPrinter={}))})(this,(function(f){"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:(l=100)=>[27,66,l]};function g(l,t=382){const r=l.width,e=l.height,n=l.data,o=Math.ceil(r/8)*8/8;let s=[];s.push(29,118,48,0),s.push(o%256,Math.floor(o/256)),s.push(e%256,Math.floor(e/256));for(let h=0;h<e;h++)for(let c=0;c<o;c++){let u=0;for(let d=0;d<8;d++){const b=c*8+d;if(b>=r)continue;const x=(h*r+b)*4;n[x]+n[x+1]+n[x+2]<t&&(u|=1<<7-d)}s.push(u)}return s}function w(l,t=384,r=22){if(typeof document>"u")throw new Error("textToImageData() requires browser environment with Canvas API");const e=document.createElement("canvas"),n=e.getContext("2d"),a="'Sarabun', sans-serif",o=r+12,s=l.split(`
2
+ `),h=s.length*o+20;e.width=t,e.height=h,n.fillStyle="white",n.fillRect(0,0,e.width,e.height),n.fillStyle="black",n.font=`${r}px ${a}`,n.textBaseline="top";let c=10;for(let u of s)n.fillText(u,10,c),c+=o;return n.getImageData(0,0,t,h)}class p{constructor(t=384){this.driverApi=null,this.printerWidth=t,this.buffer=[]}init(){return this.buffer.push(...i.INIT),this}align(t=1){const r=[...i.ALIGN_LEFT];return r[2]=t,this.buffer.push(...r),this}bold(t=!0){return this.buffer.push(...t?i.BOLD_ON:i.BOLD_OFF),this}feed(t=1){for(let r=0;r<t;r++)this.buffer.push(i.LF);return this}cut(t=!1){return this.buffer.push(...t?i.CUT_PARTIAL:i.CUT_FULL),this}line(t=""){const e=new TextEncoder().encode(t+`
3
+ `);return this.buffer.push(...e),this}newline(t=1){for(let r=0;r<t;r++)this.buffer.push(i.LF);return this}divider(t="-",r=32){return this.line(t.repeat(r)),this}beep(t=1,r=100){for(let e=0;e<t;e++)this.buffer.push(...i.BEEP(r));return this}drawerKick(){return this.buffer.push(...i.DRAWER_KICK),this}raw(t){return this.buffer.push(...t),this}image(t){const r=g(t);return this.buffer.push(...r),this}printThaiText(t,r=22){if(typeof document>"u")return console.error("Browser environment required for printThaiText"),this;const e=document.createElement("canvas"),n=e.getContext("2d"),a="'Sarabun', sans-serif",o=r+12,s=t.split(`
4
+ `),h=s.length*o+20;e.width=this.printerWidth,e.height=h,n.fillStyle="white",n.fillRect(0,0,e.width,e.height),n.fillStyle="black",n.font=`${r}px ${a}`,n.textBaseline="top";let c=10;for(let d of s)n.fillText(d,10,c),c+=o;const u=n.getImageData(0,0,this.printerWidth,h);return this.image(u)}clear(){return this.buffer=[],this}getBuffer(){return new Uint8Array(this.buffer)}async findDriver(){if(this.driverApi)try{if((await fetch(`${this.driverApi}/health`)).ok)return!0}catch{console.warn("Driver connection lost, rescanning..."),this.driverApi=null}for(let t=9123;t<=9130;t++){const r=`http://localhost:${t}`;try{const e=new AbortController,n=setTimeout(()=>e.abort(),100),a=await fetch(`${r}/health`,{signal:e.signal});if(clearTimeout(n),a.ok&&(await a.json()).service==="CDH-Driver")return this.driverApi=r,console.log(`✅ Driver found at: ${r}`),!0}catch{continue}}return console.error("❌ CDH Driver not found (Is .exe running?)"),!1}async print(t){if(!await this.findDriver())throw new Error("Cannot connect to Printer Driver. Please run the application.");if(!t){const e=await p.getPrinters(this.driverApi);if(e.length>0)t=e[0].name;else throw new Error("No printer found in Windows settings.")}try{const e=this.getBuffer(),n=encodeURIComponent(t);if(!(await fetch(`${this.driverApi}/print?printer=${n}`,{method:"POST",headers:{"Content-Type":"application/octet-stream"},body:e})).ok)throw new Error("Driver returned error");return this.clear(),{success:!0}}catch(e){throw console.error("Print Failed:",e),e}}static async getPrinters(t=null){let r=t;if(!r){const e=new p;if(await e.findDriver())r=e.driverApi;else return[]}try{return await(await fetch(`${r}/printers`)).json()}catch(e){return console.error("Get Printers Failed:",e),[]}}}f.ThermalPrinter=p,f.textToImageData=w,Object.defineProperty(f,Symbol.toStringTag,{value:"Module"})}));
5
5
  //# sourceMappingURL=thermal-printer.umd.js.map
@@ -1 +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/**\r\n * แปลงข้อความเป็น ImageData (Client-side only)\r\n * ต้องใช้ใน Browser environment เท่านั้น\r\n *\r\n * ⚠️ REQUIRED: Sarabun font must be loaded 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 * @param {string} text - Thai text to convert\r\n * @param {number} width - Canvas width in pixels (default: 384)\r\n * @param {number} fontSize - Font size in pixels (default: 22)\r\n * @returns {ImageData} Canvas ImageData object\r\n * @throws {Error} If not in browser environment\r\n *\r\n * @example\r\n * // Client-side (React/Next.js)\r\n * const imageData = textToImageData(\"สวัสดี\", 384, 22);\r\n * await fetch('/api/print', {\r\n * body: JSON.stringify({\r\n * data: Array.from(imageData.data),\r\n * width: imageData.width,\r\n * height: imageData.height,\r\n * })\r\n * });\r\n */\r\nexport function textToImageData(text, width = 384, fontSize = 22) {\r\n // ตรวจสอบว่าเป็น Browser environment\r\n if (typeof document === \"undefined\") {\r\n throw new Error(\r\n \"textToImageData() requires browser environment with Canvas API\",\r\n );\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 = width;\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 // ดึง ImageData\r\n return ctx.getImageData(0, 0, width, height);\r\n}\r\n","// src/index.js\r\nimport { CMD } from \"./constants\";\r\nimport { convertCanvasToEscPos, textToImageData } from \"./utils\";\r\n\r\n/**\r\n * Thermal Printer Library for ESC/POS printers\r\n *\r\n * IMPORTANT: When using printThaiText() or textToImageData(), 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 { textToImageData } from \"./utils\";\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","textToImageData","text","fontSize","canvas","ctx","fontFamily","lineHeight","lines","line","ThermalPrinter","driverApiUrl","alignment","cmd","enable","i","partial","bytes","count","char","times","printerName","printers","bufferData","encodedName","e","apiUrl"],"mappings":"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"}
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 * รองรับภาพ QR Code, Logo, Thai Text เป็นต้น\r\n *\r\n * @param {ImageData|Object} imageData - ImageData object หรือ ImageDataLike {data, width, height}\r\n * @param {number} brightnessThreshold - ค่าเกณฑ์สำหรับแยกสีดำ/ขาว (0-765, default: 382)\r\n * @returns {number[]} Array ของ Byte คำสั่ง ESC/POS\r\n *\r\n * @example\r\n * // QR Code (300x300px)\r\n * const qrCanvas = document.createElement('canvas');\r\n * await QRCode.toCanvas(qrCanvas, 'https://example.com', { width: 300 });\r\n * const imageData = qrCanvas.getContext('2d').getImageData(0, 0, 300, 300);\r\n * const bytes = convertCanvasToEscPos(imageData);\r\n */\r\nexport function convertCanvasToEscPos(imageData, brightnessThreshold = 382) {\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 แนวนอน (pad ให้ได้ width ที่หารด้วย 8 ลงตัว)\r\n const paddedWidth = Math.ceil(width / 8) * 8;\r\n const xBytes = paddedWidth / 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 const pixelX = x * 8 + bit;\r\n\r\n // ถ้าเกินขอบภาพ ให้เป็นสีขาว (Bit=0)\r\n if (pixelX >= width) {\r\n continue;\r\n }\r\n\r\n // คำนวณตำแหน่ง Pixel ใน Array (RGBA = 4 ช่องต่อ 1 pixel)\r\n const pxIndex = (y * width + pixelX) * 4;\r\n\r\n // เช็คความสว่าง: R+G+B (ระหว่าง 0-765)\r\n // ถ้าค่าต่ำกว่า threshold ให้ถือเป็นสีดำ (Bit=1)\r\n const brightness =\r\n pixels[pxIndex] + // Red\r\n pixels[pxIndex + 1] + // Green\r\n pixels[pxIndex + 2]; // Blue\r\n // (ไม่ใช้ Alpha channel)\r\n\r\n if (brightness < brightnessThreshold) {\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/**\r\n * แปลงข้อความเป็น ImageData (Client-side only)\r\n * ต้องใช้ใน Browser environment เท่านั้น\r\n *\r\n * ⚠️ REQUIRED: Sarabun font must be loaded 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 * @param {string} text - Thai text to convert\r\n * @param {number} width - Canvas width in pixels (default: 384)\r\n * @param {number} fontSize - Font size in pixels (default: 22)\r\n * @returns {ImageData} Canvas ImageData object\r\n * @throws {Error} If not in browser environment\r\n *\r\n * @example\r\n * // Client-side (React/Next.js)\r\n * const imageData = textToImageData(\"สวัสดี\", 384, 22);\r\n * await fetch('/api/print', {\r\n * body: JSON.stringify({\r\n * data: Array.from(imageData.data),\r\n * width: imageData.width,\r\n * height: imageData.height,\r\n * })\r\n * });\r\n */\r\nexport function textToImageData(text, width = 384, fontSize = 22) {\r\n // ตรวจสอบว่าเป็น Browser environment\r\n if (typeof document === \"undefined\") {\r\n throw new Error(\r\n \"textToImageData() requires browser environment with Canvas API\",\r\n );\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 = width;\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 // ดึง ImageData\r\n return ctx.getImageData(0, 0, width, height);\r\n}\r\n","// src/index.js\r\nimport { CMD } from \"./constants\";\r\nimport { convertCanvasToEscPos, textToImageData } from \"./utils\";\r\n\r\n/**\r\n * Thermal Printer Library for ESC/POS printers\r\n *\r\n * IMPORTANT: When using printThaiText() or textToImageData(), 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 { textToImageData } from \"./utils\";\r\nexport class ThermalPrinter {\r\n constructor(width = 384) {\r\n this.driverApi = null; // เริ่มต้นยังไม่มี URL\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 // --- 🔥 Auto Discovery & Print Logic 🔥 ---\r\n\r\n /**\r\n * วิ่งหา Driver ใน Port 9123-9130\r\n * (Lazy Mode: เรียกใช้เมื่อจำเป็น)\r\n */\r\n async findDriver() {\r\n // 1. ถ้ามี URL เดิมอยู่ ลองเช็คว่ายังอยู่ไหม (Ping)\r\n if (this.driverApi) {\r\n try {\r\n const res = await fetch(`${this.driverApi}/health`);\r\n if (res.ok) return true; // ยังอยู่ดี\r\n } catch (e) {\r\n console.warn(\"Driver connection lost, rescanning...\");\r\n this.driverApi = null;\r\n }\r\n }\r\n\r\n // 2. ถ้าไม่มี หรือหลุด ให้วนหาใหม่\r\n for (let port = 9123; port <= 9130; port++) {\r\n const url = `http://localhost:${port}`;\r\n try {\r\n // ตั้ง Timeout สั้นๆ (100ms) เพื่อความเร็ว\r\n const controller = new AbortController();\r\n const timeoutId = setTimeout(() => controller.abort(), 100);\r\n\r\n const res = await fetch(`${url}/health`, {\r\n signal: controller.signal,\r\n });\r\n clearTimeout(timeoutId);\r\n\r\n if (res.ok) {\r\n const data = await res.json();\r\n // เช็ค Signature ว่าใช่ Driver ของเราไหม\r\n if (data.service === \"CDH-Driver\") {\r\n this.driverApi = url;\r\n console.log(`✅ Driver found at: ${url}`);\r\n return true;\r\n }\r\n }\r\n } catch (e) {\r\n // Port ไม่ว่าง หรือไม่ใช่ของเรา -> ข้าม\r\n continue;\r\n }\r\n }\r\n console.error(\"❌ CDH Driver not found (Is .exe running?)\");\r\n return false;\r\n }\r\n\r\n async print(printerName) {\r\n // 1. หา Driver ก่อน (Lazy Discovery)\r\n const found = await this.findDriver();\r\n if (!found) {\r\n throw new Error(\r\n \"Cannot connect to Printer Driver. Please run the application.\",\r\n );\r\n }\r\n\r\n // 2. ถ้าไม่ระบุชื่อ Printer ให้ดึงตัวแรก\r\n if (!printerName) {\r\n const printers = await ThermalPrinter.getPrinters(this.driverApi);\r\n if (printers.length > 0) printerName = printers[0].name;\r\n else throw new Error(\"No printer found in Windows settings.\");\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 returned 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 Method: ต้องสร้าง instance ชั่วคราวไปหา driver\r\n static async getPrinters(overrideUrl = null) {\r\n let apiUrl = overrideUrl;\r\n\r\n if (!apiUrl) {\r\n const temp = new ThermalPrinter();\r\n const found = await temp.findDriver();\r\n if (found) apiUrl = temp.driverApi;\r\n else return [];\r\n }\r\n\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(\"Get Printers Failed:\", e);\r\n return [];\r\n }\r\n }\r\n}\r\n"],"names":["CMD","duration","convertCanvasToEscPos","imageData","brightnessThreshold","width","height","pixels","xBytes","command","y","x","byte","bit","pixelX","pxIndex","textToImageData","text","fontSize","canvas","ctx","fontFamily","lineHeight","lines","line","ThermalPrinter","alignment","cmd","enable","i","partial","bytes","count","char","times","port","url","controller","timeoutId","res","printerName","printers","bufferData","encodedName","overrideUrl","apiUrl","temp"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naphatjm/cdh-thermal-printer",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Thai Thermal Printer Library via Local Driver",
5
5
  "type": "module",
6
6
  "files": [