@worm-vue3-print/core 1.2.2 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3819 @@
1
+ // src/lexer.ts
2
+ var DANGEROUS_PROPS = /* @__PURE__ */ new Set([
3
+ "constructor",
4
+ "__proto__",
5
+ "prototype",
6
+ "__defineGetter__",
7
+ "__defineSetter__",
8
+ "__lookupGetter__",
9
+ "__lookupSetter__"
10
+ ]);
11
+ var RESERVED_WORDS = /* @__PURE__ */ new Set([
12
+ "new",
13
+ "function",
14
+ "class",
15
+ "this",
16
+ "delete",
17
+ "typeof",
18
+ "void",
19
+ "in",
20
+ "of",
21
+ "var",
22
+ "let",
23
+ "const",
24
+ "return",
25
+ "if",
26
+ "while",
27
+ "for"
28
+ ]);
29
+ function tokenize(input) {
30
+ const tokens = [];
31
+ let i = 0;
32
+ const n = input.length;
33
+ while (i < n) {
34
+ const ch = input[i];
35
+ if (/\s/.test(ch)) {
36
+ i++;
37
+ continue;
38
+ }
39
+ if (/[0-9]/.test(ch) || ch === "." && i + 1 < n && /[0-9]/.test(input[i + 1])) {
40
+ let j = i;
41
+ while (j < n && /[0-9.]/.test(input[j])) j++;
42
+ tokens.push({ type: "num", value: input.slice(i, j), position: i });
43
+ i = j;
44
+ continue;
45
+ }
46
+ if (ch === "'" || ch === '"') {
47
+ let j = i + 1;
48
+ let s = "";
49
+ while (j < n && input[j] !== ch) {
50
+ if (input[j] === "\\" && j + 1 < n) {
51
+ s += input[j + 1];
52
+ j += 2;
53
+ } else {
54
+ s += input[j];
55
+ j++;
56
+ }
57
+ }
58
+ if (j >= n) throw new Error("\u5B57\u7B26\u4E32\u672A\u95ED\u5408");
59
+ tokens.push({ type: "str", value: s, position: i });
60
+ i = j + 1;
61
+ continue;
62
+ }
63
+ if (/[A-Za-z_$]/.test(ch)) {
64
+ let j = i;
65
+ while (j < n && /[A-Za-z0-9_$]/.test(input[j])) j++;
66
+ const word = input.slice(i, j);
67
+ if (RESERVED_WORDS.has(word)) {
68
+ throw new Error(`\u4E0D\u5141\u8BB8\u4F7F\u7528\u5173\u952E\u5B57: ${word}`);
69
+ }
70
+ if (DANGEROUS_PROPS.has(word)) {
71
+ throw new Error(`\u7981\u6B62\u8BBF\u95EE: ${word}`);
72
+ }
73
+ if (word === "true" || word === "false") {
74
+ tokens.push({ type: "bool", value: word, position: i });
75
+ } else {
76
+ tokens.push({ type: "ident", value: word, position: i });
77
+ }
78
+ i = j;
79
+ continue;
80
+ }
81
+ const three = input.slice(i, i + 3);
82
+ if (three === "===" || three === "!==") {
83
+ tokens.push({ type: "punc", value: three, position: i });
84
+ i += 3;
85
+ continue;
86
+ }
87
+ const two = input.slice(i, i + 2);
88
+ if (two === "=>") throw new Error("\u4E0D\u5141\u8BB8\u51FD\u6570\u5B9A\u4E49");
89
+ if (["==", "!=", "<=", ">=", "&&", "||"].includes(two)) {
90
+ tokens.push({ type: "punc", value: two, position: i });
91
+ i += 2;
92
+ continue;
93
+ }
94
+ if (ch === "=") throw new Error("\u4E0D\u5141\u8BB8\u8D4B\u503C");
95
+ if (ch === ";") throw new Error("\u4E0D\u5141\u8BB8\u591A\u6761\u8BED\u53E5");
96
+ if ("+-*/%()[]{},:.?!<>".includes(ch)) {
97
+ tokens.push({ type: "punc", value: ch, position: i });
98
+ i++;
99
+ continue;
100
+ }
101
+ throw new Error(`\u975E\u6CD5\u5B57\u7B26: ${ch}`);
102
+ }
103
+ return tokens;
104
+ }
105
+
106
+ // src/parser.ts
107
+ function parse(tokens) {
108
+ const parser = new Parser(tokens);
109
+ return parser.parse();
110
+ }
111
+ var Parser = class {
112
+ constructor(tokens) {
113
+ this.pos = 0;
114
+ this.tokens = tokens;
115
+ }
116
+ parse() {
117
+ const node = this.parseTernary();
118
+ if (this.pos < this.tokens.length) {
119
+ throw new Error(`\u8868\u8FBE\u5F0F\u5B58\u5728\u591A\u4F59\u5185\u5BB9: ${this.tokens[this.pos].value}`);
120
+ }
121
+ return node;
122
+ }
123
+ peek() {
124
+ return this.tokens[this.pos];
125
+ }
126
+ next() {
127
+ const t = this.tokens[this.pos++];
128
+ if (!t) throw new Error("\u8868\u8FBE\u5F0F\u610F\u5916\u7ED3\u675F");
129
+ return t;
130
+ }
131
+ eatPunc(value) {
132
+ const t = this.peek();
133
+ if (t && t.type === "punc" && t.value === value) {
134
+ this.pos++;
135
+ return true;
136
+ }
137
+ return false;
138
+ }
139
+ expectPunc(value) {
140
+ if (!this.eatPunc(value)) {
141
+ const pos = this.peek()?.position ?? 0;
142
+ throw new Error(`\u4F4D\u7F6E ${pos}: \u671F\u671B "${value}"`);
143
+ }
144
+ }
145
+ // 三元运算符 ?: (优先级最低)
146
+ parseTernary() {
147
+ const cond = this.parseLogicalOr();
148
+ if (this.eatPunc("?")) {
149
+ const consequent = this.parseTernary();
150
+ this.expectPunc(":");
151
+ const alternate = this.parseTernary();
152
+ return { type: "ternary", cond, consequent, alternate };
153
+ }
154
+ return cond;
155
+ }
156
+ // ||
157
+ parseLogicalOr() {
158
+ let left = this.parseLogicalAnd();
159
+ while (this.eatPunc("||")) {
160
+ const right = this.parseLogicalAnd();
161
+ left = { type: "binary", op: "||", left, right };
162
+ }
163
+ return left;
164
+ }
165
+ // &&
166
+ parseLogicalAnd() {
167
+ let left = this.parseEquality();
168
+ while (this.eatPunc("&&")) {
169
+ const right = this.parseEquality();
170
+ left = { type: "binary", op: "&&", left, right };
171
+ }
172
+ return left;
173
+ }
174
+ // == != === !==
175
+ parseEquality() {
176
+ let left = this.parseRelational();
177
+ for (; ; ) {
178
+ const t = this.peek();
179
+ if (t?.type === "punc" && ["==", "!=", "===", "!=="].includes(t.value)) {
180
+ this.pos++;
181
+ const right = this.parseRelational();
182
+ left = { type: "binary", op: t.value, left, right };
183
+ } else {
184
+ return left;
185
+ }
186
+ }
187
+ }
188
+ // < > <= >=
189
+ parseRelational() {
190
+ let left = this.parseAdditive();
191
+ for (; ; ) {
192
+ const t = this.peek();
193
+ if (t?.type === "punc" && ["<", ">", "<=", ">="].includes(t.value)) {
194
+ this.pos++;
195
+ const right = this.parseAdditive();
196
+ left = { type: "binary", op: t.value, left, right };
197
+ } else {
198
+ return left;
199
+ }
200
+ }
201
+ }
202
+ // + - (二元)
203
+ parseAdditive() {
204
+ let left = this.parseMultiplicative();
205
+ for (; ; ) {
206
+ const t = this.peek();
207
+ if (t?.type === "punc" && (t.value === "+" || t.value === "-")) {
208
+ this.pos++;
209
+ const right = this.parseMultiplicative();
210
+ left = { type: "binary", op: t.value, left, right };
211
+ } else {
212
+ return left;
213
+ }
214
+ }
215
+ }
216
+ // * / %
217
+ parseMultiplicative() {
218
+ let left = this.parseUnary();
219
+ for (; ; ) {
220
+ const t = this.peek();
221
+ if (t?.type === "punc" && ["*", "/", "%"].includes(t.value)) {
222
+ this.pos++;
223
+ const right = this.parseUnary();
224
+ left = { type: "binary", op: t.value, left, right };
225
+ } else {
226
+ return left;
227
+ }
228
+ }
229
+ }
230
+ // 一元 ! - +
231
+ parseUnary() {
232
+ const t = this.peek();
233
+ if (t?.type === "punc" && ["!", "-", "+"].includes(t.value)) {
234
+ this.pos++;
235
+ const arg = this.parseUnary();
236
+ return { type: "unary", op: t.value, arg, prefix: true };
237
+ }
238
+ return this.parsePostfix();
239
+ }
240
+ // 成员访问 x.y, x[i], 函数调用 f()
241
+ parsePostfix() {
242
+ let node = this.parsePrimary();
243
+ for (; ; ) {
244
+ if (this.eatPunc(".")) {
245
+ const prop = this.next();
246
+ if (prop.type !== "ident") {
247
+ throw new Error(`\u4F4D\u7F6E ${prop.position}: \u6210\u5458\u8BBF\u95EE\u9700\u8981\u5C5E\u6027\u540D`);
248
+ }
249
+ node = {
250
+ type: "member",
251
+ object: node,
252
+ property: prop.value,
253
+ computed: false
254
+ };
255
+ } else if (this.eatPunc("[")) {
256
+ const index = this.parseTernary();
257
+ this.expectPunc("]");
258
+ if (index.type === "str") {
259
+ node = {
260
+ type: "member",
261
+ object: node,
262
+ property: index.value,
263
+ computed: false
264
+ };
265
+ } else {
266
+ node = {
267
+ type: "member",
268
+ object: node,
269
+ property: index.name ?? String(index.value),
270
+ computed: true
271
+ };
272
+ }
273
+ } else if (this.peek()?.type === "punc" && this.peek()?.value === "(" && (node.type === "ident" || node.type === "member")) {
274
+ this.pos++;
275
+ const args = this.parseArgs();
276
+ node = { type: "call", callee: node, args };
277
+ } else {
278
+ return node;
279
+ }
280
+ }
281
+ }
282
+ parseArgs() {
283
+ const args = [];
284
+ if (this.eatPunc(")")) return args;
285
+ for (; ; ) {
286
+ args.push(this.parseTernary());
287
+ if (this.eatPunc(")")) return args;
288
+ this.expectPunc(",");
289
+ }
290
+ }
291
+ parsePrimary() {
292
+ const t = this.next();
293
+ if (t.type === "num") {
294
+ const v = Number(t.value);
295
+ if (isNaN(v)) throw new Error(`\u975E\u6CD5\u6570\u5B57: ${t.value}`);
296
+ return { type: "num", value: v };
297
+ }
298
+ if (t.type === "str") {
299
+ return { type: "str", value: t.value };
300
+ }
301
+ if (t.type === "bool") {
302
+ return { type: "bool", value: t.value === "true" };
303
+ }
304
+ if (t.type === "ident") {
305
+ return { type: "ident", name: t.value };
306
+ }
307
+ if (t.value === "(") {
308
+ const inner = this.parseTernary();
309
+ this.expectPunc(")");
310
+ return inner;
311
+ }
312
+ if (t.value === "[") {
313
+ const elements = [];
314
+ if (!this.eatPunc("]")) {
315
+ for (; ; ) {
316
+ elements.push(this.parseTernary());
317
+ if (this.eatPunc("]")) break;
318
+ this.expectPunc(",");
319
+ }
320
+ }
321
+ return { type: "array", elements };
322
+ }
323
+ if (t.value === "{") {
324
+ const properties = [];
325
+ if (!this.eatPunc("}")) {
326
+ for (; ; ) {
327
+ const keyToken = this.next();
328
+ if (keyToken.type !== "ident" && keyToken.type !== "str") {
329
+ throw new Error(`\u4F4D\u7F6E ${keyToken.position}: \u5BF9\u8C61\u952E\u5FC5\u987B\u662F\u6807\u8BC6\u7B26\u6216\u5B57\u7B26\u4E32`);
330
+ }
331
+ this.expectPunc(":");
332
+ properties.push({ key: keyToken.value, value: this.parseTernary() });
333
+ if (this.eatPunc("}")) break;
334
+ this.expectPunc(",");
335
+ }
336
+ }
337
+ return { type: "object", properties };
338
+ }
339
+ throw new Error(`\u4F4D\u7F6E ${t.position}: \u610F\u5916\u7684\u7B26\u53F7: ${t.value}`);
340
+ }
341
+ };
342
+
343
+ // src/numeric.ts
344
+ var MAX_DIGITS = 20;
345
+ var EXTRA_SCALE = 8;
346
+ function toNumber(value, fallback = 0) {
347
+ if (typeof value === "number") return Number.isFinite(value) ? value : fallback;
348
+ if (typeof value === "string") {
349
+ const s = value.trim();
350
+ if (s === "") return fallback;
351
+ const n = Number(s);
352
+ return Number.isFinite(n) ? n : fallback;
353
+ }
354
+ if (value == null) return fallback;
355
+ if (typeof value === "boolean") return value ? 1 : 0;
356
+ return fallback;
357
+ }
358
+ function isNumericLike(value) {
359
+ if (typeof value === "number") return Number.isFinite(value);
360
+ if (typeof value === "string") {
361
+ const s = value.trim();
362
+ return s !== "" && Number.isFinite(Number(s));
363
+ }
364
+ return false;
365
+ }
366
+ function normalizeFloat(value) {
367
+ if (!Number.isFinite(value) || Number.isInteger(value)) return value;
368
+ if (Math.abs(value) >= 1e15) return value;
369
+ return Number(value.toPrecision(12));
370
+ }
371
+ function decimalsOf(n) {
372
+ if (!Number.isFinite(n) || Number.isInteger(n)) return 0;
373
+ const s = String(n);
374
+ if (s.includes("e") || s.includes("E")) return 0;
375
+ const i = s.indexOf(".");
376
+ return i === -1 ? 0 : s.length - i - 1;
377
+ }
378
+ function rescale(value, decimals) {
379
+ if (!Number.isFinite(value)) return 0;
380
+ const d = Math.min(Math.max(decimals, 0), 12);
381
+ if (d === 0) return Math.round(value);
382
+ const f = 10 ** d;
383
+ const scaled = value * f;
384
+ if (!Number.isFinite(scaled) || Math.abs(scaled) >= 1e15) return normalizeFloat(value);
385
+ return Math.round(scaled) / f;
386
+ }
387
+ function decimalAdd(left, right) {
388
+ const a = toNumber(left);
389
+ const b = toNumber(right);
390
+ const raw = a + b;
391
+ return rescale(raw, Math.max(decimalsOf(a), decimalsOf(b)));
392
+ }
393
+ function decimalSubtract(left, right) {
394
+ const a = toNumber(left);
395
+ const b = toNumber(right);
396
+ const raw = a - b;
397
+ return rescale(raw, Math.max(decimalsOf(a), decimalsOf(b)));
398
+ }
399
+ function decimalMultiply(left, right) {
400
+ const a = toNumber(left);
401
+ const b = toNumber(right);
402
+ const raw = a * b;
403
+ return rescale(raw, decimalsOf(a) + decimalsOf(b));
404
+ }
405
+ function decimalDivide(left, right) {
406
+ const a = toNumber(left);
407
+ const b = toNumber(right);
408
+ if (b === 0) return 0;
409
+ return normalizeFloat(a / b);
410
+ }
411
+ function decimalModulo(left, right) {
412
+ const a = toNumber(left);
413
+ const b = toNumber(right);
414
+ if (b === 0) return 0;
415
+ return normalizeFloat(a % b);
416
+ }
417
+ function normalizeDigits(digits) {
418
+ if (digits === void 0 || digits === null) return 2;
419
+ const d = Math.trunc(toNumber(digits, 2));
420
+ if (!Number.isFinite(d)) return 2;
421
+ return Math.min(Math.max(d, -MAX_DIGITS), MAX_DIGITS);
422
+ }
423
+ function roundTo(value, digits, mode) {
424
+ const n = toNumber(value, 0);
425
+ const d = normalizeDigits(digits);
426
+ if (n === 0) return 0;
427
+ if (Math.abs(n) >= 1e21) return normalizeFloat(n);
428
+ const scale = Math.max(d, 0) + EXTRA_SCALE;
429
+ const fixed = n.toFixed(scale);
430
+ if (!/^-?\d+(\.\d+)?$/.test(fixed)) return normalizeFloat(n);
431
+ const negative = fixed.startsWith("-");
432
+ const digitsStr = fixed.replace("-", "").replace(".", "");
433
+ let scaled = BigInt(digitsStr || "0");
434
+ if (negative) scaled = -scaled;
435
+ const pow10 = 10n ** BigInt(scale - d);
436
+ const sign = scaled < 0n ? -1n : 1n;
437
+ const abs = scaled < 0n ? -scaled : scaled;
438
+ let q = abs / pow10;
439
+ const r = abs % pow10;
440
+ const doubled = 2n * r;
441
+ if (mode === "up") {
442
+ if (r !== 0n) q += 1n;
443
+ } else if (mode !== "down" && doubled >= pow10) {
444
+ if (doubled > pow10 || mode === "half-up" || q % 2n === 1n) q += 1n;
445
+ }
446
+ const result = sign * q;
447
+ return normalizeFloat(d >= 0 ? Number(result) / 10 ** d : Number(result) * 10 ** -d);
448
+ }
449
+
450
+ // src/evaluator.ts
451
+ var SAFE_GLOBALS = {
452
+ Math,
453
+ Number,
454
+ String,
455
+ Boolean,
456
+ parseInt,
457
+ parseFloat,
458
+ isNaN,
459
+ true: true,
460
+ false: false,
461
+ null: null,
462
+ undefined: void 0
463
+ };
464
+ var DANGEROUS_PROPS2 = /* @__PURE__ */ new Set([
465
+ "constructor",
466
+ "__proto__",
467
+ "prototype",
468
+ "__defineGetter__",
469
+ "__defineSetter__",
470
+ "__lookupGetter__",
471
+ "__lookupSetter__"
472
+ ]);
473
+ var CALLABLE_GLOBALS = /* @__PURE__ */ new Set(["Number", "String", "Boolean", "parseInt", "parseFloat", "isNaN"]);
474
+ function evaluate(node, context, functions = {}) {
475
+ switch (node.type) {
476
+ case "num":
477
+ return node.value;
478
+ case "str":
479
+ return node.value;
480
+ case "bool":
481
+ return node.value;
482
+ case "ident":
483
+ return evalIdent(node.name, context, functions);
484
+ case "binary":
485
+ return evalBinary(node, context, functions);
486
+ case "unary":
487
+ return evalUnary(node, context, functions);
488
+ case "ternary":
489
+ return evaluate(node.cond, context, functions) ? evaluate(node.consequent, context, functions) : evaluate(node.alternate, context, functions);
490
+ case "member":
491
+ return evalMember(node, context, functions);
492
+ case "call":
493
+ return evalCall(node, context, functions);
494
+ case "array":
495
+ return node.elements.map((e) => evaluate(e, context, functions));
496
+ case "object":
497
+ return Object.fromEntries(
498
+ node.properties.map((p) => [p.key, evaluate(p.value, context, functions)])
499
+ );
500
+ }
501
+ }
502
+ function evalIdent(name, context, functions) {
503
+ if (Object.prototype.hasOwnProperty.call(context, name)) {
504
+ return context[name];
505
+ }
506
+ if (Object.prototype.hasOwnProperty.call(SAFE_GLOBALS, name)) {
507
+ return SAFE_GLOBALS[name];
508
+ }
509
+ throw new Error(`\u672A\u5B9A\u4E49\u7684\u6807\u8BC6\u7B26: ${name}`);
510
+ }
511
+ function evalBinary(node, context, functions) {
512
+ const left = evaluate(node.left, context, functions);
513
+ const right = evaluate(node.right, context, functions);
514
+ switch (node.op) {
515
+ // 数值运算统一走十进制精确实现:消除 0.1 + 0.2 类浮点噪声、字符串数字按数值处理、除零兜底 0
516
+ case "+":
517
+ return isNumericOperands(left, right) ? decimalAdd(left, right) : concatOperands(left, right);
518
+ case "-":
519
+ return decimalSubtract(left, right);
520
+ case "*":
521
+ return decimalMultiply(left, right);
522
+ case "/":
523
+ return decimalDivide(left, right);
524
+ case "%":
525
+ return decimalModulo(left, right);
526
+ case "<":
527
+ return left < right;
528
+ case ">":
529
+ return left > right;
530
+ case "<=":
531
+ return left <= right;
532
+ case ">=":
533
+ return left >= right;
534
+ case "==":
535
+ return left == right;
536
+ case "!=":
537
+ return left != right;
538
+ case "===":
539
+ return left === right;
540
+ case "!==":
541
+ return left !== right;
542
+ case "&&":
543
+ return left && right;
544
+ case "||":
545
+ return left || right;
546
+ default:
547
+ throw new Error(`\u672A\u77E5\u8FD0\u7B97\u7B26: ${node.op}`);
548
+ }
549
+ }
550
+ function evalUnary(node, context, functions) {
551
+ const arg = evaluate(node.arg, context, functions);
552
+ switch (node.op) {
553
+ case "-":
554
+ return decimalSubtract(0, arg);
555
+ case "+":
556
+ return toNumber(arg);
557
+ case "!":
558
+ return !arg;
559
+ default:
560
+ throw new Error(`\u672A\u77E5\u4E00\u5143\u8FD0\u7B97\u7B26: ${node.op}`);
561
+ }
562
+ }
563
+ function concatOperands(left, right) {
564
+ return `${left == null ? "" : String(left)}${right == null ? "" : String(right)}`;
565
+ }
566
+ function isNumericOperands(left, right) {
567
+ const l = operandKind(left);
568
+ const r = operandKind(right);
569
+ if (l === "other" || r === "other") return false;
570
+ return l === "num" || r === "num";
571
+ }
572
+ function operandKind(value) {
573
+ if (value == null || value === "") return "neutral";
574
+ if (isNumericLike(value)) return "num";
575
+ return "other";
576
+ }
577
+ function evalMember(node, context, functions) {
578
+ const obj = evaluate(node.object, context, functions);
579
+ if (obj == null) {
580
+ throw new Error("\u65E0\u6CD5\u8BBF\u95EE\u7A7A\u503C\u7684\u5C5E\u6027");
581
+ }
582
+ if (DANGEROUS_PROPS2.has(node.property)) {
583
+ throw new Error(`\u7981\u6B62\u8BBF\u95EE\u5C5E\u6027: ${node.property}`);
584
+ }
585
+ return obj[node.property];
586
+ }
587
+ function evalCall(node, context, functions) {
588
+ if (node.callee.type === "ident" && CALLABLE_GLOBALS.has(node.callee.name)) {
589
+ const fn = SAFE_GLOBALS[node.callee.name];
590
+ const args = node.args.map((a) => evaluate(a, context, functions));
591
+ return fn(...args);
592
+ }
593
+ if (node.callee.type === "member" && node.callee.object.type === "ident" && node.callee.object.name === "Math") {
594
+ const method = Math[node.callee.property];
595
+ if (typeof method !== "function") {
596
+ throw new Error(`Math.${node.callee.property} \u4E0D\u662F\u65B9\u6CD5`);
597
+ }
598
+ const args = node.args.map((a) => evaluate(a, context, functions));
599
+ return method(...args);
600
+ }
601
+ let fnName;
602
+ if (node.callee.type === "ident") {
603
+ fnName = node.callee.name;
604
+ } else if (node.callee.type === "member") {
605
+ throw new Error("\u4E0D\u652F\u6301\u65B9\u6CD5\u8C03\u7528\uFF0C\u8BF7\u4F7F\u7528\u9876\u7EA7\u51FD\u6570");
606
+ }
607
+ if (fnName && functions[fnName]) {
608
+ const args = node.args.map((a) => evaluate(a, context, functions));
609
+ return functions[fnName](...args);
610
+ }
611
+ if (fnName && Object.prototype.hasOwnProperty.call(context, fnName)) {
612
+ throw new Error(`${fnName} \u4E0D\u662F\u51FD\u6570`);
613
+ }
614
+ throw new Error(`\u4E0D\u5141\u8BB8\u8C03\u7528: ${fnName ?? "\u8868\u8FBE\u5F0F"}`);
615
+ }
616
+
617
+ // src/template-parser.ts
618
+ function parseTemplate(template) {
619
+ const parts = [];
620
+ let i = 0;
621
+ const n = template.length;
622
+ while (i < n) {
623
+ if (template[i] === "{") {
624
+ let depth = 1;
625
+ let j2 = i + 1;
626
+ while (j2 < n && depth > 0) {
627
+ if (template[j2] === "{") depth++;
628
+ else if (template[j2] === "}") depth--;
629
+ j2++;
630
+ }
631
+ if (depth !== 0) {
632
+ throw new Error(`\u4F4D\u7F6E ${i}: \u82B1\u62EC\u53F7\u672A\u95ED\u5408`);
633
+ }
634
+ const exprStr = template.slice(i + 1, j2 - 1);
635
+ if (exprStr.trim() === "") {
636
+ throw new Error(`\u4F4D\u7F6E ${i}: \u7A7A\u8868\u8FBE\u5F0F`);
637
+ }
638
+ const tokens = tokenize(exprStr);
639
+ const expression = parse(tokens);
640
+ parts.push({ type: "expr", expression });
641
+ i = j2;
642
+ continue;
643
+ }
644
+ let j = i;
645
+ while (j < n && template[j] !== "{") {
646
+ if (template[j] === "\\" && j + 1 < n && (template[j + 1] === "{" || template[j + 1] === "}")) {
647
+ j += 2;
648
+ } else {
649
+ j++;
650
+ }
651
+ }
652
+ parts.push({ type: "text", value: template.slice(i, j).replace(/\\([{}])/g, "$1") });
653
+ i = j;
654
+ }
655
+ return { type: "template", parts };
656
+ }
657
+ function renderTemplate(ast, context, functions = {}) {
658
+ let result = "";
659
+ for (const part of ast.parts) {
660
+ if (part.type === "text") {
661
+ result += part.value;
662
+ } else {
663
+ const value = evaluate(part.expression, context, functions);
664
+ result += String(value ?? "");
665
+ }
666
+ }
667
+ return result;
668
+ }
669
+ function compileTemplate(template, functions = {}) {
670
+ const ast = parseTemplate(template);
671
+ return (context) => renderTemplate(ast, context, functions);
672
+ }
673
+
674
+ // src/utils.ts
675
+ function getByPath(obj, path) {
676
+ if (obj == null || path == null) return void 0;
677
+ if (typeof obj === "object" && path in obj) return obj[path];
678
+ const parts = path.split(".");
679
+ let cur = obj;
680
+ for (const p of parts) {
681
+ if (cur == null) return void 0;
682
+ cur = cur[p];
683
+ }
684
+ return cur;
685
+ }
686
+
687
+ // src/functions/format.ts
688
+ function formatMoney(value) {
689
+ const num = Number(value);
690
+ if (isNaN(num)) return String(value);
691
+ const sign = num < 0 ? "-" : "";
692
+ const abs = Math.abs(num);
693
+ const [intPart, decPart] = abs.toFixed(2).split(".");
694
+ const formatted = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
695
+ return `${sign}${formatted}.${decPart}`;
696
+ }
697
+ function formatDate(value, fmt) {
698
+ let date;
699
+ if (value instanceof Date) {
700
+ date = value;
701
+ } else if (typeof value === "number") {
702
+ date = new Date(value);
703
+ } else if (typeof value === "string") {
704
+ date = new Date(value);
705
+ } else {
706
+ return String(value);
707
+ }
708
+ if (isNaN(date.getTime())) return String(value);
709
+ const pad3 = (n) => String(n).padStart(2, "0");
710
+ return fmt.replace("YYYY", String(date.getFullYear())).replace("MM", pad3(date.getMonth() + 1)).replace("DD", pad3(date.getDate())).replace("HH", pad3(date.getHours())).replace("mm", pad3(date.getMinutes())).replace("ss", pad3(date.getSeconds()));
711
+ }
712
+ function toUpperCaseAmount(value) {
713
+ const num = Number(value);
714
+ if (isNaN(num)) return String(value);
715
+ if (num === 0) return "\u96F6\u5143\u6574";
716
+ const digits = ["\u96F6", "\u58F9", "\u8D30", "\u53C1", "\u8086", "\u4F0D", "\u9646", "\u67D2", "\u634C", "\u7396"];
717
+ const units = ["", "\u62FE", "\u4F70", "\u4EDF"];
718
+ const bigUnits = ["", "\u4E07", "\u4EBF"];
719
+ const sign = num < 0 ? "\u8D1F" : "";
720
+ const abs = Math.abs(num);
721
+ const [intStr, decStr] = abs.toFixed(2).split(".");
722
+ const intNum = parseInt(intStr, 10);
723
+ let result = "";
724
+ if (intNum > 0) {
725
+ const intChars = intStr.split("").reverse();
726
+ let groupIdx = 0;
727
+ let lastDigit = 0;
728
+ let needZero = false;
729
+ for (let i = 0; i < intChars.length; i++) {
730
+ const d = parseInt(intChars[i], 10);
731
+ const unit = units[groupIdx % 4];
732
+ const bigUnit = bigUnits[Math.floor(groupIdx / 4)];
733
+ if (d === 0) {
734
+ if (lastDigit !== 0) needZero = true;
735
+ } else {
736
+ if (needZero) {
737
+ result = "\u96F6" + result;
738
+ needZero = false;
739
+ }
740
+ result = digits[d] + unit + result;
741
+ lastDigit = d;
742
+ }
743
+ if (groupIdx % 4 === 3 && groupIdx > 0) {
744
+ result = bigUnit + result;
745
+ }
746
+ groupIdx++;
747
+ }
748
+ result += "\u5143";
749
+ }
750
+ const jiao = parseInt(decStr[0], 10);
751
+ const fen = parseInt(decStr[1], 10);
752
+ if (jiao === 0 && fen === 0) {
753
+ result += "\u6574";
754
+ } else {
755
+ if (jiao > 0) {
756
+ result += digits[jiao] + "\u89D2";
757
+ }
758
+ if (fen > 0) {
759
+ result += digits[fen] + "\u5206";
760
+ }
761
+ }
762
+ return sign + result;
763
+ }
764
+ function ifFn(condition, trueVal, falseVal) {
765
+ return condition ? trueVal : falseVal;
766
+ }
767
+
768
+ // src/functions/aggregate.ts
769
+ function sum(rows, field) {
770
+ return rows.reduce((acc, row) => acc + (Number(getByPath(row, field)) || 0), 0);
771
+ }
772
+ function avg(rows, field) {
773
+ if (rows.length === 0) return 0;
774
+ return sum(rows, field) / rows.length;
775
+ }
776
+ function count(rows, field) {
777
+ return rows.length;
778
+ }
779
+ function min(rows, field) {
780
+ if (rows.length === 0) return 0;
781
+ return Math.min(...rows.map((row) => Number(getByPath(row, field)) || 0));
782
+ }
783
+ function max(rows, field) {
784
+ if (rows.length === 0) return 0;
785
+ return Math.max(...rows.map((row) => Number(getByPath(row, field)) || 0));
786
+ }
787
+
788
+ // src/functions/system.ts
789
+ var _pageIndex = 1;
790
+ var _totalPages = 1;
791
+ function setPageIndex(v) {
792
+ _pageIndex = v;
793
+ }
794
+ function setTotalPages(v) {
795
+ _totalPages = v;
796
+ }
797
+ var systemVars = {
798
+ pageIndex: () => _pageIndex,
799
+ totalPages: () => _totalPages,
800
+ printDate: () => {
801
+ const now = /* @__PURE__ */ new Date();
802
+ const y = now.getFullYear();
803
+ const m = String(now.getMonth() + 1).padStart(2, "0");
804
+ const d = String(now.getDate()).padStart(2, "0");
805
+ return `${y}-${m}-${d}`;
806
+ },
807
+ printTime: () => Date.now()
808
+ };
809
+
810
+ // src/functions/math.ts
811
+ function addNumbers(...values) {
812
+ return values.reduce((acc, v) => decimalAdd(acc, v), 0);
813
+ }
814
+ function subtractNumbers(...values) {
815
+ if (values.length === 0) return 0;
816
+ return values.slice(1).reduce((acc, v) => decimalSubtract(acc, v), decimalAdd(values[0], 0));
817
+ }
818
+ function multiplyNumbers(...values) {
819
+ if (values.length === 0) return 0;
820
+ return values.reduce((acc, v) => decimalMultiply(acc, v), 1);
821
+ }
822
+ function divideNumbers(a, b) {
823
+ return decimalDivide(a, b);
824
+ }
825
+ function round(value, digits) {
826
+ return roundTo(value, digits, "half-up");
827
+ }
828
+ function roundUp(value, digits) {
829
+ return roundTo(value, digits, "up");
830
+ }
831
+ function roundDown(value, digits) {
832
+ return roundTo(value, digits, "down");
833
+ }
834
+ function roundHalfEven(value, digits) {
835
+ return roundTo(value, digits, "half-even");
836
+ }
837
+
838
+ // src/render/expression-eval.ts
839
+ var RenderEngine = class {
840
+ constructor() {
841
+ this.functions = {};
842
+ }
843
+ registerFunction(name, fn) {
844
+ this.functions[name] = fn;
845
+ }
846
+ evaluate(expression, context) {
847
+ const tokens = tokenize(expression);
848
+ const ast = parse(tokens);
849
+ return evaluate(ast, context, this.functions);
850
+ }
851
+ render(template, context) {
852
+ return compileTemplate(template, this.functions)(context);
853
+ }
854
+ };
855
+ var engine = new RenderEngine();
856
+ var FORMAT_FUNCTIONS = {
857
+ MONEY: formatMoney,
858
+ DATE: formatDate,
859
+ UPPER: toUpperCaseAmount,
860
+ IF: ifFn,
861
+ CONCAT: (...args) => args.filter((v) => v != null).map(String).join(""),
862
+ IFEMPTY: (v, d) => v != null && v !== "" ? String(v) : d,
863
+ LEN: (s) => String(s).length,
864
+ // 四则运算(与 +/-/*// 运算符同一套数值语义,供不方便写运算符的场景使用)
865
+ ADD: addNumbers,
866
+ SUB: subtractNumbers,
867
+ MUL: multiplyNumbers,
868
+ DIV: divideNumbers,
869
+ // 数值修约
870
+ ROUND: round,
871
+ ROUNDUP: roundUp,
872
+ CEIL: roundUp,
873
+ ROUNDDOWN: roundDown,
874
+ FLOOR: roundDown,
875
+ ROUNDBANK: roundHalfEven
876
+ };
877
+ for (const [name, fn] of Object.entries(FORMAT_FUNCTIONS)) {
878
+ engine.registerFunction(name, fn);
879
+ }
880
+ function aggregate(fn, field, rows) {
881
+ switch (fn.toUpperCase()) {
882
+ case "SUM":
883
+ return sum(rows, field);
884
+ case "AVG":
885
+ return avg(rows, field);
886
+ case "COUNT":
887
+ return count(rows, field);
888
+ case "MIN":
889
+ return min(rows, field);
890
+ case "MAX":
891
+ return max(rows, field);
892
+ default:
893
+ return 0;
894
+ }
895
+ }
896
+ function safeEval(expr, context) {
897
+ return engine.evaluate(expr, context);
898
+ }
899
+ function evaluateTemplate(text, ctx) {
900
+ if (!text) return "";
901
+ if (!text.includes("{")) return text;
902
+ const rows = Array.isArray(ctx.rows) ? ctx.rows : [];
903
+ const processedText = text.replace(/\{([^}]+)\}/g, (_match, inner) => {
904
+ let expr = inner.trim();
905
+ expr = expr.replace(/\b(SUM|AVG|COUNT|MIN|MAX)\(([^)]+)\)/g, (_m, fn, arg) => {
906
+ const field = arg.trim().replace(/^['"]|['"]$/g, "");
907
+ return String(aggregate(fn, field, rows));
908
+ });
909
+ return `{${expr}}`;
910
+ });
911
+ try {
912
+ return engine.render(processedText, ctx);
913
+ } catch {
914
+ return text;
915
+ }
916
+ }
917
+
918
+ // src/render/data-binder.ts
919
+ function bindData(template, printData, baseUrl, fontBaseUrl) {
920
+ const data = { ...resolveSystemVariables(), ...printData ?? {} };
921
+ const bound = JSON.parse(JSON.stringify(template));
922
+ if (bound.header?.elements) {
923
+ bound.header.elements = bound.header.elements.map((el) => bindElement(el, data, baseUrl));
924
+ }
925
+ if (bound.footer?.elements) {
926
+ bound.footer.elements = bound.footer.elements.map((el) => bindElement(el, data, baseUrl));
927
+ }
928
+ if (bound.firstPageOverlay?.elements) {
929
+ bound.firstPageOverlay.elements = bound.firstPageOverlay.elements.map((el) => bindElement(el, data, baseUrl));
930
+ }
931
+ bound.elements = bound.elements.map((el) => bindElement(el, data, baseUrl));
932
+ const fontBase = fontBaseUrl ?? baseUrl;
933
+ if (fontBase && bound.fonts?.length) {
934
+ const prefix = fontBase.replace(/\/+$/, "");
935
+ bound.fonts = bound.fonts.map((font) => ({
936
+ ...font,
937
+ files: (font.files ?? []).map((file) => ({
938
+ ...file,
939
+ // 绝对 URL(含协议相对 //)原样使用
940
+ url: file.url?.startsWith("/") && !file.url.startsWith("//") ? prefix + file.url : file.url
941
+ }))
942
+ }));
943
+ }
944
+ return bound;
945
+ }
946
+ function bindElement(el, data, baseUrl) {
947
+ const cloned = { ...el, options: { ...el.options } };
948
+ if (typeof cloned.options.formatter === "string") {
949
+ const raw = cloned.options.formatter;
950
+ if (referencesPageNumbers(raw)) {
951
+ cloned.options.rawFormatter = raw;
952
+ } else {
953
+ cloned.options.formatter = evaluateTemplate(raw, data);
954
+ }
955
+ }
956
+ const isImage = cloned.type === "image" || cloned.printElementType?.type === "image";
957
+ if (isImage && typeof cloned.options.src === "string") {
958
+ let src = cloned.options.src;
959
+ if (src.includes("{")) {
960
+ src = evaluateTemplate(src, data);
961
+ }
962
+ if (src && baseUrl && src.startsWith("/")) {
963
+ src = baseUrl.replace(/\/+$/, "") + src;
964
+ }
965
+ cloned.options.src = src;
966
+ }
967
+ if (cloned.type === "table" || cloned.printElementType?.type === "table") {
968
+ bindTableData(cloned, data);
969
+ }
970
+ return cloned;
971
+ }
972
+ function resolveListSource(opts, data) {
973
+ const explicitKey = opts.dataSource ?? opts.fields?.[0]?.dataSource ?? opts.fields?.[0]?.field;
974
+ if (explicitKey && Array.isArray(data[explicitKey])) {
975
+ return { list: data[explicitKey], key: explicitKey };
976
+ }
977
+ for (const [k, v] of Object.entries(data)) {
978
+ if (Array.isArray(v)) return { list: v, key: k };
979
+ }
980
+ return { list: [], key: explicitKey };
981
+ }
982
+ function bindTableData(el, data) {
983
+ const opts = el.options;
984
+ const rows = opts.tableRows;
985
+ if (!Array.isArray(rows) || rows.length === 0) return;
986
+ const mode = opts.tableMode ?? "dynamic";
987
+ const { list, key } = mode === "dynamic" ? resolveListSource(opts, data) : { list: [], key: void 0 };
988
+ const itemCtx = (item) => key ? { ...data, ...item, [key]: item } : { ...data, ...item };
989
+ const summaryRows = key ? list.map((item) => ({ ...item, [key]: item })) : list;
990
+ const renderRows = [];
991
+ const dataRowCtx = [];
992
+ const subtotalTemplates = [];
993
+ const summaryRenderRows = [];
994
+ let dataStartIdx = -1;
995
+ for (const row of rows) {
996
+ if (mode === "dynamic" && row.type === "data") {
997
+ for (const item of list) {
998
+ if (dataStartIdx < 0) dataStartIdx = renderRows.length;
999
+ const ctx = itemCtx(item);
1000
+ renderRows.push(makeRenderRow(row, (cell) => {
1001
+ return resolveCellText(cell, ctx);
1002
+ }));
1003
+ dataRowCtx.push(ctx);
1004
+ }
1005
+ continue;
1006
+ }
1007
+ if (mode === "dynamic" && row.type === "subtotal") {
1008
+ const tpl = makeRenderRow(row, (cell) => resolveCellText(cell, { rows: summaryRows, ...data }), true);
1009
+ subtotalTemplates.push(tpl);
1010
+ renderRows.push(tpl);
1011
+ continue;
1012
+ }
1013
+ if (mode === "dynamic" && row.type === "summary") {
1014
+ const summaryRow = makeRenderRow(row, (cell) => resolveCellText(cell, { rows: summaryRows, ...data }));
1015
+ summaryRenderRows.push(summaryRow);
1016
+ renderRows.push(summaryRow);
1017
+ continue;
1018
+ }
1019
+ renderRows.push(makeRenderRow(row, (cell) => resolveCellText(cell, data)));
1020
+ }
1021
+ opts._renderRows = renderRows;
1022
+ opts._repeatHeaderCount = countRepeatHeader(rows);
1023
+ opts._dataRowCtx = dataRowCtx;
1024
+ opts._dataStartIdx = dataStartIdx < 0 ? renderRows.length : dataStartIdx;
1025
+ opts._subtotalTemplates = subtotalTemplates;
1026
+ opts._summaryRows = summaryRenderRows;
1027
+ opts._mainData = data;
1028
+ }
1029
+ function resolveCellText(cell, ctx) {
1030
+ const formatter = cell.formatter;
1031
+ if (!formatter) return "";
1032
+ if (referencesPageNumbers(formatter)) return formatter;
1033
+ return evaluateTemplate(formatter, ctx);
1034
+ }
1035
+ function makeRenderRow(row, resolve, keepRaw = false) {
1036
+ return {
1037
+ type: row.type,
1038
+ height: row.height ?? 8,
1039
+ cells: row.cells.map((cell) => ({
1040
+ content: cell.merged ? "" : resolve(cell),
1041
+ // 小计行(keepRaw)与引用页码的单元格都保留原始表达式:前者按当页数据行重算,后者按当页页码重算
1042
+ ...(keepRaw || referencesPageNumbers(cell.formatter)) && !cell.merged ? { rawFormatter: cell.formatter ?? "" } : {},
1043
+ cellType: cell.cellType,
1044
+ barcodeType: cell.barcodeType,
1045
+ qrCodeLevel: cell.qrCodeLevel,
1046
+ showBarcodeText: cell.showBarcodeText,
1047
+ printerDpi: cell.printerDpi,
1048
+ fit: cell.fit,
1049
+ maxWidth: cell.maxWidth,
1050
+ maxHeight: cell.maxHeight,
1051
+ rowspan: cell.rowspan ?? 1,
1052
+ colspan: cell.colspan ?? 1,
1053
+ merged: cell.merged === true,
1054
+ align: cell.align,
1055
+ valign: cell.valign,
1056
+ fontSize: cell.fontSize,
1057
+ fontFamily: cell.fontFamily,
1058
+ fontWeight: cell.fontWeight,
1059
+ color: cell.color,
1060
+ backgroundColor: cell.backgroundColor,
1061
+ borders: cell.borders,
1062
+ padding: cell.padding,
1063
+ wordWrap: cell.wordWrap,
1064
+ textFit: cell.textFit,
1065
+ shrinkMinFontSize: cell.shrinkMinFontSize
1066
+ }))
1067
+ };
1068
+ }
1069
+ function countRepeatHeader(rows) {
1070
+ const headerCount = countLeadingHeaderRows(rows);
1071
+ if (headerCount === 0) return 0;
1072
+ const enabled = rows.slice(0, headerCount).some((row) => row.repeatOnPage === true);
1073
+ if (!enabled) return 0;
1074
+ return alignToRowspanBoundary(rows, headerCount);
1075
+ }
1076
+ function countLeadingHeaderRows(rows) {
1077
+ let n = 0;
1078
+ for (const row of rows) {
1079
+ if (row?.type !== "header") break;
1080
+ n++;
1081
+ }
1082
+ return n;
1083
+ }
1084
+ function isRowspanComplete(rows, n) {
1085
+ for (let r = 0; r < n; r++) {
1086
+ for (const cell of rows[r]?.cells ?? []) {
1087
+ if (cell?.merged) continue;
1088
+ if (r + (cell?.rowspan ?? 1) > n) return false;
1089
+ }
1090
+ }
1091
+ return true;
1092
+ }
1093
+ function alignToRowspanBoundary(rows, max2) {
1094
+ for (let n = max2; n >= 1; n--) {
1095
+ if (isRowspanComplete(rows, n)) return n;
1096
+ }
1097
+ return 1;
1098
+ }
1099
+ var pad2 = (n) => String(n).padStart(2, "0");
1100
+ function resolveSystemVariables(now = /* @__PURE__ */ new Date(), page = {}) {
1101
+ return {
1102
+ printDate: `${now.getFullYear()}-${pad2(now.getMonth() + 1)}-${pad2(now.getDate())}`,
1103
+ printTime: `${pad2(now.getHours())}:${pad2(now.getMinutes())}:${pad2(now.getSeconds())}`,
1104
+ pageIndex: page.pageIndex ?? 1,
1105
+ totalPages: page.totalPages ?? 1
1106
+ };
1107
+ }
1108
+ function referencesPageNumbers(expr) {
1109
+ return typeof expr === "string" && /\b(pageIndex|totalPages)\b/.test(expr);
1110
+ }
1111
+ function injectSystemVariables(html, now = /* @__PURE__ */ new Date()) {
1112
+ const { printDate, printTime } = resolveSystemVariables(now);
1113
+ return html.replace(/\{printDate\}/g, printDate).replace(/\{printTime\}/g, printTime);
1114
+ }
1115
+
1116
+ // src/print/fonts.ts
1117
+ function buildFontFaceCss(fonts) {
1118
+ const blocks = [];
1119
+ for (const font of fonts ?? []) {
1120
+ const family = font?.family?.trim();
1121
+ if (!family) continue;
1122
+ const quoted = `"${family.replace(/"/g, "")}"`;
1123
+ for (const file of font.files ?? []) {
1124
+ const url = file?.url?.trim();
1125
+ if (!url) continue;
1126
+ const weight = Number.isFinite(file.weight) ? Number(file.weight) : 400;
1127
+ const style = file.style === "italic" ? "italic" : "normal";
1128
+ blocks.push(
1129
+ `@font-face{font-family:${quoted};src:url("${url}")${fontFormatHint(url)};font-weight:${weight};font-style:${style};font-display:block;}`
1130
+ );
1131
+ }
1132
+ }
1133
+ return blocks.length ? `${blocks.join("")}
1134
+ ` : "";
1135
+ }
1136
+ function fontFormatHint(url) {
1137
+ const path = url.split(/[?#]/)[0].toLowerCase();
1138
+ if (path.endsWith(".woff2")) return ' format("woff2")';
1139
+ if (path.endsWith(".woff")) return ' format("woff")';
1140
+ if (path.endsWith(".ttf")) return ' format("truetype")';
1141
+ if (path.endsWith(".otf")) return ' format("opentype")';
1142
+ return "";
1143
+ }
1144
+ var FALLBACK_FONT_STACK = [
1145
+ '"Microsoft YaHei"',
1146
+ '"PingFang SC"',
1147
+ '"Helvetica Neue"',
1148
+ "Arial",
1149
+ "sans-serif"
1150
+ ];
1151
+ function escapeInlineStyleValue(cssValue) {
1152
+ return cssValue.replace(/&/g, "&amp;").replace(/"/g, "&quot;");
1153
+ }
1154
+ function toFontFamilyStack(family) {
1155
+ const name = family?.trim();
1156
+ if (!name) return FALLBACK_FONT_STACK.join(", ");
1157
+ const head = name.includes(",") ? name : `"${name.replace(/"/g, "")}"`;
1158
+ return [head, ...FALLBACK_FONT_STACK].join(", ");
1159
+ }
1160
+
1161
+ // src/render/types.ts
1162
+ var PAPER_DIMENSIONS = {
1163
+ A4: { width: 210, height: 297 },
1164
+ A3: { width: 297, height: 420 },
1165
+ A5: { width: 148, height: 210 },
1166
+ Letter: { width: 216, height: 279 },
1167
+ Legal: { width: 216, height: 356 },
1168
+ // 针式打印纸(241 系列等分):11 英寸整张 279.4mm 按等分取整
1169
+ DOT_FULL: { width: 241, height: 279.4 },
1170
+ DOT_HALF: { width: 241, height: 139.7 },
1171
+ DOT_THIRD: { width: 241, height: 93.1 },
1172
+ // 标签纸
1173
+ LABEL_80X60: { width: 80, height: 60 },
1174
+ LABEL_60X40: { width: 60, height: 40 },
1175
+ LABEL_40X30: { width: 40, height: 30 },
1176
+ // 小票纸(热敏卷纸):高度仅为设计画布高度,出纸按内容推导
1177
+ THERMAL_57: { width: 57, height: 297 },
1178
+ THERMAL_80: { width: 80, height: 297 },
1179
+ THERMAL_110: { width: 110, height: 297 },
1180
+ CUSTOM: { width: 210, height: 297 },
1181
+ // 连续纸:默认 80mm 热敏;高度仅为设计画布高度,出纸按内容推导
1182
+ CONTINUOUS: { width: 80, height: 297 }
1183
+ };
1184
+ var CONTINUOUS_PAPER_SIZES = /* @__PURE__ */ new Set([
1185
+ "CONTINUOUS",
1186
+ "THERMAL_57",
1187
+ "THERMAL_80",
1188
+ "THERMAL_110"
1189
+ ]);
1190
+ function isContinuousPaperSize(paperSize) {
1191
+ return CONTINUOUS_PAPER_SIZES.has(paperSize);
1192
+ }
1193
+ function getPaperDimensions(template) {
1194
+ const continuous = isContinuousPaperSize(template.paperSize);
1195
+ const base = template.paperSize === "CUSTOM" || continuous ? {
1196
+ width: template.customWidth ?? PAPER_DIMENSIONS[template.paperSize].width,
1197
+ height: template.customHeight ?? PAPER_DIMENSIONS[template.paperSize].height
1198
+ } : PAPER_DIMENSIONS[template.paperSize];
1199
+ if (template.orientation === "landscape" && !continuous) {
1200
+ return { width: base.height, height: base.width };
1201
+ }
1202
+ return { ...base };
1203
+ }
1204
+ function getOutputPaperDimensions(template) {
1205
+ const continuous = isContinuousPaperSize(template.paperSize);
1206
+ if (continuous || template.tiling?.enabled === true) {
1207
+ return getPaperDimensions(template);
1208
+ }
1209
+ const design = getPaperDimensions(template);
1210
+ const swap = template.outputRotation === 90 || template.outputRotation === 270;
1211
+ return swap ? { width: design.height, height: design.width } : design;
1212
+ }
1213
+ function getOutputRotationAngle(template) {
1214
+ const continuous = isContinuousPaperSize(template.paperSize);
1215
+ if (continuous || template.tiling?.enabled === true) return 0;
1216
+ return template.outputRotation ?? 0;
1217
+ }
1218
+ function isContinuousPaper(template) {
1219
+ return isContinuousPaperSize(template.paperSize);
1220
+ }
1221
+
1222
+ // src/render/css-builder.ts
1223
+ function mm(value) {
1224
+ return `${value}mm`;
1225
+ }
1226
+ function resolveOuterPaper(template, pageHeightMm) {
1227
+ const out = getOutputPaperDimensions(template);
1228
+ return pageHeightMm && pageHeightMm > 0 && isContinuousPaper(template) ? { width: out.width, height: pageHeightMm } : out;
1229
+ }
1230
+ function resolveAreaPaper(template, pageHeightMm) {
1231
+ const design = getPaperDimensions(template);
1232
+ return pageHeightMm && pageHeightMm > 0 && isContinuousPaper(template) ? { width: design.width, height: pageHeightMm } : design;
1233
+ }
1234
+ function screenBlock() {
1235
+ return `
1236
+ /* \u2500\u2500 \u5168\u5C40\u91CD\u7F6E \u2500\u2500 */
1237
+ * { margin: 0; padding: 0; box-sizing: border-box; }
1238
+
1239
+ /* \u6253\u5370\u5FC5\u987B\u4FDD\u7559\u5143\u7D20\u80CC\u666F\u8272\uFF08Chromium \u9ED8\u8BA4\u5254\u9664\u80CC\u666F\uFF0C\u9700\u663E\u5F0F\u58F0\u660E\uFF09 */
1240
+ * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
1241
+
1242
+ body { font-family: ${FALLBACK_FONT_STACK.join(", ")}; }
1243
+
1244
+ /* \u2500\u2500 \u5C4F\u5E55\u9884\u89C8\uFF1A\u7070\u5E95 + \u7EB8\u5F20\u9634\u5F71/\u9875\u95F4\u8DDD\uFF1B\u6253\u5370\u65F6\u53BB\u9664 \u2500\u2500 */
1245
+ @media screen {
1246
+ body { background: #e9ebee; }
1247
+ .print-page { margin: 12px auto; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.18); }
1248
+ }
1249
+ @media print {
1250
+ body { background: #fff; }
1251
+ .print-page { margin: 0; box-shadow: none; }
1252
+ }
1253
+ `;
1254
+ }
1255
+ function watermarkBlock() {
1256
+ return `
1257
+ /* \u2500\u2500 \u6C34\u5370\u5C42\uFF1A\u8986\u76D6\u6574\u9875\u3001\u4F4D\u4E8E\u6240\u6709\u5185\u5BB9\u4E4B\u4E0B\uFF08\u663E\u5F0F\u77E2\u91CF\u74E6\u7247\uFF0C\u7981\u6B62\u7528 CSS \u5E73\u94FA\u80CC\u666F\uFF1A
1258
+ Chromium \u4F1A\u628A\u5B83\u7F16\u8BD1\u6210 PDF \u5E73\u94FA\u56FE\u6848\uFF0C\u51FA\u7EB8\u94FE\u8DEF\u7684 RIP \u4F1A\u5FFD\u7565\u56FE\u6848\u77E9\u9635\u5BFC\u81F4\u6C34\u5370\u653E\u5927/\u9519\u4F4D\uFF09 \u2500\u2500 */
1259
+ .watermark-layer {
1260
+ position: absolute;
1261
+ top: 0;
1262
+ left: 0;
1263
+ width: 100%;
1264
+ height: 100%;
1265
+ z-index: 0;
1266
+ pointer-events: none;
1267
+ overflow: hidden;
1268
+ }
1269
+ /* \u5355\u5757\u6C34\u5370\u74E6\u7247\uFF1A\u4F4D\u7F6E/\u5C3A\u5BF8\u7531 core \u7684\u74E6\u7247\u7F51\u683C\u7ED9\u51FA\uFF08mm\uFF09 */
1270
+ .watermark-tile {
1271
+ position: absolute;
1272
+ pointer-events: none;
1273
+ }
1274
+ `;
1275
+ }
1276
+ function elementBlock() {
1277
+ return `
1278
+ /* \u2500\u2500 \u5143\u7D20\u901A\u7528\u5B9A\u4F4D \u2500\u2500 */
1279
+ .print-element {
1280
+ position: absolute;
1281
+ overflow: hidden;
1282
+ }
1283
+
1284
+ /* \u2500\u2500 \u8868\u683C\u6837\u5F0F \u2500\u2500 */
1285
+ .print-table {
1286
+ width: 100%;
1287
+ border-collapse: collapse;
1288
+ table-layout: fixed;
1289
+ }
1290
+ .print-table th,
1291
+ .print-table td {
1292
+ border: 1px solid #ccc;
1293
+ padding: 2mm 3mm;
1294
+ word-break: break-all;
1295
+ overflow: hidden;
1296
+ }
1297
+ .print-table thead th {
1298
+ background-color: #f5f5f5;
1299
+ font-weight: bold;
1300
+ }
1301
+
1302
+ /* \u2500\u2500 \u6D4B\u91CF\u6A21\u5F0F \u2500\u2500 */
1303
+ .measure-mode .print-page {
1304
+ height: auto;
1305
+ min-height: auto;
1306
+ overflow: visible;
1307
+ }
1308
+ .measure-mode .content-area {
1309
+ height: auto;
1310
+ overflow: visible;
1311
+ }
1312
+ `;
1313
+ }
1314
+ function pageRuleBlock(paper) {
1315
+ return `
1316
+ /* \u2500\u2500 \u6253\u5370\u7EB8\u5F20\uFF1A\u6D4F\u89C8\u5668\u539F\u751F\u6253\u5370\u6309\u6B64\u5C3A\u5BF8\u5206\u9875\uFF08Playwright page.pdf \u4EE5\u663E\u5F0F\u5BBD\u9AD8\u4E3A\u51C6\uFF0C\u65E0\u526F\u4F5C\u7528\uFF09 \u2500\u2500 */
1317
+ @page { size: ${mm(paper.width)} ${mm(paper.height)}; margin: 0; }
1318
+ `;
1319
+ }
1320
+ function buildRotorCss(area, margins, angle) {
1321
+ const base = `
1322
+ /* \u2500\u2500 \u51FA\u7EB8\u8F6C\u5B50\uFF1A\u8BBE\u8BA1\u7A3F\u6574\u9875\u65CB\u8F6C ${angle}\xB0 \u586B\u5165\u51FA\u7EB8\u7EB8\u5F20 \u2500\u2500 */
1323
+ .print-page-rotor {
1324
+ position: absolute;
1325
+ top: 0;
1326
+ left: 0;
1327
+ width: ${mm(area.width)};
1328
+ height: ${mm(area.height)};
1329
+ padding: ${mm(margins.top)} ${mm(margins.right)} ${mm(margins.bottom)} ${mm(margins.left)};
1330
+ box-sizing: border-box;
1331
+ transform-origin: 0 0;
1332
+ }`;
1333
+ if (angle === 0) return base.trim();
1334
+ const transforms = {
1335
+ 90: `translate(${mm(area.height)}, 0mm) rotate(90deg)`,
1336
+ 180: `translate(${mm(area.width)}, ${mm(area.height)}) rotate(180deg)`,
1337
+ 270: `translate(0mm, ${mm(area.width)}) rotate(270deg)`
1338
+ };
1339
+ const tf = transforms[angle];
1340
+ return [
1341
+ base.trim(),
1342
+ `.print-page-rotor-${angle} {
1343
+ transform: ${tf};
1344
+ }`
1345
+ ].join("\n");
1346
+ }
1347
+ function pageGeometryBlock(template, outerPaper, pageSel, rotated) {
1348
+ const { top: mt, right: mr, bottom: mb, left: ml } = template.margins;
1349
+ const padding = rotated ? "0" : `${mm(mt)} ${mm(mr)} ${mm(mb)} ${mm(ml)}`;
1350
+ return `
1351
+ /* \u2500\u2500 \u7EB8\u5F20\u9875\u9762 \u2500\u2500 */
1352
+ ${pageSel} {
1353
+ width: ${mm(outerPaper.width)};
1354
+ min-height: ${mm(outerPaper.height)};
1355
+ background: ${template.pageBackground ?? "#fff"};
1356
+ padding: ${padding};
1357
+ position: relative;
1358
+ page-break-after: always;
1359
+ overflow: hidden;
1360
+ }
1361
+ .print-page:last-child {
1362
+ page-break-after: auto;
1363
+ }
1364
+ `;
1365
+ }
1366
+ function areaGeometryBlock(template, paper, desc) {
1367
+ const { right: mr, left: ml } = template.margins;
1368
+ const { bottom: mb } = template.margins;
1369
+ const headerH = template.header?.height ?? 0;
1370
+ const footerH = template.footer?.height ?? 0;
1371
+ const overlayH = template.firstPageOverlay?.height ?? 0;
1372
+ const contentWidth = paper.width - ml - mr;
1373
+ return `
1374
+ /* \u2500\u2500 \u9875\u7709 \u2500\u2500 */
1375
+ ${desc}.page-header {
1376
+ width: ${mm(contentWidth)};
1377
+ height: ${mm(headerH)};
1378
+ position: relative;
1379
+ }
1380
+
1381
+ /* \u2500\u2500 \u9875\u811A\uFF1A\u7EDD\u5BF9\u5B9A\u4F4D\u56FA\u5B9A\u5728\u9875\u9762\u5E95\u90E8\uFF08\u5185\u5BB9\u4E0D\u8DB3\u65F6\u4E0D\u968F\u6587\u6863\u6D41\u4E0A\u6D6E\uFF09 \u2500\u2500
1382
+ \u7528\u663E\u5F0F top \u5B9A\u4F4D\u5230\u300C\u7EB8\u9AD8 - \u4E0B\u8FB9\u8DDD - \u9875\u811A\u9AD8\u300D\uFF0C\u4FDD\u8BC1\u4E0B\u8FB9\u8DDD\u751F\u6548\u3001
1383
+ \u4E0E\u8BBE\u8BA1\u5668 CanvasPaper \u7684\u4E09\u533A\u51E0\u4F55\u4E00\u81F4\u3002 */
1384
+ ${desc}.page-footer {
1385
+ width: ${mm(contentWidth)};
1386
+ height: ${mm(footerH)};
1387
+ position: absolute;
1388
+ top: ${mm(paper.height - mb - footerH)};
1389
+ left: 0;
1390
+ }
1391
+
1392
+ /* \u2500\u2500 \u5185\u5BB9\u533A \u2500\u2500 */
1393
+ ${desc}.content-area {
1394
+ width: ${mm(contentWidth)};
1395
+ position: relative;
1396
+ overflow: visible;
1397
+ }
1398
+
1399
+ /* \u2500\u2500 \u9996\u9875\u53E0\u52A0\u533A\u57DF \u2500\u2500 */
1400
+ ${desc}.first-page-overlay {
1401
+ width: ${mm(contentWidth)};
1402
+ height: ${mm(overlayH)};
1403
+ position: relative;
1404
+ }
1405
+ `;
1406
+ }
1407
+ function buildBasePageCss() {
1408
+ return [screenBlock(), watermarkBlock(), elementBlock()].join("").trim();
1409
+ }
1410
+ function buildPageRuleCss(template, pageHeightMm) {
1411
+ return pageRuleBlock(resolveOuterPaper(template, pageHeightMm)).trim();
1412
+ }
1413
+ function buildPageGeometryCss(template, scope, pageHeightMm) {
1414
+ const angle = getOutputRotationAngle(template);
1415
+ const rotated = angle !== 0;
1416
+ const outer = resolveOuterPaper(template, pageHeightMm);
1417
+ const area = resolveAreaPaper(template, pageHeightMm);
1418
+ const pageSel = scope ? `${scope}.print-page` : ".print-page";
1419
+ const desc = scope ? `${scope} ` : "";
1420
+ return [
1421
+ pageGeometryBlock(template, outer, pageSel, rotated),
1422
+ areaGeometryBlock(template, area, desc),
1423
+ ...rotated ? [buildRotorCss(area, template.margins, angle)] : []
1424
+ ].join("").trim();
1425
+ }
1426
+ function buildPageCss(template, pageHeightMm) {
1427
+ const angle = getOutputRotationAngle(template);
1428
+ const rotated = angle !== 0;
1429
+ const outer = resolveOuterPaper(template, pageHeightMm);
1430
+ const area = resolveAreaPaper(template, pageHeightMm);
1431
+ return [
1432
+ pageRuleBlock(outer),
1433
+ screenBlock(),
1434
+ pageGeometryBlock(template, outer, ".print-page", rotated),
1435
+ watermarkBlock(),
1436
+ areaGeometryBlock(template, area, ""),
1437
+ elementBlock(),
1438
+ ...rotated ? [buildRotorCss(area, template.margins, angle)] : []
1439
+ ].join("").trim();
1440
+ }
1441
+ var COPY_BREAK_CSS = ".print-copy:not(:last-child){break-after:page;page-break-after:always;}";
1442
+ function buildBatchPageCss(template, copies) {
1443
+ if (!isContinuousPaper(template)) {
1444
+ return `${buildPageCss(template)}
1445
+ ${COPY_BREAK_CSS}`;
1446
+ }
1447
+ const { bottom: mb } = template.margins;
1448
+ const footerH = template.footer?.height ?? 0;
1449
+ const width = getPaperDimensions(template).width;
1450
+ const base = buildPageCss(template, copies[0]?.heightMm);
1451
+ const scoped = copies.map((copy, i) => {
1452
+ const h = copy.heightMm;
1453
+ const rules = [
1454
+ `@page copy${i} { size: ${mm(width)} ${h ? mm(h) : "auto"}; margin: 0; }`,
1455
+ `.print-copy-${i} { page: copy${i}; }`
1456
+ ];
1457
+ if (h) {
1458
+ rules.push(`.print-copy-${i} .print-page { min-height: ${mm(h)}; }`);
1459
+ rules.push(`.print-copy-${i} .page-footer { top: ${mm(h - mb - footerH)}; }`);
1460
+ }
1461
+ return rules.join("\n");
1462
+ }).join("\n");
1463
+ return `${base}
1464
+ ${scoped}
1465
+ ${COPY_BREAK_CSS}`;
1466
+ }
1467
+ function buildSheetPageCss(layout) {
1468
+ return `
1469
+ /* \u2500\u2500 \u62FC\u7248\u7EB8\u5F20\uFF1A@page \u5C3A\u5BF8 = \u76EE\u6807\u7EB8\u3002\u670D\u52A1\u7AEF/\u5BA2\u6237\u7AEF\u94FE\u8DEF\u4EE5 paperMm \u663E\u5F0F\u5B9A\u5C3A\u5BF8\u3001\u4E0D\u770B\u8FD9\u91CC\uFF0C
1470
+ \u4F46\u6D4F\u89C8\u5668\u94FE\u8DEF\u53EA\u770B @page\uFF0C\u6545\u8FD9\u6761\u662F\u786C\u9700\u6C42 \u2500\u2500 */
1471
+ @page { size: ${mm(layout.sheet.width)} ${mm(layout.sheet.height)}; margin: 0; }
1472
+
1473
+ /* \u2500\u2500 \u4E00\u5F20\u76EE\u6807\u7EB8 \u2500\u2500 */
1474
+ .print-sheet {
1475
+ width: ${mm(layout.sheet.width)};
1476
+ height: ${mm(layout.sheet.height)};
1477
+ position: relative;
1478
+ overflow: hidden;
1479
+ break-after: page;
1480
+ page-break-after: always;
1481
+ }
1482
+ .print-sheet:last-child { break-after: auto; page-break-after: auto; }
1483
+
1484
+ /* \u2500\u2500 \u4E00\u683C\uFF1A\u7EDD\u5BF9\u5B9A\u4F4D\uFF0C\u4F4D\u7F6E\u7531 tilePosition() \u4EE5\u884C\u5185 style \u7ED9\u51FA \u2500\u2500 */
1485
+ .print-tile {
1486
+ position: absolute;
1487
+ width: ${mm(layout.tile.width)};
1488
+ height: ${mm(layout.tile.height)};
1489
+ overflow: hidden;
1490
+ }
1491
+ /* \u9632\u5FA1\u6027\u58F0\u660E\uFF1A\u7EDD\u5BF9\u5B9A\u4F4D + overflow:hidden \u5BB9\u5668\u5185\u7684\u540E\u4EE3\u4E0D\u4EA7\u751F\u5206\u9875\u70B9\uFF0C\u5F53\u524D\u5E03\u5C40\u4E0B\u65E0\u5B9E\u9645\u4F5C\u7528\uFF1B
1492
+ \u82E5\u5C06\u6765\u6539\u7528 flex/grid \u5E03\u5C40\uFF0C\u683C\u5185\u6574\u9875\u4F1A\u91CD\u65B0\u53C2\u4E0E\u5206\u9875\uFF0C\u6545\u4FDD\u7559 */
1493
+ .print-tile > .print-page { break-after: auto; page-break-after: auto; }
1494
+
1495
+ @media screen {
1496
+ .print-sheet { margin: 12px auto; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.18); }
1497
+ /* \u5FC5\u9700\uFF1A\u62B5\u6D88\u6807\u7B7E CSS \u7684 @media screen{.print-page{margin:12px auto}}\uFF0C
1498
+ \u5426\u5219\u8BBE\u8BA1\u5668\u9884\u89C8\u9519\u4F4D 3.17mm\u3001\u4E0E\u51FA\u7EB8\u4E0D\u4E00\u81F4 */
1499
+ .print-tile > .print-page { margin: 0; box-shadow: none; }
1500
+ }
1501
+ `;
1502
+ }
1503
+ function elementPositionStyle(left, top, width, height, zIndex) {
1504
+ const parts = [
1505
+ `position:absolute`,
1506
+ `left:${mm(left)}`,
1507
+ `top:${mm(top)}`,
1508
+ `width:${mm(width)}`
1509
+ ];
1510
+ if (height !== void 0 && height > 0) {
1511
+ parts.push(`height:${mm(height)}`);
1512
+ }
1513
+ if (zIndex !== void 0) {
1514
+ parts.push(`z-index:${zIndex}`);
1515
+ }
1516
+ return parts.join(";") + ";";
1517
+ }
1518
+
1519
+ // src/designer/utils/units.ts
1520
+ function ptToMm(pt) {
1521
+ return pt / 2.83464566929;
1522
+ }
1523
+ function pxToMm(px) {
1524
+ return px * (25.4 / 96);
1525
+ }
1526
+ function mmToPx(mm2) {
1527
+ return mm2 * (96 / 25.4);
1528
+ }
1529
+
1530
+ // src/render/text-fit.ts
1531
+ var DEFAULT_SHRINK_MIN_FONT_SIZE_PT = 6;
1532
+ var MIN_SHRINK_FONT_SIZE_PT = 1;
1533
+ var VALID_FITS = ["clip", "shrink", "autoHeight"];
1534
+ function normalizeFit(value) {
1535
+ return typeof value === "string" && VALID_FITS.includes(value) ? value : void 0;
1536
+ }
1537
+ var ELEMENT_DEFAULT_FIT = {
1538
+ text: "clip",
1539
+ longText: "autoHeight"
1540
+ };
1541
+ function resolveElementTextFit(type, opts) {
1542
+ return normalizeFit(opts?.textFit) ?? ELEMENT_DEFAULT_FIT[type] ?? "clip";
1543
+ }
1544
+ function resolveCellTextFit(cell) {
1545
+ return normalizeFit(cell?.textFit) ?? (cell?.wordWrap === false ? "clip" : "autoHeight");
1546
+ }
1547
+ function resolveShrinkMinFontSize(pt) {
1548
+ if (typeof pt !== "number" || !Number.isFinite(pt) || pt <= 0) return DEFAULT_SHRINK_MIN_FONT_SIZE_PT;
1549
+ return Math.max(pt, MIN_SHRINK_FONT_SIZE_PT);
1550
+ }
1551
+ function roundFontSize(pt) {
1552
+ return Math.round(pt * 100) / 100;
1553
+ }
1554
+ function floorFontSize(pt) {
1555
+ return Math.floor(pt * 100) / 100;
1556
+ }
1557
+ function cellFitKey(elementId, kind, rowIndex, colIndex) {
1558
+ return `${elementId}#${kind}#${rowIndex}:${colIndex}`;
1559
+ }
1560
+ function parseCellFitKey(key) {
1561
+ const first = key.indexOf("#");
1562
+ if (first < 0) return void 0;
1563
+ const last = key.lastIndexOf("#");
1564
+ const kind = key.slice(first + 1, last);
1565
+ if (kind !== "b" && kind !== "st" && kind !== "sm") return void 0;
1566
+ const [rowIndex, colIndex] = key.slice(last + 1).split(":").map(Number);
1567
+ if (!Number.isFinite(rowIndex) || !Number.isFinite(colIndex)) return void 0;
1568
+ return { elementId: key.slice(0, first), kind, rowIndex, colIndex };
1569
+ }
1570
+ function cellFitWidthMm(colWidths, colIndex, cell, defaultPadding = 1) {
1571
+ const span = Math.max(cell?.colspan ?? 1, 1);
1572
+ let width = 0;
1573
+ for (let i = 0; i < span; i++) {
1574
+ width += colWidths[colIndex + i] ?? 0;
1575
+ }
1576
+ if (width <= 0) return 0;
1577
+ const padding = cell?.padding ?? defaultPadding;
1578
+ const borderMm = (ptToMm(cell?.borders?.left?.width ?? 0) + ptToMm(cell?.borders?.right?.width ?? 0)) / 2;
1579
+ return Math.max(width - padding * 2 - borderMm, 0.5);
1580
+ }
1581
+ function cellFitCapMm(rows, rowIndex, cell, defaultPadding = 1) {
1582
+ const span = Math.max(cell?.rowspan ?? 1, 1);
1583
+ let height = 0;
1584
+ for (let i = 0; i < span; i++) {
1585
+ height += rows[rowIndex + i]?.height ?? 8;
1586
+ }
1587
+ const padding = cell?.padding ?? defaultPadding;
1588
+ const borderMm = (ptToMm(cell?.borders?.top?.width ?? 0) + ptToMm(cell?.borders?.bottom?.width ?? 0)) / 2;
1589
+ return Math.max(height - padding * 2 - borderMm, 0.5);
1590
+ }
1591
+
1592
+ // src/render/watermark.ts
1593
+ var PX_PER_MM = 96 / 25.4;
1594
+ var MM_PER_PX = 25.4 / 96;
1595
+ var WATERMARK_DEFAULTS = {
1596
+ color: "#cccccc",
1597
+ opacity: 0.15,
1598
+ rotate: -30,
1599
+ /** 瓦片默认尺寸(px):决定平铺疏密,默认 260×180 */
1600
+ tileWidth: 260,
1601
+ tileHeight: 180,
1602
+ /** 瓦片下限(px),防止文字裁剪/异常平铺 */
1603
+ minTileWidth: 140,
1604
+ minTileHeight: 100,
1605
+ /** 瓦片字号固定 16px:密度只改变平铺疏密,不改字体大小 */
1606
+ fontSize: 16
1607
+ };
1608
+ var WATERMARK_DENSITY_PRESETS = {
1609
+ dense: { width: 200, height: 120, label: "\u5BC6" },
1610
+ medium: { width: 260, height: 180, label: "\u4E2D\uFF08\u9ED8\u8BA4\uFF09" },
1611
+ loose: { width: 340, height: 260, label: "\u758F" }
1612
+ };
1613
+ function clampTile(value, fallback, min2) {
1614
+ const n = typeof value === "number" && Number.isFinite(value) ? value : fallback;
1615
+ return Math.max(min2, Math.round(n));
1616
+ }
1617
+ function resolveTileSize(wm) {
1618
+ return {
1619
+ width: clampTile(wm?.tileWidth, WATERMARK_DEFAULTS.tileWidth, WATERMARK_DEFAULTS.minTileWidth),
1620
+ height: clampTile(wm?.tileHeight, WATERMARK_DEFAULTS.tileHeight, WATERMARK_DEFAULTS.minTileHeight)
1621
+ };
1622
+ }
1623
+ function isWatermarkVisible(wm) {
1624
+ if (!wm) return false;
1625
+ if (!wm.mode || wm.mode === "fixed") {
1626
+ return !!wm.content && wm.content.trim().length > 0;
1627
+ }
1628
+ return !!wm.binding && wm.binding.trim().length > 0;
1629
+ }
1630
+ function resolveWatermarkText(wm, printData, systemVars2) {
1631
+ if (!wm) return "";
1632
+ let text = "";
1633
+ if (!wm.mode || wm.mode === "fixed") {
1634
+ text = wm.content ?? "";
1635
+ } else if (typeof wm.binding === "string" && wm.binding.trim()) {
1636
+ const binding = wm.binding.trim();
1637
+ const data = Array.isArray(printData) ? printData[0] ?? {} : printData ?? {};
1638
+ const ctx = { ...resolveSystemVariables(), ...systemVars2, ...data };
1639
+ let resolved;
1640
+ if (binding.includes("{")) {
1641
+ resolved = evaluateTemplate(binding, ctx);
1642
+ } else {
1643
+ try {
1644
+ resolved = safeEval(binding, ctx);
1645
+ } catch {
1646
+ resolved = void 0;
1647
+ }
1648
+ if (resolved == null || typeof resolved === "object") {
1649
+ resolved = getByPath(data, binding);
1650
+ }
1651
+ }
1652
+ if (resolved != null && typeof resolved !== "object" && String(resolved) !== binding) {
1653
+ text = String(resolved);
1654
+ } else {
1655
+ text = wm.testData ?? `[${wm.binding}]`;
1656
+ }
1657
+ }
1658
+ if (wm.timestamp && text) {
1659
+ text = `${text} ${formatTimestamp(wm.format)}`;
1660
+ }
1661
+ return text;
1662
+ }
1663
+ function pad(n) {
1664
+ return String(n).padStart(2, "0");
1665
+ }
1666
+ function formatTimestamp(format) {
1667
+ const fmt = format && format.trim() ? format : "YYYY-MM-DD HH:mm";
1668
+ const d = /* @__PURE__ */ new Date();
1669
+ const map = {
1670
+ YYYY: String(d.getFullYear()),
1671
+ MM: pad(d.getMonth() + 1),
1672
+ DD: pad(d.getDate()),
1673
+ HH: pad(d.getHours()),
1674
+ mm: pad(d.getMinutes()),
1675
+ ss: pad(d.getSeconds())
1676
+ };
1677
+ return fmt.replace(/YYYY|MM|DD|HH|mm|ss/g, (k) => map[k] ?? k);
1678
+ }
1679
+ function round4(n) {
1680
+ return Math.round(n * 1e4) / 1e4;
1681
+ }
1682
+ function escXml(text) {
1683
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1684
+ }
1685
+ function resolveWatermarkLayout(wm, printData, paperMm, systemVars2) {
1686
+ if (!isWatermarkVisible(wm)) return null;
1687
+ const text = resolveWatermarkText(wm, printData, systemVars2);
1688
+ if (!text) return null;
1689
+ const tile = resolveTileSize(wm);
1690
+ const tileWidthMm = round4(tile.width * MM_PER_PX);
1691
+ const tileHeightMm = round4(tile.height * MM_PER_PX);
1692
+ const paper = {
1693
+ width: Math.max(0, Number.isFinite(paperMm?.width) ? paperMm.width : 0),
1694
+ height: Math.max(0, Number.isFinite(paperMm?.height) ? paperMm.height : 0)
1695
+ };
1696
+ const columns = Math.max(1, Math.ceil(paper.width / tileWidthMm));
1697
+ const rows = Math.max(1, Math.ceil(paper.height / tileHeightMm));
1698
+ const tiles = [];
1699
+ for (let r = 0; r < rows; r++) {
1700
+ for (let c = 0; c < columns; c++) {
1701
+ tiles.push({
1702
+ leftMm: round4(c * tileWidthMm),
1703
+ topMm: round4(r * tileHeightMm),
1704
+ widthMm: tileWidthMm,
1705
+ heightMm: tileHeightMm
1706
+ });
1707
+ }
1708
+ }
1709
+ return {
1710
+ text,
1711
+ color: wm?.color || WATERMARK_DEFAULTS.color,
1712
+ rotate: wm?.rotate ?? WATERMARK_DEFAULTS.rotate,
1713
+ opacity: wm?.opacity ?? WATERMARK_DEFAULTS.opacity,
1714
+ fontSizePx: WATERMARK_DEFAULTS.fontSize,
1715
+ tileWidthPx: tile.width,
1716
+ tileHeightPx: tile.height,
1717
+ tileWidthMm,
1718
+ tileHeightMm,
1719
+ columns,
1720
+ rows,
1721
+ tiles
1722
+ };
1723
+ }
1724
+ function renderWatermarkTileSvg(layout, tile) {
1725
+ const tileWidthPx = layout.tileWidthPx;
1726
+ const tileHeightPx = layout.tileHeightPx;
1727
+ const cx = tileWidthPx / 2;
1728
+ const cy = tileHeightPx / 2;
1729
+ const style = `left:${tile.leftMm}mm;top:${tile.topMm}mm;width:${tile.widthMm}mm;height:${tile.heightMm}mm`;
1730
+ return `<svg class="watermark-tile" style="${style}" viewBox="0 0 ${tileWidthPx} ${tileHeightPx}" xmlns="http://www.w3.org/2000/svg"><text x="${cx}" y="${cy}" font-size="${layout.fontSizePx}" fill="${layout.color}" text-anchor="middle" dominant-baseline="middle" transform="rotate(${layout.rotate},${cx},${cy})">${escXml(layout.text)}</text></svg>`;
1731
+ }
1732
+ function renderWatermarkLayerHtml(wm, printData, paperMm, systemVars2) {
1733
+ const layout = resolveWatermarkLayout(wm, printData, paperMm, systemVars2);
1734
+ if (!layout) return "";
1735
+ const tiles = layout.tiles.map((t) => renderWatermarkTileSvg(layout, t)).join("\n");
1736
+ return `
1737
+ <div class="watermark-layer" style="opacity:${layout.opacity}">
1738
+ ${tiles}
1739
+ </div>`;
1740
+ }
1741
+
1742
+ // src/render/pagination-engine.ts
1743
+ function paginationOf(el) {
1744
+ return el.options?.pagination ?? el.pagination;
1745
+ }
1746
+ function tablePaginationOf(el) {
1747
+ return el.options?.tablePagination ?? el.tablePagination;
1748
+ }
1749
+ var SAFETY_MARGIN = 2;
1750
+ function isTableEl(el) {
1751
+ return el.type === "table" || el.printElementType?.type === "table";
1752
+ }
1753
+ function tableDesignBottom(el) {
1754
+ const opts = el.options ?? {};
1755
+ const top = opts.top ?? 0;
1756
+ const rows = opts.tableRows ?? [];
1757
+ if (rows.length > 0) {
1758
+ return top + rows.reduce((s, r) => s + (r.height ?? 0), 0);
1759
+ }
1760
+ return top + (opts.height ?? 0);
1761
+ }
1762
+ function buildFollowMap(sorted, excludedIds) {
1763
+ const map = /* @__PURE__ */ new Map();
1764
+ let currentTableId = null;
1765
+ let currentTableBottom = -1;
1766
+ for (const el of sorted) {
1767
+ if (isTableEl(el)) {
1768
+ currentTableId = el.id;
1769
+ currentTableBottom = tableDesignBottom(el);
1770
+ map.set(el.id, []);
1771
+ continue;
1772
+ }
1773
+ if (excludedIds?.has(el.id)) continue;
1774
+ const top = el.options?.top ?? 0;
1775
+ if (currentTableId && top >= currentTableBottom) {
1776
+ map.get(currentTableId).push(el.id);
1777
+ }
1778
+ }
1779
+ return map;
1780
+ }
1781
+ function followGroupHeight(el, members, template, _measuredElements) {
1782
+ const tableBottom = tableDesignBottom(el);
1783
+ let maxBottom = tableBottom;
1784
+ for (const id of members) {
1785
+ const m = template.elements.find((e) => e.id === id);
1786
+ if (!m) continue;
1787
+ maxBottom = Math.max(maxBottom, (m.options?.top ?? 0) + (m.options?.height ?? 0));
1788
+ }
1789
+ return Math.max(maxBottom - tableBottom, 0);
1790
+ }
1791
+ function effectiveHeight(el, measured) {
1792
+ return Math.max(el.options?.height ?? 0, measured.get(el.id)?.measuredHeight ?? 0);
1793
+ }
1794
+ function buildPaginationUnits(sorted, measured, groupMap) {
1795
+ const unitOf = /* @__PURE__ */ new Map();
1796
+ for (const members of groupMap.values()) {
1797
+ if (members.length < 2) continue;
1798
+ const unit = {
1799
+ anchorId: members[0].id,
1800
+ ids: members.map((m) => m.id),
1801
+ forceFirstPage: members.some((m) => paginationOf(m)?.pageable === false)
1802
+ };
1803
+ for (const m of members) unitOf.set(m.id, unit);
1804
+ }
1805
+ const eligible = sorted.filter(
1806
+ (el) => !isTableEl(el) && !el.options?.groupId && paginationOf(el)?.pageable !== false
1807
+ );
1808
+ const parent = /* @__PURE__ */ new Map();
1809
+ eligible.forEach((el) => parent.set(el.id, el.id));
1810
+ const find = (id) => {
1811
+ let root = id;
1812
+ while (parent.get(root) !== root) root = parent.get(root);
1813
+ let cur = id;
1814
+ while (parent.get(cur) !== root) {
1815
+ const next = parent.get(cur);
1816
+ parent.set(cur, root);
1817
+ cur = next;
1818
+ }
1819
+ return root;
1820
+ };
1821
+ const union = (a, b) => {
1822
+ const ra = find(a);
1823
+ const rb = find(b);
1824
+ if (ra !== rb) parent.set(rb, ra);
1825
+ };
1826
+ const active = [];
1827
+ for (const el of eligible) {
1828
+ const top = el.options?.top ?? 0;
1829
+ for (let i = active.length - 1; i >= 0; i--) {
1830
+ if (active[i].bottom <= top) active.splice(i, 1);
1831
+ }
1832
+ const bottom = top + effectiveHeight(el, measured);
1833
+ for (const a of active) {
1834
+ if (a.bottom > top) union(el.id, a.id);
1835
+ }
1836
+ active.push({ id: el.id, top, bottom });
1837
+ }
1838
+ const clusters = /* @__PURE__ */ new Map();
1839
+ for (const el of eligible) {
1840
+ const root = find(el.id);
1841
+ const arr = clusters.get(root);
1842
+ if (arr) arr.push(el.id);
1843
+ else clusters.set(root, [el.id]);
1844
+ }
1845
+ for (const ids of clusters.values()) {
1846
+ if (ids.length < 2) continue;
1847
+ const unit = { anchorId: ids[0], ids, forceFirstPage: false };
1848
+ for (const id of ids) unitOf.set(id, unit);
1849
+ }
1850
+ return unitOf;
1851
+ }
1852
+ function paginate(template, measuredElements) {
1853
+ const paper = getPaperDimensions(template);
1854
+ const { top: mt, bottom: mb } = template.margins;
1855
+ const headerH = template.header?.height ?? 0;
1856
+ const footerH = template.footer?.height ?? 0;
1857
+ const overlayH = template.firstPageOverlay?.height ?? 0;
1858
+ const continuous = isContinuousPaper(template);
1859
+ const contentHeight = continuous ? Number.POSITIVE_INFINITY : paper.height - mt - mb - headerH - footerH;
1860
+ if (!continuous && contentHeight <= 0) {
1861
+ throw new Error(
1862
+ `\u9875\u9762\u53EF\u7528\u9AD8\u5EA6\u4E0D\u8DB3: paper=${paper.height}mm, margins=${mt + mb}mm, header=${headerH}mm, footer=${footerH}mm`
1863
+ );
1864
+ }
1865
+ const orderIndex = /* @__PURE__ */ new Map();
1866
+ template.elements.forEach((e, idx) => orderIndex.set(e.id, idx));
1867
+ const sorted = [...template.elements].sort((a, b) => {
1868
+ const topA = a.options?.top ?? 0;
1869
+ const topB = b.options?.top ?? 0;
1870
+ if (topA !== topB) return topA - topB;
1871
+ const zA = a.options?.zIndex ?? 0;
1872
+ const zB = b.options?.zIndex ?? 0;
1873
+ if (zA !== zB) return zA - zB;
1874
+ return (orderIndex.get(a.id) ?? 0) - (orderIndex.get(b.id) ?? 0);
1875
+ });
1876
+ const explicitGroupIds = /* @__PURE__ */ new Set();
1877
+ const groupMap = /* @__PURE__ */ new Map();
1878
+ for (const el of sorted) {
1879
+ if (isTableEl(el)) continue;
1880
+ const gid = el.options?.groupId;
1881
+ if (!gid) continue;
1882
+ explicitGroupIds.add(el.id);
1883
+ const arr = groupMap.get(gid);
1884
+ if (arr) arr.push(el);
1885
+ else groupMap.set(gid, [el]);
1886
+ }
1887
+ const followMap = buildFollowMap(sorted, explicitGroupIds);
1888
+ const followOwner = /* @__PURE__ */ new Map();
1889
+ for (const [tableId, members] of followMap) {
1890
+ for (const m of members) followOwner.set(m, tableId);
1891
+ }
1892
+ const elById = /* @__PURE__ */ new Map();
1893
+ for (const el of sorted) elById.set(el.id, el);
1894
+ const unitOf = buildPaginationUnits(sorted, measuredElements, groupMap);
1895
+ const pages = [];
1896
+ let currentPage = [];
1897
+ let remaining = contentHeight - overlayH - SAFETY_MARGIN;
1898
+ let isFirstPage = true;
1899
+ let pageBroken = false;
1900
+ function sectionTop(el) {
1901
+ return pageBroken ? 0 : el.options?.top ?? 0;
1902
+ }
1903
+ function fullPageHeight() {
1904
+ return isFirstPage ? contentHeight - overlayH - SAFETY_MARGIN : contentHeight - SAFETY_MARGIN;
1905
+ }
1906
+ let overflowOnCurrent = false;
1907
+ function pushPage() {
1908
+ pages.push(
1909
+ overflowOnCurrent ? { pageIndex: pages.length, sections: [...currentPage], overflow: true } : { pageIndex: pages.length, sections: [...currentPage] }
1910
+ );
1911
+ }
1912
+ function noteOverflow(bottom) {
1913
+ if (bottom > contentHeight) overflowOnCurrent = true;
1914
+ }
1915
+ function finishPage(overflow = false) {
1916
+ if (currentPage.length === 0) {
1917
+ overflowOnCurrent = overflowOnCurrent || overflow;
1918
+ return;
1919
+ }
1920
+ pushPage();
1921
+ currentPage = [];
1922
+ remaining = contentHeight - SAFETY_MARGIN;
1923
+ isFirstPage = false;
1924
+ pageBroken = true;
1925
+ overflowOnCurrent = false;
1926
+ }
1927
+ let i = 0;
1928
+ while (i < sorted.length) {
1929
+ const el = sorted[i];
1930
+ if (followOwner.has(el.id)) {
1931
+ i++;
1932
+ continue;
1933
+ }
1934
+ const unit = unitOf.get(el.id);
1935
+ if (unit && unit.anchorId !== el.id) {
1936
+ i++;
1937
+ continue;
1938
+ }
1939
+ if (paginationOf(el)?.pageable === false || unit?.forceFirstPage) {
1940
+ if (unit) {
1941
+ for (const id of unit.ids) {
1942
+ currentPage.push({ elementId: id, type: "element", renderTop: elById.get(id)?.options?.top ?? 0 });
1943
+ }
1944
+ } else {
1945
+ currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
1946
+ }
1947
+ i++;
1948
+ continue;
1949
+ }
1950
+ if (isTableEl(el)) {
1951
+ i = paginateTable(el, measuredElements, i);
1952
+ } else if (unit) {
1953
+ i = paginateUnit(unit, i);
1954
+ } else {
1955
+ i = paginateNonTable(el, measuredElements, sorted, i);
1956
+ }
1957
+ }
1958
+ if (currentPage.length > 0) {
1959
+ pushPage();
1960
+ }
1961
+ if (pages.length === 0) {
1962
+ pages.push({ pageIndex: 0, sections: [] });
1963
+ }
1964
+ return pages;
1965
+ function paginateNonTable(el, measured, sortedList, idx) {
1966
+ const elHeight = measured.get(el.id)?.measuredHeight ?? el.options?.height ?? 0;
1967
+ if (elHeight <= remaining) {
1968
+ const top2 = pageBroken ? 0 : el.options?.top ?? 0;
1969
+ noteOverflow(top2 + elHeight);
1970
+ currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
1971
+ remaining -= elHeight;
1972
+ if (paginationOf(el)?.keepWithNext && idx + 1 < sortedList.length) {
1973
+ const nextEl = sortedList[idx + 1];
1974
+ const nextHeight = measured.get(nextEl.id)?.measuredHeight ?? nextEl.options?.height ?? 0;
1975
+ if (elHeight + nextHeight > fullPageHeight()) {
1976
+ return idx + 1;
1977
+ }
1978
+ if (nextHeight > remaining) {
1979
+ currentPage.pop();
1980
+ finishPage();
1981
+ currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
1982
+ remaining -= elHeight;
1983
+ }
1984
+ }
1985
+ return idx + 1;
1986
+ }
1987
+ const top = pageBroken ? 0 : el.options?.top ?? 0;
1988
+ finishPage(top + elHeight > contentHeight);
1989
+ currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
1990
+ remaining -= elHeight;
1991
+ return idx + 1;
1992
+ }
1993
+ function paginateUnit(unit, idx) {
1994
+ const members = unit.ids.map((id) => elById.get(id)).filter((m) => !!m);
1995
+ let minTop = Number.POSITIVE_INFINITY;
1996
+ let maxBottom = Number.NEGATIVE_INFINITY;
1997
+ for (const m of members) {
1998
+ const top2 = m.options?.top ?? 0;
1999
+ minTop = Math.min(minTop, top2);
2000
+ maxBottom = Math.max(maxBottom, top2 + effectiveHeight(m, measuredElements));
2001
+ }
2002
+ const unitHeight = Math.max(maxBottom - minTop, 0);
2003
+ const place = () => {
2004
+ const offset = pageBroken ? -minTop : 0;
2005
+ noteOverflow(minTop + offset + unitHeight);
2006
+ for (const m of members) {
2007
+ currentPage.push({
2008
+ elementId: m.id,
2009
+ type: "element",
2010
+ renderTop: (m.options?.top ?? 0) + offset
2011
+ });
2012
+ }
2013
+ remaining -= unitHeight;
2014
+ };
2015
+ if (unitHeight <= remaining) {
2016
+ place();
2017
+ return idx + 1;
2018
+ }
2019
+ const top = pageBroken ? 0 : minTop;
2020
+ finishPage(top + unitHeight > contentHeight);
2021
+ place();
2022
+ return idx + 1;
2023
+ }
2024
+ function paginateTable(el, measured, idx) {
2025
+ const opts = el.options ?? {};
2026
+ const renderRows = opts._renderRows ?? [];
2027
+ const rowHeights = measured.get(el.id)?.measuredRowHeights ?? [];
2028
+ const hasSubtotal = (opts._subtotalTemplates?.length ?? 0) > 0;
2029
+ const hasSummary = (opts._summaryRows?.length ?? 0) > 0;
2030
+ const subtotalH = hasSubtotal ? specialRowHeight(renderRows, rowHeights, "subtotal") : 0;
2031
+ const summaryH = hasSummary ? specialRowHeight(renderRows, rowHeights, "summary") : 0;
2032
+ const isSpecial = (r) => hasSubtotal && r.type === "subtotal" || hasSummary && r.type === "summary";
2033
+ const bodyRows = renderRows.filter((r) => !isSpecial(r));
2034
+ const bodyHeights = rowHeights.filter((_, i2) => !isSpecial(renderRows[i2]));
2035
+ opts._renderRows = bodyRows;
2036
+ const rowCount = Math.min(bodyRows.length, bodyHeights.length);
2037
+ const repeatCount = opts._repeatHeaderCount ?? 0;
2038
+ const repeatH = measured.get(el.id)?.repeatHeaderHeight ?? rowHeights.slice(0, repeatCount).reduce((s, h) => s + h, 0);
2039
+ const freshAvail = contentHeight - SAFETY_MARGIN;
2040
+ if (repeatH > freshAvail) {
2041
+ throw new Error(
2042
+ `\u8868\u683C ${el.id} \u7684\u91CD\u590D\u8868\u5934\u9AD8\u5EA6 (${repeatH.toFixed(1)}mm) \u8D85\u8FC7\u9875\u9762\u53EF\u7528\u9AD8\u5EA6 (${freshAvail.toFixed(1)}mm)\uFF0C\u8BF7\u51CF\u5C11\u91CD\u590D\u8868\u5934\u884C`
2043
+ );
2044
+ }
2045
+ if (rowCount === 0) {
2046
+ currentPage.push({
2047
+ elementId: el.id,
2048
+ type: "table-slice",
2049
+ startRow: 0,
2050
+ endRow: 0,
2051
+ summary: summaryH > 0,
2052
+ renderTop: sectionTop(el)
2053
+ });
2054
+ remaining -= summaryH;
2055
+ applyFollowGroup(el, measured);
2056
+ return idx + 1;
2057
+ }
2058
+ const totalH = bodyHeights.slice(0, rowCount).reduce((s, h) => s + h, 0);
2059
+ const totalNeed = totalH + subtotalH + summaryH;
2060
+ if (tablePaginationOf(el)?.enabled === false) {
2061
+ if (totalNeed > remaining && currentPage.length > 0) finishPage();
2062
+ currentPage.push({
2063
+ elementId: el.id,
2064
+ type: "table-slice",
2065
+ startRow: 0,
2066
+ endRow: rowCount,
2067
+ subtotal: subtotalH > 0,
2068
+ summary: summaryH > 0,
2069
+ renderTop: sectionTop(el)
2070
+ });
2071
+ remaining -= totalNeed;
2072
+ applyFollowGroup(el, measured);
2073
+ return idx + 1;
2074
+ }
2075
+ const groups = buildRowGroups(bodyRows, rowCount);
2076
+ let sliceStart = 0;
2077
+ let firstSlice = true;
2078
+ for (const g of groups) {
2079
+ const gh = bodyHeights.slice(g.start, g.end).reduce((s, h) => s + h, 0);
2080
+ if (gh + subtotalH <= remaining) {
2081
+ remaining -= gh;
2082
+ continue;
2083
+ }
2084
+ if (g.start > sliceStart) {
2085
+ currentPage.push({
2086
+ elementId: el.id,
2087
+ type: "table-slice",
2088
+ startRow: sliceStart,
2089
+ endRow: g.start,
2090
+ subtotal: subtotalH > 0,
2091
+ ...!firstSlice && repeatCount > 0 ? { repeatHeader: true } : {},
2092
+ renderTop: sectionTop(el)
2093
+ });
2094
+ firstSlice = false;
2095
+ sliceStart = g.start;
2096
+ }
2097
+ finishPage();
2098
+ if (!firstSlice) remaining -= repeatH;
2099
+ remaining -= gh;
2100
+ }
2101
+ if (sliceStart < rowCount) {
2102
+ currentPage.push({
2103
+ elementId: el.id,
2104
+ type: "table-slice",
2105
+ startRow: sliceStart,
2106
+ endRow: rowCount,
2107
+ subtotal: subtotalH > 0,
2108
+ ...!firstSlice && repeatCount > 0 ? { repeatHeader: true } : {},
2109
+ renderTop: sectionTop(el)
2110
+ });
2111
+ }
2112
+ if (summaryH > 0) {
2113
+ if (summaryH <= remaining) {
2114
+ const lastIdx = currentPage.length - 1;
2115
+ const last = currentPage[lastIdx];
2116
+ if (last && last.type === "table-slice" && last.elementId === el.id) {
2117
+ currentPage[lastIdx] = { ...last, summary: true };
2118
+ remaining -= summaryH;
2119
+ } else {
2120
+ finishPage();
2121
+ currentPage.push({ elementId: el.id, type: "table-slice", startRow: rowCount, endRow: rowCount, summary: true, renderTop: 0 });
2122
+ remaining -= summaryH;
2123
+ }
2124
+ } else {
2125
+ finishPage();
2126
+ currentPage.push({ elementId: el.id, type: "table-slice", startRow: rowCount, endRow: rowCount, summary: true, renderTop: 0 });
2127
+ remaining -= summaryH;
2128
+ }
2129
+ }
2130
+ applyFollowGroup(el, measured);
2131
+ return idx + 1;
2132
+ }
2133
+ function applyFollowGroup(el, measured) {
2134
+ const members = followMap.get(el.id) ?? [];
2135
+ if (members.length === 0) return;
2136
+ const followH = followGroupHeight(el, members, template, measured);
2137
+ const opts = el.options ?? {};
2138
+ if (followH <= remaining) {
2139
+ const lastIdx = currentPage.length - 1;
2140
+ const last = currentPage[lastIdx];
2141
+ if (last && last.type === "table-slice" && last.elementId === el.id) {
2142
+ currentPage[lastIdx] = {
2143
+ ...last,
2144
+ type: "flow-group",
2145
+ followElementIds: members,
2146
+ // 沿用该切片在本页的定位 top(续片已 stamp 为 0)
2147
+ groupTop: last.renderTop ?? opts.top ?? 0
2148
+ };
2149
+ remaining -= followH;
2150
+ return;
2151
+ }
2152
+ }
2153
+ finishPage();
2154
+ currentPage.push({
2155
+ elementId: el.id,
2156
+ type: "flow-group",
2157
+ startRow: 0,
2158
+ endRow: 0,
2159
+ followElementIds: members,
2160
+ groupTop: 0
2161
+ });
2162
+ remaining -= followH;
2163
+ }
2164
+ }
2165
+ function buildRowGroups(renderRows, rowCount) {
2166
+ const groups = [];
2167
+ let start = 0;
2168
+ let end = 1;
2169
+ for (let r = 0; r < rowCount; r++) {
2170
+ for (const cell of renderRows[r]?.cells ?? []) {
2171
+ if (!cell.merged && (cell.rowspan ?? 1) > 1) end = Math.max(end, r + cell.rowspan);
2172
+ }
2173
+ if (r + 1 >= end) {
2174
+ groups.push({ start, end: r + 1 });
2175
+ start = r + 1;
2176
+ end = start + 1;
2177
+ }
2178
+ }
2179
+ return groups;
2180
+ }
2181
+ function specialRowHeight(renderRows, rowHeights, type) {
2182
+ let h = 0;
2183
+ for (let i = 0; i < renderRows.length; i++) {
2184
+ if (renderRows[i]?.type === type) h += rowHeights[i] ?? 0;
2185
+ }
2186
+ return h;
2187
+ }
2188
+
2189
+ // src/render/barcode-dot.ts
2190
+ var MM_PER_INCH = 25.4;
2191
+ var BARCODE_QUIET_ZONE_MODULES = 10;
2192
+ var BARCODE_BAR_HEIGHT_MODULES = 30;
2193
+ var BARCODE_TEXT_FONT_SIZE_MODULES = 10;
2194
+ var BARCODE_MARGIN_BOTTOM_MODULES = 2;
2195
+ var BARCODE_MODULE_WIDTH_MM = 0.25;
2196
+ function barcodeUnitsPerModule(barWidth) {
2197
+ const raw = typeof barWidth === "number" && Number.isFinite(barWidth) ? barWidth : 2;
2198
+ return Math.max(1, raw / 2);
2199
+ }
2200
+ function barcodePreferredModuleWidthMm(barWidth) {
2201
+ return barcodeUnitsPerModule(barWidth) * BARCODE_MODULE_WIDTH_MM;
2202
+ }
2203
+ function barcodeAvailableBoxMm(boxMm, maxMm) {
2204
+ const box = typeof boxMm === "number" && boxMm > 0 ? boxMm : Number.POSITIVE_INFINITY;
2205
+ const limit = typeof maxMm === "number" && maxMm > 0 ? Math.min(box, maxMm) : box;
2206
+ return Number.isFinite(limit) ? limit : 0;
2207
+ }
2208
+ function resolveBarcodeSize(input) {
2209
+ const unitWidth = Math.max(1, Math.ceil(input.unitWidth));
2210
+ const unitHeight = Math.max(1, Math.ceil(input.unitHeight));
2211
+ const preferredModuleMm = barcodePreferredModuleWidthMm(input.barWidth);
2212
+ const maxModuleMm = Math.min(
2213
+ input.boxWidthMm > 0 ? input.boxWidthMm / unitWidth : Number.POSITIVE_INFINITY,
2214
+ input.boxHeightMm > 0 ? input.boxHeightMm / unitHeight : Number.POSITIVE_INFINITY
2215
+ );
2216
+ const dpi = input.dpi;
2217
+ if (typeof dpi === "number" && Number.isFinite(dpi) && dpi > 0) {
2218
+ const dotsPerMm = dpi / MM_PER_INCH;
2219
+ const preferredDots = Math.max(1, Math.round(preferredModuleMm * dotsPerMm));
2220
+ const maxDots = Math.floor(maxModuleMm * dotsPerMm);
2221
+ if (maxDots >= 1) {
2222
+ const dotsPerModule = Math.min(preferredDots, maxDots);
2223
+ const moduleWidthMm2 = dotsPerModule / dotsPerMm;
2224
+ return {
2225
+ moduleWidthMm: moduleWidthMm2,
2226
+ widthMm: unitWidth * moduleWidthMm2,
2227
+ heightMm: unitHeight * moduleWidthMm2,
2228
+ dotsPerModule,
2229
+ scaledDown: dotsPerModule < preferredDots
2230
+ };
2231
+ }
2232
+ }
2233
+ const moduleWidthMm = Math.min(preferredModuleMm, maxModuleMm);
2234
+ return {
2235
+ moduleWidthMm,
2236
+ widthMm: unitWidth * moduleWidthMm,
2237
+ heightMm: unitHeight * moduleWidthMm,
2238
+ dotsPerModule: null,
2239
+ scaledDown: moduleWidthMm < preferredModuleMm
2240
+ };
2241
+ }
2242
+
2243
+ // src/render/html-generator.ts
2244
+ function pageVarsContext(vars) {
2245
+ return { ...vars.data ?? {}, pageIndex: vars.pageIndex, totalPages: vars.totalPages };
2246
+ }
2247
+ function pageVarsData(printData) {
2248
+ if (Array.isArray(printData)) return printData[0] ?? {};
2249
+ return printData ?? {};
2250
+ }
2251
+ function generateHtml(template, pageLayouts, printData, options) {
2252
+ const css = buildFontFaceCss(template.fonts) + buildPageCss(template, options?.pageHeightMm);
2253
+ const isMeasure = options?.isMeasurementPass === true;
2254
+ const totalPages = isMeasure ? 1 : pageLayouts.length;
2255
+ const ctx = {
2256
+ codeRenderer: options?.codeRenderer,
2257
+ pageHeightMm: options?.pageHeightMm
2258
+ };
2259
+ if (isMeasure) {
2260
+ return generateMeasurementHtml(template, css, ctx, printData);
2261
+ }
2262
+ return generateFinalHtml(template, pageLayouts, css, totalPages, ctx, printData);
2263
+ }
2264
+ function generateMeasurementHtml(template, css, ctx, printData) {
2265
+ const paper = getPaperDims(template);
2266
+ const contentWidth = paper.width - template.margins.left - template.margins.right;
2267
+ const elementsHtml = template.elements.map((el) => renderElement(el, true, void 0, void 0, ctx)).join("\n");
2268
+ const headerHtml = renderAreaElements(
2269
+ template.header?.elements ?? [],
2270
+ contentWidth,
2271
+ 0,
2272
+ // pageIndex placeholder
2273
+ 0,
2274
+ // totalPages placeholder
2275
+ ctx
2276
+ );
2277
+ const overlayHtml = renderAreaElements(template.firstPageOverlay?.elements ?? [], contentWidth, void 0, void 0, ctx);
2278
+ let html = `<!DOCTYPE html>
2279
+ <html lang="zh-CN">
2280
+ <head>
2281
+ <meta charset="UTF-8">
2282
+ <style>${css}</style>
2283
+ </head>
2284
+ <body class="measure-mode">
2285
+ <section class="print-page" data-measure-page="0">
2286
+ ${renderWatermarkLayerHtml(template.watermark, printData, getPaperDims(template))}
2287
+ <div class="page-header">${headerHtml}</div>
2288
+ <div class="first-page-overlay">${overlayHtml}</div>
2289
+ <div class="content-area" style="height:auto;overflow:visible;">
2290
+ ${elementsHtml}
2291
+ </div>
2292
+ </section>
2293
+ </body>
2294
+ </html>`;
2295
+ html = injectSystemVariables(html);
2296
+ return html;
2297
+ }
2298
+ function renderFinalPages(template, pageLayouts, printData, ctx, options) {
2299
+ const pageOffset = options?.pageOffset ?? 0;
2300
+ const totalPages = options?.totalPages ?? pageLayouts.length;
2301
+ const pagesHtml = pageLayouts.map((page) => {
2302
+ const pageNum = page.pageIndex + 1 + pageOffset;
2303
+ return renderPage(template, page, pageNum, totalPages, ctx, printData, options?.pageClass);
2304
+ }).join("\n");
2305
+ return injectSystemVariables(pagesHtml);
2306
+ }
2307
+ function wrapHtmlDocument(css, bodyInnerHtml, bodyClass) {
2308
+ const html = `<!DOCTYPE html>
2309
+ <html lang="zh-CN">
2310
+ <head>
2311
+ <meta charset="UTF-8">
2312
+ <style>${css}</style>
2313
+ </head>
2314
+ <body${bodyClass ? ` class="${bodyClass}"` : ""}>
2315
+ ${bodyInnerHtml}
2316
+ </body>
2317
+ </html>`;
2318
+ return injectSystemVariables(html);
2319
+ }
2320
+ function generateFinalHtml(template, pageLayouts, css, _totalPages2, ctx, printData) {
2321
+ const bodyInner = renderFinalPages(template, pageLayouts, printData ?? {}, ctx);
2322
+ return wrapHtmlDocument(
2323
+ css,
2324
+ bodyInner,
2325
+ isContinuousPaper(template) ? "continuous" : void 0
2326
+ );
2327
+ }
2328
+ function renderPage(template, page, pageNum, totalPages, ctx, printData, pageClass) {
2329
+ const paper = getPaperDims(template);
2330
+ const paperMm = {
2331
+ width: paper.width,
2332
+ height: ctx.pageHeightMm && ctx.pageHeightMm > 0 ? ctx.pageHeightMm : paper.height
2333
+ };
2334
+ const contentWidth = paper.width - template.margins.left - template.margins.right;
2335
+ const pageVars = { pageIndex: pageNum, totalPages, data: pageVarsData(printData) };
2336
+ const scoped = withPageNumbers(template, pageVars);
2337
+ const pageCtx = { ...ctx, pageVars };
2338
+ const headerHtml = renderAreaElements(
2339
+ scoped.header?.elements ?? [],
2340
+ contentWidth,
2341
+ pageNum,
2342
+ totalPages,
2343
+ pageCtx
2344
+ );
2345
+ const footerHtml = renderAreaElements(
2346
+ scoped.footer?.elements ?? [],
2347
+ contentWidth,
2348
+ pageNum,
2349
+ totalPages,
2350
+ pageCtx
2351
+ );
2352
+ const overlayHtml = page.pageIndex === 0 ? `<div class="first-page-overlay">${renderAreaElements(scoped.firstPageOverlay?.elements ?? [], contentWidth, pageNum, totalPages, pageCtx)}</div>` : "";
2353
+ let contentHtml = page.sections.map((section) => renderSection(section, scoped, pageCtx)).join("\n");
2354
+ contentHtml = contentHtml.replace(/\{pageIndex\}/g, String(pageNum));
2355
+ contentHtml = contentHtml.replace(/\{totalPages\}/g, String(totalPages));
2356
+ const pageInner = `
2357
+ ${renderWatermarkLayerHtml(template.watermark, printData, paperMm, { pageIndex: pageNum, totalPages })}
2358
+ <div class="page-header">${headerHtml}</div>
2359
+ ${overlayHtml}
2360
+ <div class="content-area">
2361
+ ${contentHtml}
2362
+ </div>
2363
+ <div class="page-footer">${footerHtml}</div>`;
2364
+ const rot = getOutputRotationAngle(template);
2365
+ const pageBody = rot !== 0 ? `<div class="print-page-rotor print-page-rotor-${rot}">${pageInner}</div>` : pageInner;
2366
+ return `<section class="print-page${pageClass ? ` ${pageClass}` : ""}" data-page="${pageNum}">
2367
+ ${pageBody}
2368
+ </section>`;
2369
+ }
2370
+ function withPageNumbers(template, vars) {
2371
+ if (!templateReferencesPageNumbers(template)) return template;
2372
+ const context = pageVarsContext(vars);
2373
+ const mapEl = (el) => {
2374
+ const raw = el.options?.rawFormatter;
2375
+ if (typeof raw !== "string") return el;
2376
+ return { ...el, options: { ...el.options, formatter: evaluateTemplate(raw, context) } };
2377
+ };
2378
+ const mapArea = (area) => area ? { ...area, elements: (area.elements ?? []).map(mapEl) } : area;
2379
+ return {
2380
+ ...template,
2381
+ elements: template.elements.map(mapEl),
2382
+ header: mapArea(template.header),
2383
+ footer: mapArea(template.footer),
2384
+ firstPageOverlay: mapArea(template.firstPageOverlay)
2385
+ };
2386
+ }
2387
+ function templateReferencesPageNumbers(template) {
2388
+ const areas = [
2389
+ template.elements,
2390
+ template.header?.elements,
2391
+ template.footer?.elements,
2392
+ template.firstPageOverlay?.elements
2393
+ ];
2394
+ return areas.some((list) => (list ?? []).some((el) => typeof el.options?.rawFormatter === "string"));
2395
+ }
2396
+ function resolveCellContent(cell, ctx) {
2397
+ const raw = cell.rawFormatter;
2398
+ if (typeof raw === "string" && raw !== "" && ctx?.pageVars) {
2399
+ return evaluateTemplate(raw, pageVarsContext(ctx.pageVars));
2400
+ }
2401
+ return cell.content;
2402
+ }
2403
+ function renderSection(section, template, ctx) {
2404
+ const el = findElement(template, section.elementId);
2405
+ if (!el) {
2406
+ return `<!-- element not found: ${section.elementId} -->`;
2407
+ }
2408
+ if (section.type === "table-slice") {
2409
+ return renderTableSlice(el, section, ctx);
2410
+ }
2411
+ if (section.type === "flow-group") {
2412
+ return renderFlowGroup(el, section, template, ctx);
2413
+ }
2414
+ return renderElement(el, false, void 0, section.renderTop, ctx);
2415
+ }
2416
+ var V_ALIGN_FLEX = { top: "flex-start", middle: "center", bottom: "flex-end" };
2417
+ var H_ALIGN_FLEX = { left: "flex-start", center: "center", right: "flex-end" };
2418
+ function textStyle(opts) {
2419
+ const parts = [];
2420
+ const fontSize = opts._fitFontSize === void 0 ? opts.fontSize : roundFontSize(opts._fitFontSize);
2421
+ if (fontSize) parts.push(`font-size:${fontSize}pt`);
2422
+ if (opts.fontFamily) parts.push(`font-family:${escapeInlineStyleValue(toFontFamilyStack(opts.fontFamily))}`);
2423
+ if (opts.fontWeight) parts.push(`font-weight:${opts.fontWeight}`);
2424
+ if (opts.color) parts.push(`color:${opts.color}`);
2425
+ if (opts.backgroundColor) parts.push(`background-color:${opts.backgroundColor}`);
2426
+ if (opts.lineHeight) parts.push(`line-height:${opts.lineHeight}pt`);
2427
+ if (opts.letterSpacing) parts.push(`letter-spacing:${opts.letterSpacing}pt`);
2428
+ if (opts.verticalAlign) {
2429
+ parts.push(`display:flex;align-items:${V_ALIGN_FLEX[opts.verticalAlign] ?? "flex-start"}`);
2430
+ parts.push(`justify-content:${H_ALIGN_FLEX[opts.textAlign ?? "left"] ?? "flex-start"}`);
2431
+ }
2432
+ if (opts.textAlign) parts.push(`text-align:${opts.textAlign}`);
2433
+ return parts.length ? parts.join(";") + ";" : "";
2434
+ }
2435
+ function elementFitStyle(fit, opts) {
2436
+ if (fit === "autoHeight") return "overflow:visible;";
2437
+ if (opts.wordWrap === false) return "white-space:nowrap;text-overflow:ellipsis;";
2438
+ return "";
2439
+ }
2440
+ function fitAttrs(fit, key, baseFontSizePt, opts) {
2441
+ if (fit !== "shrink") return "";
2442
+ const min2 = resolveShrinkMinFontSize(opts.shrinkMinFontSize);
2443
+ return ` data-fit="shrink" data-fit-key="${esc(key)}" data-fit-base="${baseFontSizePt}" data-fit-min="${min2}"`;
2444
+ }
2445
+ function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
2446
+ const opts = el.options ?? {};
2447
+ const left = opts.left ?? 0;
2448
+ const top = overrideTop ?? opts.top ?? 0;
2449
+ const width = opts.width ?? 100;
2450
+ const height = opts.height ?? void 0;
2451
+ const type = el.type || el.printElementType?.type || "text";
2452
+ const fit = resolveElementTextFit(type, opts);
2453
+ const fitHeight = fit === "autoHeight" ? void 0 : height;
2454
+ const style = containerStyle ?? elementPositionStyle(left, top, width, fitHeight, opts.zIndex);
2455
+ const measureAttr = isMeasure ? ` data-measure-id="${el.id}"` : "";
2456
+ const fitAttr = fitAttrs(fit, el.id, opts.fontSize ?? 12, opts);
2457
+ switch (type) {
2458
+ case "table":
2459
+ return renderTableElement(el, isMeasure, measureAttr, ctx);
2460
+ case "image":
2461
+ return `<div class="print-element" style="${style}"${measureAttr}>
2462
+ <img src="${esc(opts.src ?? "")}" style="width:100%;height:100%;object-fit:contain;" />
2463
+ </div>`;
2464
+ case "barcode":
2465
+ case "qrcode": {
2466
+ const codeValue = String(opts.formatter ?? opts.testData ?? "").trim();
2467
+ return `<div class="print-element" style="${style}"${measureAttr}>
2468
+ ${codeImgHtml(codeValue, type, opts, {
2469
+ fallback: `<span>${esc(codeValue)}</span>`,
2470
+ codeRenderer: ctx?.codeRenderer,
2471
+ fit: opts.fit,
2472
+ maxWidth: opts.maxWidth,
2473
+ maxHeight: opts.maxHeight,
2474
+ printerDpi: opts.printerDpi,
2475
+ targetWidthMm: opts.width,
2476
+ targetHeightMm: opts.height
2477
+ })}
2478
+ </div>`;
2479
+ }
2480
+ case "hline":
2481
+ return `<div class="print-element" style="${style};border-top:1px solid #000;height:0;"${measureAttr}></div>`;
2482
+ case "vline":
2483
+ return `<div class="print-element" style="${style};border-left:1px solid #000;width:0;"${measureAttr}></div>`;
2484
+ case "rect":
2485
+ return `<div class="print-element" style="${style};border:${opts.borderWidth ?? 1}px solid ${opts.borderColor ?? "#000"};"${measureAttr}></div>`;
2486
+ case "oval":
2487
+ return `<div class="print-element" style="${style};border:${opts.borderWidth ?? 1}px solid ${opts.borderColor ?? "#000"};border-radius:50%;"${measureAttr}></div>`;
2488
+ case "longText":
2489
+ return `<div class="print-element" style="${style}${textStyle(opts)}${elementFitStyle(fit, opts)}"${measureAttr}${fitAttr}>${esc(opts.formatter ?? opts.testData ?? "")}</div>`;
2490
+ case "html":
2491
+ return `<div class="print-element" style="${style}"${measureAttr}>${opts.testData ?? opts.title ?? ""}</div>`;
2492
+ default:
2493
+ return `<div class="print-element" style="${style}${textStyle(opts)}${elementFitStyle(fit, opts)}"${measureAttr}${fitAttr}>${esc(opts.formatter ?? opts.testData ?? "")}</div>`;
2494
+ }
2495
+ }
2496
+ function codeImgHtml(value, cellType, opts, io) {
2497
+ if (!value || !io.codeRenderer) return io.fallback;
2498
+ const { fallback, codeRenderer, fit, maxWidth, maxHeight } = io;
2499
+ const isBarcode = cellType === "barcode";
2500
+ try {
2501
+ const svg = codeRenderer.render(value, cellType, {
2502
+ barcodeType: opts.barcodeType,
2503
+ qrCodeLevel: opts.qrCodeLevel != null ? String(opts.qrCodeLevel) : void 0,
2504
+ showText: opts.hideTitle !== void 0 ? !opts.hideTitle : opts.showBarcodeText,
2505
+ barWidth: typeof opts.barWidth === "number" ? opts.barWidth : void 0,
2506
+ fontSize: typeof opts.fontSize === "number" ? opts.fontSize : void 0,
2507
+ printerDpi: isBarcode ? io.printerDpi : void 0,
2508
+ targetWidthMm: isBarcode ? barcodeAvailableBoxMm(io.targetWidthMm, maxWidth) : void 0,
2509
+ targetHeightMm: isBarcode ? barcodeAvailableBoxMm(io.targetHeightMm, maxHeight) : void 0
2510
+ });
2511
+ if (isBarcode && /^<svg[\s>]/i.test(svg)) return inlineCodeSvgHtml(svg);
2512
+ const src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
2513
+ const styleParts = [];
2514
+ styleParts.push("max-width:100%", "max-height:100%", "display:block", "margin:auto");
2515
+ if (fit) {
2516
+ styleParts.push(`object-fit:${fit}`);
2517
+ }
2518
+ if (maxWidth) {
2519
+ styleParts.push(`max-width:${maxWidth}mm`);
2520
+ }
2521
+ if (maxHeight) {
2522
+ styleParts.push(`max-height:${maxHeight}mm`);
2523
+ }
2524
+ const style = styleParts.join(";");
2525
+ return `<img src="${src}" style="${style}" />`;
2526
+ } catch {
2527
+ return fallback;
2528
+ }
2529
+ }
2530
+ function inlineCodeSvgHtml(svg) {
2531
+ const styled = svg.replace(/<svg\b/, '<svg style="display:block"');
2532
+ return `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;">${styled}</div>`;
2533
+ }
2534
+ function matrixCellStyle(cell, opts) {
2535
+ const parts = [];
2536
+ const fontSize = cell.fittedFontSize ?? cell.fontSize ?? opts.tableDefaultFontSize;
2537
+ const color = cell.color ?? opts.tableDefaultColor;
2538
+ const padding = cell.padding ?? opts.tableDefaultPadding ?? 1;
2539
+ if (fontSize) parts.push(`font-size:${fontSize}pt`);
2540
+ if (cell.fontFamily) parts.push(`font-family:${escapeInlineStyleValue(toFontFamilyStack(cell.fontFamily))}`);
2541
+ if (cell.fontWeight) parts.push(`font-weight:${cell.fontWeight}`);
2542
+ if (color) parts.push(`color:${color}`);
2543
+ if (cell.backgroundColor) parts.push(`background-color:${cell.backgroundColor}`);
2544
+ parts.push(`text-align:${cell.align ?? "left"}`);
2545
+ parts.push(`vertical-align:${cell.valign ?? "middle"}`);
2546
+ parts.push(`padding:${padding}mm`);
2547
+ parts.push(cell.wordWrap === false ? "white-space:nowrap;overflow:hidden" : "word-break:break-all");
2548
+ const b = cell.borders ?? {};
2549
+ for (const side of ["top", "right", "bottom", "left"]) {
2550
+ const border = b[side];
2551
+ parts.push(border ? `border-${side}:${border.width}pt ${border.style} ${border.color}` : `border-${side}:none`);
2552
+ }
2553
+ return parts.join(";");
2554
+ }
2555
+ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOwner = { elementId: "", kind: "b" }, rowLimit) {
2556
+ const trs = [];
2557
+ const defaultPadding = opts.tableDefaultPadding ?? 1;
2558
+ for (let r = start; r < end; r++) {
2559
+ const row = renderRows[r];
2560
+ if (!row) continue;
2561
+ const idxAttr = withRowIndex ? ` data-row-index="${r}"` : "";
2562
+ const tds = row.cells.map((cell, ci) => ({ cell, ci })).filter(({ cell }) => !cell.merged).map(({ cell, ci }) => {
2563
+ const rowspan = rowLimit === void 0 ? cell.rowspan ?? 1 : Math.max(1, Math.min(cell.rowspan ?? 1, rowLimit - r));
2564
+ const span = `${rowspan > 1 ? ` rowspan="${rowspan}"` : ""}${cell.colspan > 1 ? ` colspan="${cell.colspan}"` : ""}`;
2565
+ const colIndex = ci;
2566
+ let inner;
2567
+ if (cell.cellType === "barcode" || cell.cellType === "qrcode") {
2568
+ const codeValue = resolveCellContent(cell, ctx);
2569
+ inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;">${codeImgHtml(codeValue, cell.cellType, cell, {
2570
+ fallback: esc(codeValue),
2571
+ codeRenderer: ctx?.codeRenderer,
2572
+ fit: cell.fit,
2573
+ maxWidth: cell.maxWidth,
2574
+ maxHeight: cell.maxHeight,
2575
+ printerDpi: cell.printerDpi,
2576
+ // 可用框 = 单元格内容区(列宽/行高扣除内边距与塌陷边框);列宽缺失时宽度为 0 → 不约束宽度
2577
+ targetWidthMm: cellFitWidthMm(opts.tableColWidths ?? [], ci, cell, defaultPadding),
2578
+ targetHeightMm: cellFitCapMm(renderRows, r, cell, defaultPadding)
2579
+ })}</div>`;
2580
+ } else if (cell.cellType === "image") {
2581
+ const fit = cell.fit || "contain";
2582
+ const maxWidth = cell.maxWidth ? `max-width:${cell.maxWidth}mm;` : "max-width:100%;";
2583
+ const maxHeight = cell.maxHeight ? `max-height:${cell.maxHeight}mm;` : "max-height:100%;";
2584
+ inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;"><img src="${esc(resolveCellContent(cell, ctx))}" style="object-fit:${fit};${maxWidth}${maxHeight}display:block;" /></div>`;
2585
+ } else {
2586
+ inner = renderTextCellInner(renderRows, r, colIndex, cell, opts, fitOwner, defaultPadding, ctx);
2587
+ }
2588
+ return `<td${span} style="${matrixCellStyle(cell, opts)}">${inner}</td>`;
2589
+ }).join("");
2590
+ trs.push(`<tr${idxAttr} style="height:${row.height}mm;">${tds}</tr>`);
2591
+ }
2592
+ return trs.join("\n");
2593
+ }
2594
+ function renderTextCellInner(renderRows, rowIndex, colIndex, cell, opts, fitOwner, defaultPadding, ctx) {
2595
+ const fit = resolveCellTextFit(cell);
2596
+ const text = esc(resolveCellContent(cell, ctx));
2597
+ if (fit === "autoHeight") return text;
2598
+ const capMm = cellFitCapMm(renderRows, rowIndex, cell, defaultPadding);
2599
+ const nowrap = cell.wordWrap === false;
2600
+ const style = [
2601
+ `max-height:${capMm}mm`,
2602
+ "overflow:hidden",
2603
+ ...nowrap ? ["white-space:nowrap", "text-overflow:ellipsis"] : []
2604
+ ].join(";");
2605
+ const attrs = fit === "shrink" && fitOwner.elementId ? ` data-fit="shrink" data-fit-key="${esc(cellFitKey(fitOwner.elementId, fitOwner.kind, rowIndex, colIndex))}" data-fit-base="${cell.fontSize ?? opts.tableDefaultFontSize ?? 12}" data-fit-min="${resolveShrinkMinFontSize(cell.shrinkMinFontSize)}" data-fit-mm="${capMm}"` : "";
2606
+ return `<div class="cell-fit" style="${style}"${attrs}>${text}</div>`;
2607
+ }
2608
+ function matrixTableHtml(el, bodyHtml) {
2609
+ const opts = el.options;
2610
+ const colWidths = opts.tableColWidths ?? [];
2611
+ const colgroup = colWidths.map((w) => `<col style="width:${w}mm;">`).join("");
2612
+ const tableWidth = colWidths.reduce((s, w) => s + w, 0);
2613
+ return `<table class="print-table" data-element-id="${el.id}" style="border-collapse:collapse;table-layout:fixed;width:${tableWidth}mm;">
2614
+ <colgroup>${colgroup}</colgroup>
2615
+ <tbody>${bodyHtml}</tbody>
2616
+ </table>`;
2617
+ }
2618
+ function renderTableElement(el, isMeasure, measureAttr, ctx) {
2619
+ const opts = el.options;
2620
+ const style = elementPositionStyle(opts.left ?? 0, opts.top ?? 0, opts.width ?? 100, void 0, opts.zIndex);
2621
+ const renderRows = opts._renderRows ?? [];
2622
+ const bodyHtml = renderMatrixRows(renderRows, 0, renderRows.length, opts, isMeasure, ctx, { elementId: el.id, kind: "b" });
2623
+ return `<div class="print-element" style="${style};overflow:visible;"${measureAttr}>
2624
+ ${matrixTableHtml(el, bodyHtml)}
2625
+ </div>`;
2626
+ }
2627
+ function renderTableSlice(el, section, ctx) {
2628
+ const opts = el.options;
2629
+ const style = elementPositionStyle(opts.left ?? 0, section.renderTop ?? opts.top ?? 0, opts.width ?? 100, void 0, opts.zIndex);
2630
+ const renderRows = opts._renderRows ?? [];
2631
+ const startRow = section.startRow ?? 0;
2632
+ const endRow = section.endRow ?? renderRows.length;
2633
+ const repeatCount = opts._repeatHeaderCount ?? 0;
2634
+ const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }, repeatCount) : "";
2635
+ const bodyHtml = renderMatrixRows(renderRows, startRow, endRow, opts, false, ctx, { elementId: el.id, kind: "b" });
2636
+ const subtotalHtml = section.subtotal ? renderSubtotalRows(el, section, opts, ctx) : "";
2637
+ const summaryHtml = section.summary ? renderSummaryRows(el, opts, ctx) : "";
2638
+ return `<div class="print-element" style="${style};overflow:visible;">
2639
+ ${matrixTableHtml(el, `${repeatHtml}
2640
+ ${bodyHtml}${subtotalHtml}${summaryHtml}`)}
2641
+ </div>`;
2642
+ }
2643
+ function renderSubtotalRows(el, section, opts, ctx) {
2644
+ const templates = opts._subtotalTemplates ?? [];
2645
+ if (templates.length === 0) return "";
2646
+ const dataRowCtx = opts._dataRowCtx ?? [];
2647
+ const dataStartIdx = opts._dataStartIdx ?? 0;
2648
+ const mainData = opts._mainData ?? {};
2649
+ const bodyLen = opts._renderRows?.length ?? 0;
2650
+ const startRow = section.startRow ?? 0;
2651
+ const endRow = section.endRow ?? bodyLen;
2652
+ const dataStart = Math.max(startRow, dataStartIdx);
2653
+ const dataEnd = Math.max(endRow, dataStart);
2654
+ const pageCtx = dataEnd > dataStartIdx ? dataRowCtx.slice(Math.max(dataStart - dataStartIdx, 0), dataEnd - dataStartIdx) : [];
2655
+ const pageVars = ctx?.pageVars ? pageVarsContext(ctx.pageVars) : {};
2656
+ const rows = templates.map((tpl) => ({
2657
+ ...tpl,
2658
+ cells: tpl.cells.map((cell) => cell.rawFormatter ? {
2659
+ ...cell,
2660
+ content: evaluateTemplate(cell.rawFormatter, { rows: pageCtx, ...mainData, ...pageVars }),
2661
+ // 已按本页上下文求值,清标记避免渲染时再按页码重算一次(会丢掉 rows)
2662
+ rawFormatter: ""
2663
+ } : cell)
2664
+ }));
2665
+ return renderMatrixRows(rows, 0, rows.length, opts, false, ctx, { elementId: el.id, kind: "st" });
2666
+ }
2667
+ function renderSummaryRows(el, opts, ctx) {
2668
+ const summaryRows = opts._summaryRows ?? [];
2669
+ if (summaryRows.length === 0) return "";
2670
+ return renderMatrixRows(summaryRows, 0, summaryRows.length, opts, false, ctx, { elementId: el.id, kind: "sm" });
2671
+ }
2672
+ function renderFlowGroup(el, section, template, ctx) {
2673
+ const opts = el.options ?? {};
2674
+ const tableLeft = opts.left ?? 0;
2675
+ const tableWidth = opts.width ?? 100;
2676
+ const groupTop = section.groupTop ?? opts.top ?? 0;
2677
+ const tableBottom = tableDesignBottom(el);
2678
+ const zStyle = typeof opts.zIndex === "number" ? `z-index:${opts.zIndex};` : "";
2679
+ const style = `position:absolute;left:${mm(tableLeft)};top:${mm(groupTop)};width:${mm(tableWidth)};overflow:visible;${zStyle}`;
2680
+ const startRow = section.startRow ?? 0;
2681
+ const endRow = section.endRow ?? 0;
2682
+ let sliceHtml = "";
2683
+ if (endRow > startRow || section.subtotal || section.summary) {
2684
+ const renderRows = opts._renderRows ?? [];
2685
+ const repeatCount = opts._repeatHeaderCount ?? 0;
2686
+ const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }, repeatCount) : "";
2687
+ const bodyHtml = renderMatrixRows(renderRows, startRow, endRow, opts, false, ctx, { elementId: el.id, kind: "b" });
2688
+ const subtotalHtml = section.subtotal ? renderSubtotalRows(el, section, opts, ctx) : "";
2689
+ const summaryHtml = section.summary ? renderSummaryRows(el, opts, ctx) : "";
2690
+ sliceHtml = `<div class="flow-slice" style="position:relative;width:${mm(tableWidth)};overflow:visible;">
2691
+ ${matrixTableHtml(el, `${repeatHtml}
2692
+ ${bodyHtml}${subtotalHtml}${summaryHtml}`)}
2693
+ </div>`;
2694
+ }
2695
+ const followIds = section.followElementIds ?? [];
2696
+ const followExtent = followIds.reduce((maxBottom, id) => {
2697
+ const m = findElement(template, id);
2698
+ if (!m) return maxBottom;
2699
+ return Math.max(maxBottom, (m.options?.top ?? 0) + (m.options?.height ?? 0));
2700
+ }, tableBottom);
2701
+ const followHtml = followIds.map((id) => {
2702
+ const m = findElement(template, id);
2703
+ if (!m) return "";
2704
+ const mTop = m.options?.top ?? 0;
2705
+ const mLeft = m.options?.left ?? 0;
2706
+ const mWidth = m.options?.width ?? tableWidth;
2707
+ const mHeight = m.options?.height ?? 0;
2708
+ const mZ = m.options?.zIndex;
2709
+ const type = m.type || m.printElementType?.type || "text";
2710
+ const needsHeight = type === "rect" || type === "oval" || type === "image";
2711
+ const flowStyle = [
2712
+ "position:absolute",
2713
+ `left:${mm(mLeft - tableLeft)}`,
2714
+ `top:${mm(mTop - tableBottom)}`,
2715
+ `width:${mm(mWidth)}`,
2716
+ ...needsHeight && mHeight > 0 ? [`height:${mm(mHeight)}`] : [],
2717
+ ...typeof mZ === "number" ? [`z-index:${mZ}`] : [],
2718
+ "overflow:visible"
2719
+ ].join(";") + ";";
2720
+ return renderElement(m, false, flowStyle, void 0, ctx);
2721
+ }).join("\n");
2722
+ const followWrap = followIds.length > 0 ? `<div class="flow-follow" style="position:relative;width:${mm(tableWidth)};height:${mm(Math.max(followExtent - tableBottom, 0))};overflow:visible;">
2723
+ ${followHtml}
2724
+ </div>` : "";
2725
+ return `<div class="flow-group" style="${style}">
2726
+ ${sliceHtml}
2727
+ ${followWrap}
2728
+ </div>`;
2729
+ }
2730
+ function renderAreaElements(elements, _contentWidth, pageIndex, totalPages, ctx) {
2731
+ return elements.map((el) => {
2732
+ let html = renderAreaElement(el, ctx);
2733
+ if (pageIndex !== void 0) {
2734
+ html = html.replace(/\{pageIndex\}/g, String(pageIndex));
2735
+ }
2736
+ if (totalPages !== void 0) {
2737
+ html = html.replace(/\{totalPages\}/g, String(totalPages));
2738
+ }
2739
+ return html;
2740
+ }).join("\n");
2741
+ }
2742
+ function renderAreaElement(el, ctx) {
2743
+ return renderElement(el, false, void 0, void 0, ctx);
2744
+ }
2745
+ function findElement(template, id) {
2746
+ return template.elements.find((el) => el.id === id);
2747
+ }
2748
+ function getPaperDims(template) {
2749
+ return getPaperDimensions(template);
2750
+ }
2751
+ function esc(str) {
2752
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
2753
+ }
2754
+
2755
+ // src/render/continuous-paper.ts
2756
+ var MIN_CONTINUOUS_HEIGHT_MM = 25.4;
2757
+ function composeContinuousHeight(template, contentBottomMm) {
2758
+ const mb = template.margins?.bottom ?? 0;
2759
+ const footerH = template.footer?.height ?? 0;
2760
+ const height = Math.max(contentBottomMm, 0) + footerH + mb;
2761
+ return Math.max(MIN_CONTINUOUS_HEIGHT_MM, Math.round(height * 100) / 100);
2762
+ }
2763
+
2764
+ // src/print/units.ts
2765
+ var PX_PER_MM2 = 3.7795275591;
2766
+ var MICROMETERS_PER_MM = 1e3;
2767
+ var MM_PER_INCH2 = 25.4;
2768
+ var MICROMETERS_PER_INCH = 25400;
2769
+ function pxToMm2(px) {
2770
+ return px / PX_PER_MM2;
2771
+ }
2772
+ function mmToPx2(mm2) {
2773
+ return Math.round(mm2 * PX_PER_MM2);
2774
+ }
2775
+ function millimetersToMicrometers(mm2) {
2776
+ return Math.round(mm2 * MICROMETERS_PER_MM);
2777
+ }
2778
+ function micrometersToMillimeters(um) {
2779
+ return um / MICROMETERS_PER_MM;
2780
+ }
2781
+ function millimetersToInches(mm2) {
2782
+ return mm2 / MM_PER_INCH2;
2783
+ }
2784
+
2785
+ // src/print/errors.ts
2786
+ var PrintFailure = class extends Error {
2787
+ constructor(code, message, cause) {
2788
+ super(message);
2789
+ this.name = "PrintFailure";
2790
+ this.code = code;
2791
+ this.cause = cause;
2792
+ }
2793
+ };
2794
+ function toPrintFailure(err, fallbackCode, context) {
2795
+ if (err instanceof PrintFailure) return err;
2796
+ const detail = err instanceof Error ? err.message : String(err);
2797
+ return new PrintFailure(fallbackCode, `${context}\uFF1A${detail}`, err);
2798
+ }
2799
+ async function withTimeout(task, ms, code, message) {
2800
+ let timer;
2801
+ try {
2802
+ return await Promise.race([
2803
+ task,
2804
+ new Promise((_resolve, reject) => {
2805
+ timer = setTimeout(() => reject(new PrintFailure(code, message)), ms);
2806
+ })
2807
+ ]);
2808
+ } finally {
2809
+ if (timer) clearTimeout(timer);
2810
+ }
2811
+ }
2812
+
2813
+ // src/print/paper.ts
2814
+ function positive(value) {
2815
+ return typeof value === "number" && value > 0 ? value : void 0;
2816
+ }
2817
+ function escapeHeightMm(input) {
2818
+ return positive(input.paperHeightMm) ?? positive(input.override?.height);
2819
+ }
2820
+ function resolvePaperMm(input) {
2821
+ const width = positive(input.override?.width) ?? input.paperMm.width;
2822
+ const overrideHeight = positive(input.override?.height);
2823
+ if (input.continuous) {
2824
+ return overrideHeight ? { paperMm: { width, height: overrideHeight }, heightSource: "config" } : { paperMm: { width, height: input.paperMm.height }, heightSource: "derived" };
2825
+ }
2826
+ return { paperMm: { width, height: overrideHeight ?? input.paperMm.height }, heightSource: "config" };
2827
+ }
2828
+ function paperViewportPx(paper) {
2829
+ return { width: mmToPx2(paper.width), height: mmToPx2(paper.height) };
2830
+ }
2831
+
2832
+ // src/print/pdf-spec.ts
2833
+ var ZERO_MARGINS_MM = { top: 0, right: 0, bottom: 0, left: 0 };
2834
+ function buildPdfTargetSpec(paperMm) {
2835
+ return {
2836
+ paperMm,
2837
+ marginsMm: { ...ZERO_MARGINS_MM },
2838
+ printBackground: true,
2839
+ scale: 1,
2840
+ preferCSSPageSize: false
2841
+ };
2842
+ }
2843
+ function toElectronPrintToPdfOptions(spec) {
2844
+ return {
2845
+ margins: { ...spec.marginsMm },
2846
+ pageSize: {
2847
+ width: millimetersToInches(spec.paperMm.width),
2848
+ height: millimetersToInches(spec.paperMm.height)
2849
+ },
2850
+ printBackground: spec.printBackground,
2851
+ scale: spec.scale,
2852
+ preferCSSPageSize: spec.preferCSSPageSize
2853
+ };
2854
+ }
2855
+ function toPlaywrightPdfOptions(spec) {
2856
+ const mm2 = (value) => `${value}mm`;
2857
+ return {
2858
+ width: mm2(spec.paperMm.width),
2859
+ height: mm2(spec.paperMm.height),
2860
+ margin: {
2861
+ top: mm2(spec.marginsMm.top),
2862
+ right: mm2(spec.marginsMm.right),
2863
+ bottom: mm2(spec.marginsMm.bottom),
2864
+ left: mm2(spec.marginsMm.left)
2865
+ },
2866
+ printBackground: spec.printBackground,
2867
+ preferCSSPageSize: spec.preferCSSPageSize
2868
+ };
2869
+ }
2870
+ function buildScreenshotTargetSpec() {
2871
+ return { type: "png", fullPage: true, omitBackground: false };
2872
+ }
2873
+
2874
+ // src/print/measure.ts
2875
+ function normalizeMeasurements(raw, template) {
2876
+ const index = new Map(template.elements.map((el) => [el.id, el]));
2877
+ const measured = /* @__PURE__ */ new Map();
2878
+ for (const item of raw) {
2879
+ const element = index.get(item.id);
2880
+ const repeatCount = element?.options?._repeatHeaderCount ?? 0;
2881
+ const rowHeights = item.rowHeightsPx?.map(pxToMm2);
2882
+ measured.set(item.id, {
2883
+ id: item.id,
2884
+ measuredHeight: pxToMm2(item.heightPx),
2885
+ measuredRowHeights: rowHeights,
2886
+ repeatHeaderHeight: rowHeights && rowHeights.length > 0 && repeatCount > 0 ? rowHeights.slice(0, repeatCount).reduce((sum2, h) => sum2 + h, 0) : 0
2887
+ });
2888
+ }
2889
+ return measured;
2890
+ }
2891
+
2892
+ // src/print/apply-text-fit.ts
2893
+ var CELL_ROW_SOURCES = {
2894
+ b: "_renderRows",
2895
+ st: "_subtotalTemplates",
2896
+ sm: "_summaryRows"
2897
+ };
2898
+ function applyTextFitSizes(template, fits) {
2899
+ if (!fits || fits.length === 0) return;
2900
+ const index = /* @__PURE__ */ new Map();
2901
+ const collect = (elements) => {
2902
+ for (const el of elements ?? []) index.set(el.id, el);
2903
+ };
2904
+ collect(template.elements);
2905
+ collect(template.header?.elements);
2906
+ collect(template.footer?.elements);
2907
+ collect(template.firstPageOverlay?.elements);
2908
+ for (const fit of fits) {
2909
+ const parsed = parseCellFitKey(fit.key);
2910
+ if (!parsed) {
2911
+ const el2 = index.get(fit.key);
2912
+ if (el2) el2.options._fitFontSize = roundFontSize(fit.fontSizePt);
2913
+ continue;
2914
+ }
2915
+ const el = index.get(parsed.elementId);
2916
+ const rows = el?.options?.[CELL_ROW_SOURCES[parsed.kind]];
2917
+ const cell = rows?.[parsed.rowIndex]?.cells?.[parsed.colIndex];
2918
+ if (cell) cell.fittedFontSize = roundFontSize(fit.fontSizePt);
2919
+ }
2920
+ }
2921
+
2922
+ // src/print/codes.ts
2923
+ function codeSpecKey(value, cellType, opts = {}) {
2924
+ return JSON.stringify([
2925
+ value,
2926
+ cellType,
2927
+ opts.barcodeType ?? null,
2928
+ opts.qrCodeLevel ?? null,
2929
+ opts.showText ?? null,
2930
+ opts.barWidth ?? null,
2931
+ opts.fontSize ?? null
2932
+ ]);
2933
+ }
2934
+ function createMapCodeRenderer(map) {
2935
+ return {
2936
+ render(value, cellType, opts) {
2937
+ const svg = map.get(codeSpecKey(value, cellType, opts));
2938
+ if (!svg) throw new Error(`\u7801\u503C\u672A\u6E32\u67D3\uFF1A${value}`);
2939
+ return svg;
2940
+ }
2941
+ };
2942
+ }
2943
+ function createCollectingCodeRenderer(base) {
2944
+ const collected = /* @__PURE__ */ new Map();
2945
+ return {
2946
+ renderer: {
2947
+ render(value, cellType, opts = {}) {
2948
+ const key = codeSpecKey(value, cellType, opts);
2949
+ const hit = base?.get(key);
2950
+ if (hit) return hit;
2951
+ collected.set(key, { key, value, cellType, opts });
2952
+ throw new Error("collect");
2953
+ }
2954
+ },
2955
+ takeSpecs() {
2956
+ const specs = [...collected.values()];
2957
+ collected.clear();
2958
+ return specs;
2959
+ }
2960
+ };
2961
+ }
2962
+ function mergeCodeMaps(...maps) {
2963
+ const merged = /* @__PURE__ */ new Map();
2964
+ for (const map of maps) {
2965
+ for (const [key, svg] of map) merged.set(key, svg);
2966
+ }
2967
+ return merged;
2968
+ }
2969
+
2970
+ // src/print/driver.ts
2971
+ var EXECUTOR_TARGETS = {
2972
+ waitReady: "window",
2973
+ readMeasurements: "document",
2974
+ readContentBottom: "document",
2975
+ renderCodes: "none",
2976
+ applyTextFit: "document"
2977
+ };
2978
+
2979
+ // src/print/ports.ts
2980
+ var DEFAULT_TIMEOUT_MS = 3e4;
2981
+ var DEFAULT_READINESS_MS = 5e3;
2982
+
2983
+ // src/print/dom-host-runtime.ts
2984
+ function createDomHostRuntime(factory, bundle) {
2985
+ return {
2986
+ async withSession(options, fn) {
2987
+ const driver = await factory.createDriver();
2988
+ const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
2989
+ const readinessMs = options.readinessMs ?? DEFAULT_READINESS_MS;
2990
+ const budget = () => Math.max(1, deadline - Date.now());
2991
+ const fail = (err, code, context) => toPrintFailure(err, code, context);
2992
+ let injected = false;
2993
+ const ensureExecutor = async () => {
2994
+ if (injected) return;
2995
+ if (driver.requiresExecutor === false) {
2996
+ injected = true;
2997
+ return;
2998
+ }
2999
+ if (!bundle) throw new PrintFailure("INTERNAL", "\u7F3A\u5C11 core DOM \u6267\u884C\u5668\u4EA7\u7269");
3000
+ await driver.injectExecutor(bundle);
3001
+ injected = true;
3002
+ };
3003
+ const load = async (html, viewport) => {
3004
+ await driver.open(viewport);
3005
+ await driver.setContent(html);
3006
+ injected = false;
3007
+ await ensureExecutor();
3008
+ await driver.evaluate("waitReady", [readinessMs]);
3009
+ };
3010
+ const session = {
3011
+ async renderCodes(specs) {
3012
+ if (specs.length === 0) return /* @__PURE__ */ new Map();
3013
+ const ms = budget();
3014
+ return withTimeout(
3015
+ (async () => {
3016
+ try {
3017
+ await ensureExecutor();
3018
+ const map = await driver.evaluate("renderCodes", [specs]);
3019
+ return new Map(Object.entries(map));
3020
+ } catch (err) {
3021
+ throw fail(err, "INTERNAL", "\u7801\u503C\u6E32\u67D3\u5931\u8D25");
3022
+ }
3023
+ })(),
3024
+ ms,
3025
+ "RENDER_TIMEOUT",
3026
+ `\u7801\u503C\u6E32\u67D3\u8D85\u8FC7 ${ms}ms`
3027
+ );
3028
+ },
3029
+ async measure(html, viewport) {
3030
+ const ms = budget();
3031
+ return withTimeout(
3032
+ (async () => {
3033
+ try {
3034
+ await load(html, viewport);
3035
+ const fits = await driver.evaluate("applyTextFit") ?? [];
3036
+ const measurements = await driver.evaluate("readMeasurements");
3037
+ return { measurements, fits };
3038
+ } catch (err) {
3039
+ throw fail(err, "MEASURE_FAILED", "\u6D4B\u91CF\u5931\u8D25");
3040
+ }
3041
+ })(),
3042
+ ms,
3043
+ "RENDER_TIMEOUT",
3044
+ `\u6D4B\u91CF\u8D85\u8FC7 ${ms}ms`
3045
+ );
3046
+ },
3047
+ async probeContentBottom(html, viewport) {
3048
+ const ms = budget();
3049
+ return withTimeout(
3050
+ (async () => {
3051
+ try {
3052
+ await load(html, viewport);
3053
+ return await driver.evaluate("readContentBottom");
3054
+ } catch (err) {
3055
+ throw fail(err, "MEASURE_FAILED", "\u8FDE\u7EED\u7EB8\u63A2\u9488\u5931\u8D25");
3056
+ }
3057
+ })(),
3058
+ ms,
3059
+ "RENDER_TIMEOUT",
3060
+ `\u8FDE\u7EED\u7EB8\u63A2\u9488\u8D85\u8FC7 ${ms}ms`
3061
+ );
3062
+ },
3063
+ async toPdf(html, spec, viewport) {
3064
+ if (!driver.pdf) throw new PrintFailure("UNSUPPORTED_RUNTIME", "\u5F53\u524D\u5BBF\u4E3B\u4E0D\u652F\u6301\u751F\u6210 PDF");
3065
+ const ms = budget();
3066
+ return withTimeout(
3067
+ (async () => {
3068
+ try {
3069
+ await load(html, viewport);
3070
+ return await driver.pdf(html, spec);
3071
+ } catch (err) {
3072
+ throw fail(err, "PDF_FAILED", "PDF \u751F\u6210\u5931\u8D25");
3073
+ }
3074
+ })(),
3075
+ ms,
3076
+ "RENDER_TIMEOUT",
3077
+ `PDF \u751F\u6210\u8D85\u8FC7 ${ms}ms`
3078
+ );
3079
+ },
3080
+ async toScreenshot(html, spec, viewport) {
3081
+ if (!driver.screenshot) throw new PrintFailure("UNSUPPORTED_RUNTIME", "\u5F53\u524D\u5BBF\u4E3B\u4E0D\u652F\u6301\u622A\u56FE");
3082
+ const ms = budget();
3083
+ return withTimeout(
3084
+ (async () => {
3085
+ try {
3086
+ await load(html, viewport);
3087
+ return await driver.screenshot(html, spec);
3088
+ } catch (err) {
3089
+ throw fail(err, "SCREENSHOT_FAILED", "\u622A\u56FE\u5931\u8D25");
3090
+ }
3091
+ })(),
3092
+ ms,
3093
+ "RENDER_TIMEOUT",
3094
+ `\u622A\u56FE\u8D85\u8FC7 ${ms}ms`
3095
+ );
3096
+ }
3097
+ };
3098
+ try {
3099
+ return await fn(session);
3100
+ } finally {
3101
+ try {
3102
+ await driver.close();
3103
+ } catch {
3104
+ }
3105
+ }
3106
+ }
3107
+ };
3108
+ }
3109
+
3110
+ // src/print/normalize-print-data.ts
3111
+ var MAX_BATCH_COPIES = 500;
3112
+ function isPlainRecord(v) {
3113
+ return typeof v === "object" && v !== null && !Array.isArray(v);
3114
+ }
3115
+ function normalizePrintData(raw) {
3116
+ if (raw === void 0) return { mode: "single", data: {} };
3117
+ if (!Array.isArray(raw)) return { mode: "single", data: raw };
3118
+ if (raw.length === 0) {
3119
+ throw new Error("\u6279\u91CF\u6253\u5370\u6570\u636E\u5FC5\u987B\u662F\u975E\u7A7A\u5BF9\u8C61\u6570\u7EC4");
3120
+ }
3121
+ if (raw.length > MAX_BATCH_COPIES) {
3122
+ throw new Error(`\u6279\u91CF\u6253\u5370\u6700\u591A\u652F\u6301 ${MAX_BATCH_COPIES} \u4EFD\uFF0C\u5F53\u524D ${raw.length} \u4EFD`);
3123
+ }
3124
+ for (let i = 0; i < raw.length; i++) {
3125
+ if (!isPlainRecord(raw[i])) {
3126
+ throw new Error(`\u6279\u91CF\u6253\u5370\u6570\u636E\u7B2C ${i + 1} \u9879\u5FC5\u987B\u662F\u5BF9\u8C61`);
3127
+ }
3128
+ }
3129
+ return { mode: "batch", dataList: raw };
3130
+ }
3131
+
3132
+ // src/print/batch-compose.ts
3133
+ function composeBatchHtml(copies) {
3134
+ const bound = copies[0].bound;
3135
+ const continuous = isContinuousPaper(bound);
3136
+ const bodyInner = copies.map((copy, i) => {
3137
+ const cls = continuous ? `print-copy print-copy-${i}` : "print-copy";
3138
+ const pages = renderFinalPages(copy.bound, copy.pageLayouts, copy.data, {
3139
+ codeRenderer: copy.codeRenderer,
3140
+ pageHeightMm: copy.derivedHeightMm
3141
+ });
3142
+ return `<section class="${cls}">
3143
+ ${pages}
3144
+ </section>`;
3145
+ }).join("\n");
3146
+ const pageCss = continuous ? buildBatchPageCss(bound, copies.map((c) => ({ heightMm: c.derivedHeightMm }))) : `${buildPageCss(bound)}
3147
+ .print-copy:not(:last-child){break-after:page;page-break-after:always;}`;
3148
+ const css = buildFontFaceCss(bound.fonts) + pageCss;
3149
+ return {
3150
+ html: wrapHtmlDocument(
3151
+ css,
3152
+ bodyInner,
3153
+ continuous ? "continuous" : void 0
3154
+ ),
3155
+ pageCount: copies.reduce((sum2, c) => sum2 + c.pageLayouts.length, 0),
3156
+ copyPaperMm: copies.map((c) => c.paperMm)
3157
+ };
3158
+ }
3159
+
3160
+ // src/print/tiling.ts
3161
+ var TILE_DEFAULTS = {
3162
+ enabled: true,
3163
+ sheetPaperSize: "A4",
3164
+ sheetOrientation: "portrait",
3165
+ sheetMargin: { top: 10, right: 10, bottom: 10, left: 10 },
3166
+ gapX: 2,
3167
+ gapY: 2,
3168
+ columns: 2
3169
+ };
3170
+ var TilingError = class extends Error {
3171
+ constructor(code, message) {
3172
+ super(message);
3173
+ this.name = "TilingError";
3174
+ this.code = code;
3175
+ }
3176
+ };
3177
+ function roundMm(value) {
3178
+ return Math.round(value * 10) / 10;
3179
+ }
3180
+ function normalizeTilingOptions(t) {
3181
+ const raw = t.tiling;
3182
+ return {
3183
+ ...TILE_DEFAULTS,
3184
+ ...raw,
3185
+ sheetMargin: { ...TILE_DEFAULTS.sheetMargin, ...raw?.sheetMargin ?? {} }
3186
+ };
3187
+ }
3188
+ function resolveSheetMm(t, opts) {
3189
+ const cfg = normalizeTilingOptions(t);
3190
+ const ov = opts?.paperOverride;
3191
+ if (ov && typeof ov.width === "number" && ov.width > 0 && typeof ov.height === "number" && ov.height > 0) {
3192
+ return { width: ov.width, height: ov.height };
3193
+ }
3194
+ if (cfg.sheetPaperSize === "CUSTOM") {
3195
+ return {
3196
+ width: cfg.sheetCustomWidth ?? PAPER_DIMENSIONS.A4.width,
3197
+ height: cfg.sheetCustomHeight ?? PAPER_DIMENSIONS.A4.height
3198
+ };
3199
+ }
3200
+ const preset = cfg.sheetPaperSize ?? "A4";
3201
+ const base = PAPER_DIMENSIONS[preset] ?? PAPER_DIMENSIONS.A4;
3202
+ return cfg.sheetOrientation === "landscape" ? { width: base.height, height: base.width } : { ...base };
3203
+ }
3204
+ function availableArea(sheet, margin) {
3205
+ return {
3206
+ w: sheet.width - margin.left - margin.right,
3207
+ h: sheet.height - margin.top - margin.bottom
3208
+ };
3209
+ }
3210
+ function calcMaxColumns(availW, labelW, gapX) {
3211
+ return Math.max(0, Math.floor((availW + gapX) / (labelW + gapX)));
3212
+ }
3213
+ function computeTileLayout(t, opts) {
3214
+ const issues = validateTiling(t, opts);
3215
+ if (issues.length) throw new TilingError(issues[0].code, issues[0].message);
3216
+ const cfg = normalizeTilingOptions(t);
3217
+ const sheet = resolveSheetMm(t, opts);
3218
+ const label = getPaperDimensions(t);
3219
+ const avail = availableArea(sheet, cfg.sheetMargin);
3220
+ const rows = Math.floor((avail.h + cfg.gapY) / (label.height + cfg.gapY));
3221
+ return {
3222
+ tile: { width: label.width, height: label.height },
3223
+ sheet,
3224
+ columns: cfg.columns,
3225
+ rows,
3226
+ perSheet: cfg.columns * rows,
3227
+ maxColumns: calcMaxColumns(avail.w, label.width, cfg.gapX),
3228
+ margin: { ...cfg.sheetMargin },
3229
+ gapX: cfg.gapX,
3230
+ gapY: cfg.gapY
3231
+ };
3232
+ }
3233
+ function tilePosition(layout, index) {
3234
+ const slot = index % layout.perSheet;
3235
+ const col = slot % layout.columns;
3236
+ const row = Math.floor(slot / layout.columns);
3237
+ return {
3238
+ left: roundMm(layout.margin.left + col * (layout.tile.width + layout.gapX)),
3239
+ top: roundMm(layout.margin.top + row * (layout.tile.height + layout.gapY))
3240
+ };
3241
+ }
3242
+ function computeMaxColumns(t, opts) {
3243
+ const cfg = normalizeTilingOptions(t);
3244
+ const sheet = resolveSheetMm(t, opts);
3245
+ const label = getPaperDimensions(t);
3246
+ const availW = sheet.width - cfg.sheetMargin.left - cfg.sheetMargin.right;
3247
+ return calcMaxColumns(availW, label.width, cfg.gapX);
3248
+ }
3249
+ function validateTiling(t, opts) {
3250
+ const issues = [];
3251
+ const cfg = normalizeTilingOptions(t);
3252
+ if (isContinuousPaper(t)) {
3253
+ issues.push({
3254
+ code: "CONTINUOUS_UNSUPPORTED",
3255
+ message: "\u8FDE\u7EED\u7EB8\u4E0D\u652F\u6301\u62FC\u7248\u6253\u5370\uFF0C\u8BF7\u5C06\u6A21\u677F\u7EB8\u5F20\u6539\u4E3A\u56FA\u5B9A\u7EB8\u5F20\u6216\u5173\u95ED\u62FC\u7248"
3256
+ });
3257
+ }
3258
+ const sheetPaperSize = cfg.sheetPaperSize;
3259
+ const sheetContinuous = isContinuousPaperSize(sheetPaperSize ?? "");
3260
+ if (sheetContinuous) {
3261
+ issues.push({ code: "SHEET_CONTINUOUS", message: "\u62FC\u7248\u76EE\u6807\u7EB8\u5F20\u4E0D\u80FD\u662F\u8FDE\u7EED\u7EB8" });
3262
+ }
3263
+ const customSizeInvalid = cfg.sheetPaperSize === "CUSTOM" && !((cfg.sheetCustomWidth ?? 0) > 0 && (cfg.sheetCustomHeight ?? 0) > 0);
3264
+ if (customSizeInvalid) {
3265
+ issues.push({
3266
+ code: "SHEET_SIZE_INVALID",
3267
+ message: "\u62FC\u7248\u81EA\u5B9A\u4E49\u7EB8\u5F20\u5BBD\u9AD8\u5FC5\u987B\u662F\u5927\u4E8E 0 \u7684\u6570\u503C\uFF08mm\uFF09"
3268
+ });
3269
+ }
3270
+ if (customSizeInvalid || sheetContinuous) return issues;
3271
+ const sheet = resolveSheetMm(t, opts);
3272
+ const label = getPaperDimensions(t);
3273
+ const avail = availableArea(sheet, cfg.sheetMargin);
3274
+ const marginLR = roundMm(cfg.sheetMargin.left + cfg.sheetMargin.right);
3275
+ if (!Number.isInteger(cfg.columns) || cfg.columns < 1) {
3276
+ issues.push({ code: "COLUMNS_INVALID", message: "\u62FC\u7248\u5217\u6570\u5FC5\u987B\u662F\u5927\u4E8E 0 \u7684\u6574\u6570" });
3277
+ return issues;
3278
+ }
3279
+ const maxColumns = calcMaxColumns(avail.w, label.width, cfg.gapX);
3280
+ if (cfg.columns > maxColumns) {
3281
+ const needW = roundMm(cfg.columns * label.width + (cfg.columns - 1) * cfg.gapX);
3282
+ issues.push({
3283
+ code: "COLUMNS_OVERFLOW",
3284
+ message: "\u62FC\u7248\u5217\u6570 " + cfg.columns + " \u8D85\u51FA\u7EB8\u9762\u53EF\u7528\u5BBD\u5EA6\uFF1A" + roundMm(sheet.width) + "mm \u2212 \u5DE6\u53F3\u7559\u767D " + marginLR + "mm = " + roundMm(avail.w) + "mm\uFF0C\u6700\u591A\u53EF\u653E " + maxColumns + " \u5217\uFF1B\u5F53\u524D " + cfg.columns + " \u5217 " + label.width + "mm \u6807\u7B7E\u542B\u95F4\u8DDD\u9700\u8981 " + needW + "mm"
3285
+ });
3286
+ }
3287
+ if (Math.floor((avail.h + cfg.gapY) / (label.height + cfg.gapY)) < 1) {
3288
+ issues.push({
3289
+ code: "LABEL_TOO_TALL",
3290
+ message: "\u6807\u7B7E\u9AD8\u5EA6 " + label.height + "mm \u8D85\u51FA\u7EB8\u9762\u53EF\u7528\u9AD8\u5EA6 " + roundMm(avail.h) + "mm\uFF0C\u62FC\u7248\u6BCF\u5F20 0 \u884C\uFF1B\u8BF7\u7F29\u5C0F\u6807\u7B7E\u9AD8\u5EA6\u6216\u6539\u7528\u6A2A\u5411\u7EB8"
3291
+ });
3292
+ }
3293
+ return issues;
3294
+ }
3295
+
3296
+ // src/print/tile-compose.ts
3297
+ function composeTiledHtml(input) {
3298
+ const { copies, layout } = input;
3299
+ const bound = copies[0].bound;
3300
+ const sheets = [];
3301
+ for (let start = 0; start < copies.length; start += layout.perSheet) {
3302
+ const tiles = copies.slice(start, start + layout.perSheet).map((copy, i) => {
3303
+ const pos = tilePosition(layout, i);
3304
+ const page = renderFinalPages(copy.bound, copy.pageLayouts, copy.data, {
3305
+ codeRenderer: copy.codeRenderer
3306
+ });
3307
+ return `<div class="print-tile" style="left:${pos.left}mm;top:${pos.top}mm">
3308
+ ${page}
3309
+ </div>`;
3310
+ }).join("\n");
3311
+ sheets.push(`<section class="print-sheet">
3312
+ ${tiles}
3313
+ </section>`);
3314
+ }
3315
+ const css = buildFontFaceCss(bound.fonts) + buildPageCss(bound) + "\n" + buildSheetPageCss(layout);
3316
+ return {
3317
+ html: wrapHtmlDocument(css, sheets.join("\n")),
3318
+ sheetCount: sheets.length,
3319
+ perSheet: layout.perSheet
3320
+ };
3321
+ }
3322
+
3323
+ // src/print/multi-template.ts
3324
+ function pageLabel(p, index) {
3325
+ return p.name ? `\u7B2C ${index + 1} \u9875\u300C${p.name}\u300D` : `\u7B2C ${index + 1} \u9875`;
3326
+ }
3327
+ function normalizeTemplate(templateJson) {
3328
+ const pages = isMultiPageTemplate(templateJson) ? templateJson.pages : [templateJson];
3329
+ if (pages.length === 0) {
3330
+ throw new Error("\u591A\u9875\u9762\u6A21\u677F\u81F3\u5C11\u9700\u8981\u4E00\u9875");
3331
+ }
3332
+ if (pages.length === 1) return pages;
3333
+ const firstPaper = getPaperDimensions(pages[0]);
3334
+ pages.forEach((p, i) => {
3335
+ if (isContinuousPaper(p)) {
3336
+ throw new Error(`\u591A\u9875\u9762\u6A21\u677F\u4E0D\u652F\u6301\u8FDE\u7EED\u7EB8\uFF08${pageLabel(p, i)}\uFF09`);
3337
+ }
3338
+ if (p.tiling?.enabled === true) {
3339
+ throw new Error(`\u591A\u9875\u9762\u6A21\u677F\u4E0D\u652F\u6301\u6807\u7B7E\u62FC\u7248\uFF08${pageLabel(p, i)}\uFF09`);
3340
+ }
3341
+ const d = getPaperDimensions(p);
3342
+ if (d.width !== firstPaper.width || d.height !== firstPaper.height) {
3343
+ throw new Error(
3344
+ `\u591A\u9875\u9762\u6A21\u677F\u5404\u9875\u7EB8\u5F20\u5C3A\u5BF8\u5FC5\u987B\u4E00\u81F4\uFF08${pageLabel(p, i)} ${d.width}\xD7${d.height}mm \u4E0E\u9996\u9875 ${firstPaper.width}\xD7${firstPaper.height}mm \u4E0D\u540C\uFF09`
3345
+ );
3346
+ }
3347
+ });
3348
+ return pages;
3349
+ }
3350
+ function isMultiPageTemplate(templateJson) {
3351
+ return Array.isArray(templateJson.pages);
3352
+ }
3353
+ function mergeFontDeclarations(pages) {
3354
+ const seen = /* @__PURE__ */ new Set();
3355
+ const merged = [];
3356
+ for (const p of pages) {
3357
+ for (const f of p.fonts ?? []) {
3358
+ const key = f.family;
3359
+ if (seen.has(key)) continue;
3360
+ seen.add(key);
3361
+ merged.push(f);
3362
+ }
3363
+ }
3364
+ return merged;
3365
+ }
3366
+ function composeMultiPageDocument(copies) {
3367
+ if (copies.length === 0) {
3368
+ throw new Error("\u591A\u9875\u9762\u6A21\u677F\u81F3\u5C11\u9700\u8981\u4E00\u4EFD\u6570\u636E");
3369
+ }
3370
+ const templates = copies[0].boundPages;
3371
+ const css = buildFontFaceCss(mergeFontDeclarations(templates)) + [
3372
+ buildPageRuleCss(templates[0]),
3373
+ buildBasePageCss(),
3374
+ ...templates.map((t, i) => buildPageGeometryCss(t, `.mt-${i}`)),
3375
+ ...copies.length > 1 ? [COPY_BREAK_CSS] : []
3376
+ ].join("\n\n");
3377
+ const bodyInner = copies.map((copy) => {
3378
+ const totalPages = copy.layoutsPerPage.reduce((s, ls) => s + ls.length, 0);
3379
+ let offset = 0;
3380
+ const fragments = copy.layoutsPerPage.map((layouts, i) => {
3381
+ const html = renderFinalPages(
3382
+ copy.boundPages[i],
3383
+ layouts,
3384
+ copy.data,
3385
+ { codeRenderer: copy.codeRenderers?.[i] },
3386
+ {
3387
+ pageOffset: offset,
3388
+ totalPages,
3389
+ pageClass: `mt-${i}`
3390
+ }
3391
+ );
3392
+ offset += layouts.length;
3393
+ return html;
3394
+ });
3395
+ const pages = fragments.join("\n");
3396
+ return copies.length > 1 ? `<section class="print-copy">
3397
+ ${pages}
3398
+ </section>` : pages;
3399
+ }).join("\n");
3400
+ const pageCount = copies.reduce(
3401
+ (sum2, c) => sum2 + c.layoutsPerPage.reduce((s, ls) => s + ls.length, 0),
3402
+ 0
3403
+ );
3404
+ const pageLayouts = [];
3405
+ let g = 0;
3406
+ for (const c of copies) {
3407
+ for (const ls of c.layoutsPerPage) {
3408
+ for (const p of ls) pageLayouts.push({ ...p, pageIndex: g++ });
3409
+ }
3410
+ }
3411
+ return {
3412
+ html: wrapHtmlDocument(css, bodyInner),
3413
+ pageCount,
3414
+ pageLayouts
3415
+ };
3416
+ }
3417
+
3418
+ // src/print/pipeline.ts
3419
+ async function prepareDocument(job, runtime) {
3420
+ return runtime.withSession(job, (session) => prepareWithSession(job, session));
3421
+ }
3422
+ async function renderPdf(job, runtime) {
3423
+ return runtime.withSession(job, async (session) => {
3424
+ const prepared = await prepareWithSession(job, session);
3425
+ const viewport = paperViewportPx(prepared.paperMm);
3426
+ const pdf = await session.toPdf(prepared.html, buildPdfTargetSpec(prepared.paperMm), viewport);
3427
+ return { pdf, prepared };
3428
+ });
3429
+ }
3430
+ async function renderScreenshot(job, runtime) {
3431
+ return runtime.withSession(job, async (session) => {
3432
+ const pageTemplates = normalizeTemplate(job.templateJson);
3433
+ if (pageTemplates.length > 1) {
3434
+ const normalized2 = normalizePrintData(job.printData);
3435
+ const data2 = normalized2.mode === "batch" ? normalized2.dataList[0] : normalized2.data;
3436
+ const copy = await prepareMultiCopy(job, session, data2);
3437
+ const doc = composeMultiPageDocument([copy]);
3438
+ const viewport2 = paperViewportPx(getPaperDimensions(pageTemplates[0]));
3439
+ return session.toScreenshot(doc.html, buildScreenshotTargetSpec(), viewport2);
3440
+ }
3441
+ const singleJob = pageTemplates[0] !== job.templateJson ? { ...job, templateJson: pageTemplates[0] } : job;
3442
+ const normalized = normalizePrintData(singleJob.printData);
3443
+ const data = normalized.mode === "batch" ? normalized.dataList[0] : normalized.data;
3444
+ const bound = bindData(singleJob.templateJson, data, singleJob.baseUrl, singleJob.fontBaseUrl);
3445
+ const built = await buildHtmlWithCodes({
3446
+ bound,
3447
+ job: singleJob,
3448
+ session,
3449
+ data,
3450
+ pageLayouts: [],
3451
+ isMeasurementPass: true
3452
+ });
3453
+ const viewport = paperViewportPx(getPaperDimensions(bound));
3454
+ return session.toScreenshot(built.html, buildScreenshotTargetSpec(), viewport);
3455
+ });
3456
+ }
3457
+ async function prepareWithSession(job, session) {
3458
+ const pageTemplates = normalizeTemplate(job.templateJson);
3459
+ if (pageTemplates.length > 1) {
3460
+ const normalized2 = normalizePrintData(job.printData);
3461
+ const rows = normalized2.mode === "batch" ? normalized2.dataList : [normalized2.data];
3462
+ const copies2 = [];
3463
+ for (let i = 0; i < rows.length; i++) {
3464
+ try {
3465
+ copies2.push(await prepareMultiCopy(job, session, rows[i]));
3466
+ } catch (err) {
3467
+ const reason = err instanceof Error ? err.message : String(err);
3468
+ throw new Error(`\u7B2C ${i + 1} \u4EFD\u6E32\u67D3\u5931\u8D25\uFF1A${reason}`);
3469
+ }
3470
+ }
3471
+ const doc = composeMultiPageDocument(copies2);
3472
+ return {
3473
+ html: doc.html,
3474
+ pageCount: doc.pageCount,
3475
+ paperMm: getOutputPaperDimensions(pageTemplates[0]),
3476
+ continuous: false,
3477
+ heightSource: "config",
3478
+ pageLayouts: doc.pageLayouts,
3479
+ copies: copies2.length
3480
+ };
3481
+ }
3482
+ const singleJob = pageTemplates[0] !== job.templateJson ? { ...job, templateJson: pageTemplates[0] } : job;
3483
+ const template = pageTemplates[0];
3484
+ const normalized = normalizePrintData(singleJob.printData);
3485
+ if (normalized.mode === "single") {
3486
+ const single = await prepareSingleWithSession(singleJob, session, normalized.data);
3487
+ if (template.tiling?.enabled === true) {
3488
+ return composeTiledPrepared(singleJob, [{
3489
+ bound: single.bound,
3490
+ pageLayouts: single.pageLayouts,
3491
+ data: normalized.data,
3492
+ codeRenderer: single.codeRenderer,
3493
+ derivedHeightMm: single.derivedHeightMm,
3494
+ paperMm: single.paperMm,
3495
+ heightSource: single.heightSource
3496
+ }]);
3497
+ }
3498
+ return toPreparedDocument(single);
3499
+ }
3500
+ const copies = [];
3501
+ for (let i = 0; i < normalized.dataList.length; i++) {
3502
+ try {
3503
+ const single = await prepareSingleWithSession(singleJob, session, normalized.dataList[i]);
3504
+ copies.push({
3505
+ bound: single.bound,
3506
+ pageLayouts: single.pageLayouts,
3507
+ data: normalized.dataList[i],
3508
+ codeRenderer: single.codeRenderer,
3509
+ derivedHeightMm: single.derivedHeightMm,
3510
+ paperMm: single.paperMm,
3511
+ heightSource: single.heightSource
3512
+ });
3513
+ } catch (err) {
3514
+ const reason = err instanceof Error ? err.message : String(err);
3515
+ throw new Error(`\u7B2C ${i + 1} \u4EFD\u6E32\u67D3\u5931\u8D25\uFF1A${reason}`);
3516
+ }
3517
+ }
3518
+ if (template.tiling?.enabled === true) {
3519
+ return composeTiledPrepared(singleJob, copies);
3520
+ }
3521
+ const merged = composeBatchHtml(copies);
3522
+ return {
3523
+ html: merged.html,
3524
+ pageCount: merged.pageCount,
3525
+ paperMm: copies[0].paperMm,
3526
+ continuous: isContinuousPaper(copies[0].bound),
3527
+ // 同模板同参数各份来源必然一致;不能用 derivedHeightMm 是否存在判断——
3528
+ // 连续纸逃生门时该字段有值但来源是 config
3529
+ heightSource: copies[0].heightSource,
3530
+ // 各份 pageIndex 均从 0 开始,批量拼接后该字段仅作调试用途
3531
+ pageLayouts: copies.flatMap((c) => c.pageLayouts),
3532
+ copies: copies.length,
3533
+ copyPaperMm: merged.copyPaperMm
3534
+ };
3535
+ }
3536
+ function toPreparedDocument(s) {
3537
+ return {
3538
+ html: s.html,
3539
+ pageCount: s.pageCount,
3540
+ paperMm: s.paperMm,
3541
+ continuous: s.continuous,
3542
+ heightSource: s.heightSource,
3543
+ pageLayouts: s.pageLayouts,
3544
+ copies: 1
3545
+ };
3546
+ }
3547
+ function composeTiledPrepared(job, copies) {
3548
+ const bound = copies[0].bound;
3549
+ const layout = computeTileLayout(bound, { paperOverride: job.paperOverride });
3550
+ copies.forEach((copy, i) => {
3551
+ const overflow = copy.pageLayouts[0]?.overflow === true;
3552
+ if (copy.pageLayouts.length !== 1 || overflow) {
3553
+ const detail = overflow ? "\u5185\u5BB9\u8D85\u51FA\u7EB8\u5F20\u9AD8\u5EA6" : `\u6E32\u67D3\u51FA ${copy.pageLayouts.length} \u9875`;
3554
+ throw new Error(
3555
+ `\u62FC\u7248\u8981\u6C42\u6BCF\u4EFD\u6807\u7B7E\u6070\u597D 1 \u9875\uFF0C\u7B2C ${i + 1} \u4EFD${detail}\uFF1B\u8BF7\u7F29\u5C0F\u5185\u5BB9\u6216\u8C03\u6574\u6807\u7B7E\u7EB8\u5F20\u9AD8\u5EA6`
3556
+ );
3557
+ }
3558
+ });
3559
+ const tiled = composeTiledHtml({
3560
+ copies: copies.map((c) => ({
3561
+ bound: c.bound,
3562
+ pageLayouts: c.pageLayouts,
3563
+ data: c.data,
3564
+ codeRenderer: c.codeRenderer
3565
+ })),
3566
+ layout
3567
+ });
3568
+ return {
3569
+ html: tiled.html,
3570
+ // 语义变更:pageCount = 实际输出张数(客户端任务历史、预览「共 N 页」都按此口径)
3571
+ pageCount: tiled.sheetCount,
3572
+ paperMm: { width: layout.sheet.width, height: layout.sheet.height },
3573
+ continuous: false,
3574
+ heightSource: "config",
3575
+ // 调试用途;各份 pageIndex 均从 0 开始
3576
+ pageLayouts: copies.flatMap((c) => c.pageLayouts),
3577
+ copies: copies.length
3578
+ };
3579
+ }
3580
+ async function prepareSingleWithSession(job, session, data) {
3581
+ const bound = bindData(job.templateJson, data, job.baseUrl, job.fontBaseUrl);
3582
+ const continuous = isContinuousPaper(bound);
3583
+ const designPaper = getPaperDimensions(bound);
3584
+ const viewport = paperViewportPx(designPaper);
3585
+ const heightEscape = escapeHeightMm({ paperHeightMm: job.paperHeightMm, override: job.paperOverride });
3586
+ const measurement = await buildHtmlWithCodes({
3587
+ bound,
3588
+ job,
3589
+ session,
3590
+ data,
3591
+ pageLayouts: [],
3592
+ isMeasurementPass: true
3593
+ });
3594
+ const measurements = await session.measure(measurement.html, viewport);
3595
+ applyTextFitSizes(bound, measurements.fits);
3596
+ const pageLayouts = paginate(bound, normalizeMeasurements(measurements.measurements, bound));
3597
+ const finalBuild = await buildHtmlWithCodes({
3598
+ bound,
3599
+ job,
3600
+ session,
3601
+ data,
3602
+ pageLayouts,
3603
+ isMeasurementPass: false,
3604
+ baseMap: measurement.map
3605
+ });
3606
+ let html = finalBuild.html;
3607
+ let derivedHeightMm;
3608
+ if (continuous) {
3609
+ if (heightEscape && heightEscape > 0) {
3610
+ derivedHeightMm = heightEscape;
3611
+ } else {
3612
+ const bottomPx = await session.probeContentBottom(html, viewport);
3613
+ derivedHeightMm = composeContinuousHeight(bound, pxToMm2(bottomPx));
3614
+ }
3615
+ html = generateHtml(bound, pageLayouts, data, {
3616
+ codeRenderer: finalBuild.codeRenderer,
3617
+ pageHeightMm: derivedHeightMm
3618
+ });
3619
+ }
3620
+ const overrideForPaper = continuous && heightEscape ? { ...job.paperOverride, height: heightEscape } : job.paperOverride;
3621
+ const outputPaper = getOutputPaperDimensions(bound);
3622
+ const { paperMm, heightSource } = resolvePaperMm({
3623
+ paperMm: { width: outputPaper.width, height: derivedHeightMm ?? outputPaper.height },
3624
+ continuous,
3625
+ override: overrideForPaper
3626
+ });
3627
+ return {
3628
+ html,
3629
+ pageCount: pageLayouts.length,
3630
+ paperMm,
3631
+ continuous,
3632
+ heightSource,
3633
+ pageLayouts,
3634
+ copies: 1,
3635
+ bound,
3636
+ codeRenderer: finalBuild.codeRenderer,
3637
+ derivedHeightMm
3638
+ };
3639
+ }
3640
+ async function prepareMultiCopy(job, session, data) {
3641
+ const templates = normalizeTemplate(job.templateJson);
3642
+ const viewport = paperViewportPx(getPaperDimensions(templates[0]));
3643
+ const boundPages = [];
3644
+ const layoutsPerPage = [];
3645
+ const codeRenderers = [];
3646
+ for (const t of templates) {
3647
+ const bound = bindData(t, data, job.baseUrl, job.fontBaseUrl);
3648
+ const measurement = await buildHtmlWithCodes({ bound, job, session, data, pageLayouts: [], isMeasurementPass: true });
3649
+ const measurements = await session.measure(measurement.html, viewport);
3650
+ applyTextFitSizes(bound, measurements.fits);
3651
+ const layouts = paginate(bound, normalizeMeasurements(measurements.measurements, bound));
3652
+ const final = await buildHtmlWithCodes({ bound, job, session, data, pageLayouts: layouts, isMeasurementPass: false, baseMap: measurement.map });
3653
+ boundPages.push(bound);
3654
+ layoutsPerPage.push(layouts);
3655
+ codeRenderers.push(final.codeRenderer);
3656
+ }
3657
+ return { boundPages, layoutsPerPage, data, codeRenderers };
3658
+ }
3659
+ async function buildHtmlWithCodes(input) {
3660
+ const baseMap = input.baseMap ?? /* @__PURE__ */ new Map();
3661
+ if (input.job.codeRenderer) {
3662
+ const html2 = generateHtml(input.bound, input.pageLayouts, input.data, {
3663
+ isMeasurementPass: input.isMeasurementPass,
3664
+ codeRenderer: input.job.codeRenderer
3665
+ });
3666
+ return { html: html2, codeRenderer: input.job.codeRenderer, map: baseMap };
3667
+ }
3668
+ const collector = createCollectingCodeRenderer(baseMap);
3669
+ const draft = generateHtml(input.bound, input.pageLayouts, input.data, {
3670
+ isMeasurementPass: input.isMeasurementPass,
3671
+ codeRenderer: collector.renderer
3672
+ });
3673
+ const extra = collector.takeSpecs();
3674
+ const rendered = extra.length > 0 ? await input.session.renderCodes(extra) : /* @__PURE__ */ new Map();
3675
+ const map = mergeCodeMaps(baseMap, rendered);
3676
+ const codeRenderer = map.size > 0 ? createMapCodeRenderer(map) : void 0;
3677
+ if (extra.length === 0) return { html: draft, codeRenderer, map };
3678
+ const html = generateHtml(input.bound, input.pageLayouts, input.data, {
3679
+ isMeasurementPass: input.isMeasurementPass,
3680
+ codeRenderer
3681
+ });
3682
+ return { html, codeRenderer, map };
3683
+ }
3684
+
3685
+ export {
3686
+ tokenize,
3687
+ parse,
3688
+ evaluate,
3689
+ parseTemplate,
3690
+ renderTemplate,
3691
+ compileTemplate,
3692
+ getByPath,
3693
+ formatMoney,
3694
+ formatDate,
3695
+ toUpperCaseAmount,
3696
+ ifFn,
3697
+ sum,
3698
+ avg,
3699
+ count,
3700
+ min,
3701
+ max,
3702
+ setPageIndex,
3703
+ setTotalPages,
3704
+ systemVars,
3705
+ addNumbers,
3706
+ subtractNumbers,
3707
+ multiplyNumbers,
3708
+ divideNumbers,
3709
+ round,
3710
+ roundUp,
3711
+ roundDown,
3712
+ roundHalfEven,
3713
+ safeEval,
3714
+ evaluateTemplate,
3715
+ bindData,
3716
+ resolveSystemVariables,
3717
+ injectSystemVariables,
3718
+ buildFontFaceCss,
3719
+ FALLBACK_FONT_STACK,
3720
+ escapeInlineStyleValue,
3721
+ toFontFamilyStack,
3722
+ PAPER_DIMENSIONS,
3723
+ isContinuousPaperSize,
3724
+ getPaperDimensions,
3725
+ isContinuousPaper,
3726
+ mm,
3727
+ buildBasePageCss,
3728
+ buildPageRuleCss,
3729
+ buildPageGeometryCss,
3730
+ buildPageCss,
3731
+ elementPositionStyle,
3732
+ ptToMm,
3733
+ pxToMm,
3734
+ mmToPx,
3735
+ DEFAULT_SHRINK_MIN_FONT_SIZE_PT,
3736
+ MIN_SHRINK_FONT_SIZE_PT,
3737
+ resolveElementTextFit,
3738
+ resolveCellTextFit,
3739
+ resolveShrinkMinFontSize,
3740
+ roundFontSize,
3741
+ floorFontSize,
3742
+ cellFitKey,
3743
+ parseCellFitKey,
3744
+ cellFitWidthMm,
3745
+ cellFitCapMm,
3746
+ PX_PER_MM,
3747
+ MM_PER_PX,
3748
+ WATERMARK_DEFAULTS,
3749
+ WATERMARK_DENSITY_PRESETS,
3750
+ isWatermarkVisible,
3751
+ resolveWatermarkText,
3752
+ formatTimestamp,
3753
+ resolveWatermarkLayout,
3754
+ renderWatermarkTileSvg,
3755
+ renderWatermarkLayerHtml,
3756
+ tableDesignBottom,
3757
+ paginate,
3758
+ MM_PER_INCH,
3759
+ BARCODE_QUIET_ZONE_MODULES,
3760
+ BARCODE_BAR_HEIGHT_MODULES,
3761
+ BARCODE_TEXT_FONT_SIZE_MODULES,
3762
+ BARCODE_MARGIN_BOTTOM_MODULES,
3763
+ BARCODE_MODULE_WIDTH_MM,
3764
+ barcodeUnitsPerModule,
3765
+ barcodePreferredModuleWidthMm,
3766
+ barcodeAvailableBoxMm,
3767
+ resolveBarcodeSize,
3768
+ generateHtml,
3769
+ MIN_CONTINUOUS_HEIGHT_MM,
3770
+ composeContinuousHeight,
3771
+ MICROMETERS_PER_MM,
3772
+ MM_PER_INCH2,
3773
+ MICROMETERS_PER_INCH,
3774
+ pxToMm2,
3775
+ mmToPx2,
3776
+ millimetersToMicrometers,
3777
+ micrometersToMillimeters,
3778
+ millimetersToInches,
3779
+ PrintFailure,
3780
+ toPrintFailure,
3781
+ withTimeout,
3782
+ escapeHeightMm,
3783
+ resolvePaperMm,
3784
+ paperViewportPx,
3785
+ buildPdfTargetSpec,
3786
+ toElectronPrintToPdfOptions,
3787
+ toPlaywrightPdfOptions,
3788
+ buildScreenshotTargetSpec,
3789
+ normalizeMeasurements,
3790
+ applyTextFitSizes,
3791
+ codeSpecKey,
3792
+ createMapCodeRenderer,
3793
+ createCollectingCodeRenderer,
3794
+ mergeCodeMaps,
3795
+ EXECUTOR_TARGETS,
3796
+ DEFAULT_TIMEOUT_MS,
3797
+ DEFAULT_READINESS_MS,
3798
+ createDomHostRuntime,
3799
+ MAX_BATCH_COPIES,
3800
+ normalizePrintData,
3801
+ composeBatchHtml,
3802
+ TILE_DEFAULTS,
3803
+ TilingError,
3804
+ roundMm,
3805
+ normalizeTilingOptions,
3806
+ resolveSheetMm,
3807
+ computeTileLayout,
3808
+ tilePosition,
3809
+ computeMaxColumns,
3810
+ validateTiling,
3811
+ composeTiledHtml,
3812
+ normalizeTemplate,
3813
+ isMultiPageTemplate,
3814
+ mergeFontDeclarations,
3815
+ composeMultiPageDocument,
3816
+ prepareDocument,
3817
+ renderPdf,
3818
+ renderScreenshot
3819
+ };