@worm-vue3-print/core 1.2.2 → 1.3.0

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.
package/dist/index.cjs CHANGED
@@ -20,38 +20,118 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ DEFAULT_READINESS_MS: () => DEFAULT_READINESS_MS,
24
+ DEFAULT_SHRINK_MIN_FONT_SIZE_PT: () => DEFAULT_SHRINK_MIN_FONT_SIZE_PT,
25
+ DEFAULT_TIMEOUT_MS: () => DEFAULT_TIMEOUT_MS,
26
+ EXECUTOR_TARGETS: () => EXECUTOR_TARGETS,
27
+ FALLBACK_FONT_STACK: () => FALLBACK_FONT_STACK,
28
+ MAX_BATCH_COPIES: () => MAX_BATCH_COPIES,
29
+ MICROMETERS_PER_INCH: () => MICROMETERS_PER_INCH,
30
+ MICROMETERS_PER_MM: () => MICROMETERS_PER_MM,
31
+ MIN_CONTINUOUS_HEIGHT_MM: () => MIN_CONTINUOUS_HEIGHT_MM,
32
+ MIN_SHRINK_FONT_SIZE_PT: () => MIN_SHRINK_FONT_SIZE_PT,
33
+ MM_PER_INCH: () => MM_PER_INCH,
34
+ MM_PER_PX: () => MM_PER_PX,
23
35
  PAPER_DIMENSIONS: () => PAPER_DIMENSIONS,
36
+ PX_PER_MM: () => PX_PER_MM,
37
+ PrintFailure: () => PrintFailure,
38
+ TILE_DEFAULTS: () => TILE_DEFAULTS,
24
39
  TemplateEngine: () => TemplateEngine,
40
+ TilingError: () => TilingError,
41
+ WATERMARK_DEFAULTS: () => WATERMARK_DEFAULTS,
42
+ WATERMARK_DENSITY_PRESETS: () => WATERMARK_DENSITY_PRESETS,
43
+ applyTextFitSizes: () => applyTextFitSizes,
25
44
  avg: () => avg,
26
45
  bindData: () => bindData,
46
+ buildBasePageCss: () => buildBasePageCss,
47
+ buildFontFaceCss: () => buildFontFaceCss,
27
48
  buildPageCss: () => buildPageCss,
49
+ buildPageGeometryCss: () => buildPageGeometryCss,
50
+ buildPageRuleCss: () => buildPageRuleCss,
51
+ buildPdfTargetSpec: () => buildPdfTargetSpec,
52
+ buildScreenshotTargetSpec: () => buildScreenshotTargetSpec,
53
+ cellFitCapMm: () => cellFitCapMm,
54
+ cellFitKey: () => cellFitKey,
55
+ codeSpecKey: () => codeSpecKey,
28
56
  compileTemplate: () => compileTemplate,
57
+ composeBatchHtml: () => composeBatchHtml,
58
+ composeContinuousHeight: () => composeContinuousHeight,
59
+ composeMultiPageDocument: () => composeMultiPageDocument,
60
+ composeTiledHtml: () => composeTiledHtml,
61
+ computeMaxColumns: () => computeMaxColumns,
62
+ computeTileLayout: () => computeTileLayout,
29
63
  count: () => count,
64
+ createCollectingCodeRenderer: () => createCollectingCodeRenderer,
65
+ createDomHostRuntime: () => createDomHostRuntime,
66
+ createMapCodeRenderer: () => createMapCodeRenderer,
30
67
  elementPositionStyle: () => elementPositionStyle,
68
+ escapeHeightMm: () => escapeHeightMm,
69
+ escapeInlineStyleValue: () => escapeInlineStyleValue,
31
70
  evaluate: () => evaluate,
32
71
  evaluateTemplate: () => evaluateTemplate,
72
+ floorFontSize: () => floorFontSize,
33
73
  formatDate: () => formatDate,
34
74
  formatMoney: () => formatMoney,
75
+ formatTimestamp: () => formatTimestamp,
35
76
  generateHtml: () => generateHtml,
36
77
  getByPath: () => getByPath,
37
78
  getPaperDimensions: () => getPaperDimensions,
38
79
  ifFn: () => ifFn,
39
80
  injectSystemVariables: () => injectSystemVariables,
81
+ isContinuousPaper: () => isContinuousPaper,
82
+ isContinuousPaperSize: () => isContinuousPaperSize,
83
+ isMultiPageTemplate: () => isMultiPageTemplate,
84
+ isWatermarkVisible: () => isWatermarkVisible,
40
85
  max: () => max,
86
+ mergeCodeMaps: () => mergeCodeMaps,
87
+ mergeFontDeclarations: () => mergeFontDeclarations,
88
+ micrometersToMillimeters: () => micrometersToMillimeters,
89
+ millimetersToInches: () => millimetersToInches,
90
+ millimetersToMicrometers: () => millimetersToMicrometers,
41
91
  min: () => min,
42
92
  mm: () => mm,
93
+ mmToPx: () => mmToPx,
94
+ normalizeMeasurements: () => normalizeMeasurements,
95
+ normalizePrintData: () => normalizePrintData,
96
+ normalizeTemplate: () => normalizeTemplate,
97
+ normalizeTilingOptions: () => normalizeTilingOptions,
43
98
  paginate: () => paginate,
99
+ paperViewportPx: () => paperViewportPx,
44
100
  parse: () => parse,
101
+ parseCellFitKey: () => parseCellFitKey,
45
102
  parseTemplate: () => parseTemplate,
103
+ prepareDocument: () => prepareDocument,
104
+ pxToMm: () => pxToMm,
105
+ renderPdf: () => renderPdf,
106
+ renderScreenshot: () => renderScreenshot,
46
107
  renderTemplate: () => renderTemplate,
108
+ renderWatermarkLayerHtml: () => renderWatermarkLayerHtml,
109
+ renderWatermarkTileSvg: () => renderWatermarkTileSvg,
110
+ resolveCellTextFit: () => resolveCellTextFit,
111
+ resolveElementTextFit: () => resolveElementTextFit,
112
+ resolvePaperMm: () => resolvePaperMm,
113
+ resolveSheetMm: () => resolveSheetMm,
114
+ resolveShrinkMinFontSize: () => resolveShrinkMinFontSize,
115
+ resolveSystemVariables: () => resolveSystemVariables,
116
+ resolveWatermarkLayout: () => resolveWatermarkLayout,
117
+ resolveWatermarkText: () => resolveWatermarkText,
118
+ roundFontSize: () => roundFontSize,
119
+ roundMm: () => roundMm,
47
120
  safeEval: () => safeEval,
48
121
  setPageIndex: () => setPageIndex,
49
122
  setTotalPages: () => setTotalPages,
50
123
  sum: () => sum,
51
124
  systemVars: () => systemVars,
52
125
  tableDesignBottom: () => tableDesignBottom,
126
+ tilePosition: () => tilePosition,
127
+ toElectronPrintToPdfOptions: () => toElectronPrintToPdfOptions,
128
+ toFontFamilyStack: () => toFontFamilyStack,
129
+ toPlaywrightPdfOptions: () => toPlaywrightPdfOptions,
130
+ toPrintFailure: () => toPrintFailure,
53
131
  toUpperCaseAmount: () => toUpperCaseAmount,
54
- tokenize: () => tokenize
132
+ tokenize: () => tokenize,
133
+ validateTiling: () => validateTiling,
134
+ withTimeout: () => withTimeout
55
135
  });
56
136
  module.exports = __toCommonJS(index_exports);
57
137
 
@@ -641,8 +721,8 @@ function formatDate(value, fmt) {
641
721
  return String(value);
642
722
  }
643
723
  if (isNaN(date.getTime())) return String(value);
644
- const pad = (n) => String(n).padStart(2, "0");
645
- return fmt.replace("YYYY", String(date.getFullYear())).replace("MM", pad(date.getMonth() + 1)).replace("DD", pad(date.getDate())).replace("HH", pad(date.getHours())).replace("mm", pad(date.getMinutes())).replace("ss", pad(date.getSeconds()));
724
+ const pad3 = (n) => String(n).padStart(2, "0");
725
+ 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()));
646
726
  }
647
727
  function toUpperCaseAmount(value) {
648
728
  const num = Number(value);
@@ -812,9 +892,8 @@ function evaluateTemplate(text, ctx) {
812
892
  }
813
893
 
814
894
  // src/render/data-binder.ts
815
- function bindData(template, printData, baseUrl) {
816
- const raw = printData ?? {};
817
- const data = Array.isArray(raw) ? raw[0] ?? {} : raw;
895
+ function bindData(template, printData, baseUrl, fontBaseUrl) {
896
+ const data = printData ?? {};
818
897
  const bound = JSON.parse(JSON.stringify(template));
819
898
  if (bound.header?.elements) {
820
899
  bound.header.elements = bound.header.elements.map((el) => bindElement(el, data, baseUrl));
@@ -826,6 +905,18 @@ function bindData(template, printData, baseUrl) {
826
905
  bound.firstPageOverlay.elements = bound.firstPageOverlay.elements.map((el) => bindElement(el, data, baseUrl));
827
906
  }
828
907
  bound.elements = bound.elements.map((el) => bindElement(el, data, baseUrl));
908
+ const fontBase = fontBaseUrl ?? baseUrl;
909
+ if (fontBase && bound.fonts?.length) {
910
+ const prefix = fontBase.replace(/\/+$/, "");
911
+ bound.fonts = bound.fonts.map((font) => ({
912
+ ...font,
913
+ files: (font.files ?? []).map((file) => ({
914
+ ...file,
915
+ // 绝对 URL(含协议相对 //)原样使用
916
+ url: file.url?.startsWith("/") && !file.url.startsWith("//") ? prefix + file.url : file.url
917
+ }))
918
+ }));
919
+ }
829
920
  return bound;
830
921
  }
831
922
  function bindElement(el, data, baseUrl) {
@@ -931,6 +1022,7 @@ function makeRenderRow(row, resolve, keepRaw = false) {
931
1022
  barcodeType: cell.barcodeType,
932
1023
  qrCodeLevel: cell.qrCodeLevel,
933
1024
  showBarcodeText: cell.showBarcodeText,
1025
+ printerDpi: cell.printerDpi,
934
1026
  fit: cell.fit,
935
1027
  maxWidth: cell.maxWidth,
936
1028
  maxHeight: cell.maxHeight,
@@ -940,12 +1032,15 @@ function makeRenderRow(row, resolve, keepRaw = false) {
940
1032
  align: cell.align,
941
1033
  valign: cell.valign,
942
1034
  fontSize: cell.fontSize,
1035
+ fontFamily: cell.fontFamily,
943
1036
  fontWeight: cell.fontWeight,
944
1037
  color: cell.color,
945
1038
  backgroundColor: cell.backgroundColor,
946
1039
  borders: cell.borders,
947
1040
  padding: cell.padding,
948
- wordWrap: cell.wordWrap
1041
+ wordWrap: cell.wordWrap,
1042
+ textFit: cell.textFit,
1043
+ shrinkMinFontSize: cell.shrinkMinFontSize
949
1044
  }))
950
1045
  };
951
1046
  }
@@ -960,10 +1055,63 @@ function countRepeatHeader(rows) {
960
1055
  }
961
1056
  return n;
962
1057
  }
963
- function injectSystemVariables(html) {
964
- const now = /* @__PURE__ */ new Date();
965
- const printDate = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
966
- return html.replace(/\{printDate\}/g, printDate);
1058
+ var pad2 = (n) => String(n).padStart(2, "0");
1059
+ function resolveSystemVariables(now = /* @__PURE__ */ new Date(), page = {}) {
1060
+ return {
1061
+ printDate: `${now.getFullYear()}-${pad2(now.getMonth() + 1)}-${pad2(now.getDate())}`,
1062
+ printTime: `${pad2(now.getHours())}:${pad2(now.getMinutes())}:${pad2(now.getSeconds())}`,
1063
+ pageIndex: page.pageIndex ?? 1,
1064
+ totalPages: page.totalPages ?? 1
1065
+ };
1066
+ }
1067
+ function injectSystemVariables(html, now = /* @__PURE__ */ new Date()) {
1068
+ const { printDate, printTime } = resolveSystemVariables(now);
1069
+ return html.replace(/\{printDate\}/g, printDate).replace(/\{printTime\}/g, printTime);
1070
+ }
1071
+
1072
+ // src/print/fonts.ts
1073
+ function buildFontFaceCss(fonts) {
1074
+ const blocks = [];
1075
+ for (const font of fonts ?? []) {
1076
+ const family = font?.family?.trim();
1077
+ if (!family) continue;
1078
+ const quoted = `"${family.replace(/"/g, "")}"`;
1079
+ for (const file of font.files ?? []) {
1080
+ const url = file?.url?.trim();
1081
+ if (!url) continue;
1082
+ const weight = Number.isFinite(file.weight) ? Number(file.weight) : 400;
1083
+ const style = file.style === "italic" ? "italic" : "normal";
1084
+ blocks.push(
1085
+ `@font-face{font-family:${quoted};src:url("${url}")${fontFormatHint(url)};font-weight:${weight};font-style:${style};font-display:block;}`
1086
+ );
1087
+ }
1088
+ }
1089
+ return blocks.length ? `${blocks.join("")}
1090
+ ` : "";
1091
+ }
1092
+ function fontFormatHint(url) {
1093
+ const path = url.split(/[?#]/)[0].toLowerCase();
1094
+ if (path.endsWith(".woff2")) return ' format("woff2")';
1095
+ if (path.endsWith(".woff")) return ' format("woff")';
1096
+ if (path.endsWith(".ttf")) return ' format("truetype")';
1097
+ if (path.endsWith(".otf")) return ' format("opentype")';
1098
+ return "";
1099
+ }
1100
+ var FALLBACK_FONT_STACK = [
1101
+ '"Microsoft YaHei"',
1102
+ '"PingFang SC"',
1103
+ '"Helvetica Neue"',
1104
+ "Arial",
1105
+ "sans-serif"
1106
+ ];
1107
+ function escapeInlineStyleValue(cssValue) {
1108
+ return cssValue.replace(/&/g, "&").replace(/"/g, """);
1109
+ }
1110
+ function toFontFamilyStack(family) {
1111
+ const name = family?.trim();
1112
+ if (!name) return FALLBACK_FONT_STACK.join(", ");
1113
+ const head = name.includes(",") ? name : `"${name.replace(/"/g, "")}"`;
1114
+ return [head, ...FALLBACK_FONT_STACK].join(", ");
967
1115
  }
968
1116
 
969
1117
  // src/render/types.ts
@@ -973,38 +1121,81 @@ var PAPER_DIMENSIONS = {
973
1121
  A5: { width: 148, height: 210 },
974
1122
  Letter: { width: 216, height: 279 },
975
1123
  Legal: { width: 216, height: 356 },
976
- CUSTOM: { width: 210, height: 297 }
1124
+ // 针式打印纸(241 系列等分):11 英寸整张 279.4mm 按等分取整
1125
+ DOT_FULL: { width: 241, height: 279.4 },
1126
+ DOT_HALF: { width: 241, height: 139.7 },
1127
+ DOT_THIRD: { width: 241, height: 93.1 },
1128
+ // 标签纸
1129
+ LABEL_80X60: { width: 80, height: 60 },
1130
+ LABEL_60X40: { width: 60, height: 40 },
1131
+ LABEL_40X30: { width: 40, height: 30 },
1132
+ // 小票纸(热敏卷纸):高度仅为设计画布高度,出纸按内容推导
1133
+ THERMAL_57: { width: 57, height: 297 },
1134
+ THERMAL_80: { width: 80, height: 297 },
1135
+ THERMAL_110: { width: 110, height: 297 },
1136
+ CUSTOM: { width: 210, height: 297 },
1137
+ // 连续纸:默认 80mm 热敏;高度仅为设计画布高度,出纸按内容推导
1138
+ CONTINUOUS: { width: 80, height: 297 }
977
1139
  };
1140
+ var CONTINUOUS_PAPER_SIZES = /* @__PURE__ */ new Set([
1141
+ "CONTINUOUS",
1142
+ "THERMAL_57",
1143
+ "THERMAL_80",
1144
+ "THERMAL_110"
1145
+ ]);
1146
+ function isContinuousPaperSize(paperSize) {
1147
+ return CONTINUOUS_PAPER_SIZES.has(paperSize);
1148
+ }
978
1149
  function getPaperDimensions(template) {
979
- const base = template.paperSize === "CUSTOM" ? { width: template.customWidth ?? 210, height: template.customHeight ?? 297 } : PAPER_DIMENSIONS[template.paperSize];
980
- if (template.orientation === "landscape") {
1150
+ const continuous = isContinuousPaperSize(template.paperSize);
1151
+ const base = template.paperSize === "CUSTOM" || continuous ? {
1152
+ width: template.customWidth ?? PAPER_DIMENSIONS[template.paperSize].width,
1153
+ height: template.customHeight ?? PAPER_DIMENSIONS[template.paperSize].height
1154
+ } : PAPER_DIMENSIONS[template.paperSize];
1155
+ if (template.orientation === "landscape" && !continuous) {
981
1156
  return { width: base.height, height: base.width };
982
1157
  }
983
1158
  return { ...base };
984
1159
  }
1160
+ function getOutputPaperDimensions(template) {
1161
+ const continuous = isContinuousPaperSize(template.paperSize);
1162
+ if (continuous || template.tiling?.enabled === true) {
1163
+ return getPaperDimensions(template);
1164
+ }
1165
+ const design = getPaperDimensions(template);
1166
+ const swap = template.outputRotation === 90 || template.outputRotation === 270;
1167
+ return swap ? { width: design.height, height: design.width } : design;
1168
+ }
1169
+ function getOutputRotationAngle(template) {
1170
+ const continuous = isContinuousPaperSize(template.paperSize);
1171
+ if (continuous || template.tiling?.enabled === true) return 0;
1172
+ return template.outputRotation ?? 0;
1173
+ }
1174
+ function isContinuousPaper(template) {
1175
+ return isContinuousPaperSize(template.paperSize);
1176
+ }
985
1177
 
986
1178
  // src/render/css-builder.ts
987
1179
  function mm(value) {
988
1180
  return `${value}mm`;
989
1181
  }
990
- function buildPageCss(template) {
991
- const paper = getPaperDimensions(template);
992
- const { top: mt, right: mr, bottom: mb, left: ml } = template.margins;
993
- const headerH = template.header?.height ?? 0;
994
- const footerH = template.footer?.height ?? 0;
995
- const overlayH = template.firstPageOverlay?.height ?? 0;
996
- const contentWidth = paper.width - ml - mr;
1182
+ function resolveOuterPaper(template, pageHeightMm) {
1183
+ const out = getOutputPaperDimensions(template);
1184
+ return pageHeightMm && pageHeightMm > 0 && isContinuousPaper(template) ? { width: out.width, height: pageHeightMm } : out;
1185
+ }
1186
+ function resolveAreaPaper(template, pageHeightMm) {
1187
+ const design = getPaperDimensions(template);
1188
+ return pageHeightMm && pageHeightMm > 0 && isContinuousPaper(template) ? { width: design.width, height: pageHeightMm } : design;
1189
+ }
1190
+ function screenBlock() {
997
1191
  return `
998
- /* \u2500\u2500 \u6253\u5370\u7EB8\u5F20\uFF1A\u6D4F\u89C8\u5668\u539F\u751F\u6253\u5370\u6309\u6B64\u5C3A\u5BF8\u5206\u9875\uFF08Playwright page.pdf \u4EE5\u663E\u5F0F\u5BBD\u9AD8\u4E3A\u51C6\uFF0C\u65E0\u526F\u4F5C\u7528\uFF09 \u2500\u2500 */
999
- @page { size: ${mm(paper.width)} ${mm(paper.height)}; margin: 0; }
1000
-
1001
1192
  /* \u2500\u2500 \u5168\u5C40\u91CD\u7F6E \u2500\u2500 */
1002
1193
  * { margin: 0; padding: 0; box-sizing: border-box; }
1003
1194
 
1004
1195
  /* \u6253\u5370\u5FC5\u987B\u4FDD\u7559\u5143\u7D20\u80CC\u666F\u8272\uFF08Chromium \u9ED8\u8BA4\u5254\u9664\u80CC\u666F\uFF0C\u9700\u663E\u5F0F\u58F0\u660E\uFF09 */
1005
1196
  * { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
1006
1197
 
1007
- body { font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif; }
1198
+ body { font-family: ${FALLBACK_FONT_STACK.join(", ")}; }
1008
1199
 
1009
1200
  /* \u2500\u2500 \u5C4F\u5E55\u9884\u89C8\uFF1A\u7070\u5E95 + \u7EB8\u5F20\u9634\u5F71/\u9875\u95F4\u8DDD\uFF1B\u6253\u5370\u65F6\u53BB\u9664 \u2500\u2500 */
1010
1201
  @media screen {
@@ -1015,13 +1206,110 @@ body { font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, s
1015
1206
  body { background: #fff; }
1016
1207
  .print-page { margin: 0; box-shadow: none; }
1017
1208
  }
1209
+ `;
1210
+ }
1211
+ function watermarkBlock() {
1212
+ return `
1213
+ /* \u2500\u2500 \u6C34\u5370\u5C42\uFF1A\u8986\u76D6\u6574\u9875\u3001\u4F4D\u4E8E\u6240\u6709\u5185\u5BB9\u4E4B\u4E0B\uFF08\u663E\u5F0F\u77E2\u91CF\u74E6\u7247\uFF0C\u7981\u6B62\u7528 CSS \u5E73\u94FA\u80CC\u666F\uFF1A
1214
+ Chromium \u4F1A\u628A\u5B83\u7F16\u8BD1\u6210 PDF \u5E73\u94FA\u56FE\u6848\uFF0C\u51FA\u7EB8\u94FE\u8DEF\u7684 RIP \u4F1A\u5FFD\u7565\u56FE\u6848\u77E9\u9635\u5BFC\u81F4\u6C34\u5370\u653E\u5927/\u9519\u4F4D\uFF09 \u2500\u2500 */
1215
+ .watermark-layer {
1216
+ position: absolute;
1217
+ top: 0;
1218
+ left: 0;
1219
+ width: 100%;
1220
+ height: 100%;
1221
+ z-index: 0;
1222
+ pointer-events: none;
1223
+ overflow: hidden;
1224
+ }
1225
+ /* \u5355\u5757\u6C34\u5370\u74E6\u7247\uFF1A\u4F4D\u7F6E/\u5C3A\u5BF8\u7531 core \u7684\u74E6\u7247\u7F51\u683C\u7ED9\u51FA\uFF08mm\uFF09 */
1226
+ .watermark-tile {
1227
+ position: absolute;
1228
+ pointer-events: none;
1229
+ }
1230
+ `;
1231
+ }
1232
+ function elementBlock() {
1233
+ return `
1234
+ /* \u2500\u2500 \u5143\u7D20\u901A\u7528\u5B9A\u4F4D \u2500\u2500 */
1235
+ .print-element {
1236
+ position: absolute;
1237
+ overflow: hidden;
1238
+ }
1239
+
1240
+ /* \u2500\u2500 \u8868\u683C\u6837\u5F0F \u2500\u2500 */
1241
+ .print-table {
1242
+ width: 100%;
1243
+ border-collapse: collapse;
1244
+ table-layout: fixed;
1245
+ }
1246
+ .print-table th,
1247
+ .print-table td {
1248
+ border: 1px solid #ccc;
1249
+ padding: 2mm 3mm;
1250
+ word-break: break-all;
1251
+ overflow: hidden;
1252
+ }
1253
+ .print-table thead th {
1254
+ background-color: #f5f5f5;
1255
+ font-weight: bold;
1256
+ }
1018
1257
 
1258
+ /* \u2500\u2500 \u6D4B\u91CF\u6A21\u5F0F \u2500\u2500 */
1259
+ .measure-mode .print-page {
1260
+ height: auto;
1261
+ min-height: auto;
1262
+ overflow: visible;
1263
+ }
1264
+ .measure-mode .content-area {
1265
+ height: auto;
1266
+ overflow: visible;
1267
+ }
1268
+ `;
1269
+ }
1270
+ function pageRuleBlock(paper) {
1271
+ return `
1272
+ /* \u2500\u2500 \u6253\u5370\u7EB8\u5F20\uFF1A\u6D4F\u89C8\u5668\u539F\u751F\u6253\u5370\u6309\u6B64\u5C3A\u5BF8\u5206\u9875\uFF08Playwright page.pdf \u4EE5\u663E\u5F0F\u5BBD\u9AD8\u4E3A\u51C6\uFF0C\u65E0\u526F\u4F5C\u7528\uFF09 \u2500\u2500 */
1273
+ @page { size: ${mm(paper.width)} ${mm(paper.height)}; margin: 0; }
1274
+ `;
1275
+ }
1276
+ function buildRotorCss(area, margins, angle) {
1277
+ const base = `
1278
+ /* \u2500\u2500 \u51FA\u7EB8\u8F6C\u5B50\uFF1A\u8BBE\u8BA1\u7A3F\u6574\u9875\u65CB\u8F6C ${angle}\xB0 \u586B\u5165\u51FA\u7EB8\u7EB8\u5F20 \u2500\u2500 */
1279
+ .print-page-rotor {
1280
+ position: absolute;
1281
+ top: 0;
1282
+ left: 0;
1283
+ width: ${mm(area.width)};
1284
+ height: ${mm(area.height)};
1285
+ padding: ${mm(margins.top)} ${mm(margins.right)} ${mm(margins.bottom)} ${mm(margins.left)};
1286
+ box-sizing: border-box;
1287
+ transform-origin: 0 0;
1288
+ }`;
1289
+ if (angle === 0) return base.trim();
1290
+ const transforms = {
1291
+ 90: `translate(${mm(area.height)}, 0mm) rotate(90deg)`,
1292
+ 180: `translate(${mm(area.width)}, ${mm(area.height)}) rotate(180deg)`,
1293
+ 270: `translate(0mm, ${mm(area.width)}) rotate(270deg)`
1294
+ };
1295
+ const tf = transforms[angle];
1296
+ return [
1297
+ base.trim(),
1298
+ `.print-page-rotor-${angle} {
1299
+ transform: ${tf};
1300
+ }`
1301
+ ].join("\n");
1302
+ }
1303
+ function pageGeometryBlock(template, outerPaper, pageSel, rotated) {
1304
+ const { top: mt, right: mr, bottom: mb, left: ml } = template.margins;
1305
+ const padding = rotated ? "0" : `${mm(mt)} ${mm(mr)} ${mm(mb)} ${mm(ml)}`;
1306
+ return `
1019
1307
  /* \u2500\u2500 \u7EB8\u5F20\u9875\u9762 \u2500\u2500 */
1020
- .print-page {
1021
- width: ${mm(paper.width)};
1022
- min-height: ${mm(paper.height)};
1308
+ ${pageSel} {
1309
+ width: ${mm(outerPaper.width)};
1310
+ min-height: ${mm(outerPaper.height)};
1023
1311
  background: ${template.pageBackground ?? "#fff"};
1024
- padding: ${mm(mt)} ${mm(mr)} ${mm(mb)} ${mm(ml)};
1312
+ padding: ${padding};
1025
1313
  position: relative;
1026
1314
  page-break-after: always;
1027
1315
  overflow: hidden;
@@ -1029,9 +1317,18 @@ body { font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, s
1029
1317
  .print-page:last-child {
1030
1318
  page-break-after: auto;
1031
1319
  }
1032
-
1320
+ `;
1321
+ }
1322
+ function areaGeometryBlock(template, paper, desc) {
1323
+ const { right: mr, left: ml } = template.margins;
1324
+ const { bottom: mb } = template.margins;
1325
+ const headerH = template.header?.height ?? 0;
1326
+ const footerH = template.footer?.height ?? 0;
1327
+ const overlayH = template.firstPageOverlay?.height ?? 0;
1328
+ const contentWidth = paper.width - ml - mr;
1329
+ return `
1033
1330
  /* \u2500\u2500 \u9875\u7709 \u2500\u2500 */
1034
- .page-header {
1331
+ ${desc}.page-header {
1035
1332
  width: ${mm(contentWidth)};
1036
1333
  height: ${mm(headerH)};
1037
1334
  position: relative;
@@ -1040,7 +1337,7 @@ body { font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, s
1040
1337
  /* \u2500\u2500 \u9875\u811A\uFF1A\u7EDD\u5BF9\u5B9A\u4F4D\u56FA\u5B9A\u5728\u9875\u9762\u5E95\u90E8\uFF08\u5185\u5BB9\u4E0D\u8DB3\u65F6\u4E0D\u968F\u6587\u6863\u6D41\u4E0A\u6D6E\uFF09 \u2500\u2500
1041
1338
  \u7528\u663E\u5F0F top \u5B9A\u4F4D\u5230\u300C\u7EB8\u9AD8 - \u4E0B\u8FB9\u8DDD - \u9875\u811A\u9AD8\u300D\uFF0C\u4FDD\u8BC1\u4E0B\u8FB9\u8DDD\u751F\u6548\u3001
1042
1339
  \u4E0E\u8BBE\u8BA1\u5668 CanvasPaper \u7684\u4E09\u533A\u51E0\u4F55\u4E00\u81F4\u3002 */
1043
- .page-footer {
1340
+ ${desc}.page-footer {
1044
1341
  width: ${mm(contentWidth)};
1045
1342
  height: ${mm(footerH)};
1046
1343
  position: absolute;
@@ -1049,54 +1346,115 @@ body { font-family: "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, s
1049
1346
  }
1050
1347
 
1051
1348
  /* \u2500\u2500 \u5185\u5BB9\u533A \u2500\u2500 */
1052
- .content-area {
1349
+ ${desc}.content-area {
1053
1350
  width: ${mm(contentWidth)};
1054
1351
  position: relative;
1055
1352
  overflow: visible;
1056
1353
  }
1057
1354
 
1058
1355
  /* \u2500\u2500 \u9996\u9875\u53E0\u52A0\u533A\u57DF \u2500\u2500 */
1059
- .first-page-overlay {
1356
+ ${desc}.first-page-overlay {
1060
1357
  width: ${mm(contentWidth)};
1061
1358
  height: ${mm(overlayH)};
1062
1359
  position: relative;
1063
1360
  }
1361
+ `;
1362
+ }
1363
+ function buildBasePageCss() {
1364
+ return [screenBlock(), watermarkBlock(), elementBlock()].join("").trim();
1365
+ }
1366
+ function buildPageRuleCss(template, pageHeightMm) {
1367
+ return pageRuleBlock(resolveOuterPaper(template, pageHeightMm)).trim();
1368
+ }
1369
+ function buildPageGeometryCss(template, scope, pageHeightMm) {
1370
+ const angle = getOutputRotationAngle(template);
1371
+ const rotated = angle !== 0;
1372
+ const outer = resolveOuterPaper(template, pageHeightMm);
1373
+ const area = resolveAreaPaper(template, pageHeightMm);
1374
+ const pageSel = scope ? `${scope}.print-page` : ".print-page";
1375
+ const desc = scope ? `${scope} ` : "";
1376
+ return [
1377
+ pageGeometryBlock(template, outer, pageSel, rotated),
1378
+ areaGeometryBlock(template, area, desc),
1379
+ ...rotated ? [buildRotorCss(area, template.margins, angle)] : []
1380
+ ].join("").trim();
1381
+ }
1382
+ function buildPageCss(template, pageHeightMm) {
1383
+ const angle = getOutputRotationAngle(template);
1384
+ const rotated = angle !== 0;
1385
+ const outer = resolveOuterPaper(template, pageHeightMm);
1386
+ const area = resolveAreaPaper(template, pageHeightMm);
1387
+ return [
1388
+ pageRuleBlock(outer),
1389
+ screenBlock(),
1390
+ pageGeometryBlock(template, outer, ".print-page", rotated),
1391
+ watermarkBlock(),
1392
+ areaGeometryBlock(template, area, ""),
1393
+ elementBlock(),
1394
+ ...rotated ? [buildRotorCss(area, template.margins, angle)] : []
1395
+ ].join("").trim();
1396
+ }
1397
+ var COPY_BREAK_CSS = ".print-copy:not(:last-child){break-after:page;page-break-after:always;}";
1398
+ function buildBatchPageCss(template, copies) {
1399
+ if (!isContinuousPaper(template)) {
1400
+ return `${buildPageCss(template)}
1401
+ ${COPY_BREAK_CSS}`;
1402
+ }
1403
+ const { bottom: mb } = template.margins;
1404
+ const footerH = template.footer?.height ?? 0;
1405
+ const width = getPaperDimensions(template).width;
1406
+ const base = buildPageCss(template, copies[0]?.heightMm);
1407
+ const scoped = copies.map((copy, i) => {
1408
+ const h = copy.heightMm;
1409
+ const rules = [
1410
+ `@page copy${i} { size: ${mm(width)} ${h ? mm(h) : "auto"}; margin: 0; }`,
1411
+ `.print-copy-${i} { page: copy${i}; }`
1412
+ ];
1413
+ if (h) {
1414
+ rules.push(`.print-copy-${i} .print-page { min-height: ${mm(h)}; }`);
1415
+ rules.push(`.print-copy-${i} .page-footer { top: ${mm(h - mb - footerH)}; }`);
1416
+ }
1417
+ return rules.join("\n");
1418
+ }).join("\n");
1419
+ return `${base}
1420
+ ${scoped}
1421
+ ${COPY_BREAK_CSS}`;
1422
+ }
1423
+ function buildSheetPageCss(layout) {
1424
+ return `
1425
+ /* \u2500\u2500 \u62FC\u7248\u7EB8\u5F20\uFF1A@page \u5C3A\u5BF8 = \u76EE\u6807\u7EB8\u3002\u670D\u52A1\u7AEF/\u5BA2\u6237\u7AEF\u94FE\u8DEF\u4EE5 paperMm \u663E\u5F0F\u5B9A\u5C3A\u5BF8\u3001\u4E0D\u770B\u8FD9\u91CC\uFF0C
1426
+ \u4F46\u6D4F\u89C8\u5668\u94FE\u8DEF\u53EA\u770B @page\uFF0C\u6545\u8FD9\u6761\u662F\u786C\u9700\u6C42 \u2500\u2500 */
1427
+ @page { size: ${mm(layout.sheet.width)} ${mm(layout.sheet.height)}; margin: 0; }
1064
1428
 
1065
- /* \u2500\u2500 \u5143\u7D20\u901A\u7528\u5B9A\u4F4D \u2500\u2500 */
1066
- .print-element {
1067
- position: absolute;
1429
+ /* \u2500\u2500 \u4E00\u5F20\u76EE\u6807\u7EB8 \u2500\u2500 */
1430
+ .print-sheet {
1431
+ width: ${mm(layout.sheet.width)};
1432
+ height: ${mm(layout.sheet.height)};
1433
+ position: relative;
1068
1434
  overflow: hidden;
1435
+ break-after: page;
1436
+ page-break-after: always;
1069
1437
  }
1438
+ .print-sheet:last-child { break-after: auto; page-break-after: auto; }
1070
1439
 
1071
- /* \u2500\u2500 \u8868\u683C\u6837\u5F0F \u2500\u2500 */
1072
- .print-table {
1073
- width: 100%;
1074
- border-collapse: collapse;
1075
- table-layout: fixed;
1076
- }
1077
- .print-table th,
1078
- .print-table td {
1079
- border: 1px solid #ccc;
1080
- padding: 2mm 3mm;
1081
- word-break: break-all;
1440
+ /* \u2500\u2500 \u4E00\u683C\uFF1A\u7EDD\u5BF9\u5B9A\u4F4D\uFF0C\u4F4D\u7F6E\u7531 tilePosition() \u4EE5\u884C\u5185 style \u7ED9\u51FA \u2500\u2500 */
1441
+ .print-tile {
1442
+ position: absolute;
1443
+ width: ${mm(layout.tile.width)};
1444
+ height: ${mm(layout.tile.height)};
1082
1445
  overflow: hidden;
1083
1446
  }
1084
- .print-table thead th {
1085
- background-color: #f5f5f5;
1086
- font-weight: bold;
1087
- }
1447
+ /* \u9632\u5FA1\u6027\u58F0\u660E\uFF1A\u7EDD\u5BF9\u5B9A\u4F4D + overflow:hidden \u5BB9\u5668\u5185\u7684\u540E\u4EE3\u4E0D\u4EA7\u751F\u5206\u9875\u70B9\uFF0C\u5F53\u524D\u5E03\u5C40\u4E0B\u65E0\u5B9E\u9645\u4F5C\u7528\uFF1B
1448
+ \u82E5\u5C06\u6765\u6539\u7528 flex/grid \u5E03\u5C40\uFF0C\u683C\u5185\u6574\u9875\u4F1A\u91CD\u65B0\u53C2\u4E0E\u5206\u9875\uFF0C\u6545\u4FDD\u7559 */
1449
+ .print-tile > .print-page { break-after: auto; page-break-after: auto; }
1088
1450
 
1089
- /* \u2500\u2500 \u6D4B\u91CF\u6A21\u5F0F \u2500\u2500 */
1090
- .measure-mode .print-page {
1091
- height: auto;
1092
- min-height: auto;
1093
- overflow: visible;
1094
- }
1095
- .measure-mode .content-area {
1096
- height: auto;
1097
- overflow: visible;
1451
+ @media screen {
1452
+ .print-sheet { margin: 12px auto; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.18); }
1453
+ /* \u5FC5\u9700\uFF1A\u62B5\u6D88\u6807\u7B7E CSS \u7684 @media screen{.print-page{margin:12px auto}}\uFF0C
1454
+ \u5426\u5219\u8BBE\u8BA1\u5668\u9884\u89C8\u9519\u4F4D 3.17mm\u3001\u4E0E\u51FA\u7EB8\u4E0D\u4E00\u81F4 */
1455
+ .print-tile > .print-page { margin: 0; box-shadow: none; }
1098
1456
  }
1099
- `.trim();
1457
+ `;
1100
1458
  }
1101
1459
  function elementPositionStyle(left, top, width, height, zIndex) {
1102
1460
  const parts = [
@@ -1114,6 +1472,223 @@ function elementPositionStyle(left, top, width, height, zIndex) {
1114
1472
  return parts.join(";") + ";";
1115
1473
  }
1116
1474
 
1475
+ // src/designer/utils/units.ts
1476
+ function ptToMm(pt) {
1477
+ return pt / 2.83464566929;
1478
+ }
1479
+
1480
+ // src/render/text-fit.ts
1481
+ var DEFAULT_SHRINK_MIN_FONT_SIZE_PT = 6;
1482
+ var MIN_SHRINK_FONT_SIZE_PT = 1;
1483
+ var VALID_FITS = ["clip", "shrink", "autoHeight"];
1484
+ function normalizeFit(value) {
1485
+ return typeof value === "string" && VALID_FITS.includes(value) ? value : void 0;
1486
+ }
1487
+ var ELEMENT_DEFAULT_FIT = {
1488
+ text: "clip",
1489
+ longText: "autoHeight"
1490
+ };
1491
+ function resolveElementTextFit(type, opts) {
1492
+ return normalizeFit(opts?.textFit) ?? ELEMENT_DEFAULT_FIT[type] ?? "clip";
1493
+ }
1494
+ function resolveCellTextFit(cell) {
1495
+ return normalizeFit(cell?.textFit) ?? (cell?.wordWrap === false ? "clip" : "autoHeight");
1496
+ }
1497
+ function resolveShrinkMinFontSize(pt) {
1498
+ if (typeof pt !== "number" || !Number.isFinite(pt) || pt <= 0) return DEFAULT_SHRINK_MIN_FONT_SIZE_PT;
1499
+ return Math.max(pt, MIN_SHRINK_FONT_SIZE_PT);
1500
+ }
1501
+ function roundFontSize(pt) {
1502
+ return Math.round(pt * 100) / 100;
1503
+ }
1504
+ function floorFontSize(pt) {
1505
+ return Math.floor(pt * 100) / 100;
1506
+ }
1507
+ function cellFitKey(elementId, kind, rowIndex, colIndex) {
1508
+ return `${elementId}#${kind}#${rowIndex}:${colIndex}`;
1509
+ }
1510
+ function parseCellFitKey(key) {
1511
+ const first = key.indexOf("#");
1512
+ if (first < 0) return void 0;
1513
+ const last = key.lastIndexOf("#");
1514
+ const kind = key.slice(first + 1, last);
1515
+ if (kind !== "b" && kind !== "st" && kind !== "sm") return void 0;
1516
+ const [rowIndex, colIndex] = key.slice(last + 1).split(":").map(Number);
1517
+ if (!Number.isFinite(rowIndex) || !Number.isFinite(colIndex)) return void 0;
1518
+ return { elementId: key.slice(0, first), kind, rowIndex, colIndex };
1519
+ }
1520
+ function cellFitWidthMm(colWidths, colIndex, cell, defaultPadding = 1) {
1521
+ const span = Math.max(cell?.colspan ?? 1, 1);
1522
+ let width = 0;
1523
+ for (let i = 0; i < span; i++) {
1524
+ width += colWidths[colIndex + i] ?? 0;
1525
+ }
1526
+ if (width <= 0) return 0;
1527
+ const padding = cell?.padding ?? defaultPadding;
1528
+ const borderMm = (ptToMm(cell?.borders?.left?.width ?? 0) + ptToMm(cell?.borders?.right?.width ?? 0)) / 2;
1529
+ return Math.max(width - padding * 2 - borderMm, 0.5);
1530
+ }
1531
+ function cellFitCapMm(rows, rowIndex, cell, defaultPadding = 1) {
1532
+ const span = Math.max(cell?.rowspan ?? 1, 1);
1533
+ let height = 0;
1534
+ for (let i = 0; i < span; i++) {
1535
+ height += rows[rowIndex + i]?.height ?? 8;
1536
+ }
1537
+ const padding = cell?.padding ?? defaultPadding;
1538
+ const borderMm = (ptToMm(cell?.borders?.top?.width ?? 0) + ptToMm(cell?.borders?.bottom?.width ?? 0)) / 2;
1539
+ return Math.max(height - padding * 2 - borderMm, 0.5);
1540
+ }
1541
+
1542
+ // src/render/watermark.ts
1543
+ var PX_PER_MM = 96 / 25.4;
1544
+ var MM_PER_PX = 25.4 / 96;
1545
+ var WATERMARK_DEFAULTS = {
1546
+ color: "#cccccc",
1547
+ opacity: 0.15,
1548
+ rotate: -30,
1549
+ /** 瓦片默认尺寸(px):决定平铺疏密,默认 260×180 */
1550
+ tileWidth: 260,
1551
+ tileHeight: 180,
1552
+ /** 瓦片下限(px),防止文字裁剪/异常平铺 */
1553
+ minTileWidth: 140,
1554
+ minTileHeight: 100,
1555
+ /** 瓦片字号固定 16px:密度只改变平铺疏密,不改字体大小 */
1556
+ fontSize: 16
1557
+ };
1558
+ var WATERMARK_DENSITY_PRESETS = {
1559
+ dense: { width: 200, height: 120, label: "\u5BC6" },
1560
+ medium: { width: 260, height: 180, label: "\u4E2D\uFF08\u9ED8\u8BA4\uFF09" },
1561
+ loose: { width: 340, height: 260, label: "\u758F" }
1562
+ };
1563
+ function clampTile(value, fallback, min2) {
1564
+ const n = typeof value === "number" && Number.isFinite(value) ? value : fallback;
1565
+ return Math.max(min2, Math.round(n));
1566
+ }
1567
+ function resolveTileSize(wm) {
1568
+ return {
1569
+ width: clampTile(wm?.tileWidth, WATERMARK_DEFAULTS.tileWidth, WATERMARK_DEFAULTS.minTileWidth),
1570
+ height: clampTile(wm?.tileHeight, WATERMARK_DEFAULTS.tileHeight, WATERMARK_DEFAULTS.minTileHeight)
1571
+ };
1572
+ }
1573
+ function isWatermarkVisible(wm) {
1574
+ if (!wm) return false;
1575
+ if (!wm.mode || wm.mode === "fixed") {
1576
+ return !!wm.content && wm.content.trim().length > 0;
1577
+ }
1578
+ return !!wm.binding && wm.binding.trim().length > 0;
1579
+ }
1580
+ function resolveWatermarkText(wm, printData, systemVars2) {
1581
+ if (!wm) return "";
1582
+ let text = "";
1583
+ if (!wm.mode || wm.mode === "fixed") {
1584
+ text = wm.content ?? "";
1585
+ } else if (typeof wm.binding === "string" && wm.binding.trim()) {
1586
+ const binding = wm.binding.trim();
1587
+ const data = Array.isArray(printData) ? printData[0] ?? {} : printData ?? {};
1588
+ const ctx = { ...resolveSystemVariables(), ...systemVars2, ...data };
1589
+ let resolved;
1590
+ if (binding.includes("{")) {
1591
+ resolved = evaluateTemplate(binding, ctx);
1592
+ } else {
1593
+ try {
1594
+ resolved = safeEval(binding, ctx);
1595
+ } catch {
1596
+ resolved = void 0;
1597
+ }
1598
+ if (resolved == null || typeof resolved === "object") {
1599
+ resolved = getByPath(data, binding);
1600
+ }
1601
+ }
1602
+ if (resolved != null && typeof resolved !== "object" && String(resolved) !== binding) {
1603
+ text = String(resolved);
1604
+ } else {
1605
+ text = wm.testData ?? `[${wm.binding}]`;
1606
+ }
1607
+ }
1608
+ if (wm.timestamp && text) {
1609
+ text = `${text} ${formatTimestamp(wm.format)}`;
1610
+ }
1611
+ return text;
1612
+ }
1613
+ function pad(n) {
1614
+ return String(n).padStart(2, "0");
1615
+ }
1616
+ function formatTimestamp(format) {
1617
+ const fmt = format && format.trim() ? format : "YYYY-MM-DD HH:mm";
1618
+ const d = /* @__PURE__ */ new Date();
1619
+ const map = {
1620
+ YYYY: String(d.getFullYear()),
1621
+ MM: pad(d.getMonth() + 1),
1622
+ DD: pad(d.getDate()),
1623
+ HH: pad(d.getHours()),
1624
+ mm: pad(d.getMinutes()),
1625
+ ss: pad(d.getSeconds())
1626
+ };
1627
+ return fmt.replace(/YYYY|MM|DD|HH|mm|ss/g, (k) => map[k] ?? k);
1628
+ }
1629
+ function round4(n) {
1630
+ return Math.round(n * 1e4) / 1e4;
1631
+ }
1632
+ function escXml(text) {
1633
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1634
+ }
1635
+ function resolveWatermarkLayout(wm, printData, paperMm, systemVars2) {
1636
+ if (!isWatermarkVisible(wm)) return null;
1637
+ const text = resolveWatermarkText(wm, printData, systemVars2);
1638
+ if (!text) return null;
1639
+ const tile = resolveTileSize(wm);
1640
+ const tileWidthMm = round4(tile.width * MM_PER_PX);
1641
+ const tileHeightMm = round4(tile.height * MM_PER_PX);
1642
+ const paper = {
1643
+ width: Math.max(0, Number.isFinite(paperMm?.width) ? paperMm.width : 0),
1644
+ height: Math.max(0, Number.isFinite(paperMm?.height) ? paperMm.height : 0)
1645
+ };
1646
+ const columns = Math.max(1, Math.ceil(paper.width / tileWidthMm));
1647
+ const rows = Math.max(1, Math.ceil(paper.height / tileHeightMm));
1648
+ const tiles = [];
1649
+ for (let r = 0; r < rows; r++) {
1650
+ for (let c = 0; c < columns; c++) {
1651
+ tiles.push({
1652
+ leftMm: round4(c * tileWidthMm),
1653
+ topMm: round4(r * tileHeightMm),
1654
+ widthMm: tileWidthMm,
1655
+ heightMm: tileHeightMm
1656
+ });
1657
+ }
1658
+ }
1659
+ return {
1660
+ text,
1661
+ color: wm?.color || WATERMARK_DEFAULTS.color,
1662
+ rotate: wm?.rotate ?? WATERMARK_DEFAULTS.rotate,
1663
+ opacity: wm?.opacity ?? WATERMARK_DEFAULTS.opacity,
1664
+ fontSizePx: WATERMARK_DEFAULTS.fontSize,
1665
+ tileWidthPx: tile.width,
1666
+ tileHeightPx: tile.height,
1667
+ tileWidthMm,
1668
+ tileHeightMm,
1669
+ columns,
1670
+ rows,
1671
+ tiles
1672
+ };
1673
+ }
1674
+ function renderWatermarkTileSvg(layout, tile) {
1675
+ const tileWidthPx = layout.tileWidthPx;
1676
+ const tileHeightPx = layout.tileHeightPx;
1677
+ const cx = tileWidthPx / 2;
1678
+ const cy = tileHeightPx / 2;
1679
+ const style = `left:${tile.leftMm}mm;top:${tile.topMm}mm;width:${tile.widthMm}mm;height:${tile.heightMm}mm`;
1680
+ return `<svg class="watermark-tile" style="${style}" viewBox="0 0 ${tileWidthPx} ${tileHeightPx}" xmlns="http://www.w3.org/2000/svg"><text x="${cx}" y="${cy}" font-size="${layout.fontSizePx}" fill="${layout.color}" text-anchor="middle" dominant-baseline="middle" transform="rotate(${layout.rotate},${cx},${cy})">${escXml(layout.text)}</text></svg>`;
1681
+ }
1682
+ function renderWatermarkLayerHtml(wm, printData, paperMm, systemVars2) {
1683
+ const layout = resolveWatermarkLayout(wm, printData, paperMm, systemVars2);
1684
+ if (!layout) return "";
1685
+ const tiles = layout.tiles.map((t) => renderWatermarkTileSvg(layout, t)).join("\n");
1686
+ return `
1687
+ <div class="watermark-layer" style="opacity:${layout.opacity}">
1688
+ ${tiles}
1689
+ </div>`;
1690
+ }
1691
+
1117
1692
  // src/render/pagination-engine.ts
1118
1693
  function paginationOf(el) {
1119
1694
  return el.options?.pagination ?? el.pagination;
@@ -1134,7 +1709,7 @@ function tableDesignBottom(el) {
1134
1709
  }
1135
1710
  return top + (opts.height ?? 0);
1136
1711
  }
1137
- function buildFollowMap(sorted) {
1712
+ function buildFollowMap(sorted, excludedIds) {
1138
1713
  const map = /* @__PURE__ */ new Map();
1139
1714
  let currentTableId = null;
1140
1715
  let currentTableBottom = -1;
@@ -1145,6 +1720,7 @@ function buildFollowMap(sorted) {
1145
1720
  map.set(el.id, []);
1146
1721
  continue;
1147
1722
  }
1723
+ if (excludedIds?.has(el.id)) continue;
1148
1724
  const top = el.options?.top ?? 0;
1149
1725
  if (currentTableId && top >= currentTableBottom) {
1150
1726
  map.get(currentTableId).push(el.id);
@@ -1162,28 +1738,110 @@ function followGroupHeight(el, members, template, _measuredElements) {
1162
1738
  }
1163
1739
  return Math.max(maxBottom - tableBottom, 0);
1164
1740
  }
1741
+ function effectiveHeight(el, measured) {
1742
+ return Math.max(el.options?.height ?? 0, measured.get(el.id)?.measuredHeight ?? 0);
1743
+ }
1744
+ function buildPaginationUnits(sorted, measured, groupMap) {
1745
+ const unitOf = /* @__PURE__ */ new Map();
1746
+ for (const members of groupMap.values()) {
1747
+ if (members.length < 2) continue;
1748
+ const unit = {
1749
+ anchorId: members[0].id,
1750
+ ids: members.map((m) => m.id),
1751
+ forceFirstPage: members.some((m) => paginationOf(m)?.pageable === false)
1752
+ };
1753
+ for (const m of members) unitOf.set(m.id, unit);
1754
+ }
1755
+ const eligible = sorted.filter(
1756
+ (el) => !isTableEl(el) && !el.options?.groupId && paginationOf(el)?.pageable !== false
1757
+ );
1758
+ const parent = /* @__PURE__ */ new Map();
1759
+ eligible.forEach((el) => parent.set(el.id, el.id));
1760
+ const find = (id) => {
1761
+ let root = id;
1762
+ while (parent.get(root) !== root) root = parent.get(root);
1763
+ let cur = id;
1764
+ while (parent.get(cur) !== root) {
1765
+ const next = parent.get(cur);
1766
+ parent.set(cur, root);
1767
+ cur = next;
1768
+ }
1769
+ return root;
1770
+ };
1771
+ const union = (a, b) => {
1772
+ const ra = find(a);
1773
+ const rb = find(b);
1774
+ if (ra !== rb) parent.set(rb, ra);
1775
+ };
1776
+ const active = [];
1777
+ for (const el of eligible) {
1778
+ const top = el.options?.top ?? 0;
1779
+ for (let i = active.length - 1; i >= 0; i--) {
1780
+ if (active[i].bottom <= top) active.splice(i, 1);
1781
+ }
1782
+ const bottom = top + effectiveHeight(el, measured);
1783
+ for (const a of active) {
1784
+ if (a.bottom > top) union(el.id, a.id);
1785
+ }
1786
+ active.push({ id: el.id, top, bottom });
1787
+ }
1788
+ const clusters = /* @__PURE__ */ new Map();
1789
+ for (const el of eligible) {
1790
+ const root = find(el.id);
1791
+ const arr = clusters.get(root);
1792
+ if (arr) arr.push(el.id);
1793
+ else clusters.set(root, [el.id]);
1794
+ }
1795
+ for (const ids of clusters.values()) {
1796
+ if (ids.length < 2) continue;
1797
+ const unit = { anchorId: ids[0], ids, forceFirstPage: false };
1798
+ for (const id of ids) unitOf.set(id, unit);
1799
+ }
1800
+ return unitOf;
1801
+ }
1165
1802
  function paginate(template, measuredElements) {
1166
1803
  const paper = getPaperDimensions(template);
1167
1804
  const { top: mt, bottom: mb } = template.margins;
1168
1805
  const headerH = template.header?.height ?? 0;
1169
1806
  const footerH = template.footer?.height ?? 0;
1170
1807
  const overlayH = template.firstPageOverlay?.height ?? 0;
1171
- const contentHeight = paper.height - mt - mb - headerH - footerH;
1172
- if (contentHeight <= 0) {
1808
+ const continuous = isContinuousPaper(template);
1809
+ const contentHeight = continuous ? Number.POSITIVE_INFINITY : paper.height - mt - mb - headerH - footerH;
1810
+ if (!continuous && contentHeight <= 0) {
1173
1811
  throw new Error(
1174
1812
  `\u9875\u9762\u53EF\u7528\u9AD8\u5EA6\u4E0D\u8DB3: paper=${paper.height}mm, margins=${mt + mb}mm, header=${headerH}mm, footer=${footerH}mm`
1175
1813
  );
1176
1814
  }
1815
+ const orderIndex = /* @__PURE__ */ new Map();
1816
+ template.elements.forEach((e, idx) => orderIndex.set(e.id, idx));
1177
1817
  const sorted = [...template.elements].sort((a, b) => {
1178
1818
  const topA = a.options?.top ?? 0;
1179
1819
  const topB = b.options?.top ?? 0;
1180
- return topA - topB;
1820
+ if (topA !== topB) return topA - topB;
1821
+ const zA = a.options?.zIndex ?? 0;
1822
+ const zB = b.options?.zIndex ?? 0;
1823
+ if (zA !== zB) return zA - zB;
1824
+ return (orderIndex.get(a.id) ?? 0) - (orderIndex.get(b.id) ?? 0);
1181
1825
  });
1182
- const followMap = buildFollowMap(sorted);
1826
+ const explicitGroupIds = /* @__PURE__ */ new Set();
1827
+ const groupMap = /* @__PURE__ */ new Map();
1828
+ for (const el of sorted) {
1829
+ if (isTableEl(el)) continue;
1830
+ const gid = el.options?.groupId;
1831
+ if (!gid) continue;
1832
+ explicitGroupIds.add(el.id);
1833
+ const arr = groupMap.get(gid);
1834
+ if (arr) arr.push(el);
1835
+ else groupMap.set(gid, [el]);
1836
+ }
1837
+ const followMap = buildFollowMap(sorted, explicitGroupIds);
1183
1838
  const followOwner = /* @__PURE__ */ new Map();
1184
1839
  for (const [tableId, members] of followMap) {
1185
1840
  for (const m of members) followOwner.set(m, tableId);
1186
1841
  }
1842
+ const elById = /* @__PURE__ */ new Map();
1843
+ for (const el of sorted) elById.set(el.id, el);
1844
+ const unitOf = buildPaginationUnits(sorted, measuredElements, groupMap);
1187
1845
  const pages = [];
1188
1846
  let currentPage = [];
1189
1847
  let remaining = contentHeight - overlayH - SAFETY_MARGIN;
@@ -1195,12 +1853,26 @@ function paginate(template, measuredElements) {
1195
1853
  function fullPageHeight() {
1196
1854
  return isFirstPage ? contentHeight - overlayH - SAFETY_MARGIN : contentHeight - SAFETY_MARGIN;
1197
1855
  }
1198
- function finishPage() {
1199
- pages.push({ pageIndex: pages.length, sections: [...currentPage] });
1856
+ let overflowOnCurrent = false;
1857
+ function pushPage() {
1858
+ pages.push(
1859
+ overflowOnCurrent ? { pageIndex: pages.length, sections: [...currentPage], overflow: true } : { pageIndex: pages.length, sections: [...currentPage] }
1860
+ );
1861
+ }
1862
+ function noteOverflow(bottom) {
1863
+ if (bottom > contentHeight) overflowOnCurrent = true;
1864
+ }
1865
+ function finishPage(overflow = false) {
1866
+ if (currentPage.length === 0) {
1867
+ overflowOnCurrent = overflowOnCurrent || overflow;
1868
+ return;
1869
+ }
1870
+ pushPage();
1200
1871
  currentPage = [];
1201
1872
  remaining = contentHeight - SAFETY_MARGIN;
1202
1873
  isFirstPage = false;
1203
1874
  pageBroken = true;
1875
+ overflowOnCurrent = false;
1204
1876
  }
1205
1877
  let i = 0;
1206
1878
  while (i < sorted.length) {
@@ -1209,19 +1881,32 @@ function paginate(template, measuredElements) {
1209
1881
  i++;
1210
1882
  continue;
1211
1883
  }
1212
- if (paginationOf(el)?.pageable === false) {
1213
- currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
1884
+ const unit = unitOf.get(el.id);
1885
+ if (unit && unit.anchorId !== el.id) {
1886
+ i++;
1887
+ continue;
1888
+ }
1889
+ if (paginationOf(el)?.pageable === false || unit?.forceFirstPage) {
1890
+ if (unit) {
1891
+ for (const id of unit.ids) {
1892
+ currentPage.push({ elementId: id, type: "element", renderTop: elById.get(id)?.options?.top ?? 0 });
1893
+ }
1894
+ } else {
1895
+ currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
1896
+ }
1214
1897
  i++;
1215
1898
  continue;
1216
1899
  }
1217
1900
  if (isTableEl(el)) {
1218
1901
  i = paginateTable(el, measuredElements, i);
1902
+ } else if (unit) {
1903
+ i = paginateUnit(unit, i);
1219
1904
  } else {
1220
1905
  i = paginateNonTable(el, measuredElements, sorted, i);
1221
1906
  }
1222
1907
  }
1223
1908
  if (currentPage.length > 0) {
1224
- pages.push({ pageIndex: pages.length, sections: [...currentPage] });
1909
+ pushPage();
1225
1910
  }
1226
1911
  if (pages.length === 0) {
1227
1912
  pages.push({ pageIndex: 0, sections: [] });
@@ -1230,6 +1915,8 @@ function paginate(template, measuredElements) {
1230
1915
  function paginateNonTable(el, measured, sortedList, idx) {
1231
1916
  const elHeight = measured.get(el.id)?.measuredHeight ?? el.options?.height ?? 0;
1232
1917
  if (elHeight <= remaining) {
1918
+ const top2 = pageBroken ? 0 : el.options?.top ?? 0;
1919
+ noteOverflow(top2 + elHeight);
1233
1920
  currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
1234
1921
  remaining -= elHeight;
1235
1922
  if (paginationOf(el)?.keepWithNext && idx + 1 < sortedList.length) {
@@ -1247,11 +1934,43 @@ function paginate(template, measuredElements) {
1247
1934
  }
1248
1935
  return idx + 1;
1249
1936
  }
1250
- finishPage();
1937
+ const top = pageBroken ? 0 : el.options?.top ?? 0;
1938
+ finishPage(top + elHeight > contentHeight);
1251
1939
  currentPage.push({ elementId: el.id, type: "element", renderTop: sectionTop(el) });
1252
1940
  remaining -= elHeight;
1253
1941
  return idx + 1;
1254
1942
  }
1943
+ function paginateUnit(unit, idx) {
1944
+ const members = unit.ids.map((id) => elById.get(id)).filter((m) => !!m);
1945
+ let minTop = Number.POSITIVE_INFINITY;
1946
+ let maxBottom = Number.NEGATIVE_INFINITY;
1947
+ for (const m of members) {
1948
+ const top2 = m.options?.top ?? 0;
1949
+ minTop = Math.min(minTop, top2);
1950
+ maxBottom = Math.max(maxBottom, top2 + effectiveHeight(m, measuredElements));
1951
+ }
1952
+ const unitHeight = Math.max(maxBottom - minTop, 0);
1953
+ const place = () => {
1954
+ const offset = pageBroken ? -minTop : 0;
1955
+ noteOverflow(minTop + offset + unitHeight);
1956
+ for (const m of members) {
1957
+ currentPage.push({
1958
+ elementId: m.id,
1959
+ type: "element",
1960
+ renderTop: (m.options?.top ?? 0) + offset
1961
+ });
1962
+ }
1963
+ remaining -= unitHeight;
1964
+ };
1965
+ if (unitHeight <= remaining) {
1966
+ place();
1967
+ return idx + 1;
1968
+ }
1969
+ const top = pageBroken ? 0 : minTop;
1970
+ finishPage(top + unitHeight > contentHeight);
1971
+ place();
1972
+ return idx + 1;
1973
+ }
1255
1974
  function paginateTable(el, measured, idx) {
1256
1975
  const opts = el.options ?? {};
1257
1976
  const renderRows = opts._renderRows ?? [];
@@ -1417,18 +2136,28 @@ function specialRowHeight(renderRows, rowHeights, type) {
1417
2136
  return h;
1418
2137
  }
1419
2138
 
2139
+ // src/render/barcode-dot.ts
2140
+ function barcodeAvailableBoxMm(boxMm, maxMm) {
2141
+ const box = typeof boxMm === "number" && boxMm > 0 ? boxMm : Number.POSITIVE_INFINITY;
2142
+ const limit = typeof maxMm === "number" && maxMm > 0 ? Math.min(box, maxMm) : box;
2143
+ return Number.isFinite(limit) ? limit : 0;
2144
+ }
2145
+
1420
2146
  // src/render/html-generator.ts
1421
2147
  function generateHtml(template, pageLayouts, printData, options) {
1422
- const css = buildPageCss(template);
2148
+ const css = buildFontFaceCss(template.fonts) + buildPageCss(template, options?.pageHeightMm);
1423
2149
  const isMeasure = options?.isMeasurementPass === true;
1424
2150
  const totalPages = isMeasure ? 1 : pageLayouts.length;
1425
- const ctx = { codeRenderer: options?.codeRenderer };
2151
+ const ctx = {
2152
+ codeRenderer: options?.codeRenderer,
2153
+ pageHeightMm: options?.pageHeightMm
2154
+ };
1426
2155
  if (isMeasure) {
1427
- return generateMeasurementHtml(template, css, ctx);
2156
+ return generateMeasurementHtml(template, css, ctx, printData);
1428
2157
  }
1429
- return generateFinalHtml(template, pageLayouts, css, totalPages, ctx);
2158
+ return generateFinalHtml(template, pageLayouts, css, totalPages, ctx, printData);
1430
2159
  }
1431
- function generateMeasurementHtml(template, css, ctx) {
2160
+ function generateMeasurementHtml(template, css, ctx, printData) {
1432
2161
  const paper = getPaperDims(template);
1433
2162
  const contentWidth = paper.width - template.margins.left - template.margins.right;
1434
2163
  const elementsHtml = template.elements.map((el) => renderElement(el, true, void 0, void 0, ctx)).join("\n");
@@ -1450,6 +2179,7 @@ function generateMeasurementHtml(template, css, ctx) {
1450
2179
  </head>
1451
2180
  <body class="measure-mode">
1452
2181
  <section class="print-page" data-measure-page="0">
2182
+ ${renderWatermarkLayerHtml(template.watermark, printData, getPaperDims(template))}
1453
2183
  <div class="page-header">${headerHtml}</div>
1454
2184
  <div class="first-page-overlay">${overlayHtml}</div>
1455
2185
  <div class="content-area" style="height:auto;overflow:visible;">
@@ -1461,26 +2191,42 @@ function generateMeasurementHtml(template, css, ctx) {
1461
2191
  html = injectSystemVariables(html);
1462
2192
  return html;
1463
2193
  }
1464
- function generateFinalHtml(template, pageLayouts, css, totalPages, ctx) {
2194
+ function renderFinalPages(template, pageLayouts, printData, ctx, options) {
2195
+ const pageOffset = options?.pageOffset ?? 0;
2196
+ const totalPages = options?.totalPages ?? pageLayouts.length;
1465
2197
  const pagesHtml = pageLayouts.map((page) => {
1466
- const pageNum = page.pageIndex + 1;
1467
- return renderPage(template, page, pageNum, totalPages, ctx);
2198
+ const pageNum = page.pageIndex + 1 + pageOffset;
2199
+ return renderPage(template, page, pageNum, totalPages, ctx, printData, options?.pageClass);
1468
2200
  }).join("\n");
1469
- let html = `<!DOCTYPE html>
2201
+ return injectSystemVariables(pagesHtml);
2202
+ }
2203
+ function wrapHtmlDocument(css, bodyInnerHtml, bodyClass) {
2204
+ const html = `<!DOCTYPE html>
1470
2205
  <html lang="zh-CN">
1471
2206
  <head>
1472
2207
  <meta charset="UTF-8">
1473
2208
  <style>${css}</style>
1474
2209
  </head>
1475
- <body>
1476
- ${pagesHtml}
2210
+ <body${bodyClass ? ` class="${bodyClass}"` : ""}>
2211
+ ${bodyInnerHtml}
1477
2212
  </body>
1478
2213
  </html>`;
1479
- html = injectSystemVariables(html);
1480
- return html;
2214
+ return injectSystemVariables(html);
2215
+ }
2216
+ function generateFinalHtml(template, pageLayouts, css, _totalPages2, ctx, printData) {
2217
+ const bodyInner = renderFinalPages(template, pageLayouts, printData ?? {}, ctx);
2218
+ return wrapHtmlDocument(
2219
+ css,
2220
+ bodyInner,
2221
+ isContinuousPaper(template) ? "continuous" : void 0
2222
+ );
1481
2223
  }
1482
- function renderPage(template, page, pageNum, totalPages, ctx) {
2224
+ function renderPage(template, page, pageNum, totalPages, ctx, printData, pageClass) {
1483
2225
  const paper = getPaperDims(template);
2226
+ const paperMm = {
2227
+ width: paper.width,
2228
+ height: ctx.pageHeightMm && ctx.pageHeightMm > 0 ? ctx.pageHeightMm : paper.height
2229
+ };
1484
2230
  const contentWidth = paper.width - template.margins.left - template.margins.right;
1485
2231
  const headerHtml = renderAreaElements(
1486
2232
  template.header?.elements ?? [],
@@ -1500,13 +2246,18 @@ function renderPage(template, page, pageNum, totalPages, ctx) {
1500
2246
  let contentHtml = page.sections.map((section) => renderSection(section, template, ctx)).join("\n");
1501
2247
  contentHtml = contentHtml.replace(/\{pageIndex\}/g, String(pageNum));
1502
2248
  contentHtml = contentHtml.replace(/\{totalPages\}/g, String(totalPages));
1503
- return `<section class="print-page" data-page="${pageNum}">
2249
+ const pageInner = `
2250
+ ${renderWatermarkLayerHtml(template.watermark, printData, paperMm, { pageIndex: pageNum, totalPages })}
1504
2251
  <div class="page-header">${headerHtml}</div>
1505
2252
  ${overlayHtml}
1506
2253
  <div class="content-area">
1507
2254
  ${contentHtml}
1508
2255
  </div>
1509
- <div class="page-footer">${footerHtml}</div>
2256
+ <div class="page-footer">${footerHtml}</div>`;
2257
+ const rot = getOutputRotationAngle(template);
2258
+ const pageBody = rot !== 0 ? `<div class="print-page-rotor print-page-rotor-${rot}">${pageInner}</div>` : pageInner;
2259
+ return `<section class="print-page${pageClass ? ` ${pageClass}` : ""}" data-page="${pageNum}">
2260
+ ${pageBody}
1510
2261
  </section>`;
1511
2262
  }
1512
2263
  function renderSection(section, template, ctx) {
@@ -1526,8 +2277,9 @@ var V_ALIGN_FLEX = { top: "flex-start", middle: "center", bottom: "flex-end" };
1526
2277
  var H_ALIGN_FLEX = { left: "flex-start", center: "center", right: "flex-end" };
1527
2278
  function textStyle(opts) {
1528
2279
  const parts = [];
1529
- if (opts.fontSize) parts.push(`font-size:${opts.fontSize}pt`);
1530
- if (opts.fontFamily) parts.push(`font-family:${opts.fontFamily}`);
2280
+ const fontSize = opts._fitFontSize === void 0 ? opts.fontSize : roundFontSize(opts._fitFontSize);
2281
+ if (fontSize) parts.push(`font-size:${fontSize}pt`);
2282
+ if (opts.fontFamily) parts.push(`font-family:${escapeInlineStyleValue(toFontFamilyStack(opts.fontFamily))}`);
1531
2283
  if (opts.fontWeight) parts.push(`font-weight:${opts.fontWeight}`);
1532
2284
  if (opts.color) parts.push(`color:${opts.color}`);
1533
2285
  if (opts.backgroundColor) parts.push(`background-color:${opts.backgroundColor}`);
@@ -1540,15 +2292,28 @@ function textStyle(opts) {
1540
2292
  if (opts.textAlign) parts.push(`text-align:${opts.textAlign}`);
1541
2293
  return parts.length ? parts.join(";") + ";" : "";
1542
2294
  }
2295
+ function elementFitStyle(fit, opts) {
2296
+ if (fit === "autoHeight") return "overflow:visible;";
2297
+ if (opts.wordWrap === false) return "white-space:nowrap;text-overflow:ellipsis;";
2298
+ return "";
2299
+ }
2300
+ function fitAttrs(fit, key, baseFontSizePt, opts) {
2301
+ if (fit !== "shrink") return "";
2302
+ const min2 = resolveShrinkMinFontSize(opts.shrinkMinFontSize);
2303
+ return ` data-fit="shrink" data-fit-key="${esc(key)}" data-fit-base="${baseFontSizePt}" data-fit-min="${min2}"`;
2304
+ }
1543
2305
  function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
1544
2306
  const opts = el.options ?? {};
1545
2307
  const left = opts.left ?? 0;
1546
2308
  const top = overrideTop ?? opts.top ?? 0;
1547
2309
  const width = opts.width ?? 100;
1548
2310
  const height = opts.height ?? void 0;
1549
- const style = containerStyle ?? elementPositionStyle(left, top, width, height, opts.zIndex);
1550
- const measureAttr = isMeasure ? ` data-measure-id="${el.id}"` : "";
1551
2311
  const type = el.type || el.printElementType?.type || "text";
2312
+ const fit = resolveElementTextFit(type, opts);
2313
+ const fitHeight = fit === "autoHeight" ? void 0 : height;
2314
+ const style = containerStyle ?? elementPositionStyle(left, top, width, fitHeight, opts.zIndex);
2315
+ const measureAttr = isMeasure ? ` data-measure-id="${el.id}"` : "";
2316
+ const fitAttr = fitAttrs(fit, el.id, opts.fontSize ?? 12, opts);
1552
2317
  switch (type) {
1553
2318
  case "table":
1554
2319
  return renderTableElement(el, isMeasure, measureAttr, ctx);
@@ -1559,9 +2324,17 @@ function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
1559
2324
  case "barcode":
1560
2325
  case "qrcode": {
1561
2326
  const codeValue = String(opts.formatter ?? opts.testData ?? "").trim();
1562
- const fill = type === "barcode";
1563
2327
  return `<div class="print-element" style="${style}"${measureAttr}>
1564
- ${codeImgHtml(codeValue, type, opts, `<span>${esc(codeValue)}</span>`, fill, ctx?.codeRenderer)}
2328
+ ${codeImgHtml(codeValue, type, opts, {
2329
+ fallback: `<span>${esc(codeValue)}</span>`,
2330
+ codeRenderer: ctx?.codeRenderer,
2331
+ fit: opts.fit,
2332
+ maxWidth: opts.maxWidth,
2333
+ maxHeight: opts.maxHeight,
2334
+ printerDpi: opts.printerDpi,
2335
+ targetWidthMm: opts.width,
2336
+ targetHeightMm: opts.height
2337
+ })}
1565
2338
  </div>`;
1566
2339
  }
1567
2340
  case "hline":
@@ -1573,30 +2346,32 @@ function renderElement(el, isMeasure, containerStyle, overrideTop, ctx) {
1573
2346
  case "oval":
1574
2347
  return `<div class="print-element" style="${style};border:${opts.borderWidth ?? 1}px solid ${opts.borderColor ?? "#000"};border-radius:50%;"${measureAttr}></div>`;
1575
2348
  case "longText":
1576
- return `<div class="print-element" style="${style}${textStyle(opts)}overflow:visible;"${measureAttr}>${esc(opts.formatter ?? opts.testData ?? "")}</div>`;
2349
+ return `<div class="print-element" style="${style}${textStyle(opts)}${elementFitStyle(fit, opts)}"${measureAttr}${fitAttr}>${esc(opts.formatter ?? opts.testData ?? "")}</div>`;
1577
2350
  case "html":
1578
2351
  return `<div class="print-element" style="${style}"${measureAttr}>${opts.testData ?? opts.title ?? ""}</div>`;
1579
2352
  default:
1580
- return `<div class="print-element" style="${style}${textStyle(opts)}"${measureAttr}>${esc(opts.formatter ?? opts.testData ?? "")}</div>`;
2353
+ return `<div class="print-element" style="${style}${textStyle(opts)}${elementFitStyle(fit, opts)}"${measureAttr}${fitAttr}>${esc(opts.formatter ?? opts.testData ?? "")}</div>`;
1581
2354
  }
1582
2355
  }
1583
- function codeImgHtml(value, cellType, opts, fallbackHtml, fill = false, codeRenderer, fit, maxWidth, maxHeight) {
1584
- if (!value || !codeRenderer) return fallbackHtml;
2356
+ function codeImgHtml(value, cellType, opts, io) {
2357
+ if (!value || !io.codeRenderer) return io.fallback;
2358
+ const { fallback, codeRenderer, fit, maxWidth, maxHeight } = io;
2359
+ const isBarcode = cellType === "barcode";
1585
2360
  try {
1586
2361
  const svg = codeRenderer.render(value, cellType, {
1587
2362
  barcodeType: opts.barcodeType,
1588
2363
  qrCodeLevel: opts.qrCodeLevel != null ? String(opts.qrCodeLevel) : void 0,
1589
2364
  showText: opts.hideTitle !== void 0 ? !opts.hideTitle : opts.showBarcodeText,
1590
2365
  barWidth: typeof opts.barWidth === "number" ? opts.barWidth : void 0,
1591
- fontSize: typeof opts.fontSize === "number" ? opts.fontSize : void 0
2366
+ fontSize: typeof opts.fontSize === "number" ? opts.fontSize : void 0,
2367
+ printerDpi: isBarcode ? io.printerDpi : void 0,
2368
+ targetWidthMm: isBarcode ? barcodeAvailableBoxMm(io.targetWidthMm, maxWidth) : void 0,
2369
+ targetHeightMm: isBarcode ? barcodeAvailableBoxMm(io.targetHeightMm, maxHeight) : void 0
1592
2370
  });
2371
+ if (isBarcode && /^<svg[\s>]/i.test(svg)) return inlineCodeSvgHtml(svg);
1593
2372
  const src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
1594
2373
  const styleParts = [];
1595
- if (fill) {
1596
- styleParts.push("width:100%", "height:100%", "object-fit:contain", "display:block", "margin:auto");
1597
- } else {
1598
- styleParts.push("max-width:100%", "max-height:100%", "display:block", "margin:auto");
1599
- }
2374
+ styleParts.push("max-width:100%", "max-height:100%", "display:block", "margin:auto");
1600
2375
  if (fit) {
1601
2376
  styleParts.push(`object-fit:${fit}`);
1602
2377
  }
@@ -1609,15 +2384,20 @@ function codeImgHtml(value, cellType, opts, fallbackHtml, fill = false, codeRend
1609
2384
  const style = styleParts.join(";");
1610
2385
  return `<img src="${src}" style="${style}" />`;
1611
2386
  } catch {
1612
- return fallbackHtml;
2387
+ return fallback;
1613
2388
  }
1614
2389
  }
2390
+ function inlineCodeSvgHtml(svg) {
2391
+ const styled = svg.replace(/<svg\b/, '<svg style="display:block"');
2392
+ return `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;">${styled}</div>`;
2393
+ }
1615
2394
  function matrixCellStyle(cell, opts) {
1616
2395
  const parts = [];
1617
- const fontSize = cell.fontSize ?? opts.tableDefaultFontSize;
2396
+ const fontSize = cell.fittedFontSize ?? cell.fontSize ?? opts.tableDefaultFontSize;
1618
2397
  const color = cell.color ?? opts.tableDefaultColor;
1619
2398
  const padding = cell.padding ?? opts.tableDefaultPadding ?? 1;
1620
2399
  if (fontSize) parts.push(`font-size:${fontSize}pt`);
2400
+ if (cell.fontFamily) parts.push(`font-family:${escapeInlineStyleValue(toFontFamilyStack(cell.fontFamily))}`);
1621
2401
  if (cell.fontWeight) parts.push(`font-weight:${cell.fontWeight}`);
1622
2402
  if (color) parts.push(`color:${color}`);
1623
2403
  if (cell.backgroundColor) parts.push(`background-color:${cell.backgroundColor}`);
@@ -1632,25 +2412,36 @@ function matrixCellStyle(cell, opts) {
1632
2412
  }
1633
2413
  return parts.join(";");
1634
2414
  }
1635
- function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx) {
2415
+ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx, fitOwner = { elementId: "", kind: "b" }) {
1636
2416
  const trs = [];
2417
+ const defaultPadding = opts.tableDefaultPadding ?? 1;
1637
2418
  for (let r = start; r < end; r++) {
1638
2419
  const row = renderRows[r];
1639
2420
  if (!row) continue;
1640
2421
  const idxAttr = withRowIndex ? ` data-row-index="${r}"` : "";
1641
- const tds = row.cells.filter((cell) => !cell.merged).map((cell) => {
2422
+ const tds = row.cells.map((cell, ci) => ({ cell, ci })).filter(({ cell }) => !cell.merged).map(({ cell, ci }) => {
1642
2423
  const span = `${cell.rowspan > 1 ? ` rowspan="${cell.rowspan}"` : ""}${cell.colspan > 1 ? ` colspan="${cell.colspan}"` : ""}`;
2424
+ const colIndex = ci;
1643
2425
  let inner;
1644
2426
  if (cell.cellType === "barcode" || cell.cellType === "qrcode") {
1645
- const cellFill = cell.cellType === "barcode";
1646
- inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;">${codeImgHtml(cell.content, cell.cellType, cell, esc(cell.content), cellFill, ctx?.codeRenderer, cell.fit, cell.maxWidth, cell.maxHeight)}</div>`;
2427
+ inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;">${codeImgHtml(cell.content, cell.cellType, cell, {
2428
+ fallback: esc(cell.content),
2429
+ codeRenderer: ctx?.codeRenderer,
2430
+ fit: cell.fit,
2431
+ maxWidth: cell.maxWidth,
2432
+ maxHeight: cell.maxHeight,
2433
+ printerDpi: cell.printerDpi,
2434
+ // 可用框 = 单元格内容区(列宽/行高扣除内边距与塌陷边框);列宽缺失时宽度为 0 → 不约束宽度
2435
+ targetWidthMm: cellFitWidthMm(opts.tableColWidths ?? [], ci, cell, defaultPadding),
2436
+ targetHeightMm: cellFitCapMm(renderRows, r, cell, defaultPadding)
2437
+ })}</div>`;
1647
2438
  } else if (cell.cellType === "image") {
1648
2439
  const fit = cell.fit || "contain";
1649
2440
  const maxWidth = cell.maxWidth ? `max-width:${cell.maxWidth}mm;` : "max-width:100%;";
1650
2441
  const maxHeight = cell.maxHeight ? `max-height:${cell.maxHeight}mm;` : "max-height:100%;";
1651
2442
  inner = `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden;"><img src="${esc(cell.content)}" style="object-fit:${fit};${maxWidth}${maxHeight}display:block;" /></div>`;
1652
2443
  } else {
1653
- inner = esc(cell.content);
2444
+ inner = renderTextCellInner(renderRows, r, colIndex, cell, opts, fitOwner, defaultPadding);
1654
2445
  }
1655
2446
  return `<td${span} style="${matrixCellStyle(cell, opts)}">${inner}</td>`;
1656
2447
  }).join("");
@@ -1658,6 +2449,20 @@ function renderMatrixRows(renderRows, start, end, opts, withRowIndex, ctx) {
1658
2449
  }
1659
2450
  return trs.join("\n");
1660
2451
  }
2452
+ function renderTextCellInner(renderRows, rowIndex, colIndex, cell, opts, fitOwner, defaultPadding) {
2453
+ const fit = resolveCellTextFit(cell);
2454
+ const text = esc(cell.content);
2455
+ if (fit === "autoHeight") return text;
2456
+ const capMm = cellFitCapMm(renderRows, rowIndex, cell, defaultPadding);
2457
+ const nowrap = cell.wordWrap === false;
2458
+ const style = [
2459
+ `max-height:${capMm}mm`,
2460
+ "overflow:hidden",
2461
+ ...nowrap ? ["white-space:nowrap", "text-overflow:ellipsis"] : []
2462
+ ].join(";");
2463
+ const attrs = fit === "shrink" && fitOwner.elementId ? ` data-fit="shrink" data-fit-key="${esc(cellFitKey(fitOwner.elementId, fitOwner.kind, rowIndex, colIndex))}" data-fit-base="${cell.fontSize ?? opts.tableDefaultFontSize ?? 12}" data-fit-min="${resolveShrinkMinFontSize(cell.shrinkMinFontSize)}" data-fit-mm="${capMm}"` : "";
2464
+ return `<div class="cell-fit" style="${style}"${attrs}>${text}</div>`;
2465
+ }
1661
2466
  function matrixTableHtml(el, bodyHtml) {
1662
2467
  const opts = el.options;
1663
2468
  const colWidths = opts.tableColWidths ?? [];
@@ -1672,7 +2477,7 @@ function renderTableElement(el, isMeasure, measureAttr, ctx) {
1672
2477
  const opts = el.options;
1673
2478
  const style = elementPositionStyle(opts.left ?? 0, opts.top ?? 0, opts.width ?? 100, void 0, opts.zIndex);
1674
2479
  const renderRows = opts._renderRows ?? [];
1675
- const bodyHtml = renderMatrixRows(renderRows, 0, renderRows.length, opts, isMeasure, ctx);
2480
+ const bodyHtml = renderMatrixRows(renderRows, 0, renderRows.length, opts, isMeasure, ctx, { elementId: el.id, kind: "b" });
1676
2481
  return `<div class="print-element" style="${style};overflow:visible;"${measureAttr}>
1677
2482
  ${matrixTableHtml(el, bodyHtml)}
1678
2483
  </div>`;
@@ -1684,8 +2489,8 @@ function renderTableSlice(el, section, ctx) {
1684
2489
  const startRow = section.startRow ?? 0;
1685
2490
  const endRow = section.endRow ?? renderRows.length;
1686
2491
  const repeatCount = opts._repeatHeaderCount ?? 0;
1687
- const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx) : "";
1688
- const bodyHtml = renderMatrixRows(renderRows, startRow, endRow, opts, false, ctx);
2492
+ const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }) : "";
2493
+ const bodyHtml = renderMatrixRows(renderRows, startRow, endRow, opts, false, ctx, { elementId: el.id, kind: "b" });
1689
2494
  const subtotalHtml = section.subtotal ? renderSubtotalRows(el, section, opts, ctx) : "";
1690
2495
  const summaryHtml = section.summary ? renderSummaryRows(el, opts, ctx) : "";
1691
2496
  return `<div class="print-element" style="${style};overflow:visible;">
@@ -1709,12 +2514,12 @@ function renderSubtotalRows(el, section, opts, ctx) {
1709
2514
  ...tpl,
1710
2515
  cells: tpl.cells.map((cell) => cell.rawFormatter ? { ...cell, content: evaluateTemplate(cell.rawFormatter, { rows: pageCtx, ...mainData }) } : cell)
1711
2516
  }));
1712
- return renderMatrixRows(rows, 0, rows.length, opts, false, ctx);
2517
+ return renderMatrixRows(rows, 0, rows.length, opts, false, ctx, { elementId: el.id, kind: "st" });
1713
2518
  }
1714
2519
  function renderSummaryRows(el, opts, ctx) {
1715
2520
  const summaryRows = opts._summaryRows ?? [];
1716
2521
  if (summaryRows.length === 0) return "";
1717
- return renderMatrixRows(summaryRows, 0, summaryRows.length, opts, false, ctx);
2522
+ return renderMatrixRows(summaryRows, 0, summaryRows.length, opts, false, ctx, { elementId: el.id, kind: "sm" });
1718
2523
  }
1719
2524
  function renderFlowGroup(el, section, template, ctx) {
1720
2525
  const opts = el.options ?? {};
@@ -1730,8 +2535,8 @@ function renderFlowGroup(el, section, template, ctx) {
1730
2535
  if (endRow > startRow || section.subtotal || section.summary) {
1731
2536
  const renderRows = opts._renderRows ?? [];
1732
2537
  const repeatCount = opts._repeatHeaderCount ?? 0;
1733
- const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx) : "";
1734
- const bodyHtml = renderMatrixRows(renderRows, startRow, endRow, opts, false, ctx);
2538
+ const repeatHtml = section.repeatHeader && repeatCount > 0 ? renderMatrixRows(renderRows, 0, repeatCount, opts, false, ctx, { elementId: el.id, kind: "b" }) : "";
2539
+ const bodyHtml = renderMatrixRows(renderRows, startRow, endRow, opts, false, ctx, { elementId: el.id, kind: "b" });
1735
2540
  const subtotalHtml = section.subtotal ? renderSubtotalRows(el, section, opts, ctx) : "";
1736
2541
  const summaryHtml = section.summary ? renderSummaryRows(el, opts, ctx) : "";
1737
2542
  sliceHtml = `<div class="flow-slice" style="position:relative;width:${mm(tableWidth)};overflow:visible;">
@@ -1799,6 +2604,936 @@ function esc(str) {
1799
2604
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1800
2605
  }
1801
2606
 
2607
+ // src/render/continuous-paper.ts
2608
+ var MIN_CONTINUOUS_HEIGHT_MM = 25.4;
2609
+ function composeContinuousHeight(template, contentBottomMm) {
2610
+ const mb = template.margins?.bottom ?? 0;
2611
+ const footerH = template.footer?.height ?? 0;
2612
+ const height = Math.max(contentBottomMm, 0) + footerH + mb;
2613
+ return Math.max(MIN_CONTINUOUS_HEIGHT_MM, Math.round(height * 100) / 100);
2614
+ }
2615
+
2616
+ // src/print/units.ts
2617
+ var PX_PER_MM2 = 3.7795275591;
2618
+ var MICROMETERS_PER_MM = 1e3;
2619
+ var MM_PER_INCH = 25.4;
2620
+ var MICROMETERS_PER_INCH = 25400;
2621
+ function pxToMm(px) {
2622
+ return px / PX_PER_MM2;
2623
+ }
2624
+ function mmToPx(mm2) {
2625
+ return Math.round(mm2 * PX_PER_MM2);
2626
+ }
2627
+ function millimetersToMicrometers(mm2) {
2628
+ return Math.round(mm2 * MICROMETERS_PER_MM);
2629
+ }
2630
+ function micrometersToMillimeters(um) {
2631
+ return um / MICROMETERS_PER_MM;
2632
+ }
2633
+ function millimetersToInches(mm2) {
2634
+ return mm2 / MM_PER_INCH;
2635
+ }
2636
+
2637
+ // src/print/errors.ts
2638
+ var PrintFailure = class extends Error {
2639
+ constructor(code, message, cause) {
2640
+ super(message);
2641
+ this.name = "PrintFailure";
2642
+ this.code = code;
2643
+ this.cause = cause;
2644
+ }
2645
+ };
2646
+ function toPrintFailure(err, fallbackCode, context) {
2647
+ if (err instanceof PrintFailure) return err;
2648
+ const detail = err instanceof Error ? err.message : String(err);
2649
+ return new PrintFailure(fallbackCode, `${context}\uFF1A${detail}`, err);
2650
+ }
2651
+ async function withTimeout(task, ms, code, message) {
2652
+ let timer;
2653
+ try {
2654
+ return await Promise.race([
2655
+ task,
2656
+ new Promise((_resolve, reject) => {
2657
+ timer = setTimeout(() => reject(new PrintFailure(code, message)), ms);
2658
+ })
2659
+ ]);
2660
+ } finally {
2661
+ if (timer) clearTimeout(timer);
2662
+ }
2663
+ }
2664
+
2665
+ // src/print/paper.ts
2666
+ function positive(value) {
2667
+ return typeof value === "number" && value > 0 ? value : void 0;
2668
+ }
2669
+ function escapeHeightMm(input) {
2670
+ return positive(input.paperHeightMm) ?? positive(input.override?.height);
2671
+ }
2672
+ function resolvePaperMm(input) {
2673
+ const width = positive(input.override?.width) ?? input.paperMm.width;
2674
+ const overrideHeight = positive(input.override?.height);
2675
+ if (input.continuous) {
2676
+ return overrideHeight ? { paperMm: { width, height: overrideHeight }, heightSource: "config" } : { paperMm: { width, height: input.paperMm.height }, heightSource: "derived" };
2677
+ }
2678
+ return { paperMm: { width, height: overrideHeight ?? input.paperMm.height }, heightSource: "config" };
2679
+ }
2680
+ function paperViewportPx(paper) {
2681
+ return { width: mmToPx(paper.width), height: mmToPx(paper.height) };
2682
+ }
2683
+
2684
+ // src/print/pdf-spec.ts
2685
+ var ZERO_MARGINS_MM = { top: 0, right: 0, bottom: 0, left: 0 };
2686
+ function buildPdfTargetSpec(paperMm) {
2687
+ return {
2688
+ paperMm,
2689
+ marginsMm: { ...ZERO_MARGINS_MM },
2690
+ printBackground: true,
2691
+ scale: 1,
2692
+ preferCSSPageSize: false
2693
+ };
2694
+ }
2695
+ function toElectronPrintToPdfOptions(spec) {
2696
+ return {
2697
+ margins: { ...spec.marginsMm },
2698
+ pageSize: {
2699
+ width: millimetersToInches(spec.paperMm.width),
2700
+ height: millimetersToInches(spec.paperMm.height)
2701
+ },
2702
+ printBackground: spec.printBackground,
2703
+ scale: spec.scale,
2704
+ preferCSSPageSize: spec.preferCSSPageSize
2705
+ };
2706
+ }
2707
+ function toPlaywrightPdfOptions(spec) {
2708
+ const mm2 = (value) => `${value}mm`;
2709
+ return {
2710
+ width: mm2(spec.paperMm.width),
2711
+ height: mm2(spec.paperMm.height),
2712
+ margin: {
2713
+ top: mm2(spec.marginsMm.top),
2714
+ right: mm2(spec.marginsMm.right),
2715
+ bottom: mm2(spec.marginsMm.bottom),
2716
+ left: mm2(spec.marginsMm.left)
2717
+ },
2718
+ printBackground: spec.printBackground,
2719
+ preferCSSPageSize: spec.preferCSSPageSize
2720
+ };
2721
+ }
2722
+ function buildScreenshotTargetSpec() {
2723
+ return { type: "png", fullPage: true, omitBackground: false };
2724
+ }
2725
+
2726
+ // src/print/measure.ts
2727
+ function normalizeMeasurements(raw, template) {
2728
+ const index = new Map(template.elements.map((el) => [el.id, el]));
2729
+ const measured = /* @__PURE__ */ new Map();
2730
+ for (const item of raw) {
2731
+ const element = index.get(item.id);
2732
+ const repeatCount = element?.options?._repeatHeaderCount ?? 0;
2733
+ const rowHeights = item.rowHeightsPx?.map(pxToMm);
2734
+ measured.set(item.id, {
2735
+ id: item.id,
2736
+ measuredHeight: pxToMm(item.heightPx),
2737
+ measuredRowHeights: rowHeights,
2738
+ repeatHeaderHeight: rowHeights && rowHeights.length > 0 && repeatCount > 0 ? rowHeights.slice(0, repeatCount).reduce((sum2, h) => sum2 + h, 0) : 0
2739
+ });
2740
+ }
2741
+ return measured;
2742
+ }
2743
+
2744
+ // src/print/apply-text-fit.ts
2745
+ var CELL_ROW_SOURCES = {
2746
+ b: "_renderRows",
2747
+ st: "_subtotalTemplates",
2748
+ sm: "_summaryRows"
2749
+ };
2750
+ function applyTextFitSizes(template, fits) {
2751
+ if (!fits || fits.length === 0) return;
2752
+ const index = /* @__PURE__ */ new Map();
2753
+ const collect = (elements) => {
2754
+ for (const el of elements ?? []) index.set(el.id, el);
2755
+ };
2756
+ collect(template.elements);
2757
+ collect(template.header?.elements);
2758
+ collect(template.footer?.elements);
2759
+ collect(template.firstPageOverlay?.elements);
2760
+ for (const fit of fits) {
2761
+ const parsed = parseCellFitKey(fit.key);
2762
+ if (!parsed) {
2763
+ const el2 = index.get(fit.key);
2764
+ if (el2) el2.options._fitFontSize = roundFontSize(fit.fontSizePt);
2765
+ continue;
2766
+ }
2767
+ const el = index.get(parsed.elementId);
2768
+ const rows = el?.options?.[CELL_ROW_SOURCES[parsed.kind]];
2769
+ const cell = rows?.[parsed.rowIndex]?.cells?.[parsed.colIndex];
2770
+ if (cell) cell.fittedFontSize = roundFontSize(fit.fontSizePt);
2771
+ }
2772
+ }
2773
+
2774
+ // src/print/codes.ts
2775
+ function codeSpecKey(value, cellType, opts = {}) {
2776
+ return JSON.stringify([
2777
+ value,
2778
+ cellType,
2779
+ opts.barcodeType ?? null,
2780
+ opts.qrCodeLevel ?? null,
2781
+ opts.showText ?? null,
2782
+ opts.barWidth ?? null,
2783
+ opts.fontSize ?? null
2784
+ ]);
2785
+ }
2786
+ function createMapCodeRenderer(map) {
2787
+ return {
2788
+ render(value, cellType, opts) {
2789
+ const svg = map.get(codeSpecKey(value, cellType, opts));
2790
+ if (!svg) throw new Error(`\u7801\u503C\u672A\u6E32\u67D3\uFF1A${value}`);
2791
+ return svg;
2792
+ }
2793
+ };
2794
+ }
2795
+ function createCollectingCodeRenderer(base) {
2796
+ const collected = /* @__PURE__ */ new Map();
2797
+ return {
2798
+ renderer: {
2799
+ render(value, cellType, opts = {}) {
2800
+ const key = codeSpecKey(value, cellType, opts);
2801
+ const hit = base?.get(key);
2802
+ if (hit) return hit;
2803
+ collected.set(key, { key, value, cellType, opts });
2804
+ throw new Error("collect");
2805
+ }
2806
+ },
2807
+ takeSpecs() {
2808
+ const specs = [...collected.values()];
2809
+ collected.clear();
2810
+ return specs;
2811
+ }
2812
+ };
2813
+ }
2814
+ function mergeCodeMaps(...maps) {
2815
+ const merged = /* @__PURE__ */ new Map();
2816
+ for (const map of maps) {
2817
+ for (const [key, svg] of map) merged.set(key, svg);
2818
+ }
2819
+ return merged;
2820
+ }
2821
+
2822
+ // src/print/driver.ts
2823
+ var EXECUTOR_TARGETS = {
2824
+ waitReady: "window",
2825
+ readMeasurements: "document",
2826
+ readContentBottom: "document",
2827
+ renderCodes: "none",
2828
+ applyTextFit: "document"
2829
+ };
2830
+
2831
+ // src/print/ports.ts
2832
+ var DEFAULT_TIMEOUT_MS = 3e4;
2833
+ var DEFAULT_READINESS_MS = 5e3;
2834
+
2835
+ // src/print/dom-host-runtime.ts
2836
+ function createDomHostRuntime(factory, bundle) {
2837
+ return {
2838
+ async withSession(options, fn) {
2839
+ const driver = await factory.createDriver();
2840
+ const deadline = Date.now() + (options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
2841
+ const readinessMs = options.readinessMs ?? DEFAULT_READINESS_MS;
2842
+ const budget = () => Math.max(1, deadline - Date.now());
2843
+ const fail = (err, code, context) => toPrintFailure(err, code, context);
2844
+ let injected = false;
2845
+ const ensureExecutor = async () => {
2846
+ if (injected) return;
2847
+ if (driver.requiresExecutor === false) {
2848
+ injected = true;
2849
+ return;
2850
+ }
2851
+ if (!bundle) throw new PrintFailure("INTERNAL", "\u7F3A\u5C11 core DOM \u6267\u884C\u5668\u4EA7\u7269");
2852
+ await driver.injectExecutor(bundle);
2853
+ injected = true;
2854
+ };
2855
+ const load = async (html, viewport) => {
2856
+ await driver.open(viewport);
2857
+ await driver.setContent(html);
2858
+ injected = false;
2859
+ await ensureExecutor();
2860
+ await driver.evaluate("waitReady", [readinessMs]);
2861
+ };
2862
+ const session = {
2863
+ async renderCodes(specs) {
2864
+ if (specs.length === 0) return /* @__PURE__ */ new Map();
2865
+ const ms = budget();
2866
+ return withTimeout(
2867
+ (async () => {
2868
+ try {
2869
+ await ensureExecutor();
2870
+ const map = await driver.evaluate("renderCodes", [specs]);
2871
+ return new Map(Object.entries(map));
2872
+ } catch (err) {
2873
+ throw fail(err, "INTERNAL", "\u7801\u503C\u6E32\u67D3\u5931\u8D25");
2874
+ }
2875
+ })(),
2876
+ ms,
2877
+ "RENDER_TIMEOUT",
2878
+ `\u7801\u503C\u6E32\u67D3\u8D85\u8FC7 ${ms}ms`
2879
+ );
2880
+ },
2881
+ async measure(html, viewport) {
2882
+ const ms = budget();
2883
+ return withTimeout(
2884
+ (async () => {
2885
+ try {
2886
+ await load(html, viewport);
2887
+ const fits = await driver.evaluate("applyTextFit") ?? [];
2888
+ const measurements = await driver.evaluate("readMeasurements");
2889
+ return { measurements, fits };
2890
+ } catch (err) {
2891
+ throw fail(err, "MEASURE_FAILED", "\u6D4B\u91CF\u5931\u8D25");
2892
+ }
2893
+ })(),
2894
+ ms,
2895
+ "RENDER_TIMEOUT",
2896
+ `\u6D4B\u91CF\u8D85\u8FC7 ${ms}ms`
2897
+ );
2898
+ },
2899
+ async probeContentBottom(html, viewport) {
2900
+ const ms = budget();
2901
+ return withTimeout(
2902
+ (async () => {
2903
+ try {
2904
+ await load(html, viewport);
2905
+ return await driver.evaluate("readContentBottom");
2906
+ } catch (err) {
2907
+ throw fail(err, "MEASURE_FAILED", "\u8FDE\u7EED\u7EB8\u63A2\u9488\u5931\u8D25");
2908
+ }
2909
+ })(),
2910
+ ms,
2911
+ "RENDER_TIMEOUT",
2912
+ `\u8FDE\u7EED\u7EB8\u63A2\u9488\u8D85\u8FC7 ${ms}ms`
2913
+ );
2914
+ },
2915
+ async toPdf(html, spec, viewport) {
2916
+ if (!driver.pdf) throw new PrintFailure("UNSUPPORTED_RUNTIME", "\u5F53\u524D\u5BBF\u4E3B\u4E0D\u652F\u6301\u751F\u6210 PDF");
2917
+ const ms = budget();
2918
+ return withTimeout(
2919
+ (async () => {
2920
+ try {
2921
+ await load(html, viewport);
2922
+ return await driver.pdf(html, spec);
2923
+ } catch (err) {
2924
+ throw fail(err, "PDF_FAILED", "PDF \u751F\u6210\u5931\u8D25");
2925
+ }
2926
+ })(),
2927
+ ms,
2928
+ "RENDER_TIMEOUT",
2929
+ `PDF \u751F\u6210\u8D85\u8FC7 ${ms}ms`
2930
+ );
2931
+ },
2932
+ async toScreenshot(html, spec, viewport) {
2933
+ if (!driver.screenshot) throw new PrintFailure("UNSUPPORTED_RUNTIME", "\u5F53\u524D\u5BBF\u4E3B\u4E0D\u652F\u6301\u622A\u56FE");
2934
+ const ms = budget();
2935
+ return withTimeout(
2936
+ (async () => {
2937
+ try {
2938
+ await load(html, viewport);
2939
+ return await driver.screenshot(html, spec);
2940
+ } catch (err) {
2941
+ throw fail(err, "SCREENSHOT_FAILED", "\u622A\u56FE\u5931\u8D25");
2942
+ }
2943
+ })(),
2944
+ ms,
2945
+ "RENDER_TIMEOUT",
2946
+ `\u622A\u56FE\u8D85\u8FC7 ${ms}ms`
2947
+ );
2948
+ }
2949
+ };
2950
+ try {
2951
+ return await fn(session);
2952
+ } finally {
2953
+ try {
2954
+ await driver.close();
2955
+ } catch {
2956
+ }
2957
+ }
2958
+ }
2959
+ };
2960
+ }
2961
+
2962
+ // src/print/normalize-print-data.ts
2963
+ var MAX_BATCH_COPIES = 500;
2964
+ function isPlainRecord(v) {
2965
+ return typeof v === "object" && v !== null && !Array.isArray(v);
2966
+ }
2967
+ function normalizePrintData(raw) {
2968
+ if (raw === void 0) return { mode: "single", data: {} };
2969
+ if (!Array.isArray(raw)) return { mode: "single", data: raw };
2970
+ if (raw.length === 0) {
2971
+ throw new Error("\u6279\u91CF\u6253\u5370\u6570\u636E\u5FC5\u987B\u662F\u975E\u7A7A\u5BF9\u8C61\u6570\u7EC4");
2972
+ }
2973
+ if (raw.length > MAX_BATCH_COPIES) {
2974
+ throw new Error(`\u6279\u91CF\u6253\u5370\u6700\u591A\u652F\u6301 ${MAX_BATCH_COPIES} \u4EFD\uFF0C\u5F53\u524D ${raw.length} \u4EFD`);
2975
+ }
2976
+ for (let i = 0; i < raw.length; i++) {
2977
+ if (!isPlainRecord(raw[i])) {
2978
+ throw new Error(`\u6279\u91CF\u6253\u5370\u6570\u636E\u7B2C ${i + 1} \u9879\u5FC5\u987B\u662F\u5BF9\u8C61`);
2979
+ }
2980
+ }
2981
+ return { mode: "batch", dataList: raw };
2982
+ }
2983
+
2984
+ // src/print/batch-compose.ts
2985
+ function composeBatchHtml(copies) {
2986
+ const bound = copies[0].bound;
2987
+ const continuous = isContinuousPaper(bound);
2988
+ const bodyInner = copies.map((copy, i) => {
2989
+ const cls = continuous ? `print-copy print-copy-${i}` : "print-copy";
2990
+ const pages = renderFinalPages(copy.bound, copy.pageLayouts, copy.data, {
2991
+ codeRenderer: copy.codeRenderer,
2992
+ pageHeightMm: copy.derivedHeightMm
2993
+ });
2994
+ return `<section class="${cls}">
2995
+ ${pages}
2996
+ </section>`;
2997
+ }).join("\n");
2998
+ const pageCss = continuous ? buildBatchPageCss(bound, copies.map((c) => ({ heightMm: c.derivedHeightMm }))) : `${buildPageCss(bound)}
2999
+ .print-copy:not(:last-child){break-after:page;page-break-after:always;}`;
3000
+ const css = buildFontFaceCss(bound.fonts) + pageCss;
3001
+ return {
3002
+ html: wrapHtmlDocument(
3003
+ css,
3004
+ bodyInner,
3005
+ continuous ? "continuous" : void 0
3006
+ ),
3007
+ pageCount: copies.reduce((sum2, c) => sum2 + c.pageLayouts.length, 0),
3008
+ copyPaperMm: copies.map((c) => c.paperMm)
3009
+ };
3010
+ }
3011
+
3012
+ // src/print/tiling.ts
3013
+ var TILE_DEFAULTS = {
3014
+ enabled: true,
3015
+ sheetPaperSize: "A4",
3016
+ sheetOrientation: "portrait",
3017
+ sheetMargin: { top: 10, right: 10, bottom: 10, left: 10 },
3018
+ gapX: 2,
3019
+ gapY: 2,
3020
+ columns: 2
3021
+ };
3022
+ var TilingError = class extends Error {
3023
+ constructor(code, message) {
3024
+ super(message);
3025
+ this.name = "TilingError";
3026
+ this.code = code;
3027
+ }
3028
+ };
3029
+ function roundMm(value) {
3030
+ return Math.round(value * 10) / 10;
3031
+ }
3032
+ function normalizeTilingOptions(t) {
3033
+ const raw = t.tiling;
3034
+ return {
3035
+ ...TILE_DEFAULTS,
3036
+ ...raw,
3037
+ sheetMargin: { ...TILE_DEFAULTS.sheetMargin, ...raw?.sheetMargin ?? {} }
3038
+ };
3039
+ }
3040
+ function resolveSheetMm(t, opts) {
3041
+ const cfg = normalizeTilingOptions(t);
3042
+ const ov = opts?.paperOverride;
3043
+ if (ov && typeof ov.width === "number" && ov.width > 0 && typeof ov.height === "number" && ov.height > 0) {
3044
+ return { width: ov.width, height: ov.height };
3045
+ }
3046
+ if (cfg.sheetPaperSize === "CUSTOM") {
3047
+ return {
3048
+ width: cfg.sheetCustomWidth ?? PAPER_DIMENSIONS.A4.width,
3049
+ height: cfg.sheetCustomHeight ?? PAPER_DIMENSIONS.A4.height
3050
+ };
3051
+ }
3052
+ const preset = cfg.sheetPaperSize ?? "A4";
3053
+ const base = PAPER_DIMENSIONS[preset] ?? PAPER_DIMENSIONS.A4;
3054
+ return cfg.sheetOrientation === "landscape" ? { width: base.height, height: base.width } : { ...base };
3055
+ }
3056
+ function availableArea(sheet, margin) {
3057
+ return {
3058
+ w: sheet.width - margin.left - margin.right,
3059
+ h: sheet.height - margin.top - margin.bottom
3060
+ };
3061
+ }
3062
+ function calcMaxColumns(availW, labelW, gapX) {
3063
+ return Math.max(0, Math.floor((availW + gapX) / (labelW + gapX)));
3064
+ }
3065
+ function computeTileLayout(t, opts) {
3066
+ const issues = validateTiling(t, opts);
3067
+ if (issues.length) throw new TilingError(issues[0].code, issues[0].message);
3068
+ const cfg = normalizeTilingOptions(t);
3069
+ const sheet = resolveSheetMm(t, opts);
3070
+ const label = getPaperDimensions(t);
3071
+ const avail = availableArea(sheet, cfg.sheetMargin);
3072
+ const rows = Math.floor((avail.h + cfg.gapY) / (label.height + cfg.gapY));
3073
+ return {
3074
+ tile: { width: label.width, height: label.height },
3075
+ sheet,
3076
+ columns: cfg.columns,
3077
+ rows,
3078
+ perSheet: cfg.columns * rows,
3079
+ maxColumns: calcMaxColumns(avail.w, label.width, cfg.gapX),
3080
+ margin: { ...cfg.sheetMargin },
3081
+ gapX: cfg.gapX,
3082
+ gapY: cfg.gapY
3083
+ };
3084
+ }
3085
+ function tilePosition(layout, index) {
3086
+ const slot = index % layout.perSheet;
3087
+ const col = slot % layout.columns;
3088
+ const row = Math.floor(slot / layout.columns);
3089
+ return {
3090
+ left: roundMm(layout.margin.left + col * (layout.tile.width + layout.gapX)),
3091
+ top: roundMm(layout.margin.top + row * (layout.tile.height + layout.gapY))
3092
+ };
3093
+ }
3094
+ function computeMaxColumns(t, opts) {
3095
+ const cfg = normalizeTilingOptions(t);
3096
+ const sheet = resolveSheetMm(t, opts);
3097
+ const label = getPaperDimensions(t);
3098
+ const availW = sheet.width - cfg.sheetMargin.left - cfg.sheetMargin.right;
3099
+ return calcMaxColumns(availW, label.width, cfg.gapX);
3100
+ }
3101
+ function validateTiling(t, opts) {
3102
+ const issues = [];
3103
+ const cfg = normalizeTilingOptions(t);
3104
+ if (isContinuousPaper(t)) {
3105
+ issues.push({
3106
+ code: "CONTINUOUS_UNSUPPORTED",
3107
+ message: "\u8FDE\u7EED\u7EB8\u4E0D\u652F\u6301\u62FC\u7248\u6253\u5370\uFF0C\u8BF7\u5C06\u6A21\u677F\u7EB8\u5F20\u6539\u4E3A\u56FA\u5B9A\u7EB8\u5F20\u6216\u5173\u95ED\u62FC\u7248"
3108
+ });
3109
+ }
3110
+ const sheetPaperSize = cfg.sheetPaperSize;
3111
+ const sheetContinuous = isContinuousPaperSize(sheetPaperSize ?? "");
3112
+ if (sheetContinuous) {
3113
+ issues.push({ code: "SHEET_CONTINUOUS", message: "\u62FC\u7248\u76EE\u6807\u7EB8\u5F20\u4E0D\u80FD\u662F\u8FDE\u7EED\u7EB8" });
3114
+ }
3115
+ const customSizeInvalid = cfg.sheetPaperSize === "CUSTOM" && !((cfg.sheetCustomWidth ?? 0) > 0 && (cfg.sheetCustomHeight ?? 0) > 0);
3116
+ if (customSizeInvalid) {
3117
+ issues.push({
3118
+ code: "SHEET_SIZE_INVALID",
3119
+ message: "\u62FC\u7248\u81EA\u5B9A\u4E49\u7EB8\u5F20\u5BBD\u9AD8\u5FC5\u987B\u662F\u5927\u4E8E 0 \u7684\u6570\u503C\uFF08mm\uFF09"
3120
+ });
3121
+ }
3122
+ if (customSizeInvalid || sheetContinuous) return issues;
3123
+ const sheet = resolveSheetMm(t, opts);
3124
+ const label = getPaperDimensions(t);
3125
+ const avail = availableArea(sheet, cfg.sheetMargin);
3126
+ const marginLR = roundMm(cfg.sheetMargin.left + cfg.sheetMargin.right);
3127
+ if (!Number.isInteger(cfg.columns) || cfg.columns < 1) {
3128
+ issues.push({ code: "COLUMNS_INVALID", message: "\u62FC\u7248\u5217\u6570\u5FC5\u987B\u662F\u5927\u4E8E 0 \u7684\u6574\u6570" });
3129
+ return issues;
3130
+ }
3131
+ const maxColumns = calcMaxColumns(avail.w, label.width, cfg.gapX);
3132
+ if (cfg.columns > maxColumns) {
3133
+ const needW = roundMm(cfg.columns * label.width + (cfg.columns - 1) * cfg.gapX);
3134
+ issues.push({
3135
+ code: "COLUMNS_OVERFLOW",
3136
+ message: "\u62FC\u7248\u5217\u6570 " + cfg.columns + " \u8D85\u51FA\u7EB8\u9762\u53EF\u7528\u5BBD\u5EA6\uFF1A" + roundMm(sheet.width) + "mm \u2212 \u5DE6\u53F3\u7559\u767D " + marginLR + "mm = " + roundMm(avail.w) + "mm\uFF0C\u6700\u591A\u53EF\u653E " + maxColumns + " \u5217\uFF1B\u5F53\u524D " + cfg.columns + " \u5217 " + label.width + "mm \u6807\u7B7E\u542B\u95F4\u8DDD\u9700\u8981 " + needW + "mm"
3137
+ });
3138
+ }
3139
+ if (Math.floor((avail.h + cfg.gapY) / (label.height + cfg.gapY)) < 1) {
3140
+ issues.push({
3141
+ code: "LABEL_TOO_TALL",
3142
+ message: "\u6807\u7B7E\u9AD8\u5EA6 " + label.height + "mm \u8D85\u51FA\u7EB8\u9762\u53EF\u7528\u9AD8\u5EA6 " + roundMm(avail.h) + "mm\uFF0C\u62FC\u7248\u6BCF\u5F20 0 \u884C\uFF1B\u8BF7\u7F29\u5C0F\u6807\u7B7E\u9AD8\u5EA6\u6216\u6539\u7528\u6A2A\u5411\u7EB8"
3143
+ });
3144
+ }
3145
+ return issues;
3146
+ }
3147
+
3148
+ // src/print/tile-compose.ts
3149
+ function composeTiledHtml(input) {
3150
+ const { copies, layout } = input;
3151
+ const bound = copies[0].bound;
3152
+ const sheets = [];
3153
+ for (let start = 0; start < copies.length; start += layout.perSheet) {
3154
+ const tiles = copies.slice(start, start + layout.perSheet).map((copy, i) => {
3155
+ const pos = tilePosition(layout, i);
3156
+ const page = renderFinalPages(copy.bound, copy.pageLayouts, copy.data, {
3157
+ codeRenderer: copy.codeRenderer
3158
+ });
3159
+ return `<div class="print-tile" style="left:${pos.left}mm;top:${pos.top}mm">
3160
+ ${page}
3161
+ </div>`;
3162
+ }).join("\n");
3163
+ sheets.push(`<section class="print-sheet">
3164
+ ${tiles}
3165
+ </section>`);
3166
+ }
3167
+ const css = buildFontFaceCss(bound.fonts) + buildPageCss(bound) + "\n" + buildSheetPageCss(layout);
3168
+ return {
3169
+ html: wrapHtmlDocument(css, sheets.join("\n")),
3170
+ sheetCount: sheets.length,
3171
+ perSheet: layout.perSheet
3172
+ };
3173
+ }
3174
+
3175
+ // src/print/multi-template.ts
3176
+ function pageLabel(p, index) {
3177
+ return p.name ? `\u7B2C ${index + 1} \u9875\u300C${p.name}\u300D` : `\u7B2C ${index + 1} \u9875`;
3178
+ }
3179
+ function normalizeTemplate(templateJson) {
3180
+ const pages = isMultiPageTemplate(templateJson) ? templateJson.pages : [templateJson];
3181
+ if (pages.length === 0) {
3182
+ throw new Error("\u591A\u9875\u9762\u6A21\u677F\u81F3\u5C11\u9700\u8981\u4E00\u9875");
3183
+ }
3184
+ if (pages.length === 1) return pages;
3185
+ const firstPaper = getPaperDimensions(pages[0]);
3186
+ pages.forEach((p, i) => {
3187
+ if (isContinuousPaper(p)) {
3188
+ throw new Error(`\u591A\u9875\u9762\u6A21\u677F\u4E0D\u652F\u6301\u8FDE\u7EED\u7EB8\uFF08${pageLabel(p, i)}\uFF09`);
3189
+ }
3190
+ if (p.tiling?.enabled === true) {
3191
+ throw new Error(`\u591A\u9875\u9762\u6A21\u677F\u4E0D\u652F\u6301\u6807\u7B7E\u62FC\u7248\uFF08${pageLabel(p, i)}\uFF09`);
3192
+ }
3193
+ const d = getPaperDimensions(p);
3194
+ if (d.width !== firstPaper.width || d.height !== firstPaper.height) {
3195
+ throw new Error(
3196
+ `\u591A\u9875\u9762\u6A21\u677F\u5404\u9875\u7EB8\u5F20\u5C3A\u5BF8\u5FC5\u987B\u4E00\u81F4\uFF08${pageLabel(p, i)} ${d.width}\xD7${d.height}mm \u4E0E\u9996\u9875 ${firstPaper.width}\xD7${firstPaper.height}mm \u4E0D\u540C\uFF09`
3197
+ );
3198
+ }
3199
+ });
3200
+ return pages;
3201
+ }
3202
+ function isMultiPageTemplate(templateJson) {
3203
+ return Array.isArray(templateJson.pages);
3204
+ }
3205
+ function mergeFontDeclarations(pages) {
3206
+ const seen = /* @__PURE__ */ new Set();
3207
+ const merged = [];
3208
+ for (const p of pages) {
3209
+ for (const f of p.fonts ?? []) {
3210
+ const key = f.family;
3211
+ if (seen.has(key)) continue;
3212
+ seen.add(key);
3213
+ merged.push(f);
3214
+ }
3215
+ }
3216
+ return merged;
3217
+ }
3218
+ function composeMultiPageDocument(copies) {
3219
+ if (copies.length === 0) {
3220
+ throw new Error("\u591A\u9875\u9762\u6A21\u677F\u81F3\u5C11\u9700\u8981\u4E00\u4EFD\u6570\u636E");
3221
+ }
3222
+ const templates = copies[0].boundPages;
3223
+ const css = buildFontFaceCss(mergeFontDeclarations(templates)) + [
3224
+ buildPageRuleCss(templates[0]),
3225
+ buildBasePageCss(),
3226
+ ...templates.map((t, i) => buildPageGeometryCss(t, `.mt-${i}`)),
3227
+ ...copies.length > 1 ? [COPY_BREAK_CSS] : []
3228
+ ].join("\n\n");
3229
+ const bodyInner = copies.map((copy) => {
3230
+ const totalPages = copy.layoutsPerPage.reduce((s, ls) => s + ls.length, 0);
3231
+ let offset = 0;
3232
+ const fragments = copy.layoutsPerPage.map((layouts, i) => {
3233
+ const html = renderFinalPages(
3234
+ copy.boundPages[i],
3235
+ layouts,
3236
+ copy.data,
3237
+ { codeRenderer: copy.codeRenderers?.[i] },
3238
+ {
3239
+ pageOffset: offset,
3240
+ totalPages,
3241
+ pageClass: `mt-${i}`
3242
+ }
3243
+ );
3244
+ offset += layouts.length;
3245
+ return html;
3246
+ });
3247
+ const pages = fragments.join("\n");
3248
+ return copies.length > 1 ? `<section class="print-copy">
3249
+ ${pages}
3250
+ </section>` : pages;
3251
+ }).join("\n");
3252
+ const pageCount = copies.reduce(
3253
+ (sum2, c) => sum2 + c.layoutsPerPage.reduce((s, ls) => s + ls.length, 0),
3254
+ 0
3255
+ );
3256
+ const pageLayouts = [];
3257
+ let g = 0;
3258
+ for (const c of copies) {
3259
+ for (const ls of c.layoutsPerPage) {
3260
+ for (const p of ls) pageLayouts.push({ ...p, pageIndex: g++ });
3261
+ }
3262
+ }
3263
+ return {
3264
+ html: wrapHtmlDocument(css, bodyInner),
3265
+ pageCount,
3266
+ pageLayouts
3267
+ };
3268
+ }
3269
+
3270
+ // src/print/pipeline.ts
3271
+ async function prepareDocument(job, runtime) {
3272
+ return runtime.withSession(job, (session) => prepareWithSession(job, session));
3273
+ }
3274
+ async function renderPdf(job, runtime) {
3275
+ return runtime.withSession(job, async (session) => {
3276
+ const prepared = await prepareWithSession(job, session);
3277
+ const viewport = paperViewportPx(prepared.paperMm);
3278
+ const pdf = await session.toPdf(prepared.html, buildPdfTargetSpec(prepared.paperMm), viewport);
3279
+ return { pdf, prepared };
3280
+ });
3281
+ }
3282
+ async function renderScreenshot(job, runtime) {
3283
+ return runtime.withSession(job, async (session) => {
3284
+ const pageTemplates = normalizeTemplate(job.templateJson);
3285
+ if (pageTemplates.length > 1) {
3286
+ const normalized2 = normalizePrintData(job.printData);
3287
+ const data2 = normalized2.mode === "batch" ? normalized2.dataList[0] : normalized2.data;
3288
+ const copy = await prepareMultiCopy(job, session, data2);
3289
+ const doc = composeMultiPageDocument([copy]);
3290
+ const viewport2 = paperViewportPx(getPaperDimensions(pageTemplates[0]));
3291
+ return session.toScreenshot(doc.html, buildScreenshotTargetSpec(), viewport2);
3292
+ }
3293
+ const singleJob = pageTemplates[0] !== job.templateJson ? { ...job, templateJson: pageTemplates[0] } : job;
3294
+ const normalized = normalizePrintData(singleJob.printData);
3295
+ const data = normalized.mode === "batch" ? normalized.dataList[0] : normalized.data;
3296
+ const bound = bindData(singleJob.templateJson, data, singleJob.baseUrl, singleJob.fontBaseUrl);
3297
+ const built = await buildHtmlWithCodes({
3298
+ bound,
3299
+ job: singleJob,
3300
+ session,
3301
+ data,
3302
+ pageLayouts: [],
3303
+ isMeasurementPass: true
3304
+ });
3305
+ const viewport = paperViewportPx(getPaperDimensions(bound));
3306
+ return session.toScreenshot(built.html, buildScreenshotTargetSpec(), viewport);
3307
+ });
3308
+ }
3309
+ async function prepareWithSession(job, session) {
3310
+ const pageTemplates = normalizeTemplate(job.templateJson);
3311
+ if (pageTemplates.length > 1) {
3312
+ const normalized2 = normalizePrintData(job.printData);
3313
+ const rows = normalized2.mode === "batch" ? normalized2.dataList : [normalized2.data];
3314
+ const copies2 = [];
3315
+ for (let i = 0; i < rows.length; i++) {
3316
+ try {
3317
+ copies2.push(await prepareMultiCopy(job, session, rows[i]));
3318
+ } catch (err) {
3319
+ const reason = err instanceof Error ? err.message : String(err);
3320
+ throw new Error(`\u7B2C ${i + 1} \u4EFD\u6E32\u67D3\u5931\u8D25\uFF1A${reason}`);
3321
+ }
3322
+ }
3323
+ const doc = composeMultiPageDocument(copies2);
3324
+ return {
3325
+ html: doc.html,
3326
+ pageCount: doc.pageCount,
3327
+ paperMm: getOutputPaperDimensions(pageTemplates[0]),
3328
+ continuous: false,
3329
+ heightSource: "config",
3330
+ pageLayouts: doc.pageLayouts,
3331
+ copies: copies2.length
3332
+ };
3333
+ }
3334
+ const singleJob = pageTemplates[0] !== job.templateJson ? { ...job, templateJson: pageTemplates[0] } : job;
3335
+ const template = pageTemplates[0];
3336
+ const normalized = normalizePrintData(singleJob.printData);
3337
+ if (normalized.mode === "single") {
3338
+ const single = await prepareSingleWithSession(singleJob, session, normalized.data);
3339
+ if (template.tiling?.enabled === true) {
3340
+ return composeTiledPrepared(singleJob, [{
3341
+ bound: single.bound,
3342
+ pageLayouts: single.pageLayouts,
3343
+ data: normalized.data,
3344
+ codeRenderer: single.codeRenderer,
3345
+ derivedHeightMm: single.derivedHeightMm,
3346
+ paperMm: single.paperMm,
3347
+ heightSource: single.heightSource
3348
+ }]);
3349
+ }
3350
+ return toPreparedDocument(single);
3351
+ }
3352
+ const copies = [];
3353
+ for (let i = 0; i < normalized.dataList.length; i++) {
3354
+ try {
3355
+ const single = await prepareSingleWithSession(singleJob, session, normalized.dataList[i]);
3356
+ copies.push({
3357
+ bound: single.bound,
3358
+ pageLayouts: single.pageLayouts,
3359
+ data: normalized.dataList[i],
3360
+ codeRenderer: single.codeRenderer,
3361
+ derivedHeightMm: single.derivedHeightMm,
3362
+ paperMm: single.paperMm,
3363
+ heightSource: single.heightSource
3364
+ });
3365
+ } catch (err) {
3366
+ const reason = err instanceof Error ? err.message : String(err);
3367
+ throw new Error(`\u7B2C ${i + 1} \u4EFD\u6E32\u67D3\u5931\u8D25\uFF1A${reason}`);
3368
+ }
3369
+ }
3370
+ if (template.tiling?.enabled === true) {
3371
+ return composeTiledPrepared(singleJob, copies);
3372
+ }
3373
+ const merged = composeBatchHtml(copies);
3374
+ return {
3375
+ html: merged.html,
3376
+ pageCount: merged.pageCount,
3377
+ paperMm: copies[0].paperMm,
3378
+ continuous: isContinuousPaper(copies[0].bound),
3379
+ // 同模板同参数各份来源必然一致;不能用 derivedHeightMm 是否存在判断——
3380
+ // 连续纸逃生门时该字段有值但来源是 config
3381
+ heightSource: copies[0].heightSource,
3382
+ // 各份 pageIndex 均从 0 开始,批量拼接后该字段仅作调试用途
3383
+ pageLayouts: copies.flatMap((c) => c.pageLayouts),
3384
+ copies: copies.length,
3385
+ copyPaperMm: merged.copyPaperMm
3386
+ };
3387
+ }
3388
+ function toPreparedDocument(s) {
3389
+ return {
3390
+ html: s.html,
3391
+ pageCount: s.pageCount,
3392
+ paperMm: s.paperMm,
3393
+ continuous: s.continuous,
3394
+ heightSource: s.heightSource,
3395
+ pageLayouts: s.pageLayouts,
3396
+ copies: 1
3397
+ };
3398
+ }
3399
+ function composeTiledPrepared(job, copies) {
3400
+ const bound = copies[0].bound;
3401
+ const layout = computeTileLayout(bound, { paperOverride: job.paperOverride });
3402
+ copies.forEach((copy, i) => {
3403
+ const overflow = copy.pageLayouts[0]?.overflow === true;
3404
+ if (copy.pageLayouts.length !== 1 || overflow) {
3405
+ const detail = overflow ? "\u5185\u5BB9\u8D85\u51FA\u7EB8\u5F20\u9AD8\u5EA6" : `\u6E32\u67D3\u51FA ${copy.pageLayouts.length} \u9875`;
3406
+ throw new Error(
3407
+ `\u62FC\u7248\u8981\u6C42\u6BCF\u4EFD\u6807\u7B7E\u6070\u597D 1 \u9875\uFF0C\u7B2C ${i + 1} \u4EFD${detail}\uFF1B\u8BF7\u7F29\u5C0F\u5185\u5BB9\u6216\u8C03\u6574\u6807\u7B7E\u7EB8\u5F20\u9AD8\u5EA6`
3408
+ );
3409
+ }
3410
+ });
3411
+ const tiled = composeTiledHtml({
3412
+ copies: copies.map((c) => ({
3413
+ bound: c.bound,
3414
+ pageLayouts: c.pageLayouts,
3415
+ data: c.data,
3416
+ codeRenderer: c.codeRenderer
3417
+ })),
3418
+ layout
3419
+ });
3420
+ return {
3421
+ html: tiled.html,
3422
+ // 语义变更:pageCount = 实际输出张数(客户端任务历史、预览「共 N 页」都按此口径)
3423
+ pageCount: tiled.sheetCount,
3424
+ paperMm: { width: layout.sheet.width, height: layout.sheet.height },
3425
+ continuous: false,
3426
+ heightSource: "config",
3427
+ // 调试用途;各份 pageIndex 均从 0 开始
3428
+ pageLayouts: copies.flatMap((c) => c.pageLayouts),
3429
+ copies: copies.length
3430
+ };
3431
+ }
3432
+ async function prepareSingleWithSession(job, session, data) {
3433
+ const bound = bindData(job.templateJson, data, job.baseUrl, job.fontBaseUrl);
3434
+ const continuous = isContinuousPaper(bound);
3435
+ const designPaper = getPaperDimensions(bound);
3436
+ const viewport = paperViewportPx(designPaper);
3437
+ const heightEscape = escapeHeightMm({ paperHeightMm: job.paperHeightMm, override: job.paperOverride });
3438
+ const measurement = await buildHtmlWithCodes({
3439
+ bound,
3440
+ job,
3441
+ session,
3442
+ data,
3443
+ pageLayouts: [],
3444
+ isMeasurementPass: true
3445
+ });
3446
+ const measurements = await session.measure(measurement.html, viewport);
3447
+ applyTextFitSizes(bound, measurements.fits);
3448
+ const pageLayouts = paginate(bound, normalizeMeasurements(measurements.measurements, bound));
3449
+ const finalBuild = await buildHtmlWithCodes({
3450
+ bound,
3451
+ job,
3452
+ session,
3453
+ data,
3454
+ pageLayouts,
3455
+ isMeasurementPass: false,
3456
+ baseMap: measurement.map
3457
+ });
3458
+ let html = finalBuild.html;
3459
+ let derivedHeightMm;
3460
+ if (continuous) {
3461
+ if (heightEscape && heightEscape > 0) {
3462
+ derivedHeightMm = heightEscape;
3463
+ } else {
3464
+ const bottomPx = await session.probeContentBottom(html, viewport);
3465
+ derivedHeightMm = composeContinuousHeight(bound, pxToMm(bottomPx));
3466
+ }
3467
+ html = generateHtml(bound, pageLayouts, data, {
3468
+ codeRenderer: finalBuild.codeRenderer,
3469
+ pageHeightMm: derivedHeightMm
3470
+ });
3471
+ }
3472
+ const overrideForPaper = continuous && heightEscape ? { ...job.paperOverride, height: heightEscape } : job.paperOverride;
3473
+ const outputPaper = getOutputPaperDimensions(bound);
3474
+ const { paperMm, heightSource } = resolvePaperMm({
3475
+ paperMm: { width: outputPaper.width, height: derivedHeightMm ?? outputPaper.height },
3476
+ continuous,
3477
+ override: overrideForPaper
3478
+ });
3479
+ return {
3480
+ html,
3481
+ pageCount: pageLayouts.length,
3482
+ paperMm,
3483
+ continuous,
3484
+ heightSource,
3485
+ pageLayouts,
3486
+ copies: 1,
3487
+ bound,
3488
+ codeRenderer: finalBuild.codeRenderer,
3489
+ derivedHeightMm
3490
+ };
3491
+ }
3492
+ async function prepareMultiCopy(job, session, data) {
3493
+ const templates = normalizeTemplate(job.templateJson);
3494
+ const viewport = paperViewportPx(getPaperDimensions(templates[0]));
3495
+ const boundPages = [];
3496
+ const layoutsPerPage = [];
3497
+ const codeRenderers = [];
3498
+ for (const t of templates) {
3499
+ const bound = bindData(t, data, job.baseUrl, job.fontBaseUrl);
3500
+ const measurement = await buildHtmlWithCodes({ bound, job, session, data, pageLayouts: [], isMeasurementPass: true });
3501
+ const measurements = await session.measure(measurement.html, viewport);
3502
+ applyTextFitSizes(bound, measurements.fits);
3503
+ const layouts = paginate(bound, normalizeMeasurements(measurements.measurements, bound));
3504
+ const final = await buildHtmlWithCodes({ bound, job, session, data, pageLayouts: layouts, isMeasurementPass: false, baseMap: measurement.map });
3505
+ boundPages.push(bound);
3506
+ layoutsPerPage.push(layouts);
3507
+ codeRenderers.push(final.codeRenderer);
3508
+ }
3509
+ return { boundPages, layoutsPerPage, data, codeRenderers };
3510
+ }
3511
+ async function buildHtmlWithCodes(input) {
3512
+ const baseMap = input.baseMap ?? /* @__PURE__ */ new Map();
3513
+ if (input.job.codeRenderer) {
3514
+ const html2 = generateHtml(input.bound, input.pageLayouts, input.data, {
3515
+ isMeasurementPass: input.isMeasurementPass,
3516
+ codeRenderer: input.job.codeRenderer
3517
+ });
3518
+ return { html: html2, codeRenderer: input.job.codeRenderer, map: baseMap };
3519
+ }
3520
+ const collector = createCollectingCodeRenderer(baseMap);
3521
+ const draft = generateHtml(input.bound, input.pageLayouts, input.data, {
3522
+ isMeasurementPass: input.isMeasurementPass,
3523
+ codeRenderer: collector.renderer
3524
+ });
3525
+ const extra = collector.takeSpecs();
3526
+ const rendered = extra.length > 0 ? await input.session.renderCodes(extra) : /* @__PURE__ */ new Map();
3527
+ const map = mergeCodeMaps(baseMap, rendered);
3528
+ const codeRenderer = map.size > 0 ? createMapCodeRenderer(map) : void 0;
3529
+ if (extra.length === 0) return { html: draft, codeRenderer, map };
3530
+ const html = generateHtml(input.bound, input.pageLayouts, input.data, {
3531
+ isMeasurementPass: input.isMeasurementPass,
3532
+ codeRenderer
3533
+ });
3534
+ return { html, codeRenderer, map };
3535
+ }
3536
+
1802
3537
  // src/index.ts
1803
3538
  var TemplateEngine = class {
1804
3539
  constructor(options) {
@@ -1821,36 +3556,116 @@ var TemplateEngine = class {
1821
3556
  };
1822
3557
  // Annotate the CommonJS export names for ESM import in node:
1823
3558
  0 && (module.exports = {
3559
+ DEFAULT_READINESS_MS,
3560
+ DEFAULT_SHRINK_MIN_FONT_SIZE_PT,
3561
+ DEFAULT_TIMEOUT_MS,
3562
+ EXECUTOR_TARGETS,
3563
+ FALLBACK_FONT_STACK,
3564
+ MAX_BATCH_COPIES,
3565
+ MICROMETERS_PER_INCH,
3566
+ MICROMETERS_PER_MM,
3567
+ MIN_CONTINUOUS_HEIGHT_MM,
3568
+ MIN_SHRINK_FONT_SIZE_PT,
3569
+ MM_PER_INCH,
3570
+ MM_PER_PX,
1824
3571
  PAPER_DIMENSIONS,
3572
+ PX_PER_MM,
3573
+ PrintFailure,
3574
+ TILE_DEFAULTS,
1825
3575
  TemplateEngine,
3576
+ TilingError,
3577
+ WATERMARK_DEFAULTS,
3578
+ WATERMARK_DENSITY_PRESETS,
3579
+ applyTextFitSizes,
1826
3580
  avg,
1827
3581
  bindData,
3582
+ buildBasePageCss,
3583
+ buildFontFaceCss,
1828
3584
  buildPageCss,
3585
+ buildPageGeometryCss,
3586
+ buildPageRuleCss,
3587
+ buildPdfTargetSpec,
3588
+ buildScreenshotTargetSpec,
3589
+ cellFitCapMm,
3590
+ cellFitKey,
3591
+ codeSpecKey,
1829
3592
  compileTemplate,
3593
+ composeBatchHtml,
3594
+ composeContinuousHeight,
3595
+ composeMultiPageDocument,
3596
+ composeTiledHtml,
3597
+ computeMaxColumns,
3598
+ computeTileLayout,
1830
3599
  count,
3600
+ createCollectingCodeRenderer,
3601
+ createDomHostRuntime,
3602
+ createMapCodeRenderer,
1831
3603
  elementPositionStyle,
3604
+ escapeHeightMm,
3605
+ escapeInlineStyleValue,
1832
3606
  evaluate,
1833
3607
  evaluateTemplate,
3608
+ floorFontSize,
1834
3609
  formatDate,
1835
3610
  formatMoney,
3611
+ formatTimestamp,
1836
3612
  generateHtml,
1837
3613
  getByPath,
1838
3614
  getPaperDimensions,
1839
3615
  ifFn,
1840
3616
  injectSystemVariables,
3617
+ isContinuousPaper,
3618
+ isContinuousPaperSize,
3619
+ isMultiPageTemplate,
3620
+ isWatermarkVisible,
1841
3621
  max,
3622
+ mergeCodeMaps,
3623
+ mergeFontDeclarations,
3624
+ micrometersToMillimeters,
3625
+ millimetersToInches,
3626
+ millimetersToMicrometers,
1842
3627
  min,
1843
3628
  mm,
3629
+ mmToPx,
3630
+ normalizeMeasurements,
3631
+ normalizePrintData,
3632
+ normalizeTemplate,
3633
+ normalizeTilingOptions,
1844
3634
  paginate,
3635
+ paperViewportPx,
1845
3636
  parse,
3637
+ parseCellFitKey,
1846
3638
  parseTemplate,
3639
+ prepareDocument,
3640
+ pxToMm,
3641
+ renderPdf,
3642
+ renderScreenshot,
1847
3643
  renderTemplate,
3644
+ renderWatermarkLayerHtml,
3645
+ renderWatermarkTileSvg,
3646
+ resolveCellTextFit,
3647
+ resolveElementTextFit,
3648
+ resolvePaperMm,
3649
+ resolveSheetMm,
3650
+ resolveShrinkMinFontSize,
3651
+ resolveSystemVariables,
3652
+ resolveWatermarkLayout,
3653
+ resolveWatermarkText,
3654
+ roundFontSize,
3655
+ roundMm,
1848
3656
  safeEval,
1849
3657
  setPageIndex,
1850
3658
  setTotalPages,
1851
3659
  sum,
1852
3660
  systemVars,
1853
3661
  tableDesignBottom,
3662
+ tilePosition,
3663
+ toElectronPrintToPdfOptions,
3664
+ toFontFamilyStack,
3665
+ toPlaywrightPdfOptions,
3666
+ toPrintFailure,
1854
3667
  toUpperCaseAmount,
1855
- tokenize
3668
+ tokenize,
3669
+ validateTiling,
3670
+ withTimeout
1856
3671
  });