@worm-vue3-print/core 1.3.7 → 1.3.8

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
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.7
2
+ * @worm-vue3-print/core v1.3.8
3
3
  * Copyright (c) 2026 worm <liu_long_qqcom@qq.com>
4
4
  * SPDX-FileCopyrightText: 2026 worm <liu_long_qqcom@qq.com>
5
5
  * SPDX-License-Identifier: MIT
@@ -858,55 +858,55 @@ function formatDate(value, fmt) {
858
858
  const pad3 = (n) => String(n).padStart(2, "0");
859
859
  return fmt.replace("YYYY", String(date.getFullYear())).replace("MM", pad3(date.getMonth() + 1)).replace("DD", pad3(date.getDate())).replace("HH", pad3(date.getHours())).replace("mm", pad3(date.getMinutes())).replace("ss", pad3(date.getSeconds()));
860
860
  }
861
+ var CN_DIGITS = ["\u96F6", "\u58F9", "\u8D30", "\u53C1", "\u8086", "\u4F0D", "\u9646", "\u67D2", "\u634C", "\u7396"];
862
+ var CN_UNITS = ["", "\u62FE", "\u4F70", "\u4EDF"];
863
+ var CN_GROUP_UNITS = ["", "\u4E07", "\u4EBF", "\u4E07\u4EBF", "\u4EBF\u4EBF"];
864
+ function fourDigitToCapital(n) {
865
+ let out = "";
866
+ let zeroPending = false;
867
+ for (let pos = 3; pos >= 0; pos--) {
868
+ const d = Math.floor(n / 10 ** pos) % 10;
869
+ if (d === 0) {
870
+ if (out) zeroPending = true;
871
+ continue;
872
+ }
873
+ if (zeroPending) {
874
+ out += "\u96F6";
875
+ zeroPending = false;
876
+ }
877
+ out += CN_DIGITS[d] + CN_UNITS[pos];
878
+ }
879
+ return out;
880
+ }
861
881
  function toUpperCaseAmount(value) {
862
882
  const num = Number(value);
863
883
  if (isNaN(num)) return String(value);
864
884
  if (num === 0) return "\u96F6\u5143\u6574";
865
- const digits = ["\u96F6", "\u58F9", "\u8D30", "\u53C1", "\u8086", "\u4F0D", "\u9646", "\u67D2", "\u634C", "\u7396"];
866
- const units = ["", "\u62FE", "\u4F70", "\u4EDF"];
867
- const bigUnits = ["", "\u4E07", "\u4EBF"];
868
885
  const sign = num < 0 ? "\u8D1F" : "";
869
- const abs = Math.abs(num);
870
- const [intStr, decStr] = abs.toFixed(2).split(".");
871
- const intNum = parseInt(intStr, 10);
886
+ const [intStr, decStr] = Math.abs(num).toFixed(2).split(".");
887
+ const groupCount = Math.ceil(intStr.length / 4);
872
888
  let result = "";
873
- if (intNum > 0) {
874
- const intChars = intStr.split("").reverse();
875
- let groupIdx = 0;
876
- let lastDigit = 0;
877
- let needZero = false;
878
- for (let i = 0; i < intChars.length; i++) {
879
- const d = parseInt(intChars[i], 10);
880
- const unit = units[groupIdx % 4];
881
- const bigUnit = bigUnits[Math.floor(groupIdx / 4)];
882
- if (d === 0) {
883
- if (lastDigit !== 0) needZero = true;
884
- } else {
885
- if (needZero) {
886
- result = "\u96F6" + result;
887
- needZero = false;
888
- }
889
- result = digits[d] + unit + result;
890
- lastDigit = d;
891
- }
892
- if (groupIdx % 4 === 3 && groupIdx > 0) {
893
- result = bigUnit + result;
894
- }
895
- groupIdx++;
889
+ let zeroPending = false;
890
+ for (let groupIdx = groupCount - 1; groupIdx >= 0; groupIdx--) {
891
+ const end = intStr.length - groupIdx * 4;
892
+ const group = parseInt(intStr.slice(Math.max(0, end - 4), end), 10);
893
+ if (group === 0) {
894
+ if (result) zeroPending = true;
895
+ continue;
896
896
  }
897
- result += "\u5143";
897
+ if (result && (zeroPending || group < 1e3)) result += "\u96F6";
898
+ zeroPending = false;
899
+ result += fourDigitToCapital(group) + (CN_GROUP_UNITS[groupIdx] ?? "");
898
900
  }
901
+ result = `${result || "\u96F6"}\u5143`;
899
902
  const jiao = parseInt(decStr[0], 10);
900
903
  const fen = parseInt(decStr[1], 10);
901
904
  if (jiao === 0 && fen === 0) {
902
905
  result += "\u6574";
903
906
  } else {
904
- if (jiao > 0) {
905
- result += digits[jiao] + "\u89D2";
906
- }
907
- if (fen > 0) {
908
- result += digits[fen] + "\u5206";
909
- }
907
+ if (jiao === 0) result += "\u96F6";
908
+ else result += CN_DIGITS[jiao] + "\u89D2";
909
+ if (fen > 0) result += CN_DIGITS[fen] + "\u5206";
910
910
  }
911
911
  return sign + result;
912
912
  }
@@ -1194,6 +1194,7 @@ function makeRenderRow(row, resolve, keepRaw = false) {
1194
1194
  printerDpi: cell.printerDpi,
1195
1195
  fit: cell.fit,
1196
1196
  objectPosition: cell.objectPosition,
1197
+ fitScale: cell.fitScale,
1197
1198
  maxWidth: cell.maxWidth,
1198
1199
  maxHeight: cell.maxHeight,
1199
1200
  rowspan: cell.rowspan ?? 1,
@@ -1702,6 +1703,9 @@ function parseCellFitKey(key) {
1702
1703
  if (!Number.isFinite(rowIndex) || !Number.isFinite(colIndex)) return void 0;
1703
1704
  return { elementId: key.slice(0, first), kind, rowIndex, colIndex };
1704
1705
  }
1706
+ function cellImageBoxMm(fit, capMm) {
1707
+ return (fit || "contain") === "cover" ? capMm : void 0;
1708
+ }
1705
1709
  function cellFitWidthMm(colWidths, colIndex, cell, defaultPadding = 1) {
1706
1710
  const span = Math.max(cell?.colspan ?? 1, 1);
1707
1711
  let width = 0;
@@ -1753,6 +1757,35 @@ function resolveElementBorder(type, opts) {
1753
1757
  return { all, edges };
1754
1758
  }
1755
1759
 
1760
+ // src/render/crop-style.ts
1761
+ var OBJECT_POSITION_UNSAFE = /[;"'<>\\(){}\[\]@!#]/;
1762
+ var OBJECT_POSITION_MAX_LEN = 64;
1763
+ var CROP_SCALE_MIN = 100;
1764
+ var CROP_SCALE_MAX = 400;
1765
+ function sanitizeObjectPosition(value) {
1766
+ const v = (value ?? "").trim();
1767
+ if (!v || v.length > OBJECT_POSITION_MAX_LEN || OBJECT_POSITION_UNSAFE.test(v)) return "";
1768
+ return v;
1769
+ }
1770
+ function cropScalePct(fit, scale) {
1771
+ if ((fit || "contain") !== "cover") return void 0;
1772
+ if (typeof scale !== "number" || !Number.isFinite(scale)) return void 0;
1773
+ const pct = Math.round(scale);
1774
+ if (pct <= CROP_SCALE_MIN) return void 0;
1775
+ return Math.min(pct, CROP_SCALE_MAX);
1776
+ }
1777
+ function cropStyleDeclarations(fit, scale, objectPosition) {
1778
+ const pos = sanitizeObjectPosition(objectPosition);
1779
+ const out = {};
1780
+ if (pos) out.objectPosition = pos;
1781
+ const pct = cropScalePct(fit, scale);
1782
+ if (pct !== void 0) {
1783
+ out.transform = `scale(${Number((pct / 100).toFixed(2))})`;
1784
+ out.transformOrigin = pos || "50% 50%";
1785
+ }
1786
+ return out;
1787
+ }
1788
+
1756
1789
  // src/render/watermark.ts
1757
1790
  var PX_PER_MM = 96 / 25.4;
1758
1791
  var MM_PER_PX = 25.4 / 96;
@@ -1954,6 +1987,17 @@ function followGroupHeight(el, members, template, _measuredElements) {
1954
1987
  function effectiveHeight(el, measured) {
1955
1988
  return Math.max(el.options?.height ?? 0, measured.get(el.id)?.measuredHeight ?? 0);
1956
1989
  }
1990
+ function xRange(el) {
1991
+ const o = el.options ?? {};
1992
+ const x0 = o.left ?? 0;
1993
+ return [x0, x0 + (o.width ?? 100)];
1994
+ }
1995
+ function xIntersects(a, b) {
1996
+ return a[0] < b[1] && b[0] < a[1];
1997
+ }
1998
+ function xUnion(a, b) {
1999
+ return [Math.min(a[0], b[0]), Math.max(a[1], b[1])];
2000
+ }
1957
2001
  function buildPaginationUnits(sorted, measured, groupMap) {
1958
2002
  const unitOf = /* @__PURE__ */ new Map();
1959
2003
  for (const members of groupMap.values()) {
@@ -1961,7 +2005,8 @@ function buildPaginationUnits(sorted, measured, groupMap) {
1961
2005
  const unit = {
1962
2006
  anchorId: members[0].id,
1963
2007
  ids: members.map((m) => m.id),
1964
- forceFirstPage: members.some((m) => paginationOf(m)?.pageable === false)
2008
+ forceFirstPage: members.some((m) => paginationOf(m)?.pageable === false),
2009
+ band: members.map(xRange).reduce(xUnion)
1965
2010
  };
1966
2011
  for (const m of members) unitOf.set(m.id, unit);
1967
2012
  }
@@ -1993,11 +2038,13 @@ function buildPaginationUnits(sorted, measured, groupMap) {
1993
2038
  if (active[i].bottom <= top) active.splice(i, 1);
1994
2039
  }
1995
2040
  const bottom = top + effectiveHeight(el, measured);
2041
+ const x = xRange(el);
1996
2042
  for (const a of active) {
1997
- if (a.bottom > top) union(el.id, a.id);
2043
+ if (a.bottom > top && xIntersects(a.x, x)) union(el.id, a.id);
1998
2044
  }
1999
- active.push({ id: el.id, top, bottom });
2045
+ active.push({ id: el.id, top, bottom, x });
2000
2046
  }
2047
+ const eligibleById = new Map(eligible.map((e) => [e.id, e]));
2001
2048
  const clusters = /* @__PURE__ */ new Map();
2002
2049
  for (const el of eligible) {
2003
2050
  const root = find(el.id);
@@ -2007,7 +2054,12 @@ function buildPaginationUnits(sorted, measured, groupMap) {
2007
2054
  }
2008
2055
  for (const ids of clusters.values()) {
2009
2056
  if (ids.length < 2) continue;
2010
- const unit = { anchorId: ids[0], ids, forceFirstPage: false };
2057
+ const unit = {
2058
+ anchorId: ids[0],
2059
+ ids,
2060
+ forceFirstPage: false,
2061
+ band: ids.map((id) => xRange(eligibleById.get(id))).reduce(xUnion)
2062
+ };
2011
2063
  for (const id of ids) unitOf.set(id, unit);
2012
2064
  }
2013
2065
  return unitOf;
@@ -2056,17 +2108,30 @@ function paginate(template, measuredElements) {
2056
2108
  const unitOf = buildPaginationUnits(sorted, measuredElements, groupMap);
2057
2109
  const pages = [];
2058
2110
  let currentPage = [];
2059
- let remaining = contentHeight - SAFETY_MARGIN;
2111
+ let occupied = [];
2060
2112
  let pageBroken = false;
2061
- function sectionTop(el) {
2062
- return pageBroken ? pageCursorTop() : el.options?.top ?? 0;
2063
- }
2064
- function pageCursorTop() {
2065
- return Math.max(0, fullPageHeight() - remaining);
2066
- }
2067
2113
  function fullPageHeight() {
2068
2114
  return contentHeight - SAFETY_MARGIN;
2069
2115
  }
2116
+ function depthIn(x) {
2117
+ let used = 0;
2118
+ for (const r of occupied) {
2119
+ if (xIntersects(x, [r.x0, r.x1])) used += r.height;
2120
+ }
2121
+ return Math.max(0, used);
2122
+ }
2123
+ function remainingIn(x) {
2124
+ return fullPageHeight() - depthIn(x);
2125
+ }
2126
+ function occupy(x, height) {
2127
+ occupied.push({ x0: x[0], x1: x[1], height });
2128
+ }
2129
+ function sectionTop(el, x = xRange(el)) {
2130
+ return pageBroken ? pageCursorTop(x) : el.options?.top ?? 0;
2131
+ }
2132
+ function pageCursorTop(x) {
2133
+ return Math.max(0, fullPageHeight() - remainingIn(x));
2134
+ }
2070
2135
  let overflowOnCurrent = false;
2071
2136
  function pushPage() {
2072
2137
  pages.push(
@@ -2083,7 +2148,7 @@ function paginate(template, measuredElements) {
2083
2148
  }
2084
2149
  pushPage();
2085
2150
  currentPage = [];
2086
- remaining = contentHeight - SAFETY_MARGIN;
2151
+ occupied = [];
2087
2152
  pageBroken = true;
2088
2153
  overflowOnCurrent = false;
2089
2154
  }
@@ -2127,31 +2192,32 @@ function paginate(template, measuredElements) {
2127
2192
  return pages;
2128
2193
  function paginateNonTable(el, measured, sortedList, idx) {
2129
2194
  const elHeight = measured.get(el.id)?.measuredHeight ?? el.options?.height ?? 0;
2130
- if (elHeight <= remaining) {
2131
- const top2 = pageBroken ? pageCursorTop() : el.options?.top ?? 0;
2132
- noteOverflow(top2 + elHeight);
2133
- currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
2134
- remaining -= elHeight;
2195
+ const x = xRange(el);
2196
+ const place = () => {
2197
+ const top = sectionTop(el, x);
2198
+ noteOverflow(top + elHeight);
2199
+ currentPage.push({ elementId: el.id, type: "element", renderTop: top });
2200
+ occupy(x, elHeight);
2201
+ };
2202
+ if (elHeight <= remainingIn(x)) {
2203
+ place();
2135
2204
  if (paginationOf(el)?.keepWithNext && idx + 1 < sortedList.length) {
2136
2205
  const nextEl = sortedList[idx + 1];
2137
2206
  const nextHeight = measured.get(nextEl.id)?.measuredHeight ?? nextEl.options?.height ?? 0;
2138
2207
  if (elHeight + nextHeight > fullPageHeight()) {
2139
2208
  return idx + 1;
2140
2209
  }
2141
- if (nextHeight > remaining) {
2210
+ if (nextHeight > remainingIn(x)) {
2142
2211
  currentPage.pop();
2212
+ occupied.pop();
2143
2213
  finishPage();
2144
- currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
2145
- remaining -= elHeight;
2214
+ place();
2146
2215
  }
2147
2216
  }
2148
2217
  return idx + 1;
2149
2218
  }
2150
2219
  finishPage();
2151
- const top = pageBroken ? pageCursorTop() : el.options?.top ?? 0;
2152
- if (top + elHeight > contentHeight) overflowOnCurrent = true;
2153
- currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
2154
- remaining -= elHeight;
2220
+ place();
2155
2221
  return idx + 1;
2156
2222
  }
2157
2223
  function paginateUnit(unit, idx) {
@@ -2164,8 +2230,9 @@ function paginate(template, measuredElements) {
2164
2230
  maxBottom = Math.max(maxBottom, top2 + effectiveHeight(m, measuredElements));
2165
2231
  }
2166
2232
  const unitHeight = Math.max(maxBottom - minTop, 0);
2233
+ const band = unit.band;
2167
2234
  const place = () => {
2168
- const offset = pageBroken ? pageCursorTop() - minTop : 0;
2235
+ const offset = pageBroken ? pageCursorTop(band) - minTop : 0;
2169
2236
  noteOverflow(minTop + offset + unitHeight);
2170
2237
  for (const m of members) {
2171
2238
  currentPage.push({
@@ -2174,9 +2241,9 @@ function paginate(template, measuredElements) {
2174
2241
  renderTop: (m.options?.top ?? 0) + offset
2175
2242
  });
2176
2243
  }
2177
- remaining -= unitHeight;
2244
+ occupy(band, unitHeight);
2178
2245
  };
2179
- if (unitHeight <= remaining) {
2246
+ if (unitHeight <= remainingIn(band)) {
2180
2247
  place();
2181
2248
  return idx + 1;
2182
2249
  }
@@ -2200,7 +2267,8 @@ function paginate(template, measuredElements) {
2200
2267
  const rowCount = Math.min(bodyRows.length, bodyHeights.length);
2201
2268
  const repeatCount = opts._repeatHeaderCount ?? 0;
2202
2269
  const repeatH = measured.get(el.id)?.repeatHeaderHeight ?? rowHeights.slice(0, repeatCount).reduce((s, h) => s + h, 0);
2203
- const freshAvail = contentHeight - SAFETY_MARGIN;
2270
+ const freshAvail = fullPageHeight();
2271
+ const tb = xRange(el);
2204
2272
  if (repeatH > freshAvail) {
2205
2273
  throw new Error(
2206
2274
  `\u8868\u683C ${el.id} \u7684\u91CD\u590D\u8868\u5934\u9AD8\u5EA6 (${repeatH.toFixed(1)}mm) \u8D85\u8FC7\u9875\u9762\u53EF\u7528\u9AD8\u5EA6 (${freshAvail.toFixed(1)}mm)\uFF0C\u8BF7\u51CF\u5C11\u91CD\u590D\u8868\u5934\u884C`
@@ -2213,16 +2281,16 @@ function paginate(template, measuredElements) {
2213
2281
  startRow: 0,
2214
2282
  endRow: 0,
2215
2283
  summary: summaryH > 0,
2216
- renderTop: sectionTop(el)
2284
+ renderTop: sectionTop(el, tb)
2217
2285
  });
2218
- remaining -= summaryH;
2219
- applyFollowGroup(el, measured);
2286
+ occupy(tb, summaryH);
2287
+ applyFollowGroup(el, measured, tb);
2220
2288
  return idx + 1;
2221
2289
  }
2222
2290
  const totalH = bodyHeights.slice(0, rowCount).reduce((s, h) => s + h, 0);
2223
2291
  const totalNeed = totalH + subtotalH + summaryH;
2224
2292
  if (tablePaginationOf(el)?.enabled === false) {
2225
- if (totalNeed > remaining && currentPage.length > 0) finishPage();
2293
+ if (totalNeed > remainingIn(tb) && currentPage.length > 0) finishPage();
2226
2294
  currentPage.push({
2227
2295
  elementId: el.id,
2228
2296
  type: "table-slice",
@@ -2230,78 +2298,78 @@ function paginate(template, measuredElements) {
2230
2298
  endRow: rowCount,
2231
2299
  subtotal: subtotalH > 0,
2232
2300
  summary: summaryH > 0,
2233
- renderTop: sectionTop(el)
2301
+ renderTop: sectionTop(el, tb)
2234
2302
  });
2235
- remaining -= totalNeed;
2236
- applyFollowGroup(el, measured);
2303
+ occupy(tb, totalNeed);
2304
+ applyFollowGroup(el, measured, tb);
2237
2305
  return idx + 1;
2238
2306
  }
2239
2307
  const groups = buildRowGroups(bodyRows, rowCount);
2240
2308
  let sliceStart = 0;
2241
2309
  let firstSlice = true;
2242
- let sliceTop = sectionTop(el);
2310
+ let sliceTop = sectionTop(el, tb);
2311
+ let sliceUsed = 0;
2312
+ const avail = () => remainingIn(tb) - sliceUsed;
2313
+ const pushSlice = (endRow, repeatHeader) => {
2314
+ currentPage.push({
2315
+ elementId: el.id,
2316
+ type: "table-slice",
2317
+ startRow: sliceStart,
2318
+ endRow,
2319
+ subtotal: subtotalH > 0,
2320
+ ...!firstSlice && repeatHeader ? { repeatHeader: true } : {},
2321
+ renderTop: sliceTop
2322
+ });
2323
+ occupy(tb, sliceUsed);
2324
+ sliceUsed = 0;
2325
+ };
2243
2326
  for (const g of groups) {
2244
2327
  const gh = bodyHeights.slice(g.start, g.end).reduce((s, h) => s + h, 0);
2245
- if (gh + subtotalH <= remaining) {
2246
- remaining -= gh;
2328
+ if (gh + subtotalH <= avail()) {
2329
+ sliceUsed += gh;
2247
2330
  continue;
2248
2331
  }
2249
2332
  if (g.start > sliceStart) {
2250
- currentPage.push({
2251
- elementId: el.id,
2252
- type: "table-slice",
2253
- startRow: sliceStart,
2254
- endRow: g.start,
2255
- subtotal: subtotalH > 0,
2256
- ...!firstSlice && repeatCount > 0 ? { repeatHeader: true } : {},
2257
- renderTop: sliceTop
2258
- });
2333
+ pushSlice(g.start, repeatCount > 0);
2259
2334
  firstSlice = false;
2260
2335
  sliceStart = g.start;
2261
2336
  }
2262
2337
  finishPage();
2263
- sliceTop = sectionTop(el);
2264
- if (!firstSlice) remaining -= repeatH;
2265
- remaining -= gh;
2338
+ sliceTop = sectionTop(el, tb);
2339
+ sliceUsed = 0;
2340
+ if (!firstSlice) sliceUsed += repeatH;
2341
+ sliceUsed += gh;
2266
2342
  }
2267
2343
  if (sliceStart < rowCount) {
2268
- currentPage.push({
2269
- elementId: el.id,
2270
- type: "table-slice",
2271
- startRow: sliceStart,
2272
- endRow: rowCount,
2273
- subtotal: subtotalH > 0,
2274
- ...!firstSlice && repeatCount > 0 ? { repeatHeader: true } : {},
2275
- renderTop: sliceTop
2276
- });
2344
+ pushSlice(rowCount, repeatCount > 0);
2277
2345
  }
2278
2346
  if (summaryH > 0) {
2279
- if (summaryH <= remaining) {
2347
+ if (summaryH <= avail()) {
2280
2348
  const lastIdx = currentPage.length - 1;
2281
2349
  const last = currentPage[lastIdx];
2282
2350
  if (last && last.type === "table-slice" && last.elementId === el.id) {
2283
2351
  currentPage[lastIdx] = { ...last, summary: true };
2284
- remaining -= summaryH;
2352
+ occupy(tb, summaryH);
2285
2353
  } else {
2286
2354
  finishPage();
2287
2355
  currentPage.push({ elementId: el.id, type: "table-slice", startRow: rowCount, endRow: rowCount, summary: true, renderTop: 0 });
2288
- remaining -= summaryH;
2356
+ occupy(tb, summaryH);
2289
2357
  }
2290
2358
  } else {
2291
2359
  finishPage();
2292
2360
  currentPage.push({ elementId: el.id, type: "table-slice", startRow: rowCount, endRow: rowCount, summary: true, renderTop: 0 });
2293
- remaining -= summaryH;
2361
+ occupy(tb, summaryH);
2294
2362
  }
2295
2363
  }
2296
- applyFollowGroup(el, measured);
2364
+ applyFollowGroup(el, measured, tb);
2297
2365
  return idx + 1;
2298
2366
  }
2299
- function applyFollowGroup(el, measured) {
2367
+ function applyFollowGroup(el, measured, band) {
2300
2368
  const members = followMap.get(el.id) ?? [];
2301
2369
  if (members.length === 0) return;
2302
2370
  const followH = followGroupHeight(el, members, template, measured);
2303
2371
  const opts = el.options ?? {};
2304
- if (followH <= remaining) {
2372
+ if (followH <= remainingIn(band)) {
2305
2373
  const lastIdx = currentPage.length - 1;
2306
2374
  const last = currentPage[lastIdx];
2307
2375
  if (last && last.type === "table-slice" && last.elementId === el.id) {
@@ -2312,7 +2380,7 @@ function paginate(template, measuredElements) {
2312
2380
  // 沿用该切片在本页的定位 top(续片已 stamp 为 0)
2313
2381
  groupTop: last.renderTop ?? opts.top ?? 0
2314
2382
  };
2315
- remaining -= followH;
2383
+ occupy(band, followH);
2316
2384
  return;
2317
2385
  }
2318
2386
  }
@@ -2325,7 +2393,7 @@ function paginate(template, measuredElements) {
2325
2393
  followElementIds: members,
2326
2394
  groupTop: 0
2327
2395
  });
2328
- remaining -= followH;
2396
+ occupy(band, followH);
2329
2397
  }
2330
2398
  }
2331
2399
  function buildRowGroups(renderRows, rowCount) {
@@ -2553,11 +2621,8 @@ function textStyle(opts) {
2553
2621
  function indentStyle(opts) {
2554
2622
  return opts.longTextIndent ? `text-indent:${opts.longTextIndent}em;` : "";
2555
2623
  }
2556
- var OBJECT_POSITION_UNSAFE = /[;"'<>\\(){}\[\]@!#]/;
2557
- function positionStyle(objectPosition) {
2558
- const v = (objectPosition ?? "").trim();
2559
- if (!v || v.length > 64 || OBJECT_POSITION_UNSAFE.test(v)) return "";
2560
- return `object-position:${escapeInlineStyleValue(v)};`;
2624
+ function cropStyle(fit, fitScale, objectPosition) {
2625
+ return Object.entries(cropStyleDeclarations(fit, fitScale, objectPosition)).map(([key, value]) => `${key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)}:${escapeInlineStyleValue(value)};`).join("");
2561
2626
  }
2562
2627
  function elementFitStyle(fit, opts) {
2563
2628
  if (fit === "autoHeight") return "overflow:visible;";
@@ -2597,7 +2662,7 @@ function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
2597
2662
  return renderTableElement(el, isMeasure, measureAttr, ctx);
2598
2663
  case "image":
2599
2664
  return `<div class="print-element" style="${style}"${measureAttr}>
2600
- <img src="${esc(opts.src ?? "")}" style="width:100%;height:100%;object-fit:${opts.fit || "contain"};${positionStyle(opts.objectPosition)}" />
2665
+ <img src="${esc(opts.src ?? "")}" style="width:100%;height:100%;object-fit:${opts.fit || "contain"};${cropStyle(opts.fit, opts.fitScale, opts.objectPosition)}" />
2601
2666
  </div>`;
2602
2667
  case "barcode":
2603
2668
  case "qrcode": {
@@ -2720,7 +2785,10 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOw
2720
2785
  const fit = cell.fit || "contain";
2721
2786
  const maxWidth = cell.maxWidth ? `max-width:${cell.maxWidth}mm;` : "max-width:100%;";
2722
2787
  const maxHeight = cell.maxHeight ? `max-height:${cell.maxHeight}mm;` : "max-height:100%;";
2723
- 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};${positionStyle(cell.objectPosition)}${maxWidth}${maxHeight}display:block;" /></div>`;
2788
+ const boxMm = cellImageBoxMm(fit, cellFitCapMm(renderRows, r, cell, defaultPadding));
2789
+ const boxHeight = boxMm === void 0 ? "height:100%" : `height:${boxMm}mm`;
2790
+ const imgBox = boxMm === void 0 ? "" : "width:100%;height:100%;";
2791
+ inner = `<div style="width:100%;${boxHeight};display:flex;align-items:center;justify-content:center;overflow:hidden;"><img src="${esc(resolveCellContent(cell, ctx))}" style="${imgBox}object-fit:${fit};${cropStyle(fit, cell.fitScale, cell.objectPosition)}${maxWidth}${maxHeight}display:block;" /></div>`;
2724
2792
  } else {
2725
2793
  inner = renderTextCellInner(renderRows, r, colIndex, cell, opts, fitOwner, defaultPadding, ctx);
2726
2794
  }
package/dist/index.d.cts CHANGED
@@ -1,13 +1,13 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.7
2
+ * @worm-vue3-print/core v1.3.8
3
3
  * Copyright (c) 2026 worm <liu_long_qqcom@qq.com>
4
4
  * SPDX-FileCopyrightText: 2026 worm <liu_long_qqcom@qq.com>
5
5
  * SPDX-License-Identifier: MIT
6
6
  */
7
- import { T as TemplateData, C as CodeRenderer, P as PageLayout, J as MeasuredElement, K as TemplateElement, W as WatermarkOptions, L as PaperMm, N as ViewportPx, O as HeightSource, Q as MarginsMm, S as PdfTargetSpec, R as RawMeasurement, a as CodeSpec, X as CodeRenderOptions, D as DriverFactory, Y as ExecutorBundle, Z as TileLayout, _ as PrintJob, $ as PreparedDocument, a0 as RenderPdfResult, M as MultiPageTemplateData, a1 as PrintFontDeclaration } from './driver-Dzr2Aikx.cjs';
8
- export { a2 as ContinuousPaperSize, a3 as EXECUTOR_TARGETS, a4 as ExecutorMethod, a5 as ExecutorTargetKind, a6 as FALLBACK_FONT_STACK, a7 as MAX_BATCH_COPIES, a8 as NormalizedPrintData, a9 as PAPER_DIMENSIONS, aa as PageDriver, ab as PageSection, ac as PaperSize, ad as PrintDataInput, ae as PrintFontFile, af as RenderCell, ag as RenderRequest, ah as RenderRow, ai as SheetPaperSize, aj as TILE_DEFAULTS, H as TextFit, ak as TilingError, al as TilingIssue, am as TilingIssueCode, an as TilingOptions, ao as TilingResolveOptions, ap as TilingTemplateInput, aq as buildFontFaceCss, ar as computeMaxColumns, as as computeTileLayout, at as escapeInlineStyleValue, au as getPaperDimensions, av as isContinuousPaper, aw as isContinuousPaperSize, ax as normalizePrintData, ay as normalizeTilingOptions, az as resolveSheetMm, aA as roundMm, aB as tilePosition, aC as toFontFamilyStack, aD as validateTiling } from './driver-Dzr2Aikx.cjs';
9
- import { F as FitFontSize, P as PrintRuntime } from './ports-Bin4xmo0.cjs';
10
- export { C as CellFitRowKind, h as DEFAULT_READINESS_MS, D as DEFAULT_SHRINK_MIN_FONT_SIZE_PT, i as DEFAULT_TIMEOUT_MS, M as MIN_SHRINK_FONT_SIZE_PT, j as MeasureResult, k as PrintSession, S as SessionBudget, c as cellFitCapMm, a as cellFitKey, f as floorFontSize, p as parseCellFitKey, r as resolveCellTextFit, d as resolveElementTextFit, e as resolveShrinkMinFontSize, g as roundFontSize } from './ports-Bin4xmo0.cjs';
7
+ import { T as TemplateData, C as CodeRenderer, P as PageLayout, J as MeasuredElement, K as TemplateElement, W as WatermarkOptions, L as PaperMm, N as ViewportPx, O as HeightSource, Q as MarginsMm, S as PdfTargetSpec, R as RawMeasurement, a as CodeSpec, X as CodeRenderOptions, D as DriverFactory, Y as ExecutorBundle, Z as TileLayout, _ as PrintJob, $ as PreparedDocument, a0 as RenderPdfResult, M as MultiPageTemplateData, a1 as PrintFontDeclaration } from './driver-hJxBl2_n.cjs';
8
+ export { a2 as ContinuousPaperSize, a3 as EXECUTOR_TARGETS, a4 as ExecutorMethod, a5 as ExecutorTargetKind, a6 as FALLBACK_FONT_STACK, a7 as MAX_BATCH_COPIES, a8 as NormalizedPrintData, a9 as PAPER_DIMENSIONS, aa as PageDriver, ab as PageSection, ac as PaperSize, ad as PrintDataInput, ae as PrintFontFile, af as RenderCell, ag as RenderRequest, ah as RenderRow, ai as SheetPaperSize, aj as TILE_DEFAULTS, H as TextFit, ak as TilingError, al as TilingIssue, am as TilingIssueCode, an as TilingOptions, ao as TilingResolveOptions, ap as TilingTemplateInput, aq as buildFontFaceCss, ar as computeMaxColumns, as as computeTileLayout, at as escapeInlineStyleValue, au as getPaperDimensions, av as isContinuousPaper, aw as isContinuousPaperSize, ax as normalizePrintData, ay as normalizeTilingOptions, az as resolveSheetMm, aA as roundMm, aB as tilePosition, aC as toFontFamilyStack, aD as validateTiling } from './driver-hJxBl2_n.cjs';
9
+ import { F as FitFontSize, P as PrintRuntime } from './ports-B-Jb2XwT.cjs';
10
+ export { C as CellFitRowKind, i as DEFAULT_READINESS_MS, D as DEFAULT_SHRINK_MIN_FONT_SIZE_PT, j as DEFAULT_TIMEOUT_MS, M as MIN_SHRINK_FONT_SIZE_PT, k as MeasureResult, l as PrintSession, S as SessionBudget, c as cellFitCapMm, a as cellFitKey, f as floorFontSize, p as parseCellFitKey, r as resolveCellTextFit, e as resolveElementTextFit, g as resolveShrinkMinFontSize, h as roundFontSize } from './ports-B-Jb2XwT.cjs';
11
11
 
12
12
  type TokenType = 'num' | 'str' | 'bool' | 'ident' | 'punc';
13
13
  interface Token {
@@ -113,9 +113,7 @@ declare function formatMoney(value: any): string;
113
113
  * 支持: YYYY, MM, DD, HH, mm, ss
114
114
  */
115
115
  declare function formatDate(value: any, fmt: string): string;
116
- /**
117
- * 数字转大写金额
118
- */
116
+ /** 数字转大写金额(人民币票据规范:四位一节,万/亿 节单位不可缺) */
119
117
  declare function toUpperCaseAmount(value: any): string;
120
118
  /**
121
119
  * 条件判断函数
package/dist/index.d.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.7
2
+ * @worm-vue3-print/core v1.3.8
3
3
  * Copyright (c) 2026 worm <liu_long_qqcom@qq.com>
4
4
  * SPDX-FileCopyrightText: 2026 worm <liu_long_qqcom@qq.com>
5
5
  * SPDX-License-Identifier: MIT
6
6
  */
7
- import { T as TemplateData, C as CodeRenderer, P as PageLayout, J as MeasuredElement, K as TemplateElement, W as WatermarkOptions, L as PaperMm, N as ViewportPx, O as HeightSource, Q as MarginsMm, S as PdfTargetSpec, R as RawMeasurement, a as CodeSpec, X as CodeRenderOptions, D as DriverFactory, Y as ExecutorBundle, Z as TileLayout, _ as PrintJob, $ as PreparedDocument, a0 as RenderPdfResult, M as MultiPageTemplateData, a1 as PrintFontDeclaration } from './driver-Dzr2Aikx.js';
8
- export { a2 as ContinuousPaperSize, a3 as EXECUTOR_TARGETS, a4 as ExecutorMethod, a5 as ExecutorTargetKind, a6 as FALLBACK_FONT_STACK, a7 as MAX_BATCH_COPIES, a8 as NormalizedPrintData, a9 as PAPER_DIMENSIONS, aa as PageDriver, ab as PageSection, ac as PaperSize, ad as PrintDataInput, ae as PrintFontFile, af as RenderCell, ag as RenderRequest, ah as RenderRow, ai as SheetPaperSize, aj as TILE_DEFAULTS, H as TextFit, ak as TilingError, al as TilingIssue, am as TilingIssueCode, an as TilingOptions, ao as TilingResolveOptions, ap as TilingTemplateInput, aq as buildFontFaceCss, ar as computeMaxColumns, as as computeTileLayout, at as escapeInlineStyleValue, au as getPaperDimensions, av as isContinuousPaper, aw as isContinuousPaperSize, ax as normalizePrintData, ay as normalizeTilingOptions, az as resolveSheetMm, aA as roundMm, aB as tilePosition, aC as toFontFamilyStack, aD as validateTiling } from './driver-Dzr2Aikx.js';
9
- import { F as FitFontSize, P as PrintRuntime } from './ports-B8MFq_Yo.js';
10
- export { C as CellFitRowKind, h as DEFAULT_READINESS_MS, D as DEFAULT_SHRINK_MIN_FONT_SIZE_PT, i as DEFAULT_TIMEOUT_MS, M as MIN_SHRINK_FONT_SIZE_PT, j as MeasureResult, k as PrintSession, S as SessionBudget, c as cellFitCapMm, a as cellFitKey, f as floorFontSize, p as parseCellFitKey, r as resolveCellTextFit, d as resolveElementTextFit, e as resolveShrinkMinFontSize, g as roundFontSize } from './ports-B8MFq_Yo.js';
7
+ import { T as TemplateData, C as CodeRenderer, P as PageLayout, J as MeasuredElement, K as TemplateElement, W as WatermarkOptions, L as PaperMm, N as ViewportPx, O as HeightSource, Q as MarginsMm, S as PdfTargetSpec, R as RawMeasurement, a as CodeSpec, X as CodeRenderOptions, D as DriverFactory, Y as ExecutorBundle, Z as TileLayout, _ as PrintJob, $ as PreparedDocument, a0 as RenderPdfResult, M as MultiPageTemplateData, a1 as PrintFontDeclaration } from './driver-hJxBl2_n.js';
8
+ export { a2 as ContinuousPaperSize, a3 as EXECUTOR_TARGETS, a4 as ExecutorMethod, a5 as ExecutorTargetKind, a6 as FALLBACK_FONT_STACK, a7 as MAX_BATCH_COPIES, a8 as NormalizedPrintData, a9 as PAPER_DIMENSIONS, aa as PageDriver, ab as PageSection, ac as PaperSize, ad as PrintDataInput, ae as PrintFontFile, af as RenderCell, ag as RenderRequest, ah as RenderRow, ai as SheetPaperSize, aj as TILE_DEFAULTS, H as TextFit, ak as TilingError, al as TilingIssue, am as TilingIssueCode, an as TilingOptions, ao as TilingResolveOptions, ap as TilingTemplateInput, aq as buildFontFaceCss, ar as computeMaxColumns, as as computeTileLayout, at as escapeInlineStyleValue, au as getPaperDimensions, av as isContinuousPaper, aw as isContinuousPaperSize, ax as normalizePrintData, ay as normalizeTilingOptions, az as resolveSheetMm, aA as roundMm, aB as tilePosition, aC as toFontFamilyStack, aD as validateTiling } from './driver-hJxBl2_n.js';
9
+ import { F as FitFontSize, P as PrintRuntime } from './ports-CJ0SUYFt.js';
10
+ export { C as CellFitRowKind, i as DEFAULT_READINESS_MS, D as DEFAULT_SHRINK_MIN_FONT_SIZE_PT, j as DEFAULT_TIMEOUT_MS, M as MIN_SHRINK_FONT_SIZE_PT, k as MeasureResult, l as PrintSession, S as SessionBudget, c as cellFitCapMm, a as cellFitKey, f as floorFontSize, p as parseCellFitKey, r as resolveCellTextFit, e as resolveElementTextFit, g as resolveShrinkMinFontSize, h as roundFontSize } from './ports-CJ0SUYFt.js';
11
11
 
12
12
  type TokenType = 'num' | 'str' | 'bool' | 'ident' | 'punc';
13
13
  interface Token {
@@ -113,9 +113,7 @@ declare function formatMoney(value: any): string;
113
113
  * 支持: YYYY, MM, DD, HH, mm, ss
114
114
  */
115
115
  declare function formatDate(value: any, fmt: string): string;
116
- /**
117
- * 数字转大写金额
118
- */
116
+ /** 数字转大写金额(人民币票据规范:四位一节,万/亿 节单位不可缺) */
119
117
  declare function toUpperCaseAmount(value: any): string;
120
118
  /**
121
119
  * 条件判断函数
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.7
2
+ * @worm-vue3-print/core v1.3.8
3
3
  * Copyright (c) 2026 worm <liu_long_qqcom@qq.com>
4
4
  * SPDX-FileCopyrightText: 2026 worm <liu_long_qqcom@qq.com>
5
5
  * SPDX-License-Identifier: MIT
6
6
  */
7
7
  import {
8
8
  TemplateEngine
9
- } from "./chunk-WLGEOVFK.js";
9
+ } from "./chunk-ZKHVOCU5.js";
10
10
  import {
11
11
  DEFAULT_READINESS_MS,
12
12
  DEFAULT_SHRINK_MIN_FONT_SIZE_PT,
@@ -125,7 +125,7 @@ import {
125
125
  tokenize,
126
126
  validateTiling,
127
127
  withTimeout
128
- } from "./chunk-TCI5XQKI.js";
128
+ } from "./chunk-TQJGFV2V.js";
129
129
  export {
130
130
  DEFAULT_READINESS_MS,
131
131
  DEFAULT_SHRINK_MIN_FONT_SIZE_PT,
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.7
2
+ * @worm-vue3-print/core v1.3.8
3
3
  * Copyright (c) 2026 worm <liu_long_qqcom@qq.com>
4
4
  * SPDX-FileCopyrightText: 2026 worm <liu_long_qqcom@qq.com>
5
5
  * SPDX-License-Identifier: MIT
@@ -1,10 +1,10 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.7
2
+ * @worm-vue3-print/core v1.3.8
3
3
  * Copyright (c) 2026 worm <liu_long_qqcom@qq.com>
4
4
  * SPDX-FileCopyrightText: 2026 worm <liu_long_qqcom@qq.com>
5
5
  * SPDX-License-Identifier: MIT
6
6
  */
7
- import { Y as ExecutorBundle } from '../driver-Dzr2Aikx.cjs';
7
+ import { Y as ExecutorBundle } from '../driver-hJxBl2_n.cjs';
8
8
 
9
9
  /** 读取 core 自带的 DOM 执行器 IIFE 产物;宿主把它注入页面后即可调用 __wormDom */
10
10
  declare function loadExecutorBundle(): ExecutorBundle;
@@ -1,10 +1,10 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.7
2
+ * @worm-vue3-print/core v1.3.8
3
3
  * Copyright (c) 2026 worm <liu_long_qqcom@qq.com>
4
4
  * SPDX-FileCopyrightText: 2026 worm <liu_long_qqcom@qq.com>
5
5
  * SPDX-License-Identifier: MIT
6
6
  */
7
- import { Y as ExecutorBundle } from '../driver-Dzr2Aikx.js';
7
+ import { Y as ExecutorBundle } from '../driver-hJxBl2_n.js';
8
8
 
9
9
  /** 读取 core 自带的 DOM 执行器 IIFE 产物;宿主把它注入页面后即可调用 __wormDom */
10
10
  declare function loadExecutorBundle(): ExecutorBundle;
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.7
2
+ * @worm-vue3-print/core v1.3.8
3
3
  * Copyright (c) 2026 worm <liu_long_qqcom@qq.com>
4
4
  * SPDX-FileCopyrightText: 2026 worm <liu_long_qqcom@qq.com>
5
5
  * SPDX-License-Identifier: MIT