@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.
@@ -340,6 +340,113 @@ var Parser = class {
340
340
  }
341
341
  };
342
342
 
343
+ // src/numeric.ts
344
+ var MAX_DIGITS = 20;
345
+ var EXTRA_SCALE = 8;
346
+ function toNumber(value, fallback = 0) {
347
+ if (typeof value === "number") return Number.isFinite(value) ? value : fallback;
348
+ if (typeof value === "string") {
349
+ const s = value.trim();
350
+ if (s === "") return fallback;
351
+ const n = Number(s);
352
+ return Number.isFinite(n) ? n : fallback;
353
+ }
354
+ if (value == null) return fallback;
355
+ if (typeof value === "boolean") return value ? 1 : 0;
356
+ return fallback;
357
+ }
358
+ function isNumericLike(value) {
359
+ if (typeof value === "number") return Number.isFinite(value);
360
+ if (typeof value === "string") {
361
+ const s = value.trim();
362
+ return s !== "" && Number.isFinite(Number(s));
363
+ }
364
+ return false;
365
+ }
366
+ function normalizeFloat(value) {
367
+ if (!Number.isFinite(value) || Number.isInteger(value)) return value;
368
+ if (Math.abs(value) >= 1e15) return value;
369
+ return Number(value.toPrecision(12));
370
+ }
371
+ function decimalsOf(n) {
372
+ if (!Number.isFinite(n) || Number.isInteger(n)) return 0;
373
+ const s = String(n);
374
+ if (s.includes("e") || s.includes("E")) return 0;
375
+ const i = s.indexOf(".");
376
+ return i === -1 ? 0 : s.length - i - 1;
377
+ }
378
+ function rescale(value, decimals) {
379
+ if (!Number.isFinite(value)) return 0;
380
+ const d = Math.min(Math.max(decimals, 0), 12);
381
+ if (d === 0) return Math.round(value);
382
+ const f = 10 ** d;
383
+ const scaled = value * f;
384
+ if (!Number.isFinite(scaled) || Math.abs(scaled) >= 1e15) return normalizeFloat(value);
385
+ return Math.round(scaled) / f;
386
+ }
387
+ function decimalAdd(left, right) {
388
+ const a = toNumber(left);
389
+ const b = toNumber(right);
390
+ const raw = a + b;
391
+ return rescale(raw, Math.max(decimalsOf(a), decimalsOf(b)));
392
+ }
393
+ function decimalSubtract(left, right) {
394
+ const a = toNumber(left);
395
+ const b = toNumber(right);
396
+ const raw = a - b;
397
+ return rescale(raw, Math.max(decimalsOf(a), decimalsOf(b)));
398
+ }
399
+ function decimalMultiply(left, right) {
400
+ const a = toNumber(left);
401
+ const b = toNumber(right);
402
+ const raw = a * b;
403
+ return rescale(raw, decimalsOf(a) + decimalsOf(b));
404
+ }
405
+ function decimalDivide(left, right) {
406
+ const a = toNumber(left);
407
+ const b = toNumber(right);
408
+ if (b === 0) return 0;
409
+ return normalizeFloat(a / b);
410
+ }
411
+ function decimalModulo(left, right) {
412
+ const a = toNumber(left);
413
+ const b = toNumber(right);
414
+ if (b === 0) return 0;
415
+ return normalizeFloat(a % b);
416
+ }
417
+ function normalizeDigits(digits) {
418
+ if (digits === void 0 || digits === null) return 2;
419
+ const d = Math.trunc(toNumber(digits, 2));
420
+ if (!Number.isFinite(d)) return 2;
421
+ return Math.min(Math.max(d, -MAX_DIGITS), MAX_DIGITS);
422
+ }
423
+ function roundTo(value, digits, mode) {
424
+ const n = toNumber(value, 0);
425
+ const d = normalizeDigits(digits);
426
+ if (n === 0) return 0;
427
+ if (Math.abs(n) >= 1e21) return normalizeFloat(n);
428
+ const scale = Math.max(d, 0) + EXTRA_SCALE;
429
+ const fixed = n.toFixed(scale);
430
+ if (!/^-?\d+(\.\d+)?$/.test(fixed)) return normalizeFloat(n);
431
+ const negative = fixed.startsWith("-");
432
+ const digitsStr = fixed.replace("-", "").replace(".", "");
433
+ let scaled = BigInt(digitsStr || "0");
434
+ if (negative) scaled = -scaled;
435
+ const pow10 = 10n ** BigInt(scale - d);
436
+ const sign = scaled < 0n ? -1n : 1n;
437
+ const abs = scaled < 0n ? -scaled : scaled;
438
+ let q = abs / pow10;
439
+ const r = abs % pow10;
440
+ const doubled = 2n * r;
441
+ if (mode === "up") {
442
+ if (r !== 0n) q += 1n;
443
+ } else if (mode !== "down" && doubled >= pow10) {
444
+ if (doubled > pow10 || mode === "half-up" || q % 2n === 1n) q += 1n;
445
+ }
446
+ const result = sign * q;
447
+ return normalizeFloat(d >= 0 ? Number(result) / 10 ** d : Number(result) * 10 ** -d);
448
+ }
449
+
343
450
  // src/evaluator.ts
344
451
  var SAFE_GLOBALS = {
345
452
  Math,
@@ -405,16 +512,17 @@ function evalBinary(node, context, functions) {
405
512
  const left = evaluate(node.left, context, functions);
406
513
  const right = evaluate(node.right, context, functions);
407
514
  switch (node.op) {
515
+ // 数值运算统一走十进制精确实现:消除 0.1 + 0.2 类浮点噪声、字符串数字按数值处理、除零兜底 0
408
516
  case "+":
409
- return left + right;
517
+ return isNumericOperands(left, right) ? decimalAdd(left, right) : concatOperands(left, right);
410
518
  case "-":
411
- return left - right;
519
+ return decimalSubtract(left, right);
412
520
  case "*":
413
- return left * right;
521
+ return decimalMultiply(left, right);
414
522
  case "/":
415
- return left / right;
523
+ return decimalDivide(left, right);
416
524
  case "%":
417
- return left % right;
525
+ return decimalModulo(left, right);
418
526
  case "<":
419
527
  return left < right;
420
528
  case ">":
@@ -443,15 +551,29 @@ function evalUnary(node, context, functions) {
443
551
  const arg = evaluate(node.arg, context, functions);
444
552
  switch (node.op) {
445
553
  case "-":
446
- return -arg;
554
+ return decimalSubtract(0, arg);
447
555
  case "+":
448
- return +arg;
556
+ return toNumber(arg);
449
557
  case "!":
450
558
  return !arg;
451
559
  default:
452
560
  throw new Error(`\u672A\u77E5\u4E00\u5143\u8FD0\u7B97\u7B26: ${node.op}`);
453
561
  }
454
562
  }
563
+ function concatOperands(left, right) {
564
+ return `${left == null ? "" : String(left)}${right == null ? "" : String(right)}`;
565
+ }
566
+ function isNumericOperands(left, right) {
567
+ const l = operandKind(left);
568
+ const r = operandKind(right);
569
+ if (l === "other" || r === "other") return false;
570
+ return l === "num" || r === "num";
571
+ }
572
+ function operandKind(value) {
573
+ if (value == null || value === "") return "neutral";
574
+ if (isNumericLike(value)) return "num";
575
+ return "other";
576
+ }
455
577
  function evalMember(node, context, functions) {
456
578
  const obj = evaluate(node.object, context, functions);
457
579
  if (obj == null) {
@@ -685,6 +807,34 @@ var systemVars = {
685
807
  printTime: () => Date.now()
686
808
  };
687
809
 
810
+ // src/functions/math.ts
811
+ function addNumbers(...values) {
812
+ return values.reduce((acc, v) => decimalAdd(acc, v), 0);
813
+ }
814
+ function subtractNumbers(...values) {
815
+ if (values.length === 0) return 0;
816
+ return values.slice(1).reduce((acc, v) => decimalSubtract(acc, v), decimalAdd(values[0], 0));
817
+ }
818
+ function multiplyNumbers(...values) {
819
+ if (values.length === 0) return 0;
820
+ return values.reduce((acc, v) => decimalMultiply(acc, v), 1);
821
+ }
822
+ function divideNumbers(a, b) {
823
+ return decimalDivide(a, b);
824
+ }
825
+ function round(value, digits) {
826
+ return roundTo(value, digits, "half-up");
827
+ }
828
+ function roundUp(value, digits) {
829
+ return roundTo(value, digits, "up");
830
+ }
831
+ function roundDown(value, digits) {
832
+ return roundTo(value, digits, "down");
833
+ }
834
+ function roundHalfEven(value, digits) {
835
+ return roundTo(value, digits, "half-even");
836
+ }
837
+
688
838
  // src/render/expression-eval.ts
689
839
  var RenderEngine = class {
690
840
  constructor() {
@@ -710,8 +860,19 @@ var FORMAT_FUNCTIONS = {
710
860
  IF: ifFn,
711
861
  CONCAT: (...args) => args.filter((v) => v != null).map(String).join(""),
712
862
  IFEMPTY: (v, d) => v != null && v !== "" ? String(v) : d,
713
- ROUND: (n, d) => Number(Number(n).toFixed(d)),
714
- LEN: (s) => String(s).length
863
+ LEN: (s) => String(s).length,
864
+ // 四则运算(与 +/-/*// 运算符同一套数值语义,供不方便写运算符的场景使用)
865
+ ADD: addNumbers,
866
+ SUB: subtractNumbers,
867
+ MUL: multiplyNumbers,
868
+ DIV: divideNumbers,
869
+ // 数值修约
870
+ ROUND: round,
871
+ ROUNDUP: roundUp,
872
+ CEIL: roundUp,
873
+ ROUNDDOWN: roundDown,
874
+ FLOOR: roundDown,
875
+ ROUNDBANK: roundHalfEven
715
876
  };
716
877
  for (const [name, fn] of Object.entries(FORMAT_FUNCTIONS)) {
717
878
  engine.registerFunction(name, fn);
@@ -756,7 +917,7 @@ function evaluateTemplate(text, ctx) {
756
917
 
757
918
  // src/render/data-binder.ts
758
919
  function bindData(template, printData, baseUrl, fontBaseUrl) {
759
- const data = printData ?? {};
920
+ const data = { ...resolveSystemVariables(), ...printData ?? {} };
760
921
  const bound = JSON.parse(JSON.stringify(template));
761
922
  if (bound.header?.elements) {
762
923
  bound.header.elements = bound.header.elements.map((el) => bindElement(el, data, baseUrl));
@@ -785,7 +946,12 @@ function bindData(template, printData, baseUrl, fontBaseUrl) {
785
946
  function bindElement(el, data, baseUrl) {
786
947
  const cloned = { ...el, options: { ...el.options } };
787
948
  if (typeof cloned.options.formatter === "string") {
788
- cloned.options.formatter = evaluateTemplate(cloned.options.formatter, data);
949
+ const raw = cloned.options.formatter;
950
+ if (referencesPageNumbers(raw)) {
951
+ cloned.options.rawFormatter = raw;
952
+ } else {
953
+ cloned.options.formatter = evaluateTemplate(raw, data);
954
+ }
789
955
  }
790
956
  const isImage = cloned.type === "image" || cloned.printElementType?.type === "image";
791
957
  if (isImage && typeof cloned.options.src === "string") {
@@ -832,39 +998,25 @@ function bindTableData(el, data) {
832
998
  if (dataStartIdx < 0) dataStartIdx = renderRows.length;
833
999
  const ctx = itemCtx(item);
834
1000
  renderRows.push(makeRenderRow(row, (cell) => {
835
- const formatter = cell.formatter;
836
- if (!formatter) return "";
837
- return evaluateTemplate(formatter, ctx);
1001
+ return resolveCellText(cell, ctx);
838
1002
  }));
839
1003
  dataRowCtx.push(ctx);
840
1004
  }
841
1005
  continue;
842
1006
  }
843
1007
  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);
1008
+ const tpl = makeRenderRow(row, (cell) => resolveCellText(cell, { rows: summaryRows, ...data }), true);
849
1009
  subtotalTemplates.push(tpl);
850
1010
  renderRows.push(tpl);
851
1011
  continue;
852
1012
  }
853
1013
  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
- });
1014
+ const summaryRow = makeRenderRow(row, (cell) => resolveCellText(cell, { rows: summaryRows, ...data }));
859
1015
  summaryRenderRows.push(summaryRow);
860
1016
  renderRows.push(summaryRow);
861
1017
  continue;
862
1018
  }
863
- renderRows.push(makeRenderRow(row, (cell) => {
864
- const formatter = cell.formatter;
865
- if (!formatter) return "";
866
- return evaluateTemplate(formatter, data);
867
- }));
1019
+ renderRows.push(makeRenderRow(row, (cell) => resolveCellText(cell, data)));
868
1020
  }
869
1021
  opts._renderRows = renderRows;
870
1022
  opts._repeatHeaderCount = countRepeatHeader(rows);
@@ -874,13 +1026,20 @@ function bindTableData(el, data) {
874
1026
  opts._summaryRows = summaryRenderRows;
875
1027
  opts._mainData = data;
876
1028
  }
1029
+ function resolveCellText(cell, ctx) {
1030
+ const formatter = cell.formatter;
1031
+ if (!formatter) return "";
1032
+ if (referencesPageNumbers(formatter)) return formatter;
1033
+ return evaluateTemplate(formatter, ctx);
1034
+ }
877
1035
  function makeRenderRow(row, resolve, keepRaw = false) {
878
1036
  return {
879
1037
  type: row.type,
880
1038
  height: row.height ?? 8,
881
1039
  cells: row.cells.map((cell) => ({
882
1040
  content: cell.merged ? "" : resolve(cell),
883
- ...keepRaw ? { rawFormatter: cell.merged ? "" : cell.formatter ?? "" } : {},
1041
+ // 小计行(keepRaw)与引用页码的单元格都保留原始表达式:前者按当页数据行重算,后者按当页页码重算
1042
+ ...(keepRaw || referencesPageNumbers(cell.formatter)) && !cell.merged ? { rawFormatter: cell.formatter ?? "" } : {},
884
1043
  cellType: cell.cellType,
885
1044
  barcodeType: cell.barcodeType,
886
1045
  qrCodeLevel: cell.qrCodeLevel,
@@ -908,16 +1067,35 @@ function makeRenderRow(row, resolve, keepRaw = false) {
908
1067
  };
909
1068
  }
910
1069
  function countRepeatHeader(rows) {
1070
+ const headerCount = countLeadingHeaderRows(rows);
1071
+ if (headerCount === 0) return 0;
1072
+ const enabled = rows.slice(0, headerCount).some((row) => row.repeatOnPage === true);
1073
+ if (!enabled) return 0;
1074
+ return alignToRowspanBoundary(rows, headerCount);
1075
+ }
1076
+ function countLeadingHeaderRows(rows) {
911
1077
  let n = 0;
912
1078
  for (const row of rows) {
913
- if (row.type === "header" && row.repeatOnPage === true) {
914
- n++;
915
- } else {
916
- break;
917
- }
1079
+ if (row?.type !== "header") break;
1080
+ n++;
918
1081
  }
919
1082
  return n;
920
1083
  }
1084
+ function isRowspanComplete(rows, n) {
1085
+ for (let r = 0; r < n; r++) {
1086
+ for (const cell of rows[r]?.cells ?? []) {
1087
+ if (cell?.merged) continue;
1088
+ if (r + (cell?.rowspan ?? 1) > n) return false;
1089
+ }
1090
+ }
1091
+ return true;
1092
+ }
1093
+ function alignToRowspanBoundary(rows, max2) {
1094
+ for (let n = max2; n >= 1; n--) {
1095
+ if (isRowspanComplete(rows, n)) return n;
1096
+ }
1097
+ return 1;
1098
+ }
921
1099
  var pad2 = (n) => String(n).padStart(2, "0");
922
1100
  function resolveSystemVariables(now = /* @__PURE__ */ new Date(), page = {}) {
923
1101
  return {
@@ -927,6 +1105,9 @@ function resolveSystemVariables(now = /* @__PURE__ */ new Date(), page = {}) {
927
1105
  totalPages: page.totalPages ?? 1
928
1106
  };
929
1107
  }
1108
+ function referencesPageNumbers(expr) {
1109
+ return typeof expr === "string" && /\b(pageIndex|totalPages)\b/.test(expr);
1110
+ }
930
1111
  function injectSystemVariables(html, now = /* @__PURE__ */ new Date()) {
931
1112
  const { printDate, printTime } = resolveSystemVariables(now);
932
1113
  return html.replace(/\{printDate\}/g, printDate).replace(/\{printTime\}/g, printTime);
@@ -1573,10 +1754,9 @@ function tableDesignBottom(el) {
1573
1754
  const opts = el.options ?? {};
1574
1755
  const top = opts.top ?? 0;
1575
1756
  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);
1757
+ const rowSum = rows.reduce((s, r) => s + (r.height ?? 0), 0);
1758
+ const designHeight = Math.max(opts.height ?? 0, rowSum);
1759
+ return top + designHeight;
1580
1760
  }
1581
1761
  function buildFollowMap(sorted, excludedIds) {
1582
1762
  const map = /* @__PURE__ */ new Map();
@@ -1717,7 +1897,10 @@ function paginate(template, measuredElements) {
1717
1897
  let isFirstPage = true;
1718
1898
  let pageBroken = false;
1719
1899
  function sectionTop(el) {
1720
- return pageBroken ? 0 : el.options?.top ?? 0;
1900
+ return pageBroken ? pageCursorTop() : el.options?.top ?? 0;
1901
+ }
1902
+ function pageCursorTop() {
1903
+ return Math.max(0, fullPageHeight() - remaining);
1721
1904
  }
1722
1905
  function fullPageHeight() {
1723
1906
  return isFirstPage ? contentHeight - overlayH - SAFETY_MARGIN : contentHeight - SAFETY_MARGIN;
@@ -1784,7 +1967,7 @@ function paginate(template, measuredElements) {
1784
1967
  function paginateNonTable(el, measured, sortedList, idx) {
1785
1968
  const elHeight = measured.get(el.id)?.measuredHeight ?? el.options?.height ?? 0;
1786
1969
  if (elHeight <= remaining) {
1787
- const top2 = pageBroken ? 0 : el.options?.top ?? 0;
1970
+ const top2 = pageBroken ? pageCursorTop() : el.options?.top ?? 0;
1788
1971
  noteOverflow(top2 + elHeight);
1789
1972
  currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
1790
1973
  remaining -= elHeight;
@@ -1803,8 +1986,9 @@ function paginate(template, measuredElements) {
1803
1986
  }
1804
1987
  return idx + 1;
1805
1988
  }
1806
- const top = pageBroken ? 0 : el.options?.top ?? 0;
1807
- finishPage(top + elHeight > contentHeight);
1989
+ finishPage();
1990
+ const top = pageBroken ? pageCursorTop() : el.options?.top ?? 0;
1991
+ if (top + elHeight > contentHeight) overflowOnCurrent = true;
1808
1992
  currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
1809
1993
  remaining -= elHeight;
1810
1994
  return idx + 1;
@@ -1820,7 +2004,7 @@ function paginate(template, measuredElements) {
1820
2004
  }
1821
2005
  const unitHeight = Math.max(maxBottom - minTop, 0);
1822
2006
  const place = () => {
1823
- const offset = pageBroken ? -minTop : 0;
2007
+ const offset = pageBroken ? pageCursorTop() - minTop : 0;
1824
2008
  noteOverflow(minTop + offset + unitHeight);
1825
2009
  for (const m of members) {
1826
2010
  currentPage.push({
@@ -1894,6 +2078,7 @@ function paginate(template, measuredElements) {
1894
2078
  const groups = buildRowGroups(bodyRows, rowCount);
1895
2079
  let sliceStart = 0;
1896
2080
  let firstSlice = true;
2081
+ let sliceTop = sectionTop(el);
1897
2082
  for (const g of groups) {
1898
2083
  const gh = bodyHeights.slice(g.start, g.end).reduce((s, h) => s + h, 0);
1899
2084
  if (gh + subtotalH <= remaining) {
@@ -1908,12 +2093,13 @@ function paginate(template, measuredElements) {
1908
2093
  endRow: g.start,
1909
2094
  subtotal: subtotalH > 0,
1910
2095
  ...!firstSlice && repeatCount > 0 ? { repeatHeader: true } : {},
1911
- renderTop: sectionTop(el)
2096
+ renderTop: sliceTop
1912
2097
  });
1913
2098
  firstSlice = false;
1914
2099
  sliceStart = g.start;
1915
2100
  }
1916
2101
  finishPage();
2102
+ sliceTop = sectionTop(el);
1917
2103
  if (!firstSlice) remaining -= repeatH;
1918
2104
  remaining -= gh;
1919
2105
  }
@@ -1925,7 +2111,7 @@ function paginate(template, measuredElements) {
1925
2111
  endRow: rowCount,
1926
2112
  subtotal: subtotalH > 0,
1927
2113
  ...!firstSlice && repeatCount > 0 ? { repeatHeader: true } : {},
1928
- renderTop: sectionTop(el)
2114
+ renderTop: sliceTop
1929
2115
  });
1930
2116
  }
1931
2117
  if (summaryH > 0) {
@@ -2060,6 +2246,13 @@ function resolveBarcodeSize(input) {
2060
2246
  }
2061
2247
 
2062
2248
  // src/render/html-generator.ts
2249
+ function pageVarsContext(vars) {
2250
+ return { ...vars.data ?? {}, pageIndex: vars.pageIndex, totalPages: vars.totalPages };
2251
+ }
2252
+ function pageVarsData(printData) {
2253
+ if (Array.isArray(printData)) return printData[0] ?? {};
2254
+ return printData ?? {};
2255
+ }
2063
2256
  function generateHtml(template, pageLayouts, printData, options) {
2064
2257
  const css = buildFontFaceCss(template.fonts) + buildPageCss(template, options?.pageHeightMm);
2065
2258
  const isMeasure = options?.isMeasurementPass === true;
@@ -2144,22 +2337,25 @@ function renderPage(template, page, pageNum, totalPages, ctx, printData, pageCla
2144
2337
  height: ctx.pageHeightMm && ctx.pageHeightMm > 0 ? ctx.pageHeightMm : paper.height
2145
2338
  };
2146
2339
  const contentWidth = paper.width - template.margins.left - template.margins.right;
2340
+ const pageVars = { pageIndex: pageNum, totalPages, data: pageVarsData(printData) };
2341
+ const scoped = withPageNumbers(template, pageVars);
2342
+ const pageCtx = { ...ctx, pageVars };
2147
2343
  const headerHtml = renderAreaElements(
2148
- template.header?.elements ?? [],
2344
+ scoped.header?.elements ?? [],
2149
2345
  contentWidth,
2150
2346
  pageNum,
2151
2347
  totalPages,
2152
- ctx
2348
+ pageCtx
2153
2349
  );
2154
2350
  const footerHtml = renderAreaElements(
2155
- template.footer?.elements ?? [],
2351
+ scoped.footer?.elements ?? [],
2156
2352
  contentWidth,
2157
2353
  pageNum,
2158
2354
  totalPages,
2159
- ctx
2355
+ pageCtx
2160
2356
  );
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");
2357
+ const overlayHtml = page.pageIndex === 0 ? `<div class="first-page-overlay">${renderAreaElements(scoped.firstPageOverlay?.elements ?? [], contentWidth, pageNum, totalPages, pageCtx)}</div>` : "";
2358
+ let contentHtml = page.sections.map((section) => renderSection(section, scoped, pageCtx)).join("\n");
2163
2359
  contentHtml = contentHtml.replace(/\{pageIndex\}/g, String(pageNum));
2164
2360
  contentHtml = contentHtml.replace(/\{totalPages\}/g, String(totalPages));
2165
2361
  const pageInner = `
@@ -2176,6 +2372,39 @@ function renderPage(template, page, pageNum, totalPages, ctx, printData, pageCla
2176
2372
  ${pageBody}
2177
2373
  </section>`;
2178
2374
  }
2375
+ function withPageNumbers(template, vars) {
2376
+ if (!templateReferencesPageNumbers(template)) return template;
2377
+ const context = pageVarsContext(vars);
2378
+ const mapEl = (el) => {
2379
+ const raw = el.options?.rawFormatter;
2380
+ if (typeof raw !== "string") return el;
2381
+ return { ...el, options: { ...el.options, formatter: evaluateTemplate(raw, context) } };
2382
+ };
2383
+ const mapArea = (area) => area ? { ...area, elements: (area.elements ?? []).map(mapEl) } : area;
2384
+ return {
2385
+ ...template,
2386
+ elements: template.elements.map(mapEl),
2387
+ header: mapArea(template.header),
2388
+ footer: mapArea(template.footer),
2389
+ firstPageOverlay: mapArea(template.firstPageOverlay)
2390
+ };
2391
+ }
2392
+ function templateReferencesPageNumbers(template) {
2393
+ const areas = [
2394
+ template.elements,
2395
+ template.header?.elements,
2396
+ template.footer?.elements,
2397
+ template.firstPageOverlay?.elements
2398
+ ];
2399
+ return areas.some((list) => (list ?? []).some((el) => typeof el.options?.rawFormatter === "string"));
2400
+ }
2401
+ function resolveCellContent(cell, ctx) {
2402
+ const raw = cell.rawFormatter;
2403
+ if (typeof raw === "string" && raw !== "" && ctx?.pageVars) {
2404
+ return evaluateTemplate(raw, pageVarsContext(ctx.pageVars));
2405
+ }
2406
+ return cell.content;
2407
+ }
2179
2408
  function renderSection(section, template, ctx) {
2180
2409
  const el = findElement(template, section.elementId);
2181
2410
  if (!el) {
@@ -2328,7 +2557,7 @@ function matrixCellStyle(cell, opts) {
2328
2557
  }
2329
2558
  return parts.join(";");
2330
2559
  }
2331
- function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOwner = { elementId: "", kind: "b" }) {
2560
+ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOwner = { elementId: "", kind: "b" }, rowLimit) {
2332
2561
  const trs = [];
2333
2562
  const defaultPadding = opts.tableDefaultPadding ?? 1;
2334
2563
  for (let r = start; r < end; r++) {
@@ -2336,12 +2565,14 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOw
2336
2565
  if (!row) continue;
2337
2566
  const idxAttr = withRowIndex ? ` data-row-index="${r}"` : "";
2338
2567
  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}"` : ""}`;
2568
+ const rowspan = rowLimit === void 0 ? cell.rowspan ?? 1 : Math.max(1, Math.min(cell.rowspan ?? 1, rowLimit - r));
2569
+ const span = `${rowspan > 1 ? ` rowspan="${rowspan}"` : ""}${cell.colspan > 1 ? ` colspan="${cell.colspan}"` : ""}`;
2340
2570
  const colIndex = ci;
2341
2571
  let inner;
2342
2572
  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),
2573
+ const codeValue = resolveCellContent(cell, ctx);
2574
+ inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;">${codeImgHtml(codeValue, cell.cellType, cell, {
2575
+ fallback: esc(codeValue),
2345
2576
  codeRenderer: ctx?.codeRenderer,
2346
2577
  fit: cell.fit,
2347
2578
  maxWidth: cell.maxWidth,
@@ -2355,9 +2586,9 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOw
2355
2586
  const fit = cell.fit || "contain";
2356
2587
  const maxWidth = cell.maxWidth ? `max-width:${cell.maxWidth}mm;` : "max-width:100%;";
2357
2588
  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>`;
2589
+ 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>`;
2359
2590
  } else {
2360
- inner = renderTextCellInner(renderRows, r, colIndex, cell, opts, fitOwner, defaultPadding);
2591
+ inner = renderTextCellInner(renderRows, r, colIndex, cell, opts, fitOwner, defaultPadding, ctx);
2361
2592
  }
2362
2593
  return `<td${span} style="${matrixCellStyle(cell, opts)}">${inner}</td>`;
2363
2594
  }).join("");
@@ -2365,9 +2596,9 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOw
2365
2596
  }
2366
2597
  return trs.join("\n");
2367
2598
  }
2368
- function renderTextCellInner(renderRows, rowIndex, colIndex, cell, opts, fitOwner, defaultPadding) {
2599
+ function renderTextCellInner(renderRows, rowIndex, colIndex, cell, opts, fitOwner, defaultPadding, ctx) {
2369
2600
  const fit = resolveCellTextFit(cell);
2370
- const text = esc(cell.content);
2601
+ const text = esc(resolveCellContent(cell, ctx));
2371
2602
  if (fit === "autoHeight") return text;
2372
2603
  const capMm = cellFitCapMm(renderRows, rowIndex, cell, defaultPadding);
2373
2604
  const nowrap = cell.wordWrap === false;
@@ -2405,7 +2636,7 @@ function renderTableSlice(el, section, ctx) {
2405
2636
  const startRow = section.startRow ?? 0;
2406
2637
  const endRow = section.endRow ?? renderRows.length;
2407
2638
  const repeatCount = opts._repeatHeaderCount ?? 0;
2408
- const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }) : "";
2639
+ const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }, repeatCount) : "";
2409
2640
  const bodyHtml = renderMatrixRows(renderRows, startRow, endRow, opts, false, ctx, { elementId: el.id, kind: "b" });
2410
2641
  const subtotalHtml = section.subtotal ? renderSubtotalRows(el, section, opts, ctx) : "";
2411
2642
  const summaryHtml = section.summary ? renderSummaryRows(el, opts, ctx) : "";
@@ -2426,9 +2657,15 @@ function renderSubtotalRows(el, section, opts, ctx) {
2426
2657
  const dataStart = Math.max(startRow, dataStartIdx);
2427
2658
  const dataEnd = Math.max(endRow, dataStart);
2428
2659
  const pageCtx = dataEnd > dataStartIdx ? dataRowCtx.slice(Math.max(dataStart - dataStartIdx, 0), dataEnd - dataStartIdx) : [];
2660
+ const pageVars = ctx?.pageVars ? pageVarsContext(ctx.pageVars) : {};
2429
2661
  const rows = templates.map((tpl) => ({
2430
2662
  ...tpl,
2431
- cells: tpl.cells.map((cell) => cell.rawFormatter ? { ...cell, content: evaluateTemplate(cell.rawFormatter, { rows: pageCtx, ...mainData }) } : cell)
2663
+ cells: tpl.cells.map((cell) => cell.rawFormatter ? {
2664
+ ...cell,
2665
+ content: evaluateTemplate(cell.rawFormatter, { rows: pageCtx, ...mainData, ...pageVars }),
2666
+ // 已按本页上下文求值,清标记避免渲染时再按页码重算一次(会丢掉 rows)
2667
+ rawFormatter: ""
2668
+ } : cell)
2432
2669
  }));
2433
2670
  return renderMatrixRows(rows, 0, rows.length, opts, false, ctx, { elementId: el.id, kind: "st" });
2434
2671
  }
@@ -2451,7 +2688,7 @@ function renderFlowGroup(el, section, template, ctx) {
2451
2688
  if (endRow > startRow || section.subtotal || section.summary) {
2452
2689
  const renderRows = opts._renderRows ?? [];
2453
2690
  const repeatCount = opts._repeatHeaderCount ?? 0;
2454
- const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }) : "";
2691
+ const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }, repeatCount) : "";
2455
2692
  const bodyHtml = renderMatrixRows(renderRows, startRow, endRow, opts, false, ctx, { elementId: el.id, kind: "b" });
2456
2693
  const subtotalHtml = section.subtotal ? renderSubtotalRows(el, section, opts, ctx) : "";
2457
2694
  const summaryHtml = section.summary ? renderSummaryRows(el, opts, ctx) : "";
@@ -3470,6 +3707,14 @@ export {
3470
3707
  setPageIndex,
3471
3708
  setTotalPages,
3472
3709
  systemVars,
3710
+ addNumbers,
3711
+ subtractNumbers,
3712
+ multiplyNumbers,
3713
+ divideNumbers,
3714
+ round,
3715
+ roundUp,
3716
+ roundDown,
3717
+ roundHalfEven,
3473
3718
  safeEval,
3474
3719
  evaluateTemplate,
3475
3720
  bindData,