@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.
@@ -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);
@@ -2087,10 +2268,9 @@ function tableDesignBottom(el) {
2087
2268
  const opts = el.options ?? {};
2088
2269
  const top = opts.top ?? 0;
2089
2270
  const rows = opts.tableRows ?? [];
2090
- if (rows.length > 0) {
2091
- return top + rows.reduce((s, r) => s + (r.height ?? 0), 0);
2092
- }
2093
- return top + (opts.height ?? 0);
2271
+ const rowSum = rows.reduce((s, r) => s + (r.height ?? 0), 0);
2272
+ const designHeight = Math.max(opts.height ?? 0, rowSum);
2273
+ return top + designHeight;
2094
2274
  }
2095
2275
  function buildFollowMap(sorted, excludedIds) {
2096
2276
  const map = /* @__PURE__ */ new Map();
@@ -2231,7 +2411,10 @@ function paginate(template, measuredElements) {
2231
2411
  let isFirstPage = true;
2232
2412
  let pageBroken = false;
2233
2413
  function sectionTop(el) {
2234
- return pageBroken ? 0 : el.options?.top ?? 0;
2414
+ return pageBroken ? pageCursorTop() : el.options?.top ?? 0;
2415
+ }
2416
+ function pageCursorTop() {
2417
+ return Math.max(0, fullPageHeight() - remaining);
2235
2418
  }
2236
2419
  function fullPageHeight() {
2237
2420
  return isFirstPage ? contentHeight - overlayH - SAFETY_MARGIN : contentHeight - SAFETY_MARGIN;
@@ -2298,7 +2481,7 @@ function paginate(template, measuredElements) {
2298
2481
  function paginateNonTable(el, measured, sortedList, idx) {
2299
2482
  const elHeight = measured.get(el.id)?.measuredHeight ?? el.options?.height ?? 0;
2300
2483
  if (elHeight <= remaining) {
2301
- const top2 = pageBroken ? 0 : el.options?.top ?? 0;
2484
+ const top2 = pageBroken ? pageCursorTop() : el.options?.top ?? 0;
2302
2485
  noteOverflow(top2 + elHeight);
2303
2486
  currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
2304
2487
  remaining -= elHeight;
@@ -2317,8 +2500,9 @@ function paginate(template, measuredElements) {
2317
2500
  }
2318
2501
  return idx + 1;
2319
2502
  }
2320
- const top = pageBroken ? 0 : el.options?.top ?? 0;
2321
- finishPage(top + elHeight > contentHeight);
2503
+ finishPage();
2504
+ const top = pageBroken ? pageCursorTop() : el.options?.top ?? 0;
2505
+ if (top + elHeight > contentHeight) overflowOnCurrent = true;
2322
2506
  currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
2323
2507
  remaining -= elHeight;
2324
2508
  return idx + 1;
@@ -2334,7 +2518,7 @@ function paginate(template, measuredElements) {
2334
2518
  }
2335
2519
  const unitHeight = Math.max(maxBottom - minTop, 0);
2336
2520
  const place = () => {
2337
- const offset = pageBroken ? -minTop : 0;
2521
+ const offset = pageBroken ? pageCursorTop() - minTop : 0;
2338
2522
  noteOverflow(minTop + offset + unitHeight);
2339
2523
  for (const m of members) {
2340
2524
  currentPage.push({
@@ -2408,6 +2592,7 @@ function paginate(template, measuredElements) {
2408
2592
  const groups = buildRowGroups(bodyRows, rowCount);
2409
2593
  let sliceStart = 0;
2410
2594
  let firstSlice = true;
2595
+ let sliceTop = sectionTop(el);
2411
2596
  for (const g of groups) {
2412
2597
  const gh = bodyHeights.slice(g.start, g.end).reduce((s, h) => s + h, 0);
2413
2598
  if (gh + subtotalH <= remaining) {
@@ -2422,12 +2607,13 @@ function paginate(template, measuredElements) {
2422
2607
  endRow: g.start,
2423
2608
  subtotal: subtotalH > 0,
2424
2609
  ...!firstSlice && repeatCount > 0 ? { repeatHeader: true } : {},
2425
- renderTop: sectionTop(el)
2610
+ renderTop: sliceTop
2426
2611
  });
2427
2612
  firstSlice = false;
2428
2613
  sliceStart = g.start;
2429
2614
  }
2430
2615
  finishPage();
2616
+ sliceTop = sectionTop(el);
2431
2617
  if (!firstSlice) remaining -= repeatH;
2432
2618
  remaining -= gh;
2433
2619
  }
@@ -2439,7 +2625,7 @@ function paginate(template, measuredElements) {
2439
2625
  endRow: rowCount,
2440
2626
  subtotal: subtotalH > 0,
2441
2627
  ...!firstSlice && repeatCount > 0 ? { repeatHeader: true } : {},
2442
- renderTop: sectionTop(el)
2628
+ renderTop: sliceTop
2443
2629
  });
2444
2630
  }
2445
2631
  if (summaryH > 0) {
@@ -2520,6 +2706,13 @@ function specialRowHeight(renderRows, rowHeights, type) {
2520
2706
  }
2521
2707
 
2522
2708
  // src/render/html-generator.ts
2709
+ function pageVarsContext(vars) {
2710
+ return { ...vars.data ?? {}, pageIndex: vars.pageIndex, totalPages: vars.totalPages };
2711
+ }
2712
+ function pageVarsData(printData) {
2713
+ if (Array.isArray(printData)) return printData[0] ?? {};
2714
+ return printData ?? {};
2715
+ }
2523
2716
  function generateHtml(template, pageLayouts, printData, options) {
2524
2717
  const css = buildFontFaceCss(template.fonts) + buildPageCss(template, options?.pageHeightMm);
2525
2718
  const isMeasure = options?.isMeasurementPass === true;
@@ -2604,22 +2797,25 @@ function renderPage(template, page, pageNum, totalPages, ctx, printData, pageCla
2604
2797
  height: ctx.pageHeightMm && ctx.pageHeightMm > 0 ? ctx.pageHeightMm : paper.height
2605
2798
  };
2606
2799
  const contentWidth = paper.width - template.margins.left - template.margins.right;
2800
+ const pageVars = { pageIndex: pageNum, totalPages, data: pageVarsData(printData) };
2801
+ const scoped = withPageNumbers(template, pageVars);
2802
+ const pageCtx = { ...ctx, pageVars };
2607
2803
  const headerHtml = renderAreaElements(
2608
- template.header?.elements ?? [],
2804
+ scoped.header?.elements ?? [],
2609
2805
  contentWidth,
2610
2806
  pageNum,
2611
2807
  totalPages,
2612
- ctx
2808
+ pageCtx
2613
2809
  );
2614
2810
  const footerHtml = renderAreaElements(
2615
- template.footer?.elements ?? [],
2811
+ scoped.footer?.elements ?? [],
2616
2812
  contentWidth,
2617
2813
  pageNum,
2618
2814
  totalPages,
2619
- ctx
2815
+ pageCtx
2620
2816
  );
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");
2817
+ const overlayHtml = page.pageIndex === 0 ? `<div class="first-page-overlay">${renderAreaElements(scoped.firstPageOverlay?.elements ?? [], contentWidth, pageNum, totalPages, pageCtx)}</div>` : "";
2818
+ let contentHtml = page.sections.map((section) => renderSection(section, scoped, pageCtx)).join("\n");
2623
2819
  contentHtml = contentHtml.replace(/\{pageIndex\}/g, String(pageNum));
2624
2820
  contentHtml = contentHtml.replace(/\{totalPages\}/g, String(totalPages));
2625
2821
  const pageInner = `
@@ -2636,6 +2832,39 @@ function renderPage(template, page, pageNum, totalPages, ctx, printData, pageCla
2636
2832
  ${pageBody}
2637
2833
  </section>`;
2638
2834
  }
2835
+ function withPageNumbers(template, vars) {
2836
+ if (!templateReferencesPageNumbers(template)) return template;
2837
+ const context = pageVarsContext(vars);
2838
+ const mapEl = (el) => {
2839
+ const raw = el.options?.rawFormatter;
2840
+ if (typeof raw !== "string") return el;
2841
+ return { ...el, options: { ...el.options, formatter: evaluateTemplate(raw, context) } };
2842
+ };
2843
+ const mapArea = (area) => area ? { ...area, elements: (area.elements ?? []).map(mapEl) } : area;
2844
+ return {
2845
+ ...template,
2846
+ elements: template.elements.map(mapEl),
2847
+ header: mapArea(template.header),
2848
+ footer: mapArea(template.footer),
2849
+ firstPageOverlay: mapArea(template.firstPageOverlay)
2850
+ };
2851
+ }
2852
+ function templateReferencesPageNumbers(template) {
2853
+ const areas = [
2854
+ template.elements,
2855
+ template.header?.elements,
2856
+ template.footer?.elements,
2857
+ template.firstPageOverlay?.elements
2858
+ ];
2859
+ return areas.some((list) => (list ?? []).some((el) => typeof el.options?.rawFormatter === "string"));
2860
+ }
2861
+ function resolveCellContent(cell, ctx) {
2862
+ const raw = cell.rawFormatter;
2863
+ if (typeof raw === "string" && raw !== "" && ctx?.pageVars) {
2864
+ return evaluateTemplate(raw, pageVarsContext(ctx.pageVars));
2865
+ }
2866
+ return cell.content;
2867
+ }
2639
2868
  function renderSection(section, template, ctx) {
2640
2869
  const el = findElement(template, section.elementId);
2641
2870
  if (!el) {
@@ -2788,7 +3017,7 @@ function matrixCellStyle(cell, opts) {
2788
3017
  }
2789
3018
  return parts.join(";");
2790
3019
  }
2791
- function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOwner = { elementId: "", kind: "b" }) {
3020
+ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOwner = { elementId: "", kind: "b" }, rowLimit) {
2792
3021
  const trs = [];
2793
3022
  const defaultPadding = opts.tableDefaultPadding ?? 1;
2794
3023
  for (let r = start; r < end; r++) {
@@ -2796,12 +3025,14 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOw
2796
3025
  if (!row) continue;
2797
3026
  const idxAttr = withRowIndex ? ` data-row-index="${r}"` : "";
2798
3027
  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}"` : ""}`;
3028
+ const rowspan = rowLimit === void 0 ? cell.rowspan ?? 1 : Math.max(1, Math.min(cell.rowspan ?? 1, rowLimit - r));
3029
+ const span = `${rowspan > 1 ? ` rowspan="${rowspan}"` : ""}${cell.colspan > 1 ? ` colspan="${cell.colspan}"` : ""}`;
2800
3030
  const colIndex = ci;
2801
3031
  let inner;
2802
3032
  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),
3033
+ const codeValue = resolveCellContent(cell, ctx);
3034
+ inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;">${codeImgHtml(codeValue, cell.cellType, cell, {
3035
+ fallback: esc(codeValue),
2805
3036
  codeRenderer: ctx?.codeRenderer,
2806
3037
  fit: cell.fit,
2807
3038
  maxWidth: cell.maxWidth,
@@ -2815,9 +3046,9 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOw
2815
3046
  const fit = cell.fit || "contain";
2816
3047
  const maxWidth = cell.maxWidth ? `max-width:${cell.maxWidth}mm;` : "max-width:100%;";
2817
3048
  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>`;
3049
+ 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
3050
  } else {
2820
- inner = renderTextCellInner(renderRows, r, colIndex, cell, opts, fitOwner, defaultPadding);
3051
+ inner = renderTextCellInner(renderRows, r, colIndex, cell, opts, fitOwner, defaultPadding, ctx);
2821
3052
  }
2822
3053
  return `<td${span} style="${matrixCellStyle(cell, opts)}">${inner}</td>`;
2823
3054
  }).join("");
@@ -2825,9 +3056,9 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOw
2825
3056
  }
2826
3057
  return trs.join("\n");
2827
3058
  }
2828
- function renderTextCellInner(renderRows, rowIndex, colIndex, cell, opts, fitOwner, defaultPadding) {
3059
+ function renderTextCellInner(renderRows, rowIndex, colIndex, cell, opts, fitOwner, defaultPadding, ctx) {
2829
3060
  const fit = resolveCellTextFit(cell);
2830
- const text = esc(cell.content);
3061
+ const text = esc(resolveCellContent(cell, ctx));
2831
3062
  if (fit === "autoHeight") return text;
2832
3063
  const capMm = cellFitCapMm(renderRows, rowIndex, cell, defaultPadding);
2833
3064
  const nowrap = cell.wordWrap === false;
@@ -2865,7 +3096,7 @@ function renderTableSlice(el, section, ctx) {
2865
3096
  const startRow = section.startRow ?? 0;
2866
3097
  const endRow = section.endRow ?? renderRows.length;
2867
3098
  const repeatCount = opts._repeatHeaderCount ?? 0;
2868
- const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }) : "";
3099
+ const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }, repeatCount) : "";
2869
3100
  const bodyHtml = renderMatrixRows(renderRows, startRow, endRow, opts, false, ctx, { elementId: el.id, kind: "b" });
2870
3101
  const subtotalHtml = section.subtotal ? renderSubtotalRows(el, section, opts, ctx) : "";
2871
3102
  const summaryHtml = section.summary ? renderSummaryRows(el, opts, ctx) : "";
@@ -2886,9 +3117,15 @@ function renderSubtotalRows(el, section, opts, ctx) {
2886
3117
  const dataStart = Math.max(startRow, dataStartIdx);
2887
3118
  const dataEnd = Math.max(endRow, dataStart);
2888
3119
  const pageCtx = dataEnd > dataStartIdx ? dataRowCtx.slice(Math.max(dataStart - dataStartIdx, 0), dataEnd - dataStartIdx) : [];
3120
+ const pageVars = ctx?.pageVars ? pageVarsContext(ctx.pageVars) : {};
2889
3121
  const rows = templates.map((tpl) => ({
2890
3122
  ...tpl,
2891
- cells: tpl.cells.map((cell) => cell.rawFormatter ? { ...cell, content: evaluateTemplate(cell.rawFormatter, { rows: pageCtx, ...mainData }) } : cell)
3123
+ cells: tpl.cells.map((cell) => cell.rawFormatter ? {
3124
+ ...cell,
3125
+ content: evaluateTemplate(cell.rawFormatter, { rows: pageCtx, ...mainData, ...pageVars }),
3126
+ // 已按本页上下文求值,清标记避免渲染时再按页码重算一次(会丢掉 rows)
3127
+ rawFormatter: ""
3128
+ } : cell)
2892
3129
  }));
2893
3130
  return renderMatrixRows(rows, 0, rows.length, opts, false, ctx, { elementId: el.id, kind: "st" });
2894
3131
  }
@@ -2911,7 +3148,7 @@ function renderFlowGroup(el, section, template, ctx) {
2911
3148
  if (endRow > startRow || section.subtotal || section.summary) {
2912
3149
  const renderRows = opts._renderRows ?? [];
2913
3150
  const repeatCount = opts._repeatHeaderCount ?? 0;
2914
- const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }) : "";
3151
+ const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }, repeatCount) : "";
2915
3152
  const bodyHtml = renderMatrixRows(renderRows, startRow, endRow, opts, false, ctx, { elementId: el.id, kind: "b" });
2916
3153
  const subtotalHtml = section.subtotal ? renderSubtotalRows(el, section, opts, ctx) : "";
2917
3154
  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-BBSFBAWT.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-BBSFBAWT.js";
7
7
 
8
8
  // src/index.ts
9
9
  var TemplateEngine = class {