@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
@@ -922,6 +922,130 @@ function evaluateTemplate(text, ctx) {
922
922
  }
923
923
  }
924
924
 
925
+ // src/render/flow-list.ts
926
+ var FLOW_MIN_COLUMNS = 1;
927
+ var FLOW_MAX_COLUMNS = 12;
928
+ var FLOW_MIN_ROW_HEIGHT_MM = 3;
929
+ var FLOW_MAX_ROW_HEIGHT_MM = 100;
930
+ var FLOW_MIN_GAP_X_MM = 0;
931
+ var FLOW_MAX_GAP_X_MM = 50;
932
+ var PT_TO_MM = 25.4 / 72;
933
+ function clampInt(value, min2, max2, fallback) {
934
+ if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
935
+ return Math.min(max2, Math.max(min2, Math.round(value)));
936
+ }
937
+ function resolveFlowColumns(value) {
938
+ return clampInt(value, FLOW_MIN_COLUMNS, FLOW_MAX_COLUMNS, 4);
939
+ }
940
+ function resolveFlowGapX(value) {
941
+ if (typeof value !== "number" || !Number.isFinite(value)) return 4;
942
+ return Math.min(FLOW_MAX_GAP_X_MM, Math.max(FLOW_MIN_GAP_X_MM, value));
943
+ }
944
+ function resolveFlowRowHeight(value) {
945
+ if (typeof value !== "number" || !Number.isFinite(value)) return 8;
946
+ return Math.min(FLOW_MAX_ROW_HEIGHT_MM, Math.max(FLOW_MIN_ROW_HEIGHT_MM, value));
947
+ }
948
+ function resolveFlowMaxSpan(value, columns) {
949
+ const cols = resolveFlowColumns(columns);
950
+ const fallback = Math.min(2, cols);
951
+ if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
952
+ return Math.min(cols, Math.max(1, Math.round(value)));
953
+ }
954
+ function flowColumnWidthMm(width, columns, gapX) {
955
+ const cols = resolveFlowColumns(columns);
956
+ const gap = resolveFlowGapX(gapX);
957
+ const avail = width - gap * (cols - 1);
958
+ if (avail <= 0) return 0;
959
+ return avail / cols;
960
+ }
961
+ function flowSpanWidthMm(colUnitMm, gapX, span) {
962
+ const k = Math.max(1, Math.floor(span));
963
+ return colUnitMm * k + gapX * (k - 1);
964
+ }
965
+ function estimateTextWidthMm(text, fontSizePt) {
966
+ if (!text) return 0;
967
+ const em = typeof fontSizePt === "number" && Number.isFinite(fontSizePt) && fontSizePt > 0 ? fontSizePt * PT_TO_MM : 12 * PT_TO_MM;
968
+ let units = 0;
969
+ for (const ch of text) {
970
+ if (ch === " ") {
971
+ units += 0.28;
972
+ continue;
973
+ }
974
+ const c = ch.codePointAt(0) ?? 0;
975
+ 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;
976
+ else if (/^[0-9A-Z]$/.test(ch)) units += 0.6;
977
+ else if (/^[a-z]$/.test(ch)) units += 0.5;
978
+ else if (/^[\u0020-\u007f]$/.test(ch)) units += 0.4;
979
+ else units += 0.75;
980
+ }
981
+ return units * em;
982
+ }
983
+ function spanForText(text, cfg) {
984
+ if (!cfg.autoSpan) return 1;
985
+ const ceiling = resolveFlowMaxSpan(cfg.maxSpan, cfg.columns);
986
+ if (ceiling <= 1 || cfg.colUnitMm <= 0) return 1;
987
+ const need = estimateTextWidthMm(text, cfg.fontSizePt);
988
+ for (let k = 1; k <= ceiling; k++) {
989
+ if (need <= flowSpanWidthMm(cfg.colUnitMm, cfg.gapX, k)) return k;
990
+ }
991
+ return ceiling;
992
+ }
993
+ function layoutFlowList(items, cfg) {
994
+ const columns = resolveFlowColumns(cfg.columns);
995
+ const gapX = resolveFlowGapX(cfg.gapX);
996
+ const colUnitMm = flowColumnWidthMm(cfg.width, columns, gapX);
997
+ const maxSpan = resolveFlowMaxSpan(cfg.maxSpan, columns);
998
+ const rows = [];
999
+ let current = [];
1000
+ let used = 0;
1001
+ for (const raw of items) {
1002
+ const text = String(raw ?? "");
1003
+ const span = spanForText(text, { ...cfg, columns, gapX, maxSpan, colUnitMm });
1004
+ if (current.length > 0 && used + span > columns) {
1005
+ rows.push({ cells: current });
1006
+ current = [];
1007
+ used = 0;
1008
+ }
1009
+ current.push({ text, span });
1010
+ used += span;
1011
+ }
1012
+ if (current.length > 0) rows.push({ cells: current });
1013
+ return rows;
1014
+ }
1015
+ function detectFlowListKey(expr, data) {
1016
+ if (typeof expr !== "string" || !expr.includes("{") || !data) return void 0;
1017
+ const roots = /* @__PURE__ */ new Set();
1018
+ for (const token of expr.matchAll(/\{([^{}]*)\}/g)) {
1019
+ for (const piece of (token[1] ?? "").split(/[^A-Za-z0-9_\u4e00-\u9fa5.]/)) {
1020
+ const root = piece.split(".")[0];
1021
+ if (root && !/^\d/.test(root)) roots.add(root);
1022
+ }
1023
+ }
1024
+ for (const root of roots) {
1025
+ if (Array.isArray(data[root])) return root;
1026
+ }
1027
+ return void 0;
1028
+ }
1029
+ function suggestFlowColumns(width, gapX, fontSizePt) {
1030
+ const gap = resolveFlowGapX(gapX);
1031
+ const minCol = estimateTextWidthMm("\u4E00", fontSizePt);
1032
+ if (minCol <= 0 || width <= 0) return FLOW_MIN_COLUMNS;
1033
+ const fit = Math.floor((width + gap) / (minCol + gap));
1034
+ return Math.min(FLOW_MAX_COLUMNS, Math.max(FLOW_MIN_COLUMNS, fit));
1035
+ }
1036
+ var mmNum = (v) => `${Math.round(v * 1e3) / 1e3}mm`;
1037
+ function flowGridStyle(cfg) {
1038
+ const columns = resolveFlowColumns(cfg.columns);
1039
+ const gapX = resolveFlowGapX(cfg.gapX);
1040
+ const rowHeight = resolveFlowRowHeight(cfg.rowHeight);
1041
+ return [
1042
+ `display:grid`,
1043
+ `grid-template-columns:repeat(${columns},minmax(0,1fr))`,
1044
+ `column-gap:${mmNum(gapX)}`,
1045
+ `grid-auto-rows:minmax(${mmNum(rowHeight)},auto)`
1046
+ ].join(";");
1047
+ }
1048
+
925
1049
  // src/render/data-binder.ts
926
1050
  function bindData(template, printData, baseUrl, fontBaseUrl) {
927
1051
  const data = { ...resolveSystemVariables(), ...printData ?? {} };
@@ -949,7 +1073,8 @@ function bindData(template, printData, baseUrl, fontBaseUrl) {
949
1073
  }
950
1074
  function bindElement(el, data, baseUrl) {
951
1075
  const cloned = { ...el, options: { ...el.options } };
952
- if (typeof cloned.options.formatter === "string") {
1076
+ const isFlowList = cloned.type === "flowList" || cloned.printElementType?.type === "flowList";
1077
+ if (!isFlowList && typeof cloned.options.formatter === "string") {
953
1078
  const raw = cloned.options.formatter;
954
1079
  if (referencesPageNumbers(raw)) {
955
1080
  cloned.options.rawFormatter = raw;
@@ -971,8 +1096,23 @@ function bindElement(el, data, baseUrl) {
971
1096
  if (cloned.type === "table" || cloned.printElementType?.type === "table") {
972
1097
  bindTableData(cloned, data);
973
1098
  }
1099
+ if (cloned.type === "flowList" || cloned.printElementType?.type === "flowList") {
1100
+ bindFlowListData(cloned, data);
1101
+ }
974
1102
  return cloned;
975
1103
  }
1104
+ function bindFlowListData(el, data) {
1105
+ const template = typeof el.options.formatter === "string" ? el.options.formatter : "";
1106
+ const source = { ...el.options, dataSource: el.options.dataSource ?? detectFlowListKey(template, data) };
1107
+ const { list, key } = resolveListSource(source, data);
1108
+ const itemCtx = (item) => key ? { ...data, ...item, [key]: item } : { ...data, ...item };
1109
+ const items = list.map((item) => {
1110
+ if (!template) return String(item ?? "");
1111
+ return referencesPageNumbers(template) ? template : evaluateTemplate(template, itemCtx(item));
1112
+ });
1113
+ el.options._flowItems = items;
1114
+ el.options._flowRawFormatter = referencesPageNumbers(template) ? template : void 0;
1115
+ }
976
1116
  function resolveListSource(opts, data) {
977
1117
  const explicitKey = opts.dataSource ?? opts.fields?.[0]?.dataSource ?? opts.fields?.[0]?.field;
978
1118
  if (explicitKey && Array.isArray(data[explicitKey])) {
@@ -1048,6 +1188,12 @@ function makeRenderRow(row, resolve, keepRaw = false) {
1048
1188
  barcodeType: cell.barcodeType,
1049
1189
  qrCodeLevel: cell.qrCodeLevel,
1050
1190
  qrQuietZone: cell.qrQuietZone,
1191
+ barQuietZone: cell.barQuietZone,
1192
+ // 条高同样必须进白名单:漏一个,属性台填的 mm 就只在设计器生效、出纸静默按缺省 30 模块
1193
+ barHeight: cell.barHeight,
1194
+ // 条宽与条码文本字号也必须在白名单里:漏一个,属性台填的倍率就只在设计器生效、出纸静默按缺省
1195
+ barWidth: cell.barWidth,
1196
+ barFontSize: cell.barFontSize,
1051
1197
  showBarcodeText: cell.showBarcodeText,
1052
1198
  printerDpi: cell.printerDpi,
1053
1199
  fit: cell.fit,
@@ -1164,6 +1310,57 @@ function toFontFamilyStack(family) {
1164
1310
  const head = name.includes(",") ? name : `"${name.replace(/"/g, "")}"`;
1165
1311
  return [head, ...FALLBACK_FONT_STACK].join(", ");
1166
1312
  }
1313
+ function resolveCanvasFontFamily(family) {
1314
+ return family ? toFontFamilyStack(family) : "inherit";
1315
+ }
1316
+ function normalizeFontCatalog(host) {
1317
+ const out = [];
1318
+ const seen = /* @__PURE__ */ new Set();
1319
+ for (const font of host ?? []) {
1320
+ const family = font?.family?.trim();
1321
+ if (!family) continue;
1322
+ const key = family.toLowerCase();
1323
+ if (seen.has(key)) continue;
1324
+ seen.add(key);
1325
+ const label = font?.label?.trim();
1326
+ out.push(label ? { family, label } : { family });
1327
+ }
1328
+ return out;
1329
+ }
1330
+ function findFontOption(fonts, family) {
1331
+ const key = family?.trim().toLowerCase();
1332
+ if (!key) return void 0;
1333
+ return fonts.find((f) => f.family.trim().toLowerCase() === key);
1334
+ }
1335
+ function fontOptionLabel(option) {
1336
+ return option.label ? `${option.label}\uFF08${option.family}\uFF09` : option.family;
1337
+ }
1338
+ function normalizeFontQuery(value) {
1339
+ return value.toLowerCase().replace(/\s+/g, "");
1340
+ }
1341
+ function matchRank(nameKey, queryKey) {
1342
+ if (nameKey === queryKey) return 0;
1343
+ if (nameKey.startsWith(queryKey)) return 1;
1344
+ if (nameKey.includes(queryKey)) return 2;
1345
+ let cursor = 0;
1346
+ for (const char of nameKey) {
1347
+ if (char === queryKey[cursor]) cursor++;
1348
+ if (cursor === queryKey.length) return 3;
1349
+ }
1350
+ return -1;
1351
+ }
1352
+ function fontMatchRank(font, queryKey) {
1353
+ const byFamily = matchRank(normalizeFontQuery(font.family), queryKey);
1354
+ if (!font.label) return byFamily;
1355
+ const byLabel = matchRank(normalizeFontQuery(font.label), queryKey);
1356
+ if (byFamily < 0) return byLabel;
1357
+ return byLabel < 0 ? byFamily : Math.min(byFamily, byLabel);
1358
+ }
1359
+ function filterFontOptions(fonts, query) {
1360
+ const queryKey = normalizeFontQuery(query.trim());
1361
+ if (!queryKey) return [...fonts];
1362
+ return fonts.map((font, index) => ({ font, index, rank: fontMatchRank(font, queryKey) })).filter((item) => item.rank >= 0).sort((a, b) => a.rank - b.rank || a.index - b.index).map((item) => item.font);
1363
+ }
1167
1364
 
1168
1365
  // src/render/types.ts
1169
1366
  var PAPER_DIMENSIONS = {
@@ -1298,7 +1495,8 @@ function elementBlock() {
1298
1495
  .print-table td {
1299
1496
  border: 1px solid #ccc;
1300
1497
  padding: 2mm 3mm;
1301
- word-break: break-all;
1498
+ word-break: normal;
1499
+ overflow-wrap: break-word;
1302
1500
  overflow: hidden;
1303
1501
  }
1304
1502
  .print-table thead th {
@@ -1536,7 +1734,9 @@ function normalizeFit(value) {
1536
1734
  }
1537
1735
  var ELEMENT_DEFAULT_FIT = {
1538
1736
  text: "clip",
1539
- longText: "autoHeight"
1737
+ longText: "autoHeight",
1738
+ // 流式列表一框一页:超出元素框高的单元按截断处理,不自动撑破纸面(specs 第八节第 8 项)
1739
+ flowList: "clip"
1540
1740
  };
1541
1741
  function resolveElementTextFit(type, opts) {
1542
1742
  return normalizeFit(opts?.textFit) ?? ELEMENT_DEFAULT_FIT[type] ?? "clip";
@@ -1601,6 +1801,7 @@ function acceptsElementBorder(type) {
1601
1801
  var VALID_STYLES = /* @__PURE__ */ new Set(["solid", "dashed", "dotted", "double"]);
1602
1802
  function parseBorder(width, style, color) {
1603
1803
  if (typeof width !== "number" || !Number.isFinite(width) || width <= 0) return null;
1804
+ if (style === "none") return null;
1604
1805
  return {
1605
1806
  borderWidth: `${width}pt`,
1606
1807
  borderStyle: typeof style === "string" && VALID_STYLES.has(style) ? style : "solid",
@@ -2290,10 +2491,38 @@ function specialRowHeight(renderRows, rowHeights, type) {
2290
2491
  // src/render/barcode-dot.ts
2291
2492
  var MM_PER_INCH = 25.4;
2292
2493
  var BARCODE_QUIET_ZONE_MODULES = 10;
2494
+ function barcodeQuietZoneModules(value) {
2495
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.floor(value)) : BARCODE_QUIET_ZONE_MODULES;
2496
+ }
2293
2497
  var BARCODE_BAR_HEIGHT_MODULES = 30;
2294
2498
  var BARCODE_TEXT_FONT_SIZE_MODULES = 10;
2295
2499
  var BARCODE_MARGIN_BOTTOM_MODULES = 2;
2500
+ var QR_DEFAULT_QUIET_MODULES = 0;
2296
2501
  var BARCODE_MODULE_WIDTH_MM = 0.25;
2502
+ var BARCODE_MIN_MODULE_WIDTH_MM = 0.254;
2503
+ function recommendBarcodeBarWidth(dpi) {
2504
+ if (typeof dpi !== "number" || !Number.isFinite(dpi) || dpi <= 0) return null;
2505
+ const dotsPerMm = dpi / MM_PER_INCH;
2506
+ for (let bw = 2; bw <= 8; bw += 0.5) {
2507
+ const dotsPerModule = Math.max(1, Math.round(barcodePreferredModuleWidthMm(bw) * dotsPerMm));
2508
+ const moduleWidthMm = dotsPerModule / dotsPerMm;
2509
+ if (moduleWidthMm >= BARCODE_MIN_MODULE_WIDTH_MM - 1e-9) {
2510
+ return { barWidth: bw, dotsPerModule, moduleWidthMm };
2511
+ }
2512
+ }
2513
+ return null;
2514
+ }
2515
+ function barcodeBarWidthRecommendText(dpi, currentBarWidth) {
2516
+ const rec = recommendBarcodeBarWidth(dpi);
2517
+ if (!rec) return "";
2518
+ const mm2 = (v) => String(Number(v.toFixed(3)));
2519
+ const head = `\u6309 ${dpi}dpi \u63A8\u8350\u6761\u5BBD\u500D\u7387 ${rec.barWidth}\uFF08\u6BCF\u6A21\u5757 ${rec.dotsPerModule} \u4E2A\u6253\u5370\u70B9 = ${mm2(rec.moduleWidthMm)}mm\uFF0C\u53D6\u8FBE\u5230 ${mm2(BARCODE_MIN_MODULE_WIDTH_MM)}mm \u4E0B\u9650\u7684\u6700\u5C0F\u6863\uFF09`;
2520
+ const cur = barcodePreferredModuleWidthMm(currentBarWidth ?? 2);
2521
+ const curDots = Math.max(1, Math.round(cur * dpi / MM_PER_INCH));
2522
+ const curMm = curDots / (dpi / MM_PER_INCH);
2523
+ const tail = curMm >= BARCODE_MIN_MODULE_WIDTH_MM - 1e-9 ? `\u5F53\u524D\u500D\u7387 ${currentBarWidth ?? 2} \u6298\u7B97\u4E3A ${curDots} \u70B9 / ${mm2(curMm)}mm\uFF0C\u5DF2\u8FBE\u6807` : `\u5F53\u524D\u500D\u7387 ${currentBarWidth ?? 2} \u53EA\u6709 ${curDots} \u70B9 / ${mm2(curMm)}mm\uFF0C\u4F4E\u4E8E\u4E0B\u9650`;
2524
+ return `${head}\uFF1B${tail}\u3002\u8FD9\u91CC\u53EA\u63D0\u793A\uFF0C\u4E0D\u4F1A\u66FF\u4F60\u6539\u503C`;
2525
+ }
2297
2526
  function barcodeUnitsPerModule(barWidth) {
2298
2527
  const raw = typeof barWidth === "number" && Number.isFinite(barWidth) ? barWidth : 2;
2299
2528
  return Math.max(1, raw / 2);
@@ -2301,6 +2530,36 @@ function barcodeUnitsPerModule(barWidth) {
2301
2530
  function barcodePreferredModuleWidthMm(barWidth) {
2302
2531
  return barcodeUnitsPerModule(barWidth) * BARCODE_MODULE_WIDTH_MM;
2303
2532
  }
2533
+ function barcodeBarHeightModules(input) {
2534
+ const { barHeightMm, moduleWidthMm, boxHeightMm, textModules } = input;
2535
+ const hasHeight = typeof barHeightMm === "number" && Number.isFinite(barHeightMm) && barHeightMm > 0;
2536
+ const hasGranularity = typeof moduleWidthMm === "number" && Number.isFinite(moduleWidthMm) && moduleWidthMm > 0;
2537
+ if (!hasHeight || !hasGranularity) return BARCODE_BAR_HEIGHT_MODULES;
2538
+ const wanted = Math.max(1, Math.round(barHeightMm / moduleWidthMm));
2539
+ const hasBox = typeof boxHeightMm === "number" && Number.isFinite(boxHeightMm) && boxHeightMm > 0;
2540
+ if (!hasBox) return wanted;
2541
+ const text = typeof textModules === "number" && Number.isFinite(textModules) && textModules > 0 ? textModules : 0;
2542
+ const cap = Math.floor((boxHeightMm - text * moduleWidthMm) / moduleWidthMm);
2543
+ return Math.max(1, Math.min(wanted, cap));
2544
+ }
2545
+ function dotAlignedModuleWidth(preferredMm, maxModuleMm, dotsPerMm) {
2546
+ const preferredDots = Math.max(1, Math.round(preferredMm * dotsPerMm));
2547
+ const maxDots = Math.floor(maxModuleMm * dotsPerMm);
2548
+ if (maxDots < 1) return null;
2549
+ const dotsPerModule = Math.min(preferredDots, maxDots);
2550
+ return { dotsPerModule, preferredDots, moduleWidthMm: dotsPerModule / dotsPerMm };
2551
+ }
2552
+ function barcodeWidthBoundModuleWidthMm(input) {
2553
+ const preferredMm = barcodePreferredModuleWidthMm(input.barWidth);
2554
+ const unitWidth = Math.max(1, Math.ceil(input.unitWidth));
2555
+ const maxModuleMm = input.boxWidthMm > 0 ? input.boxWidthMm / unitWidth : Number.POSITIVE_INFINITY;
2556
+ const dpi = input.dpi;
2557
+ if (typeof dpi === "number" && Number.isFinite(dpi) && dpi > 0) {
2558
+ const aligned = dotAlignedModuleWidth(preferredMm, maxModuleMm, dpi / MM_PER_INCH);
2559
+ if (aligned) return aligned.moduleWidthMm;
2560
+ }
2561
+ return Math.min(preferredMm, maxModuleMm);
2562
+ }
2304
2563
  function barcodeAvailableBoxMm(boxMm, maxMm) {
2305
2564
  const box = typeof boxMm === "number" && boxMm > 0 ? boxMm : Number.POSITIVE_INFINITY;
2306
2565
  const limit = typeof maxMm === "number" && maxMm > 0 ? Math.min(box, maxMm) : box;
@@ -2316,18 +2575,14 @@ function resolveBarcodeSize(input) {
2316
2575
  );
2317
2576
  const dpi = input.dpi;
2318
2577
  if (typeof dpi === "number" && Number.isFinite(dpi) && dpi > 0) {
2319
- const dotsPerMm = dpi / MM_PER_INCH;
2320
- const preferredDots = Math.max(1, Math.round(preferredModuleMm * dotsPerMm));
2321
- const maxDots = Math.floor(maxModuleMm * dotsPerMm);
2322
- if (maxDots >= 1) {
2323
- const dotsPerModule = Math.min(preferredDots, maxDots);
2324
- const moduleWidthMm2 = dotsPerModule / dotsPerMm;
2578
+ const aligned = dotAlignedModuleWidth(preferredModuleMm, maxModuleMm, dpi / MM_PER_INCH);
2579
+ if (aligned) {
2325
2580
  return {
2326
- moduleWidthMm: moduleWidthMm2,
2327
- widthMm: unitWidth * moduleWidthMm2,
2328
- heightMm: unitHeight * moduleWidthMm2,
2329
- dotsPerModule,
2330
- scaledDown: dotsPerModule < preferredDots
2581
+ moduleWidthMm: aligned.moduleWidthMm,
2582
+ widthMm: unitWidth * aligned.moduleWidthMm,
2583
+ heightMm: unitHeight * aligned.moduleWidthMm,
2584
+ dotsPerModule: aligned.dotsPerModule,
2585
+ scaledDown: aligned.dotsPerModule < aligned.preferredDots
2331
2586
  };
2332
2587
  }
2333
2588
  }
@@ -2340,6 +2595,39 @@ function resolveBarcodeSize(input) {
2340
2595
  scaledDown: moduleWidthMm < preferredModuleMm
2341
2596
  };
2342
2597
  }
2598
+ function layoutBarcodeSize(input) {
2599
+ const upm = barcodeUnitsPerModule(input.barWidth);
2600
+ const settle = (units) => resolveBarcodeSize({
2601
+ unitWidth: units.widthUnits / upm,
2602
+ unitHeight: units.heightUnits / upm,
2603
+ boxWidthMm: input.boxWidthMm,
2604
+ boxHeightMm: input.boxHeightMm,
2605
+ dpi: input.dpi,
2606
+ barWidth: input.barWidth
2607
+ });
2608
+ const first = input.measure(BARCODE_BAR_HEIGHT_MODULES);
2609
+ if (!first) return { barHeightModules: BARCODE_BAR_HEIGHT_MODULES, size: null };
2610
+ const hasHeight = typeof input.barHeightMm === "number" && Number.isFinite(input.barHeightMm) && input.barHeightMm > 0;
2611
+ if (!hasHeight) return { barHeightModules: BARCODE_BAR_HEIGHT_MODULES, size: settle(first) };
2612
+ const textModules = Math.max(0, first.heightUnits / upm - BARCODE_BAR_HEIGHT_MODULES);
2613
+ const barHeightModules = barcodeBarHeightModules({
2614
+ barHeightMm: input.barHeightMm,
2615
+ moduleWidthMm: barcodeWidthBoundModuleWidthMm({
2616
+ unitWidth: first.widthUnits / upm,
2617
+ boxWidthMm: input.boxWidthMm,
2618
+ dpi: input.dpi,
2619
+ barWidth: input.barWidth
2620
+ }),
2621
+ boxHeightMm: input.boxHeightMm,
2622
+ textModules
2623
+ });
2624
+ if (barHeightModules === BARCODE_BAR_HEIGHT_MODULES) {
2625
+ return { barHeightModules, size: settle(first) };
2626
+ }
2627
+ const second = input.measure(barHeightModules);
2628
+ if (!second) return { barHeightModules: BARCODE_BAR_HEIGHT_MODULES, size: null };
2629
+ return { barHeightModules, size: settle(second) };
2630
+ }
2343
2631
 
2344
2632
  // src/render/html-generator.ts
2345
2633
  function pageVarsContext(vars) {
@@ -2604,6 +2892,27 @@ function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
2604
2892
  return `<div class="print-element" style="${style};border:${opts.borderWidth || 1}pt ${opts.borderStyle || "solid"} ${opts.borderColor || "#333"};border-radius:50%;"${measureAttr}></div>`;
2605
2893
  case "longText":
2606
2894
  return `<div class="print-element" style="${style}${textStyle(opts)}${indentStyle(opts)}${elementFitStyle(fit, opts)}"${measureAttr}${fitAttr}>${esc(opts.formatter ?? opts.testData ?? "")}</div>`;
2895
+ case "flowList": {
2896
+ const columns = resolveFlowColumns(opts.flowColumns);
2897
+ const gapX = resolveFlowGapX(opts.flowGapX);
2898
+ const rowHeight = resolveFlowRowHeight(opts.flowRowHeight);
2899
+ const maxSpan = resolveFlowMaxSpan(opts.flowMaxSpan, columns);
2900
+ const boundItems = Array.isArray(opts._flowItems) ? opts._flowItems : void 0;
2901
+ const items = boundItems && boundItems.length > 0 ? boundItems : String(opts.testData ?? "").split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
2902
+ const cfg = {
2903
+ width,
2904
+ columns,
2905
+ gapX,
2906
+ rowHeight,
2907
+ maxSpan,
2908
+ fontSizePt: opts.fontSize ?? 12,
2909
+ autoSpan: opts.flowAutoSpan === true
2910
+ };
2911
+ 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 ");
2912
+ return `<div class="print-element" style="${style}${textStyle(opts)}overflow:hidden;${flowGridStyle(cfg)};"${measureAttr}${fitAttr}>
2913
+ ${cells}
2914
+ </div>`;
2915
+ }
2607
2916
  case "html":
2608
2917
  return `<div class="print-element" style="${style}"${measureAttr}>${opts.testData ?? opts.title ?? ""}</div>`;
2609
2918
  default:
@@ -2620,9 +2929,13 @@ function codeImgHtml(value, cellType, opts, io) {
2620
2929
  qrCodeLevel: opts.qrCodeLevel != null ? String(opts.qrCodeLevel) : void 0,
2621
2930
  // 0 是合法档(无白边),必须用 typeof 判定而非 or/?? 之类假值判断
2622
2931
  qrQuietZone: typeof opts.qrQuietZone === "number" ? opts.qrQuietZone : void 0,
2932
+ barQuietZone: typeof opts.barQuietZone === "number" ? opts.barQuietZone : void 0,
2933
+ // 条高(mm):元素台与单元格台都写 barHeight,非法/未填交由布局函数回退缺省档
2934
+ barHeightMm: typeof opts.barHeight === "number" ? opts.barHeight : void 0,
2623
2935
  showText: opts.hideTitle !== void 0 ? !opts.hideTitle : opts.showBarcodeText,
2624
2936
  barWidth: typeof opts.barWidth === "number" ? opts.barWidth : void 0,
2625
- fontSize: typeof opts.fontSize === "number" ? opts.fontSize : void 0,
2937
+ // 元素台的条码文本字号是 fontSize;单元格台的文本字号是 barFontSize(单元格的 fontSize 是正文字号,不该拿来决定条码)
2938
+ fontSize: typeof (opts.barFontSize ?? opts.fontSize) === "number" ? opts.barFontSize ?? opts.fontSize : void 0,
2626
2939
  printerDpi: isBarcode ? io.printerDpi : void 0,
2627
2940
  targetWidthMm: isBarcode ? barcodeAvailableBoxMm(io.targetWidthMm, maxWidth) : void 0,
2628
2941
  targetHeightMm: isBarcode ? barcodeAvailableBoxMm(io.targetHeightMm, maxHeight) : void 0
@@ -2653,8 +2966,9 @@ function matrixCellStyle(cell, opts) {
2653
2966
  const fontSize = cell.fittedFontSize ?? cell.fontSize ?? opts.tableDefaultFontSize;
2654
2967
  const color = cell.color ?? opts.tableDefaultColor;
2655
2968
  const padding = cell.padding ?? opts.tableDefaultPadding ?? 1;
2969
+ const fontFamily = cell.fontFamily ?? opts.tableDefaultFontFamily;
2656
2970
  if (fontSize) parts.push(`font-size:${fontSize}pt`);
2657
- if (cell.fontFamily) parts.push(`font-family:${escapeInlineStyleValue(toFontFamilyStack(cell.fontFamily))}`);
2971
+ if (fontFamily) parts.push(`font-family:${escapeInlineStyleValue(toFontFamilyStack(fontFamily))}`);
2658
2972
  if (cell.fontWeight) parts.push(`font-weight:${cell.fontWeight}`);
2659
2973
  if (color) parts.push(`color:${color}`);
2660
2974
  if (cell.backgroundColor) parts.push(`background-color:${cell.backgroundColor}`);
@@ -2662,7 +2976,7 @@ function matrixCellStyle(cell, opts) {
2662
2976
  if (cell.align === "justify") parts.push("text-align-last:justify");
2663
2977
  parts.push(`vertical-align:${cell.valign ?? "middle"}`);
2664
2978
  parts.push(`padding:${padding}mm`);
2665
- parts.push(cell.wordWrap === false ? "white-space:nowrap;overflow:hidden" : "word-break:break-all");
2979
+ parts.push(cell.wordWrap === false ? "white-space:nowrap;overflow:hidden" : "white-space:pre-wrap;word-break:normal;overflow-wrap:break-word");
2666
2980
  const b = cell.borders ?? {};
2667
2981
  for (const side of ["top", "right", "bottom", "left"]) {
2668
2982
  const border = b[side];
@@ -3046,7 +3360,9 @@ function codeSpecKey(value, cellType, opts = {}) {
3046
3360
  opts.showText ?? null,
3047
3361
  opts.barWidth ?? null,
3048
3362
  opts.fontSize ?? null,
3049
- opts.qrQuietZone ?? null
3363
+ opts.qrQuietZone ?? null,
3364
+ opts.barQuietZone ?? null,
3365
+ opts.barHeightMm ?? null
3050
3366
  ]);
3051
3367
  }
3052
3368
  function createMapCodeRenderer(map) {
@@ -3786,6 +4102,24 @@ export {
3786
4102
  roundHalfEven,
3787
4103
  safeEval,
3788
4104
  evaluateTemplate,
4105
+ FLOW_MIN_COLUMNS,
4106
+ FLOW_MAX_COLUMNS,
4107
+ FLOW_MIN_ROW_HEIGHT_MM,
4108
+ FLOW_MAX_ROW_HEIGHT_MM,
4109
+ FLOW_MIN_GAP_X_MM,
4110
+ FLOW_MAX_GAP_X_MM,
4111
+ resolveFlowColumns,
4112
+ resolveFlowGapX,
4113
+ resolveFlowRowHeight,
4114
+ resolveFlowMaxSpan,
4115
+ flowColumnWidthMm,
4116
+ flowSpanWidthMm,
4117
+ estimateTextWidthMm,
4118
+ spanForText,
4119
+ layoutFlowList,
4120
+ detectFlowListKey,
4121
+ suggestFlowColumns,
4122
+ flowGridStyle,
3789
4123
  bindData,
3790
4124
  resolveSystemVariables,
3791
4125
  injectSystemVariables,
@@ -3793,6 +4127,12 @@ export {
3793
4127
  FALLBACK_FONT_STACK,
3794
4128
  escapeInlineStyleValue,
3795
4129
  toFontFamilyStack,
4130
+ resolveCanvasFontFamily,
4131
+ normalizeFontCatalog,
4132
+ findFontOption,
4133
+ fontOptionLabel,
4134
+ normalizeFontQuery,
4135
+ filterFontOptions,
3796
4136
  PAPER_DIMENSIONS,
3797
4137
  isContinuousPaperSize,
3798
4138
  getPaperDimensions,
@@ -3840,14 +4180,22 @@ export {
3840
4180
  paginate,
3841
4181
  MM_PER_INCH,
3842
4182
  BARCODE_QUIET_ZONE_MODULES,
4183
+ barcodeQuietZoneModules,
3843
4184
  BARCODE_BAR_HEIGHT_MODULES,
3844
4185
  BARCODE_TEXT_FONT_SIZE_MODULES,
3845
4186
  BARCODE_MARGIN_BOTTOM_MODULES,
4187
+ QR_DEFAULT_QUIET_MODULES,
3846
4188
  BARCODE_MODULE_WIDTH_MM,
4189
+ BARCODE_MIN_MODULE_WIDTH_MM,
4190
+ recommendBarcodeBarWidth,
4191
+ barcodeBarWidthRecommendText,
3847
4192
  barcodeUnitsPerModule,
3848
4193
  barcodePreferredModuleWidthMm,
4194
+ barcodeBarHeightModules,
4195
+ barcodeWidthBoundModuleWidthMm,
3849
4196
  barcodeAvailableBoxMm,
3850
4197
  resolveBarcodeSize,
4198
+ layoutBarcodeSize,
3851
4199
  generateHtml,
3852
4200
  MIN_CONTINUOUS_HEIGHT_MM,
3853
4201
  composeContinuousHeight,
@@ -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
@@ -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
@@ -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
@@ -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