taro-bluetooth-print 2.3.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -203
- package/README.md +129 -386
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +1 -1
- package/dist/index.umd.js +1 -1
- package/dist/types/core/BluetoothPrinter.d.ts +1 -1
- package/dist/types/core/EventEmitter.d.ts +6 -26
- package/dist/types/core/index.d.ts +6 -0
- package/dist/types/drivers/CpclDriver.d.ts +304 -0
- package/dist/types/drivers/GPrinterDriver.d.ts +63 -0
- package/dist/types/drivers/ZplDriver.d.ts +325 -0
- package/dist/types/drivers/index.d.ts +9 -0
- package/dist/types/encoding/gbk-lite.d.ts +8 -0
- package/dist/types/encoding/gbk-table.d.ts +8 -30
- package/dist/types/index.d.ts +7 -7
- package/dist/types/services/ConnectionManager.d.ts +1 -1
- package/dist/types/services/index.d.ts +8 -0
- package/package.json +24 -6
- package/src/adapters/BaseAdapter.ts +6 -8
- package/src/core/BluetoothPrinter.ts +15 -15
- package/src/core/EventEmitter.ts +15 -15
- package/src/core/index.ts +7 -0
- package/src/drivers/CpclDriver.ts +549 -0
- package/src/drivers/GPrinterDriver.ts +115 -0
- package/src/drivers/TsplDriver.ts +9 -21
- package/src/drivers/ZplDriver.ts +543 -0
- package/src/drivers/index.ts +37 -0
- package/src/encoding/gbk-lite.ts +108 -0
- package/src/encoding/gbk-table.ts +80 -58
- package/src/index.ts +24 -34
- package/src/plugins/PluginManager.ts +3 -1
- package/src/plugins/builtin/LoggingPlugin.ts +4 -2
- package/src/plugins/builtin/RetryPlugin.ts +8 -14
- package/src/services/ConnectionManager.ts +22 -22
- package/src/services/index.ts +16 -0
package/src/core/EventEmitter.ts
CHANGED
|
@@ -24,12 +24,12 @@
|
|
|
24
24
|
* });
|
|
25
25
|
* ```
|
|
26
26
|
*/
|
|
27
|
+
import { Logger } from '@/utils/logger';
|
|
28
|
+
|
|
27
29
|
export class EventEmitter<T> {
|
|
28
|
-
// 使用Map存储事件监听器,Set确保每个监听器唯一
|
|
29
30
|
private listeners: Map<keyof T, Set<(data: T[keyof T]) => void>> = new Map();
|
|
30
|
-
|
|
31
|
-
// Debug mode flag
|
|
32
31
|
private debugMode = false;
|
|
32
|
+
protected readonly logger = Logger.scope('EventEmitter');
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
35
|
* Subscribe to an event
|
|
@@ -45,7 +45,7 @@ export class EventEmitter<T> {
|
|
|
45
45
|
this.listeners.get(event)!.add(handler as (data: T[keyof T]) => void);
|
|
46
46
|
|
|
47
47
|
if (this.debugMode) {
|
|
48
|
-
|
|
48
|
+
this.logger.debug(`EventEmitter: Added listener for "${String(event)}"`, {
|
|
49
49
|
listenerCount: this.listenerCount(event),
|
|
50
50
|
});
|
|
51
51
|
}
|
|
@@ -90,7 +90,7 @@ export class EventEmitter<T> {
|
|
|
90
90
|
this.listeners.set(event, newHandlers);
|
|
91
91
|
|
|
92
92
|
if (this.debugMode) {
|
|
93
|
-
|
|
93
|
+
this.logger.debug(`EventEmitter: Prepend listener for "${String(event)}"`, {
|
|
94
94
|
listenerCount: this.listenerCount(event),
|
|
95
95
|
});
|
|
96
96
|
}
|
|
@@ -125,7 +125,7 @@ export class EventEmitter<T> {
|
|
|
125
125
|
handlers.delete(handler as (data: T[keyof T]) => void);
|
|
126
126
|
|
|
127
127
|
if (this.debugMode) {
|
|
128
|
-
|
|
128
|
+
this.logger.debug(`EventEmitter: Removed listener for "${String(event)}"`, {
|
|
129
129
|
listenerCount: handlers.size,
|
|
130
130
|
});
|
|
131
131
|
}
|
|
@@ -135,7 +135,7 @@ export class EventEmitter<T> {
|
|
|
135
135
|
this.listeners.delete(event);
|
|
136
136
|
|
|
137
137
|
if (this.debugMode) {
|
|
138
|
-
|
|
138
|
+
this.logger.debug(`EventEmitter: Removed event "${String(event)}" (no more listeners)`);
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
}
|
|
@@ -154,7 +154,7 @@ export class EventEmitter<T> {
|
|
|
154
154
|
const data = args[0] as T[K];
|
|
155
155
|
|
|
156
156
|
if (this.debugMode) {
|
|
157
|
-
|
|
157
|
+
this.logger.debug(`EventEmitter: Emitting "${String(event)}"`, {
|
|
158
158
|
data,
|
|
159
159
|
listenerCount: this.listenerCount(event),
|
|
160
160
|
});
|
|
@@ -184,7 +184,7 @@ export class EventEmitter<T> {
|
|
|
184
184
|
}
|
|
185
185
|
} catch (error) {
|
|
186
186
|
// 捕获并处理事件处理程序中的错误,避免影响其他监听器
|
|
187
|
-
|
|
187
|
+
this.logger.error(`Error in event handler for "${String(event)}":`, error);
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
}
|
|
@@ -204,7 +204,7 @@ export class EventEmitter<T> {
|
|
|
204
204
|
const data = args[0] as T[K];
|
|
205
205
|
|
|
206
206
|
if (this.debugMode) {
|
|
207
|
-
|
|
207
|
+
this.logger.debug(`EventEmitter: Emitting async "${String(event)}"`, {
|
|
208
208
|
data,
|
|
209
209
|
listenerCount: this.listenerCount(event),
|
|
210
210
|
});
|
|
@@ -238,7 +238,7 @@ export class EventEmitter<T> {
|
|
|
238
238
|
}
|
|
239
239
|
} catch (error) {
|
|
240
240
|
// 捕获并处理事件处理程序中的错误,避免影响其他监听器
|
|
241
|
-
|
|
241
|
+
this.logger.error(`Error in event handler for "${String(event)}":`, error);
|
|
242
242
|
}
|
|
243
243
|
})()
|
|
244
244
|
);
|
|
@@ -247,7 +247,7 @@ export class EventEmitter<T> {
|
|
|
247
247
|
await Promise.all(promises);
|
|
248
248
|
|
|
249
249
|
if (this.debugMode) {
|
|
250
|
-
|
|
250
|
+
this.logger.debug(`EventEmitter: Finished emitting async "${String(event)}"`);
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
|
|
@@ -262,7 +262,7 @@ export class EventEmitter<T> {
|
|
|
262
262
|
this.listeners.delete(event);
|
|
263
263
|
|
|
264
264
|
if (this.debugMode) {
|
|
265
|
-
|
|
265
|
+
this.logger.debug(
|
|
266
266
|
`EventEmitter: Removed all ${listenerCount} listeners for "${String(event)}"`
|
|
267
267
|
);
|
|
268
268
|
}
|
|
@@ -272,7 +272,7 @@ export class EventEmitter<T> {
|
|
|
272
272
|
this.listeners.clear();
|
|
273
273
|
|
|
274
274
|
if (this.debugMode) {
|
|
275
|
-
|
|
275
|
+
this.logger.debug(`EventEmitter: Removed all ${eventCount} events and their listeners`);
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
}
|
|
@@ -327,7 +327,7 @@ export class EventEmitter<T> {
|
|
|
327
327
|
*/
|
|
328
328
|
setDebugMode(enabled: boolean): void {
|
|
329
329
|
this.debugMode = enabled;
|
|
330
|
-
|
|
330
|
+
this.logger.debug(`EventEmitter: Debug mode ${enabled ? 'enabled' : 'disabled'}`);
|
|
331
331
|
}
|
|
332
332
|
|
|
333
333
|
/**
|
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CPCL Driver
|
|
3
|
+
* PCL commands for Compact Print Language - HP/Honeywell/Toddler printers
|
|
4
|
+
*
|
|
5
|
+
* CPCL is commonly used in mobile/portable thermal printers
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Logger } from '@/utils/logger';
|
|
9
|
+
import { Encoding } from '@/utils/encoding';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* CPCL page size presets
|
|
13
|
+
*/
|
|
14
|
+
export type CPCLPageSize = 'A4' | 'A5' | 'LETTER' | '4X6' | '4X2' | '4X4' | '2.25X1.25' | 'CUSTOM';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* CPCL Text options
|
|
18
|
+
*/
|
|
19
|
+
export interface CpclTextOptions {
|
|
20
|
+
/** X position in dots */
|
|
21
|
+
x?: number;
|
|
22
|
+
/** Y position in dots */
|
|
23
|
+
y?: number;
|
|
24
|
+
/** Font type: 0-6 (0=OCR, 1=OCR-B, 2=CG Triumvirate, etc.) */
|
|
25
|
+
font?: number;
|
|
26
|
+
/** Horizontal multiplier (1-8) */
|
|
27
|
+
xMulti?: number;
|
|
28
|
+
/** Vertical multiplier (1-8) */
|
|
29
|
+
yMulti?: number;
|
|
30
|
+
/** Rotation: 0, 90, 180, 270 */
|
|
31
|
+
rotation?: 0 | 90 | 180 | 270;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* CPCL Barcode options
|
|
36
|
+
*/
|
|
37
|
+
export interface CpclBarcodeOptions {
|
|
38
|
+
/** X position in dots */
|
|
39
|
+
x?: number;
|
|
40
|
+
/** Y position in dots */
|
|
41
|
+
y?: number;
|
|
42
|
+
/** Barcode type */
|
|
43
|
+
type:
|
|
44
|
+
| '128'
|
|
45
|
+
| '39'
|
|
46
|
+
| 'EAN13'
|
|
47
|
+
| 'EAN8'
|
|
48
|
+
| 'UPCA'
|
|
49
|
+
| 'UPCE'
|
|
50
|
+
| 'MSI'
|
|
51
|
+
| 'PLESSEY'
|
|
52
|
+
| 'PDF417'
|
|
53
|
+
| 'DATAMATRIX'
|
|
54
|
+
| 'QR';
|
|
55
|
+
/** Barcode height in dots (default: 50) */
|
|
56
|
+
height?: number;
|
|
57
|
+
/** Wide bar width for 39 (default: 2) */
|
|
58
|
+
wide?: number;
|
|
59
|
+
/** Narrow bar width (default: 1) */
|
|
60
|
+
narrow?: number;
|
|
61
|
+
/** Show human-readable text (default: true) */
|
|
62
|
+
readable?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* CPCL QR Code options
|
|
67
|
+
*/
|
|
68
|
+
export interface CpclQRCodeOptions {
|
|
69
|
+
/** X position in dots */
|
|
70
|
+
x?: number;
|
|
71
|
+
/** Y position in dots */
|
|
72
|
+
y?: number;
|
|
73
|
+
/** Model (default: 2) */
|
|
74
|
+
model?: 1 | 2;
|
|
75
|
+
/** Error correction level: L, M, Q, H */
|
|
76
|
+
errorCorrection?: 'L' | 'M' | 'Q' | 'H';
|
|
77
|
+
/** Cell size (default: 4) */
|
|
78
|
+
cellSize?: number;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* CPCL Line options
|
|
83
|
+
*/
|
|
84
|
+
export interface CpclLineOptions {
|
|
85
|
+
/** Start X position */
|
|
86
|
+
x1: number;
|
|
87
|
+
/** Start Y position */
|
|
88
|
+
y1: number;
|
|
89
|
+
/** End X position */
|
|
90
|
+
x2: number;
|
|
91
|
+
/** End Y position */
|
|
92
|
+
y2: number;
|
|
93
|
+
/** Line width/thickness */
|
|
94
|
+
width?: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* CPCL Box options
|
|
99
|
+
*/
|
|
100
|
+
export interface CpclBoxOptions {
|
|
101
|
+
/** X position */
|
|
102
|
+
x: number;
|
|
103
|
+
/** Y position */
|
|
104
|
+
y: number;
|
|
105
|
+
/** Width */
|
|
106
|
+
width: number;
|
|
107
|
+
/** Height */
|
|
108
|
+
height: number;
|
|
109
|
+
/** Border thickness */
|
|
110
|
+
thickness?: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* CPCL Driver for HP, Honeywell, and Toddler portable printers
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```typescript
|
|
118
|
+
* const cpcl = new CpclDriver();
|
|
119
|
+
*
|
|
120
|
+
* const commands = cpcl
|
|
121
|
+
* .pageStart()
|
|
122
|
+
* .text('Hello World!', { x: 50, y: 50 })
|
|
123
|
+
* .barcode('1234567890', { x: 50, y: 150, type: '128' })
|
|
124
|
+
* .qrcode('https://example.com', { x: 300, y: 50 })
|
|
125
|
+
* .pageEnd()
|
|
126
|
+
* .getCommands();
|
|
127
|
+
* ```
|
|
128
|
+
*/
|
|
129
|
+
export class CpclDriver {
|
|
130
|
+
private commands: string[] = [];
|
|
131
|
+
private readonly logger = Logger.scope('CpclDriver');
|
|
132
|
+
|
|
133
|
+
// Page dimensions
|
|
134
|
+
private pageWidth = 576; // 4" at 144 DPI (common)
|
|
135
|
+
private pageHeight = 0; // 0 = continuous
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Initialize CPCL driver
|
|
139
|
+
* @param width - Page width in dots
|
|
140
|
+
* @param height - Page height in dots (0 = continuous)
|
|
141
|
+
*/
|
|
142
|
+
constructor(width = 576, height = 0) {
|
|
143
|
+
this.pageWidth = width;
|
|
144
|
+
this.pageHeight = height;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Set page size
|
|
149
|
+
* @param width - Width in dots
|
|
150
|
+
* @param height - Height in dots (0 = continuous)
|
|
151
|
+
*/
|
|
152
|
+
setPageSize(width: number, height = 0): this {
|
|
153
|
+
this.pageWidth = width;
|
|
154
|
+
this.pageHeight = height;
|
|
155
|
+
return this;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Use standard page size
|
|
160
|
+
* @param size - Preset size name
|
|
161
|
+
*/
|
|
162
|
+
usePageSize(size: CPCLPageSize): this {
|
|
163
|
+
const sizes: Record<CPCLPageSize, [number, number]> = {
|
|
164
|
+
'4X6': [576, 864],
|
|
165
|
+
'4X4': [576, 576],
|
|
166
|
+
'4X2': [576, 288],
|
|
167
|
+
'2.25X1.25': [324, 180],
|
|
168
|
+
A4: [992, 1406],
|
|
169
|
+
A5: [701, 992],
|
|
170
|
+
LETTER: [1050, 1500],
|
|
171
|
+
CUSTOM: [576, 0],
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const [width, height] = sizes[size] || [576, 0];
|
|
175
|
+
this.pageWidth = width;
|
|
176
|
+
this.pageHeight = height;
|
|
177
|
+
return this;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Start a new page
|
|
182
|
+
*/
|
|
183
|
+
pageStart(): this {
|
|
184
|
+
this.commands.push(`! U1 SETLP 5 0 30`);
|
|
185
|
+
this.commands.push(`! U1 PAGE WIDTH ${this.pageWidth}`);
|
|
186
|
+
if (this.pageHeight > 0) {
|
|
187
|
+
this.commands.push(`! U1 PAGE HEIGHT ${this.pageHeight}`);
|
|
188
|
+
}
|
|
189
|
+
this.commands.push('START');
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* End current page
|
|
195
|
+
*/
|
|
196
|
+
pageEnd(): this {
|
|
197
|
+
this.commands.push('END');
|
|
198
|
+
return this;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Form feed to next label
|
|
203
|
+
*/
|
|
204
|
+
formFeed(): this {
|
|
205
|
+
this.commands.push('FORM');
|
|
206
|
+
return this;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Set line print mode
|
|
211
|
+
* @param font - Font number (0-6)
|
|
212
|
+
* @param xMulti - Horizontal multiplier
|
|
213
|
+
* @param yMulti - Vertical multiplier
|
|
214
|
+
*/
|
|
215
|
+
setLinePrint(font = 5, xMulti = 0, yMulti = 30): this {
|
|
216
|
+
this.commands.push(`! U1 SETLP ${font} ${xMulti} ${yMulti}`);
|
|
217
|
+
return this;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Set text font
|
|
222
|
+
* @param font - Font: 0=OCR, 1=OCR-B, 2=CG Triumvirate Bold, 3-6=Various sizes
|
|
223
|
+
* @param xMulti - Horizontal multiplier (1-8)
|
|
224
|
+
* @param yMulti - Vertical multiplier (1-8)
|
|
225
|
+
* @param rotation - Rotation angle
|
|
226
|
+
*/
|
|
227
|
+
setFont(font = 3, xMulti = 1, yMulti = 1, rotation: 0 | 90 | 180 | 270 = 0): this {
|
|
228
|
+
const rotationMap: Record<number, string> = { 0: 'N', 90: 'R', 180: 'B', 270: 'I' };
|
|
229
|
+
const rot = rotationMap[rotation] || 'N';
|
|
230
|
+
this.commands.push(`! U1 SETLP ${font} ${xMulti} ${yMulti} ${rot}`);
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Add text at current position
|
|
236
|
+
* @param content - Text content
|
|
237
|
+
*/
|
|
238
|
+
text(content: string): this {
|
|
239
|
+
this.commands.push(`! U1 TEXT ${this.escapeText(content)}`);
|
|
240
|
+
return this;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Add positioned text
|
|
245
|
+
* @param content - Text content
|
|
246
|
+
* @param options - Text options
|
|
247
|
+
*/
|
|
248
|
+
textAt(content: string, options: CpclTextOptions): this {
|
|
249
|
+
const { x = 0, y = 0, font = 3, xMulti = 1, yMulti = 1, rotation = 0 } = options;
|
|
250
|
+
const rotationMap: Record<number, string> = { 0: 'N', 90: 'R', 180: 'B', 270: 'I' };
|
|
251
|
+
const rot = rotationMap[rotation] || 'N';
|
|
252
|
+
|
|
253
|
+
this.commands.push(`! U1 SETLP ${font} ${xMulti} ${yMulti} ${rot}`);
|
|
254
|
+
this.commands.push(`! U1 ${x} ${y} TEXT ${this.escapeText(content)}`);
|
|
255
|
+
return this;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Add text using legacy command
|
|
260
|
+
* @param content - Text content
|
|
261
|
+
* @param x - X position
|
|
262
|
+
* @param y - Y position
|
|
263
|
+
* @param font - Font number
|
|
264
|
+
*/
|
|
265
|
+
legacyText(content: string, x: number, y: number, font = 3): this {
|
|
266
|
+
this.commands.push(`TEXT ${x} ${y} ${font} "${this.escapeText(content)}"`);
|
|
267
|
+
return this;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Add barcode at position
|
|
272
|
+
* @param content - Barcode content
|
|
273
|
+
* @param options - Barcode options
|
|
274
|
+
*/
|
|
275
|
+
barcode(content: string, options: CpclBarcodeOptions): this {
|
|
276
|
+
const {
|
|
277
|
+
x = 0,
|
|
278
|
+
y = 0,
|
|
279
|
+
type = '128',
|
|
280
|
+
height = 50,
|
|
281
|
+
wide = 2,
|
|
282
|
+
narrow = 1,
|
|
283
|
+
readable = true,
|
|
284
|
+
} = options;
|
|
285
|
+
|
|
286
|
+
const readableFlag = readable ? 'B' : 'N';
|
|
287
|
+
|
|
288
|
+
// CPCL barcode commands
|
|
289
|
+
switch (type) {
|
|
290
|
+
case '128':
|
|
291
|
+
this.commands.push(
|
|
292
|
+
`BARCODE 128 ${x} ${y} ${height} ${readableFlag} 0 ${narrow} ${wide} "${content}"`
|
|
293
|
+
);
|
|
294
|
+
break;
|
|
295
|
+
case '39':
|
|
296
|
+
this.commands.push(
|
|
297
|
+
`BARCODE 39 ${x} ${y} ${height} ${readableFlag} ${wide} ${narrow} "${content}"`
|
|
298
|
+
);
|
|
299
|
+
break;
|
|
300
|
+
case 'EAN13':
|
|
301
|
+
this.commands.push(`BARCODE EAN13 ${x} ${y} ${height} ${readableFlag} "${content}"`);
|
|
302
|
+
break;
|
|
303
|
+
case 'EAN8':
|
|
304
|
+
this.commands.push(`BARCODE EAN8 ${x} ${y} ${height} ${readableFlag} "${content}"`);
|
|
305
|
+
break;
|
|
306
|
+
case 'UPCA':
|
|
307
|
+
this.commands.push(`BARCODE UPCA ${x} ${y} ${height} ${readableFlag} "${content}"`);
|
|
308
|
+
break;
|
|
309
|
+
case 'UPCE':
|
|
310
|
+
this.commands.push(`BARCODE UPCE ${x} ${y} ${height} ${readableFlag} "${content}"`);
|
|
311
|
+
break;
|
|
312
|
+
case 'PDF417':
|
|
313
|
+
this.commands.push(`BARCODE PDF417 ${x} ${y} 6 200 "${content}"`);
|
|
314
|
+
break;
|
|
315
|
+
case 'DATAMATRIX':
|
|
316
|
+
this.commands.push(`BARCODE DATAMATRIX ${x} ${y} 200 "${content}"`);
|
|
317
|
+
break;
|
|
318
|
+
case 'QR':
|
|
319
|
+
// QR uses separate command
|
|
320
|
+
this.qrcode(content, { x, y });
|
|
321
|
+
break;
|
|
322
|
+
default:
|
|
323
|
+
this.commands.push(
|
|
324
|
+
`BARCODE 128 ${x} ${y} ${height} ${readableFlag} 0 ${narrow} ${wide} "${content}"`
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return this;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Add Code 128 barcode (most common)
|
|
333
|
+
* @param content - Barcode content
|
|
334
|
+
* @param x - X position
|
|
335
|
+
* @param y - Y position
|
|
336
|
+
* @param height - Barcode height
|
|
337
|
+
*/
|
|
338
|
+
code128(content: string, x = 0, y = 0, height = 50): this {
|
|
339
|
+
return this.barcode(content, { x, y, type: '128', height });
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Add Code 39 barcode
|
|
344
|
+
* @param content - Barcode content
|
|
345
|
+
* @param x - X position
|
|
346
|
+
* @param y - Y position
|
|
347
|
+
* @param height - Barcode height
|
|
348
|
+
*/
|
|
349
|
+
code39(content: string, x = 0, y = 0, height = 50): this {
|
|
350
|
+
return this.barcode(content, { x, y, type: '39', height, wide: 2, narrow: 1 });
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Add QR code
|
|
355
|
+
* @param content - QR code content
|
|
356
|
+
* @param options - QR options
|
|
357
|
+
*/
|
|
358
|
+
qrcode(content: string, options?: CpclQRCodeOptions): this {
|
|
359
|
+
const { x = 0, y = 0, model = 2, errorCorrection = 'M', cellSize = 4 } = options || {};
|
|
360
|
+
this.commands.push(
|
|
361
|
+
`BARCODE QR ${x} ${y} ${model} ${cellSize} A ${errorCorrection} "${content}"`
|
|
362
|
+
);
|
|
363
|
+
return this;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Add 2D barcode (PDF417 or DataMatrix)
|
|
368
|
+
* @param content - Content
|
|
369
|
+
* @param type - Type: PDF417 or DATAMATRIX
|
|
370
|
+
* @param x - X position
|
|
371
|
+
* @param y - Y position
|
|
372
|
+
* @param height - Height
|
|
373
|
+
*/
|
|
374
|
+
twoDBarcode(content: string, type: 'PDF417' | 'DATAMATRIX', x = 0, y = 0, height = 200): this {
|
|
375
|
+
if (type === 'PDF417') {
|
|
376
|
+
this.commands.push(`BARCODE PDF417 ${x} ${y} 6 ${height} "${content}"`);
|
|
377
|
+
} else {
|
|
378
|
+
this.commands.push(`BARCODE DATAMATRIX ${x} ${y} ${height} "${content}"`);
|
|
379
|
+
}
|
|
380
|
+
return this;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Add line
|
|
385
|
+
* @param options - Line options
|
|
386
|
+
*/
|
|
387
|
+
line(options: CpclLineOptions): this {
|
|
388
|
+
const { x1, y1, x2, y2, width = 1 } = options;
|
|
389
|
+
this.commands.push(`LINE ${x1} ${y1} ${x2} ${y2} ${width}`);
|
|
390
|
+
return this;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Add box/rectangle
|
|
395
|
+
* @param options - Box options
|
|
396
|
+
*/
|
|
397
|
+
box(options: CpclBoxOptions): this {
|
|
398
|
+
const { x, y, width, height, thickness = 1 } = options;
|
|
399
|
+
this.commands.push(`BOX ${x} ${y} ${width} ${height} ${thickness}`);
|
|
400
|
+
return this;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Add inverse area (white on black)
|
|
405
|
+
* @param x - X position
|
|
406
|
+
* @param y - Y position
|
|
407
|
+
* @param width - Width
|
|
408
|
+
* @param height - Height
|
|
409
|
+
*/
|
|
410
|
+
inverse(x: number, y: number, width: number, height: number): this {
|
|
411
|
+
this.commands.push(`INVERSE ${width} ${height} ${x} ${y}`);
|
|
412
|
+
return this;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Set print density
|
|
417
|
+
* @param density - Density value (-50 to +50)
|
|
418
|
+
*/
|
|
419
|
+
setDensity(density: number): this {
|
|
420
|
+
const safeDensity = Math.min(50, Math.max(-50, density));
|
|
421
|
+
this.commands.push(`! U1 SETDENSITY ${safeDensity}`);
|
|
422
|
+
return this;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Set speed
|
|
427
|
+
* @param speed - Speed: 2-6 (2=50%, 3=75%, 4=100%, 5=125%, 6=150%)
|
|
428
|
+
*/
|
|
429
|
+
setSpeed(speed: number): this {
|
|
430
|
+
const safeSpeed = Math.min(6, Math.max(2, speed));
|
|
431
|
+
this.commands.push(`! U1 SETSPEED ${safeSpeed}`);
|
|
432
|
+
return this;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Cut paper (if cutter installed)
|
|
437
|
+
*/
|
|
438
|
+
cut(): this {
|
|
439
|
+
this.commands.push('CUT');
|
|
440
|
+
return this;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Partial cut
|
|
445
|
+
*/
|
|
446
|
+
partialCut(): this {
|
|
447
|
+
this.commands.push('PCUT');
|
|
448
|
+
return this;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Feed and cut
|
|
453
|
+
*/
|
|
454
|
+
feedCut(): this {
|
|
455
|
+
this.commands.push('FEED CUT');
|
|
456
|
+
return this;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Sound beep
|
|
461
|
+
* @param count - Number of beeps
|
|
462
|
+
* @param duration - Duration in 10ms units
|
|
463
|
+
*/
|
|
464
|
+
beep(count = 1, duration = 5): this {
|
|
465
|
+
this.commands.push(`BEEP ${count} ${duration}`);
|
|
466
|
+
return this;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Add logo/image
|
|
471
|
+
* @param x - X position
|
|
472
|
+
* @param y - Y position
|
|
473
|
+
* @param logoName - Stored logo name
|
|
474
|
+
*/
|
|
475
|
+
logo(x: number, y: number, logoName: string): this {
|
|
476
|
+
this.commands.push(`LOGO ${x} ${y} "${logoName}"`);
|
|
477
|
+
return this;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Download logo to printer (store in memory)
|
|
482
|
+
* Note: This is a placeholder for future implementation
|
|
483
|
+
* @param logoName - Name to store logo as
|
|
484
|
+
* @param _bitmap - Logo bitmap data (placeholder)
|
|
485
|
+
*/
|
|
486
|
+
downloadLogo(logoName: string, _bitmap: Uint8Array): this {
|
|
487
|
+
this.commands.push(`! DF ${logoName}`);
|
|
488
|
+
// TODO: Encode bitmap to CPCL format
|
|
489
|
+
this.logger.debug('CPCL logo download not fully implemented');
|
|
490
|
+
this.commands.push(`! DF`);
|
|
491
|
+
return this;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Print stored logo
|
|
496
|
+
* @param logoName - Logo name
|
|
497
|
+
* @param x - X position
|
|
498
|
+
* @param y - Y position
|
|
499
|
+
*/
|
|
500
|
+
printLogo(logoName: string, x = 0, y = 0): this {
|
|
501
|
+
this.commands.push(`LOGO ${x} ${y} "${logoName}"`);
|
|
502
|
+
return this;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Get all commands as string
|
|
507
|
+
*/
|
|
508
|
+
getCommands(): string {
|
|
509
|
+
return this.commands.join('\r\n') + '\r\n';
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Get commands as buffer for sending to printer
|
|
514
|
+
*/
|
|
515
|
+
getBuffer(): Uint8Array {
|
|
516
|
+
const commandString = this.getCommands();
|
|
517
|
+
this.logger.debug(`CPCL commands:\n${commandString}`);
|
|
518
|
+
return Encoding.encode(commandString, 'ASCII');
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Get raw command list
|
|
523
|
+
*/
|
|
524
|
+
getCommandList(): string[] {
|
|
525
|
+
return [...this.commands];
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Clear all commands
|
|
530
|
+
*/
|
|
531
|
+
reset(): this {
|
|
532
|
+
this.commands = [];
|
|
533
|
+
return this;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Get page width
|
|
538
|
+
*/
|
|
539
|
+
getPageWidth(): number {
|
|
540
|
+
return this.pageWidth;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Escape special characters in text
|
|
545
|
+
*/
|
|
546
|
+
private escapeText(str: string): string {
|
|
547
|
+
return str.replace(/"/g, '""');
|
|
548
|
+
}
|
|
549
|
+
}
|