jc-printer-sdk-ts 0.1.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/src/builder.ts ADDED
@@ -0,0 +1,562 @@
1
+ import { ItemAlignment, PenAlignment } from "./constants.js";
2
+ import type {
3
+ BitmapLike,
4
+ CanvasCommand,
5
+ CommitPayload,
6
+ IAtBitmapDocument,
7
+ LabelBarcodeOptions,
8
+ LabelGraphOptions,
9
+ LabelImageOptions,
10
+ LabelLineOptions,
11
+ LabelQrCodeOptions,
12
+ LabelTextOptions,
13
+ PageOptions,
14
+ Point,
15
+ PrintDocument,
16
+ PrintJobOptions,
17
+ PrintPage,
18
+ } from "./types.js";
19
+
20
+ function clone<T>(value: T): T {
21
+ if (typeof globalThis.structuredClone === "function") {
22
+ return globalThis.structuredClone(value);
23
+ }
24
+ return JSON.parse(JSON.stringify(value)) as T;
25
+ }
26
+
27
+ function normalizeFonts(fonts: string | string[] | undefined): string[] {
28
+ if (Array.isArray(fonts)) {
29
+ return fonts.filter(Boolean);
30
+ }
31
+ if (typeof fonts === "string" && fonts.trim()) {
32
+ return [fonts];
33
+ }
34
+ return [];
35
+ }
36
+
37
+ function toCommand(kind: string, payload: Record<string, unknown>): CanvasCommand {
38
+ return { kind, payload };
39
+ }
40
+
41
+ function safeNumber(value: number | undefined, fallback: number): number {
42
+ return Number.isFinite(value as number) ? (value as number) : fallback;
43
+ }
44
+
45
+ function buildInfoPage(page: PrintPage, job: PrintJobOptions): string {
46
+ return JSON.stringify({
47
+ printerImageProcessingInfo: {
48
+ orientation: page.orientation,
49
+ margin: [0, 0, 0, 0],
50
+ printQuantity: page.printQuantity || job.totalQuantity || 1,
51
+ horizontalOffset: 0,
52
+ verticalOffset: 0,
53
+ width: page.width,
54
+ height: page.height,
55
+ printMultiple: job.printMultiple ?? 1,
56
+ epc: job.epc ?? "",
57
+ },
58
+ });
59
+ }
60
+
61
+ function bitmapFromPayload(
62
+ description: string,
63
+ width: number,
64
+ height: number,
65
+ payload: Record<string, unknown>,
66
+ ): BitmapLike {
67
+ return {
68
+ width,
69
+ height,
70
+ description,
71
+ meta: payload,
72
+ };
73
+ }
74
+
75
+ function dimensionsFromInfo(info: string): Pick<BitmapLike, "width" | "height"> {
76
+ try {
77
+ const parsed = JSON.parse(info) as {
78
+ printerImageProcessingInfo?: { width?: unknown; height?: unknown };
79
+ };
80
+ const imageInfo = parsed.printerImageProcessingInfo;
81
+ const width = Number(imageInfo?.width ?? 0);
82
+ const height = Number(imageInfo?.height ?? 0);
83
+ return {
84
+ width: Number.isFinite(width) ? width : 0,
85
+ height: Number.isFinite(height) ? height : 0,
86
+ };
87
+ } catch {
88
+ return { width: 0, height: 0 };
89
+ }
90
+ }
91
+
92
+ export class LabelBuilder {
93
+ readonly unit: "mm" | "px";
94
+
95
+ private job: PrintJobOptions = {};
96
+ private pages: PrintPage[] = [];
97
+ private currentPage: PrintPage | null = null;
98
+ private pageDefaults: PageOptions | null = null;
99
+ private itemOrientation = 0;
100
+ private itemHorizontalAlignment: number = ItemAlignment.LEFT;
101
+ private itemVerticalAlignment: number = ItemAlignment.TOP;
102
+ private itemPenAlignment: number = PenAlignment.CENTER;
103
+
104
+ constructor(unit: "mm" | "px" = "mm") {
105
+ this.unit = unit;
106
+ }
107
+
108
+ reset(): void {
109
+ this.job = {};
110
+ this.pages = [];
111
+ this.currentPage = null;
112
+ this.pageDefaults = null;
113
+ this.itemOrientation = 0;
114
+ this.itemHorizontalAlignment = ItemAlignment.LEFT;
115
+ this.itemVerticalAlignment = ItemAlignment.TOP;
116
+ this.itemPenAlignment = PenAlignment.CENTER;
117
+ }
118
+
119
+ startJob(density: number, paperType: number, printMode: number): void {
120
+ this.pages = [];
121
+ this.currentPage = null;
122
+ this.pageDefaults = null;
123
+ this.job = {
124
+ ...this.job,
125
+ density,
126
+ paperType,
127
+ printMode,
128
+ pageUnit: this.unit,
129
+ };
130
+ }
131
+
132
+ abortJob(): void {
133
+ this.reset();
134
+ }
135
+
136
+ startPage(): void {
137
+ this.closePage();
138
+ this.openPage(this.pageDefaults ?? {
139
+ width: 0,
140
+ height: 0,
141
+ orientation: 0,
142
+ fonts: [],
143
+ background: 0xffffff,
144
+ printQuantity: this.job.totalQuantity ?? 1,
145
+ drawParams: {},
146
+ });
147
+ }
148
+
149
+ endPage(): void {
150
+ this.closePage();
151
+ }
152
+
153
+ endJob(): void {
154
+ this.closePage();
155
+ }
156
+
157
+ drawEmptyLabel(
158
+ width: number,
159
+ height: number,
160
+ orientation: number,
161
+ fonts: string | string[],
162
+ ): void {
163
+ this.closePage();
164
+ this.openPage({
165
+ width,
166
+ height,
167
+ orientation,
168
+ fonts: normalizeFonts(fonts),
169
+ background: 0xffffff,
170
+ printQuantity: this.job.totalQuantity ?? 1,
171
+ drawParams: {},
172
+ });
173
+ this.pageDefaults = {
174
+ width,
175
+ height,
176
+ orientation,
177
+ fonts: normalizeFonts(fonts),
178
+ background: 0xffffff,
179
+ printQuantity: this.job.totalQuantity ?? 1,
180
+ drawParams: {},
181
+ };
182
+ }
183
+
184
+ drawLabelText(
185
+ x: number,
186
+ y: number,
187
+ width: number,
188
+ height: number,
189
+ text: string,
190
+ fontName: string,
191
+ fontSize: number,
192
+ options: LabelTextOptions = {},
193
+ ): void {
194
+ this.addCommand("drawLabelText", {
195
+ x,
196
+ y,
197
+ width,
198
+ height,
199
+ text,
200
+ fontName,
201
+ fontSize,
202
+ rotation: options.rotation ?? 0,
203
+ horizontalAlignment: options.horizontalAlignment ?? ItemAlignment.LEFT,
204
+ verticalAlignment: options.verticalAlignment ?? ItemAlignment.TOP,
205
+ mode: options.mode ?? 0,
206
+ indent: options.indent ?? 0,
207
+ scale: options.scale ?? 1,
208
+ flags: clone(options.flags ?? []),
209
+ unit: this.unit,
210
+ });
211
+ }
212
+
213
+ drawLabelBarCode(
214
+ x: number,
215
+ y: number,
216
+ width: number,
217
+ height: number,
218
+ codeType: number,
219
+ text: string,
220
+ options: LabelBarcodeOptions = {},
221
+ ): void {
222
+ this.addCommand("drawLabelBarCode", {
223
+ x,
224
+ y,
225
+ width,
226
+ height,
227
+ codeType,
228
+ text,
229
+ rotation: options.rotation ?? 0,
230
+ ratio: options.ratio ?? 1,
231
+ fontSize: options.fontSize ?? 0,
232
+ fontName: options.fontName ?? "",
233
+ showText: options.showText ?? true,
234
+ threshold: options.threshold ?? 0,
235
+ alignment: options.alignment ?? ItemAlignment.LEFT,
236
+ quietZone: options.quietZone ?? 0,
237
+ unit: this.unit,
238
+ });
239
+ }
240
+
241
+ drawLabelQrCode(
242
+ x: number,
243
+ y: number,
244
+ width: number,
245
+ height: number,
246
+ text: string,
247
+ options: LabelQrCodeOptions = {},
248
+ ): void {
249
+ this.addCommand("drawLabelQrCode", {
250
+ x,
251
+ y,
252
+ width,
253
+ height,
254
+ text,
255
+ rotation: options.rotation ?? 0,
256
+ version: options.version ?? 0,
257
+ errorCorrectionLevel: options.errorCorrectionLevel ?? 0,
258
+ margin: options.margin ?? 0,
259
+ unit: this.unit,
260
+ });
261
+ }
262
+
263
+ drawLabelGraph(
264
+ x: number,
265
+ y: number,
266
+ width: number,
267
+ height: number,
268
+ graphType: number,
269
+ options: LabelGraphOptions = {},
270
+ ): void {
271
+ this.addCommand("drawLabelGraph", {
272
+ x,
273
+ y,
274
+ width,
275
+ height,
276
+ graphType,
277
+ rotation: options.rotation ?? 0,
278
+ lineWidth: options.lineWidth ?? 1,
279
+ penAlignment: options.penAlignment ?? PenAlignment.CENTER,
280
+ dashPattern: clone(options.dashPattern ?? []),
281
+ unit: this.unit,
282
+ });
283
+ }
284
+
285
+ drawLabelImage(
286
+ source: string | BitmapLike,
287
+ x: number,
288
+ y: number,
289
+ width: number,
290
+ height: number,
291
+ options: LabelImageOptions = {},
292
+ ): void {
293
+ this.addCommand("drawLabelImage", {
294
+ source,
295
+ x,
296
+ y,
297
+ width,
298
+ height,
299
+ rotation: options.rotation ?? 0,
300
+ threshold: options.threshold ?? 0,
301
+ scale: options.scale ?? 1,
302
+ actualSize: options.actualSize ?? false,
303
+ unit: this.unit,
304
+ });
305
+ }
306
+
307
+ drawLabelLine(
308
+ x: number,
309
+ y: number,
310
+ width: number,
311
+ height: number,
312
+ options: LabelLineOptions = {},
313
+ ): void {
314
+ this.addCommand("drawLabelLine", {
315
+ x,
316
+ y,
317
+ width,
318
+ height,
319
+ rotation: options.rotation ?? 0,
320
+ lineWidth: options.lineWidth ?? 1,
321
+ penAlignment: options.penAlignment ?? PenAlignment.CENTER,
322
+ dashPattern: clone(options.dashPattern ?? []),
323
+ unit: this.unit,
324
+ });
325
+ }
326
+
327
+ setDrawParam(name: string, value: unknown): void {
328
+ this.ensurePage();
329
+ this.currentPage!.drawParams[name] = clone(value);
330
+ }
331
+
332
+ getItemOrientation(): number {
333
+ return this.itemOrientation;
334
+ }
335
+
336
+ setItemOrientation(value: number): void {
337
+ this.itemOrientation = value;
338
+ }
339
+
340
+ getItemHorizontalAlignment(): number {
341
+ return this.itemHorizontalAlignment;
342
+ }
343
+
344
+ setItemHorizontalAlignment(value: number): void {
345
+ this.itemHorizontalAlignment = value;
346
+ }
347
+
348
+ getItemVerticalAlignment(): number {
349
+ return this.itemVerticalAlignment;
350
+ }
351
+
352
+ setItemVerticalAlignment(value: number): void {
353
+ this.itemVerticalAlignment = value;
354
+ }
355
+
356
+ getItemPenAlignment(): number {
357
+ return this.itemPenAlignment;
358
+ }
359
+
360
+ setItemPenAlignment(value: number): void {
361
+ this.itemPenAlignment = value;
362
+ }
363
+
364
+ setBackground(color: number): void {
365
+ this.ensurePage();
366
+ this.currentPage!.background = color;
367
+ }
368
+
369
+ getPrintMultiple(): number {
370
+ return this.job.printMultiple ?? 1;
371
+ }
372
+
373
+ setTotalPrintQuantity(quantity: number): void {
374
+ this.job.totalQuantity = quantity;
375
+ }
376
+
377
+ setTotalQuantityOfPrints(quantity: number): void {
378
+ this.setTotalPrintQuantity(quantity);
379
+ }
380
+
381
+ getJobPages(): PrintPage[] {
382
+ const pages = this.currentPage
383
+ ? [...this.pages, clone(this.currentPage)]
384
+ : this.pages;
385
+ return clone(pages);
386
+ }
387
+
388
+ getCurrentPage(): PrintPage | null {
389
+ return this.currentPage ? clone(this.currentPage) : null;
390
+ }
391
+
392
+ toDocument(): PrintDocument {
393
+ const pages = this.getJobPages();
394
+ return {
395
+ schema: "jc-printer-sdk-ts/print-document@1",
396
+ unit: this.unit,
397
+ job: clone(this.job),
398
+ pages,
399
+ };
400
+ }
401
+
402
+ serialize(): string {
403
+ return JSON.stringify(this.toDocument());
404
+ }
405
+
406
+ generateLabelJson(): Uint8Array {
407
+ const page = this.currentPage ?? this.pages[this.pages.length - 1];
408
+ const serialized = page
409
+ ? JSON.stringify({
410
+ schema: "jc-printer-sdk-ts/page@1",
411
+ unit: this.unit,
412
+ job: clone(this.job),
413
+ page: clone(page),
414
+ })
415
+ : JSON.stringify({
416
+ schema: "jc-printer-sdk-ts/page@1",
417
+ unit: this.unit,
418
+ job: clone(this.job),
419
+ page: null,
420
+ });
421
+ return new TextEncoder().encode(serialized);
422
+ }
423
+
424
+ toCommitPayload(): CommitPayload {
425
+ const pages = this.getJobPages();
426
+ return {
427
+ jsonPages: pages.map((page) =>
428
+ JSON.stringify({
429
+ schema: "jc-printer-sdk-ts/page@1",
430
+ unit: this.unit,
431
+ job: clone(this.job),
432
+ page,
433
+ }),
434
+ ),
435
+ infoPages: pages.map((page) => buildInfoPage(page, this.job)),
436
+ };
437
+ }
438
+
439
+ commitImageData(
440
+ orientation: number,
441
+ bitmap: BitmapLike,
442
+ width: number,
443
+ height: number,
444
+ printQuantity: number,
445
+ marginLeft: number,
446
+ marginTop: number,
447
+ marginRight: number,
448
+ marginBottom: number,
449
+ rfid: string,
450
+ ): void {
451
+ this.addCommand("commitImageData", {
452
+ orientation,
453
+ bitmap: clone(bitmap),
454
+ width,
455
+ height,
456
+ printQuantity,
457
+ marginLeft,
458
+ marginTop,
459
+ marginRight,
460
+ marginBottom,
461
+ rfid,
462
+ });
463
+ }
464
+
465
+ generatePreviewImage(json: string, info: string): BitmapLike {
466
+ const { width, height } = dimensionsFromInfo(info);
467
+ return bitmapFromPayload("preview", width, height, { json, info, unit: this.unit });
468
+ }
469
+
470
+ getMultiple(): number {
471
+ return this.getPrintMultiple();
472
+ }
473
+
474
+ measureFontHeight(
475
+ text: string,
476
+ x: number,
477
+ y: number,
478
+ width: number,
479
+ height: number,
480
+ rotation: number,
481
+ fontSize: number,
482
+ ): number {
483
+ const lines = text.split(/\r?\n/).length;
484
+ const size = safeNumber(fontSize, 0);
485
+ return Math.max(size, Math.ceil(size * 1.2 * lines));
486
+ }
487
+
488
+ getStrPrintSize(text: string, fontStyle: number, fontSize: number): Point {
489
+ const lines = text.split(/\r?\n/);
490
+ const maxLine = lines.reduce((longest, line) => Math.max(longest, line.length), 0);
491
+ const width = Math.ceil(maxLine * fontSize * 0.6);
492
+ const height = Math.ceil(lines.length * fontSize * 1.2);
493
+ return { x: width, y: height };
494
+ }
495
+
496
+ getMin1DBarcode(text: string, codeType: number): BitmapLike {
497
+ return bitmapFromPayload("min-1d-barcode", 0, 0, { text, codeType });
498
+ }
499
+
500
+ getMin2DQRCode(text: string): BitmapLike {
501
+ return bitmapFromPayload("min-2d-qrcode", 0, 0, { text });
502
+ }
503
+
504
+ addLabelCommand(kind: string, payload: Record<string, unknown>): void {
505
+ this.addCommand(kind, payload);
506
+ }
507
+
508
+ private ensurePage(): void {
509
+ if (!this.currentPage) {
510
+ this.openPage(this.pageDefaults ?? {
511
+ width: 0,
512
+ height: 0,
513
+ orientation: 0,
514
+ fonts: [],
515
+ background: 0xffffff,
516
+ printQuantity: this.job.totalQuantity ?? 1,
517
+ drawParams: {},
518
+ });
519
+ }
520
+ }
521
+
522
+ private openPage(options: PageOptions): void {
523
+ this.currentPage = {
524
+ width: options.width,
525
+ height: options.height,
526
+ orientation: options.orientation,
527
+ fonts: clone(options.fonts ?? []),
528
+ background: options.background ?? 0xffffff,
529
+ printQuantity: options.printQuantity ?? this.job.totalQuantity ?? 1,
530
+ drawParams: clone(options.drawParams ?? {}),
531
+ commands: [],
532
+ };
533
+ }
534
+
535
+ private closePage(): void {
536
+ if (!this.currentPage) {
537
+ return;
538
+ }
539
+ this.pages.push(clone(this.currentPage));
540
+ this.currentPage = null;
541
+ }
542
+
543
+ private addCommand(kind: string, payload: Record<string, unknown>): void {
544
+ this.ensurePage();
545
+ this.currentPage!.commands.push(toCommand(kind, clone(payload)));
546
+ }
547
+ }
548
+
549
+ export function createLabelBuilder(unit: "mm" | "px" = "mm"): LabelBuilder {
550
+ return new LabelBuilder(unit);
551
+ }
552
+
553
+ export function createAtBitmapDocument(
554
+ job: PrintJobOptions = {},
555
+ ): IAtBitmapDocument {
556
+ return {
557
+ schema: "jc-printer-sdk-ts/at-bitmap@1",
558
+ unit: "px",
559
+ job: clone(job),
560
+ pages: [],
561
+ };
562
+ }