@worm-vue3-print/core 1.3.8 → 1.3.10

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.8
2
+ * @worm-vue3-print/core v1.3.10
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
@@ -200,9 +200,13 @@ var import_qrcode = __toESM(require("qrcode"), 1);
200
200
  // src/render/barcode-dot.ts
201
201
  var MM_PER_INCH = 25.4;
202
202
  var BARCODE_QUIET_ZONE_MODULES = 10;
203
+ function barcodeQuietZoneModules(value) {
204
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : BARCODE_QUIET_ZONE_MODULES;
205
+ }
203
206
  var BARCODE_BAR_HEIGHT_MODULES = 30;
204
207
  var BARCODE_TEXT_FONT_SIZE_MODULES = 10;
205
208
  var BARCODE_MARGIN_BOTTOM_MODULES = 2;
209
+ var QR_DEFAULT_QUIET_MODULES = 0;
206
210
  var BARCODE_MODULE_WIDTH_MM = 0.25;
207
211
  function barcodeUnitsPerModule(barWidth) {
208
212
  const raw = typeof barWidth === "number" && Number.isFinite(barWidth) ? barWidth : 2;
@@ -211,6 +215,36 @@ function barcodeUnitsPerModule(barWidth) {
211
215
  function barcodePreferredModuleWidthMm(barWidth) {
212
216
  return barcodeUnitsPerModule(barWidth) * BARCODE_MODULE_WIDTH_MM;
213
217
  }
218
+ function barcodeBarHeightModules(input) {
219
+ const { barHeightMm, moduleWidthMm, boxHeightMm, textModules } = input;
220
+ const hasHeight = typeof barHeightMm === "number" && Number.isFinite(barHeightMm) && barHeightMm > 0;
221
+ const hasGranularity = typeof moduleWidthMm === "number" && Number.isFinite(moduleWidthMm) && moduleWidthMm > 0;
222
+ if (!hasHeight || !hasGranularity) return BARCODE_BAR_HEIGHT_MODULES;
223
+ const wanted = Math.max(1, Math.round(barHeightMm / moduleWidthMm));
224
+ const hasBox = typeof boxHeightMm === "number" && Number.isFinite(boxHeightMm) && boxHeightMm > 0;
225
+ if (!hasBox) return wanted;
226
+ const text = typeof textModules === "number" && Number.isFinite(textModules) && textModules > 0 ? textModules : 0;
227
+ const cap = Math.floor((boxHeightMm - text * moduleWidthMm) / moduleWidthMm);
228
+ return Math.max(1, Math.min(wanted, cap));
229
+ }
230
+ function dotAlignedModuleWidth(preferredMm, maxModuleMm, dotsPerMm) {
231
+ const preferredDots = Math.max(1, Math.round(preferredMm * dotsPerMm));
232
+ const maxDots = Math.floor(maxModuleMm * dotsPerMm);
233
+ if (maxDots < 1) return null;
234
+ const dotsPerModule = Math.min(preferredDots, maxDots);
235
+ return { dotsPerModule, preferredDots, moduleWidthMm: dotsPerModule / dotsPerMm };
236
+ }
237
+ function barcodeWidthBoundModuleWidthMm(input) {
238
+ const preferredMm = barcodePreferredModuleWidthMm(input.barWidth);
239
+ const unitWidth = Math.max(1, Math.ceil(input.unitWidth));
240
+ const maxModuleMm = input.boxWidthMm > 0 ? input.boxWidthMm / unitWidth : Number.POSITIVE_INFINITY;
241
+ const dpi = input.dpi;
242
+ if (typeof dpi === "number" && Number.isFinite(dpi) && dpi > 0) {
243
+ const aligned = dotAlignedModuleWidth(preferredMm, maxModuleMm, dpi / MM_PER_INCH);
244
+ if (aligned) return aligned.moduleWidthMm;
245
+ }
246
+ return Math.min(preferredMm, maxModuleMm);
247
+ }
214
248
  function barcodeAvailableBoxMm(boxMm, maxMm) {
215
249
  const box = typeof boxMm === "number" && boxMm > 0 ? boxMm : Number.POSITIVE_INFINITY;
216
250
  const limit = typeof maxMm === "number" && maxMm > 0 ? Math.min(box, maxMm) : box;
@@ -226,18 +260,14 @@ function resolveBarcodeSize(input) {
226
260
  );
227
261
  const dpi = input.dpi;
228
262
  if (typeof dpi === "number" && Number.isFinite(dpi) && dpi > 0) {
229
- const dotsPerMm = dpi / MM_PER_INCH;
230
- const preferredDots = Math.max(1, Math.round(preferredModuleMm * dotsPerMm));
231
- const maxDots = Math.floor(maxModuleMm * dotsPerMm);
232
- if (maxDots >= 1) {
233
- const dotsPerModule = Math.min(preferredDots, maxDots);
234
- const moduleWidthMm2 = dotsPerModule / dotsPerMm;
263
+ const aligned = dotAlignedModuleWidth(preferredModuleMm, maxModuleMm, dpi / MM_PER_INCH);
264
+ if (aligned) {
235
265
  return {
236
- moduleWidthMm: moduleWidthMm2,
237
- widthMm: unitWidth * moduleWidthMm2,
238
- heightMm: unitHeight * moduleWidthMm2,
239
- dotsPerModule,
240
- scaledDown: dotsPerModule < preferredDots
266
+ moduleWidthMm: aligned.moduleWidthMm,
267
+ widthMm: unitWidth * aligned.moduleWidthMm,
268
+ heightMm: unitHeight * aligned.moduleWidthMm,
269
+ dotsPerModule: aligned.dotsPerModule,
270
+ scaledDown: aligned.dotsPerModule < aligned.preferredDots
241
271
  };
242
272
  }
243
273
  }
@@ -250,6 +280,39 @@ function resolveBarcodeSize(input) {
250
280
  scaledDown: moduleWidthMm < preferredModuleMm
251
281
  };
252
282
  }
283
+ function layoutBarcodeSize(input) {
284
+ const upm = barcodeUnitsPerModule(input.barWidth);
285
+ const settle = (units) => resolveBarcodeSize({
286
+ unitWidth: units.widthUnits / upm,
287
+ unitHeight: units.heightUnits / upm,
288
+ boxWidthMm: input.boxWidthMm,
289
+ boxHeightMm: input.boxHeightMm,
290
+ dpi: input.dpi,
291
+ barWidth: input.barWidth
292
+ });
293
+ const first = input.measure(BARCODE_BAR_HEIGHT_MODULES);
294
+ if (!first) return { barHeightModules: BARCODE_BAR_HEIGHT_MODULES, size: null };
295
+ const hasHeight = typeof input.barHeightMm === "number" && Number.isFinite(input.barHeightMm) && input.barHeightMm > 0;
296
+ if (!hasHeight) return { barHeightModules: BARCODE_BAR_HEIGHT_MODULES, size: settle(first) };
297
+ const textModules = Math.max(0, first.heightUnits / upm - BARCODE_BAR_HEIGHT_MODULES);
298
+ const barHeightModules = barcodeBarHeightModules({
299
+ barHeightMm: input.barHeightMm,
300
+ moduleWidthMm: barcodeWidthBoundModuleWidthMm({
301
+ unitWidth: first.widthUnits / upm,
302
+ boxWidthMm: input.boxWidthMm,
303
+ dpi: input.dpi,
304
+ barWidth: input.barWidth
305
+ }),
306
+ boxHeightMm: input.boxHeightMm,
307
+ textModules
308
+ });
309
+ if (barHeightModules === BARCODE_BAR_HEIGHT_MODULES) {
310
+ return { barHeightModules, size: settle(first) };
311
+ }
312
+ const second = input.measure(barHeightModules);
313
+ if (!second) return { barHeightModules: BARCODE_BAR_HEIGHT_MODULES, size: null };
314
+ return { barHeightModules, size: settle(second) };
315
+ }
253
316
 
254
317
  // src/browser/browser-code-renderer.ts
255
318
  var SVG_NS = "http://www.w3.org/2000/svg";
@@ -263,36 +326,39 @@ function readViewBox(svg) {
263
326
  function renderBarcodeSvg(value, opts) {
264
327
  const svg = document.createElementNS(SVG_NS, "svg");
265
328
  const unitPerModule = barcodeUnitsPerModule(opts.barWidth);
266
- (0, import_jsbarcode.default)(svg, value, {
267
- format: opts.barcodeType || "CODE128",
268
- width: unitPerModule,
269
- height: BARCODE_BAR_HEIGHT_MODULES * unitPerModule,
270
- displayValue: opts.showText !== false,
271
- fontSize: (opts.fontSize ?? BARCODE_TEXT_FONT_SIZE_MODULES) * unitPerModule,
272
- // 静区只留左右:上下留白会白白吃掉元素高度(jsbarcode 的 margin 是四边通配)
273
- margin: 0,
274
- marginLeft: BARCODE_QUIET_ZONE_MODULES * unitPerModule,
275
- marginRight: BARCODE_QUIET_ZONE_MODULES * unitPerModule,
276
- marginTop: 0,
277
- marginBottom: BARCODE_MARGIN_BOTTOM_MODULES * unitPerModule
278
- });
279
- const viewBox = readViewBox(svg);
280
- if (viewBox) {
281
- const size = resolveBarcodeSize({
282
- unitWidth: viewBox.width / unitPerModule,
283
- unitHeight: viewBox.height / unitPerModule,
284
- boxWidthMm: opts.targetWidthMm ?? 0,
285
- boxHeightMm: opts.targetHeightMm ?? 0,
286
- dpi: opts.printerDpi,
287
- barWidth: opts.barWidth
329
+ const quietModules = barcodeQuietZoneModules(opts.barQuietZone);
330
+ const measure = (barHeightModules) => {
331
+ (0, import_jsbarcode.default)(svg, value, {
332
+ format: opts.barcodeType || "CODE128",
333
+ width: unitPerModule,
334
+ height: barHeightModules * unitPerModule,
335
+ displayValue: opts.showText !== false,
336
+ fontSize: (opts.fontSize ?? BARCODE_TEXT_FONT_SIZE_MODULES) * unitPerModule,
337
+ // 静区只留左右:上下留白会白白吃掉元素高度(jsbarcode 的 margin 是四边通配)
338
+ margin: 0,
339
+ marginLeft: quietModules * unitPerModule,
340
+ marginRight: quietModules * unitPerModule,
341
+ marginTop: 0,
342
+ marginBottom: BARCODE_MARGIN_BOTTOM_MODULES * unitPerModule
288
343
  });
289
- svg.setAttribute("width", `${size.widthMm}mm`);
290
- svg.setAttribute("height", `${size.heightMm}mm`);
344
+ const viewBox = readViewBox(svg);
345
+ return viewBox ? { widthUnits: viewBox.width, heightUnits: viewBox.height } : null;
346
+ };
347
+ const layout = layoutBarcodeSize({
348
+ barHeightMm: opts.barHeightMm,
349
+ boxWidthMm: opts.targetWidthMm ?? 0,
350
+ boxHeightMm: opts.targetHeightMm ?? 0,
351
+ dpi: opts.printerDpi,
352
+ barWidth: opts.barWidth,
353
+ measure
354
+ });
355
+ if (layout.size) {
356
+ svg.setAttribute("width", `${layout.size.widthMm}mm`);
357
+ svg.setAttribute("height", `${layout.size.heightMm}mm`);
291
358
  }
292
359
  svg.setAttribute("shape-rendering", "crispEdges");
293
360
  return svg.outerHTML;
294
361
  }
295
- var QR_DEFAULT_QUIET_MODULES = 0;
296
362
  function qrQuietModules(value) {
297
363
  return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : QR_DEFAULT_QUIET_MODULES;
298
364
  }
@@ -343,7 +409,9 @@ function normalizeFit(value) {
343
409
  }
344
410
  var ELEMENT_DEFAULT_FIT = {
345
411
  text: "clip",
346
- longText: "autoHeight"
412
+ longText: "autoHeight",
413
+ // 流式列表一框一页:超出元素框高的单元按截断处理,不自动撑破纸面(specs 第八节第 8 项)
414
+ flowList: "clip"
347
415
  };
348
416
  function resolveElementTextFit(type, opts) {
349
417
  return normalizeFit(opts?.textFit) ?? ELEMENT_DEFAULT_FIT[type] ?? "clip";
@@ -1503,6 +1571,123 @@ function evaluateTemplate(text, ctx) {
1503
1571
  }
1504
1572
  }
1505
1573
 
1574
+ // src/render/flow-list.ts
1575
+ var FLOW_MIN_COLUMNS = 1;
1576
+ var FLOW_MAX_COLUMNS = 12;
1577
+ var FLOW_MIN_ROW_HEIGHT_MM = 3;
1578
+ var FLOW_MAX_ROW_HEIGHT_MM = 100;
1579
+ var FLOW_MIN_GAP_X_MM = 0;
1580
+ var FLOW_MAX_GAP_X_MM = 50;
1581
+ var PT_TO_MM = 25.4 / 72;
1582
+ function clampInt(value, min2, max2, fallback) {
1583
+ if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
1584
+ return Math.min(max2, Math.max(min2, Math.round(value)));
1585
+ }
1586
+ function resolveFlowColumns(value) {
1587
+ return clampInt(value, FLOW_MIN_COLUMNS, FLOW_MAX_COLUMNS, 4);
1588
+ }
1589
+ function resolveFlowGapX(value) {
1590
+ if (typeof value !== "number" || !Number.isFinite(value)) return 4;
1591
+ return Math.min(FLOW_MAX_GAP_X_MM, Math.max(FLOW_MIN_GAP_X_MM, value));
1592
+ }
1593
+ function resolveFlowRowHeight(value) {
1594
+ if (typeof value !== "number" || !Number.isFinite(value)) return 8;
1595
+ return Math.min(FLOW_MAX_ROW_HEIGHT_MM, Math.max(FLOW_MIN_ROW_HEIGHT_MM, value));
1596
+ }
1597
+ function resolveFlowMaxSpan(value, columns) {
1598
+ const cols = resolveFlowColumns(columns);
1599
+ const fallback = Math.min(2, cols);
1600
+ if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
1601
+ return Math.min(cols, Math.max(1, Math.round(value)));
1602
+ }
1603
+ function flowColumnWidthMm(width, columns, gapX) {
1604
+ const cols = resolveFlowColumns(columns);
1605
+ const gap = resolveFlowGapX(gapX);
1606
+ const avail = width - gap * (cols - 1);
1607
+ if (avail <= 0) return 0;
1608
+ return avail / cols;
1609
+ }
1610
+ function flowSpanWidthMm(colUnitMm, gapX, span) {
1611
+ const k = Math.max(1, Math.floor(span));
1612
+ return colUnitMm * k + gapX * (k - 1);
1613
+ }
1614
+ function estimateTextWidthMm(text, fontSizePt) {
1615
+ if (!text) return 0;
1616
+ const em = typeof fontSizePt === "number" && Number.isFinite(fontSizePt) && fontSizePt > 0 ? fontSizePt * PT_TO_MM : 12 * PT_TO_MM;
1617
+ let units = 0;
1618
+ for (const ch of text) {
1619
+ if (ch === " ") {
1620
+ units += 0.28;
1621
+ continue;
1622
+ }
1623
+ const c = ch.codePointAt(0) ?? 0;
1624
+ if (c >= 12288 && c <= 12351 || c >= 13312 && c <= 19903 || c >= 19968 && c <= 40959 || c >= 63744 && c <= 64255 || c >= 65280 && c <= 65376 || c >= 65504 && c <= 65510 || c >= 131072) units += 1;
1625
+ else if (/^[0-9A-Z]$/.test(ch)) units += 0.6;
1626
+ else if (/^[a-z]$/.test(ch)) units += 0.5;
1627
+ else if (/^[\u0020-\u007f]$/.test(ch)) units += 0.4;
1628
+ else units += 0.75;
1629
+ }
1630
+ return units * em;
1631
+ }
1632
+ function spanForText(text, cfg) {
1633
+ if (!cfg.autoSpan) return 1;
1634
+ const ceiling = resolveFlowMaxSpan(cfg.maxSpan, cfg.columns);
1635
+ if (ceiling <= 1 || cfg.colUnitMm <= 0) return 1;
1636
+ const need = estimateTextWidthMm(text, cfg.fontSizePt);
1637
+ for (let k = 1; k <= ceiling; k++) {
1638
+ if (need <= flowSpanWidthMm(cfg.colUnitMm, cfg.gapX, k)) return k;
1639
+ }
1640
+ return ceiling;
1641
+ }
1642
+ function layoutFlowList(items, cfg) {
1643
+ const columns = resolveFlowColumns(cfg.columns);
1644
+ const gapX = resolveFlowGapX(cfg.gapX);
1645
+ const colUnitMm = flowColumnWidthMm(cfg.width, columns, gapX);
1646
+ const maxSpan = resolveFlowMaxSpan(cfg.maxSpan, columns);
1647
+ const rows = [];
1648
+ let current = [];
1649
+ let used = 0;
1650
+ for (const raw of items) {
1651
+ const text = String(raw ?? "");
1652
+ const span = spanForText(text, { ...cfg, columns, gapX, maxSpan, colUnitMm });
1653
+ if (current.length > 0 && used + span > columns) {
1654
+ rows.push({ cells: current });
1655
+ current = [];
1656
+ used = 0;
1657
+ }
1658
+ current.push({ text, span });
1659
+ used += span;
1660
+ }
1661
+ if (current.length > 0) rows.push({ cells: current });
1662
+ return rows;
1663
+ }
1664
+ function detectFlowListKey(expr, data) {
1665
+ if (typeof expr !== "string" || !expr.includes("{") || !data) return void 0;
1666
+ const roots = /* @__PURE__ */ new Set();
1667
+ for (const token of expr.matchAll(/\{([^{}]*)\}/g)) {
1668
+ for (const piece of (token[1] ?? "").split(/[^A-Za-z0-9_\u4e00-\u9fa5.]/)) {
1669
+ const root = piece.split(".")[0];
1670
+ if (root && !/^\d/.test(root)) roots.add(root);
1671
+ }
1672
+ }
1673
+ for (const root of roots) {
1674
+ if (Array.isArray(data[root])) return root;
1675
+ }
1676
+ return void 0;
1677
+ }
1678
+ var mmNum = (v) => `${Math.round(v * 1e3) / 1e3}mm`;
1679
+ function flowGridStyle(cfg) {
1680
+ const columns = resolveFlowColumns(cfg.columns);
1681
+ const gapX = resolveFlowGapX(cfg.gapX);
1682
+ const rowHeight = resolveFlowRowHeight(cfg.rowHeight);
1683
+ return [
1684
+ `display:grid`,
1685
+ `grid-template-columns:repeat(${columns},minmax(0,1fr))`,
1686
+ `column-gap:${mmNum(gapX)}`,
1687
+ `grid-auto-rows:minmax(${mmNum(rowHeight)},auto)`
1688
+ ].join(";");
1689
+ }
1690
+
1506
1691
  // src/render/data-binder.ts
1507
1692
  function bindData(template, printData, baseUrl, fontBaseUrl) {
1508
1693
  const data = { ...resolveSystemVariables(), ...printData ?? {} };
@@ -1530,7 +1715,8 @@ function bindData(template, printData, baseUrl, fontBaseUrl) {
1530
1715
  }
1531
1716
  function bindElement(el, data, baseUrl) {
1532
1717
  const cloned = { ...el, options: { ...el.options } };
1533
- if (typeof cloned.options.formatter === "string") {
1718
+ const isFlowList = cloned.type === "flowList" || cloned.printElementType?.type === "flowList";
1719
+ if (!isFlowList && typeof cloned.options.formatter === "string") {
1534
1720
  const raw = cloned.options.formatter;
1535
1721
  if (referencesPageNumbers(raw)) {
1536
1722
  cloned.options.rawFormatter = raw;
@@ -1552,8 +1738,23 @@ function bindElement(el, data, baseUrl) {
1552
1738
  if (cloned.type === "table" || cloned.printElementType?.type === "table") {
1553
1739
  bindTableData(cloned, data);
1554
1740
  }
1741
+ if (cloned.type === "flowList" || cloned.printElementType?.type === "flowList") {
1742
+ bindFlowListData(cloned, data);
1743
+ }
1555
1744
  return cloned;
1556
1745
  }
1746
+ function bindFlowListData(el, data) {
1747
+ const template = typeof el.options.formatter === "string" ? el.options.formatter : "";
1748
+ const source = { ...el.options, dataSource: el.options.dataSource ?? detectFlowListKey(template, data) };
1749
+ const { list, key } = resolveListSource(source, data);
1750
+ const itemCtx = (item) => key ? { ...data, ...item, [key]: item } : { ...data, ...item };
1751
+ const items = list.map((item) => {
1752
+ if (!template) return String(item ?? "");
1753
+ return referencesPageNumbers(template) ? template : evaluateTemplate(template, itemCtx(item));
1754
+ });
1755
+ el.options._flowItems = items;
1756
+ el.options._flowRawFormatter = referencesPageNumbers(template) ? template : void 0;
1757
+ }
1557
1758
  function resolveListSource(opts, data) {
1558
1759
  const explicitKey = opts.dataSource ?? opts.fields?.[0]?.dataSource ?? opts.fields?.[0]?.field;
1559
1760
  if (explicitKey && Array.isArray(data[explicitKey])) {
@@ -1629,6 +1830,12 @@ function makeRenderRow(row, resolve, keepRaw = false) {
1629
1830
  barcodeType: cell.barcodeType,
1630
1831
  qrCodeLevel: cell.qrCodeLevel,
1631
1832
  qrQuietZone: cell.qrQuietZone,
1833
+ barQuietZone: cell.barQuietZone,
1834
+ // 条高同样必须进白名单:漏一个,属性台填的 mm 就只在设计器生效、出纸静默按缺省 30 模块
1835
+ barHeight: cell.barHeight,
1836
+ // 条宽与条码文本字号也必须在白名单里:漏一个,属性台填的倍率就只在设计器生效、出纸静默按缺省
1837
+ barWidth: cell.barWidth,
1838
+ barFontSize: cell.barFontSize,
1632
1839
  showBarcodeText: cell.showBarcodeText,
1633
1840
  printerDpi: cell.printerDpi,
1634
1841
  fit: cell.fit,
@@ -1879,7 +2086,8 @@ function elementBlock() {
1879
2086
  .print-table td {
1880
2087
  border: 1px solid #ccc;
1881
2088
  padding: 2mm 3mm;
1882
- word-break: break-all;
2089
+ word-break: normal;
2090
+ overflow-wrap: break-word;
1883
2091
  overflow: hidden;
1884
2092
  }
1885
2093
  .print-table thead th {
@@ -2103,6 +2311,7 @@ var SELF_BORDER_TYPES = /* @__PURE__ */ new Set(["rect", "oval", "hline", "vline
2103
2311
  var VALID_STYLES = /* @__PURE__ */ new Set(["solid", "dashed", "dotted", "double"]);
2104
2312
  function parseBorder(width, style, color) {
2105
2313
  if (typeof width !== "number" || !Number.isFinite(width) || width <= 0) return null;
2314
+ if (style === "none") return null;
2106
2315
  return {
2107
2316
  borderWidth: `${width}pt`,
2108
2317
  borderStyle: typeof style === "string" && VALID_STYLES.has(style) ? style : "solid",
@@ -3047,6 +3256,27 @@ function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
3047
3256
  return `<div class="print-element" style="${style};border:${opts.borderWidth || 1}pt ${opts.borderStyle || "solid"} ${opts.borderColor || "#333"};border-radius:50%;"${measureAttr}></div>`;
3048
3257
  case "longText":
3049
3258
  return `<div class="print-element" style="${style}${textStyle(opts)}${indentStyle(opts)}${elementFitStyle(fit, opts)}"${measureAttr}${fitAttr}>${esc(opts.formatter ?? opts.testData ?? "")}</div>`;
3259
+ case "flowList": {
3260
+ const columns = resolveFlowColumns(opts.flowColumns);
3261
+ const gapX = resolveFlowGapX(opts.flowGapX);
3262
+ const rowHeight = resolveFlowRowHeight(opts.flowRowHeight);
3263
+ const maxSpan = resolveFlowMaxSpan(opts.flowMaxSpan, columns);
3264
+ const boundItems = Array.isArray(opts._flowItems) ? opts._flowItems : void 0;
3265
+ const items = boundItems && boundItems.length > 0 ? boundItems : String(opts.testData ?? "").split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
3266
+ const cfg = {
3267
+ width,
3268
+ columns,
3269
+ gapX,
3270
+ rowHeight,
3271
+ maxSpan,
3272
+ fontSizePt: opts.fontSize ?? 12,
3273
+ autoSpan: opts.flowAutoSpan === true
3274
+ };
3275
+ const cells = layoutFlowList(items, cfg).flatMap((row) => row.cells).map((c) => `<div class="print-flow-cell"${c.span > 1 ? ` style="grid-column:span ${c.span};"` : ""}>${esc(c.text)}</div>`).join("\n ");
3276
+ return `<div class="print-element" style="${style}${textStyle(opts)}overflow:hidden;${flowGridStyle(cfg)};"${measureAttr}${fitAttr}>
3277
+ ${cells}
3278
+ </div>`;
3279
+ }
3050
3280
  case "html":
3051
3281
  return `<div class="print-element" style="${style}"${measureAttr}>${opts.testData ?? opts.title ?? ""}</div>`;
3052
3282
  default:
@@ -3063,9 +3293,13 @@ function codeImgHtml(value, cellType, opts, io) {
3063
3293
  qrCodeLevel: opts.qrCodeLevel != null ? String(opts.qrCodeLevel) : void 0,
3064
3294
  // 0 是合法档(无白边),必须用 typeof 判定而非 or/?? 之类假值判断
3065
3295
  qrQuietZone: typeof opts.qrQuietZone === "number" ? opts.qrQuietZone : void 0,
3296
+ barQuietZone: typeof opts.barQuietZone === "number" ? opts.barQuietZone : void 0,
3297
+ // 条高(mm):元素台与单元格台都写 barHeight,非法/未填交由布局函数回退缺省档
3298
+ barHeightMm: typeof opts.barHeight === "number" ? opts.barHeight : void 0,
3066
3299
  showText: opts.hideTitle !== void 0 ? !opts.hideTitle : opts.showBarcodeText,
3067
3300
  barWidth: typeof opts.barWidth === "number" ? opts.barWidth : void 0,
3068
- fontSize: typeof opts.fontSize === "number" ? opts.fontSize : void 0,
3301
+ // 元素台的条码文本字号是 fontSize;单元格台的文本字号是 barFontSize(单元格的 fontSize 是正文字号,不该拿来决定条码)
3302
+ fontSize: typeof (opts.barFontSize ?? opts.fontSize) === "number" ? opts.barFontSize ?? opts.fontSize : void 0,
3069
3303
  printerDpi: isBarcode ? io.printerDpi : void 0,
3070
3304
  targetWidthMm: isBarcode ? barcodeAvailableBoxMm(io.targetWidthMm, maxWidth) : void 0,
3071
3305
  targetHeightMm: isBarcode ? barcodeAvailableBoxMm(io.targetHeightMm, maxHeight) : void 0
@@ -3096,8 +3330,9 @@ function matrixCellStyle(cell, opts) {
3096
3330
  const fontSize = cell.fittedFontSize ?? cell.fontSize ?? opts.tableDefaultFontSize;
3097
3331
  const color = cell.color ?? opts.tableDefaultColor;
3098
3332
  const padding = cell.padding ?? opts.tableDefaultPadding ?? 1;
3333
+ const fontFamily = cell.fontFamily ?? opts.tableDefaultFontFamily;
3099
3334
  if (fontSize) parts.push(`font-size:${fontSize}pt`);
3100
- if (cell.fontFamily) parts.push(`font-family:${escapeInlineStyleValue(toFontFamilyStack(cell.fontFamily))}`);
3335
+ if (fontFamily) parts.push(`font-family:${escapeInlineStyleValue(toFontFamilyStack(fontFamily))}`);
3101
3336
  if (cell.fontWeight) parts.push(`font-weight:${cell.fontWeight}`);
3102
3337
  if (color) parts.push(`color:${color}`);
3103
3338
  if (cell.backgroundColor) parts.push(`background-color:${cell.backgroundColor}`);
@@ -3105,7 +3340,7 @@ function matrixCellStyle(cell, opts) {
3105
3340
  if (cell.align === "justify") parts.push("text-align-last:justify");
3106
3341
  parts.push(`vertical-align:${cell.valign ?? "middle"}`);
3107
3342
  parts.push(`padding:${padding}mm`);
3108
- parts.push(cell.wordWrap === false ? "white-space:nowrap;overflow:hidden" : "word-break:break-all");
3343
+ parts.push(cell.wordWrap === false ? "white-space:nowrap;overflow:hidden" : "white-space:pre-wrap;word-break:normal;overflow-wrap:break-word");
3109
3344
  const b = cell.borders ?? {};
3110
3345
  for (const side of ["top", "right", "bottom", "left"]) {
3111
3346
  const border = b[side];
@@ -3335,7 +3570,9 @@ function codeSpecKey(value, cellType, opts = {}) {
3335
3570
  opts.showText ?? null,
3336
3571
  opts.barWidth ?? null,
3337
3572
  opts.fontSize ?? null,
3338
- opts.qrQuietZone ?? null
3573
+ opts.qrQuietZone ?? null,
3574
+ opts.barQuietZone ?? null,
3575
+ opts.barHeightMm ?? null
3339
3576
  ]);
3340
3577
  }
3341
3578
  function createMapCodeRenderer(map) {
@@ -1,11 +1,11 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.8
2
+ * @worm-vue3-print/core v1.3.10
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-hJxBl2_n.cjs';
8
- import { P as PrintRuntime, F as FitFontSize } from '../ports-B-Jb2XwT.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-DW3mxuRF.cjs';
8
+ import { P as PrintRuntime, F as FitFontSize } from '../ports-DJ7_blE7.cjs';
9
9
 
10
10
  interface BrowserRenderResult {
11
11
  /** 最终多页 HTML 字符串 */
@@ -1,11 +1,11 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.8
2
+ * @worm-vue3-print/core v1.3.10
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-hJxBl2_n.js';
8
- import { P as PrintRuntime, F as FitFontSize } from '../ports-CJ0SUYFt.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-DW3mxuRF.js';
8
+ import { P as PrintRuntime, F as FitFontSize } from '../ports-Brn5xcLD.js';
9
9
 
10
10
  interface BrowserRenderResult {
11
11
  /** 最终多页 HTML 字符串 */
@@ -1,23 +1,23 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.8
2
+ * @worm-vue3-print/core v1.3.10
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
- BARCODE_BAR_HEIGHT_MODULES,
9
8
  BARCODE_MARGIN_BOTTOM_MODULES,
10
- BARCODE_QUIET_ZONE_MODULES,
11
9
  BARCODE_TEXT_FONT_SIZE_MODULES,
12
10
  EXECUTOR_TARGETS,
11
+ QR_DEFAULT_QUIET_MODULES,
12
+ barcodeQuietZoneModules,
13
13
  barcodeUnitsPerModule,
14
14
  createDomHostRuntime,
15
15
  floorFontSize,
16
+ layoutBarcodeSize,
16
17
  mmToPx,
17
18
  prepareDocument,
18
- resolveBarcodeSize,
19
19
  resolveShrinkMinFontSize
20
- } from "../chunk-TQJGFV2V.js";
20
+ } from "../chunk-7FXIWNYK.js";
21
21
 
22
22
  // src/browser/browser-code-renderer.ts
23
23
  import JsBarcode from "jsbarcode";
@@ -33,36 +33,39 @@ function readViewBox(svg) {
33
33
  function renderBarcodeSvg(value, opts) {
34
34
  const svg = document.createElementNS(SVG_NS, "svg");
35
35
  const unitPerModule = barcodeUnitsPerModule(opts.barWidth);
36
- JsBarcode(svg, value, {
37
- format: opts.barcodeType || "CODE128",
38
- width: unitPerModule,
39
- height: BARCODE_BAR_HEIGHT_MODULES * unitPerModule,
40
- displayValue: opts.showText !== false,
41
- fontSize: (opts.fontSize ?? BARCODE_TEXT_FONT_SIZE_MODULES) * unitPerModule,
42
- // 静区只留左右:上下留白会白白吃掉元素高度(jsbarcode 的 margin 是四边通配)
43
- margin: 0,
44
- marginLeft: BARCODE_QUIET_ZONE_MODULES * unitPerModule,
45
- marginRight: BARCODE_QUIET_ZONE_MODULES * unitPerModule,
46
- marginTop: 0,
47
- marginBottom: BARCODE_MARGIN_BOTTOM_MODULES * unitPerModule
48
- });
49
- const viewBox = readViewBox(svg);
50
- if (viewBox) {
51
- const size = resolveBarcodeSize({
52
- unitWidth: viewBox.width / unitPerModule,
53
- unitHeight: viewBox.height / unitPerModule,
54
- boxWidthMm: opts.targetWidthMm ?? 0,
55
- boxHeightMm: opts.targetHeightMm ?? 0,
56
- dpi: opts.printerDpi,
57
- barWidth: opts.barWidth
36
+ const quietModules = barcodeQuietZoneModules(opts.barQuietZone);
37
+ const measure = (barHeightModules) => {
38
+ JsBarcode(svg, value, {
39
+ format: opts.barcodeType || "CODE128",
40
+ width: unitPerModule,
41
+ height: barHeightModules * unitPerModule,
42
+ displayValue: opts.showText !== false,
43
+ fontSize: (opts.fontSize ?? BARCODE_TEXT_FONT_SIZE_MODULES) * unitPerModule,
44
+ // 静区只留左右:上下留白会白白吃掉元素高度(jsbarcode 的 margin 是四边通配)
45
+ margin: 0,
46
+ marginLeft: quietModules * unitPerModule,
47
+ marginRight: quietModules * unitPerModule,
48
+ marginTop: 0,
49
+ marginBottom: BARCODE_MARGIN_BOTTOM_MODULES * unitPerModule
58
50
  });
59
- svg.setAttribute("width", `${size.widthMm}mm`);
60
- svg.setAttribute("height", `${size.heightMm}mm`);
51
+ const viewBox = readViewBox(svg);
52
+ return viewBox ? { widthUnits: viewBox.width, heightUnits: viewBox.height } : null;
53
+ };
54
+ const layout = layoutBarcodeSize({
55
+ barHeightMm: opts.barHeightMm,
56
+ boxWidthMm: opts.targetWidthMm ?? 0,
57
+ boxHeightMm: opts.targetHeightMm ?? 0,
58
+ dpi: opts.printerDpi,
59
+ barWidth: opts.barWidth,
60
+ measure
61
+ });
62
+ if (layout.size) {
63
+ svg.setAttribute("width", `${layout.size.widthMm}mm`);
64
+ svg.setAttribute("height", `${layout.size.heightMm}mm`);
61
65
  }
62
66
  svg.setAttribute("shape-rendering", "crispEdges");
63
67
  return svg.outerHTML;
64
68
  }
65
- var QR_DEFAULT_QUIET_MODULES = 0;
66
69
  function qrQuietModules(value) {
67
70
  return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : QR_DEFAULT_QUIET_MODULES;
68
71
  }
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * @worm-vue3-print/core v1.3.8
2
+ * @worm-vue3-print/core v1.3.10
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-TQJGFV2V.js";
12
+ } from "./chunk-7FXIWNYK.js";
13
13
 
14
14
  // src/index.ts
15
15
  var TemplateEngine = class {