@naphatjm/cdh-thermal-printer 1.0.5 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -130,6 +130,40 @@ printer
130
130
  .print(printers[0].name);
131
131
  ```
132
132
 
133
+ ### Print QR Code (Advanced Example)
134
+
135
+ ```javascript
136
+ import QRCode from "qrcode";
137
+
138
+ const printer = new ThermalPrinter(384);
139
+ const canvas = document.createElement("canvas");
140
+
141
+ // Generate QR code (any size, will be auto-padded)
142
+ await QRCode.toCanvas(canvas, "https://example.com", {
143
+ width: 300, // ✨ Auto-pads to 304px (multiple of 8)
144
+ margin: 2,
145
+ color: {
146
+ dark: "#000000",
147
+ light: "#FFFFFF",
148
+ },
149
+ });
150
+
151
+ // Extract image data
152
+ const imageData = canvas
153
+ .getContext("2d")
154
+ ?.getImageData(0, 0, canvas.width, canvas.height);
155
+
156
+ // Send to printer (auto-discovers driver)
157
+ const printers = await ThermalPrinter.getPrinters();
158
+ printer
159
+ .init()
160
+ .align(1)
161
+ .image(imageData) // ← Handles any width automatically
162
+ .feed(2)
163
+ .cut()
164
+ .print(printers[0].name);
165
+ ```
166
+
133
167
  ## ⚠️ Important: Font Setup for Thai Text
134
168
 
135
169
  When using `printThaiText()` or `textToImageData()`, you **must** load the Sarabun font in your HTML file:
@@ -587,6 +621,47 @@ const imageDataLike = {
587
621
  printer.image(imageDataLike);
588
622
  ```
589
623
 
624
+ ### Issue: QR Code prints blurry or corrupted
625
+
626
+ **Problem:** QR code width not compatible with thermal printer (not multiple of 8 pixels)
627
+
628
+ **Solution:** Library auto-pads any width - no need to manually adjust
629
+
630
+ ```javascript
631
+ // ✅ Works - Any size QR code
632
+ const qrCanvas = document.createElement("canvas");
633
+ await QRCode.toCanvas(qrCanvas, "https://example.com", { width: 300 }); // 300px QR
634
+ const imageData = qrCanvas.getContext("2d")?.getImageData(0, 0, 300, 300);
635
+ printer.image(imageData); // Auto-pads to 304px (38 bytes × 8 = 304px)
636
+
637
+ // ✅ Also works
638
+ await QRCode.toCanvas(qrCanvas, "https://example.com", { width: 256 }); // 256px QR
639
+ // Auto-pads to 256px (already multiple of 8)
640
+ ```
641
+
642
+ **How it works:**
643
+
644
+ - QR 300px → padded to 304px (38 bytes)
645
+ - QR 256px → padded to 256px (32 bytes, already aligned)
646
+ - QR 210px → padded to 216px (27 bytes)
647
+
648
+ ### Issue: QR Code prints as solid black or white square
649
+
650
+ **Problem:** Image brightness threshold doesn't match QR code contrast
651
+
652
+ **Solution:** QR codes use pure black (0) and pure white (255), threshold of 382 is perfect for them
653
+
654
+ ```javascript
655
+ // This should work out-of-the-box
656
+ const imageData = canvas
657
+ .getContext("2d")
658
+ ?.getImageData(0, 0, canvas.width, canvas.height);
659
+ printer.image(imageData); // Uses default threshold 382 (0-765 scale)
660
+
661
+ // If custom image needs adjustment (not typical)
662
+ // Advanced: Can be customized in convertCanvasToEscPos() function
663
+ ```
664
+
590
665
  ### Issue: TextToImageData throws error on server
591
666
 
592
667
  **Problem:** `textToImageData()` requires browser Canvas API
@@ -659,6 +734,17 @@ printer.image(imageData).print();
659
734
  #### Image & Printing
660
735
 
661
736
  - `image(imageData)` - Print image from canvas ImageData or ImageDataLike object
737
+ - **Auto-adjusts width:** Any image width is automatically padded to multiple of 8 pixels
738
+ - **QR Code friendly:** Supports any QR code size (300px, 256px, etc.)
739
+ - **Smart brightness detection:** Uses configurable threshold (default: 382) to convert to black/white
740
+ - **Supports:** QR codes, logos, barcodes, custom graphics
741
+
742
+ ```javascript
743
+ // Works with any image size
744
+ const imageData = canvas.getContext("2d")?.getImageData(0, 0, 300, 300); // 300px QR
745
+ printer.image(imageData); // Auto-pads to 304px
746
+ ```
747
+
662
748
  - `printThaiText(text, fontSize)` - Print Thai text with automatic image rendering (client-side only, default fontSize: 22px)
663
749
  - `raw(bytes)` - Send raw byte array
664
750
 
@@ -1,4 +1,4 @@
1
- const h = {
1
+ const o = {
2
2
  ESC: 27,
3
3
  GS: 29,
4
4
  LF: 10,
@@ -20,55 +20,60 @@ const h = {
20
20
  // Beep (ESC B - default 100ms)
21
21
  BEEP: (l = 100) => [27, 66, l]
22
22
  };
23
- function x(l) {
24
- const t = l.width, r = l.height, e = l.data, n = t / 8;
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
25
  let i = [];
26
- i.push(29, 118, 48, 0), i.push(n % 256, Math.floor(n / 256)), i.push(r % 256, Math.floor(r / 256));
27
- for (let s = 0; s < r; s++)
28
- for (let c = 0; c < n; c++) {
29
- let a = 0;
30
- for (let o = 0; o < 8; o++) {
31
- const f = (s * t + c * 8 + o) * 4;
32
- e[f] + e[f + 1] + e[f + 2] < 380 && (a |= 1 << 7 - o);
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
+ let f = 0;
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
- i.push(a);
39
+ i.push(f);
35
40
  }
36
41
  return i;
37
42
  }
38
- function p(l, t = 384, r = 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 e = document.createElement("canvas"), n = e.getContext("2d"), i = "'Sarabun', sans-serif", s = r + 12, c = l.split(`
44
- `), a = c.length * s + 20;
45
- e.width = t, e.height = a, n.fillStyle = "white", n.fillRect(0, 0, e.width, e.height), n.fillStyle = "black", n.font = `${r}px ${i}`, n.textBaseline = "top";
46
- let o = 10;
47
- for (let f of c)
48
- n.fillText(f, 10, o), o += s;
49
- return n.getImageData(0, 0, t, a);
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
- class u {
56
+ class x {
52
57
  constructor(t = 384) {
53
58
  this.driverApi = null, this.printerWidth = t, this.buffer = [];
54
59
  }
55
60
  // --- Basic Commands ---
56
61
  init() {
57
- return this.buffer.push(...h.INIT), this;
62
+ return this.buffer.push(...o.INIT), this;
58
63
  }
59
64
  align(t = 1) {
60
- const r = [...h.ALIGN_LEFT];
65
+ const r = [...o.ALIGN_LEFT];
61
66
  return r[2] = t, this.buffer.push(...r), this;
62
67
  }
63
68
  bold(t = !0) {
64
- return this.buffer.push(...t ? h.BOLD_ON : h.BOLD_OFF), this;
69
+ return this.buffer.push(...t ? o.BOLD_ON : o.BOLD_OFF), this;
65
70
  }
66
71
  feed(t = 1) {
67
- for (let r = 0; r < t; r++) this.buffer.push(h.LF);
72
+ for (let r = 0; r < t; r++) this.buffer.push(o.LF);
68
73
  return this;
69
74
  }
70
75
  cut(t = !1) {
71
- return this.buffer.push(...t ? h.CUT_PARTIAL : h.CUT_FULL), this;
76
+ return this.buffer.push(...t ? o.CUT_PARTIAL : o.CUT_FULL), this;
72
77
  }
73
78
  line(t = "") {
74
79
  const e = new TextEncoder().encode(t + `
@@ -77,7 +82,7 @@ class u {
77
82
  }
78
83
  newline(t = 1) {
79
84
  for (let r = 0; r < t; r++)
80
- this.buffer.push(h.LF);
85
+ this.buffer.push(o.LF);
81
86
  return this;
82
87
  }
83
88
  divider(t = "-", r = 32) {
@@ -85,18 +90,18 @@ class u {
85
90
  }
86
91
  beep(t = 1, r = 100) {
87
92
  for (let e = 0; e < t; e++)
88
- this.buffer.push(...h.BEEP(r));
93
+ this.buffer.push(...o.BEEP(r));
89
94
  return this;
90
95
  }
91
96
  drawerKick() {
92
- return this.buffer.push(...h.DRAWER_KICK), this;
97
+ return this.buffer.push(...o.DRAWER_KICK), this;
93
98
  }
94
99
  raw(t) {
95
100
  return this.buffer.push(...t), this;
96
101
  }
97
102
  // --- Image & Thai Text Logic ---
98
103
  image(t) {
99
- const r = x(t);
104
+ const r = b(t);
100
105
  return this.buffer.push(...r), this;
101
106
  }
102
107
  /**
@@ -113,13 +118,13 @@ class u {
113
118
  printThaiText(t, r = 22) {
114
119
  if (typeof document > "u")
115
120
  return console.error("Browser environment required for printThaiText"), this;
116
- const e = document.createElement("canvas"), n = e.getContext("2d"), i = "'Sarabun', sans-serif", s = r + 12, c = t.split(`
117
- `), a = c.length * s + 20;
118
- e.width = this.printerWidth, e.height = a, n.fillStyle = "white", n.fillRect(0, 0, e.width, e.height), n.fillStyle = "black", n.font = `${r}px ${i}`, n.textBaseline = "top";
119
- let o = 10;
120
- for (let d of c)
121
- n.fillText(d, 10, o), o += s;
122
- const f = n.getImageData(0, 0, this.printerWidth, a);
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);
123
128
  return this.image(f);
124
129
  }
125
130
  // --- Execution ---
@@ -144,10 +149,10 @@ class u {
144
149
  for (let t = 9123; t <= 9130; t++) {
145
150
  const r = `http://localhost:${t}`;
146
151
  try {
147
- const e = new AbortController(), n = setTimeout(() => e.abort(), 100), i = await fetch(`${r}/health`, {
152
+ const e = new AbortController(), n = setTimeout(() => e.abort(), 100), a = await fetch(`${r}/health`, {
148
153
  signal: e.signal
149
154
  });
150
- if (clearTimeout(n), i.ok && (await i.json()).service === "CDH-Driver")
155
+ if (clearTimeout(n), a.ok && (await a.json()).service === "CDH-Driver")
151
156
  return this.driverApi = r, console.log(`✅ Driver found at: ${r}`), !0;
152
157
  } catch {
153
158
  continue;
@@ -161,7 +166,7 @@ class u {
161
166
  "Cannot connect to Printer Driver. Please run the application."
162
167
  );
163
168
  if (!t) {
164
- const e = await u.getPrinters(this.driverApi);
169
+ const e = await x.getPrinters(this.driverApi);
165
170
  if (e.length > 0) t = e[0].name;
166
171
  else throw new Error("No printer found in Windows settings.");
167
172
  }
@@ -184,7 +189,7 @@ class u {
184
189
  static async getPrinters(t = null) {
185
190
  let r = t;
186
191
  if (!r) {
187
- const e = new u();
192
+ const e = new x();
188
193
  if (await e.findDriver()) r = e.driverApi;
189
194
  else return [];
190
195
  }
@@ -196,7 +201,7 @@ class u {
196
201
  }
197
202
  }
198
203
  export {
199
- u as ThermalPrinter,
200
- p as textToImageData
204
+ x as ThermalPrinter,
205
+ g as textToImageData
201
206
  };
202
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(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","width","height","pixels","xBytes","command","y","x","byte","bit","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
+ {"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(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 p(l){const t=l.width,r=l.height,e=l.data,n=t/8;let s=[];s.push(29,118,48,0),s.push(n%256,Math.floor(n/256)),s.push(r%256,Math.floor(r/256));for(let o=0;o<r;o++)for(let c=0;c<n;c++){let a=0;for(let h=0;h<8;h++){const u=(o*t+c*8+h)*4;e[u]+e[u+1]+e[u+2]<380&&(a|=1<<7-h)}s.push(a)}return s}function b(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"),s="'Sarabun', sans-serif",o=r+12,c=l.split(`
2
- `),a=c.length*o+20;e.width=t,e.height=a,n.fillStyle="white",n.fillRect(0,0,e.width,e.height),n.fillStyle="black",n.font=`${r}px ${s}`,n.textBaseline="top";let h=10;for(let u of c)n.fillText(u,10,h),h+=o;return n.getImageData(0,0,t,a)}class d{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=p(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"),s="'Sarabun', sans-serif",o=r+12,c=t.split(`
4
- `),a=c.length*o+20;e.width=this.printerWidth,e.height=a,n.fillStyle="white",n.fillRect(0,0,e.width,e.height),n.fillStyle="black",n.font=`${r}px ${s}`,n.textBaseline="top";let h=10;for(let x of c)n.fillText(x,10,h),h+=o;const u=n.getImageData(0,0,this.printerWidth,a);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),s=await fetch(`${r}/health`,{signal:e.signal});if(clearTimeout(n),s.ok&&(await s.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 d.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 d;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=d,f.textToImageData=b,Object.defineProperty(f,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(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","width","height","pixels","xBytes","command","y","x","byte","bit","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
+ {"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.5",
3
+ "version": "1.0.6",
4
4
  "description": "Thai Thermal Printer Library via Local Driver",
5
5
  "type": "module",
6
6
  "files": [