@risleylima/escpos 0.2.0 → 1.0.0

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.
Files changed (106) hide show
  1. package/README.md +94 -766
  2. package/dist/adapter/index.d.ts +29 -0
  3. package/dist/adapter/index.d.ts.map +1 -0
  4. package/dist/adapter/index.js +25 -0
  5. package/dist/adapter/index.js.map +1 -0
  6. package/dist/index.d.ts +14 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +25 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/network-adapter/index.d.ts +24 -0
  11. package/dist/network-adapter/index.d.ts.map +1 -0
  12. package/dist/network-adapter/index.js +263 -0
  13. package/dist/network-adapter/index.js.map +1 -0
  14. package/dist/printer/commands-types.d.ts +37 -0
  15. package/dist/printer/commands-types.d.ts.map +1 -0
  16. package/dist/printer/commands-types.js +63 -0
  17. package/dist/printer/commands-types.js.map +1 -0
  18. package/dist/printer/commands.d.ts +169 -0
  19. package/dist/printer/commands.d.ts.map +1 -0
  20. package/dist/printer/commands.js +192 -0
  21. package/dist/printer/commands.js.map +1 -0
  22. package/dist/printer/image-loader.d.ts +17 -0
  23. package/dist/printer/image-loader.d.ts.map +1 -0
  24. package/dist/printer/image-loader.js +462 -0
  25. package/dist/printer/image-loader.js.map +1 -0
  26. package/dist/printer/image.d.ts +43 -0
  27. package/dist/printer/image.d.ts.map +1 -0
  28. package/dist/printer/image.js +132 -0
  29. package/dist/printer/image.js.map +1 -0
  30. package/dist/printer/index.d.ts +158 -0
  31. package/dist/printer/index.d.ts.map +1 -0
  32. package/dist/printer/index.js +703 -0
  33. package/dist/printer/index.js.map +1 -0
  34. package/dist/printer/profiles/bematech/mp4200th.d.ts +13 -0
  35. package/dist/printer/profiles/bematech/mp4200th.d.ts.map +1 -0
  36. package/dist/printer/profiles/bematech/mp4200th.js +29 -0
  37. package/dist/printer/profiles/bematech/mp4200th.js.map +1 -0
  38. package/dist/printer/profiles/custom/bematech-mp4200th.d.ts +13 -0
  39. package/dist/printer/profiles/custom/bematech-mp4200th.d.ts.map +1 -0
  40. package/dist/printer/profiles/custom/bematech-mp4200th.js +21 -0
  41. package/dist/printer/profiles/custom/bematech-mp4200th.js.map +1 -0
  42. package/dist/printer/profiles/custom/vkp80iii.d.ts +19 -0
  43. package/dist/printer/profiles/custom/vkp80iii.d.ts.map +1 -0
  44. package/dist/printer/profiles/custom/vkp80iii.js +87 -0
  45. package/dist/printer/profiles/custom/vkp80iii.js.map +1 -0
  46. package/dist/printer/profiles/default.d.ts +7 -0
  47. package/dist/printer/profiles/default.d.ts.map +1 -0
  48. package/dist/printer/profiles/default.js +15 -0
  49. package/dist/printer/profiles/default.js.map +1 -0
  50. package/dist/printer/profiles/index.d.ts +41 -0
  51. package/dist/printer/profiles/index.d.ts.map +1 -0
  52. package/dist/printer/profiles/index.js +98 -0
  53. package/dist/printer/profiles/index.js.map +1 -0
  54. package/dist/printer/profiles/merge.d.ts +7 -0
  55. package/dist/printer/profiles/merge.d.ts.map +1 -0
  56. package/dist/printer/profiles/merge.js +58 -0
  57. package/dist/printer/profiles/merge.js.map +1 -0
  58. package/dist/printer/profiles/types.d.ts +99 -0
  59. package/dist/printer/profiles/types.d.ts.map +1 -0
  60. package/dist/printer/profiles/types.js +8 -0
  61. package/dist/printer/profiles/types.js.map +1 -0
  62. package/dist/printer/utils.d.ts +9 -0
  63. package/dist/printer/utils.d.ts.map +1 -0
  64. package/dist/printer/utils.js +54 -0
  65. package/dist/printer/utils.js.map +1 -0
  66. package/dist/serial-adapter/index.d.ts +17 -0
  67. package/dist/serial-adapter/index.d.ts.map +1 -0
  68. package/dist/serial-adapter/index.js +172 -0
  69. package/dist/serial-adapter/index.js.map +1 -0
  70. package/dist/usb-adapter/index.d.ts +20 -0
  71. package/dist/usb-adapter/index.d.ts.map +1 -0
  72. package/dist/usb-adapter/index.js +264 -0
  73. package/dist/usb-adapter/index.js.map +1 -0
  74. package/package.json +42 -15
  75. package/CHANGELOG.md +0 -68
  76. package/docs/COVERAGE_ANALYSIS.md +0 -98
  77. package/docs/DEPENDENCIES_REVIEW.md +0 -127
  78. package/docs/JSDOC_REVIEW.md +0 -122
  79. package/docs/LIBRARY_OVERVIEW.md +0 -383
  80. package/docs/PRE_PUBLISH_CHECKLIST.md +0 -331
  81. package/docs/PUBLIC_API_ANALYSIS.md +0 -223
  82. package/docs/README.md +0 -34
  83. package/docs/SERIALPORT_V13_MIGRATION_COMPLETE.md +0 -127
  84. package/docs/TESTS_IMPLEMENTED.md +0 -129
  85. package/docs/USB_V2_REVIEW.md +0 -148
  86. package/docs/VERIFICATION_RESULTS.md +0 -172
  87. package/examples/printTest.js +0 -59
  88. package/index.js +0 -7
  89. package/jest.config.js +0 -16
  90. package/src/adapter/index.js +0 -75
  91. package/src/printer/commands.js +0 -199
  92. package/src/printer/image.js +0 -159
  93. package/src/printer/index.js +0 -621
  94. package/src/printer/utils.js +0 -58
  95. package/src/serial-adapter/index.js +0 -198
  96. package/src/usb-adapter/index.js +0 -283
  97. package/tests/README.md +0 -67
  98. package/tests/integration/printer-flow.test.js +0 -128
  99. package/tests/unit/adapters/adapter.test.js +0 -49
  100. package/tests/unit/adapters/serial-adapter.test.js +0 -238
  101. package/tests/unit/adapters/usb-adapter.test.js +0 -319
  102. package/tests/unit/image/image.test.js +0 -157
  103. package/tests/unit/printer/buffer.test.js +0 -60
  104. package/tests/unit/printer/commands.test.js +0 -109
  105. package/tests/unit/printer/printer.test.js +0 -405
  106. package/tests/unit/utils/utils.test.js +0 -96
@@ -0,0 +1,703 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.Printer = void 0;
37
+ const commands_1 = require("./commands");
38
+ const commands_types_1 = require("./commands-types");
39
+ const profiles_1 = require("./profiles");
40
+ const utils = __importStar(require("./utils"));
41
+ const image_1 = require("./image");
42
+ class SpecBuffer {
43
+ constructor(maxSize = 10 * 1024 * 1024) {
44
+ this.currentSize = 0;
45
+ this.chunks = [];
46
+ this.maxSize = maxSize;
47
+ }
48
+ write(data, type = 'ascii') {
49
+ const chunk = Buffer.isBuffer(data) ? data : Buffer.from(data, type);
50
+ if (this.currentSize + chunk.length > this.maxSize) {
51
+ throw new Error(`Printer buffer overflow: max size of ${this.maxSize} bytes reached.`);
52
+ }
53
+ this.chunks.push(chunk);
54
+ this.currentSize += chunk.length;
55
+ }
56
+ prepend(data) {
57
+ if (data.length === 0)
58
+ return;
59
+ this.chunks.unshift(data);
60
+ this.currentSize += data.length;
61
+ }
62
+ flush() {
63
+ if (this.chunks.length === 0)
64
+ return Buffer.alloc(0);
65
+ const data = Buffer.concat(this.chunks);
66
+ this.chunks = [];
67
+ this.currentSize = 0;
68
+ return data;
69
+ }
70
+ size() {
71
+ return this.currentSize;
72
+ }
73
+ }
74
+ /** Normalize "options or encoding as 3rd/4th param" pattern (DRY for lineItem, total, etc.). */
75
+ function normalizeOptionsEncoding(optionsOrEncoding, encoding) {
76
+ if (typeof optionsOrEncoding === 'string') {
77
+ return { encoding: optionsOrEncoding };
78
+ }
79
+ return { options: optionsOrEncoding, encoding };
80
+ }
81
+ function buildStyleMap(cmd) {
82
+ const T = cmd.TEXT_FORMAT;
83
+ return {
84
+ B: [T.TXT_BOLD_ON, T.TXT_ITALIC_OFF, T.TXT_UNDERL_OFF],
85
+ I: [T.TXT_BOLD_OFF, T.TXT_ITALIC_ON, T.TXT_UNDERL_OFF],
86
+ U: [T.TXT_BOLD_OFF, T.TXT_ITALIC_OFF, T.TXT_UNDERL_ON],
87
+ U2: [T.TXT_BOLD_OFF, T.TXT_ITALIC_OFF, T.TXT_UNDERL2_ON],
88
+ BI: [T.TXT_BOLD_ON, T.TXT_ITALIC_ON, T.TXT_UNDERL_OFF],
89
+ BIU: [T.TXT_BOLD_ON, T.TXT_ITALIC_ON, T.TXT_UNDERL_ON],
90
+ BIU2: [T.TXT_BOLD_ON, T.TXT_ITALIC_ON, T.TXT_UNDERL2_ON],
91
+ BU: [T.TXT_BOLD_ON, T.TXT_ITALIC_OFF, T.TXT_UNDERL_OFF],
92
+ BU2: [T.TXT_BOLD_ON, T.TXT_ITALIC_OFF, T.TXT_UNDERL2_ON],
93
+ IU: [T.TXT_BOLD_OFF, T.TXT_ITALIC_ON, T.TXT_UNDERL_ON],
94
+ IU2: [T.TXT_BOLD_OFF, T.TXT_ITALIC_ON, T.TXT_UNDERL2_ON],
95
+ NORMAL: [T.TXT_BOLD_OFF, T.TXT_ITALIC_OFF, T.TXT_UNDERL_OFF],
96
+ };
97
+ }
98
+ class Printer {
99
+ resolveTicketPresentationCommand(options) {
100
+ if (!this.profile)
101
+ return undefined;
102
+ const merged = { ...(this.options?.ticketPresentation ?? {}), ...(options ?? {}) };
103
+ this.profile.validateTicketPresentationOptions?.(merged);
104
+ return this.profile.getTicketPresentationCommand?.(merged) ?? this.profile.paperEjectAfterCut;
105
+ }
106
+ constructor(adapter, options) {
107
+ this.ioChain = Promise.resolve();
108
+ this.adapter = adapter;
109
+ this.options = options;
110
+ this.buffer = new SpecBuffer(options?.maxBufferSize);
111
+ this.Image = image_1.Image;
112
+ const profileResolver = options?.profileRegistry?.getProfile ?? profiles_1.getProfile;
113
+ const commandsResolver = options?.profileRegistry?.getCommandsForProfile ?? profiles_1.getCommandsForProfile;
114
+ const profile = options?.profile === undefined
115
+ ? undefined
116
+ : typeof options.profile === 'string'
117
+ ? profileResolver(options.profile)
118
+ : options.profile;
119
+ if (typeof options?.profile === 'string' && !profile) {
120
+ throw new Error(`Unknown profile "${options.profile}". Register it first or pass a valid profile object.`);
121
+ }
122
+ this.commands = profile ? commandsResolver(profile) : commands_1.commands;
123
+ this.profile = profile;
124
+ this.styleMap = buildStyleMap(this.commands);
125
+ this.encoding = options?.encoding ?? 'utf8';
126
+ this.width = options?.width ?? profile?.defaultPaperWidth ?? 80;
127
+ }
128
+ enqueueIo(task) {
129
+ const run = this.ioChain.then(task, task);
130
+ this.ioChain = run.then(() => undefined, () => undefined);
131
+ return run;
132
+ }
133
+ /**
134
+ * Set paper width (characters per line). If the profile defines a hardware command
135
+ * (e.g. GS W), it is sent to the printer; otherwise only the internal width is updated.
136
+ */
137
+ paperWidth(width) {
138
+ const w = Math.max(1, Math.floor(Number(width) || 80));
139
+ if (this.profile?.paperWidths && !this.profile.paperWidths.includes(w)) {
140
+ console.warn(`[escpos] Paper width ${w} may not be supported by profile "${this.profile.id}".`);
141
+ }
142
+ this.width = w;
143
+ const cmd = this.profile?.getPaperWidthCommand?.(w);
144
+ if (cmd)
145
+ this.buffer.write(cmd);
146
+ return this;
147
+ }
148
+ setCharacterCodeTable(codeTable) {
149
+ this.currentCodepage = codeTable;
150
+ this.buffer.write(Buffer.concat([
151
+ this.commands.ESC,
152
+ Buffer.from([0x74]), // 't'
153
+ Buffer.from([codeTable]),
154
+ ]));
155
+ return this;
156
+ }
157
+ margin(type, size) {
158
+ if (typeof type !== 'string' || !type) {
159
+ throw new TypeError('margin(type, size): type must be a non-empty string (LEFT, RIGHT, BOTTOM)');
160
+ }
161
+ const key = type.toUpperCase();
162
+ const margin = this.commands.MARGINS[key];
163
+ if (!margin) {
164
+ throw new TypeError(`margin(type, size): invalid type "${type}". Use LEFT, RIGHT, or BOTTOM.`);
165
+ }
166
+ this.buffer.write(Buffer.concat([margin, Buffer.from(this.commands.numToHexString(size), 'hex')]));
167
+ return this;
168
+ }
169
+ marginBottomCancel() {
170
+ this.buffer.write(this.commands.MARGIN_BOTTOM_CANCEL);
171
+ return this;
172
+ }
173
+ print(content) {
174
+ this.buffer.write(Buffer.isBuffer(content) ? content : Buffer.from(content, 'ascii'));
175
+ return this;
176
+ }
177
+ println(content) {
178
+ this.buffer.write(Buffer.concat([
179
+ Buffer.isBuffer(content) ? content : Buffer.from(content, 'ascii'),
180
+ this.commands.EOL,
181
+ ]));
182
+ return this;
183
+ }
184
+ newLine() {
185
+ this.buffer.write(this.commands.EOL);
186
+ return this;
187
+ }
188
+ /**
189
+ * Internal helper to set codepage based on encoding if profile supports it.
190
+ */
191
+ autoSetCodepage(encoding) {
192
+ if (!this.profile?.codepages)
193
+ return;
194
+ const cp = this.profile.codepages[encoding];
195
+ if (cp !== undefined && cp !== this.currentCodepage) {
196
+ this.setCharacterCodeTable(cp);
197
+ }
198
+ }
199
+ /**
200
+ * Print text with encoding. If the profile has a mapping for the encoding,
201
+ * it automatically sends the codepage command (ESC t n).
202
+ */
203
+ text(content, encoding) {
204
+ const enc = encoding ?? this.encoding;
205
+ this.autoSetCodepage(enc);
206
+ const buf = Buffer.isEncoding(enc)
207
+ ? Buffer.from(content, enc)
208
+ : Buffer.from(content, 'utf8');
209
+ return this.print(buf);
210
+ }
211
+ textln(content, encoding) {
212
+ const enc = encoding ?? this.encoding;
213
+ this.autoSetCodepage(enc);
214
+ const buf = Buffer.isEncoding(enc)
215
+ ? Buffer.from(content, enc)
216
+ : Buffer.from(content, 'utf8');
217
+ return this.println(buf);
218
+ }
219
+ drawLine(character = '-') {
220
+ for (let i = 0; i < this.width; i++) {
221
+ this.buffer.write(Buffer.from(character, 'ascii'));
222
+ }
223
+ return this.newLine();
224
+ }
225
+ section(title, encoding) {
226
+ this.drawLine('-');
227
+ this.centerln(title, encoding);
228
+ this.drawLine('-');
229
+ return this;
230
+ }
231
+ center(content, encoding) {
232
+ this.align('ct');
233
+ this.text(content, encoding);
234
+ this.align('lt');
235
+ return this;
236
+ }
237
+ centerln(content, encoding) {
238
+ this.align('ct');
239
+ this.textln(content, encoding);
240
+ this.align('lt');
241
+ return this;
242
+ }
243
+ right(content, encoding) {
244
+ this.align('rt');
245
+ this.text(content, encoding);
246
+ this.align('lt');
247
+ return this;
248
+ }
249
+ rightln(content, encoding) {
250
+ this.align('rt');
251
+ this.textln(content, encoding);
252
+ this.align('lt');
253
+ return this;
254
+ }
255
+ row(columns, encoding) {
256
+ if (!Array.isArray(columns) || columns.length === 0)
257
+ return this;
258
+ let line = '';
259
+ for (const col of columns) {
260
+ const w = Math.max(0, Number(col.width) || 0);
261
+ const align = (col.align ?? 'left').toLowerCase();
262
+ let s = String(col.text ?? '');
263
+ if (utils.textLength(s) > w)
264
+ s = utils.textSubstring(s, 0, w);
265
+ const len = utils.textLength(s);
266
+ const pad = w - len;
267
+ if (align === 'right')
268
+ line += ' '.repeat(pad) + s;
269
+ else if (align === 'center')
270
+ line += ' '.repeat(Math.floor(pad / 2)) + s + ' '.repeat(pad - Math.floor(pad / 2));
271
+ else
272
+ line += s + ' '.repeat(pad);
273
+ }
274
+ this.textln(line, encoding);
275
+ return this;
276
+ }
277
+ /**
278
+ * Alias for row() but allows using decimal proportions for widths (e.g. 0.5 for 50%).
279
+ */
280
+ tableCustom(columns, encoding) {
281
+ const formattedCols = columns.map(col => ({
282
+ text: col.text,
283
+ width: col.width < 1 ? Math.floor(this.width * col.width) : col.width,
284
+ align: (col.align?.toLowerCase() ?? 'left')
285
+ }));
286
+ return this.row(formattedCols, encoding);
287
+ }
288
+ lineItem(desc, price, optionsOrEncoding, encoding) {
289
+ const { options, encoding: enc } = normalizeOptionsEncoding(optionsOrEncoding, encoding);
290
+ const priceWidth = options?.priceWidth ?? 12;
291
+ const descWidth = options?.descWidth ?? this.width - priceWidth;
292
+ return this.row([
293
+ { text: String(desc ?? ''), width: descWidth },
294
+ { text: String(price ?? ''), width: priceWidth, align: 'right' },
295
+ ], enc);
296
+ }
297
+ lineItemWithQty(desc, qty, price, optionsOrEncoding, encoding) {
298
+ const { options, encoding: enc } = normalizeOptionsEncoding(optionsOrEncoding, encoding);
299
+ const priceWidth = options?.priceWidth ?? 12;
300
+ const qtyWidth = options?.qtyWidth ?? 6;
301
+ const descWidth = options?.descWidth ?? this.width - priceWidth - qtyWidth;
302
+ return this.row([
303
+ { text: String(desc ?? ''), width: descWidth },
304
+ { text: String(qty ?? ''), width: qtyWidth, align: 'right' },
305
+ { text: String(price ?? ''), width: priceWidth, align: 'right' },
306
+ ], enc);
307
+ }
308
+ total(label, value, optionsOrEncoding, encoding) {
309
+ const { options, encoding: enc } = normalizeOptionsEncoding(optionsOrEncoding, encoding);
310
+ const bold = !options || options.bold !== false;
311
+ if (bold)
312
+ this.style('b');
313
+ this.row([
314
+ { text: String(label ?? ''), width: this.width - 12 },
315
+ { text: String(value ?? ''), width: 12, align: 'right' },
316
+ ], enc);
317
+ if (bold)
318
+ this.style('normal');
319
+ return this;
320
+ }
321
+ encode(encoding) {
322
+ this.encoding = encoding;
323
+ return this;
324
+ }
325
+ feed(n = 1) {
326
+ const lines = Math.min(255, Math.max(0, Math.floor(Number(n) || 0)));
327
+ if (lines > 0) {
328
+ this.buffer.write(Buffer.concat(new Array(lines).fill(this.commands.EOL)));
329
+ }
330
+ return this;
331
+ }
332
+ feedLines(n) {
333
+ const byte = Math.min(255, Math.max(0, Number(n) || 0));
334
+ this.buffer.write(Buffer.concat([this.commands.FEED_LINES, Buffer.from([byte])]));
335
+ return this;
336
+ }
337
+ control(ctrl) {
338
+ if (typeof ctrl !== 'string' || !ctrl) {
339
+ throw new TypeError('control(ctrl): ctrl must be a non-empty string');
340
+ }
341
+ const key = (0, commands_types_1.getFeedControlKey)(ctrl);
342
+ if (!key) {
343
+ throw new TypeError(`control(ctrl): invalid ctrl "${ctrl}". Use LF, GLF, FF, CR, HT, or VT.`);
344
+ }
345
+ this.buffer.write(this.commands.FEED_CONTROL_SEQUENCES[key]);
346
+ return this;
347
+ }
348
+ align(align) {
349
+ if (typeof align !== 'string' || !align) {
350
+ throw new TypeError('align(align): align must be a non-empty string (LT, CT, RT)');
351
+ }
352
+ const key = (0, commands_types_1.getAlignKey)(align);
353
+ if (!key) {
354
+ throw new TypeError(`align(align): invalid align "${align}". Use LT, CT, or RT.`);
355
+ }
356
+ const cmd = this.commands.TEXT_FORMAT[key];
357
+ this.buffer.write(cmd);
358
+ return this;
359
+ }
360
+ font(family) {
361
+ if (typeof family !== 'string' || !family) {
362
+ throw new TypeError('font(family): family must be a non-empty string (A, B, C)');
363
+ }
364
+ const key = (0, commands_types_1.getFontKey)(family);
365
+ if (!key) {
366
+ throw new TypeError(`font(family): invalid family "${family}". Use A, B, or C.`);
367
+ }
368
+ this.buffer.write(this.commands.TEXT_FORMAT[key]);
369
+ this.width = family.toUpperCase() === 'A' ? (this.options?.width ?? 42) : (this.options?.width ?? 56);
370
+ return this;
371
+ }
372
+ style(type) {
373
+ if (typeof type !== 'string') {
374
+ throw new TypeError('style(type): type must be a string');
375
+ }
376
+ const key = type.toUpperCase();
377
+ const buffers = this.styleMap[key] ?? this.styleMap.NORMAL;
378
+ this.buffer.write(Buffer.concat(buffers));
379
+ return this;
380
+ }
381
+ size(width, height) {
382
+ this.buffer.write(this.commands.TEXT_FORMAT.TXT_CUSTOM_SIZE(width, height));
383
+ return this;
384
+ }
385
+ /** Write default or parameterised command (DRY for spacing/lineSpace). */
386
+ writeOptionalNumber(defaultBuf, setPrefix, n) {
387
+ if (n === undefined || n === null) {
388
+ this.buffer.write(defaultBuf);
389
+ }
390
+ else {
391
+ if (!Number.isInteger(n) || n < 0 || n > 255) {
392
+ throw new TypeError('numeric command parameter must be an integer between 0 and 255');
393
+ }
394
+ this.buffer.write(Buffer.concat([setPrefix, Buffer.from(this.commands.numToHexString(n), 'hex')]));
395
+ }
396
+ }
397
+ spacing(n) {
398
+ this.writeOptionalNumber(this.commands.CHARACTER_SPACING.CS_DEFAULT, this.commands.CHARACTER_SPACING.CS_SET, n);
399
+ return this;
400
+ }
401
+ lineSpace(n) {
402
+ this.writeOptionalNumber(this.commands.LINE_SPACING.LS_DEFAULT, this.commands.LINE_SPACING.LS_SET, n);
403
+ return this;
404
+ }
405
+ hardware(hw) {
406
+ if (typeof hw !== 'string' || !hw) {
407
+ throw new TypeError('hardware(hw): hw must be a non-empty string (INIT, SELECT, RESET)');
408
+ }
409
+ const key = (0, commands_types_1.getHardwareKey)(hw);
410
+ if (!key) {
411
+ throw new TypeError(`hardware(hw): invalid hw "${hw}". Use INIT, SELECT, or RESET.`);
412
+ }
413
+ this.buffer.write(this.commands.HARDWARE[key]);
414
+ return this;
415
+ }
416
+ barcode(code, type = 'EAN13', options) {
417
+ const profileBuf = this.profile?.buildBarcode?.(code, type, options, {
418
+ commands: this.commands,
419
+ getParityBit: utils.getParityBit,
420
+ codeLength: utils.codeLength,
421
+ });
422
+ if (Buffer.isBuffer(profileBuf)) {
423
+ this.buffer.write(profileBuf);
424
+ return this;
425
+ }
426
+ options = options ?? {};
427
+ const width = options.width;
428
+ const height = options.height;
429
+ const position = options.position;
430
+ const font = options.font;
431
+ const includeParity = options.includeParity !== false;
432
+ const convertCode = String(code);
433
+ let parityBit = '';
434
+ let codeLen = Buffer.alloc(0);
435
+ if (type === 'EAN13' && convertCode.length !== 12 && convertCode.length !== 13) {
436
+ throw new Error('EAN13 Barcode type requires code length 12 or 13');
437
+ }
438
+ if (type === 'EAN8' && convertCode.length !== 7 && convertCode.length !== 8) {
439
+ throw new Error('EAN8 Barcode type requires code length 7 or 8');
440
+ }
441
+ const bf = this.commands.BARCODE_FORMAT;
442
+ const widthKey = width != null && width >= 1 && width <= 5 ? width : undefined;
443
+ if (widthKey !== undefined) {
444
+ this.buffer.write(bf.BARCODE_WIDTH[widthKey]);
445
+ }
446
+ else {
447
+ this.buffer.write(bf.BARCODE_WIDTH_DEFAULT);
448
+ }
449
+ if (height != null && height >= 1 && height <= 255) {
450
+ this.buffer.write(bf.BARCODE_HEIGHT(height));
451
+ }
452
+ else {
453
+ this.buffer.write(bf.BARCODE_HEIGHT_DEFAULT);
454
+ }
455
+ const fontKey = 'BARCODE_FONT_' + (font || 'A').toUpperCase();
456
+ const posKey = 'BARCODE_TXT_' + (position || 'BLW').toUpperCase();
457
+ const typeKey = 'BARCODE_' + (type || 'EAN13').replace('-', '_').toUpperCase();
458
+ const bfRecord = bf;
459
+ const fontBuf = bfRecord[fontKey];
460
+ const posBuf = bfRecord[posKey];
461
+ const typeBuf = bfRecord[typeKey];
462
+ if (Buffer.isBuffer(fontBuf))
463
+ this.buffer.write(fontBuf);
464
+ if (Buffer.isBuffer(posBuf))
465
+ this.buffer.write(posBuf);
466
+ if (Buffer.isBuffer(typeBuf))
467
+ this.buffer.write(typeBuf);
468
+ if (includeParity && (type === 'EAN13' || type === 'EAN8')) {
469
+ const expectsParity = (type === 'EAN13' && convertCode.length === 12) ||
470
+ (type === 'EAN8' && convertCode.length === 7);
471
+ if (expectsParity)
472
+ parityBit = utils.getParityBit(code);
473
+ }
474
+ if (type === 'CODE128' || type === 'CODE93') {
475
+ codeLen = utils.codeLength(code);
476
+ }
477
+ this.buffer.write(Buffer.concat([
478
+ codeLen,
479
+ Buffer.from(code + (includeParity ? parityBit : ''), 'ascii'),
480
+ Buffer.from('00', 'hex'),
481
+ ]));
482
+ return this;
483
+ }
484
+ image(image, density = 'd24') {
485
+ if (!(image instanceof image_1.Image))
486
+ throw new TypeError('Only Image object supported');
487
+ const n = ['d8', 's8'].includes(density) ? 1 : 3;
488
+ const key = (0, commands_types_1.getBitmapDensityKey)(density);
489
+ if (!key)
490
+ throw new TypeError(`image(..., density): invalid density "${density}". Use s8, d8, s24, d24.`);
491
+ const header = this.commands.BITMAP_FORMAT[key];
492
+ const bitmap = image.toBitmap(n * 8);
493
+ for (const line of bitmap.data) {
494
+ const lineLength = Buffer.allocUnsafe(2);
495
+ lineLength.writeUInt16LE(line.length / n, 0);
496
+ this.buffer.write(Buffer.concat([
497
+ header,
498
+ lineLength,
499
+ Buffer.from(line),
500
+ this.commands.ESC,
501
+ this.commands.FEED_CONTROL_SEQUENCES.CTL_GLF,
502
+ ]));
503
+ }
504
+ return this;
505
+ }
506
+ raster(image, mode = 'normal') {
507
+ if (!(image instanceof image_1.Image))
508
+ throw new TypeError('Only Image object supported');
509
+ if (mode === 'dhdw' || mode === 'dwh' || mode === 'dhw')
510
+ mode = 'dwdh';
511
+ const raster = image.toRaster();
512
+ const key = (0, commands_types_1.getGsv0ModeKey)(mode);
513
+ if (!key)
514
+ throw new TypeError(`raster(..., mode): invalid mode "${mode}". Use normal, dw, dh, dwdh.`);
515
+ const header = this.commands.GSV0_FORMAT[key];
516
+ const width = Buffer.allocUnsafe(2);
517
+ width.writeUInt16LE(raster.width, 0);
518
+ const height = Buffer.allocUnsafe(2);
519
+ height.writeUInt16LE(raster.height, 0);
520
+ this.buffer.write(Buffer.concat([header, width, height, Buffer.from(raster.data)]));
521
+ return this;
522
+ }
523
+ cashdraw(pin = 2) {
524
+ this.buffer.write(this.commands.CASH_DRAWER[(0, commands_types_1.getCashDrawerKey)(pin)]);
525
+ return this;
526
+ }
527
+ beep(n = 1, t = 1) {
528
+ const nB = Buffer.allocUnsafe(1);
529
+ const tB = Buffer.allocUnsafe(1);
530
+ nB.writeUInt8(n, 0);
531
+ tB.writeUInt8(t, 0);
532
+ this.buffer.write(Buffer.concat([this.commands.BEEP, nB, tB]));
533
+ return this;
534
+ }
535
+ /**
536
+ * Feed paper then cut.
537
+ * If the profile has ejectCommandIncludesCut (e.g. CUSTOM VKP80III),
538
+ * we still send feed if feed > 0 before the eject command.
539
+ */
540
+ cut(part = true, feed = 3) {
541
+ const n = Math.min(255, Math.max(0, feed));
542
+ const presentationCmd = this.resolveTicketPresentationCommand();
543
+ const useEjectOnly = this.profile?.ejectCommandIncludesCut && presentationCmd?.length;
544
+ if (n > 0) {
545
+ this.buffer.write(Buffer.concat([this.commands.FEED_LINES, Buffer.from([n])]));
546
+ }
547
+ if (!useEjectOnly) {
548
+ this.buffer.write(this.commands.PAPER[part ? 'PAPER_PART_CUT' : 'PAPER_FULL_CUT']);
549
+ }
550
+ if (presentationCmd?.length) {
551
+ this.buffer.write(presentationCmd);
552
+ }
553
+ return this;
554
+ }
555
+ /**
556
+ * High-level ticket presentation API.
557
+ * Uses profile-specific presentation command when available; otherwise falls back to cut().
558
+ */
559
+ presentTicket(options) {
560
+ const n = Math.min(255, Math.max(0, options?.feed ?? 3));
561
+ const part = options?.part !== false;
562
+ const presentationCmd = this.resolveTicketPresentationCommand(options);
563
+ if (!presentationCmd?.length)
564
+ return this.cut(part, n);
565
+ if (n > 0) {
566
+ this.buffer.write(Buffer.concat([this.commands.FEED_LINES, Buffer.from([n])]));
567
+ }
568
+ if (!this.profile?.ejectCommandIncludesCut) {
569
+ this.buffer.write(this.commands.PAPER[part ? 'PAPER_PART_CUT' : 'PAPER_FULL_CUT']);
570
+ }
571
+ this.buffer.write(presentationCmd);
572
+ return this;
573
+ }
574
+ async flush() {
575
+ const buf = this.buffer.flush();
576
+ if (buf.length === 0)
577
+ return this;
578
+ try {
579
+ await this.adapter.write(buf);
580
+ }
581
+ catch (error) {
582
+ // Preserve payload for retry in caller-controlled recovery flows.
583
+ this.buffer.prepend(buf);
584
+ throw error;
585
+ }
586
+ return this;
587
+ }
588
+ async close(options) {
589
+ const buf = this.buffer.flush();
590
+ if (buf.length > 0) {
591
+ try {
592
+ await this.adapter.write(buf);
593
+ }
594
+ catch (error) {
595
+ this.buffer.prepend(buf);
596
+ throw error;
597
+ }
598
+ }
599
+ await this.adapter.close(options);
600
+ return this;
601
+ }
602
+ color(color) {
603
+ this.buffer.write(this.commands.COLOR[color === 0 || color === 1 ? color : 0]);
604
+ return this;
605
+ }
606
+ setReverseColors(bool) {
607
+ this.buffer.write(bool ? this.commands.COLOR.REVERSE : this.commands.COLOR.UNREVERSE);
608
+ return this;
609
+ }
610
+ setReverseColorsAlt(bool) {
611
+ this.buffer.write(bool ? this.commands.COLOR.REVERSE_ALT : this.commands.COLOR.UNREVERSE_ALT);
612
+ return this;
613
+ }
614
+ raw(data) {
615
+ if (Buffer.isBuffer(data)) {
616
+ this.buffer.write(data);
617
+ }
618
+ else if (typeof data === 'string') {
619
+ const normalized = data.toLowerCase().replace(/(\s|:)/g, '');
620
+ if (normalized.length === 0 || normalized.length % 2 !== 0 || !/^[0-9a-f]+$/.test(normalized)) {
621
+ throw new TypeError('raw(data): hex string must have even length and contain only [0-9a-f]');
622
+ }
623
+ this.buffer.write(Buffer.from(normalized, 'hex'));
624
+ }
625
+ else {
626
+ throw new Error('Data is Invalid!');
627
+ }
628
+ return this;
629
+ }
630
+ /**
631
+ * Modern QR Code implementation using GS ( k.
632
+ * QR sequence (cn=49): fn=65 (model), fn=67 (module size), fn=69 (EC level), fn=80 (store), fn=81 (print).
633
+ */
634
+ qrcode(code, options = {}) {
635
+ const profileBuf = this.profile?.buildQrCode?.(code, options, {
636
+ commands: this.commands,
637
+ });
638
+ if (Buffer.isBuffer(profileBuf)) {
639
+ this.buffer.write(profileBuf);
640
+ return this;
641
+ }
642
+ const model = options.model ?? 2;
643
+ const size = options.size ?? 6;
644
+ const level = (options.level ?? 'L').toUpperCase();
645
+ const levels = { L: 48, M: 49, Q: 50, H: 51 };
646
+ const cmd = this.commands.CODE2D_FORMAT.GS_H;
647
+ // 1. Select Model (Function 165 -> 0x41)
648
+ this.buffer.write(Buffer.concat([cmd, Buffer.from([0x04, 0x00, 0x31, 0x41, model, 0x00])]));
649
+ // 2. Set Module Size (Function 167 -> 0x43)
650
+ this.buffer.write(Buffer.concat([cmd, Buffer.from([0x03, 0x00, 0x31, 0x43, size])]));
651
+ // 3. Set Error Correction Level (Function 169 -> 0x45)
652
+ this.buffer.write(Buffer.concat([cmd, Buffer.from([0x03, 0x00, 0x31, 0x45, levels[level] ?? 48])]));
653
+ // 4. Store Data (Function 180)
654
+ const data = Buffer.from(code, 'utf8');
655
+ const len = data.length + 3;
656
+ const pL = len % 256;
657
+ const pH = Math.floor(len / 256);
658
+ this.buffer.write(Buffer.concat([cmd, Buffer.from([pL, pH, 0x31, 0x50, 0x30]), data]));
659
+ // 5. Print QR Code (Function 181)
660
+ this.buffer.write(Buffer.concat([cmd, Buffer.from([0x03, 0x00, 0x31, 0x51, 0x30])]));
661
+ return this;
662
+ }
663
+ /**
664
+ * Legacy 2D code command using ESC Z / GS Z command family.
665
+ * Use `qrcode(...)` for modern GS ( k flow when possible.
666
+ */
667
+ code2d(code, type = 'QR', level) {
668
+ const f = this.commands.CODE2D_FORMAT;
669
+ const typeMap = {
670
+ PDF417: f.TYPE_PDF417,
671
+ DATAMATRIX: f.TYPE_DATAMATRIX,
672
+ QR: f.TYPE_QR,
673
+ };
674
+ const levelMap = {
675
+ L: f.QR_LEVEL_L,
676
+ M: f.QR_LEVEL_M,
677
+ Q: f.QR_LEVEL_Q,
678
+ H: f.QR_LEVEL_H,
679
+ };
680
+ const typeCmd = typeMap[type];
681
+ const levelCmd = level ? levelMap[level] : undefined;
682
+ this.buffer.write(Buffer.concat([
683
+ typeCmd,
684
+ f.CODE2D,
685
+ ...(levelCmd ? [levelCmd] : []),
686
+ Buffer.from(String(code), 'ascii'),
687
+ ]));
688
+ return this;
689
+ }
690
+ /**
691
+ * Request printer status in real-time.
692
+ * Returns a Buffer with the status byte.
693
+ */
694
+ async getStatus(type = 'PRINTER') {
695
+ return this.enqueueIo(async () => {
696
+ const n = this.commands.STATUS[type];
697
+ await this.adapter.write(this.commands.STATUS.DLE_EOT(n));
698
+ return this.adapter.read();
699
+ });
700
+ }
701
+ }
702
+ exports.Printer = Printer;
703
+ //# sourceMappingURL=index.js.map