@thermal-label/brother-ql-core 0.0.1 → 0.2.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.
Files changed (60) hide show
  1. package/dist/__tests__/colour.test.d.ts +2 -0
  2. package/dist/__tests__/colour.test.d.ts.map +1 -0
  3. package/dist/__tests__/colour.test.js +106 -0
  4. package/dist/__tests__/colour.test.js.map +1 -0
  5. package/dist/__tests__/devices.test.js +16 -2
  6. package/dist/__tests__/devices.test.js.map +1 -1
  7. package/dist/__tests__/media.test.js +17 -6
  8. package/dist/__tests__/media.test.js.map +1 -1
  9. package/dist/__tests__/preview.test.d.ts +2 -0
  10. package/dist/__tests__/preview.test.d.ts.map +1 -0
  11. package/dist/__tests__/preview.test.js +41 -0
  12. package/dist/__tests__/preview.test.js.map +1 -0
  13. package/dist/__tests__/status.test.js +28 -22
  14. package/dist/__tests__/status.test.js.map +1 -1
  15. package/dist/colour.d.ts +26 -0
  16. package/dist/colour.d.ts.map +1 -0
  17. package/dist/colour.js +84 -0
  18. package/dist/colour.js.map +1 -0
  19. package/dist/devices.d.ts +40 -21
  20. package/dist/devices.d.ts.map +1 -1
  21. package/dist/devices.js +38 -19
  22. package/dist/devices.js.map +1 -1
  23. package/dist/index.d.ts +6 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +4 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/media.d.ts +31 -4
  28. package/dist/media.d.ts.map +1 -1
  29. package/dist/media.js +73 -23
  30. package/dist/media.js.map +1 -1
  31. package/dist/preview.d.ts +13 -0
  32. package/dist/preview.d.ts.map +1 -0
  33. package/dist/preview.js +32 -0
  34. package/dist/preview.js.map +1 -0
  35. package/dist/protocol.d.ts +2 -2
  36. package/dist/protocol.d.ts.map +1 -1
  37. package/dist/protocol.js +4 -4
  38. package/dist/protocol.js.map +1 -1
  39. package/dist/status.d.ts +20 -2
  40. package/dist/status.d.ts.map +1 -1
  41. package/dist/status.js +59 -44
  42. package/dist/status.js.map +1 -1
  43. package/dist/types.d.ts +36 -30
  44. package/dist/types.d.ts.map +1 -1
  45. package/dist/types.js +1 -0
  46. package/dist/types.js.map +1 -1
  47. package/package.json +3 -2
  48. package/src/__tests__/colour.test.ts +126 -0
  49. package/src/__tests__/devices.test.ts +18 -2
  50. package/src/__tests__/media.test.ts +17 -6
  51. package/src/__tests__/preview.test.ts +52 -0
  52. package/src/__tests__/status.test.ts +31 -22
  53. package/src/colour.ts +101 -0
  54. package/src/devices.ts +41 -22
  55. package/src/index.ts +31 -9
  56. package/src/media.ts +86 -27
  57. package/src/preview.ts +34 -0
  58. package/src/protocol.ts +6 -6
  59. package/src/status.ts +63 -47
  60. package/src/types.ts +38 -33
package/src/devices.ts CHANGED
@@ -1,199 +1,214 @@
1
- import { type DeviceDescriptor } from './types.js';
1
+ import { type BrotherQLDevice } from './types.js';
2
2
 
3
3
  const MASS_STORAGE_PIDS = new Set([0x20aa, 0x20ab]);
4
4
 
5
5
  export const DEVICES = {
6
6
  QL_820NWB: {
7
7
  name: 'QL-820NWB',
8
+ family: 'brother-ql',
9
+ transports: ['usb', 'webusb', 'tcp', 'serial', 'web-serial'],
8
10
  vid: 0x04f9,
9
11
  pid: 0x20a7,
10
12
  headPins: 720,
11
13
  bytesPerRow: 90,
12
14
  twoColor: true,
13
15
  network: 'wifi+wired',
14
- bluetooth: false,
15
16
  autocut: true,
16
17
  compression: true,
17
18
  editorLite: true,
18
19
  },
19
20
  QL_820NWBc: {
20
21
  name: 'QL-820NWBc',
22
+ family: 'brother-ql',
23
+ transports: ['usb', 'webusb', 'tcp', 'serial', 'web-serial'],
21
24
  vid: 0x04f9,
22
25
  pid: 0x209d,
23
26
  headPins: 720,
24
27
  bytesPerRow: 90,
25
28
  twoColor: true,
26
29
  network: 'wifi+wired',
27
- bluetooth: false,
28
30
  autocut: true,
29
31
  compression: true,
30
32
  editorLite: true,
31
33
  },
32
34
  QL_810W: {
33
35
  name: 'QL-810W',
36
+ family: 'brother-ql',
37
+ transports: ['usb', 'webusb', 'tcp'],
34
38
  vid: 0x04f9,
35
39
  pid: 0x209c,
36
40
  headPins: 720,
37
41
  bytesPerRow: 90,
38
42
  twoColor: true,
39
43
  network: 'wifi',
40
- bluetooth: false,
41
44
  autocut: true,
42
45
  compression: true,
43
46
  editorLite: true,
44
47
  },
45
48
  QL_800: {
46
49
  name: 'QL-800',
50
+ family: 'brother-ql',
51
+ transports: ['usb', 'webusb'],
47
52
  vid: 0x04f9,
48
53
  pid: 0x209b,
49
54
  headPins: 720,
50
55
  bytesPerRow: 90,
51
56
  twoColor: true,
52
57
  network: 'none',
53
- bluetooth: false,
54
58
  autocut: true,
55
59
  compression: true,
56
60
  editorLite: true,
57
61
  },
58
62
  QL_720NW: {
59
63
  name: 'QL-720NW',
64
+ family: 'brother-ql',
65
+ transports: ['usb', 'webusb', 'tcp'],
60
66
  vid: 0x04f9,
61
67
  pid: 0x2045,
62
68
  headPins: 720,
63
69
  bytesPerRow: 90,
64
70
  twoColor: false,
65
71
  network: 'wired',
66
- bluetooth: false,
67
72
  autocut: true,
68
73
  compression: true,
69
74
  editorLite: false,
70
75
  },
71
76
  QL_710W: {
72
77
  name: 'QL-710W',
78
+ family: 'brother-ql',
79
+ transports: ['usb', 'webusb', 'tcp'],
73
80
  vid: 0x04f9,
74
81
  pid: 0x2044,
75
82
  headPins: 720,
76
83
  bytesPerRow: 90,
77
84
  twoColor: false,
78
85
  network: 'wifi',
79
- bluetooth: false,
80
86
  autocut: true,
81
87
  compression: true,
82
88
  editorLite: true,
83
89
  },
84
90
  QL_700: {
85
91
  name: 'QL-700',
92
+ family: 'brother-ql',
93
+ transports: ['usb', 'webusb'],
86
94
  vid: 0x04f9,
87
95
  pid: 0x2042,
88
96
  headPins: 720,
89
97
  bytesPerRow: 90,
90
98
  twoColor: false,
91
99
  network: 'none',
92
- bluetooth: false,
93
100
  autocut: true,
94
101
  compression: true,
95
102
  editorLite: true,
96
103
  },
97
104
  QL_600: {
98
105
  name: 'QL-600',
106
+ family: 'brother-ql',
107
+ transports: ['usb', 'webusb'],
99
108
  vid: 0x04f9,
100
109
  pid: 0x2100,
101
110
  headPins: 720,
102
111
  bytesPerRow: 90,
103
112
  twoColor: false,
104
113
  network: 'none',
105
- bluetooth: false,
106
114
  autocut: true,
107
115
  compression: true,
108
116
  editorLite: false,
109
117
  },
110
118
  QL_580N: {
111
119
  name: 'QL-580N',
120
+ family: 'brother-ql',
121
+ transports: ['usb', 'webusb', 'tcp'],
112
122
  vid: 0x04f9,
113
123
  pid: 0x201b,
114
124
  headPins: 720,
115
125
  bytesPerRow: 90,
116
126
  twoColor: false,
117
127
  network: 'wired',
118
- bluetooth: false,
119
128
  autocut: true,
120
129
  compression: true,
121
130
  editorLite: false,
122
131
  },
123
132
  QL_570: {
124
133
  name: 'QL-570',
134
+ family: 'brother-ql',
135
+ transports: ['usb', 'webusb'],
125
136
  vid: 0x04f9,
126
137
  pid: 0x2019,
127
138
  headPins: 720,
128
139
  bytesPerRow: 90,
129
140
  twoColor: false,
130
141
  network: 'none',
131
- bluetooth: false,
132
142
  autocut: true,
133
143
  compression: true,
134
144
  editorLite: false,
135
145
  },
136
146
  QL_560: {
137
147
  name: 'QL-560',
148
+ family: 'brother-ql',
149
+ transports: ['usb', 'webusb'],
138
150
  vid: 0x04f9,
139
151
  pid: 0x2018,
140
152
  headPins: 720,
141
153
  bytesPerRow: 90,
142
154
  twoColor: false,
143
155
  network: 'none',
144
- bluetooth: false,
145
156
  autocut: true,
146
157
  compression: false,
147
158
  editorLite: false,
148
159
  },
149
160
  QL_550: {
150
161
  name: 'QL-550',
162
+ family: 'brother-ql',
163
+ transports: ['usb', 'webusb'],
151
164
  vid: 0x04f9,
152
165
  pid: 0x2016,
153
166
  headPins: 720,
154
167
  bytesPerRow: 90,
155
168
  twoColor: false,
156
169
  network: 'none',
157
- bluetooth: false,
158
170
  autocut: false,
159
171
  compression: false,
160
172
  editorLite: false,
161
173
  },
162
174
  QL_500: {
163
175
  name: 'QL-500',
176
+ family: 'brother-ql',
177
+ transports: ['usb', 'webusb'],
164
178
  vid: 0x04f9,
165
179
  pid: 0x2013,
166
180
  headPins: 720,
167
181
  bytesPerRow: 90,
168
182
  twoColor: false,
169
183
  network: 'none',
170
- bluetooth: false,
171
184
  autocut: false,
172
185
  compression: false,
173
186
  editorLite: false,
174
187
  },
175
188
  QL_650TD: {
176
189
  name: 'QL-650TD',
190
+ family: 'brother-ql',
191
+ transports: ['usb', 'webusb'],
177
192
  vid: 0x04f9,
178
193
  pid: 0x201c,
179
194
  headPins: 720,
180
195
  bytesPerRow: 90,
181
196
  twoColor: false,
182
197
  network: 'none',
183
- bluetooth: false,
184
198
  autocut: true,
185
199
  compression: true,
186
200
  editorLite: false,
187
201
  },
188
202
  QL_1100: {
189
203
  name: 'QL-1100',
204
+ family: 'brother-ql',
205
+ transports: ['usb', 'webusb'],
190
206
  vid: 0x04f9,
191
207
  pid: 0x20a8,
192
208
  headPins: 1296,
193
209
  bytesPerRow: 162,
194
210
  twoColor: false,
195
211
  network: 'none',
196
- bluetooth: false,
197
212
  autocut: true,
198
213
  compression: true,
199
214
  editorLite: true,
@@ -201,13 +216,14 @@ export const DEVICES = {
201
216
  },
202
217
  QL_1110NWB: {
203
218
  name: 'QL-1110NWB',
219
+ family: 'brother-ql',
220
+ transports: ['usb', 'webusb', 'tcp'],
204
221
  vid: 0x04f9,
205
222
  pid: 0x20a9,
206
223
  headPins: 1296,
207
224
  bytesPerRow: 162,
208
225
  twoColor: false,
209
226
  network: 'wifi+wired',
210
- bluetooth: false,
211
227
  autocut: true,
212
228
  compression: true,
213
229
  editorLite: true,
@@ -215,46 +231,49 @@ export const DEVICES = {
215
231
  },
216
232
  QL_1115NWB: {
217
233
  name: 'QL-1115NWB',
234
+ family: 'brother-ql',
235
+ transports: ['usb', 'webusb', 'tcp'],
218
236
  vid: 0x04f9,
219
237
  pid: 0x20ac,
220
238
  headPins: 1296,
221
239
  bytesPerRow: 162,
222
240
  twoColor: false,
223
241
  network: 'wifi+wired',
224
- bluetooth: false,
225
242
  autocut: true,
226
243
  compression: true,
227
244
  editorLite: true,
228
245
  },
229
246
  QL_1050: {
230
247
  name: 'QL-1050',
248
+ family: 'brother-ql',
249
+ transports: ['usb', 'webusb'],
231
250
  vid: 0x04f9,
232
251
  pid: 0x2027,
233
252
  headPins: 1296,
234
253
  bytesPerRow: 162,
235
254
  twoColor: false,
236
255
  network: 'none',
237
- bluetooth: false,
238
256
  autocut: true,
239
257
  compression: true,
240
258
  editorLite: false,
241
259
  },
242
260
  QL_1060N: {
243
261
  name: 'QL-1060N',
262
+ family: 'brother-ql',
263
+ transports: ['usb', 'webusb', 'tcp'],
244
264
  vid: 0x04f9,
245
265
  pid: 0x2028,
246
266
  headPins: 1296,
247
267
  bytesPerRow: 162,
248
268
  twoColor: false,
249
269
  network: 'wired',
250
- bluetooth: false,
251
270
  autocut: true,
252
271
  compression: true,
253
272
  editorLite: false,
254
273
  },
255
- } as const satisfies Record<string, DeviceDescriptor>;
274
+ } as const satisfies Record<string, BrotherQLDevice>;
256
275
 
257
- export function findDevice(vid: number, pid: number): DeviceDescriptor | undefined {
276
+ export function findDevice(vid: number, pid: number): BrotherQLDevice | undefined {
258
277
  return Object.values(DEVICES).find(d => d.vid === vid && d.pid === pid);
259
278
  }
260
279
 
package/src/index.ts CHANGED
@@ -1,22 +1,44 @@
1
1
  export type { LabelBitmap, RawImageData } from '@mbtech-nl/bitmap';
2
2
  export { renderText, renderImage, rotateBitmap, flipHorizontal } from '@mbtech-nl/bitmap';
3
3
 
4
+ export type {
5
+ DeviceDescriptor,
6
+ MediaDescriptor,
7
+ PreviewOptions,
8
+ PreviewPlane,
9
+ PreviewResult,
10
+ PrintOptions,
11
+ PrinterAdapter,
12
+ PrinterError,
13
+ PrinterStatus,
14
+ Transport,
15
+ TransportType,
16
+ } from '@thermal-label/contracts';
17
+
18
+ export { MediaNotSpecifiedError, UnsupportedOperationError } from '@thermal-label/contracts';
19
+
4
20
  export { DEVICES, findDevice, isMassStorageMode } from './devices.js';
5
- export { MEDIA, findMedia, findMediaByWidth } from './media.js';
21
+ export {
22
+ DEFAULT_MEDIA,
23
+ MEDIA,
24
+ findMedia,
25
+ findMediaByDimensions,
26
+ findMediaByWidth,
27
+ } from './media.js';
6
28
  export { encodeJob } from './protocol.js';
7
29
  export { parseStatus, STATUS_REQUEST } from './status.js';
30
+ export { isRedish, splitTwoColor, type TwoColorOptions, type TwoColorResult } from './colour.js';
31
+ export { createPreviewOffline } from './preview.js';
8
32
 
9
33
  export type {
10
- MediaType,
11
- HeadWidth,
34
+ BrotherQLDevice,
35
+ BrotherQLMedia,
36
+ BrotherQLStatus,
12
37
  ColorMode,
38
+ HeadWidth,
39
+ JobOptions,
40
+ MediaType,
13
41
  NetworkSupport,
14
- DeviceDescriptor,
15
- MediaDescriptor,
16
42
  PageData,
17
43
  PageOptions,
18
- JobOptions,
19
- PrinterStatus,
20
- TextPrintOptions,
21
- ImagePrintOptions,
22
44
  } from './types.js';
package/src/media.ts CHANGED
@@ -1,13 +1,23 @@
1
- import { type MediaDescriptor, type MediaType } from './types.js';
1
+ import { type BrotherQLMedia, type MediaType } from './types.js';
2
2
 
3
- export const MEDIA: Record<number, MediaDescriptor> = {
3
+ /**
4
+ * Registry of supported Brother QL consumables.
5
+ *
6
+ * Entries are keyed by the firmware media id — the same number the
7
+ * printer reports in the 32-byte status response. `heightMm` is
8
+ * omitted for continuous media (variable length) and set for die-cut
9
+ * labels (fixed length). Only DK-22251 has `colorCapable: true` — the
10
+ * driver uses that to run two-colour plane separation before sending
11
+ * the raster.
12
+ */
13
+ export const MEDIA: Record<number, BrotherQLMedia> = {
4
14
  // Continuous length tape
5
15
  257: {
6
16
  id: 257,
7
17
  name: '12mm continuous',
8
18
  type: 'continuous',
9
19
  widthMm: 12,
10
- lengthMm: 0,
20
+ colorCapable: false,
11
21
  printAreaDots: 106,
12
22
  leftMarginPins: 585,
13
23
  rightMarginPins: 29,
@@ -17,7 +27,7 @@ export const MEDIA: Record<number, MediaDescriptor> = {
17
27
  name: '29mm continuous (DK-22210)',
18
28
  type: 'continuous',
19
29
  widthMm: 29,
20
- lengthMm: 0,
30
+ colorCapable: false,
21
31
  printAreaDots: 306,
22
32
  leftMarginPins: 408,
23
33
  rightMarginPins: 6,
@@ -27,7 +37,7 @@ export const MEDIA: Record<number, MediaDescriptor> = {
27
37
  name: '38mm continuous (DK-22225)',
28
38
  type: 'continuous',
29
39
  widthMm: 38,
30
- lengthMm: 0,
40
+ colorCapable: false,
31
41
  printAreaDots: 413,
32
42
  leftMarginPins: 295,
33
43
  rightMarginPins: 12,
@@ -37,7 +47,7 @@ export const MEDIA: Record<number, MediaDescriptor> = {
37
47
  name: '50mm continuous (DK-22246)',
38
48
  type: 'continuous',
39
49
  widthMm: 50,
40
- lengthMm: 0,
50
+ colorCapable: false,
41
51
  printAreaDots: 554,
42
52
  leftMarginPins: 154,
43
53
  rightMarginPins: 12,
@@ -47,7 +57,7 @@ export const MEDIA: Record<number, MediaDescriptor> = {
47
57
  name: '54mm continuous (DK-22214)',
48
58
  type: 'continuous',
49
59
  widthMm: 54,
50
- lengthMm: 0,
60
+ colorCapable: false,
51
61
  printAreaDots: 590,
52
62
  leftMarginPins: 130,
53
63
  rightMarginPins: 0,
@@ -57,7 +67,7 @@ export const MEDIA: Record<number, MediaDescriptor> = {
57
67
  name: '62mm continuous (DK-22205)',
58
68
  type: 'continuous',
59
69
  widthMm: 62,
60
- lengthMm: 0,
70
+ colorCapable: false,
61
71
  printAreaDots: 696,
62
72
  leftMarginPins: 12,
63
73
  rightMarginPins: 12,
@@ -67,18 +77,17 @@ export const MEDIA: Record<number, MediaDescriptor> = {
67
77
  name: '62mm continuous two-color (DK-22251)',
68
78
  type: 'continuous',
69
79
  widthMm: 62,
70
- lengthMm: 0,
80
+ colorCapable: true,
71
81
  printAreaDots: 696,
72
82
  leftMarginPins: 12,
73
83
  rightMarginPins: 12,
74
- twoColorTape: true,
75
84
  },
76
85
  260: {
77
86
  id: 260,
78
87
  name: '102mm continuous (DK-22243)',
79
88
  type: 'continuous',
80
89
  widthMm: 102,
81
- lengthMm: 0,
90
+ colorCapable: false,
82
91
  printAreaDots: 1164,
83
92
  leftMarginPins: 76,
84
93
  rightMarginPins: 56,
@@ -90,7 +99,8 @@ export const MEDIA: Record<number, MediaDescriptor> = {
90
99
  name: '17×54mm die-cut (DK-11204)',
91
100
  type: 'die-cut',
92
101
  widthMm: 17,
93
- lengthMm: 54,
102
+ heightMm: 54,
103
+ colorCapable: false,
94
104
  printAreaDots: 165,
95
105
  leftMarginPins: 0,
96
106
  rightMarginPins: 0,
@@ -101,7 +111,8 @@ export const MEDIA: Record<number, MediaDescriptor> = {
101
111
  name: '17×87mm die-cut (DK-11203)',
102
112
  type: 'die-cut',
103
113
  widthMm: 17,
104
- lengthMm: 87,
114
+ heightMm: 87,
115
+ colorCapable: false,
105
116
  printAreaDots: 165,
106
117
  leftMarginPins: 0,
107
118
  rightMarginPins: 0,
@@ -112,7 +123,8 @@ export const MEDIA: Record<number, MediaDescriptor> = {
112
123
  name: '23×23mm die-cut',
113
124
  type: 'die-cut',
114
125
  widthMm: 23,
115
- lengthMm: 23,
126
+ heightMm: 23,
127
+ colorCapable: false,
116
128
  printAreaDots: 236,
117
129
  leftMarginPins: 0,
118
130
  rightMarginPins: 0,
@@ -123,7 +135,8 @@ export const MEDIA: Record<number, MediaDescriptor> = {
123
135
  name: '29×90mm die-cut (DK-11201)',
124
136
  type: 'die-cut',
125
137
  widthMm: 29,
126
- lengthMm: 90,
138
+ heightMm: 90,
139
+ colorCapable: false,
127
140
  printAreaDots: 306,
128
141
  leftMarginPins: 0,
129
142
  rightMarginPins: 0,
@@ -134,7 +147,8 @@ export const MEDIA: Record<number, MediaDescriptor> = {
134
147
  name: '38×90mm die-cut (DK-11218)',
135
148
  type: 'die-cut',
136
149
  widthMm: 38,
137
- lengthMm: 90,
150
+ heightMm: 90,
151
+ colorCapable: false,
138
152
  printAreaDots: 413,
139
153
  leftMarginPins: 0,
140
154
  rightMarginPins: 0,
@@ -145,7 +159,8 @@ export const MEDIA: Record<number, MediaDescriptor> = {
145
159
  name: '39×48mm die-cut (DK-11219)',
146
160
  type: 'die-cut',
147
161
  widthMm: 39,
148
- lengthMm: 48,
162
+ heightMm: 48,
163
+ colorCapable: false,
149
164
  printAreaDots: 425,
150
165
  leftMarginPins: 0,
151
166
  rightMarginPins: 0,
@@ -156,7 +171,8 @@ export const MEDIA: Record<number, MediaDescriptor> = {
156
171
  name: '52×29mm die-cut',
157
172
  type: 'die-cut',
158
173
  widthMm: 52,
159
- lengthMm: 29,
174
+ heightMm: 29,
175
+ colorCapable: false,
160
176
  printAreaDots: 578,
161
177
  leftMarginPins: 0,
162
178
  rightMarginPins: 0,
@@ -167,7 +183,8 @@ export const MEDIA: Record<number, MediaDescriptor> = {
167
183
  name: '62×29mm die-cut (DK-11209)',
168
184
  type: 'die-cut',
169
185
  widthMm: 62,
170
- lengthMm: 29,
186
+ heightMm: 29,
187
+ colorCapable: false,
171
188
  printAreaDots: 696,
172
189
  leftMarginPins: 0,
173
190
  rightMarginPins: 0,
@@ -178,7 +195,8 @@ export const MEDIA: Record<number, MediaDescriptor> = {
178
195
  name: '62×100mm die-cut (DK-11202)',
179
196
  type: 'die-cut',
180
197
  widthMm: 62,
181
- lengthMm: 100,
198
+ heightMm: 100,
199
+ colorCapable: false,
182
200
  printAreaDots: 696,
183
201
  leftMarginPins: 0,
184
202
  rightMarginPins: 0,
@@ -189,7 +207,8 @@ export const MEDIA: Record<number, MediaDescriptor> = {
189
207
  name: '102×51mm die-cut (DK-11240)',
190
208
  type: 'die-cut',
191
209
  widthMm: 102,
192
- lengthMm: 51,
210
+ heightMm: 51,
211
+ colorCapable: false,
193
212
  printAreaDots: 1164,
194
213
  leftMarginPins: 0,
195
214
  rightMarginPins: 0,
@@ -200,7 +219,8 @@ export const MEDIA: Record<number, MediaDescriptor> = {
200
219
  name: '102×152mm die-cut (DK-11241)',
201
220
  type: 'die-cut',
202
221
  widthMm: 102,
203
- lengthMm: 152,
222
+ heightMm: 152,
223
+ colorCapable: false,
204
224
  printAreaDots: 1164,
205
225
  leftMarginPins: 0,
206
226
  rightMarginPins: 0,
@@ -211,7 +231,8 @@ export const MEDIA: Record<number, MediaDescriptor> = {
211
231
  name: '12mm Ø die-cut',
212
232
  type: 'die-cut',
213
233
  widthMm: 12,
214
- lengthMm: 12,
234
+ heightMm: 12,
235
+ colorCapable: false,
215
236
  printAreaDots: 94,
216
237
  leftMarginPins: 0,
217
238
  rightMarginPins: 0,
@@ -222,7 +243,8 @@ export const MEDIA: Record<number, MediaDescriptor> = {
222
243
  name: '24mm Ø die-cut (DK-11221)',
223
244
  type: 'die-cut',
224
245
  widthMm: 24,
225
- lengthMm: 24,
246
+ heightMm: 24,
247
+ colorCapable: false,
226
248
  printAreaDots: 236,
227
249
  leftMarginPins: 0,
228
250
  rightMarginPins: 0,
@@ -233,7 +255,8 @@ export const MEDIA: Record<number, MediaDescriptor> = {
233
255
  name: '58mm Ø die-cut (DK-11207)',
234
256
  type: 'die-cut',
235
257
  widthMm: 58,
236
- lengthMm: 58,
258
+ heightMm: 58,
259
+ colorCapable: false,
237
260
  printAreaDots: 618,
238
261
  leftMarginPins: 0,
239
262
  rightMarginPins: 0,
@@ -241,10 +264,46 @@ export const MEDIA: Record<number, MediaDescriptor> = {
241
264
  },
242
265
  };
243
266
 
244
- export function findMedia(id: number): MediaDescriptor | undefined {
267
+ /**
268
+ * Default media when `createPreview()` is called without media and
269
+ * without a detected roll. 62mm continuous (DK-22205) is the common
270
+ * single-colour shipping roll.
271
+ */
272
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- DK-22205 is a hard-coded registry key
273
+ export const DEFAULT_MEDIA: BrotherQLMedia = MEDIA[259]!;
274
+
275
+ export function findMedia(id: number): BrotherQLMedia | undefined {
245
276
  return MEDIA[id];
246
277
  }
247
278
 
248
- export function findMediaByWidth(widthMm: number, type: MediaType): MediaDescriptor[] {
279
+ export function findMediaByWidth(widthMm: number, type: MediaType): BrotherQLMedia[] {
249
280
  return Object.values(MEDIA).filter(m => m.widthMm === widthMm && m.type === type);
250
281
  }
282
+
283
+ /**
284
+ * Match status-response dimensions to a media registry entry.
285
+ *
286
+ * @param widthMm media width in mm (status byte 10)
287
+ * @param heightMm media length in mm (status byte 17) — 0 = continuous
288
+ * @param twoColorMode true when the status response indicates the
289
+ * printer is configured for two-colour media. When
290
+ * both DK-22205 (259) and DK-22251 (251) match the
291
+ * dimensions, the flag picks the right one.
292
+ */
293
+ export function findMediaByDimensions(
294
+ widthMm: number,
295
+ heightMm: number,
296
+ twoColorMode = false,
297
+ ): BrotherQLMedia | undefined {
298
+ if (heightMm === 0) {
299
+ const continuousMatches = Object.values(MEDIA).filter(
300
+ m => m.type === 'continuous' && m.widthMm === widthMm,
301
+ );
302
+ if (continuousMatches.length === 0) return undefined;
303
+ const preferred = continuousMatches.find(m => m.colorCapable === twoColorMode);
304
+ return preferred ?? continuousMatches[0];
305
+ }
306
+ return Object.values(MEDIA).find(
307
+ m => m.type === 'die-cut' && m.widthMm === widthMm && m.heightMm === heightMm,
308
+ );
309
+ }
package/src/preview.ts ADDED
@@ -0,0 +1,34 @@
1
+ /* eslint-disable import-x/consistent-type-specifier-style */
2
+ import { renderImage, type RawImageData } from '@mbtech-nl/bitmap';
3
+ import type { PreviewResult } from '@thermal-label/contracts';
4
+ import { splitTwoColor } from './colour.js';
5
+ import { type BrotherQLMedia } from './types.js';
6
+
7
+ /**
8
+ * Offline preview without a live printer connection.
9
+ *
10
+ * Two-colour aware: when `media.colorCapable` is true (DK-22251), the
11
+ * image is split into a black and a red plane via `splitTwoColor()` —
12
+ * exactly what `print()` does for that media. Single-colour media
13
+ * returns one black plane.
14
+ */
15
+ export function createPreviewOffline(image: RawImageData, media: BrotherQLMedia): PreviewResult {
16
+ if (media.colorCapable) {
17
+ const { black, red } = splitTwoColor(image);
18
+ return {
19
+ planes: [
20
+ { name: 'black', bitmap: black, displayColor: '#000000' },
21
+ { name: 'red', bitmap: red, displayColor: '#ff0000' },
22
+ ],
23
+ media,
24
+ assumed: false,
25
+ };
26
+ }
27
+
28
+ const bitmap = renderImage(image, { dither: true });
29
+ return {
30
+ planes: [{ name: 'black', bitmap, displayColor: '#000000' }],
31
+ media,
32
+ assumed: false,
33
+ };
34
+ }
package/src/protocol.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { getRow, createBitmap } from '@mbtech-nl/bitmap';
2
- import { type MediaDescriptor, type PageData, type JobOptions, type PageOptions } from './types.js';
2
+ import { type BrotherQLMedia, type PageData, type JobOptions, type PageOptions } from './types.js';
3
3
 
4
4
  export function buildInvalidate(): Uint8Array {
5
5
  return new Uint8Array(200);
@@ -22,7 +22,7 @@ export function buildStatusNotification(enabled: boolean): Uint8Array {
22
22
  }
23
23
 
24
24
  export function buildPrintInfo(
25
- media: MediaDescriptor,
25
+ media: BrotherQLMedia,
26
26
  rowCount: number,
27
27
  pageIndex: number,
28
28
  ): Uint8Array {
@@ -38,7 +38,7 @@ export function buildPrintInfo(
38
38
  buf[3] = validFlags;
39
39
  buf[4] = mediaType;
40
40
  buf[5] = media.widthMm;
41
- buf[6] = media.lengthMm;
41
+ buf[6] = media.heightMm ?? 0;
42
42
  // rowCount little-endian at bytes 7-8 (offsets 4-5 in param block)
43
43
  buf[7] = rowCount & 0xff;
44
44
  buf[8] = (rowCount >> 8) & 0xff;
@@ -161,12 +161,12 @@ export function encodeJob(pages: PageData[], options: JobOptions = {}): Uint8Arr
161
161
  const compress = opts.compress ?? false;
162
162
  const { bitmap, media } = page;
163
163
 
164
- // twoColorTape media (e.g. DK-22251) requires two-color mode even for black-only jobs.
164
+ // colorCapable media (e.g. DK-22251) requires two-color mode even for black-only jobs.
165
165
  // Auto-create an empty red plane when the tape demands it but caller didn't supply one.
166
- const twoColor = page.redBitmap !== undefined || media.twoColorTape === true;
166
+ const twoColor = page.redBitmap !== undefined || media.colorCapable;
167
167
  const redBitmap =
168
168
  page.redBitmap ??
169
- (media.twoColorTape ? createBitmap(bitmap.widthPx, bitmap.heightPx) : undefined);
169
+ (media.colorCapable ? createBitmap(bitmap.widthPx, bitmap.heightPx) : undefined);
170
170
 
171
171
  if (twoColor && redBitmap !== undefined) {
172
172
  if (bitmap.widthPx !== redBitmap.widthPx || bitmap.heightPx !== redBitmap.heightPx) {