@worm-vue3-print/core 1.3.0 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -40,6 +40,7 @@ __export(index_exports, {
40
40
  TilingError: () => TilingError,
41
41
  WATERMARK_DEFAULTS: () => WATERMARK_DEFAULTS,
42
42
  WATERMARK_DENSITY_PRESETS: () => WATERMARK_DENSITY_PRESETS,
43
+ addNumbers: () => addNumbers,
43
44
  applyTextFitSizes: () => applyTextFitSizes,
44
45
  avg: () => avg,
45
46
  bindData: () => bindData,
@@ -64,6 +65,7 @@ __export(index_exports, {
64
65
  createCollectingCodeRenderer: () => createCollectingCodeRenderer,
65
66
  createDomHostRuntime: () => createDomHostRuntime,
66
67
  createMapCodeRenderer: () => createMapCodeRenderer,
68
+ divideNumbers: () => divideNumbers,
67
69
  elementPositionStyle: () => elementPositionStyle,
68
70
  escapeHeightMm: () => escapeHeightMm,
69
71
  escapeInlineStyleValue: () => escapeInlineStyleValue,
@@ -91,6 +93,7 @@ __export(index_exports, {
91
93
  min: () => min,
92
94
  mm: () => mm,
93
95
  mmToPx: () => mmToPx,
96
+ multiplyNumbers: () => multiplyNumbers,
94
97
  normalizeMeasurements: () => normalizeMeasurements,
95
98
  normalizePrintData: () => normalizePrintData,
96
99
  normalizeTemplate: () => normalizeTemplate,
@@ -115,11 +118,16 @@ __export(index_exports, {
115
118
  resolveSystemVariables: () => resolveSystemVariables,
116
119
  resolveWatermarkLayout: () => resolveWatermarkLayout,
117
120
  resolveWatermarkText: () => resolveWatermarkText,
121
+ round: () => round,
122
+ roundDown: () => roundDown,
118
123
  roundFontSize: () => roundFontSize,
124
+ roundHalfEven: () => roundHalfEven,
119
125
  roundMm: () => roundMm,
126
+ roundUp: () => roundUp,
120
127
  safeEval: () => safeEval,
121
128
  setPageIndex: () => setPageIndex,
122
129
  setTotalPages: () => setTotalPages,
130
+ subtractNumbers: () => subtractNumbers,
123
131
  sum: () => sum,
124
132
  systemVars: () => systemVars,
125
133
  tableDesignBottom: () => tableDesignBottom,
@@ -477,6 +485,113 @@ var Parser = class {
477
485
  }
478
486
  };
479
487
 
488
+ // src/numeric.ts
489
+ var MAX_DIGITS = 20;
490
+ var EXTRA_SCALE = 8;
491
+ function toNumber(value, fallback = 0) {
492
+ if (typeof value === "number") return Number.isFinite(value) ? value : fallback;
493
+ if (typeof value === "string") {
494
+ const s = value.trim();
495
+ if (s === "") return fallback;
496
+ const n = Number(s);
497
+ return Number.isFinite(n) ? n : fallback;
498
+ }
499
+ if (value == null) return fallback;
500
+ if (typeof value === "boolean") return value ? 1 : 0;
501
+ return fallback;
502
+ }
503
+ function isNumericLike(value) {
504
+ if (typeof value === "number") return Number.isFinite(value);
505
+ if (typeof value === "string") {
506
+ const s = value.trim();
507
+ return s !== "" && Number.isFinite(Number(s));
508
+ }
509
+ return false;
510
+ }
511
+ function normalizeFloat(value) {
512
+ if (!Number.isFinite(value) || Number.isInteger(value)) return value;
513
+ if (Math.abs(value) >= 1e15) return value;
514
+ return Number(value.toPrecision(12));
515
+ }
516
+ function decimalsOf(n) {
517
+ if (!Number.isFinite(n) || Number.isInteger(n)) return 0;
518
+ const s = String(n);
519
+ if (s.includes("e") || s.includes("E")) return 0;
520
+ const i = s.indexOf(".");
521
+ return i === -1 ? 0 : s.length - i - 1;
522
+ }
523
+ function rescale(value, decimals) {
524
+ if (!Number.isFinite(value)) return 0;
525
+ const d = Math.min(Math.max(decimals, 0), 12);
526
+ if (d === 0) return Math.round(value);
527
+ const f = 10 ** d;
528
+ const scaled = value * f;
529
+ if (!Number.isFinite(scaled) || Math.abs(scaled) >= 1e15) return normalizeFloat(value);
530
+ return Math.round(scaled) / f;
531
+ }
532
+ function decimalAdd(left, right) {
533
+ const a = toNumber(left);
534
+ const b = toNumber(right);
535
+ const raw = a + b;
536
+ return rescale(raw, Math.max(decimalsOf(a), decimalsOf(b)));
537
+ }
538
+ function decimalSubtract(left, right) {
539
+ const a = toNumber(left);
540
+ const b = toNumber(right);
541
+ const raw = a - b;
542
+ return rescale(raw, Math.max(decimalsOf(a), decimalsOf(b)));
543
+ }
544
+ function decimalMultiply(left, right) {
545
+ const a = toNumber(left);
546
+ const b = toNumber(right);
547
+ const raw = a * b;
548
+ return rescale(raw, decimalsOf(a) + decimalsOf(b));
549
+ }
550
+ function decimalDivide(left, right) {
551
+ const a = toNumber(left);
552
+ const b = toNumber(right);
553
+ if (b === 0) return 0;
554
+ return normalizeFloat(a / b);
555
+ }
556
+ function decimalModulo(left, right) {
557
+ const a = toNumber(left);
558
+ const b = toNumber(right);
559
+ if (b === 0) return 0;
560
+ return normalizeFloat(a % b);
561
+ }
562
+ function normalizeDigits(digits) {
563
+ if (digits === void 0 || digits === null) return 2;
564
+ const d = Math.trunc(toNumber(digits, 2));
565
+ if (!Number.isFinite(d)) return 2;
566
+ return Math.min(Math.max(d, -MAX_DIGITS), MAX_DIGITS);
567
+ }
568
+ function roundTo(value, digits, mode) {
569
+ const n = toNumber(value, 0);
570
+ const d = normalizeDigits(digits);
571
+ if (n === 0) return 0;
572
+ if (Math.abs(n) >= 1e21) return normalizeFloat(n);
573
+ const scale = Math.max(d, 0) + EXTRA_SCALE;
574
+ const fixed = n.toFixed(scale);
575
+ if (!/^-?\d+(\.\d+)?$/.test(fixed)) return normalizeFloat(n);
576
+ const negative = fixed.startsWith("-");
577
+ const digitsStr = fixed.replace("-", "").replace(".", "");
578
+ let scaled = BigInt(digitsStr || "0");
579
+ if (negative) scaled = -scaled;
580
+ const pow10 = 10n ** BigInt(scale - d);
581
+ const sign = scaled < 0n ? -1n : 1n;
582
+ const abs = scaled < 0n ? -scaled : scaled;
583
+ let q = abs / pow10;
584
+ const r = abs % pow10;
585
+ const doubled = 2n * r;
586
+ if (mode === "up") {
587
+ if (r !== 0n) q += 1n;
588
+ } else if (mode !== "down" && doubled >= pow10) {
589
+ if (doubled > pow10 || mode === "half-up" || q % 2n === 1n) q += 1n;
590
+ }
591
+ const result = sign * q;
592
+ return normalizeFloat(d >= 0 ? Number(result) / 10 ** d : Number(result) * 10 ** -d);
593
+ }
594
+
480
595
  // src/evaluator.ts
481
596
  var SAFE_GLOBALS = {
482
597
  Math,
@@ -542,16 +657,17 @@ function evalBinary(node, context, functions) {
542
657
  const left = evaluate(node.left, context, functions);
543
658
  const right = evaluate(node.right, context, functions);
544
659
  switch (node.op) {
660
+ // 数值运算统一走十进制精确实现:消除 0.1 + 0.2 类浮点噪声、字符串数字按数值处理、除零兜底 0
545
661
  case "+":
546
- return left + right;
662
+ return isNumericOperands(left, right) ? decimalAdd(left, right) : concatOperands(left, right);
547
663
  case "-":
548
- return left - right;
664
+ return decimalSubtract(left, right);
549
665
  case "*":
550
- return left * right;
666
+ return decimalMultiply(left, right);
551
667
  case "/":
552
- return left / right;
668
+ return decimalDivide(left, right);
553
669
  case "%":
554
- return left % right;
670
+ return decimalModulo(left, right);
555
671
  case "<":
556
672
  return left < right;
557
673
  case ">":
@@ -580,15 +696,29 @@ function evalUnary(node, context, functions) {
580
696
  const arg = evaluate(node.arg, context, functions);
581
697
  switch (node.op) {
582
698
  case "-":
583
- return -arg;
699
+ return decimalSubtract(0, arg);
584
700
  case "+":
585
- return +arg;
701
+ return toNumber(arg);
586
702
  case "!":
587
703
  return !arg;
588
704
  default:
589
705
  throw new Error(`\u672A\u77E5\u4E00\u5143\u8FD0\u7B97\u7B26: ${node.op}`);
590
706
  }
591
707
  }
708
+ function concatOperands(left, right) {
709
+ return `${left == null ? "" : String(left)}${right == null ? "" : String(right)}`;
710
+ }
711
+ function isNumericOperands(left, right) {
712
+ const l = operandKind(left);
713
+ const r = operandKind(right);
714
+ if (l === "other" || r === "other") return false;
715
+ return l === "num" || r === "num";
716
+ }
717
+ function operandKind(value) {
718
+ if (value == null || value === "") return "neutral";
719
+ if (isNumericLike(value)) return "num";
720
+ return "other";
721
+ }
592
722
  function evalMember(node, context, functions) {
593
723
  const obj = evaluate(node.object, context, functions);
594
724
  if (obj == null) {
@@ -822,6 +952,34 @@ var systemVars = {
822
952
  printTime: () => Date.now()
823
953
  };
824
954
 
955
+ // src/functions/math.ts
956
+ function addNumbers(...values) {
957
+ return values.reduce((acc, v) => decimalAdd(acc, v), 0);
958
+ }
959
+ function subtractNumbers(...values) {
960
+ if (values.length === 0) return 0;
961
+ return values.slice(1).reduce((acc, v) => decimalSubtract(acc, v), decimalAdd(values[0], 0));
962
+ }
963
+ function multiplyNumbers(...values) {
964
+ if (values.length === 0) return 0;
965
+ return values.reduce((acc, v) => decimalMultiply(acc, v), 1);
966
+ }
967
+ function divideNumbers(a, b) {
968
+ return decimalDivide(a, b);
969
+ }
970
+ function round(value, digits) {
971
+ return roundTo(value, digits, "half-up");
972
+ }
973
+ function roundUp(value, digits) {
974
+ return roundTo(value, digits, "up");
975
+ }
976
+ function roundDown(value, digits) {
977
+ return roundTo(value, digits, "down");
978
+ }
979
+ function roundHalfEven(value, digits) {
980
+ return roundTo(value, digits, "half-even");
981
+ }
982
+
825
983
  // src/render/expression-eval.ts
826
984
  var RenderEngine = class {
827
985
  constructor() {
@@ -847,8 +1005,19 @@ var FORMAT_FUNCTIONS = {
847
1005
  IF: ifFn,
848
1006
  CONCAT: (...args) => args.filter((v) => v != null).map(String).join(""),
849
1007
  IFEMPTY: (v, d) => v != null && v !== "" ? String(v) : d,
850
- ROUND: (n, d) => Number(Number(n).toFixed(d)),
851
- LEN: (s) => String(s).length
1008
+ LEN: (s) => String(s).length,
1009
+ // 四则运算(与 +/-/*// 运算符同一套数值语义,供不方便写运算符的场景使用)
1010
+ ADD: addNumbers,
1011
+ SUB: subtractNumbers,
1012
+ MUL: multiplyNumbers,
1013
+ DIV: divideNumbers,
1014
+ // 数值修约
1015
+ ROUND: round,
1016
+ ROUNDUP: roundUp,
1017
+ CEIL: roundUp,
1018
+ ROUNDDOWN: roundDown,
1019
+ FLOOR: roundDown,
1020
+ ROUNDBANK: roundHalfEven
852
1021
  };
853
1022
  for (const [name, fn] of Object.entries(FORMAT_FUNCTIONS)) {
854
1023
  engine.registerFunction(name, fn);
@@ -893,7 +1062,7 @@ function evaluateTemplate(text, ctx) {
893
1062
 
894
1063
  // src/render/data-binder.ts
895
1064
  function bindData(template, printData, baseUrl, fontBaseUrl) {
896
- const data = printData ?? {};
1065
+ const data = { ...resolveSystemVariables(), ...printData ?? {} };
897
1066
  const bound = JSON.parse(JSON.stringify(template));
898
1067
  if (bound.header?.elements) {
899
1068
  bound.header.elements = bound.header.elements.map((el) => bindElement(el, data, baseUrl));
@@ -922,7 +1091,12 @@ function bindData(template, printData, baseUrl, fontBaseUrl) {
922
1091
  function bindElement(el, data, baseUrl) {
923
1092
  const cloned = { ...el, options: { ...el.options } };
924
1093
  if (typeof cloned.options.formatter === "string") {
925
- cloned.options.formatter = evaluateTemplate(cloned.options.formatter, data);
1094
+ const raw = cloned.options.formatter;
1095
+ if (referencesPageNumbers(raw)) {
1096
+ cloned.options.rawFormatter = raw;
1097
+ } else {
1098
+ cloned.options.formatter = evaluateTemplate(raw, data);
1099
+ }
926
1100
  }
927
1101
  const isImage = cloned.type === "image" || cloned.printElementType?.type === "image";
928
1102
  if (isImage && typeof cloned.options.src === "string") {
@@ -969,39 +1143,25 @@ function bindTableData(el, data) {
969
1143
  if (dataStartIdx < 0) dataStartIdx = renderRows.length;
970
1144
  const ctx = itemCtx(item);
971
1145
  renderRows.push(makeRenderRow(row, (cell) => {
972
- const formatter = cell.formatter;
973
- if (!formatter) return "";
974
- return evaluateTemplate(formatter, ctx);
1146
+ return resolveCellText(cell, ctx);
975
1147
  }));
976
1148
  dataRowCtx.push(ctx);
977
1149
  }
978
1150
  continue;
979
1151
  }
980
1152
  if (mode === "dynamic" && row.type === "subtotal") {
981
- const tpl = makeRenderRow(row, (cell) => {
982
- const formatter = cell.formatter;
983
- if (!formatter) return "";
984
- return evaluateTemplate(formatter, { rows: summaryRows, ...data });
985
- }, true);
1153
+ const tpl = makeRenderRow(row, (cell) => resolveCellText(cell, { rows: summaryRows, ...data }), true);
986
1154
  subtotalTemplates.push(tpl);
987
1155
  renderRows.push(tpl);
988
1156
  continue;
989
1157
  }
990
1158
  if (mode === "dynamic" && row.type === "summary") {
991
- const summaryRow = makeRenderRow(row, (cell) => {
992
- const formatter = cell.formatter;
993
- if (!formatter) return "";
994
- return evaluateTemplate(formatter, { rows: summaryRows, ...data });
995
- });
1159
+ const summaryRow = makeRenderRow(row, (cell) => resolveCellText(cell, { rows: summaryRows, ...data }));
996
1160
  summaryRenderRows.push(summaryRow);
997
1161
  renderRows.push(summaryRow);
998
1162
  continue;
999
1163
  }
1000
- renderRows.push(makeRenderRow(row, (cell) => {
1001
- const formatter = cell.formatter;
1002
- if (!formatter) return "";
1003
- return evaluateTemplate(formatter, data);
1004
- }));
1164
+ renderRows.push(makeRenderRow(row, (cell) => resolveCellText(cell, data)));
1005
1165
  }
1006
1166
  opts._renderRows = renderRows;
1007
1167
  opts._repeatHeaderCount = countRepeatHeader(rows);
@@ -1011,13 +1171,20 @@ function bindTableData(el, data) {
1011
1171
  opts._summaryRows = summaryRenderRows;
1012
1172
  opts._mainData = data;
1013
1173
  }
1174
+ function resolveCellText(cell, ctx) {
1175
+ const formatter = cell.formatter;
1176
+ if (!formatter) return "";
1177
+ if (referencesPageNumbers(formatter)) return formatter;
1178
+ return evaluateTemplate(formatter, ctx);
1179
+ }
1014
1180
  function makeRenderRow(row, resolve, keepRaw = false) {
1015
1181
  return {
1016
1182
  type: row.type,
1017
1183
  height: row.height ?? 8,
1018
1184
  cells: row.cells.map((cell) => ({
1019
1185
  content: cell.merged ? "" : resolve(cell),
1020
- ...keepRaw ? { rawFormatter: cell.merged ? "" : cell.formatter ?? "" } : {},
1186
+ // 小计行(keepRaw)与引用页码的单元格都保留原始表达式:前者按当页数据行重算,后者按当页页码重算
1187
+ ...(keepRaw || referencesPageNumbers(cell.formatter)) && !cell.merged ? { rawFormatter: cell.formatter ?? "" } : {},
1021
1188
  cellType: cell.cellType,
1022
1189
  barcodeType: cell.barcodeType,
1023
1190
  qrCodeLevel: cell.qrCodeLevel,
@@ -1045,16 +1212,35 @@ function makeRenderRow(row, resolve, keepRaw = false) {
1045
1212
  };
1046
1213
  }
1047
1214
  function countRepeatHeader(rows) {
1215
+ const headerCount = countLeadingHeaderRows(rows);
1216
+ if (headerCount === 0) return 0;
1217
+ const enabled = rows.slice(0, headerCount).some((row) => row.repeatOnPage === true);
1218
+ if (!enabled) return 0;
1219
+ return alignToRowspanBoundary(rows, headerCount);
1220
+ }
1221
+ function countLeadingHeaderRows(rows) {
1048
1222
  let n = 0;
1049
1223
  for (const row of rows) {
1050
- if (row.type === "header" && row.repeatOnPage === true) {
1051
- n++;
1052
- } else {
1053
- break;
1054
- }
1224
+ if (row?.type !== "header") break;
1225
+ n++;
1055
1226
  }
1056
1227
  return n;
1057
1228
  }
1229
+ function isRowspanComplete(rows, n) {
1230
+ for (let r = 0; r < n; r++) {
1231
+ for (const cell of rows[r]?.cells ?? []) {
1232
+ if (cell?.merged) continue;
1233
+ if (r + (cell?.rowspan ?? 1) > n) return false;
1234
+ }
1235
+ }
1236
+ return true;
1237
+ }
1238
+ function alignToRowspanBoundary(rows, max2) {
1239
+ for (let n = max2; n >= 1; n--) {
1240
+ if (isRowspanComplete(rows, n)) return n;
1241
+ }
1242
+ return 1;
1243
+ }
1058
1244
  var pad2 = (n) => String(n).padStart(2, "0");
1059
1245
  function resolveSystemVariables(now = /* @__PURE__ */ new Date(), page = {}) {
1060
1246
  return {
@@ -1064,6 +1250,9 @@ function resolveSystemVariables(now = /* @__PURE__ */ new Date(), page = {}) {
1064
1250
  totalPages: page.totalPages ?? 1
1065
1251
  };
1066
1252
  }
1253
+ function referencesPageNumbers(expr) {
1254
+ return typeof expr === "string" && /\b(pageIndex|totalPages)\b/.test(expr);
1255
+ }
1067
1256
  function injectSystemVariables(html, now = /* @__PURE__ */ new Date()) {
1068
1257
  const { printDate, printTime } = resolveSystemVariables(now);
1069
1258
  return html.replace(/\{printDate\}/g, printDate).replace(/\{printTime\}/g, printTime);
@@ -1704,10 +1893,9 @@ function tableDesignBottom(el) {
1704
1893
  const opts = el.options ?? {};
1705
1894
  const top = opts.top ?? 0;
1706
1895
  const rows = opts.tableRows ?? [];
1707
- if (rows.length > 0) {
1708
- return top + rows.reduce((s, r) => s + (r.height ?? 0), 0);
1709
- }
1710
- return top + (opts.height ?? 0);
1896
+ const rowSum = rows.reduce((s, r) => s + (r.height ?? 0), 0);
1897
+ const designHeight = Math.max(opts.height ?? 0, rowSum);
1898
+ return top + designHeight;
1711
1899
  }
1712
1900
  function buildFollowMap(sorted, excludedIds) {
1713
1901
  const map = /* @__PURE__ */ new Map();
@@ -1848,7 +2036,10 @@ function paginate(template, measuredElements) {
1848
2036
  let isFirstPage = true;
1849
2037
  let pageBroken = false;
1850
2038
  function sectionTop(el) {
1851
- return pageBroken ? 0 : el.options?.top ?? 0;
2039
+ return pageBroken ? pageCursorTop() : el.options?.top ?? 0;
2040
+ }
2041
+ function pageCursorTop() {
2042
+ return Math.max(0, fullPageHeight() - remaining);
1852
2043
  }
1853
2044
  function fullPageHeight() {
1854
2045
  return isFirstPage ? contentHeight - overlayH - SAFETY_MARGIN : contentHeight - SAFETY_MARGIN;
@@ -1915,7 +2106,7 @@ function paginate(template, measuredElements) {
1915
2106
  function paginateNonTable(el, measured, sortedList, idx) {
1916
2107
  const elHeight = measured.get(el.id)?.measuredHeight ?? el.options?.height ?? 0;
1917
2108
  if (elHeight <= remaining) {
1918
- const top2 = pageBroken ? 0 : el.options?.top ?? 0;
2109
+ const top2 = pageBroken ? pageCursorTop() : el.options?.top ?? 0;
1919
2110
  noteOverflow(top2 + elHeight);
1920
2111
  currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
1921
2112
  remaining -= elHeight;
@@ -1934,8 +2125,9 @@ function paginate(template, measuredElements) {
1934
2125
  }
1935
2126
  return idx + 1;
1936
2127
  }
1937
- const top = pageBroken ? 0 : el.options?.top ?? 0;
1938
- finishPage(top + elHeight > contentHeight);
2128
+ finishPage();
2129
+ const top = pageBroken ? pageCursorTop() : el.options?.top ?? 0;
2130
+ if (top + elHeight > contentHeight) overflowOnCurrent = true;
1939
2131
  currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
1940
2132
  remaining -= elHeight;
1941
2133
  return idx + 1;
@@ -1951,7 +2143,7 @@ function paginate(template, measuredElements) {
1951
2143
  }
1952
2144
  const unitHeight = Math.max(maxBottom - minTop, 0);
1953
2145
  const place = () => {
1954
- const offset = pageBroken ? -minTop : 0;
2146
+ const offset = pageBroken ? pageCursorTop() - minTop : 0;
1955
2147
  noteOverflow(minTop + offset + unitHeight);
1956
2148
  for (const m of members) {
1957
2149
  currentPage.push({
@@ -2025,6 +2217,7 @@ function paginate(template, measuredElements) {
2025
2217
  const groups = buildRowGroups(bodyRows, rowCount);
2026
2218
  let sliceStart = 0;
2027
2219
  let firstSlice = true;
2220
+ let sliceTop = sectionTop(el);
2028
2221
  for (const g of groups) {
2029
2222
  const gh = bodyHeights.slice(g.start, g.end).reduce((s, h) => s + h, 0);
2030
2223
  if (gh + subtotalH <= remaining) {
@@ -2039,12 +2232,13 @@ function paginate(template, measuredElements) {
2039
2232
  endRow: g.start,
2040
2233
  subtotal: subtotalH > 0,
2041
2234
  ...!firstSlice && repeatCount > 0 ? { repeatHeader: true } : {},
2042
- renderTop: sectionTop(el)
2235
+ renderTop: sliceTop
2043
2236
  });
2044
2237
  firstSlice = false;
2045
2238
  sliceStart = g.start;
2046
2239
  }
2047
2240
  finishPage();
2241
+ sliceTop = sectionTop(el);
2048
2242
  if (!firstSlice) remaining -= repeatH;
2049
2243
  remaining -= gh;
2050
2244
  }
@@ -2056,7 +2250,7 @@ function paginate(template, measuredElements) {
2056
2250
  endRow: rowCount,
2057
2251
  subtotal: subtotalH > 0,
2058
2252
  ...!firstSlice && repeatCount > 0 ? { repeatHeader: true } : {},
2059
- renderTop: sectionTop(el)
2253
+ renderTop: sliceTop
2060
2254
  });
2061
2255
  }
2062
2256
  if (summaryH > 0) {
@@ -2144,6 +2338,13 @@ function barcodeAvailableBoxMm(boxMm, maxMm) {
2144
2338
  }
2145
2339
 
2146
2340
  // src/render/html-generator.ts
2341
+ function pageVarsContext(vars) {
2342
+ return { ...vars.data ?? {}, pageIndex: vars.pageIndex, totalPages: vars.totalPages };
2343
+ }
2344
+ function pageVarsData(printData) {
2345
+ if (Array.isArray(printData)) return printData[0] ?? {};
2346
+ return printData ?? {};
2347
+ }
2147
2348
  function generateHtml(template, pageLayouts, printData, options) {
2148
2349
  const css = buildFontFaceCss(template.fonts) + buildPageCss(template, options?.pageHeightMm);
2149
2350
  const isMeasure = options?.isMeasurementPass === true;
@@ -2228,22 +2429,25 @@ function renderPage(template, page, pageNum, totalPages, ctx, printData, pageCla
2228
2429
  height: ctx.pageHeightMm && ctx.pageHeightMm > 0 ? ctx.pageHeightMm : paper.height
2229
2430
  };
2230
2431
  const contentWidth = paper.width - template.margins.left - template.margins.right;
2432
+ const pageVars = { pageIndex: pageNum, totalPages, data: pageVarsData(printData) };
2433
+ const scoped = withPageNumbers(template, pageVars);
2434
+ const pageCtx = { ...ctx, pageVars };
2231
2435
  const headerHtml = renderAreaElements(
2232
- template.header?.elements ?? [],
2436
+ scoped.header?.elements ?? [],
2233
2437
  contentWidth,
2234
2438
  pageNum,
2235
2439
  totalPages,
2236
- ctx
2440
+ pageCtx
2237
2441
  );
2238
2442
  const footerHtml = renderAreaElements(
2239
- template.footer?.elements ?? [],
2443
+ scoped.footer?.elements ?? [],
2240
2444
  contentWidth,
2241
2445
  pageNum,
2242
2446
  totalPages,
2243
- ctx
2447
+ pageCtx
2244
2448
  );
2245
- const overlayHtml = page.pageIndex === 0 ? `<div class="first-page-overlay">${renderAreaElements(template.firstPageOverlay?.elements ?? [], contentWidth, void 0, void 0, ctx)}</div>` : "";
2246
- let contentHtml = page.sections.map((section) => renderSection(section, template, ctx)).join("\n");
2449
+ const overlayHtml = page.pageIndex === 0 ? `<div class="first-page-overlay">${renderAreaElements(scoped.firstPageOverlay?.elements ?? [], contentWidth, pageNum, totalPages, pageCtx)}</div>` : "";
2450
+ let contentHtml = page.sections.map((section) => renderSection(section, scoped, pageCtx)).join("\n");
2247
2451
  contentHtml = contentHtml.replace(/\{pageIndex\}/g, String(pageNum));
2248
2452
  contentHtml = contentHtml.replace(/\{totalPages\}/g, String(totalPages));
2249
2453
  const pageInner = `
@@ -2260,6 +2464,39 @@ function renderPage(template, page, pageNum, totalPages, ctx, printData, pageCla
2260
2464
  ${pageBody}
2261
2465
  </section>`;
2262
2466
  }
2467
+ function withPageNumbers(template, vars) {
2468
+ if (!templateReferencesPageNumbers(template)) return template;
2469
+ const context = pageVarsContext(vars);
2470
+ const mapEl = (el) => {
2471
+ const raw = el.options?.rawFormatter;
2472
+ if (typeof raw !== "string") return el;
2473
+ return { ...el, options: { ...el.options, formatter: evaluateTemplate(raw, context) } };
2474
+ };
2475
+ const mapArea = (area) => area ? { ...area, elements: (area.elements ?? []).map(mapEl) } : area;
2476
+ return {
2477
+ ...template,
2478
+ elements: template.elements.map(mapEl),
2479
+ header: mapArea(template.header),
2480
+ footer: mapArea(template.footer),
2481
+ firstPageOverlay: mapArea(template.firstPageOverlay)
2482
+ };
2483
+ }
2484
+ function templateReferencesPageNumbers(template) {
2485
+ const areas = [
2486
+ template.elements,
2487
+ template.header?.elements,
2488
+ template.footer?.elements,
2489
+ template.firstPageOverlay?.elements
2490
+ ];
2491
+ return areas.some((list) => (list ?? []).some((el) => typeof el.options?.rawFormatter === "string"));
2492
+ }
2493
+ function resolveCellContent(cell, ctx) {
2494
+ const raw = cell.rawFormatter;
2495
+ if (typeof raw === "string" && raw !== "" && ctx?.pageVars) {
2496
+ return evaluateTemplate(raw, pageVarsContext(ctx.pageVars));
2497
+ }
2498
+ return cell.content;
2499
+ }
2263
2500
  function renderSection(section, template, ctx) {
2264
2501
  const el = findElement(template, section.elementId);
2265
2502
  if (!el) {
@@ -2412,7 +2649,7 @@ function matrixCellStyle(cell, opts) {
2412
2649
  }
2413
2650
  return parts.join(";");
2414
2651
  }
2415
- function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOwner = { elementId: "", kind: "b" }) {
2652
+ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOwner = { elementId: "", kind: "b" }, rowLimit) {
2416
2653
  const trs = [];
2417
2654
  const defaultPadding = opts.tableDefaultPadding ?? 1;
2418
2655
  for (let r = start; r < end; r++) {
@@ -2420,12 +2657,14 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOw
2420
2657
  if (!row) continue;
2421
2658
  const idxAttr = withRowIndex ? ` data-row-index="${r}"` : "";
2422
2659
  const tds = row.cells.map((cell, ci) => ({ cell, ci })).filter(({ cell }) => !cell.merged).map(({ cell, ci }) => {
2423
- const span = `${cell.rowspan > 1 ? ` rowspan="${cell.rowspan}"` : ""}${cell.colspan > 1 ? ` colspan="${cell.colspan}"` : ""}`;
2660
+ const rowspan = rowLimit === void 0 ? cell.rowspan ?? 1 : Math.max(1, Math.min(cell.rowspan ?? 1, rowLimit - r));
2661
+ const span = `${rowspan > 1 ? ` rowspan="${rowspan}"` : ""}${cell.colspan > 1 ? ` colspan="${cell.colspan}"` : ""}`;
2424
2662
  const colIndex = ci;
2425
2663
  let inner;
2426
2664
  if (cell.cellType === "barcode" || cell.cellType === "qrcode") {
2427
- inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;">${codeImgHtml(cell.content, cell.cellType, cell, {
2428
- fallback: esc(cell.content),
2665
+ const codeValue = resolveCellContent(cell, ctx);
2666
+ inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;">${codeImgHtml(codeValue, cell.cellType, cell, {
2667
+ fallback: esc(codeValue),
2429
2668
  codeRenderer: ctx?.codeRenderer,
2430
2669
  fit: cell.fit,
2431
2670
  maxWidth: cell.maxWidth,
@@ -2439,9 +2678,9 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOw
2439
2678
  const fit = cell.fit || "contain";
2440
2679
  const maxWidth = cell.maxWidth ? `max-width:${cell.maxWidth}mm;` : "max-width:100%;";
2441
2680
  const maxHeight = cell.maxHeight ? `max-height:${cell.maxHeight}mm;` : "max-height:100%;";
2442
- 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>`;
2681
+ inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;"><img src="${esc(resolveCellContent(cell, ctx))}" style="object-fit:${fit};${maxWidth}${maxHeight}display:block;" /></div>`;
2443
2682
  } else {
2444
- inner = renderTextCellInner(renderRows, r, colIndex, cell, opts, fitOwner, defaultPadding);
2683
+ inner = renderTextCellInner(renderRows, r, colIndex, cell, opts, fitOwner, defaultPadding, ctx);
2445
2684
  }
2446
2685
  return `<td${span} style="${matrixCellStyle(cell, opts)}">${inner}</td>`;
2447
2686
  }).join("");
@@ -2449,9 +2688,9 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOw
2449
2688
  }
2450
2689
  return trs.join("\n");
2451
2690
  }
2452
- function renderTextCellInner(renderRows, rowIndex, colIndex, cell, opts, fitOwner, defaultPadding) {
2691
+ function renderTextCellInner(renderRows, rowIndex, colIndex, cell, opts, fitOwner, defaultPadding, ctx) {
2453
2692
  const fit = resolveCellTextFit(cell);
2454
- const text = esc(cell.content);
2693
+ const text = esc(resolveCellContent(cell, ctx));
2455
2694
  if (fit === "autoHeight") return text;
2456
2695
  const capMm = cellFitCapMm(renderRows, rowIndex, cell, defaultPadding);
2457
2696
  const nowrap = cell.wordWrap === false;
@@ -2489,7 +2728,7 @@ function renderTableSlice(el, section, ctx) {
2489
2728
  const startRow = section.startRow ?? 0;
2490
2729
  const endRow = section.endRow ?? renderRows.length;
2491
2730
  const repeatCount = opts._repeatHeaderCount ?? 0;
2492
- const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }) : "";
2731
+ const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }, repeatCount) : "";
2493
2732
  const bodyHtml = renderMatrixRows(renderRows, startRow, endRow, opts, false, ctx, { elementId: el.id, kind: "b" });
2494
2733
  const subtotalHtml = section.subtotal ? renderSubtotalRows(el, section, opts, ctx) : "";
2495
2734
  const summaryHtml = section.summary ? renderSummaryRows(el, opts, ctx) : "";
@@ -2510,9 +2749,15 @@ function renderSubtotalRows(el, section, opts, ctx) {
2510
2749
  const dataStart = Math.max(startRow, dataStartIdx);
2511
2750
  const dataEnd = Math.max(endRow, dataStart);
2512
2751
  const pageCtx = dataEnd > dataStartIdx ? dataRowCtx.slice(Math.max(dataStart - dataStartIdx, 0), dataEnd - dataStartIdx) : [];
2752
+ const pageVars = ctx?.pageVars ? pageVarsContext(ctx.pageVars) : {};
2513
2753
  const rows = templates.map((tpl) => ({
2514
2754
  ...tpl,
2515
- cells: tpl.cells.map((cell) => cell.rawFormatter ? { ...cell, content: evaluateTemplate(cell.rawFormatter, { rows: pageCtx, ...mainData }) } : cell)
2755
+ cells: tpl.cells.map((cell) => cell.rawFormatter ? {
2756
+ ...cell,
2757
+ content: evaluateTemplate(cell.rawFormatter, { rows: pageCtx, ...mainData, ...pageVars }),
2758
+ // 已按本页上下文求值,清标记避免渲染时再按页码重算一次(会丢掉 rows)
2759
+ rawFormatter: ""
2760
+ } : cell)
2516
2761
  }));
2517
2762
  return renderMatrixRows(rows, 0, rows.length, opts, false, ctx, { elementId: el.id, kind: "st" });
2518
2763
  }
@@ -2535,7 +2780,7 @@ function renderFlowGroup(el, section, template, ctx) {
2535
2780
  if (endRow > startRow || section.subtotal || section.summary) {
2536
2781
  const renderRows = opts._renderRows ?? [];
2537
2782
  const repeatCount = opts._repeatHeaderCount ?? 0;
2538
- const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }) : "";
2783
+ const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }, repeatCount) : "";
2539
2784
  const bodyHtml = renderMatrixRows(renderRows, startRow, endRow, opts, false, ctx, { elementId: el.id, kind: "b" });
2540
2785
  const subtotalHtml = section.subtotal ? renderSubtotalRows(el, section, opts, ctx) : "";
2541
2786
  const summaryHtml = section.summary ? renderSummaryRows(el, opts, ctx) : "";
@@ -3576,6 +3821,7 @@ var TemplateEngine = class {
3576
3821
  TilingError,
3577
3822
  WATERMARK_DEFAULTS,
3578
3823
  WATERMARK_DENSITY_PRESETS,
3824
+ addNumbers,
3579
3825
  applyTextFitSizes,
3580
3826
  avg,
3581
3827
  bindData,
@@ -3600,6 +3846,7 @@ var TemplateEngine = class {
3600
3846
  createCollectingCodeRenderer,
3601
3847
  createDomHostRuntime,
3602
3848
  createMapCodeRenderer,
3849
+ divideNumbers,
3603
3850
  elementPositionStyle,
3604
3851
  escapeHeightMm,
3605
3852
  escapeInlineStyleValue,
@@ -3627,6 +3874,7 @@ var TemplateEngine = class {
3627
3874
  min,
3628
3875
  mm,
3629
3876
  mmToPx,
3877
+ multiplyNumbers,
3630
3878
  normalizeMeasurements,
3631
3879
  normalizePrintData,
3632
3880
  normalizeTemplate,
@@ -3651,11 +3899,16 @@ var TemplateEngine = class {
3651
3899
  resolveSystemVariables,
3652
3900
  resolveWatermarkLayout,
3653
3901
  resolveWatermarkText,
3902
+ round,
3903
+ roundDown,
3654
3904
  roundFontSize,
3905
+ roundHalfEven,
3655
3906
  roundMm,
3907
+ roundUp,
3656
3908
  safeEval,
3657
3909
  setPageIndex,
3658
3910
  setTotalPages,
3911
+ subtractNumbers,
3659
3912
  sum,
3660
3913
  systemVars,
3661
3914
  tableDesignBottom,