@worm-vue3-print/core 1.2.2 → 1.3.0

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