@worm-vue3-print/core 1.3.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -954,6 +954,113 @@ var Parser = class {
954
954
  }
955
955
  };
956
956
 
957
+ // src/numeric.ts
958
+ var MAX_DIGITS = 20;
959
+ var EXTRA_SCALE = 8;
960
+ function toNumber(value, fallback = 0) {
961
+ if (typeof value === "number") return Number.isFinite(value) ? value : fallback;
962
+ if (typeof value === "string") {
963
+ const s = value.trim();
964
+ if (s === "") return fallback;
965
+ const n = Number(s);
966
+ return Number.isFinite(n) ? n : fallback;
967
+ }
968
+ if (value == null) return fallback;
969
+ if (typeof value === "boolean") return value ? 1 : 0;
970
+ return fallback;
971
+ }
972
+ function isNumericLike(value) {
973
+ if (typeof value === "number") return Number.isFinite(value);
974
+ if (typeof value === "string") {
975
+ const s = value.trim();
976
+ return s !== "" && Number.isFinite(Number(s));
977
+ }
978
+ return false;
979
+ }
980
+ function normalizeFloat(value) {
981
+ if (!Number.isFinite(value) || Number.isInteger(value)) return value;
982
+ if (Math.abs(value) >= 1e15) return value;
983
+ return Number(value.toPrecision(12));
984
+ }
985
+ function decimalsOf(n) {
986
+ if (!Number.isFinite(n) || Number.isInteger(n)) return 0;
987
+ const s = String(n);
988
+ if (s.includes("e") || s.includes("E")) return 0;
989
+ const i = s.indexOf(".");
990
+ return i === -1 ? 0 : s.length - i - 1;
991
+ }
992
+ function rescale(value, decimals) {
993
+ if (!Number.isFinite(value)) return 0;
994
+ const d = Math.min(Math.max(decimals, 0), 12);
995
+ if (d === 0) return Math.round(value);
996
+ const f = 10 ** d;
997
+ const scaled = value * f;
998
+ if (!Number.isFinite(scaled) || Math.abs(scaled) >= 1e15) return normalizeFloat(value);
999
+ return Math.round(scaled) / f;
1000
+ }
1001
+ function decimalAdd(left, right) {
1002
+ const a = toNumber(left);
1003
+ const b = toNumber(right);
1004
+ const raw = a + b;
1005
+ return rescale(raw, Math.max(decimalsOf(a), decimalsOf(b)));
1006
+ }
1007
+ function decimalSubtract(left, right) {
1008
+ const a = toNumber(left);
1009
+ const b = toNumber(right);
1010
+ const raw = a - b;
1011
+ return rescale(raw, Math.max(decimalsOf(a), decimalsOf(b)));
1012
+ }
1013
+ function decimalMultiply(left, right) {
1014
+ const a = toNumber(left);
1015
+ const b = toNumber(right);
1016
+ const raw = a * b;
1017
+ return rescale(raw, decimalsOf(a) + decimalsOf(b));
1018
+ }
1019
+ function decimalDivide(left, right) {
1020
+ const a = toNumber(left);
1021
+ const b = toNumber(right);
1022
+ if (b === 0) return 0;
1023
+ return normalizeFloat(a / b);
1024
+ }
1025
+ function decimalModulo(left, right) {
1026
+ const a = toNumber(left);
1027
+ const b = toNumber(right);
1028
+ if (b === 0) return 0;
1029
+ return normalizeFloat(a % b);
1030
+ }
1031
+ function normalizeDigits(digits) {
1032
+ if (digits === void 0 || digits === null) return 2;
1033
+ const d = Math.trunc(toNumber(digits, 2));
1034
+ if (!Number.isFinite(d)) return 2;
1035
+ return Math.min(Math.max(d, -MAX_DIGITS), MAX_DIGITS);
1036
+ }
1037
+ function roundTo(value, digits, mode) {
1038
+ const n = toNumber(value, 0);
1039
+ const d = normalizeDigits(digits);
1040
+ if (n === 0) return 0;
1041
+ if (Math.abs(n) >= 1e21) return normalizeFloat(n);
1042
+ const scale = Math.max(d, 0) + EXTRA_SCALE;
1043
+ const fixed = n.toFixed(scale);
1044
+ if (!/^-?\d+(\.\d+)?$/.test(fixed)) return normalizeFloat(n);
1045
+ const negative = fixed.startsWith("-");
1046
+ const digitsStr = fixed.replace("-", "").replace(".", "");
1047
+ let scaled = BigInt(digitsStr || "0");
1048
+ if (negative) scaled = -scaled;
1049
+ const pow10 = 10n ** BigInt(scale - d);
1050
+ const sign = scaled < 0n ? -1n : 1n;
1051
+ const abs = scaled < 0n ? -scaled : scaled;
1052
+ let q = abs / pow10;
1053
+ const r = abs % pow10;
1054
+ const doubled = 2n * r;
1055
+ if (mode === "up") {
1056
+ if (r !== 0n) q += 1n;
1057
+ } else if (mode !== "down" && doubled >= pow10) {
1058
+ if (doubled > pow10 || mode === "half-up" || q % 2n === 1n) q += 1n;
1059
+ }
1060
+ const result = sign * q;
1061
+ return normalizeFloat(d >= 0 ? Number(result) / 10 ** d : Number(result) * 10 ** -d);
1062
+ }
1063
+
957
1064
  // src/evaluator.ts
958
1065
  var SAFE_GLOBALS = {
959
1066
  Math,
@@ -1019,16 +1126,17 @@ function evalBinary(node, context, functions) {
1019
1126
  const left = evaluate(node.left, context, functions);
1020
1127
  const right = evaluate(node.right, context, functions);
1021
1128
  switch (node.op) {
1129
+ // 数值运算统一走十进制精确实现:消除 0.1 + 0.2 类浮点噪声、字符串数字按数值处理、除零兜底 0
1022
1130
  case "+":
1023
- return left + right;
1131
+ return isNumericOperands(left, right) ? decimalAdd(left, right) : concatOperands(left, right);
1024
1132
  case "-":
1025
- return left - right;
1133
+ return decimalSubtract(left, right);
1026
1134
  case "*":
1027
- return left * right;
1135
+ return decimalMultiply(left, right);
1028
1136
  case "/":
1029
- return left / right;
1137
+ return decimalDivide(left, right);
1030
1138
  case "%":
1031
- return left % right;
1139
+ return decimalModulo(left, right);
1032
1140
  case "<":
1033
1141
  return left < right;
1034
1142
  case ">":
@@ -1057,15 +1165,29 @@ function evalUnary(node, context, functions) {
1057
1165
  const arg = evaluate(node.arg, context, functions);
1058
1166
  switch (node.op) {
1059
1167
  case "-":
1060
- return -arg;
1168
+ return decimalSubtract(0, arg);
1061
1169
  case "+":
1062
- return +arg;
1170
+ return toNumber(arg);
1063
1171
  case "!":
1064
1172
  return !arg;
1065
1173
  default:
1066
1174
  throw new Error(`\u672A\u77E5\u4E00\u5143\u8FD0\u7B97\u7B26: ${node.op}`);
1067
1175
  }
1068
1176
  }
1177
+ function concatOperands(left, right) {
1178
+ return `${left == null ? "" : String(left)}${right == null ? "" : String(right)}`;
1179
+ }
1180
+ function isNumericOperands(left, right) {
1181
+ const l = operandKind(left);
1182
+ const r = operandKind(right);
1183
+ if (l === "other" || r === "other") return false;
1184
+ return l === "num" || r === "num";
1185
+ }
1186
+ function operandKind(value) {
1187
+ if (value == null || value === "") return "neutral";
1188
+ if (isNumericLike(value)) return "num";
1189
+ return "other";
1190
+ }
1069
1191
  function evalMember(node, context, functions) {
1070
1192
  const obj = evaluate(node.object, context, functions);
1071
1193
  if (obj == null) {
@@ -1277,6 +1399,34 @@ function max(rows, field) {
1277
1399
  return Math.max(...rows.map((row) => Number(getByPath(row, field)) || 0));
1278
1400
  }
1279
1401
 
1402
+ // src/functions/math.ts
1403
+ function addNumbers(...values) {
1404
+ return values.reduce((acc, v) => decimalAdd(acc, v), 0);
1405
+ }
1406
+ function subtractNumbers(...values) {
1407
+ if (values.length === 0) return 0;
1408
+ return values.slice(1).reduce((acc, v) => decimalSubtract(acc, v), decimalAdd(values[0], 0));
1409
+ }
1410
+ function multiplyNumbers(...values) {
1411
+ if (values.length === 0) return 0;
1412
+ return values.reduce((acc, v) => decimalMultiply(acc, v), 1);
1413
+ }
1414
+ function divideNumbers(a, b) {
1415
+ return decimalDivide(a, b);
1416
+ }
1417
+ function round(value, digits) {
1418
+ return roundTo(value, digits, "half-up");
1419
+ }
1420
+ function roundUp(value, digits) {
1421
+ return roundTo(value, digits, "up");
1422
+ }
1423
+ function roundDown(value, digits) {
1424
+ return roundTo(value, digits, "down");
1425
+ }
1426
+ function roundHalfEven(value, digits) {
1427
+ return roundTo(value, digits, "half-even");
1428
+ }
1429
+
1280
1430
  // src/render/expression-eval.ts
1281
1431
  var RenderEngine = class {
1282
1432
  constructor() {
@@ -1302,8 +1452,19 @@ var FORMAT_FUNCTIONS = {
1302
1452
  IF: ifFn,
1303
1453
  CONCAT: (...args) => args.filter((v) => v != null).map(String).join(""),
1304
1454
  IFEMPTY: (v, d) => v != null && v !== "" ? String(v) : d,
1305
- ROUND: (n, d) => Number(Number(n).toFixed(d)),
1306
- LEN: (s) => String(s).length
1455
+ LEN: (s) => String(s).length,
1456
+ // 四则运算(与 +/-/*// 运算符同一套数值语义,供不方便写运算符的场景使用)
1457
+ ADD: addNumbers,
1458
+ SUB: subtractNumbers,
1459
+ MUL: multiplyNumbers,
1460
+ DIV: divideNumbers,
1461
+ // 数值修约
1462
+ ROUND: round,
1463
+ ROUNDUP: roundUp,
1464
+ CEIL: roundUp,
1465
+ ROUNDDOWN: roundDown,
1466
+ FLOOR: roundDown,
1467
+ ROUNDBANK: roundHalfEven
1307
1468
  };
1308
1469
  for (const [name, fn] of Object.entries(FORMAT_FUNCTIONS)) {
1309
1470
  engine.registerFunction(name, fn);
@@ -1348,7 +1509,7 @@ function evaluateTemplate(text, ctx) {
1348
1509
 
1349
1510
  // src/render/data-binder.ts
1350
1511
  function bindData(template, printData, baseUrl, fontBaseUrl) {
1351
- const data = printData ?? {};
1512
+ const data = { ...resolveSystemVariables(), ...printData ?? {} };
1352
1513
  const bound = JSON.parse(JSON.stringify(template));
1353
1514
  if (bound.header?.elements) {
1354
1515
  bound.header.elements = bound.header.elements.map((el) => bindElement(el, data, baseUrl));
@@ -1377,7 +1538,12 @@ function bindData(template, printData, baseUrl, fontBaseUrl) {
1377
1538
  function bindElement(el, data, baseUrl) {
1378
1539
  const cloned = { ...el, options: { ...el.options } };
1379
1540
  if (typeof cloned.options.formatter === "string") {
1380
- cloned.options.formatter = evaluateTemplate(cloned.options.formatter, data);
1541
+ const raw = cloned.options.formatter;
1542
+ if (referencesPageNumbers(raw)) {
1543
+ cloned.options.rawFormatter = raw;
1544
+ } else {
1545
+ cloned.options.formatter = evaluateTemplate(raw, data);
1546
+ }
1381
1547
  }
1382
1548
  const isImage = cloned.type === "image" || cloned.printElementType?.type === "image";
1383
1549
  if (isImage && typeof cloned.options.src === "string") {
@@ -1424,39 +1590,25 @@ function bindTableData(el, data) {
1424
1590
  if (dataStartIdx < 0) dataStartIdx = renderRows.length;
1425
1591
  const ctx = itemCtx(item);
1426
1592
  renderRows.push(makeRenderRow(row, (cell) => {
1427
- const formatter = cell.formatter;
1428
- if (!formatter) return "";
1429
- return evaluateTemplate(formatter, ctx);
1593
+ return resolveCellText(cell, ctx);
1430
1594
  }));
1431
1595
  dataRowCtx.push(ctx);
1432
1596
  }
1433
1597
  continue;
1434
1598
  }
1435
1599
  if (mode === "dynamic" && row.type === "subtotal") {
1436
- const tpl = makeRenderRow(row, (cell) => {
1437
- const formatter = cell.formatter;
1438
- if (!formatter) return "";
1439
- return evaluateTemplate(formatter, { rows: summaryRows, ...data });
1440
- }, true);
1600
+ const tpl = makeRenderRow(row, (cell) => resolveCellText(cell, { rows: summaryRows, ...data }), true);
1441
1601
  subtotalTemplates.push(tpl);
1442
1602
  renderRows.push(tpl);
1443
1603
  continue;
1444
1604
  }
1445
1605
  if (mode === "dynamic" && row.type === "summary") {
1446
- const summaryRow = makeRenderRow(row, (cell) => {
1447
- const formatter = cell.formatter;
1448
- if (!formatter) return "";
1449
- return evaluateTemplate(formatter, { rows: summaryRows, ...data });
1450
- });
1606
+ const summaryRow = makeRenderRow(row, (cell) => resolveCellText(cell, { rows: summaryRows, ...data }));
1451
1607
  summaryRenderRows.push(summaryRow);
1452
1608
  renderRows.push(summaryRow);
1453
1609
  continue;
1454
1610
  }
1455
- renderRows.push(makeRenderRow(row, (cell) => {
1456
- const formatter = cell.formatter;
1457
- if (!formatter) return "";
1458
- return evaluateTemplate(formatter, data);
1459
- }));
1611
+ renderRows.push(makeRenderRow(row, (cell) => resolveCellText(cell, data)));
1460
1612
  }
1461
1613
  opts._renderRows = renderRows;
1462
1614
  opts._repeatHeaderCount = countRepeatHeader(rows);
@@ -1466,13 +1618,20 @@ function bindTableData(el, data) {
1466
1618
  opts._summaryRows = summaryRenderRows;
1467
1619
  opts._mainData = data;
1468
1620
  }
1621
+ function resolveCellText(cell, ctx) {
1622
+ const formatter = cell.formatter;
1623
+ if (!formatter) return "";
1624
+ if (referencesPageNumbers(formatter)) return formatter;
1625
+ return evaluateTemplate(formatter, ctx);
1626
+ }
1469
1627
  function makeRenderRow(row, resolve, keepRaw = false) {
1470
1628
  return {
1471
1629
  type: row.type,
1472
1630
  height: row.height ?? 8,
1473
1631
  cells: row.cells.map((cell) => ({
1474
1632
  content: cell.merged ? "" : resolve(cell),
1475
- ...keepRaw ? { rawFormatter: cell.merged ? "" : cell.formatter ?? "" } : {},
1633
+ // 小计行(keepRaw)与引用页码的单元格都保留原始表达式:前者按当页数据行重算,后者按当页页码重算
1634
+ ...(keepRaw || referencesPageNumbers(cell.formatter)) && !cell.merged ? { rawFormatter: cell.formatter ?? "" } : {},
1476
1635
  cellType: cell.cellType,
1477
1636
  barcodeType: cell.barcodeType,
1478
1637
  qrCodeLevel: cell.qrCodeLevel,
@@ -1500,16 +1659,35 @@ function makeRenderRow(row, resolve, keepRaw = false) {
1500
1659
  };
1501
1660
  }
1502
1661
  function countRepeatHeader(rows) {
1662
+ const headerCount = countLeadingHeaderRows(rows);
1663
+ if (headerCount === 0) return 0;
1664
+ const enabled = rows.slice(0, headerCount).some((row) => row.repeatOnPage === true);
1665
+ if (!enabled) return 0;
1666
+ return alignToRowspanBoundary(rows, headerCount);
1667
+ }
1668
+ function countLeadingHeaderRows(rows) {
1503
1669
  let n = 0;
1504
1670
  for (const row of rows) {
1505
- if (row.type === "header" && row.repeatOnPage === true) {
1506
- n++;
1507
- } else {
1508
- break;
1509
- }
1671
+ if (row?.type !== "header") break;
1672
+ n++;
1510
1673
  }
1511
1674
  return n;
1512
1675
  }
1676
+ function isRowspanComplete(rows, n) {
1677
+ for (let r = 0; r < n; r++) {
1678
+ for (const cell of rows[r]?.cells ?? []) {
1679
+ if (cell?.merged) continue;
1680
+ if (r + (cell?.rowspan ?? 1) > n) return false;
1681
+ }
1682
+ }
1683
+ return true;
1684
+ }
1685
+ function alignToRowspanBoundary(rows, max2) {
1686
+ for (let n = max2; n >= 1; n--) {
1687
+ if (isRowspanComplete(rows, n)) return n;
1688
+ }
1689
+ return 1;
1690
+ }
1513
1691
  var pad2 = (n) => String(n).padStart(2, "0");
1514
1692
  function resolveSystemVariables(now = /* @__PURE__ */ new Date(), page = {}) {
1515
1693
  return {
@@ -1519,6 +1697,9 @@ function resolveSystemVariables(now = /* @__PURE__ */ new Date(), page = {}) {
1519
1697
  totalPages: page.totalPages ?? 1
1520
1698
  };
1521
1699
  }
1700
+ function referencesPageNumbers(expr) {
1701
+ return typeof expr === "string" && /\b(pageIndex|totalPages)\b/.test(expr);
1702
+ }
1522
1703
  function injectSystemVariables(html, now = /* @__PURE__ */ new Date()) {
1523
1704
  const { printDate, printTime } = resolveSystemVariables(now);
1524
1705
  return html.replace(/\{printDate\}/g, printDate).replace(/\{printTime\}/g, printTime);
@@ -2520,6 +2701,13 @@ function specialRowHeight(renderRows, rowHeights, type) {
2520
2701
  }
2521
2702
 
2522
2703
  // src/render/html-generator.ts
2704
+ function pageVarsContext(vars) {
2705
+ return { ...vars.data ?? {}, pageIndex: vars.pageIndex, totalPages: vars.totalPages };
2706
+ }
2707
+ function pageVarsData(printData) {
2708
+ if (Array.isArray(printData)) return printData[0] ?? {};
2709
+ return printData ?? {};
2710
+ }
2523
2711
  function generateHtml(template, pageLayouts, printData, options) {
2524
2712
  const css = buildFontFaceCss(template.fonts) + buildPageCss(template, options?.pageHeightMm);
2525
2713
  const isMeasure = options?.isMeasurementPass === true;
@@ -2604,22 +2792,25 @@ function renderPage(template, page, pageNum, totalPages, ctx, printData, pageCla
2604
2792
  height: ctx.pageHeightMm && ctx.pageHeightMm > 0 ? ctx.pageHeightMm : paper.height
2605
2793
  };
2606
2794
  const contentWidth = paper.width - template.margins.left - template.margins.right;
2795
+ const pageVars = { pageIndex: pageNum, totalPages, data: pageVarsData(printData) };
2796
+ const scoped = withPageNumbers(template, pageVars);
2797
+ const pageCtx = { ...ctx, pageVars };
2607
2798
  const headerHtml = renderAreaElements(
2608
- template.header?.elements ?? [],
2799
+ scoped.header?.elements ?? [],
2609
2800
  contentWidth,
2610
2801
  pageNum,
2611
2802
  totalPages,
2612
- ctx
2803
+ pageCtx
2613
2804
  );
2614
2805
  const footerHtml = renderAreaElements(
2615
- template.footer?.elements ?? [],
2806
+ scoped.footer?.elements ?? [],
2616
2807
  contentWidth,
2617
2808
  pageNum,
2618
2809
  totalPages,
2619
- ctx
2810
+ pageCtx
2620
2811
  );
2621
- const overlayHtml = page.pageIndex === 0 ? `<div class="first-page-overlay">${renderAreaElements(template.firstPageOverlay?.elements ?? [], contentWidth, void 0, void 0, ctx)}</div>` : "";
2622
- let contentHtml = page.sections.map((section) => renderSection(section, template, ctx)).join("\n");
2812
+ const overlayHtml = page.pageIndex === 0 ? `<div class="first-page-overlay">${renderAreaElements(scoped.firstPageOverlay?.elements ?? [], contentWidth, pageNum, totalPages, pageCtx)}</div>` : "";
2813
+ let contentHtml = page.sections.map((section) => renderSection(section, scoped, pageCtx)).join("\n");
2623
2814
  contentHtml = contentHtml.replace(/\{pageIndex\}/g, String(pageNum));
2624
2815
  contentHtml = contentHtml.replace(/\{totalPages\}/g, String(totalPages));
2625
2816
  const pageInner = `
@@ -2636,6 +2827,39 @@ function renderPage(template, page, pageNum, totalPages, ctx, printData, pageCla
2636
2827
  ${pageBody}
2637
2828
  </section>`;
2638
2829
  }
2830
+ function withPageNumbers(template, vars) {
2831
+ if (!templateReferencesPageNumbers(template)) return template;
2832
+ const context = pageVarsContext(vars);
2833
+ const mapEl = (el) => {
2834
+ const raw = el.options?.rawFormatter;
2835
+ if (typeof raw !== "string") return el;
2836
+ return { ...el, options: { ...el.options, formatter: evaluateTemplate(raw, context) } };
2837
+ };
2838
+ const mapArea = (area) => area ? { ...area, elements: (area.elements ?? []).map(mapEl) } : area;
2839
+ return {
2840
+ ...template,
2841
+ elements: template.elements.map(mapEl),
2842
+ header: mapArea(template.header),
2843
+ footer: mapArea(template.footer),
2844
+ firstPageOverlay: mapArea(template.firstPageOverlay)
2845
+ };
2846
+ }
2847
+ function templateReferencesPageNumbers(template) {
2848
+ const areas = [
2849
+ template.elements,
2850
+ template.header?.elements,
2851
+ template.footer?.elements,
2852
+ template.firstPageOverlay?.elements
2853
+ ];
2854
+ return areas.some((list) => (list ?? []).some((el) => typeof el.options?.rawFormatter === "string"));
2855
+ }
2856
+ function resolveCellContent(cell, ctx) {
2857
+ const raw = cell.rawFormatter;
2858
+ if (typeof raw === "string" && raw !== "" && ctx?.pageVars) {
2859
+ return evaluateTemplate(raw, pageVarsContext(ctx.pageVars));
2860
+ }
2861
+ return cell.content;
2862
+ }
2639
2863
  function renderSection(section, template, ctx) {
2640
2864
  const el = findElement(template, section.elementId);
2641
2865
  if (!el) {
@@ -2788,7 +3012,7 @@ function matrixCellStyle(cell, opts) {
2788
3012
  }
2789
3013
  return parts.join(";");
2790
3014
  }
2791
- function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOwner = { elementId: "", kind: "b" }) {
3015
+ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOwner = { elementId: "", kind: "b" }, rowLimit) {
2792
3016
  const trs = [];
2793
3017
  const defaultPadding = opts.tableDefaultPadding ?? 1;
2794
3018
  for (let r = start; r < end; r++) {
@@ -2796,12 +3020,14 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOw
2796
3020
  if (!row) continue;
2797
3021
  const idxAttr = withRowIndex ? ` data-row-index="${r}"` : "";
2798
3022
  const tds = row.cells.map((cell, ci) => ({ cell, ci })).filter(({ cell }) => !cell.merged).map(({ cell, ci }) => {
2799
- const span = `${cell.rowspan > 1 ? ` rowspan="${cell.rowspan}"` : ""}${cell.colspan > 1 ? ` colspan="${cell.colspan}"` : ""}`;
3023
+ const rowspan = rowLimit === void 0 ? cell.rowspan ?? 1 : Math.max(1, Math.min(cell.rowspan ?? 1, rowLimit - r));
3024
+ const span = `${rowspan > 1 ? ` rowspan="${rowspan}"` : ""}${cell.colspan > 1 ? ` colspan="${cell.colspan}"` : ""}`;
2800
3025
  const colIndex = ci;
2801
3026
  let inner;
2802
3027
  if (cell.cellType === "barcode" || cell.cellType === "qrcode") {
2803
- inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;">${codeImgHtml(cell.content, cell.cellType, cell, {
2804
- fallback: esc(cell.content),
3028
+ const codeValue = resolveCellContent(cell, ctx);
3029
+ inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;">${codeImgHtml(codeValue, cell.cellType, cell, {
3030
+ fallback: esc(codeValue),
2805
3031
  codeRenderer: ctx?.codeRenderer,
2806
3032
  fit: cell.fit,
2807
3033
  maxWidth: cell.maxWidth,
@@ -2815,9 +3041,9 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOw
2815
3041
  const fit = cell.fit || "contain";
2816
3042
  const maxWidth = cell.maxWidth ? `max-width:${cell.maxWidth}mm;` : "max-width:100%;";
2817
3043
  const maxHeight = cell.maxHeight ? `max-height:${cell.maxHeight}mm;` : "max-height:100%;";
2818
- 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>`;
3044
+ 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>`;
2819
3045
  } else {
2820
- inner = renderTextCellInner(renderRows, r, colIndex, cell, opts, fitOwner, defaultPadding);
3046
+ inner = renderTextCellInner(renderRows, r, colIndex, cell, opts, fitOwner, defaultPadding, ctx);
2821
3047
  }
2822
3048
  return `<td${span} style="${matrixCellStyle(cell, opts)}">${inner}</td>`;
2823
3049
  }).join("");
@@ -2825,9 +3051,9 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOw
2825
3051
  }
2826
3052
  return trs.join("\n");
2827
3053
  }
2828
- function renderTextCellInner(renderRows, rowIndex, colIndex, cell, opts, fitOwner, defaultPadding) {
3054
+ function renderTextCellInner(renderRows, rowIndex, colIndex, cell, opts, fitOwner, defaultPadding, ctx) {
2829
3055
  const fit = resolveCellTextFit(cell);
2830
- const text = esc(cell.content);
3056
+ const text = esc(resolveCellContent(cell, ctx));
2831
3057
  if (fit === "autoHeight") return text;
2832
3058
  const capMm = cellFitCapMm(renderRows, rowIndex, cell, defaultPadding);
2833
3059
  const nowrap = cell.wordWrap === false;
@@ -2865,7 +3091,7 @@ function renderTableSlice(el, section, ctx) {
2865
3091
  const startRow = section.startRow ?? 0;
2866
3092
  const endRow = section.endRow ?? renderRows.length;
2867
3093
  const repeatCount = opts._repeatHeaderCount ?? 0;
2868
- const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }) : "";
3094
+ const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }, repeatCount) : "";
2869
3095
  const bodyHtml = renderMatrixRows(renderRows, startRow, endRow, opts, false, ctx, { elementId: el.id, kind: "b" });
2870
3096
  const subtotalHtml = section.subtotal ? renderSubtotalRows(el, section, opts, ctx) : "";
2871
3097
  const summaryHtml = section.summary ? renderSummaryRows(el, opts, ctx) : "";
@@ -2886,9 +3112,15 @@ function renderSubtotalRows(el, section, opts, ctx) {
2886
3112
  const dataStart = Math.max(startRow, dataStartIdx);
2887
3113
  const dataEnd = Math.max(endRow, dataStart);
2888
3114
  const pageCtx = dataEnd > dataStartIdx ? dataRowCtx.slice(Math.max(dataStart - dataStartIdx, 0), dataEnd - dataStartIdx) : [];
3115
+ const pageVars = ctx?.pageVars ? pageVarsContext(ctx.pageVars) : {};
2889
3116
  const rows = templates.map((tpl) => ({
2890
3117
  ...tpl,
2891
- cells: tpl.cells.map((cell) => cell.rawFormatter ? { ...cell, content: evaluateTemplate(cell.rawFormatter, { rows: pageCtx, ...mainData }) } : cell)
3118
+ cells: tpl.cells.map((cell) => cell.rawFormatter ? {
3119
+ ...cell,
3120
+ content: evaluateTemplate(cell.rawFormatter, { rows: pageCtx, ...mainData, ...pageVars }),
3121
+ // 已按本页上下文求值,清标记避免渲染时再按页码重算一次(会丢掉 rows)
3122
+ rawFormatter: ""
3123
+ } : cell)
2892
3124
  }));
2893
3125
  return renderMatrixRows(rows, 0, rows.length, opts, false, ctx, { elementId: el.id, kind: "st" });
2894
3126
  }
@@ -2911,7 +3143,7 @@ function renderFlowGroup(el, section, template, ctx) {
2911
3143
  if (endRow > startRow || section.subtotal || section.summary) {
2912
3144
  const renderRows = opts._renderRows ?? [];
2913
3145
  const repeatCount = opts._repeatHeaderCount ?? 0;
2914
- const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }) : "";
3146
+ const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }, repeatCount) : "";
2915
3147
  const bodyHtml = renderMatrixRows(renderRows, startRow, endRow, opts, false, ctx, { elementId: el.id, kind: "b" });
2916
3148
  const subtotalHtml = section.subtotal ? renderSubtotalRows(el, section, opts, ctx) : "";
2917
3149
  const summaryHtml = section.summary ? renderSummaryRows(el, opts, ctx) : "";
@@ -1,5 +1,5 @@
1
- import { P as PageLayout, T as TemplateData, M as MultiPageTemplateData, C as CodeRenderer, D as DriverFactory, R as RawMeasurement, a as CodeSpec } from '../driver-CsWkGK4a.cjs';
2
- import { P as PrintRuntime, F as FitFontSize } from '../ports-q934kIC7.cjs';
1
+ import { P as PageLayout, T as TemplateData, M as MultiPageTemplateData, C as CodeRenderer, D as DriverFactory, R as RawMeasurement, a as CodeSpec } from '../driver-Dn_YAzO5.cjs';
2
+ import { P as PrintRuntime, F as FitFontSize } from '../ports-Cf5sjwls.cjs';
3
3
 
4
4
  interface BrowserRenderResult {
5
5
  /** 最终多页 HTML 字符串 */
@@ -1,5 +1,5 @@
1
- import { P as PageLayout, T as TemplateData, M as MultiPageTemplateData, C as CodeRenderer, D as DriverFactory, R as RawMeasurement, a as CodeSpec } from '../driver-CsWkGK4a.js';
2
- import { P as PrintRuntime, F as FitFontSize } from '../ports-B63x7kCX.js';
1
+ import { P as PageLayout, T as TemplateData, M as MultiPageTemplateData, C as CodeRenderer, D as DriverFactory, R as RawMeasurement, a as CodeSpec } from '../driver-Dn_YAzO5.js';
2
+ import { P as PrintRuntime, F as FitFontSize } from '../ports-BvwlA_km.js';
3
3
 
4
4
  interface BrowserRenderResult {
5
5
  /** 最终多页 HTML 字符串 */
@@ -11,7 +11,7 @@ import {
11
11
  prepareDocument,
12
12
  resolveBarcodeSize,
13
13
  resolveShrinkMinFontSize
14
- } from "../chunk-VFEURLAZ.js";
14
+ } from "../chunk-KFRPTLFV.js";
15
15
 
16
16
  // src/browser/browser-code-renderer.ts
17
17
  import JsBarcode from "jsbarcode";
@@ -3,7 +3,7 @@ import {
3
3
  evaluate,
4
4
  parse,
5
5
  tokenize
6
- } from "./chunk-VFEURLAZ.js";
6
+ } from "./chunk-KFRPTLFV.js";
7
7
 
8
8
  // src/index.ts
9
9
  var TemplateEngine = class {