@worm-vue3-print/core 1.3.6 → 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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.6
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
@@ -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
  }
@@ -1047,9 +1047,12 @@ function makeRenderRow(row, resolve, keepRaw = false) {
1047
1047
  cellType: cell.cellType,
1048
1048
  barcodeType: cell.barcodeType,
1049
1049
  qrCodeLevel: cell.qrCodeLevel,
1050
+ qrQuietZone: cell.qrQuietZone,
1050
1051
  showBarcodeText: cell.showBarcodeText,
1051
1052
  printerDpi: cell.printerDpi,
1052
1053
  fit: cell.fit,
1054
+ objectPosition: cell.objectPosition,
1055
+ fitScale: cell.fitScale,
1053
1056
  maxWidth: cell.maxWidth,
1054
1057
  maxHeight: cell.maxHeight,
1055
1058
  rowspan: cell.rowspan ?? 1,
@@ -1564,6 +1567,9 @@ function parseCellFitKey(key) {
1564
1567
  if (!Number.isFinite(rowIndex) || !Number.isFinite(colIndex)) return void 0;
1565
1568
  return { elementId: key.slice(0, first), kind, rowIndex, colIndex };
1566
1569
  }
1570
+ function cellImageBoxMm(fit, capMm) {
1571
+ return (fit || "contain") === "cover" ? capMm : void 0;
1572
+ }
1567
1573
  function cellFitWidthMm(colWidths, colIndex, cell, defaultPadding = 1) {
1568
1574
  const span = Math.max(cell?.colspan ?? 1, 1);
1569
1575
  let width = 0;
@@ -1618,6 +1624,35 @@ function resolveElementBorder(type, opts) {
1618
1624
  return { all, edges };
1619
1625
  }
1620
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
+
1621
1656
  // src/render/watermark.ts
1622
1657
  var PX_PER_MM = 96 / 25.4;
1623
1658
  var MM_PER_PX = 25.4 / 96;
@@ -1819,6 +1854,17 @@ function followGroupHeight(el, members, template, _measuredElements) {
1819
1854
  function effectiveHeight(el, measured) {
1820
1855
  return Math.max(el.options?.height ?? 0, measured.get(el.id)?.measuredHeight ?? 0);
1821
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
+ }
1822
1868
  function buildPaginationUnits(sorted, measured, groupMap) {
1823
1869
  const unitOf = /* @__PURE__ */ new Map();
1824
1870
  for (const members of groupMap.values()) {
@@ -1826,7 +1872,8 @@ function buildPaginationUnits(sorted, measured, groupMap) {
1826
1872
  const unit = {
1827
1873
  anchorId: members[0].id,
1828
1874
  ids: members.map((m) => m.id),
1829
- forceFirstPage: members.some((m) => paginationOf(m)?.pageable === false)
1875
+ forceFirstPage: members.some((m) => paginationOf(m)?.pageable === false),
1876
+ band: members.map(xRange).reduce(xUnion)
1830
1877
  };
1831
1878
  for (const m of members) unitOf.set(m.id, unit);
1832
1879
  }
@@ -1858,11 +1905,13 @@ function buildPaginationUnits(sorted, measured, groupMap) {
1858
1905
  if (active[i].bottom <= top) active.splice(i, 1);
1859
1906
  }
1860
1907
  const bottom = top + effectiveHeight(el, measured);
1908
+ const x = xRange(el);
1861
1909
  for (const a of active) {
1862
- if (a.bottom > top) union(el.id, a.id);
1910
+ if (a.bottom > top && xIntersects(a.x, x)) union(el.id, a.id);
1863
1911
  }
1864
- active.push({ id: el.id, top, bottom });
1912
+ active.push({ id: el.id, top, bottom, x });
1865
1913
  }
1914
+ const eligibleById = new Map(eligible.map((e) => [e.id, e]));
1866
1915
  const clusters = /* @__PURE__ */ new Map();
1867
1916
  for (const el of eligible) {
1868
1917
  const root = find(el.id);
@@ -1872,7 +1921,12 @@ function buildPaginationUnits(sorted, measured, groupMap) {
1872
1921
  }
1873
1922
  for (const ids of clusters.values()) {
1874
1923
  if (ids.length < 2) continue;
1875
- 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
+ };
1876
1930
  for (const id of ids) unitOf.set(id, unit);
1877
1931
  }
1878
1932
  return unitOf;
@@ -1921,17 +1975,30 @@ function paginate(template, measuredElements) {
1921
1975
  const unitOf = buildPaginationUnits(sorted, measuredElements, groupMap);
1922
1976
  const pages = [];
1923
1977
  let currentPage = [];
1924
- let remaining = contentHeight - SAFETY_MARGIN;
1978
+ let occupied = [];
1925
1979
  let pageBroken = false;
1926
- function sectionTop(el) {
1927
- return pageBroken ? pageCursorTop() : el.options?.top ?? 0;
1928
- }
1929
- function pageCursorTop() {
1930
- return Math.max(0, fullPageHeight() - remaining);
1931
- }
1932
1980
  function fullPageHeight() {
1933
1981
  return contentHeight - SAFETY_MARGIN;
1934
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
+ }
1935
2002
  let overflowOnCurrent = false;
1936
2003
  function pushPage() {
1937
2004
  pages.push(
@@ -1948,7 +2015,7 @@ function paginate(template, measuredElements) {
1948
2015
  }
1949
2016
  pushPage();
1950
2017
  currentPage = [];
1951
- remaining = contentHeight - SAFETY_MARGIN;
2018
+ occupied = [];
1952
2019
  pageBroken = true;
1953
2020
  overflowOnCurrent = false;
1954
2021
  }
@@ -1992,31 +2059,32 @@ function paginate(template, measuredElements) {
1992
2059
  return pages;
1993
2060
  function paginateNonTable(el, measured, sortedList, idx) {
1994
2061
  const elHeight = measured.get(el.id)?.measuredHeight ?? el.options?.height ?? 0;
1995
- if (elHeight <= remaining) {
1996
- const top2 = pageBroken ? pageCursorTop() : el.options?.top ?? 0;
1997
- noteOverflow(top2 + elHeight);
1998
- currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
1999
- 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();
2000
2071
  if (paginationOf(el)?.keepWithNext && idx + 1 < sortedList.length) {
2001
2072
  const nextEl = sortedList[idx + 1];
2002
2073
  const nextHeight = measured.get(nextEl.id)?.measuredHeight ?? nextEl.options?.height ?? 0;
2003
2074
  if (elHeight + nextHeight > fullPageHeight()) {
2004
2075
  return idx + 1;
2005
2076
  }
2006
- if (nextHeight > remaining) {
2077
+ if (nextHeight > remainingIn(x)) {
2007
2078
  currentPage.pop();
2079
+ occupied.pop();
2008
2080
  finishPage();
2009
- currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
2010
- remaining -= elHeight;
2081
+ place();
2011
2082
  }
2012
2083
  }
2013
2084
  return idx + 1;
2014
2085
  }
2015
2086
  finishPage();
2016
- const top = pageBroken ? pageCursorTop() : el.options?.top ?? 0;
2017
- if (top + elHeight > contentHeight) overflowOnCurrent = true;
2018
- currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
2019
- remaining -= elHeight;
2087
+ place();
2020
2088
  return idx + 1;
2021
2089
  }
2022
2090
  function paginateUnit(unit, idx) {
@@ -2029,8 +2097,9 @@ function paginate(template, measuredElements) {
2029
2097
  maxBottom = Math.max(maxBottom, top2 + effectiveHeight(m, measuredElements));
2030
2098
  }
2031
2099
  const unitHeight = Math.max(maxBottom - minTop, 0);
2100
+ const band = unit.band;
2032
2101
  const place = () => {
2033
- const offset = pageBroken ? pageCursorTop() - minTop : 0;
2102
+ const offset = pageBroken ? pageCursorTop(band) - minTop : 0;
2034
2103
  noteOverflow(minTop + offset + unitHeight);
2035
2104
  for (const m of members) {
2036
2105
  currentPage.push({
@@ -2039,9 +2108,9 @@ function paginate(template, measuredElements) {
2039
2108
  renderTop: (m.options?.top ?? 0) + offset
2040
2109
  });
2041
2110
  }
2042
- remaining -= unitHeight;
2111
+ occupy(band, unitHeight);
2043
2112
  };
2044
- if (unitHeight <= remaining) {
2113
+ if (unitHeight <= remainingIn(band)) {
2045
2114
  place();
2046
2115
  return idx + 1;
2047
2116
  }
@@ -2065,7 +2134,8 @@ function paginate(template, measuredElements) {
2065
2134
  const rowCount = Math.min(bodyRows.length, bodyHeights.length);
2066
2135
  const repeatCount = opts._repeatHeaderCount ?? 0;
2067
2136
  const repeatH = measured.get(el.id)?.repeatHeaderHeight ?? rowHeights.slice(0, repeatCount).reduce((s, h) => s + h, 0);
2068
- const freshAvail = contentHeight - SAFETY_MARGIN;
2137
+ const freshAvail = fullPageHeight();
2138
+ const tb = xRange(el);
2069
2139
  if (repeatH > freshAvail) {
2070
2140
  throw new Error(
2071
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`
@@ -2078,16 +2148,16 @@ function paginate(template, measuredElements) {
2078
2148
  startRow: 0,
2079
2149
  endRow: 0,
2080
2150
  summary: summaryH > 0,
2081
- renderTop: sectionTop(el)
2151
+ renderTop: sectionTop(el, tb)
2082
2152
  });
2083
- remaining -= summaryH;
2084
- applyFollowGroup(el, measured);
2153
+ occupy(tb, summaryH);
2154
+ applyFollowGroup(el, measured, tb);
2085
2155
  return idx + 1;
2086
2156
  }
2087
2157
  const totalH = bodyHeights.slice(0, rowCount).reduce((s, h) => s + h, 0);
2088
2158
  const totalNeed = totalH + subtotalH + summaryH;
2089
2159
  if (tablePaginationOf(el)?.enabled === false) {
2090
- if (totalNeed > remaining && currentPage.length > 0) finishPage();
2160
+ if (totalNeed > remainingIn(tb) && currentPage.length > 0) finishPage();
2091
2161
  currentPage.push({
2092
2162
  elementId: el.id,
2093
2163
  type: "table-slice",
@@ -2095,78 +2165,78 @@ function paginate(template, measuredElements) {
2095
2165
  endRow: rowCount,
2096
2166
  subtotal: subtotalH > 0,
2097
2167
  summary: summaryH > 0,
2098
- renderTop: sectionTop(el)
2168
+ renderTop: sectionTop(el, tb)
2099
2169
  });
2100
- remaining -= totalNeed;
2101
- applyFollowGroup(el, measured);
2170
+ occupy(tb, totalNeed);
2171
+ applyFollowGroup(el, measured, tb);
2102
2172
  return idx + 1;
2103
2173
  }
2104
2174
  const groups = buildRowGroups(bodyRows, rowCount);
2105
2175
  let sliceStart = 0;
2106
2176
  let firstSlice = true;
2107
- 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
+ };
2108
2193
  for (const g of groups) {
2109
2194
  const gh = bodyHeights.slice(g.start, g.end).reduce((s, h) => s + h, 0);
2110
- if (gh + subtotalH <= remaining) {
2111
- remaining -= gh;
2195
+ if (gh + subtotalH <= avail()) {
2196
+ sliceUsed += gh;
2112
2197
  continue;
2113
2198
  }
2114
2199
  if (g.start > sliceStart) {
2115
- currentPage.push({
2116
- elementId: el.id,
2117
- type: "table-slice",
2118
- startRow: sliceStart,
2119
- endRow: g.start,
2120
- subtotal: subtotalH > 0,
2121
- ...!firstSlice && repeatCount > 0 ? { repeatHeader: true } : {},
2122
- renderTop: sliceTop
2123
- });
2200
+ pushSlice(g.start, repeatCount > 0);
2124
2201
  firstSlice = false;
2125
2202
  sliceStart = g.start;
2126
2203
  }
2127
2204
  finishPage();
2128
- sliceTop = sectionTop(el);
2129
- if (!firstSlice) remaining -= repeatH;
2130
- remaining -= gh;
2205
+ sliceTop = sectionTop(el, tb);
2206
+ sliceUsed = 0;
2207
+ if (!firstSlice) sliceUsed += repeatH;
2208
+ sliceUsed += gh;
2131
2209
  }
2132
2210
  if (sliceStart < rowCount) {
2133
- currentPage.push({
2134
- elementId: el.id,
2135
- type: "table-slice",
2136
- startRow: sliceStart,
2137
- endRow: rowCount,
2138
- subtotal: subtotalH > 0,
2139
- ...!firstSlice && repeatCount > 0 ? { repeatHeader: true } : {},
2140
- renderTop: sliceTop
2141
- });
2211
+ pushSlice(rowCount, repeatCount > 0);
2142
2212
  }
2143
2213
  if (summaryH > 0) {
2144
- if (summaryH <= remaining) {
2214
+ if (summaryH <= avail()) {
2145
2215
  const lastIdx = currentPage.length - 1;
2146
2216
  const last = currentPage[lastIdx];
2147
2217
  if (last && last.type === "table-slice" && last.elementId === el.id) {
2148
2218
  currentPage[lastIdx] = { ...last, summary: true };
2149
- remaining -= summaryH;
2219
+ occupy(tb, summaryH);
2150
2220
  } else {
2151
2221
  finishPage();
2152
2222
  currentPage.push({ elementId: el.id, type: "table-slice", startRow: rowCount, endRow: rowCount, summary: true, renderTop: 0 });
2153
- remaining -= summaryH;
2223
+ occupy(tb, summaryH);
2154
2224
  }
2155
2225
  } else {
2156
2226
  finishPage();
2157
2227
  currentPage.push({ elementId: el.id, type: "table-slice", startRow: rowCount, endRow: rowCount, summary: true, renderTop: 0 });
2158
- remaining -= summaryH;
2228
+ occupy(tb, summaryH);
2159
2229
  }
2160
2230
  }
2161
- applyFollowGroup(el, measured);
2231
+ applyFollowGroup(el, measured, tb);
2162
2232
  return idx + 1;
2163
2233
  }
2164
- function applyFollowGroup(el, measured) {
2234
+ function applyFollowGroup(el, measured, band) {
2165
2235
  const members = followMap.get(el.id) ?? [];
2166
2236
  if (members.length === 0) return;
2167
2237
  const followH = followGroupHeight(el, members, template, measured);
2168
2238
  const opts = el.options ?? {};
2169
- if (followH <= remaining) {
2239
+ if (followH <= remainingIn(band)) {
2170
2240
  const lastIdx = currentPage.length - 1;
2171
2241
  const last = currentPage[lastIdx];
2172
2242
  if (last && last.type === "table-slice" && last.elementId === el.id) {
@@ -2177,7 +2247,7 @@ function paginate(template, measuredElements) {
2177
2247
  // 沿用该切片在本页的定位 top(续片已 stamp 为 0)
2178
2248
  groupTop: last.renderTop ?? opts.top ?? 0
2179
2249
  };
2180
- remaining -= followH;
2250
+ occupy(band, followH);
2181
2251
  return;
2182
2252
  }
2183
2253
  }
@@ -2190,7 +2260,7 @@ function paginate(template, measuredElements) {
2190
2260
  followElementIds: members,
2191
2261
  groupTop: 0
2192
2262
  });
2193
- remaining -= followH;
2263
+ occupy(band, followH);
2194
2264
  }
2195
2265
  }
2196
2266
  function buildRowGroups(renderRows, rowCount) {
@@ -2451,12 +2521,23 @@ function textStyle(opts) {
2451
2521
  if (opts.lineHeight) parts.push(`line-height:${opts.lineHeight}pt`);
2452
2522
  if (opts.letterSpacing) parts.push(`letter-spacing:${opts.letterSpacing}pt`);
2453
2523
  if (opts.verticalAlign) {
2454
- parts.push(`display:flex;align-items:${V_ALIGN_FLEX[opts.verticalAlign] ?? "flex-start"}`);
2455
- parts.push(`justify-content:${H_ALIGN_FLEX[opts.textAlign ?? "left"] ?? "flex-start"}`);
2524
+ if (opts.textAlign === "justify") {
2525
+ parts.push(`display:flex;flex-direction:column;justify-content:${V_ALIGN_FLEX[opts.verticalAlign] ?? "flex-start"}`);
2526
+ } else {
2527
+ parts.push(`display:flex;align-items:${V_ALIGN_FLEX[opts.verticalAlign] ?? "flex-start"}`);
2528
+ parts.push(`justify-content:${H_ALIGN_FLEX[opts.textAlign ?? "left"] ?? "flex-start"}`);
2529
+ }
2456
2530
  }
2457
2531
  if (opts.textAlign) parts.push(`text-align:${opts.textAlign}`);
2532
+ if (opts.textAlign === "justify") parts.push("text-align-last:justify");
2458
2533
  return parts.length ? parts.join(";") + ";" : "";
2459
2534
  }
2535
+ function indentStyle(opts) {
2536
+ return opts.longTextIndent ? `text-indent:${opts.longTextIndent}em;` : "";
2537
+ }
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("");
2540
+ }
2460
2541
  function elementFitStyle(fit, opts) {
2461
2542
  if (fit === "autoHeight") return "overflow:visible;";
2462
2543
  if (opts.wordWrap === false) return "white-space:nowrap;text-overflow:ellipsis;";
@@ -2495,7 +2576,7 @@ function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
2495
2576
  return renderTableElement(el, isMeasure, measureAttr, ctx);
2496
2577
  case "image":
2497
2578
  return `<div class="print-element" style="${style}"${measureAttr}>
2498
- <img src="${esc(opts.src ?? "")}" style="width:100%;height:100%;object-fit:contain;" />
2579
+ <img src="${esc(opts.src ?? "")}" style="width:100%;height:100%;object-fit:${opts.fit || "contain"};${cropStyle(opts.fit, opts.fitScale, opts.objectPosition)}" />
2499
2580
  </div>`;
2500
2581
  case "barcode":
2501
2582
  case "qrcode": {
@@ -2522,7 +2603,7 @@ function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
2522
2603
  case "oval":
2523
2604
  return `<div class="print-element" style="${style};border:${opts.borderWidth || 1}pt ${opts.borderStyle || "solid"} ${opts.borderColor || "#333"};border-radius:50%;"${measureAttr}></div>`;
2524
2605
  case "longText":
2525
- return `<div class="print-element" style="${style}${textStyle(opts)}${elementFitStyle(fit, opts)}"${measureAttr}${fitAttr}>${esc(opts.formatter ?? opts.testData ?? "")}</div>`;
2606
+ return `<div class="print-element" style="${style}${textStyle(opts)}${indentStyle(opts)}${elementFitStyle(fit, opts)}"${measureAttr}${fitAttr}>${esc(opts.formatter ?? opts.testData ?? "")}</div>`;
2526
2607
  case "html":
2527
2608
  return `<div class="print-element" style="${style}"${measureAttr}>${opts.testData ?? opts.title ?? ""}</div>`;
2528
2609
  default:
@@ -2537,6 +2618,8 @@ function codeImgHtml(value, cellType, opts, io) {
2537
2618
  const svg = codeRenderer.render(value, cellType, {
2538
2619
  barcodeType: opts.barcodeType,
2539
2620
  qrCodeLevel: opts.qrCodeLevel != null ? String(opts.qrCodeLevel) : void 0,
2621
+ // 0 是合法档(无白边),必须用 typeof 判定而非 or/?? 之类假值判断
2622
+ qrQuietZone: typeof opts.qrQuietZone === "number" ? opts.qrQuietZone : void 0,
2540
2623
  showText: opts.hideTitle !== void 0 ? !opts.hideTitle : opts.showBarcodeText,
2541
2624
  barWidth: typeof opts.barWidth === "number" ? opts.barWidth : void 0,
2542
2625
  fontSize: typeof opts.fontSize === "number" ? opts.fontSize : void 0,
@@ -2547,10 +2630,8 @@ function codeImgHtml(value, cellType, opts, io) {
2547
2630
  if (isBarcode && /^<svg[\s>]/i.test(svg)) return inlineCodeSvgHtml(svg);
2548
2631
  const src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
2549
2632
  const styleParts = [];
2550
- styleParts.push("max-width:100%", "max-height:100%", "display:block", "margin:auto");
2551
- if (fit) {
2552
- styleParts.push(`object-fit:${fit}`);
2553
- }
2633
+ styleParts.push("width:100%", "height:100%", "max-width:100%", "max-height:100%", "display:block", "margin:auto");
2634
+ styleParts.push(`object-fit:${fit || "contain"}`);
2554
2635
  if (maxWidth) {
2555
2636
  styleParts.push(`max-width:${maxWidth}mm`);
2556
2637
  }
@@ -2578,6 +2659,7 @@ function matrixCellStyle(cell, opts) {
2578
2659
  if (color) parts.push(`color:${color}`);
2579
2660
  if (cell.backgroundColor) parts.push(`background-color:${cell.backgroundColor}`);
2580
2661
  parts.push(`text-align:${cell.align ?? "left"}`);
2662
+ if (cell.align === "justify") parts.push("text-align-last:justify");
2581
2663
  parts.push(`vertical-align:${cell.valign ?? "middle"}`);
2582
2664
  parts.push(`padding:${padding}mm`);
2583
2665
  parts.push(cell.wordWrap === false ? "white-space:nowrap;overflow:hidden" : "word-break:break-all");
@@ -2617,7 +2699,10 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOw
2617
2699
  const fit = cell.fit || "contain";
2618
2700
  const maxWidth = cell.maxWidth ? `max-width:${cell.maxWidth}mm;` : "max-width:100%;";
2619
2701
  const maxHeight = cell.maxHeight ? `max-height:${cell.maxHeight}mm;` : "max-height:100%;";
2620
- 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>`;
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>`;
2621
2706
  } else {
2622
2707
  inner = renderTextCellInner(renderRows, r, colIndex, cell, opts, fitOwner, defaultPadding, ctx);
2623
2708
  }
@@ -2960,7 +3045,8 @@ function codeSpecKey(value, cellType, opts = {}) {
2960
3045
  opts.qrCodeLevel ?? null,
2961
3046
  opts.showText ?? null,
2962
3047
  opts.barWidth ?? null,
2963
- opts.fontSize ?? null
3048
+ opts.fontSize ?? null,
3049
+ opts.qrQuietZone ?? null
2964
3050
  ]);
2965
3051
  }
2966
3052
  function createMapCodeRenderer(map) {
@@ -3729,11 +3815,17 @@ export {
3729
3815
  floorFontSize,
3730
3816
  cellFitKey,
3731
3817
  parseCellFitKey,
3818
+ cellImageBoxMm,
3732
3819
  cellFitWidthMm,
3733
3820
  cellFitCapMm,
3734
3821
  ELEMENT_BORDER_SIDES,
3735
3822
  acceptsElementBorder,
3736
3823
  resolveElementBorder,
3824
+ CROP_SCALE_MIN,
3825
+ CROP_SCALE_MAX,
3826
+ sanitizeObjectPosition,
3827
+ cropScalePct,
3828
+ cropStyleDeclarations,
3737
3829
  PX_PER_MM,
3738
3830
  MM_PER_PX,
3739
3831
  WATERMARK_DEFAULTS,
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.6
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
@@ -9,7 +9,7 @@ import {
9
9
  evaluate,
10
10
  parse,
11
11
  tokenize
12
- } from "./chunk-JQUJ7AF3.js";
12
+ } from "./chunk-TQJGFV2V.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.6
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,5 +1,5 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.6
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,5 +1,5 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.6
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,5 +1,5 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.6
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