@worm-vue3-print/core 1.3.7 → 1.3.9

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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.7
2
+ * @worm-vue3-print/core v1.3.9
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
@@ -716,55 +716,55 @@ function formatDate(value, fmt) {
716
716
  const pad3 = (n) => String(n).padStart(2, "0");
717
717
  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()));
718
718
  }
719
+ var CN_DIGITS = ["\u96F6", "\u58F9", "\u8D30", "\u53C1", "\u8086", "\u4F0D", "\u9646", "\u67D2", "\u634C", "\u7396"];
720
+ var CN_UNITS = ["", "\u62FE", "\u4F70", "\u4EDF"];
721
+ var CN_GROUP_UNITS = ["", "\u4E07", "\u4EBF", "\u4E07\u4EBF", "\u4EBF\u4EBF"];
722
+ function fourDigitToCapital(n) {
723
+ let out = "";
724
+ let zeroPending = false;
725
+ for (let pos = 3; pos >= 0; pos--) {
726
+ const d = Math.floor(n / 10 ** pos) % 10;
727
+ if (d === 0) {
728
+ if (out) zeroPending = true;
729
+ continue;
730
+ }
731
+ if (zeroPending) {
732
+ out += "\u96F6";
733
+ zeroPending = false;
734
+ }
735
+ out += CN_DIGITS[d] + CN_UNITS[pos];
736
+ }
737
+ return out;
738
+ }
719
739
  function toUpperCaseAmount(value) {
720
740
  const num = Number(value);
721
741
  if (isNaN(num)) return String(value);
722
742
  if (num === 0) return "\u96F6\u5143\u6574";
723
- const digits = ["\u96F6", "\u58F9", "\u8D30", "\u53C1", "\u8086", "\u4F0D", "\u9646", "\u67D2", "\u634C", "\u7396"];
724
- const units = ["", "\u62FE", "\u4F70", "\u4EDF"];
725
- const bigUnits = ["", "\u4E07", "\u4EBF"];
726
743
  const sign = num < 0 ? "\u8D1F" : "";
727
- const abs = Math.abs(num);
728
- const [intStr, decStr] = abs.toFixed(2).split(".");
729
- const intNum = parseInt(intStr, 10);
744
+ const [intStr, decStr] = Math.abs(num).toFixed(2).split(".");
745
+ const groupCount = Math.ceil(intStr.length / 4);
730
746
  let result = "";
731
- if (intNum > 0) {
732
- const intChars = intStr.split("").reverse();
733
- let groupIdx = 0;
734
- let lastDigit = 0;
735
- let needZero = false;
736
- for (let i = 0; i < intChars.length; i++) {
737
- const d = parseInt(intChars[i], 10);
738
- const unit = units[groupIdx % 4];
739
- const bigUnit = bigUnits[Math.floor(groupIdx / 4)];
740
- if (d === 0) {
741
- if (lastDigit !== 0) needZero = true;
742
- } else {
743
- if (needZero) {
744
- result = "\u96F6" + result;
745
- needZero = false;
746
- }
747
- result = digits[d] + unit + result;
748
- lastDigit = d;
749
- }
750
- if (groupIdx % 4 === 3 && groupIdx > 0) {
751
- result = bigUnit + result;
752
- }
753
- groupIdx++;
747
+ let zeroPending = false;
748
+ for (let groupIdx = groupCount - 1; groupIdx >= 0; groupIdx--) {
749
+ const end = intStr.length - groupIdx * 4;
750
+ const group = parseInt(intStr.slice(Math.max(0, end - 4), end), 10);
751
+ if (group === 0) {
752
+ if (result) zeroPending = true;
753
+ continue;
754
754
  }
755
- result += "\u5143";
755
+ if (result && (zeroPending || group < 1e3)) result += "\u96F6";
756
+ zeroPending = false;
757
+ result += fourDigitToCapital(group) + (CN_GROUP_UNITS[groupIdx] ?? "");
756
758
  }
759
+ result = `${result || "\u96F6"}\u5143`;
757
760
  const jiao = parseInt(decStr[0], 10);
758
761
  const fen = parseInt(decStr[1], 10);
759
762
  if (jiao === 0 && fen === 0) {
760
763
  result += "\u6574";
761
764
  } else {
762
- if (jiao > 0) {
763
- result += digits[jiao] + "\u89D2";
764
- }
765
- if (fen > 0) {
766
- result += digits[fen] + "\u5206";
767
- }
765
+ if (jiao === 0) result += "\u96F6";
766
+ else result += CN_DIGITS[jiao] + "\u89D2";
767
+ if (fen > 0) result += CN_DIGITS[fen] + "\u5206";
768
768
  }
769
769
  return sign + result;
770
770
  }
@@ -1052,6 +1052,7 @@ function makeRenderRow(row, resolve, keepRaw = false) {
1052
1052
  printerDpi: cell.printerDpi,
1053
1053
  fit: cell.fit,
1054
1054
  objectPosition: cell.objectPosition,
1055
+ fitScale: cell.fitScale,
1055
1056
  maxWidth: cell.maxWidth,
1056
1057
  maxHeight: cell.maxHeight,
1057
1058
  rowspan: cell.rowspan ?? 1,
@@ -1566,6 +1567,9 @@ function parseCellFitKey(key) {
1566
1567
  if (!Number.isFinite(rowIndex) || !Number.isFinite(colIndex)) return void 0;
1567
1568
  return { elementId: key.slice(0, first), kind, rowIndex, colIndex };
1568
1569
  }
1570
+ function cellImageBoxMm(fit, capMm) {
1571
+ return (fit || "contain") === "cover" ? capMm : void 0;
1572
+ }
1569
1573
  function cellFitWidthMm(colWidths, colIndex, cell, defaultPadding = 1) {
1570
1574
  const span = Math.max(cell?.colspan ?? 1, 1);
1571
1575
  let width = 0;
@@ -1620,6 +1624,35 @@ function resolveElementBorder(type, opts) {
1620
1624
  return { all, edges };
1621
1625
  }
1622
1626
 
1627
+ // src/render/crop-style.ts
1628
+ var OBJECT_POSITION_UNSAFE = /[;"'<>\\(){}\[\]@!#]/;
1629
+ var OBJECT_POSITION_MAX_LEN = 64;
1630
+ var CROP_SCALE_MIN = 100;
1631
+ var CROP_SCALE_MAX = 400;
1632
+ function sanitizeObjectPosition(value) {
1633
+ const v = (value ?? "").trim();
1634
+ if (!v || v.length > OBJECT_POSITION_MAX_LEN || OBJECT_POSITION_UNSAFE.test(v)) return "";
1635
+ return v;
1636
+ }
1637
+ function cropScalePct(fit, scale) {
1638
+ if ((fit || "contain") !== "cover") return void 0;
1639
+ if (typeof scale !== "number" || !Number.isFinite(scale)) return void 0;
1640
+ const pct = Math.round(scale);
1641
+ if (pct <= CROP_SCALE_MIN) return void 0;
1642
+ return Math.min(pct, CROP_SCALE_MAX);
1643
+ }
1644
+ function cropStyleDeclarations(fit, scale, objectPosition) {
1645
+ const pos = sanitizeObjectPosition(objectPosition);
1646
+ const out = {};
1647
+ if (pos) out.objectPosition = pos;
1648
+ const pct = cropScalePct(fit, scale);
1649
+ if (pct !== void 0) {
1650
+ out.transform = `scale(${Number((pct / 100).toFixed(2))})`;
1651
+ out.transformOrigin = pos || "50% 50%";
1652
+ }
1653
+ return out;
1654
+ }
1655
+
1623
1656
  // src/render/watermark.ts
1624
1657
  var PX_PER_MM = 96 / 25.4;
1625
1658
  var MM_PER_PX = 25.4 / 96;
@@ -1821,6 +1854,17 @@ function followGroupHeight(el, members, template, _measuredElements) {
1821
1854
  function effectiveHeight(el, measured) {
1822
1855
  return Math.max(el.options?.height ?? 0, measured.get(el.id)?.measuredHeight ?? 0);
1823
1856
  }
1857
+ function xRange(el) {
1858
+ const o = el.options ?? {};
1859
+ const x0 = o.left ?? 0;
1860
+ return [x0, x0 + (o.width ?? 100)];
1861
+ }
1862
+ function xIntersects(a, b) {
1863
+ return a[0] < b[1] && b[0] < a[1];
1864
+ }
1865
+ function xUnion(a, b) {
1866
+ return [Math.min(a[0], b[0]), Math.max(a[1], b[1])];
1867
+ }
1824
1868
  function buildPaginationUnits(sorted, measured, groupMap) {
1825
1869
  const unitOf = /* @__PURE__ */ new Map();
1826
1870
  for (const members of groupMap.values()) {
@@ -1828,7 +1872,8 @@ function buildPaginationUnits(sorted, measured, groupMap) {
1828
1872
  const unit = {
1829
1873
  anchorId: members[0].id,
1830
1874
  ids: members.map((m) => m.id),
1831
- forceFirstPage: members.some((m) => paginationOf(m)?.pageable === false)
1875
+ forceFirstPage: members.some((m) => paginationOf(m)?.pageable === false),
1876
+ band: members.map(xRange).reduce(xUnion)
1832
1877
  };
1833
1878
  for (const m of members) unitOf.set(m.id, unit);
1834
1879
  }
@@ -1860,11 +1905,13 @@ function buildPaginationUnits(sorted, measured, groupMap) {
1860
1905
  if (active[i].bottom <= top) active.splice(i, 1);
1861
1906
  }
1862
1907
  const bottom = top + effectiveHeight(el, measured);
1908
+ const x = xRange(el);
1863
1909
  for (const a of active) {
1864
- if (a.bottom > top) union(el.id, a.id);
1910
+ if (a.bottom > top && xIntersects(a.x, x)) union(el.id, a.id);
1865
1911
  }
1866
- active.push({ id: el.id, top, bottom });
1912
+ active.push({ id: el.id, top, bottom, x });
1867
1913
  }
1914
+ const eligibleById = new Map(eligible.map((e) => [e.id, e]));
1868
1915
  const clusters = /* @__PURE__ */ new Map();
1869
1916
  for (const el of eligible) {
1870
1917
  const root = find(el.id);
@@ -1874,7 +1921,12 @@ function buildPaginationUnits(sorted, measured, groupMap) {
1874
1921
  }
1875
1922
  for (const ids of clusters.values()) {
1876
1923
  if (ids.length < 2) continue;
1877
- const unit = { anchorId: ids[0], ids, forceFirstPage: false };
1924
+ const unit = {
1925
+ anchorId: ids[0],
1926
+ ids,
1927
+ forceFirstPage: false,
1928
+ band: ids.map((id) => xRange(eligibleById.get(id))).reduce(xUnion)
1929
+ };
1878
1930
  for (const id of ids) unitOf.set(id, unit);
1879
1931
  }
1880
1932
  return unitOf;
@@ -1923,17 +1975,30 @@ function paginate(template, measuredElements) {
1923
1975
  const unitOf = buildPaginationUnits(sorted, measuredElements, groupMap);
1924
1976
  const pages = [];
1925
1977
  let currentPage = [];
1926
- let remaining = contentHeight - SAFETY_MARGIN;
1978
+ let occupied = [];
1927
1979
  let pageBroken = false;
1928
- function sectionTop(el) {
1929
- return pageBroken ? pageCursorTop() : el.options?.top ?? 0;
1930
- }
1931
- function pageCursorTop() {
1932
- return Math.max(0, fullPageHeight() - remaining);
1933
- }
1934
1980
  function fullPageHeight() {
1935
1981
  return contentHeight - SAFETY_MARGIN;
1936
1982
  }
1983
+ function depthIn(x) {
1984
+ let used = 0;
1985
+ for (const r of occupied) {
1986
+ if (xIntersects(x, [r.x0, r.x1])) used += r.height;
1987
+ }
1988
+ return Math.max(0, used);
1989
+ }
1990
+ function remainingIn(x) {
1991
+ return fullPageHeight() - depthIn(x);
1992
+ }
1993
+ function occupy(x, height) {
1994
+ occupied.push({ x0: x[0], x1: x[1], height });
1995
+ }
1996
+ function sectionTop(el, x = xRange(el)) {
1997
+ return pageBroken ? pageCursorTop(x) : el.options?.top ?? 0;
1998
+ }
1999
+ function pageCursorTop(x) {
2000
+ return Math.max(0, fullPageHeight() - remainingIn(x));
2001
+ }
1937
2002
  let overflowOnCurrent = false;
1938
2003
  function pushPage() {
1939
2004
  pages.push(
@@ -1950,7 +2015,7 @@ function paginate(template, measuredElements) {
1950
2015
  }
1951
2016
  pushPage();
1952
2017
  currentPage = [];
1953
- remaining = contentHeight - SAFETY_MARGIN;
2018
+ occupied = [];
1954
2019
  pageBroken = true;
1955
2020
  overflowOnCurrent = false;
1956
2021
  }
@@ -1994,31 +2059,32 @@ function paginate(template, measuredElements) {
1994
2059
  return pages;
1995
2060
  function paginateNonTable(el, measured, sortedList, idx) {
1996
2061
  const elHeight = measured.get(el.id)?.measuredHeight ?? el.options?.height ?? 0;
1997
- if (elHeight <= remaining) {
1998
- const top2 = pageBroken ? pageCursorTop() : el.options?.top ?? 0;
1999
- noteOverflow(top2 + elHeight);
2000
- currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
2001
- remaining -= elHeight;
2062
+ const x = xRange(el);
2063
+ const place = () => {
2064
+ const top = sectionTop(el, x);
2065
+ noteOverflow(top + elHeight);
2066
+ currentPage.push({ elementId: el.id, type: "element", renderTop: top });
2067
+ occupy(x, elHeight);
2068
+ };
2069
+ if (elHeight <= remainingIn(x)) {
2070
+ place();
2002
2071
  if (paginationOf(el)?.keepWithNext && idx + 1 < sortedList.length) {
2003
2072
  const nextEl = sortedList[idx + 1];
2004
2073
  const nextHeight = measured.get(nextEl.id)?.measuredHeight ?? nextEl.options?.height ?? 0;
2005
2074
  if (elHeight + nextHeight > fullPageHeight()) {
2006
2075
  return idx + 1;
2007
2076
  }
2008
- if (nextHeight > remaining) {
2077
+ if (nextHeight > remainingIn(x)) {
2009
2078
  currentPage.pop();
2079
+ occupied.pop();
2010
2080
  finishPage();
2011
- currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
2012
- remaining -= elHeight;
2081
+ place();
2013
2082
  }
2014
2083
  }
2015
2084
  return idx + 1;
2016
2085
  }
2017
2086
  finishPage();
2018
- const top = pageBroken ? pageCursorTop() : el.options?.top ?? 0;
2019
- if (top + elHeight > contentHeight) overflowOnCurrent = true;
2020
- currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
2021
- remaining -= elHeight;
2087
+ place();
2022
2088
  return idx + 1;
2023
2089
  }
2024
2090
  function paginateUnit(unit, idx) {
@@ -2031,8 +2097,9 @@ function paginate(template, measuredElements) {
2031
2097
  maxBottom = Math.max(maxBottom, top2 + effectiveHeight(m, measuredElements));
2032
2098
  }
2033
2099
  const unitHeight = Math.max(maxBottom - minTop, 0);
2100
+ const band = unit.band;
2034
2101
  const place = () => {
2035
- const offset = pageBroken ? pageCursorTop() - minTop : 0;
2102
+ const offset = pageBroken ? pageCursorTop(band) - minTop : 0;
2036
2103
  noteOverflow(minTop + offset + unitHeight);
2037
2104
  for (const m of members) {
2038
2105
  currentPage.push({
@@ -2041,9 +2108,9 @@ function paginate(template, measuredElements) {
2041
2108
  renderTop: (m.options?.top ?? 0) + offset
2042
2109
  });
2043
2110
  }
2044
- remaining -= unitHeight;
2111
+ occupy(band, unitHeight);
2045
2112
  };
2046
- if (unitHeight <= remaining) {
2113
+ if (unitHeight <= remainingIn(band)) {
2047
2114
  place();
2048
2115
  return idx + 1;
2049
2116
  }
@@ -2067,7 +2134,8 @@ function paginate(template, measuredElements) {
2067
2134
  const rowCount = Math.min(bodyRows.length, bodyHeights.length);
2068
2135
  const repeatCount = opts._repeatHeaderCount ?? 0;
2069
2136
  const repeatH = measured.get(el.id)?.repeatHeaderHeight ?? rowHeights.slice(0, repeatCount).reduce((s, h) => s + h, 0);
2070
- const freshAvail = contentHeight - SAFETY_MARGIN;
2137
+ const freshAvail = fullPageHeight();
2138
+ const tb = xRange(el);
2071
2139
  if (repeatH > freshAvail) {
2072
2140
  throw new Error(
2073
2141
  `\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`
@@ -2080,16 +2148,16 @@ function paginate(template, measuredElements) {
2080
2148
  startRow: 0,
2081
2149
  endRow: 0,
2082
2150
  summary: summaryH > 0,
2083
- renderTop: sectionTop(el)
2151
+ renderTop: sectionTop(el, tb)
2084
2152
  });
2085
- remaining -= summaryH;
2086
- applyFollowGroup(el, measured);
2153
+ occupy(tb, summaryH);
2154
+ applyFollowGroup(el, measured, tb);
2087
2155
  return idx + 1;
2088
2156
  }
2089
2157
  const totalH = bodyHeights.slice(0, rowCount).reduce((s, h) => s + h, 0);
2090
2158
  const totalNeed = totalH + subtotalH + summaryH;
2091
2159
  if (tablePaginationOf(el)?.enabled === false) {
2092
- if (totalNeed > remaining && currentPage.length > 0) finishPage();
2160
+ if (totalNeed > remainingIn(tb) && currentPage.length > 0) finishPage();
2093
2161
  currentPage.push({
2094
2162
  elementId: el.id,
2095
2163
  type: "table-slice",
@@ -2097,78 +2165,78 @@ function paginate(template, measuredElements) {
2097
2165
  endRow: rowCount,
2098
2166
  subtotal: subtotalH > 0,
2099
2167
  summary: summaryH > 0,
2100
- renderTop: sectionTop(el)
2168
+ renderTop: sectionTop(el, tb)
2101
2169
  });
2102
- remaining -= totalNeed;
2103
- applyFollowGroup(el, measured);
2170
+ occupy(tb, totalNeed);
2171
+ applyFollowGroup(el, measured, tb);
2104
2172
  return idx + 1;
2105
2173
  }
2106
2174
  const groups = buildRowGroups(bodyRows, rowCount);
2107
2175
  let sliceStart = 0;
2108
2176
  let firstSlice = true;
2109
- let sliceTop = sectionTop(el);
2177
+ let sliceTop = sectionTop(el, tb);
2178
+ let sliceUsed = 0;
2179
+ const avail = () => remainingIn(tb) - sliceUsed;
2180
+ const pushSlice = (endRow, repeatHeader) => {
2181
+ currentPage.push({
2182
+ elementId: el.id,
2183
+ type: "table-slice",
2184
+ startRow: sliceStart,
2185
+ endRow,
2186
+ subtotal: subtotalH > 0,
2187
+ ...!firstSlice && repeatHeader ? { repeatHeader: true } : {},
2188
+ renderTop: sliceTop
2189
+ });
2190
+ occupy(tb, sliceUsed);
2191
+ sliceUsed = 0;
2192
+ };
2110
2193
  for (const g of groups) {
2111
2194
  const gh = bodyHeights.slice(g.start, g.end).reduce((s, h) => s + h, 0);
2112
- if (gh + subtotalH <= remaining) {
2113
- remaining -= gh;
2195
+ if (gh + subtotalH <= avail()) {
2196
+ sliceUsed += gh;
2114
2197
  continue;
2115
2198
  }
2116
2199
  if (g.start > sliceStart) {
2117
- currentPage.push({
2118
- elementId: el.id,
2119
- type: "table-slice",
2120
- startRow: sliceStart,
2121
- endRow: g.start,
2122
- subtotal: subtotalH > 0,
2123
- ...!firstSlice && repeatCount > 0 ? { repeatHeader: true } : {},
2124
- renderTop: sliceTop
2125
- });
2200
+ pushSlice(g.start, repeatCount > 0);
2126
2201
  firstSlice = false;
2127
2202
  sliceStart = g.start;
2128
2203
  }
2129
2204
  finishPage();
2130
- sliceTop = sectionTop(el);
2131
- if (!firstSlice) remaining -= repeatH;
2132
- remaining -= gh;
2205
+ sliceTop = sectionTop(el, tb);
2206
+ sliceUsed = 0;
2207
+ if (!firstSlice) sliceUsed += repeatH;
2208
+ sliceUsed += gh;
2133
2209
  }
2134
2210
  if (sliceStart < rowCount) {
2135
- currentPage.push({
2136
- elementId: el.id,
2137
- type: "table-slice",
2138
- startRow: sliceStart,
2139
- endRow: rowCount,
2140
- subtotal: subtotalH > 0,
2141
- ...!firstSlice && repeatCount > 0 ? { repeatHeader: true } : {},
2142
- renderTop: sliceTop
2143
- });
2211
+ pushSlice(rowCount, repeatCount > 0);
2144
2212
  }
2145
2213
  if (summaryH > 0) {
2146
- if (summaryH <= remaining) {
2214
+ if (summaryH <= avail()) {
2147
2215
  const lastIdx = currentPage.length - 1;
2148
2216
  const last = currentPage[lastIdx];
2149
2217
  if (last && last.type === "table-slice" && last.elementId === el.id) {
2150
2218
  currentPage[lastIdx] = { ...last, summary: true };
2151
- remaining -= summaryH;
2219
+ occupy(tb, summaryH);
2152
2220
  } else {
2153
2221
  finishPage();
2154
2222
  currentPage.push({ elementId: el.id, type: "table-slice", startRow: rowCount, endRow: rowCount, summary: true, renderTop: 0 });
2155
- remaining -= summaryH;
2223
+ occupy(tb, summaryH);
2156
2224
  }
2157
2225
  } else {
2158
2226
  finishPage();
2159
2227
  currentPage.push({ elementId: el.id, type: "table-slice", startRow: rowCount, endRow: rowCount, summary: true, renderTop: 0 });
2160
- remaining -= summaryH;
2228
+ occupy(tb, summaryH);
2161
2229
  }
2162
2230
  }
2163
- applyFollowGroup(el, measured);
2231
+ applyFollowGroup(el, measured, tb);
2164
2232
  return idx + 1;
2165
2233
  }
2166
- function applyFollowGroup(el, measured) {
2234
+ function applyFollowGroup(el, measured, band) {
2167
2235
  const members = followMap.get(el.id) ?? [];
2168
2236
  if (members.length === 0) return;
2169
2237
  const followH = followGroupHeight(el, members, template, measured);
2170
2238
  const opts = el.options ?? {};
2171
- if (followH <= remaining) {
2239
+ if (followH <= remainingIn(band)) {
2172
2240
  const lastIdx = currentPage.length - 1;
2173
2241
  const last = currentPage[lastIdx];
2174
2242
  if (last && last.type === "table-slice" && last.elementId === el.id) {
@@ -2179,7 +2247,7 @@ function paginate(template, measuredElements) {
2179
2247
  // 沿用该切片在本页的定位 top(续片已 stamp 为 0)
2180
2248
  groupTop: last.renderTop ?? opts.top ?? 0
2181
2249
  };
2182
- remaining -= followH;
2250
+ occupy(band, followH);
2183
2251
  return;
2184
2252
  }
2185
2253
  }
@@ -2192,7 +2260,7 @@ function paginate(template, measuredElements) {
2192
2260
  followElementIds: members,
2193
2261
  groupTop: 0
2194
2262
  });
2195
- remaining -= followH;
2263
+ occupy(band, followH);
2196
2264
  }
2197
2265
  }
2198
2266
  function buildRowGroups(renderRows, rowCount) {
@@ -2467,11 +2535,8 @@ function textStyle(opts) {
2467
2535
  function indentStyle(opts) {
2468
2536
  return opts.longTextIndent ? `text-indent:${opts.longTextIndent}em;` : "";
2469
2537
  }
2470
- var OBJECT_POSITION_UNSAFE = /[;"'<>\\(){}\[\]@!#]/;
2471
- function positionStyle(objectPosition) {
2472
- const v = (objectPosition ?? "").trim();
2473
- if (!v || v.length > 64 || OBJECT_POSITION_UNSAFE.test(v)) return "";
2474
- return `object-position:${escapeInlineStyleValue(v)};`;
2538
+ function cropStyle(fit, fitScale, objectPosition) {
2539
+ return Object.entries(cropStyleDeclarations(fit, fitScale, objectPosition)).map(([key, value]) => `${key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)}:${escapeInlineStyleValue(value)};`).join("");
2475
2540
  }
2476
2541
  function elementFitStyle(fit, opts) {
2477
2542
  if (fit === "autoHeight") return "overflow:visible;";
@@ -2511,7 +2576,7 @@ function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
2511
2576
  return renderTableElement(el, isMeasure, measureAttr, ctx);
2512
2577
  case "image":
2513
2578
  return `<div class="print-element" style="${style}"${measureAttr}>
2514
- <img src="${esc(opts.src ?? "")}" style="width:100%;height:100%;object-fit:${opts.fit || "contain"};${positionStyle(opts.objectPosition)}" />
2579
+ <img src="${esc(opts.src ?? "")}" style="width:100%;height:100%;object-fit:${opts.fit || "contain"};${cropStyle(opts.fit, opts.fitScale, opts.objectPosition)}" />
2515
2580
  </div>`;
2516
2581
  case "barcode":
2517
2582
  case "qrcode": {
@@ -2634,7 +2699,10 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOw
2634
2699
  const fit = cell.fit || "contain";
2635
2700
  const maxWidth = cell.maxWidth ? `max-width:${cell.maxWidth}mm;` : "max-width:100%;";
2636
2701
  const maxHeight = cell.maxHeight ? `max-height:${cell.maxHeight}mm;` : "max-height:100%;";
2637
- 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>`;
2702
+ const boxMm = cellImageBoxMm(fit, cellFitCapMm(renderRows, r, cell, defaultPadding));
2703
+ const boxHeight = boxMm === void 0 ? "height:100%" : `height:${boxMm}mm`;
2704
+ const imgBox = boxMm === void 0 ? "" : "width:100%;height:100%;";
2705
+ 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>`;
2638
2706
  } else {
2639
2707
  inner = renderTextCellInner(renderRows, r, colIndex, cell, opts, fitOwner, defaultPadding, ctx);
2640
2708
  }
@@ -3747,11 +3815,17 @@ export {
3747
3815
  floorFontSize,
3748
3816
  cellFitKey,
3749
3817
  parseCellFitKey,
3818
+ cellImageBoxMm,
3750
3819
  cellFitWidthMm,
3751
3820
  cellFitCapMm,
3752
3821
  ELEMENT_BORDER_SIDES,
3753
3822
  acceptsElementBorder,
3754
3823
  resolveElementBorder,
3824
+ CROP_SCALE_MIN,
3825
+ CROP_SCALE_MAX,
3826
+ sanitizeObjectPosition,
3827
+ cropScalePct,
3828
+ cropStyleDeclarations,
3755
3829
  PX_PER_MM,
3756
3830
  MM_PER_PX,
3757
3831
  WATERMARK_DEFAULTS,
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.7
2
+ * @worm-vue3-print/core v1.3.9
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
@@ -9,7 +9,7 @@ import {
9
9
  evaluate,
10
10
  parse,
11
11
  tokenize
12
- } from "./chunk-TCI5XQKI.js";
12
+ } from "./chunk-BRGDUOUB.js";
13
13
 
14
14
  // src/index.ts
15
15
  var TemplateEngine = class {
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.7
2
+ * @worm-vue3-print/core v1.3.9
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,5 +1,5 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.7
2
+ * @worm-vue3-print/core v1.3.9
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,5 +1,5 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.7
2
+ * @worm-vue3-print/core v1.3.9
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,5 +1,5 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.7
2
+ * @worm-vue3-print/core v1.3.9
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