@thermal-label/brother-ql-core 0.2.1 → 0.4.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.
- package/README.md +1 -1
- package/data/devices.json +823 -0
- package/data/media.json +823 -0
- package/dist/__tests__/devices.test.js +112 -31
- package/dist/__tests__/devices.test.js.map +1 -1
- package/dist/__tests__/media.test.js +274 -4
- package/dist/__tests__/media.test.js.map +1 -1
- package/dist/__tests__/pack-bits.test.d.ts +2 -0
- package/dist/__tests__/pack-bits.test.d.ts.map +1 -0
- package/dist/__tests__/pack-bits.test.js +90 -0
- package/dist/__tests__/pack-bits.test.js.map +1 -0
- package/dist/__tests__/preview.test.js +1 -1
- package/dist/__tests__/preview.test.js.map +1 -1
- package/dist/__tests__/protocol.test.js +214 -2
- package/dist/__tests__/protocol.test.js.map +1 -1
- package/dist/__tests__/status.test.js +71 -0
- package/dist/__tests__/status.test.js.map +1 -1
- package/dist/devices.d.ts +14 -271
- package/dist/devices.d.ts.map +1 -1
- package/dist/devices.generated.d.ts +696 -0
- package/dist/devices.generated.d.ts.map +1 -0
- package/dist/devices.generated.js +831 -0
- package/dist/devices.generated.js.map +1 -0
- package/dist/devices.js +28 -273
- package/dist/devices.js.map +1 -1
- package/dist/index.d.ts +10 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/media.d.ts +37 -10
- package/dist/media.d.ts.map +1 -1
- package/dist/media.generated.d.ts +4 -0
- package/dist/media.generated.d.ts.map +1 -0
- package/dist/media.generated.js +1640 -0
- package/dist/media.generated.js.map +1 -0
- package/dist/media.js +75 -264
- package/dist/media.js.map +1 -1
- package/dist/orientation.d.ts +11 -0
- package/dist/orientation.d.ts.map +1 -0
- package/dist/orientation.js +10 -0
- package/dist/orientation.js.map +1 -0
- package/dist/pack-bits.d.ts +20 -0
- package/dist/pack-bits.d.ts.map +1 -0
- package/dist/pack-bits.js +61 -0
- package/dist/pack-bits.js.map +1 -0
- package/dist/preview.d.ts +6 -6
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +11 -12
- package/dist/preview.js.map +1 -1
- package/dist/protocol.d.ts +54 -3
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +125 -20
- package/dist/protocol.js.map +1 -1
- package/dist/status.d.ts +5 -2
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +6 -3
- package/dist/status.js.map +1 -1
- package/dist/types.d.ts +106 -31
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -2
- package/dist/types.js.map +1 -1
- package/package.json +13 -9
- package/src/__tests__/devices.test.ts +122 -32
- package/src/__tests__/media.test.ts +312 -4
- package/src/__tests__/pack-bits.test.ts +92 -0
- package/src/__tests__/preview.test.ts +1 -1
- package/src/__tests__/protocol.test.ts +256 -1
- package/src/__tests__/status.test.ts +87 -0
- package/src/devices.generated.ts +840 -0
- package/src/devices.ts +31 -273
- package/src/index.ts +36 -8
- package/src/media.generated.ts +1644 -0
- package/src/media.ts +87 -264
- package/src/orientation.ts +11 -0
- package/src/pack-bits.ts +64 -0
- package/src/preview.ts +13 -12
- package/src/protocol.ts +204 -19
- package/src/status.ts +11 -5
- package/src/types.ts +113 -32
- package/dist/__tests__/colour.test.d.ts +0 -2
- package/dist/__tests__/colour.test.d.ts.map +0 -1
- package/dist/__tests__/colour.test.js +0 -106
- package/dist/__tests__/colour.test.js.map +0 -1
- package/dist/colour.d.ts +0 -26
- package/dist/colour.d.ts.map +0 -1
- package/dist/colour.js +0 -84
- package/dist/colour.js.map +0 -1
- package/src/__tests__/colour.test.ts +0 -126
- package/src/colour.ts +0 -101
package/src/media.ts
CHANGED
|
@@ -1,268 +1,49 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { PrintEngine } from '@thermal-label/contracts';
|
|
2
|
+
import type { BrotherQLMedia, MediaType, TapeGeometry } from './types.js';
|
|
3
|
+
import { MEDIA } from './media.generated.js';
|
|
4
|
+
|
|
5
|
+
export { MEDIA };
|
|
2
6
|
|
|
3
7
|
/**
|
|
4
|
-
*
|
|
8
|
+
* Resolve per-head-family geometry for a media entry against the
|
|
9
|
+
* engine that's about to print it.
|
|
5
10
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
11
|
+
* DK entries fall back to the flat `printAreaDots` / `leftMarginPins` /
|
|
12
|
+
* `rightMarginPins` fields on the entry — the same values every QL
|
|
13
|
+
* code path read before the head-family split landed. TZe and HSe
|
|
14
|
+
* entries dispatch on `engine.headDots`: 128 picks `geometry.narrow`,
|
|
15
|
+
* anything else picks `geometry.wide`. Throws when the requested head
|
|
16
|
+
* family has no entry (e.g. 36 mm TZe on a 128-dot head, or any HSe
|
|
17
|
+
* on PT-P910BT — those engines simply shouldn't reach this call site
|
|
18
|
+
* because `findMediaByDimensions` gates them upstream).
|
|
12
19
|
*/
|
|
13
|
-
export
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
colorCapable: false,
|
|
41
|
-
printAreaDots: 413,
|
|
42
|
-
leftMarginPins: 295,
|
|
43
|
-
rightMarginPins: 12,
|
|
44
|
-
},
|
|
45
|
-
262: {
|
|
46
|
-
id: 262,
|
|
47
|
-
name: '50mm continuous (DK-22246)',
|
|
48
|
-
type: 'continuous',
|
|
49
|
-
widthMm: 50,
|
|
50
|
-
colorCapable: false,
|
|
51
|
-
printAreaDots: 554,
|
|
52
|
-
leftMarginPins: 154,
|
|
53
|
-
rightMarginPins: 12,
|
|
54
|
-
},
|
|
55
|
-
261: {
|
|
56
|
-
id: 261,
|
|
57
|
-
name: '54mm continuous (DK-22214)',
|
|
58
|
-
type: 'continuous',
|
|
59
|
-
widthMm: 54,
|
|
60
|
-
colorCapable: false,
|
|
61
|
-
printAreaDots: 590,
|
|
62
|
-
leftMarginPins: 130,
|
|
63
|
-
rightMarginPins: 0,
|
|
64
|
-
},
|
|
65
|
-
259: {
|
|
66
|
-
id: 259,
|
|
67
|
-
name: '62mm continuous (DK-22205)',
|
|
68
|
-
type: 'continuous',
|
|
69
|
-
widthMm: 62,
|
|
70
|
-
colorCapable: false,
|
|
71
|
-
printAreaDots: 696,
|
|
72
|
-
leftMarginPins: 12,
|
|
73
|
-
rightMarginPins: 12,
|
|
74
|
-
},
|
|
75
|
-
251: {
|
|
76
|
-
id: 251,
|
|
77
|
-
name: '62mm continuous two-color (DK-22251)',
|
|
78
|
-
type: 'continuous',
|
|
79
|
-
widthMm: 62,
|
|
80
|
-
colorCapable: true,
|
|
81
|
-
printAreaDots: 696,
|
|
82
|
-
leftMarginPins: 12,
|
|
83
|
-
rightMarginPins: 12,
|
|
84
|
-
},
|
|
85
|
-
260: {
|
|
86
|
-
id: 260,
|
|
87
|
-
name: '102mm continuous (DK-22243)',
|
|
88
|
-
type: 'continuous',
|
|
89
|
-
widthMm: 102,
|
|
90
|
-
colorCapable: false,
|
|
91
|
-
printAreaDots: 1164,
|
|
92
|
-
leftMarginPins: 76,
|
|
93
|
-
rightMarginPins: 56,
|
|
94
|
-
},
|
|
95
|
-
|
|
96
|
-
// Die-cut labels
|
|
97
|
-
269: {
|
|
98
|
-
id: 269,
|
|
99
|
-
name: '17×54mm die-cut (DK-11204)',
|
|
100
|
-
type: 'die-cut',
|
|
101
|
-
widthMm: 17,
|
|
102
|
-
heightMm: 54,
|
|
103
|
-
colorCapable: false,
|
|
104
|
-
printAreaDots: 165,
|
|
105
|
-
leftMarginPins: 0,
|
|
106
|
-
rightMarginPins: 0,
|
|
107
|
-
dieCutMaskedAreaDots: 566,
|
|
108
|
-
},
|
|
109
|
-
270: {
|
|
110
|
-
id: 270,
|
|
111
|
-
name: '17×87mm die-cut (DK-11203)',
|
|
112
|
-
type: 'die-cut',
|
|
113
|
-
widthMm: 17,
|
|
114
|
-
heightMm: 87,
|
|
115
|
-
colorCapable: false,
|
|
116
|
-
printAreaDots: 165,
|
|
117
|
-
leftMarginPins: 0,
|
|
118
|
-
rightMarginPins: 0,
|
|
119
|
-
dieCutMaskedAreaDots: 956,
|
|
120
|
-
},
|
|
121
|
-
370: {
|
|
122
|
-
id: 370,
|
|
123
|
-
name: '23×23mm die-cut',
|
|
124
|
-
type: 'die-cut',
|
|
125
|
-
widthMm: 23,
|
|
126
|
-
heightMm: 23,
|
|
127
|
-
colorCapable: false,
|
|
128
|
-
printAreaDots: 236,
|
|
129
|
-
leftMarginPins: 0,
|
|
130
|
-
rightMarginPins: 0,
|
|
131
|
-
dieCutMaskedAreaDots: 202,
|
|
132
|
-
},
|
|
133
|
-
271: {
|
|
134
|
-
id: 271,
|
|
135
|
-
name: '29×90mm die-cut (DK-11201)',
|
|
136
|
-
type: 'die-cut',
|
|
137
|
-
widthMm: 29,
|
|
138
|
-
heightMm: 90,
|
|
139
|
-
colorCapable: false,
|
|
140
|
-
printAreaDots: 306,
|
|
141
|
-
leftMarginPins: 0,
|
|
142
|
-
rightMarginPins: 0,
|
|
143
|
-
dieCutMaskedAreaDots: 991,
|
|
144
|
-
},
|
|
145
|
-
272: {
|
|
146
|
-
id: 272,
|
|
147
|
-
name: '38×90mm die-cut (DK-11218)',
|
|
148
|
-
type: 'die-cut',
|
|
149
|
-
widthMm: 38,
|
|
150
|
-
heightMm: 90,
|
|
151
|
-
colorCapable: false,
|
|
152
|
-
printAreaDots: 413,
|
|
153
|
-
leftMarginPins: 0,
|
|
154
|
-
rightMarginPins: 0,
|
|
155
|
-
dieCutMaskedAreaDots: 991,
|
|
156
|
-
},
|
|
157
|
-
367: {
|
|
158
|
-
id: 367,
|
|
159
|
-
name: '39×48mm die-cut (DK-11219)',
|
|
160
|
-
type: 'die-cut',
|
|
161
|
-
widthMm: 39,
|
|
162
|
-
heightMm: 48,
|
|
163
|
-
colorCapable: false,
|
|
164
|
-
printAreaDots: 425,
|
|
165
|
-
leftMarginPins: 0,
|
|
166
|
-
rightMarginPins: 0,
|
|
167
|
-
dieCutMaskedAreaDots: 495,
|
|
168
|
-
},
|
|
169
|
-
374: {
|
|
170
|
-
id: 374,
|
|
171
|
-
name: '52×29mm die-cut',
|
|
172
|
-
type: 'die-cut',
|
|
173
|
-
widthMm: 52,
|
|
174
|
-
heightMm: 29,
|
|
175
|
-
colorCapable: false,
|
|
176
|
-
printAreaDots: 578,
|
|
177
|
-
leftMarginPins: 0,
|
|
178
|
-
rightMarginPins: 0,
|
|
179
|
-
dieCutMaskedAreaDots: 271,
|
|
180
|
-
},
|
|
181
|
-
274: {
|
|
182
|
-
id: 274,
|
|
183
|
-
name: '62×29mm die-cut (DK-11209)',
|
|
184
|
-
type: 'die-cut',
|
|
185
|
-
widthMm: 62,
|
|
186
|
-
heightMm: 29,
|
|
187
|
-
colorCapable: false,
|
|
188
|
-
printAreaDots: 696,
|
|
189
|
-
leftMarginPins: 0,
|
|
190
|
-
rightMarginPins: 0,
|
|
191
|
-
dieCutMaskedAreaDots: 271,
|
|
192
|
-
},
|
|
193
|
-
275: {
|
|
194
|
-
id: 275,
|
|
195
|
-
name: '62×100mm die-cut (DK-11202)',
|
|
196
|
-
type: 'die-cut',
|
|
197
|
-
widthMm: 62,
|
|
198
|
-
heightMm: 100,
|
|
199
|
-
colorCapable: false,
|
|
200
|
-
printAreaDots: 696,
|
|
201
|
-
leftMarginPins: 0,
|
|
202
|
-
rightMarginPins: 0,
|
|
203
|
-
dieCutMaskedAreaDots: 1109,
|
|
204
|
-
},
|
|
205
|
-
365: {
|
|
206
|
-
id: 365,
|
|
207
|
-
name: '102×51mm die-cut (DK-11240)',
|
|
208
|
-
type: 'die-cut',
|
|
209
|
-
widthMm: 102,
|
|
210
|
-
heightMm: 51,
|
|
211
|
-
colorCapable: false,
|
|
212
|
-
printAreaDots: 1164,
|
|
213
|
-
leftMarginPins: 0,
|
|
214
|
-
rightMarginPins: 0,
|
|
215
|
-
dieCutMaskedAreaDots: 526,
|
|
216
|
-
},
|
|
217
|
-
366: {
|
|
218
|
-
id: 366,
|
|
219
|
-
name: '102×152mm die-cut (DK-11241)',
|
|
220
|
-
type: 'die-cut',
|
|
221
|
-
widthMm: 102,
|
|
222
|
-
heightMm: 152,
|
|
223
|
-
colorCapable: false,
|
|
224
|
-
printAreaDots: 1164,
|
|
225
|
-
leftMarginPins: 0,
|
|
226
|
-
rightMarginPins: 0,
|
|
227
|
-
dieCutMaskedAreaDots: 1660,
|
|
228
|
-
},
|
|
229
|
-
362: {
|
|
230
|
-
id: 362,
|
|
231
|
-
name: '12mm Ø die-cut',
|
|
232
|
-
type: 'die-cut',
|
|
233
|
-
widthMm: 12,
|
|
234
|
-
heightMm: 12,
|
|
235
|
-
colorCapable: false,
|
|
236
|
-
printAreaDots: 94,
|
|
237
|
-
leftMarginPins: 0,
|
|
238
|
-
rightMarginPins: 0,
|
|
239
|
-
dieCutMaskedAreaDots: 94,
|
|
240
|
-
},
|
|
241
|
-
363: {
|
|
242
|
-
id: 363,
|
|
243
|
-
name: '24mm Ø die-cut (DK-11221)',
|
|
244
|
-
type: 'die-cut',
|
|
245
|
-
widthMm: 24,
|
|
246
|
-
heightMm: 24,
|
|
247
|
-
colorCapable: false,
|
|
248
|
-
printAreaDots: 236,
|
|
249
|
-
leftMarginPins: 0,
|
|
250
|
-
rightMarginPins: 0,
|
|
251
|
-
dieCutMaskedAreaDots: 236,
|
|
252
|
-
},
|
|
253
|
-
273: {
|
|
254
|
-
id: 273,
|
|
255
|
-
name: '58mm Ø die-cut (DK-11207)',
|
|
256
|
-
type: 'die-cut',
|
|
257
|
-
widthMm: 58,
|
|
258
|
-
heightMm: 58,
|
|
259
|
-
colorCapable: false,
|
|
260
|
-
printAreaDots: 618,
|
|
261
|
-
leftMarginPins: 0,
|
|
262
|
-
rightMarginPins: 0,
|
|
263
|
-
dieCutMaskedAreaDots: 618,
|
|
264
|
-
},
|
|
265
|
-
};
|
|
20
|
+
export function resolveTapeGeometry(
|
|
21
|
+
media: BrotherQLMedia,
|
|
22
|
+
engine: Pick<PrintEngine, 'headDots'>,
|
|
23
|
+
): TapeGeometry {
|
|
24
|
+
if (media.tapeSystem === 'dk') {
|
|
25
|
+
if (
|
|
26
|
+
typeof media.printAreaDots !== 'number' ||
|
|
27
|
+
typeof media.leftMarginPins !== 'number' ||
|
|
28
|
+
typeof media.rightMarginPins !== 'number'
|
|
29
|
+
) {
|
|
30
|
+
throw new Error(`DK media ${media.id.toString()} missing flat geometry fields`);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
printAreaDots: media.printAreaDots,
|
|
34
|
+
leftMarginPins: media.leftMarginPins,
|
|
35
|
+
rightMarginPins: media.rightMarginPins,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const family = engine.headDots === 128 ? 'narrow' : 'wide';
|
|
39
|
+
const geometry = media.geometry?.[family];
|
|
40
|
+
if (!geometry) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`${media.name} not supported on a ${engine.headDots.toString()}-dot head (no \`geometry.${family}\` entry)`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return geometry;
|
|
46
|
+
}
|
|
266
47
|
|
|
267
48
|
/**
|
|
268
49
|
* Default media when `createPreview()` is called without media and
|
|
@@ -272,6 +53,22 @@ export const MEDIA: Record<number, BrotherQLMedia> = {
|
|
|
272
53
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- DK-22205 is a hard-coded registry key
|
|
273
54
|
export const DEFAULT_MEDIA: BrotherQLMedia = MEDIA[259]!;
|
|
274
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Default media keyed by engine protocol. QL falls back to 62 mm DK
|
|
58
|
+
* continuous (DK-22205, the common starter roll); PT falls back to
|
|
59
|
+
* 12 mm TZe (id 404, the most common PT starter tape).
|
|
60
|
+
*/
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- 12 mm TZe is a hard-coded registry key
|
|
62
|
+
export const DEFAULT_PT_MEDIA: BrotherQLMedia = MEDIA[404]!;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Pick a default media entry for an engine. Used by `createPreview()`
|
|
66
|
+
* when neither user-supplied media nor a detected roll is available.
|
|
67
|
+
*/
|
|
68
|
+
export function defaultMediaForEngine(engine: Pick<PrintEngine, 'protocol'>): BrotherQLMedia {
|
|
69
|
+
return engine.protocol === 'pt-raster' ? DEFAULT_PT_MEDIA : DEFAULT_MEDIA;
|
|
70
|
+
}
|
|
71
|
+
|
|
275
72
|
export function findMedia(id: number): BrotherQLMedia | undefined {
|
|
276
73
|
return MEDIA[id];
|
|
277
74
|
}
|
|
@@ -289,21 +86,47 @@ export function findMediaByWidth(widthMm: number, type: MediaType): BrotherQLMed
|
|
|
289
86
|
* printer is configured for two-colour media. When
|
|
290
87
|
* both DK-22205 (259) and DK-22251 (251) match the
|
|
291
88
|
* dimensions, the flag picks the right one.
|
|
89
|
+
* @param engine optional engine descriptor used to gate the
|
|
90
|
+
* lookup by tape-system and head-family. When
|
|
91
|
+
* omitted, the search is restricted to DK media —
|
|
92
|
+
* legacy QL behaviour. PT-* callers pass the
|
|
93
|
+
* primary engine so the search returns TZe / HSe
|
|
94
|
+
* entries with the right `narrow` / `wide`
|
|
95
|
+
* geometry, and so a 128-dot head never resolves
|
|
96
|
+
* a 36 mm TZe / 31 mm HSe-3:1 width and
|
|
97
|
+
* PT-P910BT (TZe-only) never resolves an HSe
|
|
98
|
+
* entry.
|
|
292
99
|
*/
|
|
293
100
|
export function findMediaByDimensions(
|
|
294
101
|
widthMm: number,
|
|
295
102
|
heightMm: number,
|
|
296
103
|
twoColorMode = false,
|
|
104
|
+
engine?: Pick<PrintEngine, 'headDots' | 'mediaCompatibility'>,
|
|
297
105
|
): BrotherQLMedia | undefined {
|
|
106
|
+
// When no engine is supplied we preserve the original DK-only
|
|
107
|
+
// behaviour so legacy callers don't suddenly resolve TZe or HSe
|
|
108
|
+
// entries on width-only ambiguity (e.g. 12 mm DK-22214 vs 12 mm TZe).
|
|
109
|
+
const tapeSystems: readonly string[] | undefined =
|
|
110
|
+
engine?.mediaCompatibility ?? (engine ? undefined : ['dk']);
|
|
111
|
+
const allowed = (m: BrotherQLMedia): boolean => {
|
|
112
|
+
if (tapeSystems && !tapeSystems.includes(m.tapeSystem)) return false;
|
|
113
|
+
if (engine && m.tapeSystem !== 'dk') {
|
|
114
|
+
// TZe / HSe — require the head-family geometry to exist.
|
|
115
|
+
const family = engine.headDots === 128 ? 'narrow' : 'wide';
|
|
116
|
+
if (!m.geometry?.[family]) return false;
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
};
|
|
120
|
+
|
|
298
121
|
if (heightMm === 0) {
|
|
299
122
|
const continuousMatches = Object.values(MEDIA).filter(
|
|
300
|
-
m => m.type === 'continuous' && m.widthMm === widthMm,
|
|
123
|
+
m => m.type === 'continuous' && m.widthMm === widthMm && allowed(m),
|
|
301
124
|
);
|
|
302
125
|
if (continuousMatches.length === 0) return undefined;
|
|
303
|
-
const preferred = continuousMatches.find(m => m.
|
|
126
|
+
const preferred = continuousMatches.find(m => (m.palette !== undefined) === twoColorMode);
|
|
304
127
|
return preferred ?? continuousMatches[0];
|
|
305
128
|
}
|
|
306
129
|
return Object.values(MEDIA).find(
|
|
307
|
-
m => m.type === 'die-cut' && m.widthMm === widthMm && m.heightMm === heightMm,
|
|
130
|
+
m => m.type === 'die-cut' && m.widthMm === widthMm && m.heightMm === heightMm && allowed(m),
|
|
308
131
|
);
|
|
309
132
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RotateDirection } from '@thermal-label/contracts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Direction the Brother QL print head rotates landscape input.
|
|
5
|
+
*
|
|
6
|
+
* `90` = clockwise. Verified once on hardware with a die-cut "F"
|
|
7
|
+
* landscape print (see plan §6 step 1). Identical across every QL
|
|
8
|
+
* model — this is a print-head/leading-edge mechanical fact, not a
|
|
9
|
+
* per-media setting.
|
|
10
|
+
*/
|
|
11
|
+
export const ROTATE_DIRECTION: RotateDirection = 90;
|
package/src/pack-bits.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion --
|
|
2
|
+
* `noUncheckedIndexedAccess` types Uint8Array reads as `number | undefined`
|
|
3
|
+
* but every index in this file is bounded by `i < input.length` or
|
|
4
|
+
* `runEnd < input.length` checks. The `!` assertions collapse the
|
|
5
|
+
* unreachable `undefined` branches so branch coverage doesn't count them.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* TIFF-style PackBits row encoder used by Brother QL when compression
|
|
10
|
+
* mode (`M 02`, emitted by `buildCompression(true)`) is enabled for a job.
|
|
11
|
+
*
|
|
12
|
+
* Header byte `n` (interpreted as a signed int8):
|
|
13
|
+
* n in [0, 127]: literal run — the next `n + 1` bytes follow verbatim.
|
|
14
|
+
* n in [-127, -1]: repeat run — the next byte is repeated `1 - n` times.
|
|
15
|
+
* n = -128: no-op. Unused by this encoder.
|
|
16
|
+
*
|
|
17
|
+
* The encoder switches to repeat mode for runs of two or more identical
|
|
18
|
+
* bytes (a 2-byte repeat costs 2 wire bytes; the equivalent 2-byte literal
|
|
19
|
+
* would cost 3). Both run kinds are capped at 128 bytes to fit the header.
|
|
20
|
+
*
|
|
21
|
+
* For a typical Brother QL raster row (90 bytes, mostly zeros in margins
|
|
22
|
+
* and long runs of `0x00` / `0xff` in print area), this compresses to 5–15
|
|
23
|
+
* bytes — a 6–18× reduction. Worst-case for highly random input is one
|
|
24
|
+
* extra byte per 128 (the literal-mode header), i.e. < 1 % expansion.
|
|
25
|
+
*/
|
|
26
|
+
export function packBits(input: Uint8Array): Uint8Array {
|
|
27
|
+
if (input.length === 0) return new Uint8Array(0);
|
|
28
|
+
|
|
29
|
+
const out: number[] = [];
|
|
30
|
+
let i = 0;
|
|
31
|
+
|
|
32
|
+
while (i < input.length) {
|
|
33
|
+
// How many identical bytes start at position i (cap at 128).
|
|
34
|
+
let runEnd = i + 1;
|
|
35
|
+
while (runEnd < input.length && runEnd - i < 128 && input[runEnd] === input[i]) {
|
|
36
|
+
runEnd++;
|
|
37
|
+
}
|
|
38
|
+
const runLen = runEnd - i;
|
|
39
|
+
|
|
40
|
+
if (runLen >= 2) {
|
|
41
|
+
// Repeat run: header is signed -(runLen - 1).
|
|
42
|
+
out.push((1 - runLen) & 0xff);
|
|
43
|
+
out.push(input[i]!);
|
|
44
|
+
i = runEnd;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Otherwise emit a literal run. Stop at 128 bytes, or right before a
|
|
49
|
+
// 2+ repeat run starts (cheaper to encode that separately).
|
|
50
|
+
let litEnd = i + 1;
|
|
51
|
+
while (litEnd < input.length && litEnd - i < 128) {
|
|
52
|
+
if (litEnd + 1 < input.length && input[litEnd] === input[litEnd + 1]) {
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
litEnd++;
|
|
56
|
+
}
|
|
57
|
+
const litLen = litEnd - i;
|
|
58
|
+
out.push(litLen - 1);
|
|
59
|
+
for (let x = i; x < litEnd; x++) out.push(input[x]!);
|
|
60
|
+
i = litEnd;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return new Uint8Array(out);
|
|
64
|
+
}
|
package/src/preview.ts
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
import { renderImage, renderMultiPlaneImage } from '@mbtech-nl/bitmap';
|
|
2
|
+
import type { LabelBitmap, RawImageData } from '@mbtech-nl/bitmap';
|
|
3
3
|
import type { PreviewResult } from '@thermal-label/contracts';
|
|
4
|
-
import {
|
|
5
|
-
import { type BrotherQLMedia } from './types.js';
|
|
4
|
+
import type { BrotherQLMedia } from './types.js';
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Offline preview without a live printer connection.
|
|
9
8
|
*
|
|
10
|
-
*
|
|
11
|
-
* image is split
|
|
12
|
-
*
|
|
13
|
-
* returns one black plane
|
|
9
|
+
* Multi-ink aware: when `media.palette` is defined (DK-22251 today),
|
|
10
|
+
* the image is split per-plane via `renderMultiPlaneImage()` — the
|
|
11
|
+
* same code path `print()` takes for that media. Single-ink media
|
|
12
|
+
* returns one black plane via dithered `renderImage`.
|
|
14
13
|
*/
|
|
15
14
|
export function createPreviewOffline(image: RawImageData, media: BrotherQLMedia): PreviewResult {
|
|
16
|
-
if (media.
|
|
17
|
-
const
|
|
15
|
+
if (media.palette) {
|
|
16
|
+
const planes = renderMultiPlaneImage(image, {
|
|
17
|
+
palette: media.palette,
|
|
18
|
+
}) as Record<'black' | 'red', LabelBitmap>;
|
|
18
19
|
return {
|
|
19
20
|
planes: [
|
|
20
|
-
{ name: 'black', bitmap: black, displayColor: '#000000' },
|
|
21
|
-
{ name: 'red', bitmap: red, displayColor: '#ff0000' },
|
|
21
|
+
{ name: 'black', bitmap: planes.black, displayColor: '#000000' },
|
|
22
|
+
{ name: 'red', bitmap: planes.red, displayColor: '#ff0000' },
|
|
22
23
|
],
|
|
23
24
|
media,
|
|
24
25
|
assumed: false,
|