@ai-matrx/print 0.3.2 → 0.5.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 (55) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/booklet.cjs +4 -5
  3. package/dist/booklet.cjs.map +1 -1
  4. package/dist/booklet.js +4 -5
  5. package/dist/booklet.js.map +1 -1
  6. package/dist/certificate.cjs +748 -0
  7. package/dist/certificate.cjs.map +1 -0
  8. package/dist/certificate.d.cts +101 -0
  9. package/dist/certificate.d.ts +101 -0
  10. package/dist/certificate.js +725 -0
  11. package/dist/certificate.js.map +1 -0
  12. package/dist/core.cjs +2 -5
  13. package/dist/core.cjs.map +1 -1
  14. package/dist/core.d.cts +1 -2
  15. package/dist/core.d.ts +1 -2
  16. package/dist/core.js +1 -4
  17. package/dist/core.js.map +1 -1
  18. package/dist/education.cjs +927 -0
  19. package/dist/education.cjs.map +1 -0
  20. package/dist/education.d.cts +79 -0
  21. package/dist/education.d.ts +79 -0
  22. package/dist/education.js +904 -0
  23. package/dist/education.js.map +1 -0
  24. package/dist/exam.cjs +647 -0
  25. package/dist/exam.cjs.map +1 -0
  26. package/dist/exam.d.cts +69 -0
  27. package/dist/exam.d.ts +69 -0
  28. package/dist/exam.js +624 -0
  29. package/dist/exam.js.map +1 -0
  30. package/dist/flashcards.cjs +25 -26
  31. package/dist/flashcards.cjs.map +1 -1
  32. package/dist/flashcards.js +24 -25
  33. package/dist/flashcards.js.map +1 -1
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +4 -0
  36. package/dist/index.d.ts +4 -0
  37. package/dist/labels.cjs +14 -13
  38. package/dist/labels.cjs.map +1 -1
  39. package/dist/labels.js +14 -13
  40. package/dist/labels.js.map +1 -1
  41. package/dist/markdown.cjs +6 -5
  42. package/dist/markdown.cjs.map +1 -1
  43. package/dist/markdown.js +6 -5
  44. package/dist/markdown.js.map +1 -1
  45. package/dist/react.cjs +14 -13
  46. package/dist/react.cjs.map +1 -1
  47. package/dist/react.js +14 -13
  48. package/dist/react.js.map +1 -1
  49. package/dist/zpl.cjs +1040 -0
  50. package/dist/zpl.cjs.map +1 -0
  51. package/dist/zpl.d.cts +160 -0
  52. package/dist/zpl.d.ts +160 -0
  53. package/dist/zpl.js +1007 -0
  54. package/dist/zpl.js.map +1 -0
  55. package/package.json +43 -2
package/dist/zpl.cjs ADDED
@@ -0,0 +1,1040 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/zpl.ts
31
+ var zpl_exports = {};
32
+ __export(zpl_exports, {
33
+ DEFAULT_ZPL_DPI: () => DEFAULT_ZPL_DPI,
34
+ ZPL_MAX_MAGNIFICATION: () => ZPL_MAX_MAGNIFICATION,
35
+ ZPL_MIN_MAGNIFICATION: () => ZPL_MIN_MAGNIFICATION,
36
+ ZPL_QUIET_ZONE_MODULES: () => ZPL_QUIET_ZONE_MODULES,
37
+ ZPL_SHEET_STOCK_MESSAGE: () => ZPL_SHEET_STOCK_MESSAGE,
38
+ assertZplScannable: () => assertZplScannable,
39
+ labelToZpl: () => labelToZpl,
40
+ labelsToZpl: () => labelsToZpl,
41
+ zplFieldData: () => zplFieldData
42
+ });
43
+ module.exports = __toCommonJS(zpl_exports);
44
+
45
+ // src/core.ts
46
+ var import_html_escape = require("@ai-matrx/kit/html-escape");
47
+ function openPrintWindow(htmlDocument, filename = "print", windowFeatures = "width=920,height=720,scrollbars=yes") {
48
+ const win = window.open("", "_blank", windowFeatures);
49
+ if (!win) {
50
+ const blob = new Blob([htmlDocument], { type: "text/html;charset=utf-8" });
51
+ const url = URL.createObjectURL(blob);
52
+ const a = document.createElement("a");
53
+ a.href = url;
54
+ a.download = `${filename.replace(/\s+/g, "-").toLowerCase()}.html`;
55
+ document.body.appendChild(a);
56
+ a.click();
57
+ document.body.removeChild(a);
58
+ setTimeout(() => URL.revokeObjectURL(url), 0);
59
+ return "downloaded";
60
+ }
61
+ win.document.open();
62
+ win.document.write(htmlDocument);
63
+ win.document.close();
64
+ return "opened";
65
+ }
66
+
67
+ // src/labels.ts
68
+ var import_html_escape2 = require("@ai-matrx/kit/html-escape");
69
+
70
+ // src/qr.ts
71
+ var QR_MIN_QUIET_ZONE_MODULES = 4;
72
+ var DEFAULT_QR_EC_LEVEL = "M";
73
+ var QR_MIN_VERSION = 1;
74
+ var QR_MAX_VERSION = 40;
75
+ var BYTE_CAPACITY = [
76
+ /* v1 */
77
+ [17, 14, 11, 7],
78
+ /* v2 */
79
+ [32, 26, 20, 14],
80
+ /* v3 */
81
+ [53, 42, 32, 24],
82
+ /* v4 */
83
+ [78, 62, 46, 34],
84
+ /* v5 */
85
+ [106, 84, 60, 44],
86
+ /* v6 */
87
+ [134, 106, 74, 58],
88
+ /* v7 */
89
+ [154, 122, 86, 64],
90
+ /* v8 */
91
+ [192, 152, 108, 84],
92
+ /* v9 */
93
+ [230, 180, 130, 98],
94
+ /* v10 */
95
+ [271, 213, 151, 119],
96
+ /* v11 */
97
+ [321, 251, 177, 137],
98
+ /* v12 */
99
+ [367, 287, 203, 155],
100
+ /* v13 */
101
+ [425, 331, 241, 177],
102
+ /* v14 */
103
+ [458, 362, 258, 194],
104
+ /* v15 */
105
+ [520, 412, 292, 220],
106
+ /* v16 */
107
+ [586, 450, 322, 250],
108
+ /* v17 */
109
+ [644, 504, 364, 280],
110
+ /* v18 */
111
+ [718, 560, 394, 310],
112
+ /* v19 */
113
+ [792, 624, 442, 338],
114
+ /* v20 */
115
+ [858, 666, 482, 382],
116
+ /* v21 */
117
+ [929, 711, 509, 403],
118
+ /* v22 */
119
+ [1003, 779, 565, 439],
120
+ /* v23 */
121
+ [1091, 857, 611, 461],
122
+ /* v24 */
123
+ [1171, 911, 661, 511],
124
+ /* v25 */
125
+ [1273, 997, 715, 535],
126
+ /* v26 */
127
+ [1367, 1059, 751, 593],
128
+ /* v27 */
129
+ [1465, 1125, 805, 625],
130
+ /* v28 */
131
+ [1528, 1190, 868, 658],
132
+ /* v29 */
133
+ [1628, 1264, 908, 698],
134
+ /* v30 */
135
+ [1732, 1370, 982, 742],
136
+ /* v31 */
137
+ [1840, 1452, 1030, 790],
138
+ /* v32 */
139
+ [1952, 1538, 1112, 842],
140
+ /* v33 */
141
+ [2068, 1628, 1168, 898],
142
+ /* v34 */
143
+ [2188, 1722, 1228, 958],
144
+ /* v35 */
145
+ [2303, 1809, 1283, 983],
146
+ /* v36 */
147
+ [2431, 1911, 1351, 1051],
148
+ /* v37 */
149
+ [2563, 1989, 1423, 1093],
150
+ /* v38 */
151
+ [2699, 2099, 1499, 1139],
152
+ /* v39 */
153
+ [2809, 2213, 1579, 1219],
154
+ /* v40 */
155
+ [2953, 2331, 1663, 1273]
156
+ ];
157
+ var EC_COLUMN = { L: 0, M: 1, Q: 2, H: 3 };
158
+ function assertEcLevel(ecLevel) {
159
+ const column = EC_COLUMN[ecLevel];
160
+ if (column === void 0) {
161
+ throw new Error(`Unknown QR error correction level "${String(ecLevel)}". Expected one of "L", "M", "Q", "H".`);
162
+ }
163
+ return column;
164
+ }
165
+ function assertVersion(version) {
166
+ if (typeof version !== "number" || !Number.isInteger(version) || version < QR_MIN_VERSION || version > QR_MAX_VERSION) {
167
+ throw new Error(
168
+ `QR version must be an integer between ${QR_MIN_VERSION} and ${QR_MAX_VERSION}, got ${String(version)}.`
169
+ );
170
+ }
171
+ return version;
172
+ }
173
+ function qrByteCapacity(version, ecLevel) {
174
+ const column = assertEcLevel(ecLevel);
175
+ const row = BYTE_CAPACITY[assertVersion(version) - 1];
176
+ if (row === void 0) {
177
+ throw new Error(`No QR capacity row for version ${version}.`);
178
+ }
179
+ return row[column];
180
+ }
181
+ function qrModuleCount(version) {
182
+ return 17 + 4 * assertVersion(version);
183
+ }
184
+ function minQrVersion(payloadByteLen, ecLevel) {
185
+ const column = assertEcLevel(ecLevel);
186
+ if (typeof payloadByteLen !== "number" || !Number.isInteger(payloadByteLen) || payloadByteLen < 0) {
187
+ throw new Error(`QR payload byte length must be a non-negative integer, got ${String(payloadByteLen)}.`);
188
+ }
189
+ for (let version = QR_MIN_VERSION; version <= QR_MAX_VERSION; version += 1) {
190
+ const row = BYTE_CAPACITY[version - 1];
191
+ if (row !== void 0 && row[column] >= payloadByteLen) {
192
+ return version;
193
+ }
194
+ }
195
+ const max = qrByteCapacity(QR_MAX_VERSION, ecLevel);
196
+ throw new Error(
197
+ `QR payload of ${payloadByteLen} bytes does not fit any QR version at error correction level "${ecLevel}" (version ${QR_MAX_VERSION} holds ${max} bytes). Shorten the payload \u2014 a short resolver URL is the intended fix \u2014 or, only for a large clean code, drop to a lower error correction level.`
198
+ );
199
+ }
200
+ function resolveEcLevel(ecLevel) {
201
+ if (ecLevel === void 0) return DEFAULT_QR_EC_LEVEL;
202
+ assertEcLevel(ecLevel);
203
+ return ecLevel;
204
+ }
205
+ function resolveQuietZone(quietZoneModules) {
206
+ if (typeof quietZoneModules !== "number" || !Number.isFinite(quietZoneModules)) {
207
+ return QR_MIN_QUIET_ZONE_MODULES;
208
+ }
209
+ return Math.max(QR_MIN_QUIET_ZONE_MODULES, Math.floor(quietZoneModules));
210
+ }
211
+ function resolveWidthPx(widthPx) {
212
+ if (widthPx === void 0) return void 0;
213
+ if (typeof widthPx !== "number" || !Number.isFinite(widthPx) || widthPx <= 0) {
214
+ throw new Error(`QR widthPx must be a positive number, got ${String(widthPx)}.`);
215
+ }
216
+ return Math.round(widthPx);
217
+ }
218
+ function assertValue(value) {
219
+ if (typeof value !== "string" || value.length === 0) {
220
+ throw new Error("QR value must be a non-empty string.");
221
+ }
222
+ return value;
223
+ }
224
+ async function generateQrDataUrl(value, opts) {
225
+ assertValue(value);
226
+ const width = resolveWidthPx(opts?.widthPx);
227
+ const options = {
228
+ errorCorrectionLevel: resolveEcLevel(opts?.ecLevel),
229
+ margin: resolveQuietZone(opts?.quietZoneModules)
230
+ };
231
+ if (width !== void 0) options.width = width;
232
+ const QRCode = await import("qrcode");
233
+ return QRCode.toDataURL(value, options);
234
+ }
235
+
236
+ // src/labels.ts
237
+ var AVERY_5160 = {
238
+ id: "avery-5160",
239
+ name: 'Address 1" \xD7 2\u215D" \u2014 30 per sheet',
240
+ stockCode: "Avery 5160 / 8160",
241
+ sheetWIn: 8.5,
242
+ sheetHIn: 11,
243
+ marginTopIn: 0.5,
244
+ marginLeftIn: 0.1875,
245
+ labelWIn: 2.625,
246
+ labelHIn: 1,
247
+ cols: 3,
248
+ rows: 10,
249
+ gutterXIn: 0.125,
250
+ gutterYIn: 0,
251
+ cornerRadiusIn: 0.1
252
+ };
253
+ var LABEL_TEMPLATES = [
254
+ AVERY_5160,
255
+ {
256
+ id: "avery-5163",
257
+ name: 'Shipping 2" \xD7 4" \u2014 10 per sheet',
258
+ stockCode: "Avery 5163 / 8163",
259
+ sheetWIn: 8.5,
260
+ sheetHIn: 11,
261
+ marginTopIn: 0.5,
262
+ marginLeftIn: 0.15625,
263
+ labelWIn: 4,
264
+ labelHIn: 2,
265
+ cols: 2,
266
+ rows: 5,
267
+ gutterXIn: 0.1875,
268
+ gutterYIn: 0,
269
+ cornerRadiusIn: 0.125
270
+ },
271
+ {
272
+ id: "avery-5164",
273
+ name: 'Shipping 3\u2153" \xD7 4" \u2014 6 per sheet',
274
+ stockCode: "Avery 5164 / 8164",
275
+ sheetWIn: 8.5,
276
+ sheetHIn: 11,
277
+ marginTopIn: 0.5,
278
+ marginLeftIn: 0.15625,
279
+ labelWIn: 4,
280
+ labelHIn: 3.3333333,
281
+ cols: 2,
282
+ rows: 3,
283
+ gutterXIn: 0.1875,
284
+ gutterYIn: 0,
285
+ cornerRadiusIn: 0.125
286
+ },
287
+ {
288
+ id: "avery-22806",
289
+ name: 'Square 2" \xD7 2" \u2014 12 per sheet',
290
+ stockCode: "Avery 22806",
291
+ sheetWIn: 8.5,
292
+ sheetHIn: 11,
293
+ marginTopIn: 0.75,
294
+ marginLeftIn: 0.5,
295
+ labelWIn: 2,
296
+ labelHIn: 2,
297
+ cols: 3,
298
+ rows: 4,
299
+ gutterXIn: 0.75,
300
+ gutterYIn: 0.5,
301
+ cornerRadiusIn: 0.09375
302
+ },
303
+ {
304
+ id: "avery-22807",
305
+ name: 'Round 2" \u2014 12 per sheet',
306
+ stockCode: "Avery 22807",
307
+ sheetWIn: 8.5,
308
+ sheetHIn: 11,
309
+ marginTopIn: 0.75,
310
+ marginLeftIn: 0.5,
311
+ labelWIn: 2,
312
+ labelHIn: 2,
313
+ cols: 3,
314
+ rows: 4,
315
+ gutterXIn: 0.75,
316
+ gutterYIn: 0.5,
317
+ cornerRadiusIn: 1,
318
+ round: true
319
+ },
320
+ {
321
+ id: "brother-dk-1202",
322
+ name: 'Shipping roll 2.4" \xD7 3.9" \u2014 die-cut',
323
+ stockCode: "Brother DK-1202",
324
+ sheetWIn: 2.4,
325
+ sheetHIn: 3.9,
326
+ marginTopIn: 0,
327
+ marginLeftIn: 0,
328
+ labelWIn: 2.4,
329
+ labelHIn: 3.9,
330
+ cols: 1,
331
+ rows: 1,
332
+ gutterXIn: 0,
333
+ gutterYIn: 0,
334
+ kind: "roll"
335
+ },
336
+ {
337
+ id: "brother-dk-11201",
338
+ name: 'Address roll 1.14" \xD7 3.5" \u2014 die-cut',
339
+ stockCode: "Brother DK-11201",
340
+ sheetWIn: 1.14,
341
+ sheetHIn: 3.5,
342
+ marginTopIn: 0,
343
+ marginLeftIn: 0,
344
+ labelWIn: 1.14,
345
+ labelHIn: 3.5,
346
+ cols: 1,
347
+ rows: 1,
348
+ gutterXIn: 0,
349
+ gutterYIn: 0,
350
+ kind: "roll"
351
+ },
352
+ {
353
+ id: "dymo-30252",
354
+ name: 'Address roll 1\u215B" \xD7 3\xBD"',
355
+ stockCode: "DYMO 30252",
356
+ sheetWIn: 1.125,
357
+ sheetHIn: 3.5,
358
+ marginTopIn: 0,
359
+ marginLeftIn: 0,
360
+ labelWIn: 1.125,
361
+ labelHIn: 3.5,
362
+ cols: 1,
363
+ rows: 1,
364
+ gutterXIn: 0,
365
+ gutterYIn: 0,
366
+ kind: "roll"
367
+ },
368
+ {
369
+ id: "roll-4x6",
370
+ name: 'Shipping roll 4" \xD7 6"',
371
+ stockCode: "Thermal 4x6",
372
+ sheetWIn: 4,
373
+ sheetHIn: 6,
374
+ marginTopIn: 0,
375
+ marginLeftIn: 0,
376
+ labelWIn: 4,
377
+ labelHIn: 6,
378
+ cols: 1,
379
+ rows: 1,
380
+ gutterXIn: 0,
381
+ gutterYIn: 0,
382
+ kind: "roll"
383
+ }
384
+ ];
385
+ function getLabelTemplate(id) {
386
+ return LABEL_TEMPLATES.find((t) => t.id === id);
387
+ }
388
+ function assertTemplateGeometry(t) {
389
+ const w = t.marginLeftIn * 2 + t.cols * t.labelWIn + (t.cols - 1) * t.gutterXIn;
390
+ const h = t.marginTopIn * 2 + t.rows * t.labelHIn + (t.rows - 1) * t.gutterYIn;
391
+ const tol = 1 / 64;
392
+ if (Math.abs(w - t.sheetWIn) > tol || Math.abs(h - t.sheetHIn) > tol) {
393
+ console.error(
394
+ `[label-print] Template ${t.id} does not tile its sheet: grid ${w.toFixed(4)}\xD7${h.toFixed(4)}in vs sheet ${t.sheetWIn}\xD7${t.sheetHIn}in`
395
+ );
396
+ }
397
+ }
398
+ if (typeof process === "undefined" || true) {
399
+ LABEL_TEMPLATES.forEach(assertTemplateGeometry);
400
+ }
401
+ var DEFAULTS = {
402
+ startAtLabel: 1,
403
+ copiesPerCode: 1,
404
+ showCaption: true,
405
+ showLines: true,
406
+ rangeFrom: 0,
407
+ rangeTo: 0,
408
+ ecLevel: "M",
409
+ calibrationPage: false
410
+ };
411
+ function resolveSettings(s) {
412
+ const num = (v, d, min = 0) => typeof v === "number" && !Number.isNaN(v) ? Math.max(min, v) : d;
413
+ const ec = s?.ecLevel;
414
+ return {
415
+ startAtLabel: num(s?.startAtLabel, DEFAULTS.startAtLabel, 1),
416
+ copiesPerCode: num(s?.copiesPerCode, DEFAULTS.copiesPerCode, 1),
417
+ showCaption: s?.showCaption ?? DEFAULTS.showCaption,
418
+ showLines: s?.showLines ?? DEFAULTS.showLines,
419
+ rangeFrom: num(s?.rangeFrom, 0),
420
+ rangeTo: num(s?.rangeTo, 0),
421
+ ecLevel: ec === "L" || ec === "Q" ? ec : "M",
422
+ calibrationPage: s?.calibrationPage ?? false
423
+ };
424
+ }
425
+ var PRINT_DPI = 300;
426
+ var QUIET_ZONE_MODULES = 4;
427
+ async function generateQrDataUri(value, ecLevel, sizeInches) {
428
+ return generateQrDataUrl(value, {
429
+ ecLevel,
430
+ quietZoneModules: QUIET_ZONE_MODULES,
431
+ widthPx: Math.round(sizeInches * PRINT_DPI)
432
+ });
433
+ }
434
+ function computeCellLayout(t, showCaption, hasBadge = false) {
435
+ const padIn = Math.min(0.08, t.labelHIn * 0.06);
436
+ const wide = t.labelWIn / t.labelHIn >= 1.8;
437
+ const badgeZoneIn = hasBadge ? Math.min(0.4, t.labelHIn * 0.22) : 0;
438
+ if (wide) {
439
+ return { wide, qrIn: t.labelHIn - padIn * 2, padIn, badgeZoneIn };
440
+ }
441
+ const inset = t.round ? t.labelWIn * 0.15 : 0;
442
+ const captionZone = showCaption ? Math.min(0.28, t.labelHIn * 0.16) : 0;
443
+ const qrIn = Math.min(
444
+ t.labelWIn - padIn * 2 - inset * 2,
445
+ t.labelHIn - padIn * 2 - inset * 2 - captionZone - badgeZoneIn
446
+ );
447
+ return { wide, qrIn, padIn, badgeZoneIn };
448
+ }
449
+ function sheetStyles(t) {
450
+ const radius = t.round ? `${t.labelWIn / 2}in` : t.cornerRadiusIn ? `${t.cornerRadiusIn}in` : "0";
451
+ return `
452
+ @page { size: ${t.sheetWIn}in ${t.sheetHIn}in; margin: 0; }
453
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
454
+ html, body { width: ${t.sheetWIn}in; background: #fff; color: #000;
455
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; }
456
+
457
+ .lp-page {
458
+ width: ${t.sheetWIn}in; height: ${t.sheetHIn}in;
459
+ position: relative; overflow: hidden;
460
+ page-break-after: always; break-after: page;
461
+ border-bottom: 1px solid #e2e8f0; /* screen-only separator */
462
+ }
463
+ .lp-page:last-child { page-break-after: auto; break-after: auto; border-bottom: none; }
464
+
465
+ .lp-cell {
466
+ position: absolute;
467
+ width: ${t.labelWIn}in; height: ${t.labelHIn}in;
468
+ border-radius: ${radius};
469
+ overflow: hidden;
470
+ -webkit-print-color-adjust: exact; print-color-adjust: exact;
471
+ }
472
+ .lp-qr {
473
+ display: block;
474
+ image-rendering: pixelated; /* no resampling blur on crisp modules */
475
+ }
476
+ .lp-cell-wide { display: flex; align-items: center; }
477
+ .lp-cell-stacked { display: flex; flex-direction: column; align-items: center; justify-content: center; }
478
+
479
+ .lp-text { flex: 1 1 auto; min-width: 0; overflow: hidden; align-self: stretch;
480
+ display: flex; flex-direction: column; justify-content: center; }
481
+ .lp-fit-wrap { overflow: hidden; }
482
+ .lp-caption { font-weight: 700; line-height: 1.15; word-break: break-word; }
483
+ .lp-line { line-height: 1.25; word-break: break-word; }
484
+ .lp-caption-stacked { text-align: center; width: 100%; }
485
+
486
+ /* Badge \u2014 the LARGE token above the caption (garment size, bin letter). */
487
+ .lp-badge { font-weight: 800; line-height: 1.05; word-break: break-word;
488
+ -webkit-print-color-adjust: exact; print-color-adjust: exact; }
489
+ .lp-badge-stacked { text-align: center; width: 100%; }
490
+
491
+ /* Durable http(s) artwork in the lines zone. */
492
+ .lp-img { display: block; width: 100%; object-fit: contain; }
493
+
494
+ /* Calibration */
495
+ .lp-cal-cell { border: 0.5pt dashed #94a3b8; background: transparent; }
496
+ .lp-cal-label { position: absolute; inset: 0; display: flex; align-items: center;
497
+ justify-content: center; font-size: 7pt; color: #94a3b8; text-align: center; padding: 4px; }
498
+ .lp-crop { position: absolute; background: #94a3b8;
499
+ -webkit-print-color-adjust: exact; print-color-adjust: exact; }
500
+
501
+ @media print {
502
+ html, body { height: auto; overflow: visible; }
503
+ .lp-page { border-bottom: none; }
504
+ .screen-only { display: none !important; }
505
+ }
506
+ `;
507
+ }
508
+ function cellPosition(t, index) {
509
+ const col = index % t.cols;
510
+ const row = Math.floor(index / t.cols);
511
+ return {
512
+ x: t.marginLeftIn + col * (t.labelWIn + t.gutterXIn),
513
+ y: t.marginTopIn + row * (t.labelHIn + t.gutterYIn)
514
+ };
515
+ }
516
+ var LP_FIT_SCRIPT = `
517
+ <script>
518
+ (function () {
519
+ function fit(el, box) {
520
+ var size = parseFloat(window.getComputedStyle(el).fontSize);
521
+ var min = 4;
522
+ while (size > min && (el.scrollWidth > box.clientWidth + 1 || el.scrollHeight > box.clientHeight + 1)) {
523
+ size -= 0.5;
524
+ el.style.fontSize = size + "px";
525
+ }
526
+ }
527
+ function run() {
528
+ var els = document.querySelectorAll(".lp-fit-wrap .lp-fit");
529
+ for (var i = 0; i < els.length; i++) fit(els[i], els[i].parentElement);
530
+ }
531
+ if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", run);
532
+ else setTimeout(run, 0);
533
+ })();
534
+ <\/script>`;
535
+ function isHttpUrl(value) {
536
+ return typeof value === "string" && /^https?:\/\//i.test(value.trim());
537
+ }
538
+ function buildLabelCellHtml(label, qrDataUri, t, layout, s) {
539
+ const qr = qrDataUri ? `<img class="lp-qr" src="${qrDataUri}" alt="" style="width:${layout.qrIn}in;height:${layout.qrIn}in;" />` : "";
540
+ const captionText = s.showCaption ? label.caption ?? label.qrValue : "";
541
+ const badgeText = label.badge?.trim() ? label.badge.trim() : "";
542
+ if (layout.wide) {
543
+ const lines = s.showLines && label.lines?.length ? label.lines.filter((l) => l.trim()).map(
544
+ (l) => `<div class="lp-line" style="font-size:8pt;">${(0, import_html_escape2.escapeHtml)(l)}</div>`
545
+ ).join("") : "";
546
+ const caption2 = captionText ? `<div class="lp-fit-wrap" style="max-height:${(t.labelHIn * 0.3).toFixed(3)}in;"><div class="lp-fit lp-caption" style="font-size:12pt;">${(0, import_html_escape2.escapeHtml)(captionText)}</div></div>` : "";
547
+ const badge2 = badgeText ? `<div class="lp-fit-wrap" style="max-height:${layout.badgeZoneIn.toFixed(3)}in;"><div class="lp-fit lp-badge" style="font-size:24pt;">${(0, import_html_escape2.escapeHtml)(badgeText)}</div></div>` : "";
548
+ const image = isHttpUrl(label.imageUrl) ? `<img class="lp-img" src="${(0, import_html_escape2.escapeHtml)(label.imageUrl)}" alt="" style="height:${(t.labelHIn * 0.35).toFixed(3)}in;" />` : "";
549
+ return `<div class="lp-cell-wide" style="height:100%;padding:${layout.padIn}in;gap:${layout.padIn}in;">
550
+ ${qr}
551
+ <div class="lp-text">${badge2}${caption2}${lines}${image}</div>
552
+ </div>`;
553
+ }
554
+ const caption = captionText ? `<div class="lp-fit-wrap lp-caption-stacked" style="height:${Math.min(0.28, t.labelHIn * 0.16).toFixed(3)}in;"><div class="lp-fit lp-caption" style="font-size:9pt;">${(0, import_html_escape2.escapeHtml)(captionText)}</div></div>` : "";
555
+ const badge = badgeText ? `<div class="lp-fit-wrap lp-badge-stacked" style="height:${layout.badgeZoneIn.toFixed(3)}in;"><div class="lp-fit lp-badge" style="font-size:18pt;">${(0, import_html_escape2.escapeHtml)(badgeText)}</div></div>` : "";
556
+ return `<div class="lp-cell-stacked" style="height:100%;padding:${layout.padIn}in;">
557
+ ${qr}
558
+ ${badge}
559
+ ${caption}
560
+ </div>`;
561
+ }
562
+ function renderPages(t, cells) {
563
+ const perPage = t.cols * t.rows;
564
+ const pages = [];
565
+ for (let p = 0; p < Math.max(1, Math.ceil(cells.length / perPage)); p++) {
566
+ const batch = cells.slice(p * perPage, (p + 1) * perPage);
567
+ const cellHtml = batch.map((c, i) => {
568
+ const { x, y } = cellPosition(t, i);
569
+ return `<div class="lp-cell" style="left:${x}in;top:${y}in;">${c.html}</div>`;
570
+ }).join("\n");
571
+ pages.push(`<div class="lp-page">${cellHtml}</div>`);
572
+ }
573
+ return pages.join("\n");
574
+ }
575
+ function openSheetWindow(t, bodyHtml, title, withFitScript, warning) {
576
+ const warningHtml = warning ? `<div class="screen-only lp-warning" style="font-family:sans-serif;font-size:12px;font-weight:600;padding:9px 16px;background:#fef2f2;color:#991b1b;border-bottom:1px solid #fecaca;">${(0, import_html_escape2.escapeHtml)(warning)}</div>` : "";
577
+ const html = `<!DOCTYPE html>
578
+ <html lang="en">
579
+ <head>
580
+ <meta charset="UTF-8">
581
+ <title>${(0, import_html_escape2.escapeHtml)(title)} \u2014 ${(0, import_html_escape2.escapeHtml)(t.stockCode)}</title>
582
+ <style>${sheetStyles(t)}</style>
583
+ </head>
584
+ <body>
585
+ ${warningHtml}
586
+ <div class="screen-only" style="font-family:sans-serif;font-size:12px;padding:10px 16px;background:#f8fafc;border-bottom:1px solid #e2e8f0;display:flex;gap:10px;align-items:center;flex-wrap:wrap;">
587
+ <button onclick="window.print()" style="padding:7px 18px;background:#111;color:#fff;border:none;border-radius:6px;font-size:13px;font-weight:600;cursor:pointer;">Print / Save PDF</button>
588
+ <button onclick="window.close()" style="padding:7px 14px;background:#f1f5f9;border:1px solid #cbd5e1;border-radius:6px;font-size:13px;cursor:pointer;">Close</button>
589
+ <span style="color:#64748b;font-size:11px;">${(0, import_html_escape2.escapeHtml)(t.stockCode)} \xB7 ${(0, import_html_escape2.escapeHtml)(t.name)} \xB7 set printer to <strong>100% scale (no "fit to page"), no margins, Portrait</strong></span>
590
+ </div>
591
+ ${bodyHtml}
592
+ ${withFitScript ? LP_FIT_SCRIPT : ""}
593
+ </body>
594
+ </html>`;
595
+ return openPrintWindow(html, title);
596
+ }
597
+ function expandLabels(labels, s) {
598
+ const from = s.rangeFrom > 0 ? s.rangeFrom : 1;
599
+ const to = s.rangeTo > 0 ? Math.min(s.rangeTo, labels.length) : labels.length;
600
+ const subset = labels.slice(from - 1, to);
601
+ if (s.copiesPerCode <= 1) return subset;
602
+ const out = [];
603
+ for (const l of subset)
604
+ for (let i = 0; i < s.copiesPerCode; i++) out.push(l);
605
+ return out;
606
+ }
607
+ function buildCalibrationBody(t) {
608
+ const perPage = t.cols * t.rows;
609
+ const cells = [];
610
+ for (let i = 0; i < perPage; i++) {
611
+ const { x, y } = cellPosition(t, i);
612
+ cells.push(
613
+ `<div class="lp-cell lp-cal-cell" style="left:${x}in;top:${y}in;"><div class="lp-cal-label">${i + 1}</div></div>`
614
+ );
615
+ }
616
+ const gridR = t.sheetWIn - t.marginLeftIn;
617
+ const gridB = t.sheetHIn - t.marginTopIn;
618
+ const mark = (x, y, h) => `<div class="lp-crop" style="left:${x}in;top:${y}in;width:${h ? 0.25 : 0.0104}in;height:${h ? 0.0104 : 0.25}in;"></div>`;
619
+ const marks = [
620
+ mark(t.marginLeftIn - 0.25, t.marginTopIn, true),
621
+ mark(t.marginLeftIn, t.marginTopIn - 0.25, false),
622
+ mark(gridR, t.marginTopIn, true),
623
+ mark(gridR - 0.0104, t.marginTopIn - 0.25, false),
624
+ mark(t.marginLeftIn - 0.25, gridB - 0.0104, true),
625
+ mark(t.marginLeftIn, gridB, false),
626
+ mark(gridR, gridB - 0.0104, true),
627
+ mark(gridR - 0.0104, gridB, false)
628
+ ].join("");
629
+ const banner = `<div style="position:absolute;left:0;right:0;top:${Math.max(0, t.marginTopIn - 0.3).toFixed(2)}in;text-align:center;font-size:7pt;color:#94a3b8;">Calibration \u2014 ${(0, import_html_escape2.escapeHtml)(t.stockCode)} \xB7 hold against your label sheet: every outline must sit on a label</div>`;
630
+ return `<div class="lp-page">${banner}${marks}${cells.join("\n")}</div>`;
631
+ }
632
+ function printCalibrationSheet(template) {
633
+ return openSheetWindow(
634
+ template,
635
+ buildCalibrationBody(template),
636
+ "Label calibration",
637
+ false
638
+ );
639
+ }
640
+ var MM_PER_IN = 25.4;
641
+ var MIN_SCANNABLE_MODULE_MM = 0.4;
642
+ function assertScannable(template, payloadByteLen, ecLevel = DEFAULTS.ecLevel) {
643
+ const layout = computeCellLayout(template, true);
644
+ const printedSideMm = layout.qrIn * MM_PER_IN;
645
+ let version;
646
+ try {
647
+ version = minQrVersion(payloadByteLen, ecLevel);
648
+ } catch (err) {
649
+ return {
650
+ ok: false,
651
+ version: 0,
652
+ moduleSizeMm: 0,
653
+ minModuleSizeMm: MIN_SCANNABLE_MODULE_MM,
654
+ message: err instanceof Error ? err.message : String(err)
655
+ };
656
+ }
657
+ const modulesPerSide = qrModuleCount(version) + QUIET_ZONE_MODULES * 2;
658
+ const moduleSizeMm = printedSideMm / modulesPerSide;
659
+ if (moduleSizeMm >= MIN_SCANNABLE_MODULE_MM) {
660
+ return {
661
+ ok: true,
662
+ version,
663
+ moduleSizeMm,
664
+ minModuleSizeMm: MIN_SCANNABLE_MODULE_MM
665
+ };
666
+ }
667
+ return {
668
+ ok: false,
669
+ version,
670
+ moduleSizeMm,
671
+ minModuleSizeMm: MIN_SCANNABLE_MODULE_MM,
672
+ message: `A ${payloadByteLen}-byte payload needs a version ${version} QR, which prints at ${moduleSizeMm.toFixed(3)}mm per module on ${template.stockCode} \u2014 below the ${MIN_SCANNABLE_MODULE_MM}mm floor a scanner can resolve. Shorten the payload (a short resolver URL is the intended fix) or move to larger label stock.`
673
+ };
674
+ }
675
+ function batchScannabilityWarning(t, labels, ecLevel) {
676
+ const encoder = new TextEncoder();
677
+ let worst = -1;
678
+ for (const l of labels) {
679
+ if (!l.qrValue) continue;
680
+ const len = encoder.encode(l.qrValue).length;
681
+ if (len > worst) worst = len;
682
+ }
683
+ if (worst < 0) return void 0;
684
+ const report = assertScannable(t, worst, ecLevel);
685
+ return report.ok ? void 0 : report.message;
686
+ }
687
+ function resolveTemplate(data, variantId) {
688
+ if (variantId && variantId !== "custom") {
689
+ const t = getLabelTemplate(variantId);
690
+ if (t) return t;
691
+ }
692
+ if (data.templateId === "custom" && data.customTemplate)
693
+ return data.customTemplate;
694
+ return getLabelTemplate(data.templateId) ?? data.customTemplate ?? AVERY_5160;
695
+ }
696
+ async function printQrLabelSheet(data, variantId, rawSettings) {
697
+ const t = resolveTemplate(data, variantId);
698
+ const s = resolveSettings(rawSettings);
699
+ if (s.calibrationPage) {
700
+ return printCalibrationSheet(t);
701
+ }
702
+ const labels = expandLabels(data.labels ?? [], s);
703
+ if (!labels.length) {
704
+ return openSheetWindow(
705
+ t,
706
+ `<div class="lp-page"><div style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#94a3b8;font-size:11pt;">No labels to print.</div></div>`,
707
+ "QR labels",
708
+ false
709
+ );
710
+ }
711
+ const hasBadge = labels.some((l) => (l.badge ?? "").trim() !== "");
712
+ const layout = computeCellLayout(t, s.showCaption, hasBadge);
713
+ const uriByValue = /* @__PURE__ */ new Map();
714
+ for (const l of labels) {
715
+ if (l.qrValue && !uriByValue.has(l.qrValue)) {
716
+ uriByValue.set(
717
+ l.qrValue,
718
+ await generateQrDataUri(l.qrValue, s.ecLevel, layout.qrIn)
719
+ );
720
+ }
721
+ }
722
+ const leadingBlanks = Math.max(0, s.startAtLabel - 1) % (t.cols * t.rows);
723
+ const cells = [
724
+ ...Array.from({ length: leadingBlanks }, () => ({ html: "" })),
725
+ ...labels.map((l) => ({
726
+ html: buildLabelCellHtml(
727
+ l,
728
+ uriByValue.get(l.qrValue) ?? "",
729
+ t,
730
+ layout,
731
+ s
732
+ )
733
+ }))
734
+ ];
735
+ const perPage = t.cols * t.rows;
736
+ const tail = cells.length % perPage;
737
+ if (tail > 0)
738
+ for (let i = 0; i < perPage - tail; i++) cells.push({ html: "" });
739
+ return openSheetWindow(
740
+ t,
741
+ renderPages(t, cells),
742
+ "QR labels",
743
+ true,
744
+ batchScannabilityWarning(t, labels, s.ecLevel)
745
+ );
746
+ }
747
+ var qrLabelsPrinter = {
748
+ label: "Print QR label sheet",
749
+ variants: LABEL_TEMPLATES.map((t) => ({
750
+ id: t.id,
751
+ label: t.name,
752
+ description: t.stockCode
753
+ })),
754
+ settings: [
755
+ {
756
+ type: "number",
757
+ id: "startAtLabel",
758
+ label: "Start at label #",
759
+ description: "Skip already-used positions on a partial sheet",
760
+ defaultValue: 1,
761
+ min: 1
762
+ },
763
+ {
764
+ type: "number",
765
+ id: "copiesPerCode",
766
+ label: "Copies per code",
767
+ defaultValue: 1,
768
+ min: 1,
769
+ max: 999
770
+ },
771
+ {
772
+ type: "boolean",
773
+ id: "showCaption",
774
+ label: "Show caption",
775
+ description: "Print the caption (or the code value) beside each QR",
776
+ defaultValue: true
777
+ },
778
+ {
779
+ type: "boolean",
780
+ id: "showLines",
781
+ label: "Show detail lines",
782
+ description: "Extra text lines on wide labels",
783
+ defaultValue: true
784
+ },
785
+ {
786
+ type: "range",
787
+ id: "labelRange",
788
+ label: "Label range",
789
+ description: "Print only labels N to M (blank = all)",
790
+ fromId: "rangeFrom",
791
+ toId: "rangeTo",
792
+ defaultFrom: 0,
793
+ defaultTo: 0
794
+ },
795
+ {
796
+ type: "select",
797
+ id: "ecLevel",
798
+ label: "Error correction",
799
+ description: "Higher survives more damage; M is the warehouse default",
800
+ options: [
801
+ { value: "L", label: "L" },
802
+ { value: "M", label: "M" },
803
+ { value: "Q", label: "Q" }
804
+ ],
805
+ defaultValue: "M"
806
+ },
807
+ {
808
+ type: "boolean",
809
+ id: "calibrationPage",
810
+ label: "Calibration test page",
811
+ description: "Print outlines + crop marks only to verify alignment",
812
+ defaultValue: false
813
+ }
814
+ ],
815
+ print: (data, variantId, settings) => printQrLabelSheet(data, variantId, settings)
816
+ };
817
+
818
+ // src/zpl.ts
819
+ var ZPL_MIN_MAGNIFICATION = 1;
820
+ var ZPL_MAX_MAGNIFICATION = 10;
821
+ var ZPL_QUIET_ZONE_MODULES = 4;
822
+ var DEFAULT_ZPL_DPI = 203;
823
+ var DEFAULT_ZPL_EC_LEVEL = "M";
824
+ var MM_PER_IN2 = 25.4;
825
+ var ZPL_SHEET_STOCK_MESSAGE = "ZPL is for roll stock \u2014 use printQrLabelSheet for sheets";
826
+ function resolveOptions(opts) {
827
+ const dpi = opts?.dpi ?? DEFAULT_ZPL_DPI;
828
+ if (dpi !== 203 && dpi !== 300) {
829
+ throw new Error(
830
+ `ZPL dpi must be 203 (8 dots/mm) or 300 (12 dots/mm), got ${String(dpi)}.`
831
+ );
832
+ }
833
+ const copies = opts?.copies ?? 1;
834
+ if (!Number.isInteger(copies) || copies < 1 || copies > 9999) {
835
+ throw new Error(`ZPL copies must be an integer between 1 and 9999, got ${String(copies)}.`);
836
+ }
837
+ const rawDarkness = opts?.darkness;
838
+ let darkness;
839
+ if (rawDarkness !== void 0) {
840
+ if (!Number.isInteger(rawDarkness) || rawDarkness < -30 || rawDarkness > 30) {
841
+ throw new Error(
842
+ `ZPL darkness (^MD) must be an integer between -30 and 30, got ${String(rawDarkness)}.`
843
+ );
844
+ }
845
+ darkness = rawDarkness;
846
+ }
847
+ const ecLevel = opts?.ecLevel ?? DEFAULT_ZPL_EC_LEVEL;
848
+ if (ecLevel !== "L" && ecLevel !== "M" && ecLevel !== "Q" && ecLevel !== "H") {
849
+ throw new Error(
850
+ `ZPL QR error correction must be "L", "M", "Q" or "H", got ${String(ecLevel)}.`
851
+ );
852
+ }
853
+ return { dpi, darkness, copies, ecLevel };
854
+ }
855
+ function assertRollStock(template) {
856
+ if (template.kind !== "roll") {
857
+ throw new Error(
858
+ `${ZPL_SHEET_STOCK_MESSAGE} (template "${template.id}" \u2014 ${template.stockCode} \u2014 is ${template.cols}\xD7${template.rows} sheet stock, which has no ZPL page model).`
859
+ );
860
+ }
861
+ }
862
+ var HEX = "0123456789ABCDEF";
863
+ function zplFieldData(text) {
864
+ if (typeof text !== "string") {
865
+ throw new Error(`ZPL field data must be a string, got ${String(text)}.`);
866
+ }
867
+ if (text === "") return "";
868
+ const bytes = new TextEncoder().encode(text);
869
+ let out = "";
870
+ for (const byte of bytes) {
871
+ const safe = byte >= 32 && byte <= 126 && byte !== 94 && byte !== 126 && byte !== 95;
872
+ if (safe) {
873
+ out += String.fromCharCode(byte);
874
+ } else {
875
+ out += `_${HEX[byte >> 4 & 15] ?? "0"}${HEX[byte & 15] ?? "0"}`;
876
+ }
877
+ }
878
+ return out;
879
+ }
880
+ function dots(inches, dpi) {
881
+ return Math.round(inches * dpi);
882
+ }
883
+ function clampMagnification(value) {
884
+ if (!Number.isFinite(value)) return ZPL_MIN_MAGNIFICATION;
885
+ return Math.min(ZPL_MAX_MAGNIFICATION, Math.max(ZPL_MIN_MAGNIFICATION, Math.floor(value)));
886
+ }
887
+ function payloadByteLength(value) {
888
+ return new TextEncoder().encode(value).length;
889
+ }
890
+ function magnificationFor(qrBoxDots, payloadBytes, ecLevel) {
891
+ const version = minQrVersion(payloadBytes, ecLevel);
892
+ const modulesPerSide = qrModuleCount(version) + ZPL_QUIET_ZONE_MODULES * 2;
893
+ const magnification = clampMagnification(qrBoxDots / modulesPerSide);
894
+ return { version, magnification, drawnDots: modulesPerSide * magnification };
895
+ }
896
+ function captionZoneIn(t, wide) {
897
+ return wide ? t.labelHIn * 0.3 : Math.min(0.28, t.labelHIn * 0.16);
898
+ }
899
+ function textField(x, y, widthDots, fontHeightDots, justify, text) {
900
+ return `^FO${x},${y}^A0N,${fontHeightDots},0^FB${widthDots},1,0,${justify}^FH^FD${zplFieldData(text)}^FS`;
901
+ }
902
+ function qrField(x, y, magnification, ecLevel, value) {
903
+ return `^FO${x},${y}^BQN,2,${magnification}^FH^FD${ecLevel}A,${zplFieldData(value)}^FS`;
904
+ }
905
+ function cleanLines(lines) {
906
+ if (lines === void 0) return [];
907
+ return lines.map((l) => l.trim()).filter((l) => l !== "");
908
+ }
909
+ function labelToZpl(template, label, opts) {
910
+ assertRollStock(template);
911
+ const o = resolveOptions(opts);
912
+ const captionText = (label.caption ?? label.qrValue).trim();
913
+ const badgeText = (label.badge ?? "").trim();
914
+ const lines = cleanLines(label.lines);
915
+ const hasQr = typeof label.qrValue === "string" && label.qrValue !== "";
916
+ const layout = computeCellLayout(template, captionText !== "", badgeText !== "");
917
+ const labelWDots = dots(template.labelWIn, o.dpi);
918
+ const labelHDots = dots(template.labelHIn, o.dpi);
919
+ const padDots = dots(layout.padIn, o.dpi);
920
+ const qrBoxDots = hasQr ? dots(layout.qrIn, o.dpi) : 0;
921
+ const badgeZoneDots = badgeText !== "" ? dots(layout.badgeZoneIn, o.dpi) : 0;
922
+ const captionZoneDots = captionText !== "" ? dots(captionZoneIn(template, layout.wide), o.dpi) : 0;
923
+ const lineFontDots = Math.max(8, Math.round(o.dpi * 8 / 72));
924
+ const lineStepDots = Math.round(lineFontDots * 1.25);
925
+ const linesHDots = lines.length * lineStepDots;
926
+ const badgeFontDots = Math.max(10, Math.round(badgeZoneDots * 0.8));
927
+ const captionFontDots = Math.max(8, Math.round(captionZoneDots * 0.72));
928
+ const commands = [];
929
+ commands.push("^XA");
930
+ commands.push("^CI28");
931
+ commands.push("^LH0,0");
932
+ commands.push(`^PW${dots(template.labelWIn, o.dpi)}`);
933
+ commands.push(`^LL${dots(template.labelHIn, o.dpi)}`);
934
+ if (o.darkness !== void 0) {
935
+ commands.push(`^MD${o.darkness}`);
936
+ }
937
+ const fit = hasQr ? magnificationFor(qrBoxDots, payloadByteLength(label.qrValue), o.ecLevel) : { version: 0, magnification: 0, drawnDots: 0 };
938
+ const quietDots = fit.magnification * ZPL_QUIET_ZONE_MODULES;
939
+ if (layout.wide) {
940
+ const qrY = Math.max(padDots, Math.round((labelHDots - fit.drawnDots) / 2));
941
+ if (hasQr) {
942
+ commands.push(
943
+ qrField(padDots + quietDots, qrY + quietDots, fit.magnification, o.ecLevel, label.qrValue)
944
+ );
945
+ }
946
+ const textX = padDots + fit.drawnDots + padDots;
947
+ const textW = Math.max(1, labelWDots - textX - padDots);
948
+ let y = padDots;
949
+ if (badgeText !== "") {
950
+ commands.push(textField(textX, y, textW, badgeFontDots, "L", badgeText));
951
+ y += badgeZoneDots;
952
+ }
953
+ if (captionText !== "") {
954
+ commands.push(textField(textX, y, textW, captionFontDots, "L", captionText));
955
+ y += captionZoneDots;
956
+ }
957
+ for (const line of lines) {
958
+ commands.push(textField(textX, y, textW, lineFontDots, "L", line));
959
+ y += lineStepDots;
960
+ }
961
+ } else {
962
+ const contentH = fit.drawnDots + badgeZoneDots + captionZoneDots + linesHDots;
963
+ const textW = Math.max(1, labelWDots - padDots * 2);
964
+ let y = Math.max(padDots, Math.round((labelHDots - contentH) / 2));
965
+ if (hasQr) {
966
+ const qrX = Math.max(0, Math.round((labelWDots - fit.drawnDots) / 2));
967
+ commands.push(
968
+ qrField(qrX + quietDots, y + quietDots, fit.magnification, o.ecLevel, label.qrValue)
969
+ );
970
+ y += fit.drawnDots;
971
+ }
972
+ if (badgeText !== "") {
973
+ commands.push(textField(padDots, y, textW, badgeFontDots, "C", badgeText));
974
+ y += badgeZoneDots;
975
+ }
976
+ if (captionText !== "") {
977
+ commands.push(textField(padDots, y, textW, captionFontDots, "C", captionText));
978
+ y += captionZoneDots;
979
+ }
980
+ for (const line of lines) {
981
+ commands.push(textField(padDots, y, textW, lineFontDots, "C", line));
982
+ y += lineStepDots;
983
+ }
984
+ }
985
+ if (o.copies > 1) {
986
+ commands.push(`^PQ${o.copies},0,0,N`);
987
+ }
988
+ commands.push("^XZ");
989
+ return commands.join("\n");
990
+ }
991
+ function labelsToZpl(template, labels, opts) {
992
+ assertRollStock(template);
993
+ resolveOptions(opts);
994
+ if (labels.length === 0) return "";
995
+ return labels.map((label) => labelToZpl(template, label, opts)).join("\n");
996
+ }
997
+ function assertZplScannable(template, payloadByteLen, opts) {
998
+ const fail = (message, version2 = 0, moduleSizeMm2 = 0) => ({
999
+ ok: false,
1000
+ version: version2,
1001
+ moduleSizeMm: moduleSizeMm2,
1002
+ minModuleSizeMm: MIN_SCANNABLE_MODULE_MM,
1003
+ message
1004
+ });
1005
+ if (template.kind !== "roll") {
1006
+ return fail(
1007
+ `${ZPL_SHEET_STOCK_MESSAGE} (template "${template.id}" \u2014 ${template.stockCode} \u2014 is sheet stock).`
1008
+ );
1009
+ }
1010
+ let o;
1011
+ try {
1012
+ o = resolveOptions(opts);
1013
+ } catch (err) {
1014
+ return fail(err instanceof Error ? err.message : String(err));
1015
+ }
1016
+ const layout = computeCellLayout(template, true);
1017
+ const qrBoxDots = dots(layout.qrIn, o.dpi);
1018
+ let fit;
1019
+ try {
1020
+ fit = magnificationFor(qrBoxDots, payloadByteLen, o.ecLevel);
1021
+ } catch (err) {
1022
+ return fail(err instanceof Error ? err.message : String(err));
1023
+ }
1024
+ const { version, magnification } = fit;
1025
+ const moduleSizeMm = magnification * MM_PER_IN2 / o.dpi;
1026
+ if (moduleSizeMm >= MIN_SCANNABLE_MODULE_MM) {
1027
+ return {
1028
+ ok: true,
1029
+ version,
1030
+ moduleSizeMm,
1031
+ minModuleSizeMm: MIN_SCANNABLE_MODULE_MM
1032
+ };
1033
+ }
1034
+ return fail(
1035
+ `A ${payloadByteLen}-byte payload needs a version ${version} QR, which ZPL prints at magnification ${magnification} (${moduleSizeMm.toFixed(3)}mm per module) on ${template.stockCode} at ${o.dpi}dpi \u2014 below the ${MIN_SCANNABLE_MODULE_MM}mm floor a scanner can resolve. Shorten the payload (a short resolver URL is the intended fix), move to larger roll stock, or print on a 300dpi head.`,
1036
+ version,
1037
+ moduleSizeMm
1038
+ );
1039
+ }
1040
+ //# sourceMappingURL=zpl.cjs.map