@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
@@ -374,6 +374,9 @@ function parseCellFitKey(key) {
374
374
  if (!Number.isFinite(rowIndex) || !Number.isFinite(colIndex)) return void 0;
375
375
  return { elementId: key.slice(0, first), kind, rowIndex, colIndex };
376
376
  }
377
+ function cellImageBoxMm(fit, capMm) {
378
+ return (fit || "contain") === "cover" ? capMm : void 0;
379
+ }
377
380
  function cellFitWidthMm(colWidths, colIndex, cell, defaultPadding = 1) {
378
381
  const span = Math.max(cell?.colspan ?? 1, 1);
379
382
  let width = 0;
@@ -1303,55 +1306,55 @@ function formatDate(value, fmt) {
1303
1306
  const pad3 = (n) => String(n).padStart(2, "0");
1304
1307
  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()));
1305
1308
  }
1309
+ var CN_DIGITS = ["\u96F6", "\u58F9", "\u8D30", "\u53C1", "\u8086", "\u4F0D", "\u9646", "\u67D2", "\u634C", "\u7396"];
1310
+ var CN_UNITS = ["", "\u62FE", "\u4F70", "\u4EDF"];
1311
+ var CN_GROUP_UNITS = ["", "\u4E07", "\u4EBF", "\u4E07\u4EBF", "\u4EBF\u4EBF"];
1312
+ function fourDigitToCapital(n) {
1313
+ let out = "";
1314
+ let zeroPending = false;
1315
+ for (let pos = 3; pos >= 0; pos--) {
1316
+ const d = Math.floor(n / 10 ** pos) % 10;
1317
+ if (d === 0) {
1318
+ if (out) zeroPending = true;
1319
+ continue;
1320
+ }
1321
+ if (zeroPending) {
1322
+ out += "\u96F6";
1323
+ zeroPending = false;
1324
+ }
1325
+ out += CN_DIGITS[d] + CN_UNITS[pos];
1326
+ }
1327
+ return out;
1328
+ }
1306
1329
  function toUpperCaseAmount(value) {
1307
1330
  const num = Number(value);
1308
1331
  if (isNaN(num)) return String(value);
1309
1332
  if (num === 0) return "\u96F6\u5143\u6574";
1310
- const digits = ["\u96F6", "\u58F9", "\u8D30", "\u53C1", "\u8086", "\u4F0D", "\u9646", "\u67D2", "\u634C", "\u7396"];
1311
- const units = ["", "\u62FE", "\u4F70", "\u4EDF"];
1312
- const bigUnits = ["", "\u4E07", "\u4EBF"];
1313
1333
  const sign = num < 0 ? "\u8D1F" : "";
1314
- const abs = Math.abs(num);
1315
- const [intStr, decStr] = abs.toFixed(2).split(".");
1316
- const intNum = parseInt(intStr, 10);
1334
+ const [intStr, decStr] = Math.abs(num).toFixed(2).split(".");
1335
+ const groupCount = Math.ceil(intStr.length / 4);
1317
1336
  let result = "";
1318
- if (intNum > 0) {
1319
- const intChars = intStr.split("").reverse();
1320
- let groupIdx = 0;
1321
- let lastDigit = 0;
1322
- let needZero = false;
1323
- for (let i = 0; i < intChars.length; i++) {
1324
- const d = parseInt(intChars[i], 10);
1325
- const unit = units[groupIdx % 4];
1326
- const bigUnit = bigUnits[Math.floor(groupIdx / 4)];
1327
- if (d === 0) {
1328
- if (lastDigit !== 0) needZero = true;
1329
- } else {
1330
- if (needZero) {
1331
- result = "\u96F6" + result;
1332
- needZero = false;
1333
- }
1334
- result = digits[d] + unit + result;
1335
- lastDigit = d;
1336
- }
1337
- if (groupIdx % 4 === 3 && groupIdx > 0) {
1338
- result = bigUnit + result;
1339
- }
1340
- groupIdx++;
1337
+ let zeroPending = false;
1338
+ for (let groupIdx = groupCount - 1; groupIdx >= 0; groupIdx--) {
1339
+ const end = intStr.length - groupIdx * 4;
1340
+ const group = parseInt(intStr.slice(Math.max(0, end - 4), end), 10);
1341
+ if (group === 0) {
1342
+ if (result) zeroPending = true;
1343
+ continue;
1341
1344
  }
1342
- result += "\u5143";
1345
+ if (result && (zeroPending || group < 1e3)) result += "\u96F6";
1346
+ zeroPending = false;
1347
+ result += fourDigitToCapital(group) + (CN_GROUP_UNITS[groupIdx] ?? "");
1343
1348
  }
1349
+ result = `${result || "\u96F6"}\u5143`;
1344
1350
  const jiao = parseInt(decStr[0], 10);
1345
1351
  const fen = parseInt(decStr[1], 10);
1346
1352
  if (jiao === 0 && fen === 0) {
1347
1353
  result += "\u6574";
1348
1354
  } else {
1349
- if (jiao > 0) {
1350
- result += digits[jiao] + "\u89D2";
1351
- }
1352
- if (fen > 0) {
1353
- result += digits[fen] + "\u5206";
1354
- }
1355
+ if (jiao === 0) result += "\u96F6";
1356
+ else result += CN_DIGITS[jiao] + "\u89D2";
1357
+ if (fen > 0) result += CN_DIGITS[fen] + "\u5206";
1355
1358
  }
1356
1359
  return sign + result;
1357
1360
  }
@@ -1630,6 +1633,7 @@ function makeRenderRow(row, resolve, keepRaw = false) {
1630
1633
  printerDpi: cell.printerDpi,
1631
1634
  fit: cell.fit,
1632
1635
  objectPosition: cell.objectPosition,
1636
+ fitScale: cell.fitScale,
1633
1637
  maxWidth: cell.maxWidth,
1634
1638
  maxHeight: cell.maxHeight,
1635
1639
  rowspan: cell.rowspan ?? 1,
@@ -2122,6 +2126,35 @@ function resolveElementBorder(type, opts) {
2122
2126
  return { all, edges };
2123
2127
  }
2124
2128
 
2129
+ // src/render/crop-style.ts
2130
+ var OBJECT_POSITION_UNSAFE = /[;"'<>\\(){}\[\]@!#]/;
2131
+ var OBJECT_POSITION_MAX_LEN = 64;
2132
+ var CROP_SCALE_MIN = 100;
2133
+ var CROP_SCALE_MAX = 400;
2134
+ function sanitizeObjectPosition(value) {
2135
+ const v = (value ?? "").trim();
2136
+ if (!v || v.length > OBJECT_POSITION_MAX_LEN || OBJECT_POSITION_UNSAFE.test(v)) return "";
2137
+ return v;
2138
+ }
2139
+ function cropScalePct(fit, scale) {
2140
+ if ((fit || "contain") !== "cover") return void 0;
2141
+ if (typeof scale !== "number" || !Number.isFinite(scale)) return void 0;
2142
+ const pct = Math.round(scale);
2143
+ if (pct <= CROP_SCALE_MIN) return void 0;
2144
+ return Math.min(pct, CROP_SCALE_MAX);
2145
+ }
2146
+ function cropStyleDeclarations(fit, scale, objectPosition) {
2147
+ const pos = sanitizeObjectPosition(objectPosition);
2148
+ const out = {};
2149
+ if (pos) out.objectPosition = pos;
2150
+ const pct = cropScalePct(fit, scale);
2151
+ if (pct !== void 0) {
2152
+ out.transform = `scale(${Number((pct / 100).toFixed(2))})`;
2153
+ out.transformOrigin = pos || "50% 50%";
2154
+ }
2155
+ return out;
2156
+ }
2157
+
2125
2158
  // src/render/watermark.ts
2126
2159
  var PX_PER_MM = 96 / 25.4;
2127
2160
  var MM_PER_PX = 25.4 / 96;
@@ -2318,6 +2351,17 @@ function followGroupHeight(el, members, template, _measuredElements) {
2318
2351
  function effectiveHeight(el, measured) {
2319
2352
  return Math.max(el.options?.height ?? 0, measured.get(el.id)?.measuredHeight ?? 0);
2320
2353
  }
2354
+ function xRange(el) {
2355
+ const o = el.options ?? {};
2356
+ const x0 = o.left ?? 0;
2357
+ return [x0, x0 + (o.width ?? 100)];
2358
+ }
2359
+ function xIntersects(a, b) {
2360
+ return a[0] < b[1] && b[0] < a[1];
2361
+ }
2362
+ function xUnion(a, b) {
2363
+ return [Math.min(a[0], b[0]), Math.max(a[1], b[1])];
2364
+ }
2321
2365
  function buildPaginationUnits(sorted, measured, groupMap) {
2322
2366
  const unitOf = /* @__PURE__ */ new Map();
2323
2367
  for (const members of groupMap.values()) {
@@ -2325,7 +2369,8 @@ function buildPaginationUnits(sorted, measured, groupMap) {
2325
2369
  const unit = {
2326
2370
  anchorId: members[0].id,
2327
2371
  ids: members.map((m) => m.id),
2328
- forceFirstPage: members.some((m) => paginationOf(m)?.pageable === false)
2372
+ forceFirstPage: members.some((m) => paginationOf(m)?.pageable === false),
2373
+ band: members.map(xRange).reduce(xUnion)
2329
2374
  };
2330
2375
  for (const m of members) unitOf.set(m.id, unit);
2331
2376
  }
@@ -2357,11 +2402,13 @@ function buildPaginationUnits(sorted, measured, groupMap) {
2357
2402
  if (active[i].bottom <= top) active.splice(i, 1);
2358
2403
  }
2359
2404
  const bottom = top + effectiveHeight(el, measured);
2405
+ const x = xRange(el);
2360
2406
  for (const a of active) {
2361
- if (a.bottom > top) union(el.id, a.id);
2407
+ if (a.bottom > top && xIntersects(a.x, x)) union(el.id, a.id);
2362
2408
  }
2363
- active.push({ id: el.id, top, bottom });
2409
+ active.push({ id: el.id, top, bottom, x });
2364
2410
  }
2411
+ const eligibleById = new Map(eligible.map((e) => [e.id, e]));
2365
2412
  const clusters = /* @__PURE__ */ new Map();
2366
2413
  for (const el of eligible) {
2367
2414
  const root = find(el.id);
@@ -2371,7 +2418,12 @@ function buildPaginationUnits(sorted, measured, groupMap) {
2371
2418
  }
2372
2419
  for (const ids of clusters.values()) {
2373
2420
  if (ids.length < 2) continue;
2374
- const unit = { anchorId: ids[0], ids, forceFirstPage: false };
2421
+ const unit = {
2422
+ anchorId: ids[0],
2423
+ ids,
2424
+ forceFirstPage: false,
2425
+ band: ids.map((id) => xRange(eligibleById.get(id))).reduce(xUnion)
2426
+ };
2375
2427
  for (const id of ids) unitOf.set(id, unit);
2376
2428
  }
2377
2429
  return unitOf;
@@ -2420,17 +2472,30 @@ function paginate(template, measuredElements) {
2420
2472
  const unitOf = buildPaginationUnits(sorted, measuredElements, groupMap);
2421
2473
  const pages = [];
2422
2474
  let currentPage = [];
2423
- let remaining = contentHeight - SAFETY_MARGIN;
2475
+ let occupied = [];
2424
2476
  let pageBroken = false;
2425
- function sectionTop(el) {
2426
- return pageBroken ? pageCursorTop() : el.options?.top ?? 0;
2427
- }
2428
- function pageCursorTop() {
2429
- return Math.max(0, fullPageHeight() - remaining);
2430
- }
2431
2477
  function fullPageHeight() {
2432
2478
  return contentHeight - SAFETY_MARGIN;
2433
2479
  }
2480
+ function depthIn(x) {
2481
+ let used = 0;
2482
+ for (const r of occupied) {
2483
+ if (xIntersects(x, [r.x0, r.x1])) used += r.height;
2484
+ }
2485
+ return Math.max(0, used);
2486
+ }
2487
+ function remainingIn(x) {
2488
+ return fullPageHeight() - depthIn(x);
2489
+ }
2490
+ function occupy(x, height) {
2491
+ occupied.push({ x0: x[0], x1: x[1], height });
2492
+ }
2493
+ function sectionTop(el, x = xRange(el)) {
2494
+ return pageBroken ? pageCursorTop(x) : el.options?.top ?? 0;
2495
+ }
2496
+ function pageCursorTop(x) {
2497
+ return Math.max(0, fullPageHeight() - remainingIn(x));
2498
+ }
2434
2499
  let overflowOnCurrent = false;
2435
2500
  function pushPage() {
2436
2501
  pages.push(
@@ -2447,7 +2512,7 @@ function paginate(template, measuredElements) {
2447
2512
  }
2448
2513
  pushPage();
2449
2514
  currentPage = [];
2450
- remaining = contentHeight - SAFETY_MARGIN;
2515
+ occupied = [];
2451
2516
  pageBroken = true;
2452
2517
  overflowOnCurrent = false;
2453
2518
  }
@@ -2491,31 +2556,32 @@ function paginate(template, measuredElements) {
2491
2556
  return pages;
2492
2557
  function paginateNonTable(el, measured, sortedList, idx) {
2493
2558
  const elHeight = measured.get(el.id)?.measuredHeight ?? el.options?.height ?? 0;
2494
- if (elHeight <= remaining) {
2495
- const top2 = pageBroken ? pageCursorTop() : el.options?.top ?? 0;
2496
- noteOverflow(top2 + elHeight);
2497
- currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
2498
- remaining -= elHeight;
2559
+ const x = xRange(el);
2560
+ const place = () => {
2561
+ const top = sectionTop(el, x);
2562
+ noteOverflow(top + elHeight);
2563
+ currentPage.push({ elementId: el.id, type: "element", renderTop: top });
2564
+ occupy(x, elHeight);
2565
+ };
2566
+ if (elHeight <= remainingIn(x)) {
2567
+ place();
2499
2568
  if (paginationOf(el)?.keepWithNext && idx + 1 < sortedList.length) {
2500
2569
  const nextEl = sortedList[idx + 1];
2501
2570
  const nextHeight = measured.get(nextEl.id)?.measuredHeight ?? nextEl.options?.height ?? 0;
2502
2571
  if (elHeight + nextHeight > fullPageHeight()) {
2503
2572
  return idx + 1;
2504
2573
  }
2505
- if (nextHeight > remaining) {
2574
+ if (nextHeight > remainingIn(x)) {
2506
2575
  currentPage.pop();
2576
+ occupied.pop();
2507
2577
  finishPage();
2508
- currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
2509
- remaining -= elHeight;
2578
+ place();
2510
2579
  }
2511
2580
  }
2512
2581
  return idx + 1;
2513
2582
  }
2514
2583
  finishPage();
2515
- const top = pageBroken ? pageCursorTop() : el.options?.top ?? 0;
2516
- if (top + elHeight > contentHeight) overflowOnCurrent = true;
2517
- currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
2518
- remaining -= elHeight;
2584
+ place();
2519
2585
  return idx + 1;
2520
2586
  }
2521
2587
  function paginateUnit(unit, idx) {
@@ -2528,8 +2594,9 @@ function paginate(template, measuredElements) {
2528
2594
  maxBottom = Math.max(maxBottom, top2 + effectiveHeight(m, measuredElements));
2529
2595
  }
2530
2596
  const unitHeight = Math.max(maxBottom - minTop, 0);
2597
+ const band = unit.band;
2531
2598
  const place = () => {
2532
- const offset = pageBroken ? pageCursorTop() - minTop : 0;
2599
+ const offset = pageBroken ? pageCursorTop(band) - minTop : 0;
2533
2600
  noteOverflow(minTop + offset + unitHeight);
2534
2601
  for (const m of members) {
2535
2602
  currentPage.push({
@@ -2538,9 +2605,9 @@ function paginate(template, measuredElements) {
2538
2605
  renderTop: (m.options?.top ?? 0) + offset
2539
2606
  });
2540
2607
  }
2541
- remaining -= unitHeight;
2608
+ occupy(band, unitHeight);
2542
2609
  };
2543
- if (unitHeight <= remaining) {
2610
+ if (unitHeight <= remainingIn(band)) {
2544
2611
  place();
2545
2612
  return idx + 1;
2546
2613
  }
@@ -2564,7 +2631,8 @@ function paginate(template, measuredElements) {
2564
2631
  const rowCount = Math.min(bodyRows.length, bodyHeights.length);
2565
2632
  const repeatCount = opts._repeatHeaderCount ?? 0;
2566
2633
  const repeatH = measured.get(el.id)?.repeatHeaderHeight ?? rowHeights.slice(0, repeatCount).reduce((s, h) => s + h, 0);
2567
- const freshAvail = contentHeight - SAFETY_MARGIN;
2634
+ const freshAvail = fullPageHeight();
2635
+ const tb = xRange(el);
2568
2636
  if (repeatH > freshAvail) {
2569
2637
  throw new Error(
2570
2638
  `\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`
@@ -2577,16 +2645,16 @@ function paginate(template, measuredElements) {
2577
2645
  startRow: 0,
2578
2646
  endRow: 0,
2579
2647
  summary: summaryH > 0,
2580
- renderTop: sectionTop(el)
2648
+ renderTop: sectionTop(el, tb)
2581
2649
  });
2582
- remaining -= summaryH;
2583
- applyFollowGroup(el, measured);
2650
+ occupy(tb, summaryH);
2651
+ applyFollowGroup(el, measured, tb);
2584
2652
  return idx + 1;
2585
2653
  }
2586
2654
  const totalH = bodyHeights.slice(0, rowCount).reduce((s, h) => s + h, 0);
2587
2655
  const totalNeed = totalH + subtotalH + summaryH;
2588
2656
  if (tablePaginationOf(el)?.enabled === false) {
2589
- if (totalNeed > remaining && currentPage.length > 0) finishPage();
2657
+ if (totalNeed > remainingIn(tb) && currentPage.length > 0) finishPage();
2590
2658
  currentPage.push({
2591
2659
  elementId: el.id,
2592
2660
  type: "table-slice",
@@ -2594,78 +2662,78 @@ function paginate(template, measuredElements) {
2594
2662
  endRow: rowCount,
2595
2663
  subtotal: subtotalH > 0,
2596
2664
  summary: summaryH > 0,
2597
- renderTop: sectionTop(el)
2665
+ renderTop: sectionTop(el, tb)
2598
2666
  });
2599
- remaining -= totalNeed;
2600
- applyFollowGroup(el, measured);
2667
+ occupy(tb, totalNeed);
2668
+ applyFollowGroup(el, measured, tb);
2601
2669
  return idx + 1;
2602
2670
  }
2603
2671
  const groups = buildRowGroups(bodyRows, rowCount);
2604
2672
  let sliceStart = 0;
2605
2673
  let firstSlice = true;
2606
- let sliceTop = sectionTop(el);
2674
+ let sliceTop = sectionTop(el, tb);
2675
+ let sliceUsed = 0;
2676
+ const avail = () => remainingIn(tb) - sliceUsed;
2677
+ const pushSlice = (endRow, repeatHeader) => {
2678
+ currentPage.push({
2679
+ elementId: el.id,
2680
+ type: "table-slice",
2681
+ startRow: sliceStart,
2682
+ endRow,
2683
+ subtotal: subtotalH > 0,
2684
+ ...!firstSlice && repeatHeader ? { repeatHeader: true } : {},
2685
+ renderTop: sliceTop
2686
+ });
2687
+ occupy(tb, sliceUsed);
2688
+ sliceUsed = 0;
2689
+ };
2607
2690
  for (const g of groups) {
2608
2691
  const gh = bodyHeights.slice(g.start, g.end).reduce((s, h) => s + h, 0);
2609
- if (gh + subtotalH <= remaining) {
2610
- remaining -= gh;
2692
+ if (gh + subtotalH <= avail()) {
2693
+ sliceUsed += gh;
2611
2694
  continue;
2612
2695
  }
2613
2696
  if (g.start > sliceStart) {
2614
- currentPage.push({
2615
- elementId: el.id,
2616
- type: "table-slice",
2617
- startRow: sliceStart,
2618
- endRow: g.start,
2619
- subtotal: subtotalH > 0,
2620
- ...!firstSlice && repeatCount > 0 ? { repeatHeader: true } : {},
2621
- renderTop: sliceTop
2622
- });
2697
+ pushSlice(g.start, repeatCount > 0);
2623
2698
  firstSlice = false;
2624
2699
  sliceStart = g.start;
2625
2700
  }
2626
2701
  finishPage();
2627
- sliceTop = sectionTop(el);
2628
- if (!firstSlice) remaining -= repeatH;
2629
- remaining -= gh;
2702
+ sliceTop = sectionTop(el, tb);
2703
+ sliceUsed = 0;
2704
+ if (!firstSlice) sliceUsed += repeatH;
2705
+ sliceUsed += gh;
2630
2706
  }
2631
2707
  if (sliceStart < rowCount) {
2632
- currentPage.push({
2633
- elementId: el.id,
2634
- type: "table-slice",
2635
- startRow: sliceStart,
2636
- endRow: rowCount,
2637
- subtotal: subtotalH > 0,
2638
- ...!firstSlice && repeatCount > 0 ? { repeatHeader: true } : {},
2639
- renderTop: sliceTop
2640
- });
2708
+ pushSlice(rowCount, repeatCount > 0);
2641
2709
  }
2642
2710
  if (summaryH > 0) {
2643
- if (summaryH <= remaining) {
2711
+ if (summaryH <= avail()) {
2644
2712
  const lastIdx = currentPage.length - 1;
2645
2713
  const last = currentPage[lastIdx];
2646
2714
  if (last && last.type === "table-slice" && last.elementId === el.id) {
2647
2715
  currentPage[lastIdx] = { ...last, summary: true };
2648
- remaining -= summaryH;
2716
+ occupy(tb, summaryH);
2649
2717
  } else {
2650
2718
  finishPage();
2651
2719
  currentPage.push({ elementId: el.id, type: "table-slice", startRow: rowCount, endRow: rowCount, summary: true, renderTop: 0 });
2652
- remaining -= summaryH;
2720
+ occupy(tb, summaryH);
2653
2721
  }
2654
2722
  } else {
2655
2723
  finishPage();
2656
2724
  currentPage.push({ elementId: el.id, type: "table-slice", startRow: rowCount, endRow: rowCount, summary: true, renderTop: 0 });
2657
- remaining -= summaryH;
2725
+ occupy(tb, summaryH);
2658
2726
  }
2659
2727
  }
2660
- applyFollowGroup(el, measured);
2728
+ applyFollowGroup(el, measured, tb);
2661
2729
  return idx + 1;
2662
2730
  }
2663
- function applyFollowGroup(el, measured) {
2731
+ function applyFollowGroup(el, measured, band) {
2664
2732
  const members = followMap.get(el.id) ?? [];
2665
2733
  if (members.length === 0) return;
2666
2734
  const followH = followGroupHeight(el, members, template, measured);
2667
2735
  const opts = el.options ?? {};
2668
- if (followH <= remaining) {
2736
+ if (followH <= remainingIn(band)) {
2669
2737
  const lastIdx = currentPage.length - 1;
2670
2738
  const last = currentPage[lastIdx];
2671
2739
  if (last && last.type === "table-slice" && last.elementId === el.id) {
@@ -2676,7 +2744,7 @@ function paginate(template, measuredElements) {
2676
2744
  // 沿用该切片在本页的定位 top(续片已 stamp 为 0)
2677
2745
  groupTop: last.renderTop ?? opts.top ?? 0
2678
2746
  };
2679
- remaining -= followH;
2747
+ occupy(band, followH);
2680
2748
  return;
2681
2749
  }
2682
2750
  }
@@ -2689,7 +2757,7 @@ function paginate(template, measuredElements) {
2689
2757
  followElementIds: members,
2690
2758
  groupTop: 0
2691
2759
  });
2692
- remaining -= followH;
2760
+ occupy(band, followH);
2693
2761
  }
2694
2762
  }
2695
2763
  function buildRowGroups(renderRows, rowCount) {
@@ -2910,11 +2978,8 @@ function textStyle(opts) {
2910
2978
  function indentStyle(opts) {
2911
2979
  return opts.longTextIndent ? `text-indent:${opts.longTextIndent}em;` : "";
2912
2980
  }
2913
- var OBJECT_POSITION_UNSAFE = /[;"'<>\\(){}\[\]@!#]/;
2914
- function positionStyle(objectPosition) {
2915
- const v = (objectPosition ?? "").trim();
2916
- if (!v || v.length > 64 || OBJECT_POSITION_UNSAFE.test(v)) return "";
2917
- return `object-position:${escapeInlineStyleValue(v)};`;
2981
+ function cropStyle(fit, fitScale, objectPosition) {
2982
+ return Object.entries(cropStyleDeclarations(fit, fitScale, objectPosition)).map(([key, value]) => `${key.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`)}:${escapeInlineStyleValue(value)};`).join("");
2918
2983
  }
2919
2984
  function elementFitStyle(fit, opts) {
2920
2985
  if (fit === "autoHeight") return "overflow:visible;";
@@ -2954,7 +3019,7 @@ function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
2954
3019
  return renderTableElement(el, isMeasure, measureAttr, ctx);
2955
3020
  case "image":
2956
3021
  return `<div class="print-element" style="${style}"${measureAttr}>
2957
- <img src="${esc(opts.src ?? "")}" style="width:100%;height:100%;object-fit:${opts.fit || "contain"};${positionStyle(opts.objectPosition)}" />
3022
+ <img src="${esc(opts.src ?? "")}" style="width:100%;height:100%;object-fit:${opts.fit || "contain"};${cropStyle(opts.fit, opts.fitScale, opts.objectPosition)}" />
2958
3023
  </div>`;
2959
3024
  case "barcode":
2960
3025
  case "qrcode": {
@@ -3077,7 +3142,10 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOw
3077
3142
  const fit = cell.fit || "contain";
3078
3143
  const maxWidth = cell.maxWidth ? `max-width:${cell.maxWidth}mm;` : "max-width:100%;";
3079
3144
  const maxHeight = cell.maxHeight ? `max-height:${cell.maxHeight}mm;` : "max-height:100%;";
3080
- 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>`;
3145
+ const boxMm = cellImageBoxMm(fit, cellFitCapMm(renderRows, r, cell, defaultPadding));
3146
+ const boxHeight = boxMm === void 0 ? "height:100%" : `height:${boxMm}mm`;
3147
+ const imgBox = boxMm === void 0 ? "" : "width:100%;height:100%;";
3148
+ 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>`;
3081
3149
  } else {
3082
3150
  inner = renderTextCellInner(renderRows, r, colIndex, cell, opts, fitOwner, defaultPadding, ctx);
3083
3151
  }
@@ -1,11 +1,11 @@
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
6
6
  */
7
- import { P as PageLayout, T as TemplateData, M as MultiPageTemplateData, C as CodeRenderer, D as DriverFactory, R as RawMeasurement, a as CodeSpec } from '../driver-Dzr2Aikx.cjs';
8
- import { P as PrintRuntime, F as FitFontSize } from '../ports-Bin4xmo0.cjs';
7
+ import { P as PageLayout, T as TemplateData, M as MultiPageTemplateData, C as CodeRenderer, D as DriverFactory, R as RawMeasurement, a as CodeSpec } from '../driver-hJxBl2_n.cjs';
8
+ import { P as PrintRuntime, F as FitFontSize } from '../ports-B-Jb2XwT.cjs';
9
9
 
10
10
  interface BrowserRenderResult {
11
11
  /** 最终多页 HTML 字符串 */
@@ -1,11 +1,11 @@
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
6
6
  */
7
- import { P as PageLayout, T as TemplateData, M as MultiPageTemplateData, C as CodeRenderer, D as DriverFactory, R as RawMeasurement, a as CodeSpec } from '../driver-Dzr2Aikx.js';
8
- import { P as PrintRuntime, F as FitFontSize } from '../ports-B8MFq_Yo.js';
7
+ import { P as PageLayout, T as TemplateData, M as MultiPageTemplateData, C as CodeRenderer, D as DriverFactory, R as RawMeasurement, a as CodeSpec } from '../driver-hJxBl2_n.js';
8
+ import { P as PrintRuntime, F as FitFontSize } from '../ports-CJ0SUYFt.js';
9
9
 
10
10
  interface BrowserRenderResult {
11
11
  /** 最终多页 HTML 字符串 */
@@ -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
@@ -17,7 +17,7 @@ import {
17
17
  prepareDocument,
18
18
  resolveBarcodeSize,
19
19
  resolveShrinkMinFontSize
20
- } from "../chunk-TCI5XQKI.js";
20
+ } from "../chunk-BRGDUOUB.js";
21
21
 
22
22
  // src/browser/browser-code-renderer.ts
23
23
  import JsBarcode from "jsbarcode";