@canvas-components/print-design 0.1.1 → 0.1.2

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
@@ -727,6 +727,21 @@ function resolveTableDataSource(data, path) {
727
727
  }
728
728
 
729
729
  // src/model/table-options.ts
730
+ function resolveTableCellOverflow(column, element) {
731
+ return column.cellOverflow ?? (column.ellipsis == null ? element.cellOverflow ?? "wrap" : column.ellipsis ? "ellipsis" : "wrap");
732
+ }
733
+ function resolveTableCellPadding(column, element) {
734
+ const value = (side, fallback) => Math.max(
735
+ 0,
736
+ column.cellPadding?.[side] ?? element.cellPadding?.[side] ?? fallback
737
+ );
738
+ return {
739
+ top: value("top", 0),
740
+ right: value("right", 4),
741
+ bottom: value("bottom", 0),
742
+ left: value("left", 4)
743
+ };
744
+ }
730
745
  function resolvePrintTableOptions(element, data) {
731
746
  const pixelsPerMillimeter = 96 / 25.4;
732
747
  const columns = (items) => items.map((column) => ({
@@ -736,7 +751,8 @@ function resolvePrintTableOptions(element, data) {
736
751
  ([, value]) => value !== void 0
737
752
  )
738
753
  ) : void 0,
739
- ellipsis: column.ellipsis ?? element.cellOverflow === "ellipsis",
754
+ cellOverflow: resolveTableCellOverflow(column, element),
755
+ ellipsis: resolveTableCellOverflow(column, element) === "ellipsis",
740
756
  summary: element.showSummary === false ? false : column.summary,
741
757
  summaryTitle: element.showSummary === false ? void 0 : column.summaryTitle,
742
758
  children: column.children ? columns(column.children) : void 0
@@ -855,6 +871,249 @@ function computePrintTableSummaryValues(rows, columns) {
855
871
  return result;
856
872
  }
857
873
 
874
+ // src/model/table-layout.ts
875
+ function tableHeaderDepth(columns) {
876
+ return Math.max(
877
+ 0,
878
+ ...columns.filter((column) => !column.hidden).map(
879
+ (column) => 1 + tableHeaderDepth(column.children?.length ? column.children : [])
880
+ )
881
+ );
882
+ }
883
+ function tableHeaderCells(element) {
884
+ const depth = tableHeaderDepth(element.columns);
885
+ const cells = [];
886
+ const walk = (columns, level, start) => {
887
+ let index = start;
888
+ for (const column of columns) {
889
+ if (column.hidden) continue;
890
+ const children = column.children?.filter((item) => !item.hidden) ?? [];
891
+ const span = children.length ? tableLeafColumns(children).filter((item) => !item.hidden).length : 1;
892
+ if (!span) continue;
893
+ cells.push({
894
+ rowIndex: level,
895
+ columnIndex: index,
896
+ rowSpan: children.length ? 1 : depth - level,
897
+ colSpan: span,
898
+ column
899
+ });
900
+ if (children.length) walk(children, level + 1, index);
901
+ index += span;
902
+ }
903
+ };
904
+ walk(element.columns, 0, 0);
905
+ for (const merge of element.headerCells ?? []) {
906
+ const rowSpan = Math.max(1, merge.rowSpan ?? 1);
907
+ const colSpan = Math.max(1, merge.colSpan ?? 1);
908
+ if (rowSpan * colSpan < 2) continue;
909
+ const anchor = cells.find(
910
+ (cell) => cell.rowIndex === merge.rowIndex && cell.columnIndex === merge.columnIndex
911
+ );
912
+ if (!anchor) continue;
913
+ const inside = (cell) => cell.rowIndex >= merge.rowIndex && cell.rowIndex + cell.rowSpan <= merge.rowIndex + rowSpan && cell.columnIndex >= merge.columnIndex && cell.columnIndex + cell.colSpan <= merge.columnIndex + colSpan;
914
+ const intersects = (cell) => cell.rowIndex < merge.rowIndex + rowSpan && cell.rowIndex + cell.rowSpan > merge.rowIndex && cell.columnIndex < merge.columnIndex + colSpan && cell.columnIndex + cell.colSpan > merge.columnIndex;
915
+ if (cells.some((cell) => intersects(cell) && !inside(cell))) continue;
916
+ const retained = cells.filter((cell) => !intersects(cell));
917
+ cells.length = 0;
918
+ cells.push(...retained, { ...anchor, rowSpan, colSpan });
919
+ }
920
+ return cells.sort(
921
+ (a, b) => a.rowIndex - b.rowIndex || a.columnIndex - b.columnIndex
922
+ );
923
+ }
924
+ function tableContentHeight(element, rowCount, showSummary = true) {
925
+ const depth = element.showHeader === false ? 0 : tableHeaderDepth(element.columns);
926
+ const header = depth * (element.headerRowHeight ?? 9);
927
+ const rows = Math.max(
928
+ rowCount,
929
+ element.fillEmptyRows ? element.printRowCount ?? 0 : 0
930
+ );
931
+ const summary = showSummary && element.showSummary !== false && tableLeafColumns(element.columns).some(
932
+ (column) => !!column.summary || column.summaryTitle != null
933
+ ) ? element.summaryRowHeight ?? element.rowHeight ?? 8 : 0;
934
+ return header + rows * (element.rowHeight ?? 8) + summary;
935
+ }
936
+ function resolvedTableHeight(element, data = {}) {
937
+ const rows = resolveTableRows(element, data);
938
+ const limit = Math.max(0, Math.floor(element.printRowCount ?? 0));
939
+ if (element.rotation) return element.height;
940
+ if (!rows.length && !element.fillEmptyRows) return element.height;
941
+ return tableContentHeight(
942
+ element,
943
+ limit ? Math.min(rows.length, limit) : rows.length
944
+ );
945
+ }
946
+ function coveringTableCell(cells, rowIndex, columnIndex) {
947
+ let covering;
948
+ let largest = 0;
949
+ for (const cell of cells ?? []) {
950
+ const rowSpan = Math.max(1, cell.rowSpan ?? 1);
951
+ const colSpan = Math.max(1, cell.colSpan ?? 1);
952
+ if (rowIndex < cell.rowIndex || rowIndex >= cell.rowIndex + rowSpan || columnIndex < cell.columnIndex || columnIndex >= cell.columnIndex + colSpan)
953
+ continue;
954
+ if (rowSpan * colSpan > largest) {
955
+ covering = cell;
956
+ largest = rowSpan * colSpan;
957
+ }
958
+ }
959
+ return covering;
960
+ }
961
+ function resolveTableCells(element, rows, columns) {
962
+ const cells = [...element.cells ?? []];
963
+ const rule = element.mergeCell;
964
+ if (!rule || !rows.length || !columns.length) return cells;
965
+ const samePath = (left, right) => JSON.stringify(left) === JSON.stringify(right);
966
+ const valueKey = (value) => value == null ? "" : typeof value === "object" ? JSON.stringify(value) : String(value);
967
+ const overlaps = (row, column, rowSpan, colSpan) => cells.some(
968
+ (cell) => ((cell.rowSpan ?? 1) > 1 || (cell.colSpan ?? 1) > 1) && row < cell.rowIndex + Math.max(1, cell.rowSpan ?? 1) && row + rowSpan > cell.rowIndex && column < cell.columnIndex + Math.max(1, cell.colSpan ?? 1) && column + colSpan > cell.columnIndex
969
+ );
970
+ const rowRule = rule.row;
971
+ if (rowRule?.keys?.length) {
972
+ const targets = rowRule.columns?.length ? rowRule.columns : rowRule.keys;
973
+ const groupKey = (row) => JSON.stringify(
974
+ rowRule.keys.map((key) => valueKey(readTableCell(row, key)))
975
+ );
976
+ for (const [columnIndex, column] of columns.entries()) {
977
+ if (!targets.some((key) => samePath(column.dataIndex, key))) continue;
978
+ let rowIndex = 0;
979
+ while (rowIndex < rows.length) {
980
+ if (overlaps(rowIndex, columnIndex, 1, 1)) {
981
+ rowIndex++;
982
+ continue;
983
+ }
984
+ const current = groupKey(rows[rowIndex]);
985
+ let rowSpan = 1;
986
+ while (rowIndex + rowSpan < rows.length && groupKey(rows[rowIndex + rowSpan]) === current && !overlaps(rowIndex + rowSpan, columnIndex, 1, 1))
987
+ rowSpan++;
988
+ if (rowSpan > 1)
989
+ cells.push({
990
+ ...cells.find(
991
+ (cell) => cell.rowIndex === rowIndex && cell.columnIndex === columnIndex
992
+ ),
993
+ rowIndex,
994
+ columnIndex,
995
+ rowSpan
996
+ });
997
+ rowIndex += rowSpan;
998
+ }
999
+ }
1000
+ }
1001
+ const columnRule = rule.column;
1002
+ if (columnRule?.keys?.length) {
1003
+ for (const [rowIndex, row] of rows.entries()) {
1004
+ let columnIndex = 0;
1005
+ while (columnIndex < columns.length) {
1006
+ const column = columns[columnIndex];
1007
+ if (!columnRule.keys.some((key) => samePath(column.dataIndex, key)) || overlaps(rowIndex, columnIndex, 1, 1)) {
1008
+ columnIndex++;
1009
+ continue;
1010
+ }
1011
+ const current = valueKey(readTableCell(row, column.dataIndex));
1012
+ let colSpan = 1;
1013
+ while (columnIndex + colSpan < columns.length) {
1014
+ const nextIndex = columnIndex + colSpan;
1015
+ const next = columns[nextIndex];
1016
+ if (!columnRule.keys.some((key) => samePath(next.dataIndex, key)) || valueKey(readTableCell(row, next.dataIndex)) !== current || overlaps(rowIndex, nextIndex, 1, 1))
1017
+ break;
1018
+ colSpan++;
1019
+ }
1020
+ if (colSpan > 1)
1021
+ cells.push({
1022
+ ...cells.find(
1023
+ (cell) => cell.rowIndex === rowIndex && cell.columnIndex === columnIndex
1024
+ ),
1025
+ rowIndex,
1026
+ columnIndex,
1027
+ colSpan
1028
+ });
1029
+ columnIndex += colSpan;
1030
+ }
1031
+ }
1032
+ }
1033
+ return cells;
1034
+ }
1035
+ function expandTableCellSelection(cells, selection) {
1036
+ let firstRow = Math.min(selection.startRow, selection.endRow);
1037
+ let lastRow = Math.max(selection.startRow, selection.endRow);
1038
+ let firstColumn = Math.min(selection.startColumn, selection.endColumn);
1039
+ let lastColumn = Math.max(selection.startColumn, selection.endColumn);
1040
+ let changed = true;
1041
+ while (changed) {
1042
+ changed = false;
1043
+ for (const cell of cells ?? []) {
1044
+ const endRow = cell.rowIndex + Math.max(1, cell.rowSpan ?? 1) - 1;
1045
+ const endColumn = cell.columnIndex + Math.max(1, cell.colSpan ?? 1) - 1;
1046
+ if (cell.rowIndex > lastRow || endRow < firstRow || cell.columnIndex > lastColumn || endColumn < firstColumn)
1047
+ continue;
1048
+ const nextFirstRow = Math.min(firstRow, cell.rowIndex);
1049
+ const nextLastRow = Math.max(lastRow, endRow);
1050
+ const nextFirstColumn = Math.min(firstColumn, cell.columnIndex);
1051
+ const nextLastColumn = Math.max(lastColumn, endColumn);
1052
+ changed = changed || nextFirstRow !== firstRow || nextLastRow !== lastRow || nextFirstColumn !== firstColumn || nextLastColumn !== lastColumn;
1053
+ firstRow = nextFirstRow;
1054
+ lastRow = nextLastRow;
1055
+ firstColumn = nextFirstColumn;
1056
+ lastColumn = nextLastColumn;
1057
+ }
1058
+ }
1059
+ return { firstRow, lastRow, firstColumn, lastColumn };
1060
+ }
1061
+ function tableBorderVisible(element, data, cell, row) {
1062
+ const path = cell?.borderPath || (element.borderMode === "binding" ? element.borderPath : void 0);
1063
+ if (path) {
1064
+ const local = row ? resolvePrintDesignDataPath(row, path) : void 0;
1065
+ const value = local === void 0 ? resolvePrintDesignDataPath(data, path) : local;
1066
+ return value !== false && value !== 0 && value !== "" && value != null;
1067
+ }
1068
+ return element.borderMode !== "hide" && element.borderMode !== "binding";
1069
+ }
1070
+
1071
+ // src/rendering/elements/image.ts
1072
+ var drawImageElement = (element, context) => {
1073
+ const { ctx, x, y, width, height, onImageLoad } = context;
1074
+ const image = resolveImage(element.src, onImageLoad);
1075
+ if (image?.complete && image.naturalWidth > 0) {
1076
+ drawImageFit(ctx, image, x, y, width, height, element.fit ?? "contain");
1077
+ }
1078
+ };
1079
+ var imageCache = /* @__PURE__ */ new Map();
1080
+ var pendingImages = /* @__PURE__ */ new Set();
1081
+ function resolveImage(src, onLoad) {
1082
+ if (typeof Image === "undefined" || !src) return void 0;
1083
+ const cached = imageCache.get(src);
1084
+ if (cached) return cached;
1085
+ if (pendingImages.has(src)) return void 0;
1086
+ pendingImages.add(src);
1087
+ const image = new Image();
1088
+ image.onload = () => {
1089
+ pendingImages.delete(src);
1090
+ imageCache.set(src, image);
1091
+ onLoad?.();
1092
+ };
1093
+ image.onerror = () => pendingImages.delete(src);
1094
+ image.src = src;
1095
+ return void 0;
1096
+ }
1097
+ function drawImageFit(ctx, image, x, y, width, height, fit) {
1098
+ if (fit === "fill") {
1099
+ ctx.drawImage(image, x, y, width, height);
1100
+ return;
1101
+ }
1102
+ const ratio = fit === "cover" ? Math.max(width / image.naturalWidth, height / image.naturalHeight) : Math.min(width / image.naturalWidth, height / image.naturalHeight);
1103
+ const drawWidth = image.naturalWidth * ratio;
1104
+ const drawHeight = image.naturalHeight * ratio;
1105
+ const drawX = x + (width - drawWidth) / 2;
1106
+ const drawY = y + (height - drawHeight) / 2;
1107
+ ctx.save();
1108
+ if (fit === "cover") {
1109
+ ctx.beginPath();
1110
+ ctx.rect(x, y, width, height);
1111
+ ctx.clip();
1112
+ }
1113
+ ctx.drawImage(image, drawX, drawY, drawWidth, drawHeight);
1114
+ ctx.restore();
1115
+ }
1116
+
858
1117
  // src/rendering/elements/table.ts
859
1118
  function cellText(value) {
860
1119
  if (value == null) return "";
@@ -878,18 +1137,269 @@ function displayCell(value, column) {
878
1137
  const parts = formatted.split(".");
879
1138
  if (separator !== false)
880
1139
  parts[0] = parts[0].replace(
881
- /B(?=(d{3})+(?!d))/g,
1140
+ /\B(?=(\d{3})+(?!\d))/g,
882
1141
  typeof separator === "string" ? separator : ","
883
1142
  );
884
1143
  return (column.moneyFormat?.symbol ?? "") + parts.join(".");
885
1144
  }
886
- function drawTableElement(element, { ctx, x, y, width, scale, data }) {
1145
+ function wrapCellText(ctx, value, width) {
1146
+ const lines = [];
1147
+ for (const paragraph of value.split(/\r?\n/)) {
1148
+ let line = "";
1149
+ for (const character of paragraph) {
1150
+ if (line && ctx.measureText(line + character).width > width) {
1151
+ lines.push(line);
1152
+ line = "";
1153
+ }
1154
+ line += character;
1155
+ }
1156
+ lines.push(line);
1157
+ }
1158
+ return lines;
1159
+ }
1160
+ function ellipsisCellText(ctx, value, width) {
1161
+ if (ctx.measureText(value).width <= width) return value;
1162
+ const suffix = "...";
1163
+ if (ctx.measureText(suffix).width > width) return "";
1164
+ let text = "";
1165
+ for (const character of value) {
1166
+ if (ctx.measureText(text + character + suffix).width > width) break;
1167
+ text += character;
1168
+ }
1169
+ return text + suffix;
1170
+ }
1171
+ function drawTypedCell(ctx, column, value, x, y, width, height, onImageLoad) {
1172
+ const content = value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
1173
+ const type = typeof content?.type === "string" ? content.type : column.cellType;
1174
+ const payload = content?.value ?? content?.src ?? content?.text ?? value;
1175
+ if (!type || ["text", "emoji", "money", "link", "json"].includes(type))
1176
+ return false;
1177
+ const inset = Math.min(6, width / 8, height / 8);
1178
+ ctx.save();
1179
+ try {
1180
+ if (type === "chart") {
1181
+ if (typeof content?.chartType !== "string") return false;
1182
+ try {
1183
+ chart.drawCanvasChartToCanvas({
1184
+ ctx,
1185
+ x: x + inset,
1186
+ y: y + inset,
1187
+ width: Math.max(1, width - inset * 2),
1188
+ height: Math.max(1, height - inset * 2),
1189
+ type: content.chartType,
1190
+ option: content.option ?? payload,
1191
+ simple: true
1192
+ });
1193
+ } catch {
1194
+ return false;
1195
+ }
1196
+ return true;
1197
+ }
1198
+ if (type === "timeline") {
1199
+ const items = Array.isArray(content?.items) ? content.items : Array.isArray(payload) ? payload : [];
1200
+ if (!items.length) return false;
1201
+ ctx.strokeStyle = "#94a3b8";
1202
+ ctx.fillStyle = "#1677ff";
1203
+ ctx.beginPath();
1204
+ ctx.moveTo(x + inset + 3, y + inset);
1205
+ ctx.lineTo(x + inset + 3, y + height - inset);
1206
+ ctx.stroke();
1207
+ ctx.font = `${Math.min(12, Math.max(8, height / items.length / 2))}px sans-serif`;
1208
+ ctx.textAlign = "left";
1209
+ ctx.textBaseline = "middle";
1210
+ items.slice(0, Math.max(1, Math.floor(height / 10))).forEach((item, index, shown) => {
1211
+ const mid = y + (index + 0.5) * height / shown.length;
1212
+ ctx.beginPath();
1213
+ ctx.arc(x + inset + 3, mid, 2, 0, Math.PI * 2);
1214
+ ctx.fill();
1215
+ const label = item && typeof item === "object" ? cellText(
1216
+ item.title ?? item.content
1217
+ ) : cellText(item);
1218
+ ctx.fillText(
1219
+ label,
1220
+ x + inset + 10,
1221
+ mid,
1222
+ Math.max(1, width - inset * 2 - 10)
1223
+ );
1224
+ });
1225
+ return true;
1226
+ }
1227
+ if (type === "image") {
1228
+ drawImageElement(
1229
+ {
1230
+ id: "table-cell-image",
1231
+ type: "image",
1232
+ x: 0,
1233
+ y: 0,
1234
+ width: 1,
1235
+ height: 1,
1236
+ src: cellText(payload),
1237
+ fit: "contain"
1238
+ },
1239
+ {
1240
+ ctx,
1241
+ x: x + inset,
1242
+ y: y + inset,
1243
+ width: width - inset * 2,
1244
+ height: height - inset * 2,
1245
+ centerX: x + width / 2,
1246
+ centerY: y + height / 2,
1247
+ scale: 1,
1248
+ data: {},
1249
+ onImageLoad
1250
+ }
1251
+ );
1252
+ return true;
1253
+ }
1254
+ if (type === "qrcode") {
1255
+ const size = Math.max(1, Math.min(width, height) - inset * 2);
1256
+ try {
1257
+ qrcode.drawQrCodeToCanvas({
1258
+ ctx,
1259
+ x: x + (width - size) / 2,
1260
+ y: y + (height - size) / 2,
1261
+ width: size,
1262
+ height: size,
1263
+ value: cellText(payload)
1264
+ });
1265
+ } catch {
1266
+ return false;
1267
+ }
1268
+ return true;
1269
+ }
1270
+ if (type === "barcode") {
1271
+ try {
1272
+ barcode.drawBarcodeToCanvas({
1273
+ ctx,
1274
+ x: x + inset,
1275
+ y: y + inset,
1276
+ width: Math.max(1, width - inset * 2),
1277
+ height: Math.max(1, height - inset * 2),
1278
+ value: cellText(payload),
1279
+ format: "code128",
1280
+ displayValue: false
1281
+ });
1282
+ } catch {
1283
+ return false;
1284
+ }
1285
+ return true;
1286
+ }
1287
+ if (type === "color") {
1288
+ ctx.fillStyle = cellText(payload) || "transparent";
1289
+ ctx.fillRect(
1290
+ x + inset,
1291
+ y + inset,
1292
+ Math.max(0, width - inset * 2),
1293
+ Math.max(0, height - inset * 2)
1294
+ );
1295
+ return true;
1296
+ }
1297
+ if (type === "progress") {
1298
+ const percent = Math.max(0, Math.min(100, Number(payload) || 0));
1299
+ const barY = y + height / 2 - 3;
1300
+ ctx.fillStyle = "#e5e7eb";
1301
+ ctx.fillRect(x + inset, barY, Math.max(0, width - inset * 2), 6);
1302
+ ctx.fillStyle = "#1677ff";
1303
+ ctx.fillRect(
1304
+ x + inset,
1305
+ barY,
1306
+ Math.max(0, width - inset * 2) * percent / 100,
1307
+ 6
1308
+ );
1309
+ return true;
1310
+ }
1311
+ if (["checkbox", "radio", "switch"].includes(type)) {
1312
+ const checked = payload === true || payload === 1 || payload === "true";
1313
+ const size = Math.min(14, height - inset * 2);
1314
+ const centerX = x + width / 2, centerY = y + height / 2;
1315
+ ctx.strokeStyle = checked ? "#1677ff" : "#6b7280";
1316
+ ctx.fillStyle = checked ? "#1677ff" : "#ffffff";
1317
+ ctx.lineWidth = 1.5;
1318
+ if (type === "radio") {
1319
+ ctx.beginPath();
1320
+ ctx.arc(centerX, centerY, size / 2, 0, Math.PI * 2);
1321
+ ctx.stroke();
1322
+ if (checked) {
1323
+ ctx.beginPath();
1324
+ ctx.arc(centerX, centerY, size / 4, 0, Math.PI * 2);
1325
+ ctx.fill();
1326
+ }
1327
+ } else if (type === "switch") {
1328
+ ctx.fillRect(
1329
+ centerX - size,
1330
+ centerY - size / 3,
1331
+ size * 2,
1332
+ size * 2 / 3
1333
+ );
1334
+ ctx.fillStyle = "#ffffff";
1335
+ ctx.beginPath();
1336
+ ctx.arc(
1337
+ centerX + (checked ? size / 2 : -size / 2),
1338
+ centerY,
1339
+ size / 3,
1340
+ 0,
1341
+ Math.PI * 2
1342
+ );
1343
+ ctx.fill();
1344
+ } else {
1345
+ ctx.strokeRect(centerX - size / 2, centerY - size / 2, size, size);
1346
+ if (checked) {
1347
+ ctx.fillStyle = "#1677ff";
1348
+ ctx.fillRect(
1349
+ centerX - size / 2 + 3,
1350
+ centerY - size / 2 + 3,
1351
+ size - 6,
1352
+ size - 6
1353
+ );
1354
+ }
1355
+ }
1356
+ return true;
1357
+ }
1358
+ if (type === "rate") {
1359
+ ctx.fillStyle = "#f59e0b";
1360
+ ctx.textAlign = "center";
1361
+ ctx.textBaseline = "middle";
1362
+ ctx.fillText(
1363
+ "\u2605".repeat(Math.max(0, Math.min(5, Math.round(Number(payload) || 0)))) + "\u2606".repeat(
1364
+ 5 - Math.max(0, Math.min(5, Math.round(Number(payload) || 0)))
1365
+ ),
1366
+ x + width / 2,
1367
+ y + height / 2,
1368
+ width - inset * 2
1369
+ );
1370
+ return true;
1371
+ }
1372
+ if (type === "tag" || type === "button") {
1373
+ ctx.fillStyle = type === "tag" ? "#e6f4ff" : "#1677ff";
1374
+ ctx.fillRect(
1375
+ x + inset,
1376
+ y + inset,
1377
+ Math.max(0, width - inset * 2),
1378
+ Math.max(0, height - inset * 2)
1379
+ );
1380
+ ctx.fillStyle = type === "tag" ? "#1677ff" : "#ffffff";
1381
+ ctx.textAlign = "center";
1382
+ ctx.textBaseline = "middle";
1383
+ ctx.fillText(
1384
+ cellText(payload),
1385
+ x + width / 2,
1386
+ y + height / 2,
1387
+ width - inset * 4
1388
+ );
1389
+ return true;
1390
+ }
1391
+ return false;
1392
+ } finally {
1393
+ ctx.restore();
1394
+ }
1395
+ }
1396
+ function drawTableElement(element, { ctx, x, y, width, scale, data, onImageLoad }) {
887
1397
  const pxPerMm = 96 / 25.4;
888
1398
  const zoom = scale / pxPerMm;
889
1399
  const options = resolvePrintTableOptions(element, data);
890
- const columns = tableLeafColumns(options.columns).filter(
891
- (column) => column.hidden !== true
892
- );
1400
+ const columns = tableLeafColumns(
1401
+ options.columns.filter((column) => !column.hidden)
1402
+ ).filter((column) => column.hidden !== true);
893
1403
  if (!columns.length) return [];
894
1404
  const records = options.dataSource;
895
1405
  const count = Math.max(0, Math.floor(element.printRowCount ?? 0));
@@ -901,6 +1411,7 @@ function drawTableElement(element, { ctx, x, y, width, scale, data }) {
901
1411
  () => ({})
902
1412
  )
903
1413
  ];
1414
+ const resolvedCells = resolveTableCells(element, records, columns);
904
1415
  const summary = element.showSummary === false ? /* @__PURE__ */ new Map() : computePrintTableSummaryValues(records, columns);
905
1416
  const availableWidth = width / zoom;
906
1417
  const configured = columns.map((column) => Math.max(1, column.width ?? 100));
@@ -908,57 +1419,151 @@ function drawTableElement(element, { ctx, x, y, width, scale, data }) {
908
1419
  const widths = configured.map(
909
1420
  (value) => availableWidth * value / totalWidth
910
1421
  );
911
- const headerHeight = options.headerRowHeight ?? 0;
1422
+ const headerRowHeight = options.headerRowHeight ?? 0;
1423
+ const headerDepth = headerRowHeight ? tableHeaderDepth(options.columns) : 0;
1424
+ const headerHeight = headerRowHeight * headerDepth;
912
1425
  const rowHeight = options.rowHeight;
913
1426
  const summaryHeight = options.summaryRowHeight ?? rowHeight;
914
1427
  const borderWidth = options.styles.cell.borderWidth ?? 1;
915
1428
  const borderColor = options.styles.cell.borderColor;
916
1429
  const fontSize = options.theme.fontSize;
917
1430
  const cells = [];
918
- const paint = (value, column, left, top, cellWidth, cellHeight, zone) => {
1431
+ const paint = (value, column, left, top, cellWidth, cellHeight, zone, cell, row, rawValue) => {
919
1432
  const style = options.styles[zone];
920
- ctx.fillStyle = column[zone === "header" ? "headerStyle" : zone === "summary" ? "summaryStyle" : "cellStyle"]?.backgroundColor ?? style.backgroundColor;
1433
+ ctx.fillStyle = cell?.backgroundColor ?? column[zone === "header" ? "headerStyle" : zone === "summary" ? "summaryStyle" : "cellStyle"]?.backgroundColor ?? style.backgroundColor;
921
1434
  ctx.fillRect(left, top, cellWidth, cellHeight);
922
- if (borderWidth > 0) {
923
- ctx.strokeStyle = borderColor;
1435
+ if (borderWidth > 0 && tableBorderVisible(element, data, cell, row)) {
1436
+ ctx.strokeStyle = cell?.borderColor ?? borderColor;
924
1437
  ctx.lineWidth = borderWidth;
925
1438
  ctx.strokeRect(left, top, cellWidth, cellHeight);
926
1439
  }
927
- ctx.fillStyle = column[zone === "header" ? "headerStyle" : zone === "summary" ? "summaryStyle" : "cellStyle"]?.color ?? style.color ?? "#000000";
1440
+ ctx.fillStyle = cell?.color ?? column[zone === "header" ? "headerStyle" : zone === "summary" ? "summaryStyle" : "cellStyle"]?.color ?? style.color ?? "#000000";
1441
+ if (zone === "body" && column.cellType === "link" && !cell?.color)
1442
+ ctx.fillStyle = "#1677ff";
928
1443
  ctx.font = fontSize + "px sans-serif";
929
1444
  ctx.textBaseline = "middle";
930
- ctx.textAlign = zone === "header" ? column.headerAlign ?? column.align ?? element.cellAlign ?? "left" : column.align ?? element.cellAlign ?? "left";
931
- const inset = 4;
932
- const textX = ctx.textAlign === "right" ? left + cellWidth - inset : ctx.textAlign === "center" ? left + cellWidth / 2 : left + inset;
933
- ctx.fillText(
934
- value,
935
- textX,
936
- top + cellHeight / 2,
937
- Math.max(0, cellWidth - 2 * inset)
938
- );
1445
+ ctx.textAlign = zone === "header" ? column.headerAlign ?? column.align ?? element.cellAlign ?? "left" : column.align ?? column.cellStyle?.textAlign ?? element.cellAlign ?? "left";
1446
+ const {
1447
+ top: topPadding,
1448
+ right: rightPadding,
1449
+ bottom: bottomPadding,
1450
+ left: leftPadding
1451
+ } = resolveTableCellPadding(column, element);
1452
+ const contentWidth = Math.max(0, cellWidth - leftPadding - rightPadding);
1453
+ const contentHeight = Math.max(0, cellHeight - topPadding - bottomPadding);
1454
+ if (!contentWidth || !contentHeight) return;
1455
+ if (cell?.diagonal) {
1456
+ ctx.save();
1457
+ ctx.strokeStyle = cell.borderColor ?? borderColor;
1458
+ ctx.lineWidth = Math.max(borderWidth, 1);
1459
+ ctx.beginPath();
1460
+ if (cell.diagonal === "down") {
1461
+ ctx.moveTo(left, top);
1462
+ ctx.lineTo(left + cellWidth, top + cellHeight);
1463
+ } else {
1464
+ ctx.moveTo(left, top + cellHeight);
1465
+ ctx.lineTo(left + cellWidth, top);
1466
+ }
1467
+ ctx.stroke();
1468
+ ctx.textAlign = "left";
1469
+ ctx.textBaseline = "top";
1470
+ ctx.fillText(
1471
+ cell.diagonalStart ?? value,
1472
+ left + leftPadding,
1473
+ top + topPadding
1474
+ );
1475
+ ctx.textAlign = "right";
1476
+ ctx.textBaseline = "bottom";
1477
+ ctx.fillText(
1478
+ cell.diagonalEnd ?? "",
1479
+ left + cellWidth - rightPadding,
1480
+ top + cellHeight - bottomPadding
1481
+ );
1482
+ ctx.restore();
1483
+ return;
1484
+ }
1485
+ if (zone === "body" && drawTypedCell(
1486
+ ctx,
1487
+ column,
1488
+ rawValue,
1489
+ left,
1490
+ top,
1491
+ cellWidth,
1492
+ cellHeight,
1493
+ onImageLoad
1494
+ ))
1495
+ return;
1496
+ const overflow = column.cellOverflow ?? "wrap";
1497
+ const singleLine = value.replace(/\r?\n/g, " ");
1498
+ const lines = overflow === "wrap" ? wrapCellText(ctx, value, contentWidth) : [
1499
+ overflow === "ellipsis" ? ellipsisCellText(ctx, singleLine, contentWidth) : singleLine
1500
+ ];
1501
+ const lineHeight = fontSize * 1.2;
1502
+ const vertical = column.cellStyle?.verticalAlign ?? element.cellVerticalAlign ?? "middle";
1503
+ const firstY = vertical === "top" || lines.length * lineHeight > contentHeight ? top + topPadding + lineHeight / 2 : vertical === "bottom" ? top + cellHeight - bottomPadding - lines.length * lineHeight + lineHeight / 2 : top + topPadding + (contentHeight - lines.length * lineHeight) / 2 + lineHeight / 2;
1504
+ const textX = ctx.textAlign === "right" ? left + cellWidth - rightPadding : ctx.textAlign === "center" ? left + leftPadding + contentWidth / 2 : left + leftPadding;
1505
+ ctx.save();
1506
+ ctx.beginPath();
1507
+ ctx.rect(left + leftPadding, top + topPadding, contentWidth, contentHeight);
1508
+ ctx.clip();
1509
+ lines.forEach((line, index) => {
1510
+ if (overflow === "shrink")
1511
+ ctx.fillText(line, textX, firstY + index * lineHeight, contentWidth);
1512
+ else ctx.fillText(line, textX, firstY + index * lineHeight);
1513
+ });
1514
+ ctx.restore();
939
1515
  };
940
1516
  ctx.save();
941
1517
  try {
942
1518
  ctx.translate(x, y);
943
1519
  ctx.scale(zoom, zoom);
944
1520
  let left = 0;
945
- if (headerHeight > 0)
946
- for (const [index, column] of columns.entries()) {
1521
+ if (headerHeight > 0) {
1522
+ for (const header of tableHeaderCells(element)) {
1523
+ const cellLeft = widths.slice(0, header.columnIndex).reduce((sum, value) => sum + value, 0);
1524
+ const cellWidth = widths.slice(header.columnIndex, header.columnIndex + header.colSpan).reduce((sum, value) => sum + value, 0);
1525
+ const rect = {
1526
+ x: cellLeft,
1527
+ y: header.rowIndex * headerRowHeight,
1528
+ width: cellWidth,
1529
+ height: header.rowSpan * headerRowHeight
1530
+ };
947
1531
  paint(
948
- cellText(column.title ?? column.key ?? ""),
949
- column,
950
- left,
951
- 0,
952
- widths[index],
953
- headerHeight,
1532
+ cellText(header.column.title ?? header.column.key ?? ""),
1533
+ header.column,
1534
+ rect.x,
1535
+ rect.y,
1536
+ rect.width,
1537
+ rect.height,
954
1538
  "header"
955
1539
  );
956
- left += widths[index];
1540
+ cells.push({
1541
+ area: "header",
1542
+ rowIndex: header.rowIndex,
1543
+ columnIndex: header.columnIndex,
1544
+ column: header.column,
1545
+ rect
1546
+ });
957
1547
  }
1548
+ }
958
1549
  for (const [rowIndex, row] of visibleRows.entries()) {
959
1550
  left = 0;
960
1551
  for (const [index, column] of columns.entries()) {
1552
+ const covering = coveringTableCell(resolvedCells, rowIndex, index);
1553
+ if (covering && (covering.rowIndex !== rowIndex || covering.columnIndex !== index)) {
1554
+ left += widths[index];
1555
+ continue;
1556
+ }
961
1557
  const top = headerHeight + rowIndex * rowHeight;
1558
+ const colSpan = Math.min(
1559
+ Math.max(1, covering?.colSpan ?? 1),
1560
+ columns.length - index
1561
+ );
1562
+ const rowSpan = Math.min(
1563
+ Math.max(1, covering?.rowSpan ?? 1),
1564
+ visibleRows.length - rowIndex
1565
+ );
1566
+ const cellWidth = widths.slice(index, index + colSpan).reduce((sum, value2) => sum + value2, 0);
962
1567
  const value = rowIndex < records.length ? readTableCell(row, column.dataIndex ?? column.key) : void 0;
963
1568
  const rendered = rowIndex < records.length ? column.render?.(value, row, rowIndex, column) : void 0;
964
1569
  paint(
@@ -966,15 +1571,25 @@ function drawTableElement(element, { ctx, x, y, width, scale, data }) {
966
1571
  column,
967
1572
  left,
968
1573
  top,
969
- widths[index],
970
- rowHeight,
971
- "body"
1574
+ cellWidth,
1575
+ rowHeight * rowSpan,
1576
+ "body",
1577
+ covering,
1578
+ row,
1579
+ rendered ?? value
972
1580
  );
973
1581
  if (rowIndex < records.length)
974
1582
  cells.push({
1583
+ area: "body",
975
1584
  rowIndex,
1585
+ columnIndex: index,
976
1586
  column,
977
- rect: { x: left, y: top, width: widths[index], height: rowHeight }
1587
+ rect: {
1588
+ x: left,
1589
+ y: top,
1590
+ width: cellWidth,
1591
+ height: rowHeight * rowSpan
1592
+ }
978
1593
  });
979
1594
  left += widths[index];
980
1595
  }
@@ -2405,52 +3020,6 @@ var drawDividerElement = (element, context) => {
2405
3020
  }
2406
3021
  ctx.stroke();
2407
3022
  };
2408
-
2409
- // src/rendering/elements/image.ts
2410
- var drawImageElement = (element, context) => {
2411
- const { ctx, x, y, width, height, onImageLoad } = context;
2412
- const image = resolveImage(element.src, onImageLoad);
2413
- if (image?.complete && image.naturalWidth > 0) {
2414
- drawImageFit(ctx, image, x, y, width, height, element.fit ?? "contain");
2415
- }
2416
- };
2417
- var imageCache = /* @__PURE__ */ new Map();
2418
- var pendingImages = /* @__PURE__ */ new Set();
2419
- function resolveImage(src, onLoad) {
2420
- if (typeof Image === "undefined" || !src) return void 0;
2421
- const cached = imageCache.get(src);
2422
- if (cached) return cached;
2423
- if (pendingImages.has(src)) return void 0;
2424
- pendingImages.add(src);
2425
- const image = new Image();
2426
- image.onload = () => {
2427
- pendingImages.delete(src);
2428
- imageCache.set(src, image);
2429
- onLoad?.();
2430
- };
2431
- image.onerror = () => pendingImages.delete(src);
2432
- image.src = src;
2433
- return void 0;
2434
- }
2435
- function drawImageFit(ctx, image, x, y, width, height, fit) {
2436
- if (fit === "fill") {
2437
- ctx.drawImage(image, x, y, width, height);
2438
- return;
2439
- }
2440
- const ratio = fit === "cover" ? Math.max(width / image.naturalWidth, height / image.naturalHeight) : Math.min(width / image.naturalWidth, height / image.naturalHeight);
2441
- const drawWidth = image.naturalWidth * ratio;
2442
- const drawHeight = image.naturalHeight * ratio;
2443
- const drawX = x + (width - drawWidth) / 2;
2444
- const drawY = y + (height - drawHeight) / 2;
2445
- ctx.save();
2446
- if (fit === "cover") {
2447
- ctx.beginPath();
2448
- ctx.rect(x, y, width, height);
2449
- ctx.clip();
2450
- }
2451
- ctx.drawImage(image, drawX, drawY, drawWidth, drawHeight);
2452
- ctx.restore();
2453
- }
2454
3023
  var drawBarcodeElement = (element, context) => {
2455
3024
  const { ctx, x, y, width, height, data } = context;
2456
3025
  try {
@@ -2762,7 +3331,8 @@ function drawPrintDesignElement(ctx, element, scale, data = {}, onImageLoad) {
2762
3331
  const x = element.x * scale;
2763
3332
  const y = (element.y - (element.overflowCrop?.offset ?? 0)) * scale;
2764
3333
  const width = element.width * scale;
2765
- const height = (element.overflowCrop?.fullHeight ?? element.height) * scale;
3334
+ const visualHeight = element.type === "table" ? resolvedTableHeight(element, data) : element.height;
3335
+ const height = (element.overflowCrop?.fullHeight ?? visualHeight) * scale;
2766
3336
  const centerX = x + width / 2;
2767
3337
  const centerY = y + height / 2;
2768
3338
  ctx.save();
@@ -2853,11 +3423,12 @@ function hitTestPrintDesignPage(page, point, tolerance = 0, data = {}) {
2853
3423
  for (let index = page.elements.length - 1; index >= 0; index -= 1) {
2854
3424
  const element = page.elements[index];
2855
3425
  if (!resolvePrintDesignElementVisible(element, data)) continue;
3426
+ const visualHeight = element.type === "table" ? resolvedTableHeight(element, data) : element.height;
2856
3427
  const localPoint = rotatePoint(
2857
3428
  point,
2858
3429
  {
2859
3430
  x: element.x + element.width / 2,
2860
- y: element.y + element.height / 2
3431
+ y: element.y + visualHeight / 2
2861
3432
  },
2862
3433
  -((element.rotation ?? 0) * Math.PI) / 180
2863
3434
  );
@@ -2884,7 +3455,7 @@ function hitTestPrintDesignPage(page, point, tolerance = 0, data = {}) {
2884
3455
  }
2885
3456
  continue;
2886
3457
  }
2887
- if (localPoint.x >= element.x && localPoint.x <= element.x + element.width && localPoint.y >= element.y && localPoint.y <= element.y + element.height) {
3458
+ if (localPoint.x >= element.x && localPoint.x <= element.x + element.width && localPoint.y >= element.y && localPoint.y <= element.y + visualHeight) {
2888
3459
  return element;
2889
3460
  }
2890
3461
  }
@@ -3137,6 +3708,223 @@ function resolveFontSize(value, max) {
3137
3708
  return Number.isFinite(value) ? Math.max(8, Math.min(max, value)) : 9;
3138
3709
  }
3139
3710
 
3711
+ // src/interaction/geometry.ts
3712
+ var RESIZE_HANDLES = [
3713
+ "nw",
3714
+ "ne",
3715
+ "se",
3716
+ "sw",
3717
+ "n",
3718
+ "s",
3719
+ "w",
3720
+ "e"
3721
+ ];
3722
+ function rotatePoint2(point, center, radians) {
3723
+ const x = point.x - center.x;
3724
+ const y = point.y - center.y;
3725
+ const cosine = Math.cos(radians);
3726
+ const sine = Math.sin(radians);
3727
+ return {
3728
+ x: center.x + x * cosine - y * sine,
3729
+ y: center.y + x * sine + y * cosine
3730
+ };
3731
+ }
3732
+ function getSelectionRect(element, scale, data = {}) {
3733
+ const minimumSize = 12 / scale;
3734
+ const width = Math.max(element.width, minimumSize);
3735
+ const visualHeight = element.type === "table" ? resolvedTableHeight(element, data) : element.height;
3736
+ const height = Math.max(visualHeight, minimumSize);
3737
+ return {
3738
+ x: element.x - (width - element.width) / 2,
3739
+ y: element.y - (height - visualHeight) / 2,
3740
+ width,
3741
+ height
3742
+ };
3743
+ }
3744
+ function getRotatedRectangleCorner(rect, rotation, handle) {
3745
+ const x = handle.includes("w") ? rect.x : handle.includes("e") ? rect.x + rect.width : rect.x + rect.width / 2;
3746
+ const y = handle.includes("n") ? rect.y : handle.includes("s") ? rect.y + rect.height : rect.y + rect.height / 2;
3747
+ return rotatePoint2(
3748
+ { x, y },
3749
+ { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 },
3750
+ rotation * Math.PI / 180
3751
+ );
3752
+ }
3753
+
3754
+ // src/helper/measurement-layout.ts
3755
+ function measurementObstacles(elements, scale) {
3756
+ return elements.flatMap((element) => {
3757
+ const rect = getSelectionRect(element, scale);
3758
+ const angle = element.rotation ?? 0;
3759
+ const corners = RESIZE_HANDLES.map(
3760
+ (handle) => getRotatedRectangleCorner(rect, angle, handle)
3761
+ );
3762
+ const center = { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };
3763
+ const stem = rotatePoint2(
3764
+ { x: center.x, y: rect.y },
3765
+ center,
3766
+ angle * Math.PI / 180
3767
+ );
3768
+ const rotate = rotatePoint2(
3769
+ { x: center.x, y: rect.y - 17 / scale },
3770
+ center,
3771
+ angle * Math.PI / 180
3772
+ );
3773
+ return [
3774
+ ...[...corners, rotate].map((p) => ({
3775
+ x: p.x * scale - 10,
3776
+ y: p.y * scale - 10,
3777
+ width: 20,
3778
+ height: 20
3779
+ })),
3780
+ {
3781
+ x: Math.min(stem.x, rotate.x) * scale - 5,
3782
+ y: Math.min(stem.y, rotate.y) * scale - 5,
3783
+ width: Math.abs(stem.x - rotate.x) * scale + 10,
3784
+ height: Math.abs(stem.y - rotate.y) * scale + 10
3785
+ }
3786
+ ];
3787
+ });
3788
+ }
3789
+ function placeMeasurement(line, textWidth, obstacles, viewport) {
3790
+ const horizontal = line.y1 === line.y2;
3791
+ const intersects = (a, b) => a.x <= b.x + b.width && a.x + a.width >= b.x && a.y <= b.y + b.height && a.y + a.height >= b.y;
3792
+ const limit = horizontal ? viewport.height : viewport.width;
3793
+ for (let step = 0; step <= Math.ceil(limit / 16); step++) {
3794
+ for (const sign of step === 0 ? [1] : [1, -1]) {
3795
+ const offset = step * 16 * sign;
3796
+ const x1 = line.x1 + (horizontal ? 0 : offset), x2 = line.x2 + (horizontal ? 0 : offset);
3797
+ const y1 = line.y1 + (horizontal ? offset : 0), y2 = line.y2 + (horizontal ? offset : 0);
3798
+ const textX = (x1 + x2) / 2 + 4, textY = (y1 + y2) / 2 - 4;
3799
+ const boxes = [
3800
+ {
3801
+ x: Math.min(x1, x2) - 3,
3802
+ y: Math.min(y1, y2) - 3,
3803
+ width: Math.abs(x2 - x1) + 6,
3804
+ height: Math.abs(y2 - y1) + 6
3805
+ },
3806
+ { x: textX - 2, y: textY - 12, width: textWidth + 4, height: 16 }
3807
+ ];
3808
+ if (boxes.some(
3809
+ (b) => b.x < 0 || b.y < 0 || b.x + b.width > viewport.width || b.y + b.height > viewport.height
3810
+ ))
3811
+ continue;
3812
+ if (boxes.some((b) => obstacles.some((o) => intersects(b, o)))) continue;
3813
+ return { x1, y1, x2, y2, textX, textY };
3814
+ }
3815
+ }
3816
+ return null;
3817
+ }
3818
+
3819
+ // src/helper/alignment.ts
3820
+ function elementBounds(element) {
3821
+ const angle = (element.rotation ?? 0) * Math.PI / 180;
3822
+ const width = Math.abs(element.width * Math.cos(angle)) + Math.abs(element.height * Math.sin(angle));
3823
+ const height = Math.abs(element.width * Math.sin(angle)) + Math.abs(element.height * Math.cos(angle));
3824
+ return {
3825
+ x: element.x + (element.width - width) / 2,
3826
+ y: element.y + (element.height - height) / 2,
3827
+ width,
3828
+ height
3829
+ };
3830
+ }
3831
+ function snapCoordinate(value, targets, tolerance) {
3832
+ return targets.reduce(
3833
+ (best, target) => Math.abs(target - value) <= tolerance && Math.abs(target - value) < Math.abs(best - value) ? target : best,
3834
+ targets.find((target) => Math.abs(target - value) <= tolerance) ?? value
3835
+ );
3836
+ }
3837
+ function guideTargets(page, axis) {
3838
+ const vertical = axis === "vertical";
3839
+ const size = vertical ? page.width : page.height;
3840
+ const margins = page.margins ?? { top: 10, right: 10, bottom: 10, left: 10 };
3841
+ return [
3842
+ 0,
3843
+ size / 2,
3844
+ size,
3845
+ vertical ? margins.left : margins.top,
3846
+ size - (vertical ? margins.right : margins.bottom),
3847
+ ...page.elements.filter((e) => e.visible !== false).flatMap((e) => {
3848
+ const b = elementBounds(e);
3849
+ const start = vertical ? b.x : b.y, length = vertical ? b.width : b.height;
3850
+ return [start, start + length / 2, start + length];
3851
+ })
3852
+ ];
3853
+ }
3854
+ function nearestMeasurements(page, selectedIds) {
3855
+ const selected = page.elements.filter((e) => selectedIds.includes(e.id) && e.visible !== false).map(elementBounds);
3856
+ if (!selected.length) return [];
3857
+ const left = Math.min(...selected.map((b) => b.x)), top = Math.min(...selected.map((b) => b.y));
3858
+ const right = Math.max(...selected.map((b) => b.x + b.width)), bottom = Math.max(...selected.map((b) => b.y + b.height));
3859
+ const result = /* @__PURE__ */ new Map();
3860
+ const add = (key, x1, y1, x2, y2) => {
3861
+ const distance = Math.hypot(x2 - x1, y2 - y1);
3862
+ if (!result.has(key) || distance < result.get(key).distance)
3863
+ result.set(key, { x1, y1, x2, y2, distance });
3864
+ };
3865
+ for (const e of page.elements) {
3866
+ if (selectedIds.includes(e.id) || e.visible === false) continue;
3867
+ const b = elementBounds(e), r = b.x + b.width, d = b.y + b.height;
3868
+ if (b.y <= bottom && d >= top) {
3869
+ const y = (Math.max(top, b.y) + Math.min(bottom, d)) / 2;
3870
+ if (r <= left) add("left", r, y, left, y);
3871
+ if (b.x >= right) add("right", right, y, b.x, y);
3872
+ }
3873
+ if (b.x <= right && r >= left) {
3874
+ const x = (Math.max(left, b.x) + Math.min(right, r)) / 2;
3875
+ if (d <= top) add("top", x, d, x, top);
3876
+ if (b.y >= bottom) add("bottom", x, bottom, x, b.y);
3877
+ }
3878
+ }
3879
+ return [...result.values()];
3880
+ }
3881
+ function drawMeasurements(context, page, selectedIds, scale, unit = "mm") {
3882
+ context.save();
3883
+ context.strokeStyle = "#e11d48";
3884
+ context.fillStyle = "#e11d48";
3885
+ context.font = "11px sans-serif";
3886
+ context.setLineDash([]);
3887
+ context.lineWidth = 1;
3888
+ const obstacles = measurementObstacles(
3889
+ page.elements.filter(
3890
+ (e) => selectedIds.includes(e.id) && e.visible !== false
3891
+ ),
3892
+ scale
3893
+ );
3894
+ context.textAlign = "left";
3895
+ context.textBaseline = "alphabetic";
3896
+ for (const m of nearestMeasurements(page, selectedIds)) {
3897
+ const label = formatDisplayUnit(m.distance, unit);
3898
+ const placement = placeMeasurement(
3899
+ {
3900
+ x1: m.x1 * scale,
3901
+ y1: m.y1 * scale,
3902
+ x2: m.x2 * scale,
3903
+ y2: m.y2 * scale
3904
+ },
3905
+ context.measureText(label).width,
3906
+ obstacles,
3907
+ { width: page.width * scale, height: page.height * scale }
3908
+ );
3909
+ if (!placement) continue;
3910
+ const { x1, y1, x2, y2, textX, textY } = placement;
3911
+ const horizontal = y1 === y2;
3912
+ context.beginPath();
3913
+ context.moveTo(x1, y1);
3914
+ context.lineTo(x2, y2);
3915
+ for (const [x, y] of [
3916
+ [x1, y1],
3917
+ [x2, y2]
3918
+ ]) {
3919
+ context.moveTo(x - (horizontal ? 0 : 3), y - (horizontal ? 3 : 0));
3920
+ context.lineTo(x + (horizontal ? 0 : 3), y + (horizontal ? 3 : 0));
3921
+ }
3922
+ context.stroke();
3923
+ context.fillText(label, textX, textY);
3924
+ }
3925
+ context.restore();
3926
+ }
3927
+
3140
3928
  // src/model/pagination.ts
3141
3929
  var MAX_PAGES = 1e3;
3142
3930
  var PT_TO_MM = 25.4 / 72;
@@ -3244,7 +4032,7 @@ function paginatePrintDesignDocument(document, data = {}) {
3244
4032
  bottom,
3245
4033
  pageAt
3246
4034
  );
3247
- shift = Math.max(shift, result.logicalEnd - originalEnd);
4035
+ shift = result.logicalEnd - originalEnd;
3248
4036
  continue;
3249
4037
  }
3250
4038
  if (element.type === "text") {
@@ -3285,8 +4073,16 @@ function splitTable(element, data, startPage, startY, top, bottom, pageAt) {
3285
4073
  const rows = resolveTableRows(element, data);
3286
4074
  const limit = Math.max(0, Math.floor(element.printRowCount ?? 0));
3287
4075
  const records = limit ? rows.slice(0, limit) : rows;
4076
+ const visibleRows = element.fillEmptyRows && limit > records.length ? [
4077
+ ...records,
4078
+ ...Array.from({ length: limit - records.length }, () => ({}))
4079
+ ] : records;
4080
+ const columns = tableLeafColumns(element.columns).filter(
4081
+ (column) => !column.hidden
4082
+ );
4083
+ const resolvedCells = resolveTableCells(element, records, columns);
3288
4084
  const rowHeight = Math.max(1, element.rowHeight ?? 8);
3289
- const header = element.showHeader === false ? 0 : Math.max(1, element.headerRowHeight ?? 9);
4085
+ const header = element.showHeader === false ? 0 : tableHeaderDepth(element.columns) * Math.max(1, element.headerRowHeight ?? 9);
3290
4086
  const hasSummary = element.showSummary !== false && tableLeafColumns(element.columns).some(
3291
4087
  (column) => !!column.summary || column.summaryTitle != null
3292
4088
  );
@@ -3296,16 +4092,29 @@ function splitTable(element, data, startPage, startY, top, bottom, pageAt) {
3296
4092
  let offset = 0;
3297
4093
  do {
3298
4094
  let room = bottom - y;
3299
- if (room < header + Math.min(rowHeight, records.length ? rowHeight : 0) && y > top) {
4095
+ if (room < header + Math.min(rowHeight, visibleRows.length ? rowHeight : 0) && y > top) {
3300
4096
  pageIndex++;
3301
4097
  y = top;
3302
4098
  room = bottom - y;
3303
4099
  }
3304
- const remaining = records.length - offset;
3305
- let count = records.length ? Math.min(
4100
+ const remaining = visibleRows.length - offset;
4101
+ let count = visibleRows.length ? Math.min(
3306
4102
  remaining,
3307
4103
  Math.max(1, Math.floor((room - header) / rowHeight))
3308
4104
  ) : 0;
4105
+ if (count > 0 && count < remaining) {
4106
+ let boundary = offset + count;
4107
+ for (const cell of resolvedCells) {
4108
+ if (cell.rowIndex < boundary && cell.rowIndex + Math.max(1, cell.rowSpan ?? 1) > boundary)
4109
+ boundary = Math.min(boundary, cell.rowIndex);
4110
+ }
4111
+ if (boundary > offset) count = boundary - offset;
4112
+ else if (y > top) {
4113
+ pageIndex++;
4114
+ y = top;
4115
+ continue;
4116
+ }
4117
+ }
3309
4118
  if (hasSummary && count === remaining && header + count * rowHeight + summaryHeight > room) {
3310
4119
  const countWithSummary = Math.floor(
3311
4120
  (room - header - summaryHeight) / rowHeight
@@ -3320,8 +4129,21 @@ function splitTable(element, data, startPage, startY, top, bottom, pageAt) {
3320
4129
  count--;
3321
4130
  }
3322
4131
  }
3323
- const chunk = records.slice(offset, offset + count);
3324
- const finalChunk = offset + chunk.length >= records.length;
4132
+ if (count > 0 && count < remaining) {
4133
+ let boundary = offset + count;
4134
+ for (const cell of resolvedCells) {
4135
+ if (cell.rowIndex < boundary && cell.rowIndex + Math.max(1, cell.rowSpan ?? 1) > boundary)
4136
+ boundary = Math.min(boundary, cell.rowIndex);
4137
+ }
4138
+ if (boundary > offset) count = boundary - offset;
4139
+ else if (y > top) {
4140
+ pageIndex++;
4141
+ y = top;
4142
+ continue;
4143
+ }
4144
+ }
4145
+ const chunk = visibleRows.slice(offset, offset + count);
4146
+ const finalChunk = offset + chunk.length >= visibleRows.length;
3325
4147
  const height = Math.min(
3326
4148
  room,
3327
4149
  Math.max(
@@ -3336,17 +4158,31 @@ function splitTable(element, data, startPage, startY, top, bottom, pageAt) {
3336
4158
  height,
3337
4159
  dataMode: "static",
3338
4160
  dataSource: chunk,
4161
+ cells: resolvedCells.filter(
4162
+ (cell) => cell.rowIndex < offset + count && cell.rowIndex + Math.max(1, cell.rowSpan ?? 1) > offset
4163
+ ).map((cell) => {
4164
+ const start = Math.max(cell.rowIndex, offset);
4165
+ return {
4166
+ ...cell,
4167
+ rowIndex: start - offset,
4168
+ rowSpan: Math.min(
4169
+ cell.rowIndex + Math.max(1, cell.rowSpan ?? 1),
4170
+ offset + count
4171
+ ) - start
4172
+ };
4173
+ }),
4174
+ mergeCell: void 0,
3339
4175
  printRowCount: 0,
3340
4176
  fillEmptyRows: false,
3341
4177
  showSummary: finalChunk ? element.showSummary : false
3342
4178
  });
3343
4179
  offset += chunk.length;
3344
4180
  y += height;
3345
- if (offset < records.length) {
4181
+ if (offset < visibleRows.length) {
3346
4182
  pageIndex++;
3347
4183
  y = top;
3348
4184
  }
3349
- } while (offset < records.length);
4185
+ } while (offset < visibleRows.length);
3350
4186
  return { logicalEnd: pageIndex * (bottom - top) + (y - top) };
3351
4187
  }
3352
4188
  function splitText(element, data, startPage, startY, top, bottom, pageAt) {
@@ -3719,52 +4555,159 @@ function tableMarkup(element, data) {
3719
4555
  const limit = Math.max(0, Math.floor(element.printRowCount ?? 0));
3720
4556
  const rows = limit > 0 ? allRows.slice(0, limit) : allRows;
3721
4557
  const displayRows = element.fillEmptyRows && limit > rows.length ? [...rows, ...Array.from({ length: limit - rows.length }, () => ({}))] : rows;
4558
+ const resolvedCells = resolveTableCells(element, rows, columns);
3722
4559
  const totalWidth = columns.reduce(
3723
4560
  (sum, column) => sum + Math.max(1, Number(column.width) || 100),
3724
4561
  0
3725
4562
  );
3726
4563
  const borderWidth = element.cellBorder?.width === 0 ? 0 : Math.max(0.1, (element.cellBorder?.width ?? 1) * (25.4 / 96));
3727
- const border = borderWidth ? `${borderWidth}mm solid ${cssColor(element.cellBorder?.color, "#000000")}` : "none";
4564
+ const border = borderWidth && tableBorderVisible(element, data) ? `${borderWidth}mm solid ${cssColor(element.cellBorder?.color, "#000000")}` : "none";
3728
4565
  const fontSize = Math.max(1, element.fontSize ?? 10);
3729
- const colgroup = columns.map(
3730
- (column) => `<col style="width:${Math.max(1, Number(column.width) || 100) / totalWidth * 100}%">`
3731
- ).join("");
3732
- const headerDepth = (items) => Math.max(
3733
- 0,
3734
- ...items.map((column) => 1 + headerDepth(column.children ?? []))
3735
- );
3736
- const depth = headerDepth(visibleColumns);
3737
- const headerRows = Array.from({ length: depth }, () => []);
3738
- const addHeader = (items, level) => {
3739
- for (const column of items) {
3740
- if (column.hidden === true) continue;
3741
- const children = column.children?.filter((item) => item.hidden !== true) ?? [];
3742
- const span = children.length ? tableLeafColumns(children).filter((item) => item.hidden !== true).length : 1;
3743
- if (!span) continue;
3744
- const title = typeof column.title === "string" || typeof column.title === "number" ? column.title : column.key ?? "";
3745
- headerRows[level].push(
3746
- `<th colspan="${span}" rowspan="${children.length ? 1 : depth - level}" style="height:${cssNumber(element.headerRowHeight, 9)}mm;text-align:${cssAlign(column.headerAlign ?? column.align ?? element.cellAlign)};background:${cssColor(element.colors?.header?.backgroundColor, "transparent")};color:${cssColor(element.colors?.header?.color, "#000000")}">${escapeHtml(title)}</th>`
3747
- );
3748
- if (children.length) addHeader(children, level + 1);
4566
+ const cellCss = (column) => {
4567
+ const padding = resolveTableCellPadding(column, element);
4568
+ const side = (value, fallback) => `${Math.max(0, cssNumber(value, fallback)) * (25.4 / 96)}mm`;
4569
+ const overflow = resolveTableCellOverflow(column, element);
4570
+ return `padding:${side(padding.top, 0)} ${side(padding.right, 4)} ${side(padding.bottom, 0)} ${side(padding.left, 4)};overflow:hidden;white-space:${overflow === "wrap" ? "pre-wrap" : "nowrap"};text-overflow:${overflow === "ellipsis" ? "ellipsis" : "clip"};`;
4571
+ };
4572
+ const cellContent = (column, value, weight) => {
4573
+ const text = cellText2(value);
4574
+ if (resolveTableCellOverflow(column, element) !== "shrink")
4575
+ return escapeHtml(text);
4576
+ const fontPx = fontSize * 96 / 72;
4577
+ const padding = resolveTableCellPadding(column, element);
4578
+ const widthPx = Math.max(
4579
+ 1,
4580
+ element.width * 96 / 25.4 * weight / totalWidth - padding.left - padding.right
4581
+ );
4582
+ const estimatedPx = Array.from(text).reduce(
4583
+ (sum, character) => sum + fontPx * (character.charCodeAt(0) < 128 ? 0.55 : 1),
4584
+ 0
4585
+ );
4586
+ if (estimatedPx <= widthPx) return escapeHtml(text);
4587
+ return `<svg style="display:block;width:100%;height:1.2em" viewBox="0 0 ${widthPx} ${fontPx * 1.2}" preserveAspectRatio="none"><text x="0" y="${fontPx}" font-size="${fontPx}" fill="currentColor" textLength="${widthPx}" lengthAdjust="spacingAndGlyphs">${escapeHtml(text)}</text></svg>`;
4588
+ };
4589
+ const bodyCellContent = (column, value) => {
4590
+ const content = value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
4591
+ const type = typeof content?.type === "string" ? content.type : column.cellType;
4592
+ const payload = content?.value ?? content?.src ?? content?.text ?? value;
4593
+ const text = cellText2(payload);
4594
+ if (type === "chart" && typeof content?.chartType === "string") {
4595
+ try {
4596
+ return chart.exportCanvasChartToSvg({
4597
+ type: content.chartType,
4598
+ option: content.option ?? payload,
4599
+ width: 200,
4600
+ height: 60,
4601
+ simple: true
4602
+ });
4603
+ } catch {
4604
+ return escapeHtml(text);
4605
+ }
3749
4606
  }
4607
+ if (type === "timeline") {
4608
+ const items = Array.isArray(content?.items) ? content.items : Array.isArray(payload) ? payload : [];
4609
+ return `<div style="border-left:0.3mm solid #94a3b8;padding-left:1mm">${items.map((item) => `<div style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis">\u2022 ${escapeHtml(item && typeof item === "object" ? cellText2(item.title ?? item.content) : cellText2(item))}</div>`).join("")}</div>`;
4610
+ }
4611
+ if (type === "image")
4612
+ return `<img src="${imageSource(text)}" alt="" style="display:block;max-width:100%;max-height:100%;margin:auto;object-fit:contain">`;
4613
+ if (type === "qrcode") {
4614
+ try {
4615
+ return qrcode.exportQrCodeToSvg({ value: text, width: 96, height: 96 });
4616
+ } catch {
4617
+ return escapeHtml(text);
4618
+ }
4619
+ }
4620
+ if (type === "barcode") {
4621
+ try {
4622
+ return barcode.exportBarcodeToSvg({
4623
+ value: text,
4624
+ width: 200,
4625
+ height: 50,
4626
+ format: "code128",
4627
+ displayValue: false
4628
+ });
4629
+ } catch {
4630
+ return escapeHtml(text);
4631
+ }
4632
+ }
4633
+ if (type === "color")
4634
+ return `<span style="display:block;width:100%;height:100%;min-height:4mm;background:${cssColor(text, "transparent")}"></span>`;
4635
+ if (type === "progress")
4636
+ return `<span style="display:block;height:2mm;background:#e5e7eb"><span style="display:block;height:100%;width:${Math.max(0, Math.min(100, Number(payload) || 0))}%;background:#1677ff"></span></span>`;
4637
+ if (["checkbox", "radio", "switch"].includes(type ?? ""))
4638
+ return payload === true || payload === 1 || payload === "true" ? "\u2611" : "\u2610";
4639
+ if (type === "rate") {
4640
+ const count = Math.max(0, Math.min(5, Math.round(Number(payload) || 0)));
4641
+ return `<span style="color:#f59e0b">${"\u2605".repeat(count)}${"\u2606".repeat(5 - count)}</span>`;
4642
+ }
4643
+ if (type === "tag" || type === "button")
4644
+ return `<span style="display:inline-block;padding:0.3mm 1mm;background:${type === "tag" ? "#e6f4ff" : "#1677ff"};color:${type === "tag" ? "#1677ff" : "#fff"}">${escapeHtml(text)}</span>`;
4645
+ if (type === "json")
4646
+ return escapeHtml(
4647
+ typeof value === "object" ? JSON.stringify(value) : text
4648
+ );
4649
+ if (type === "link")
4650
+ return `<span style="color:#1677ff;text-decoration:underline">${escapeHtml(text)}</span>`;
4651
+ return cellContent(column, value, Math.max(1, Number(column.width) || 100));
3750
4652
  };
3751
- addHeader(visibleColumns, 0);
4653
+ const colgroup = columns.map(
4654
+ (column) => `<col style="width:${Math.max(1, Number(column.width) || 100) / totalWidth * 100}%">`
4655
+ ).join("");
4656
+ const depth = tableHeaderDepth(visibleColumns);
4657
+ const headerRows = Array.from({ length: depth }, () => []);
4658
+ for (const header2 of tableHeaderCells(element)) {
4659
+ const { column } = header2;
4660
+ const title = typeof column.title === "string" || typeof column.title === "number" ? column.title : column.key ?? "";
4661
+ headerRows[header2.rowIndex].push(
4662
+ `<th colspan="${header2.colSpan}" rowspan="${header2.rowSpan}" style="height:${cssNumber(element.headerRowHeight, 9)}mm;text-align:${cssAlign(column.headerAlign ?? column.align ?? element.cellAlign)};background:${cssColor(element.colors?.header?.backgroundColor, "transparent")};color:${cssColor(element.colors?.header?.color, "#000000")};${cellCss(column)}">${cellContent(
4663
+ column,
4664
+ title,
4665
+ columns.slice(header2.columnIndex, header2.columnIndex + header2.colSpan).reduce(
4666
+ (sum, leaf) => sum + Math.max(1, Number(leaf.width) || 100),
4667
+ 0
4668
+ )
4669
+ )}</th>`
4670
+ );
4671
+ }
3752
4672
  const header = element.showHeader === false ? "" : `<thead>${headerRows.map((cells) => `<tr>${cells.join("")}</tr>`).join("")}</thead>`;
3753
4673
  const body = displayRows.map(
3754
- (row) => `<tr>${columns.map((column) => {
4674
+ (row, rowIndex) => `<tr>${columns.map((column, columnIndex) => {
4675
+ const cell = coveringTableCell(
4676
+ resolvedCells,
4677
+ rowIndex,
4678
+ columnIndex
4679
+ );
4680
+ if (cell && (cell.rowIndex !== rowIndex || cell.columnIndex !== columnIndex))
4681
+ return "";
3755
4682
  const value = readTableCell(row, column.dataIndex ?? column.key);
3756
- const align = cssAlign(column.align ?? element.cellAlign);
3757
- const vertical = element.cellVerticalAlign ?? "middle";
4683
+ const rendered = column.render?.(value, row, rowIndex, column) ?? value;
4684
+ const align = cssAlign(
4685
+ column.align ?? column.cellStyle?.textAlign ?? element.cellAlign
4686
+ );
4687
+ const vertical = column.cellStyle?.verticalAlign ?? element.cellVerticalAlign ?? "middle";
3758
4688
  const background = cssColor(
3759
- element.colors?.body?.backgroundColor,
4689
+ cell?.backgroundColor ?? column.cellStyle?.backgroundColor ?? element.colors?.body?.backgroundColor,
3760
4690
  "transparent"
3761
4691
  );
3762
- const color2 = cssColor(element.colors?.body?.color, "#000000");
3763
- return `<td style="height:${cssNumber(element.rowHeight, 8)}mm;text-align:${align};vertical-align:${vertical === "top" || vertical === "bottom" ? vertical : "middle"};background:${background};color:${color2};${element.cellOverflow === "ellipsis" ? "white-space:nowrap;text-overflow:ellipsis;overflow:hidden;" : ""}">${escapeHtml(cellText2(value))}</td>`;
4692
+ const color2 = cssColor(
4693
+ cell?.color ?? column.cellStyle?.color ?? element.colors?.body?.color,
4694
+ "#000000"
4695
+ );
4696
+ const borderStyle = tableBorderVisible(element, data, cell, row) ? cell?.borderColor ? `border-color:${cssColor(cell.borderColor, "transparent")};` : "" : "border-color:transparent;";
4697
+ const colSpan = Math.min(
4698
+ Math.max(1, cell?.colSpan ?? 1),
4699
+ columns.length - columnIndex
4700
+ );
4701
+ const rowSpan = Math.min(
4702
+ Math.max(1, cell?.rowSpan ?? 1),
4703
+ displayRows.length - rowIndex
4704
+ );
4705
+ const content = cell?.diagonal ? `<div style="display:flex;flex-direction:column;justify-content:space-between;width:100%;height:100%;min-height:${cssNumber(element.rowHeight, 8) * rowSpan}mm;background:linear-gradient(${cell.diagonal === "down" ? "to top right" : "to bottom right"},transparent 49.5%,${cssColor(cell.borderColor ?? element.cellBorder?.color, "#000000")} 50%,transparent 50.5%)"><span style="align-self:flex-start">${escapeHtml(cell.diagonalStart ?? cellText2(value))}</span><span style="align-self:flex-end">${escapeHtml(cell.diagonalEnd ?? "")}</span></div>` : bodyCellContent(column, rendered);
4706
+ return `<td colspan="${colSpan}" rowspan="${rowSpan}" style="height:${cssNumber(element.rowHeight, 8)}mm;text-align:${align};vertical-align:${vertical === "top" || vertical === "bottom" ? vertical : "middle"};background:${background};color:${color2};${borderStyle}${cellCss(column)}">${content}</td>`;
3764
4707
  }).join("")}</tr>`
3765
4708
  ).join("");
3766
4709
  const summary = element.showSummary === false ? /* @__PURE__ */ new Map() : computePrintTableSummaryValues(rows, columns);
3767
- const summaryRow = summary.size ? `<tfoot><tr>${columns.map((column, index) => `<td style="height:${cssNumber(element.summaryRowHeight ?? element.rowHeight, 8)}mm;text-align:${cssAlign(column.align ?? element.cellAlign)};background:${cssColor(element.colors?.summary?.backgroundColor, "transparent")};color:${cssColor(element.colors?.summary?.color, "#000000")}">${escapeHtml(summary.get(String(column.key ?? column.dataIndex ?? index)) ?? "")}</td>`).join("")}</tr></tfoot>` : "";
4710
+ const summaryRow = summary.size ? `<tfoot><tr>${columns.map((column, index) => `<td style="height:${cssNumber(element.summaryRowHeight ?? element.rowHeight, 8)}mm;text-align:${cssAlign(column.align ?? element.cellAlign)};background:${cssColor(element.colors?.summary?.backgroundColor, "transparent")};color:${cssColor(element.colors?.summary?.color, "#000000")};${cellCss(column)}">${cellContent(column, summary.get(String(column.key ?? column.dataIndex ?? index)) ?? "", Math.max(1, Number(column.width) || 100))}</td>`).join("")}</tr></tfoot>` : "";
3768
4711
  return `<table class="pd-table" style="font-size:${fontSize}pt;--pd-border:${border}"><colgroup>${colgroup}</colgroup>${header}<tbody>${body}</tbody>${summaryRow}</table>`;
3769
4712
  }
3770
4713
  function chartMarkup(element, data) {
@@ -3933,7 +4876,7 @@ function elementMarkup(source, data, pageNumber) {
3933
4876
  const body = element.overflowCrop ? `<div style="position:relative;top:-${cssNumber(element.overflowCrop.offset, 0)}mm;width:100%;height:${cssNumber(element.overflowCrop.fullHeight, element.height)}mm">${content}</div>` : content;
3934
4877
  return `<div class="pd-element" style="${style}">${body}</div>`;
3935
4878
  }
3936
- function transformPrintDesignToHtml(document, options = {}) {
4879
+ function* generatePrintHtml(document, options = {}, physicalPages, pageOffset = 0) {
3937
4880
  const data = options.data ?? {};
3938
4881
  const selectedPages = options.pageIds ? options.pageIds.map((id) => {
3939
4882
  const page = document.pages.find((item) => item.id === id);
@@ -3941,10 +4884,15 @@ function transformPrintDesignToHtml(document, options = {}) {
3941
4884
  return page;
3942
4885
  }) : document.pages;
3943
4886
  if (!selectedPages.length) throw new RangeError("\u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u6253\u5370\u9875\u9762\u3002");
3944
- const pages = paginatePrintDesignDocument(
3945
- { ...document, pages: selectedPages },
3946
- data
3947
- ).pages;
4887
+ const pages = physicalPages ?? [];
4888
+ for (let index = 0; !physicalPages && index < selectedPages.length; index++) {
4889
+ const result = paginatePrintDesignDocument(
4890
+ { ...document, pages: [selectedPages[index]] },
4891
+ data
4892
+ ).pages;
4893
+ for (const page of result) pages.push(page);
4894
+ yield (index + 1) / selectedPages.length * 0.3;
4895
+ }
3948
4896
  const firstPage = pages[0];
3949
4897
  const uniformPageSize = pages.every(
3950
4898
  (page) => page.width === firstPage.width && page.height === firstPage.height
@@ -3970,9 +4918,15 @@ function transformPrintDesignToHtml(document, options = {}) {
3970
4918
  if (!/^[\w:/?&=.%+#;,@-]+$/.test(source)) return "";
3971
4919
  return `@font-face{font-family:${cssFont(element.fontFamily)};src:url("${source}")}`;
3972
4920
  }).join("");
3973
- const markup = pages.map(
3974
- (page, index) => `<section class="pd-page pd-page-${index}" style="background:${cssColor(page.background, "#ffffff")}">${page.elements.map((element) => elementMarkup(element, data, index + 1)).join("")}</section>`
3975
- ).join("");
4921
+ const parts = [];
4922
+ for (let index = 0; index < pages.length; index++) {
4923
+ const page = pages[index];
4924
+ parts.push(
4925
+ `<section class="pd-page pd-page-${index}" style="background:${cssColor(page.background, "#ffffff")}">${page.elements.map((element) => elementMarkup(element, data, pageOffset + index + 1)).join("")}</section>`
4926
+ );
4927
+ yield 0.3 + (index + 1) / pages.length * 0.7;
4928
+ }
4929
+ const markup = parts.join("");
3976
4930
  return `<!doctype html><html><head><meta charset="utf-8"><title>${escapeHtml(document.name)}</title><style>${fontStyles}${pageStyles}*{box-sizing:border-box}html,body{margin:0;padding:0}body{-webkit-print-color-adjust:exact;print-color-adjust:exact}.pd-page{position:relative;overflow:hidden;break-after:page;page-break-after:always}.pd-page:last-child{break-after:auto;page-break-after:auto}.pd-element{position:absolute;overflow:hidden;transform-origin:center center}.pd-element>svg{display:block;width:100%;height:100%}.pd-text{text-spacing-trim:space-all;text-autospace:no-autospace;width:100%;height:100%;display:flex;flex-direction:column;white-space:pre-wrap;overflow:hidden;overflow-wrap:anywhere}.pd-media,.pd-watermark{display:block;width:100%;height:100%}.pd-media{object-fit:contain}.pd-watermark{display:grid;overflow:hidden}.pd-watermark span{display:flex;align-items:center;justify-content:center;white-space:nowrap}.pd-table{border-collapse:collapse;width:100%;table-layout:fixed}.pd-table th,.pd-table td{border:var(--pd-border);padding:0 1mm;overflow:hidden;overflow-wrap:anywhere}.pd-table th{font-weight:600}.pd-divider{position:absolute;left:0;top:50%;width:100%;height:0}.pd-element:has(>.pd-divider[style*="border-left"]){overflow:visible}.pd-element .pd-divider[style*="border-left"]{left:50%;top:0;width:0;height:100%}@media print{.pd-page{margin:0}}</style></head><body>${markup}</body></html>`;
3977
4931
  }
3978
4932
  function printPrintDesignHtml(popup, html) {
@@ -3994,47 +4948,73 @@ function printPrintDesignHtml(popup, html) {
3994
4948
  if (popup.document.readyState === "complete") print();
3995
4949
  else popup.addEventListener("load", print, { once: true });
3996
4950
  }
3997
-
3998
- // src/interaction/geometry.ts
3999
- var RESIZE_HANDLES = [
4000
- "nw",
4001
- "ne",
4002
- "se",
4003
- "sw",
4004
- "n",
4005
- "s",
4006
- "w",
4007
- "e"
4008
- ];
4009
- function rotatePoint2(point, center, radians) {
4010
- const x = point.x - center.x;
4011
- const y = point.y - center.y;
4012
- const cosine = Math.cos(radians);
4013
- const sine = Math.sin(radians);
4014
- return {
4015
- x: center.x + x * cosine - y * sine,
4016
- y: center.y + x * sine + y * cosine
4017
- };
4018
- }
4019
- function getSelectionRect(element, scale) {
4020
- const minimumSize = 12 / scale;
4021
- const width = Math.max(element.width, minimumSize);
4022
- const height = Math.max(element.height, minimumSize);
4023
- return {
4024
- x: element.x - (width - element.width) / 2,
4025
- y: element.y - (height - element.height) / 2,
4026
- width,
4027
- height
4951
+ function transformPrintDesignToHtml(document, options = {}) {
4952
+ const task = generatePrintHtml(document, options);
4953
+ let next = task.next();
4954
+ while (!next.done) next = task.next();
4955
+ return next.value;
4956
+ }
4957
+ async function transformPrintDesignToHtmlAsync(document, options = {}) {
4958
+ const task = generatePrintHtml(document, options);
4959
+ while (true) {
4960
+ await new Promise((resolve) => setTimeout(resolve, 0));
4961
+ options.signal?.throwIfAborted();
4962
+ const deadline = performance.now() + 8;
4963
+ do {
4964
+ const next = task.next();
4965
+ if (next.done) return next.value;
4966
+ options.onProgress?.(next.value);
4967
+ } while (performance.now() < deadline);
4968
+ }
4969
+ }
4970
+ async function* transformPrintDesignToHtmlBatches(document, options) {
4971
+ const limit = Math.max(1, Math.floor(options.pagesPerBatch));
4972
+ if (!Number.isFinite(limit)) throw new RangeError("\u6BCF\u6B21\u6253\u5370\u9875\u6570\u5FC5\u987B\u662F\u6B63\u6574\u6570");
4973
+ let buffer = [];
4974
+ let offset = 0;
4975
+ const render = async () => {
4976
+ const task = generatePrintHtml(document, options, buffer, offset);
4977
+ while (true) {
4978
+ await new Promise((resolve) => setTimeout(resolve, 0));
4979
+ options.signal?.throwIfAborted();
4980
+ const deadline = performance.now() + 8;
4981
+ do {
4982
+ const next = task.next();
4983
+ if (next.done) return next.value;
4984
+ } while (performance.now() < deadline);
4985
+ }
4028
4986
  };
4029
- }
4030
- function getRotatedRectangleCorner(rect, rotation, handle) {
4031
- const x = handle.includes("w") ? rect.x : handle.includes("e") ? rect.x + rect.width : rect.x + rect.width / 2;
4032
- const y = handle.includes("n") ? rect.y : handle.includes("s") ? rect.y + rect.height : rect.y + rect.height / 2;
4033
- return rotatePoint2(
4034
- { x, y },
4035
- { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 },
4036
- rotation * Math.PI / 180
4037
- );
4987
+ for (let index = 0; index < document.pages.length; index++) {
4988
+ await new Promise((resolve) => setTimeout(resolve, 0));
4989
+ options.signal?.throwIfAborted();
4990
+ const pages = paginatePrintDesignDocument(
4991
+ { ...document, pages: [document.pages[index]] },
4992
+ options.data ?? {}
4993
+ ).pages;
4994
+ for (let pageIndex = 0; pageIndex < pages.length; pageIndex++) {
4995
+ buffer.push(pages[pageIndex]);
4996
+ if (buffer.length === limit) {
4997
+ const html = await render();
4998
+ yield {
4999
+ html,
5000
+ firstPage: offset + 1,
5001
+ lastPage: offset + buffer.length,
5002
+ hasMore: pageIndex + 1 < pages.length || index + 1 < document.pages.length
5003
+ };
5004
+ offset += buffer.length;
5005
+ buffer = [];
5006
+ options.signal?.throwIfAborted();
5007
+ }
5008
+ }
5009
+ options.onProgress?.((index + 1) / document.pages.length);
5010
+ }
5011
+ if (buffer.length)
5012
+ yield {
5013
+ html: await render(),
5014
+ firstPage: offset + 1,
5015
+ lastPage: offset + buffer.length,
5016
+ hasMore: false
5017
+ };
4038
5018
  }
4039
5019
 
4040
5020
  // src/interaction/table-column-resize.ts
@@ -4060,215 +5040,41 @@ function hitTableColumnBoundary(element, point, scale, _document) {
4060
5040
  offset += widths[i];
4061
5041
  const edge = offset * 25.4 / 96;
4062
5042
  if (Math.abs(local.x - element.x - edge) * scale <= 5)
4063
- return {
4064
- elementId: element.id,
4065
- boundary: i,
4066
- widths,
4067
- columns: element.columns,
4068
- rotation: element.rotation ?? 0
4069
- };
4070
- }
4071
- }
4072
- function resizeTableColumns(session, dx, dy) {
4073
- const angle = session.rotation * Math.PI / 180;
4074
- const delta = (dx * Math.cos(angle) + dy * Math.sin(angle)) * 96 / 25.4;
4075
- const widths = [...session.widths];
4076
- const i = session.boundary;
4077
- const minimum = Math.min(8, widths[i], widths[i + 1]);
4078
- const change = Math.max(
4079
- minimum - widths[i],
4080
- Math.min(widths[i + 1] - minimum, delta)
4081
- );
4082
- widths[i] += change;
4083
- widths[i + 1] -= change;
4084
- let index = 0;
4085
- const map = (columns) => columns.map((column) => {
4086
- if (column.hidden) return column;
4087
- if (column.children?.length)
4088
- return { ...column, children: map(column.children) };
4089
- const width = widths[index++];
4090
- return width == null ? column : {
4091
- ...column,
4092
- width,
4093
- minWidth: Math.min(column.minWidth ?? 60, width),
4094
- maxWidth: Math.max(column.maxWidth ?? 480, width)
4095
- };
4096
- });
4097
- return map(session.columns);
4098
- }
4099
-
4100
- // src/helper/measurement-layout.ts
4101
- function measurementObstacles(elements, scale) {
4102
- return elements.flatMap((element) => {
4103
- const rect = getSelectionRect(element, scale);
4104
- const angle = element.rotation ?? 0;
4105
- const corners = RESIZE_HANDLES.map(
4106
- (handle) => getRotatedRectangleCorner(rect, angle, handle)
4107
- );
4108
- const center = { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };
4109
- const stem = rotatePoint2(
4110
- { x: center.x, y: rect.y },
4111
- center,
4112
- angle * Math.PI / 180
4113
- );
4114
- const rotate = rotatePoint2(
4115
- { x: center.x, y: rect.y - 17 / scale },
4116
- center,
4117
- angle * Math.PI / 180
4118
- );
4119
- return [
4120
- ...[...corners, rotate].map((p) => ({
4121
- x: p.x * scale - 10,
4122
- y: p.y * scale - 10,
4123
- width: 20,
4124
- height: 20
4125
- })),
4126
- {
4127
- x: Math.min(stem.x, rotate.x) * scale - 5,
4128
- y: Math.min(stem.y, rotate.y) * scale - 5,
4129
- width: Math.abs(stem.x - rotate.x) * scale + 10,
4130
- height: Math.abs(stem.y - rotate.y) * scale + 10
4131
- }
4132
- ];
4133
- });
4134
- }
4135
- function placeMeasurement(line, textWidth, obstacles, viewport) {
4136
- const horizontal = line.y1 === line.y2;
4137
- const intersects = (a, b) => a.x <= b.x + b.width && a.x + a.width >= b.x && a.y <= b.y + b.height && a.y + a.height >= b.y;
4138
- const limit = horizontal ? viewport.height : viewport.width;
4139
- for (let step = 0; step <= Math.ceil(limit / 16); step++) {
4140
- for (const sign of step === 0 ? [1] : [1, -1]) {
4141
- const offset = step * 16 * sign;
4142
- const x1 = line.x1 + (horizontal ? 0 : offset), x2 = line.x2 + (horizontal ? 0 : offset);
4143
- const y1 = line.y1 + (horizontal ? offset : 0), y2 = line.y2 + (horizontal ? offset : 0);
4144
- const textX = (x1 + x2) / 2 + 4, textY = (y1 + y2) / 2 - 4;
4145
- const boxes = [
4146
- {
4147
- x: Math.min(x1, x2) - 3,
4148
- y: Math.min(y1, y2) - 3,
4149
- width: Math.abs(x2 - x1) + 6,
4150
- height: Math.abs(y2 - y1) + 6
4151
- },
4152
- { x: textX - 2, y: textY - 12, width: textWidth + 4, height: 16 }
4153
- ];
4154
- if (boxes.some(
4155
- (b) => b.x < 0 || b.y < 0 || b.x + b.width > viewport.width || b.y + b.height > viewport.height
4156
- ))
4157
- continue;
4158
- if (boxes.some((b) => obstacles.some((o) => intersects(b, o)))) continue;
4159
- return { x1, y1, x2, y2, textX, textY };
4160
- }
4161
- }
4162
- return null;
4163
- }
4164
-
4165
- // src/helper/alignment.ts
4166
- function elementBounds(element) {
4167
- const angle = (element.rotation ?? 0) * Math.PI / 180;
4168
- const width = Math.abs(element.width * Math.cos(angle)) + Math.abs(element.height * Math.sin(angle));
4169
- const height = Math.abs(element.width * Math.sin(angle)) + Math.abs(element.height * Math.cos(angle));
4170
- return {
4171
- x: element.x + (element.width - width) / 2,
4172
- y: element.y + (element.height - height) / 2,
4173
- width,
4174
- height
4175
- };
4176
- }
4177
- function snapCoordinate(value, targets, tolerance) {
4178
- return targets.reduce(
4179
- (best, target) => Math.abs(target - value) <= tolerance && Math.abs(target - value) < Math.abs(best - value) ? target : best,
4180
- targets.find((target) => Math.abs(target - value) <= tolerance) ?? value
4181
- );
4182
- }
4183
- function guideTargets(page, axis) {
4184
- const vertical = axis === "vertical";
4185
- const size = vertical ? page.width : page.height;
4186
- const margins = page.margins ?? { top: 10, right: 10, bottom: 10, left: 10 };
4187
- return [
4188
- 0,
4189
- size / 2,
4190
- size,
4191
- vertical ? margins.left : margins.top,
4192
- size - (vertical ? margins.right : margins.bottom),
4193
- ...page.elements.filter((e) => e.visible !== false).flatMap((e) => {
4194
- const b = elementBounds(e);
4195
- const start = vertical ? b.x : b.y, length = vertical ? b.width : b.height;
4196
- return [start, start + length / 2, start + length];
4197
- })
4198
- ];
4199
- }
4200
- function nearestMeasurements(page, selectedIds) {
4201
- const selected = page.elements.filter((e) => selectedIds.includes(e.id) && e.visible !== false).map(elementBounds);
4202
- if (!selected.length) return [];
4203
- const left = Math.min(...selected.map((b) => b.x)), top = Math.min(...selected.map((b) => b.y));
4204
- const right = Math.max(...selected.map((b) => b.x + b.width)), bottom = Math.max(...selected.map((b) => b.y + b.height));
4205
- const result = /* @__PURE__ */ new Map();
4206
- const add = (key, x1, y1, x2, y2) => {
4207
- const distance = Math.hypot(x2 - x1, y2 - y1);
4208
- if (!result.has(key) || distance < result.get(key).distance)
4209
- result.set(key, { x1, y1, x2, y2, distance });
4210
- };
4211
- for (const e of page.elements) {
4212
- if (selectedIds.includes(e.id) || e.visible === false) continue;
4213
- const b = elementBounds(e), r = b.x + b.width, d = b.y + b.height;
4214
- if (b.y <= bottom && d >= top) {
4215
- const y = (Math.max(top, b.y) + Math.min(bottom, d)) / 2;
4216
- if (r <= left) add("left", r, y, left, y);
4217
- if (b.x >= right) add("right", right, y, b.x, y);
4218
- }
4219
- if (b.x <= right && r >= left) {
4220
- const x = (Math.max(left, b.x) + Math.min(right, r)) / 2;
4221
- if (d <= top) add("top", x, d, x, top);
4222
- if (b.y >= bottom) add("bottom", x, bottom, x, b.y);
4223
- }
4224
- }
4225
- return [...result.values()];
4226
- }
4227
- function drawMeasurements(context, page, selectedIds, scale, unit = "mm") {
4228
- context.save();
4229
- context.strokeStyle = "#e11d48";
4230
- context.fillStyle = "#e11d48";
4231
- context.font = "11px sans-serif";
4232
- context.setLineDash([]);
4233
- context.lineWidth = 1;
4234
- const obstacles = measurementObstacles(
4235
- page.elements.filter(
4236
- (e) => selectedIds.includes(e.id) && e.visible !== false
4237
- ),
4238
- scale
4239
- );
4240
- context.textAlign = "left";
4241
- context.textBaseline = "alphabetic";
4242
- for (const m of nearestMeasurements(page, selectedIds)) {
4243
- const label = formatDisplayUnit(m.distance, unit);
4244
- const placement = placeMeasurement(
4245
- {
4246
- x1: m.x1 * scale,
4247
- y1: m.y1 * scale,
4248
- x2: m.x2 * scale,
4249
- y2: m.y2 * scale
4250
- },
4251
- context.measureText(label).width,
4252
- obstacles,
4253
- { width: page.width * scale, height: page.height * scale }
4254
- );
4255
- if (!placement) continue;
4256
- const { x1, y1, x2, y2, textX, textY } = placement;
4257
- const horizontal = y1 === y2;
4258
- context.beginPath();
4259
- context.moveTo(x1, y1);
4260
- context.lineTo(x2, y2);
4261
- for (const [x, y] of [
4262
- [x1, y1],
4263
- [x2, y2]
4264
- ]) {
4265
- context.moveTo(x - (horizontal ? 0 : 3), y - (horizontal ? 3 : 0));
4266
- context.lineTo(x + (horizontal ? 0 : 3), y + (horizontal ? 3 : 0));
4267
- }
4268
- context.stroke();
4269
- context.fillText(label, textX, textY);
5043
+ return {
5044
+ elementId: element.id,
5045
+ boundary: i,
5046
+ widths,
5047
+ columns: element.columns,
5048
+ rotation: element.rotation ?? 0
5049
+ };
4270
5050
  }
4271
- context.restore();
5051
+ }
5052
+ function resizeTableColumns(session, dx, dy) {
5053
+ const angle = session.rotation * Math.PI / 180;
5054
+ const delta = (dx * Math.cos(angle) + dy * Math.sin(angle)) * 96 / 25.4;
5055
+ const widths = [...session.widths];
5056
+ const i = session.boundary;
5057
+ const minimum = Math.min(8, widths[i], widths[i + 1]);
5058
+ const change = Math.max(
5059
+ minimum - widths[i],
5060
+ Math.min(widths[i + 1] - minimum, delta)
5061
+ );
5062
+ widths[i] += change;
5063
+ widths[i + 1] -= change;
5064
+ let index = 0;
5065
+ const map = (columns) => columns.map((column) => {
5066
+ if (column.hidden) return column;
5067
+ if (column.children?.length)
5068
+ return { ...column, children: map(column.children) };
5069
+ const width = widths[index++];
5070
+ return width == null ? column : {
5071
+ ...column,
5072
+ width,
5073
+ minWidth: Math.min(column.minWidth ?? 60, width),
5074
+ maxWidth: Math.max(column.maxWidth ?? 480, width)
5075
+ };
5076
+ });
5077
+ return map(session.columns);
4272
5078
  }
4273
5079
 
4274
5080
  // src/helper/group-resize.ts
@@ -4451,6 +5257,94 @@ function resolvePrintDesignDebugOptions(debug) {
4451
5257
  return { ...DEFAULT_PRINT_DESIGN_DEBUG_OPTIONS, ...debug };
4452
5258
  }
4453
5259
 
5260
+ // src/interaction/table-selectors.ts
5261
+ function tableSelectors(element, data = {}) {
5262
+ const leaves = tableLeafColumns(element.columns).filter(
5263
+ (column) => !column.hidden
5264
+ );
5265
+ if (!leaves.length) return [];
5266
+ const widths = leaves.map((column) => Math.max(1, column.width ?? 100));
5267
+ const total = widths.reduce((sum, value) => sum + value, 0);
5268
+ const headerCount = element.showHeader === false ? 0 : tableHeaderDepth(element.columns);
5269
+ const rowLimit = Math.max(0, Math.floor(element.printRowCount ?? 0));
5270
+ const bodyCount = rowLimit ? Math.min(resolveTableRows(element, data).length, rowLimit) : resolveTableRows(element, data).length;
5271
+ const strip = 6;
5272
+ const selectors = [
5273
+ {
5274
+ kind: "all",
5275
+ index: 0,
5276
+ x: element.x - strip,
5277
+ y: element.y - strip,
5278
+ width: strip,
5279
+ height: strip,
5280
+ label: "\u25E2"
5281
+ }
5282
+ ];
5283
+ let left = element.x;
5284
+ for (const [index, width] of widths.entries()) {
5285
+ const actualWidth = element.width * width / total;
5286
+ let number = index + 1;
5287
+ let label = "";
5288
+ while (number > 0) {
5289
+ number--;
5290
+ label = String.fromCharCode(65 + number % 26) + label;
5291
+ number = Math.floor(number / 26);
5292
+ }
5293
+ selectors.push({
5294
+ kind: "column",
5295
+ index,
5296
+ x: left,
5297
+ y: element.y - strip,
5298
+ width: actualWidth,
5299
+ height: strip,
5300
+ label
5301
+ });
5302
+ left += actualWidth;
5303
+ }
5304
+ let top = element.y;
5305
+ for (let index = 0; index < headerCount; index++) {
5306
+ selectors.push({
5307
+ kind: "row",
5308
+ area: "header",
5309
+ index,
5310
+ x: element.x - strip,
5311
+ y: top,
5312
+ width: strip,
5313
+ height: element.headerRowHeight ?? 9,
5314
+ label: `H${index + 1}`
5315
+ });
5316
+ top += element.headerRowHeight ?? 9;
5317
+ }
5318
+ for (let index = 0; index < bodyCount; index++) {
5319
+ selectors.push({
5320
+ kind: "row",
5321
+ area: "body",
5322
+ index,
5323
+ x: element.x - strip,
5324
+ y: top,
5325
+ width: strip,
5326
+ height: element.rowHeight ?? 8,
5327
+ label: String(index + 1)
5328
+ });
5329
+ top += element.rowHeight ?? 8;
5330
+ }
5331
+ return selectors;
5332
+ }
5333
+ function hitTableSelector(element, point, data = {}) {
5334
+ const center = {
5335
+ x: element.x + element.width / 2,
5336
+ y: element.y + resolvedTableHeight(element, data) / 2
5337
+ };
5338
+ const local = rotatePoint2(
5339
+ point,
5340
+ center,
5341
+ -(element.rotation ?? 0) * Math.PI / 180
5342
+ );
5343
+ return tableSelectors(element, data).find(
5344
+ (selector) => local.x >= selector.x && local.x < selector.x + selector.width && local.y >= selector.y && local.y < selector.y + selector.height
5345
+ );
5346
+ }
5347
+
4454
5348
  // src/interaction/selection.ts
4455
5349
  function shouldPrioritizeSelectionHandle(hitElementId, selectedElementIds) {
4456
5350
  return hitElementId === void 0 || selectedElementIds.includes(hitElementId);
@@ -4952,13 +5846,16 @@ var PrintDesignTextEditor = class {
4952
5846
  function canEditTable(element) {
4953
5847
  return element.type === "table" && element.dataMode !== "binding" && !element.locked && element.visible !== false;
4954
5848
  }
4955
- function hitTableCell(element, point, document) {
5849
+ function hitTableCell(element, point, document, data = {}) {
4956
5850
  const ctx = document.createElement("canvas").getContext("2d");
4957
5851
  if (!ctx) return;
4958
5852
  const scale = 96 / 25.4;
4959
5853
  const local = rotatePoint2(
4960
5854
  point,
4961
- { x: element.x + element.width / 2, y: element.y + element.height / 2 },
5855
+ {
5856
+ x: element.x + element.width / 2,
5857
+ y: element.y + resolvedTableHeight(element, data) / 2
5858
+ },
4962
5859
  -(element.rotation ?? 0) * Math.PI / 180
4963
5860
  );
4964
5861
  const x = (local.x - element.x) * scale;
@@ -4968,14 +5865,14 @@ function hitTableCell(element, point, document) {
4968
5865
  x: 0,
4969
5866
  y: 0,
4970
5867
  width: element.width * scale,
4971
- height: element.height * scale,
5868
+ height: resolvedTableHeight(element, data) * scale,
4972
5869
  scale,
4973
- data: {}
5870
+ data
4974
5871
  }).find(
4975
5872
  ({ rect }) => x >= rect.x && x < rect.x + rect.width && y >= rect.y && y < rect.y + rect.height
4976
5873
  );
4977
5874
  }
4978
- function tableCellText(element, cell, text) {
5875
+ function tableCellText(element, cell, text, data = {}) {
4979
5876
  const scale = 96 / 25.4;
4980
5877
  const { rect, column } = cell;
4981
5878
  const width = rect.width / scale;
@@ -4985,7 +5882,10 @@ function tableCellText(element, cell, text) {
4985
5882
  x: element.x + rect.x / scale + width / 2,
4986
5883
  y: element.y + rect.y / scale + height / 2
4987
5884
  },
4988
- { x: element.x + element.width / 2, y: element.y + element.height / 2 },
5885
+ {
5886
+ x: element.x + element.width / 2,
5887
+ y: element.y + resolvedTableHeight(element, data) / 2
5888
+ },
4989
5889
  (element.rotation ?? 0) * Math.PI / 180
4990
5890
  );
4991
5891
  return {
@@ -4998,7 +5898,7 @@ function tableCellText(element, cell, text) {
4998
5898
  height,
4999
5899
  rotation: element.rotation,
5000
5900
  fontSize: element.fontSize ?? 10,
5001
- textAlign: column.align === "right" ? "right" : column.align === "center" ? "center" : "left",
5901
+ textAlign: (column.align ?? column.cellStyle?.textAlign ?? element.cellAlign) === "right" ? "right" : (column.align ?? column.cellStyle?.textAlign ?? element.cellAlign) === "center" ? "center" : "left",
5002
5902
  verticalAlign: column.cellStyle?.verticalAlign ?? element.cellVerticalAlign ?? "middle",
5003
5903
  color: element.colors?.body?.color
5004
5904
  };
@@ -5027,11 +5927,70 @@ function parseTableCell(text, initial) {
5027
5927
  var PrintDesignTableEditing = class extends PrintDesignBase {
5028
5928
  constructor() {
5029
5929
  super(...arguments);
5930
+ __publicField(this, "tableCellSelection", null);
5030
5931
  __publicField(this, "tableEditing", null);
5031
5932
  }
5933
+ /** 返回表格单元格选区,供右键菜单与选区绘制使用。 */
5934
+ getTableCellSelection() {
5935
+ return this.tableCellSelection && { ...this.tableCellSelection };
5936
+ }
5937
+ /** 选择表格整行、整列或全部单元格。 */
5938
+ selectTableSelector(elementId, kind, index = 0, area = "body") {
5939
+ const element = this.getActivePage().elements.find(
5940
+ (item) => item.id === elementId
5941
+ );
5942
+ if (element?.type !== "table") return this;
5943
+ const columns = tableLeafColumns(element.columns).filter(
5944
+ (item) => !item.hidden
5945
+ ).length;
5946
+ const rows = resolveTableRows(element, this.options.data).length;
5947
+ const headerRows = element.showHeader === false ? 0 : tableHeaderDepth(element.columns);
5948
+ if (!columns || kind === "column" && (index < 0 || index >= columns) || kind === "row" && (index < 0 || index >= (area === "header" ? headerRows : rows)))
5949
+ return this;
5950
+ this.tableCellSelection = {
5951
+ elementId,
5952
+ area: kind === "row" ? area : "body",
5953
+ selectorKind: kind,
5954
+ startRow: kind === "row" ? index : 0,
5955
+ endRow: kind === "row" ? index : Math.max(0, rows - 1),
5956
+ startColumn: kind === "column" ? index : 0,
5957
+ endColumn: kind === "column" ? index : columns - 1
5958
+ };
5959
+ this.selectElements([elementId]);
5960
+ return this;
5961
+ }
5962
+ /** 选中一个单元格,按 Shift 扩展为矩形区域。 */
5963
+ selectTableCellAtPoint(element, point, extend = false) {
5964
+ const cell = hitTableCell(
5965
+ element,
5966
+ point,
5967
+ this.host.ownerDocument,
5968
+ this.options.data
5969
+ );
5970
+ if (!cell) return false;
5971
+ const current = this.tableCellSelection;
5972
+ if (extend && current?.elementId === element.id && current.area === cell.area) {
5973
+ this.tableCellSelection = {
5974
+ ...current,
5975
+ endRow: cell.rowIndex,
5976
+ endColumn: cell.columnIndex
5977
+ };
5978
+ } else {
5979
+ this.tableCellSelection = {
5980
+ elementId: element.id,
5981
+ area: cell.area,
5982
+ startRow: cell.rowIndex,
5983
+ endRow: cell.rowIndex,
5984
+ startColumn: cell.columnIndex,
5985
+ endColumn: cell.columnIndex
5986
+ };
5987
+ }
5988
+ return true;
5989
+ }
5032
5990
  beginTableEditing(element, point) {
5033
5991
  const cell = hitTableCell(element, point, this.host.ownerDocument);
5034
- if (!cell || !element.dataSource[cell.rowIndex]) return;
5992
+ if (!cell || cell.area !== "body" || !element.dataSource[cell.rowIndex])
5993
+ return;
5035
5994
  this.tableEditing?.editor.finish(false);
5036
5995
  this.selectElements([element.id]);
5037
5996
  const initial = readTableCell(
@@ -5076,7 +6035,10 @@ var PrintDesignTableEditing = class extends PrintDesignBase {
5076
6035
  }
5077
6036
  );
5078
6037
  this.tableEditing = { editor, cell, snapshot };
5079
- editor.mount(tableCellText(element, cell, text), this.pageOrigin());
6038
+ editor.mount(
6039
+ tableCellText(element, cell, text, this.options.data),
6040
+ this.pageOrigin()
6041
+ );
5080
6042
  this.render();
5081
6043
  }
5082
6044
  /** 外部变更和选中状态变化使草稿失效;缩放、平移则同步浮层位置。 */
@@ -5091,7 +6053,12 @@ var PrintDesignTableEditing = class extends PrintDesignBase {
5091
6053
  return;
5092
6054
  }
5093
6055
  session.editor.update(
5094
- tableCellText(element, session.cell, session.editor.initialText),
6056
+ tableCellText(
6057
+ element,
6058
+ session.cell,
6059
+ session.editor.initialText,
6060
+ this.options.data
6061
+ ),
5095
6062
  this.pageOrigin()
5096
6063
  );
5097
6064
  }
@@ -5369,6 +6336,48 @@ var PrintDesignRenderer = class extends PrintDesignTextEditing {
5369
6336
  drawPrintDesignBindingIndicator(ctx, element, scale, this.options);
5370
6337
  }
5371
6338
  ctx.restore();
6339
+ if (!preview && !this.options.readonly && this.selectedElementIds.length === 1) {
6340
+ const table = page.elements.find(
6341
+ (item) => item.id === this.selectedElementIds[0]
6342
+ );
6343
+ if (table?.type === "table" && table.visible !== false) {
6344
+ const active = this.tableCellSelection?.elementId === table.id ? this.tableCellSelection : null;
6345
+ ctx.save();
6346
+ const centerX = (table.x + table.width / 2) * scale;
6347
+ const centerY = (table.y + resolvedTableHeight(table, this.options.data) / 2) * scale;
6348
+ ctx.translate(centerX, centerY);
6349
+ ctx.rotate((table.rotation ?? 0) * Math.PI / 180);
6350
+ ctx.translate(-centerX, -centerY);
6351
+ ctx.textAlign = "center";
6352
+ ctx.textBaseline = "middle";
6353
+ ctx.font = `${Math.max(8, Math.min(11, 9 * this.zoom))}px sans-serif`;
6354
+ for (const selector of tableSelectors(table, this.options.data)) {
6355
+ const selected = active && (active.selectorKind === "all" || selector.kind === "column" && active.selectorKind === "column" && selector.index === active.startColumn || selector.kind === "row" && active.selectorKind === "row" && selector.area === active.area && selector.index === active.startRow);
6356
+ ctx.fillStyle = selected ? "#dbeafe" : "#f1f5f9";
6357
+ ctx.strokeStyle = "#94a3b8";
6358
+ ctx.lineWidth = 1;
6359
+ ctx.fillRect(
6360
+ selector.x * scale,
6361
+ selector.y * scale,
6362
+ selector.width * scale,
6363
+ selector.height * scale
6364
+ );
6365
+ ctx.strokeRect(
6366
+ selector.x * scale,
6367
+ selector.y * scale,
6368
+ selector.width * scale,
6369
+ selector.height * scale
6370
+ );
6371
+ ctx.fillStyle = selected ? "#1677ff" : "#475569";
6372
+ ctx.fillText(
6373
+ selector.label,
6374
+ (selector.x + selector.width / 2) * scale,
6375
+ (selector.y + selector.height / 2) * scale
6376
+ );
6377
+ }
6378
+ ctx.restore();
6379
+ }
6380
+ }
5372
6381
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
5373
6382
  drawPrintDesignGuideExtensions({
5374
6383
  guideColor: this.options.guideColor,
@@ -5484,7 +6493,7 @@ var PrintDesignRenderer = class extends PrintDesignTextEditing {
5484
6493
  if (!element || !resolvePrintDesignElementVisible(element, this.options.data))
5485
6494
  continue;
5486
6495
  const rotation = (element.rotation ?? 0) * Math.PI / 180;
5487
- const selectionRect = getSelectionRect(element, scale);
6496
+ const selectionRect = getSelectionRect(element, scale, this.options.data);
5488
6497
  const x = selectionRect.x * scale;
5489
6498
  const y = selectionRect.y * scale;
5490
6499
  const width = selectionRect.width * scale;
@@ -5500,6 +6509,7 @@ var PrintDesignRenderer = class extends PrintDesignTextEditing {
5500
6509
  ctx.setLineDash([2, 1.5]);
5501
6510
  ctx.strokeRect(x, y, width, height);
5502
6511
  ctx.setLineDash([]);
6512
+ if (element.type === "table") this.drawTableCellSelection(element, scale);
5503
6513
  const rotateHandleY = y - 17;
5504
6514
  ctx.beginPath();
5505
6515
  ctx.moveTo(cx, y);
@@ -5541,6 +6551,69 @@ var PrintDesignRenderer = class extends PrintDesignTextEditing {
5541
6551
  ctx.restore();
5542
6552
  }
5543
6553
  }
6554
+ drawTableCellSelection(element, scale) {
6555
+ const selection = this.tableCellSelection;
6556
+ if (!selection || selection.elementId !== element.id) return;
6557
+ const visible = tableLeafColumns(
6558
+ element.columns.filter((column) => !column.hidden)
6559
+ ).filter((column) => !column.hidden);
6560
+ const weights = visible.map((column) => Math.max(1, column.width ?? 100));
6561
+ const total = weights.reduce((sum, value) => sum + value, 0);
6562
+ if (selection.selectorKind === "all" || selection.selectorKind === "column") {
6563
+ if (total <= 0) return;
6564
+ const left2 = selection.selectorKind === "all" ? element.x : element.x + element.width * weights.slice(0, selection.startColumn).reduce((sum, value) => sum + value, 0) / total;
6565
+ const width = selection.selectorKind === "all" ? element.width : element.width * (weights[selection.startColumn] ?? 0) / total;
6566
+ const ctx2 = this.context;
6567
+ ctx2.fillStyle = "rgba(22,119,255,0.12)";
6568
+ ctx2.strokeStyle = "#1677ff";
6569
+ ctx2.fillRect(
6570
+ left2 * scale,
6571
+ element.y * scale,
6572
+ width * scale,
6573
+ resolvedTableHeight(element, this.options.data) * scale
6574
+ );
6575
+ ctx2.strokeRect(
6576
+ left2 * scale,
6577
+ element.y * scale,
6578
+ width * scale,
6579
+ resolvedTableHeight(element, this.options.data) * scale
6580
+ );
6581
+ return;
6582
+ }
6583
+ const isHeader = selection.area === "header";
6584
+ const rows = resolveTableRows(element, this.options.data);
6585
+ const limit = Math.max(0, Math.floor(element.printRowCount ?? 0));
6586
+ const resolvedCells = isHeader ? tableHeaderCells(element) : resolveTableCells(
6587
+ element,
6588
+ limit ? rows.slice(0, limit) : rows,
6589
+ visible
6590
+ );
6591
+ const bounds = expandTableCellSelection(resolvedCells, selection);
6592
+ const firstColumn = Math.max(0, bounds.firstColumn);
6593
+ const lastColumn = Math.min(visible.length - 1, bounds.lastColumn);
6594
+ if (total <= 0 || lastColumn < firstColumn) return;
6595
+ const firstRow = Math.max(0, bounds.firstRow);
6596
+ const lastRow = bounds.lastRow;
6597
+ const left = element.x + element.width * weights.slice(0, firstColumn).reduce((sum, value) => sum + value, 0) / total;
6598
+ const right = element.x + element.width * weights.slice(0, lastColumn + 1).reduce((sum, value) => sum + value, 0) / total;
6599
+ const top = element.y + (isHeader || element.showHeader === false ? 0 : tableHeaderDepth(element.columns) * (element.headerRowHeight ?? 9)) + firstRow * (isHeader ? element.headerRowHeight ?? 9 : element.rowHeight ?? 8);
6600
+ const height = (lastRow - firstRow + 1) * (isHeader ? element.headerRowHeight ?? 9 : element.rowHeight ?? 8);
6601
+ const ctx = this.context;
6602
+ ctx.fillStyle = "rgba(22,119,255,0.12)";
6603
+ ctx.strokeStyle = "#1677ff";
6604
+ ctx.fillRect(
6605
+ left * scale,
6606
+ top * scale,
6607
+ (right - left) * scale,
6608
+ height * scale
6609
+ );
6610
+ ctx.strokeRect(
6611
+ left * scale,
6612
+ top * scale,
6613
+ (right - left) * scale,
6614
+ height * scale
6615
+ );
6616
+ }
5544
6617
  /** 组合四边控制柄与外部图层重叠时,优先保留其它图层的选择。 */
5545
6618
  hitTestGroupResizeHandle(point, page, scale) {
5546
6619
  if (this.options.readonly || page.elements.some(
@@ -5562,7 +6635,9 @@ var PrintDesignRenderer = class extends PrintDesignTextEditing {
5562
6635
  if (!element || element.groupPath?.length || element.locked || !resolvePrintDesignElementVisible(element, this.options.data))
5563
6636
  return void 0;
5564
6637
  const tolerance = 8 / scale;
5565
- const selectionRect = getSelectionRect(element, scale);
6638
+ const selectionRect = getSelectionRect(element, scale, this.options.data);
6639
+ if (element.type === "table" && point.y > selectionRect.y + 2 / scale && point.y < selectionRect.y + selectionRect.height - 2 / scale && hitTableColumnBoundary(element, point, scale, this.host.ownerDocument))
6640
+ return void 0;
5566
6641
  for (const handle of RESIZE_HANDLES) {
5567
6642
  const corner = getRotatedRectangleCorner(
5568
6643
  selectionRect,
@@ -5581,7 +6656,7 @@ var PrintDesignRenderer = class extends PrintDesignTextEditing {
5581
6656
  );
5582
6657
  if (!element || element.groupPath?.length || element.locked || !resolvePrintDesignElementVisible(element, this.options.data))
5583
6658
  return void 0;
5584
- const selectionRect = getSelectionRect(element, scale);
6659
+ const selectionRect = getSelectionRect(element, scale, this.options.data);
5585
6660
  const center = {
5586
6661
  x: selectionRect.x + selectionRect.width / 2,
5587
6662
  y: selectionRect.y + selectionRect.height / 2
@@ -5595,7 +6670,7 @@ var PrintDesignRenderer = class extends PrintDesignTextEditing {
5595
6670
  }
5596
6671
  getRotatedCorner(element, handle, scale) {
5597
6672
  return getRotatedRectangleCorner(
5598
- getSelectionRect(element, scale),
6673
+ getSelectionRect(element, scale, this.options.data),
5599
6674
  element.rotation ?? 0,
5600
6675
  handle
5601
6676
  );
@@ -5701,6 +6776,27 @@ var PrintDesignInteraction = class extends PrintDesignRenderer {
5701
6776
  return;
5702
6777
  }
5703
6778
  }
6779
+ if (!this.options.readonly && !event.metaKey && !event.ctrlKey) {
6780
+ const selectedTable = [...page.elements].reverse().find(
6781
+ (item) => item.type === "table" && this.selectedElementIds.includes(item.id)
6782
+ );
6783
+ if (selectedTable?.type === "table") {
6784
+ const selector = hitTableSelector(
6785
+ selectedTable,
6786
+ point,
6787
+ this.options.data
6788
+ );
6789
+ if (selector) {
6790
+ this.selectTableSelector(
6791
+ selectedTable.id,
6792
+ selector.kind,
6793
+ selector.index,
6794
+ selector.area
6795
+ );
6796
+ return;
6797
+ }
6798
+ }
6799
+ }
5704
6800
  const hit = hitTestPrintDesignPage(
5705
6801
  page,
5706
6802
  point,
@@ -5767,7 +6863,7 @@ var PrintDesignInteraction = class extends PrintDesignRenderer {
5767
6863
  x: element.x,
5768
6864
  y: element.y,
5769
6865
  width: element.width,
5770
- height: element.height
6866
+ height: element.type === "table" ? resolvedTableHeight(element, this.options.data) : element.height
5771
6867
  },
5772
6868
  anchor,
5773
6869
  rotation: element.rotation ?? 0
@@ -5782,7 +6878,7 @@ var PrintDesignInteraction = class extends PrintDesignRenderer {
5782
6878
  );
5783
6879
  return;
5784
6880
  }
5785
- if (!this.options.readonly && (hit?.type === "table" || hit?.type === "pivotTable") && this.selectedElementIds.length === 1 && this.selectedElementIds[0] === hit.id) {
6881
+ if (!this.options.readonly && (hit?.type === "table" || hit?.type === "pivotTable") && !event.shiftKey && !event.metaKey && !event.ctrlKey) {
5786
6882
  const tableColumn = hitTableColumnBoundary(
5787
6883
  hit.type === "pivotTable" ? materializePivotTable(hit, this.options.data) : hit,
5788
6884
  point,
@@ -5790,6 +6886,11 @@ var PrintDesignInteraction = class extends PrintDesignRenderer {
5790
6886
  this.container.ownerDocument
5791
6887
  );
5792
6888
  if (tableColumn) {
6889
+ if (this.selectedElementIds.length !== 1 || this.selectedElementIds[0] !== hit.id) {
6890
+ this.selectedElementIds = [hit.id];
6891
+ this.emitSelection();
6892
+ this.render();
6893
+ }
5793
6894
  this.canvas.setPointerCapture?.(event.pointerId);
5794
6895
  this.dragSession = {
5795
6896
  mode: "table-column",
@@ -5805,6 +6906,13 @@ var PrintDesignInteraction = class extends PrintDesignRenderer {
5805
6906
  return;
5806
6907
  }
5807
6908
  }
6909
+ if (hit?.type === "table" && !this.options.readonly && !hit.locked && !event.metaKey && !event.ctrlKey) {
6910
+ if (this.selectTableCellAtPoint(hit, point, event.shiftKey) && event.shiftKey) {
6911
+ this.selectElements([hit.id]);
6912
+ this.render();
6913
+ return;
6914
+ }
6915
+ }
5808
6916
  if (!hit) {
5809
6917
  const initialIds = event.shiftKey || event.metaKey || event.ctrlKey ? [...this.selectedElementIds] : [];
5810
6918
  this.selectedElementIds = initialIds;
@@ -6275,6 +7383,15 @@ var PrintDesignInteraction = class extends PrintDesignRenderer {
6275
7383
  return;
6276
7384
  }
6277
7385
  const insidePage = cursorPoint.x >= 0 && cursorPoint.y >= 0 && cursorPoint.x <= page.width && cursorPoint.y <= page.height;
7386
+ if (!this.spacePressed && !this.options.readonly && this.selectedElementIds.length === 1) {
7387
+ const table = page.elements.find(
7388
+ (item) => item.id === this.selectedElementIds[0]
7389
+ );
7390
+ if (table?.type === "table" && hitTableSelector(table, cursorPoint, this.options.data)) {
7391
+ this.canvas.style.cursor = "pointer";
7392
+ return;
7393
+ }
7394
+ }
6278
7395
  if (this.spacePressed || !insidePage) {
6279
7396
  this.canvas.style.cursor = "grab";
6280
7397
  return;
@@ -6320,7 +7437,7 @@ var PrintDesignInteraction = class extends PrintDesignRenderer {
6320
7437
  return;
6321
7438
  }
6322
7439
  }
6323
- if (!this.options.readonly && (hit?.type === "table" || hit?.type === "pivotTable") && this.selectedElementIds.length === 1 && this.selectedElementIds[0] === hit.id && hitTableColumnBoundary(
7440
+ if (!this.options.readonly && (hit?.type === "table" || hit?.type === "pivotTable") && !event.shiftKey && !event.metaKey && !event.ctrlKey && hitTableColumnBoundary(
6324
7441
  hit.type === "pivotTable" ? materializePivotTable(hit, this.options.data) : hit,
6325
7442
  cursorPoint,
6326
7443
  origin.scale,
@@ -6382,6 +7499,24 @@ var PrintDesignInteraction = class extends PrintDesignRenderer {
6382
7499
  element.y = Math.round((center.y - height / 2) * 100) / 100;
6383
7500
  element.width = Math.round(width * 100) / 100;
6384
7501
  element.height = Math.round(height * 100) / 100;
7502
+ if (element.type === "table" && vertical) {
7503
+ const totalRows = resolveTableRows(element, this.options.data).length;
7504
+ const limit = Math.max(0, Math.floor(element.printRowCount ?? 0));
7505
+ const count = Math.max(
7506
+ limit ? Math.min(totalRows, limit) : totalRows,
7507
+ element.fillEmptyRows ? limit : 0,
7508
+ 1
7509
+ );
7510
+ const header = element.showHeader === false ? 0 : tableHeaderDepth(element.columns) * (element.headerRowHeight ?? 9);
7511
+ const summary = tableContentHeight(
7512
+ { ...element, fillEmptyRows: false, printRowCount: 0 },
7513
+ 0
7514
+ ) - header;
7515
+ element.rowHeight = Math.max(
7516
+ 1,
7517
+ Math.round((height - header - summary) / count * 100) / 100
7518
+ );
7519
+ }
6385
7520
  }
6386
7521
  };
6387
7522
  function oppositeResizeHandle(handle) {
@@ -6573,12 +7708,244 @@ function alignPrintDesignLayers(page, ids, align) {
6573
7708
  });
6574
7709
  }
6575
7710
 
7711
+ // src/model/table-structure.ts
7712
+ function insertTableColumn(columns, index, column) {
7713
+ const copy = structuredClone(columns);
7714
+ let position = 0;
7715
+ const walk = (items) => {
7716
+ for (let i = 0; i < items.length; i++) {
7717
+ const item = items[i];
7718
+ if (item.hidden) continue;
7719
+ const children = item.children?.filter((child) => !child.hidden);
7720
+ if (children?.length) {
7721
+ if (walk(item.children)) return true;
7722
+ } else {
7723
+ if (position === index) {
7724
+ items.splice(i, 0, column);
7725
+ return true;
7726
+ }
7727
+ position++;
7728
+ }
7729
+ }
7730
+ if (position === index && items === copy) {
7731
+ items.push(column);
7732
+ return true;
7733
+ }
7734
+ return false;
7735
+ };
7736
+ walk(copy);
7737
+ return copy;
7738
+ }
7739
+ function shiftTableCells(cells, axis, index) {
7740
+ return cells?.map((cell) => {
7741
+ const start = axis === "row" ? cell.rowIndex : cell.columnIndex;
7742
+ const span = axis === "row" ? cell.rowSpan ?? 1 : cell.colSpan ?? 1;
7743
+ if (start >= index)
7744
+ return {
7745
+ ...cell,
7746
+ [axis === "row" ? "rowIndex" : "columnIndex"]: start + 1
7747
+ };
7748
+ if (start + span > index)
7749
+ return { ...cell, [axis === "row" ? "rowSpan" : "colSpan"]: span + 1 };
7750
+ return cell;
7751
+ });
7752
+ }
7753
+ function emptyTableRow(element) {
7754
+ return tableLeafColumns(element.columns).filter((column) => !column.hidden).reduce(
7755
+ (row, column) => writeTableCell(
7756
+ row,
7757
+ column.dataIndex,
7758
+ ["money", "progress", "rate"].includes(column.cellType ?? "") ? 0 : ["checkbox", "radio", "switch"].includes(column.cellType ?? "") ? false : ""
7759
+ ),
7760
+ {}
7761
+ );
7762
+ }
7763
+
6576
7764
  // src/engine/print-design-actions.ts
6577
7765
  var PrintDesignActions = class extends PrintDesignGrouping {
6578
7766
  constructor() {
6579
7767
  super(...arguments);
6580
7768
  __publicField(this, "elementClipboard", []);
6581
7769
  }
7770
+ /** 定位画布中的行、列或全选选择器。 */
7771
+ tableSelectorAtClientPoint(clientX, clientY) {
7772
+ if (this.selectedElementIds.length !== 1 || this.options.readonly)
7773
+ return null;
7774
+ const rect = this.canvas.getBoundingClientRect();
7775
+ const origin = this.pageOrigin();
7776
+ const point = {
7777
+ x: (clientX - rect.left - origin.x) / origin.scale,
7778
+ y: (clientY - rect.top - origin.y) / origin.scale
7779
+ };
7780
+ for (const item of [...this.getActivePage().elements].reverse()) {
7781
+ if (item.type !== "table" || item.visible === false || !this.selectedElementIds.includes(item.id))
7782
+ continue;
7783
+ const selector = hitTableSelector(item, point, this.options.data);
7784
+ if (selector)
7785
+ return {
7786
+ elementId: item.id,
7787
+ kind: selector.kind,
7788
+ index: selector.index,
7789
+ area: selector.area
7790
+ };
7791
+ }
7792
+ return null;
7793
+ }
7794
+ /** 定位画布右键所指的表头或表体单元格。 */
7795
+ tableCellAtClientPoint(clientX, clientY) {
7796
+ const rect = this.canvas.getBoundingClientRect();
7797
+ const origin = this.pageOrigin();
7798
+ const point = {
7799
+ x: (clientX - rect.left - origin.x) / origin.scale,
7800
+ y: (clientY - rect.top - origin.y) / origin.scale
7801
+ };
7802
+ const hit = hitTestPrintDesignPage(
7803
+ this.getActivePage(),
7804
+ point,
7805
+ 0,
7806
+ this.options.data
7807
+ );
7808
+ if (hit?.type !== "table") return null;
7809
+ const cell = hitTableCell(
7810
+ hit,
7811
+ point,
7812
+ this.host.ownerDocument,
7813
+ this.options.data
7814
+ );
7815
+ return cell ? {
7816
+ elementId: hit.id,
7817
+ area: cell.area,
7818
+ rowIndex: cell.rowIndex,
7819
+ columnIndex: cell.columnIndex
7820
+ } : null;
7821
+ }
7822
+ /** 合并右键单元格与相邻单元格;变更纳入撤销历史。 */
7823
+ mergeTableCell(elementId, rowIndex, columnIndex, rowSpan, colSpan) {
7824
+ if (this.options.readonly) return this;
7825
+ const element = this.getActivePage().elements.find(
7826
+ (item) => item.id === elementId
7827
+ );
7828
+ if (element?.type !== "table" || element.locked) return this;
7829
+ const rows = resolveTableRows(element, this.options.data);
7830
+ const columns = tableLeafColumns(element.columns).filter(
7831
+ (item) => !item.hidden
7832
+ );
7833
+ if (rowIndex < 0 || columnIndex < 0 || rowIndex + rowSpan > rows.length || columnIndex + colSpan > columns.length)
7834
+ return this;
7835
+ if (Array.from(
7836
+ { length: rowSpan },
7837
+ (_, r) => Array.from(
7838
+ { length: colSpan },
7839
+ (_2, c) => coveringTableCell(element.cells, rowIndex + r, columnIndex + c)
7840
+ )
7841
+ ).flat().some(
7842
+ (cell) => cell && (cell.rowIndex !== rowIndex || cell.columnIndex !== columnIndex) && ((cell.rowSpan ?? 1) > 1 || (cell.colSpan ?? 1) > 1)
7843
+ ))
7844
+ return this;
7845
+ this.commitMutation((document) => {
7846
+ const target = this.getPageIn(document).elements.find(
7847
+ (item) => item.id === elementId
7848
+ );
7849
+ if (target?.type !== "table") return;
7850
+ const previous = target.cells?.find(
7851
+ (cell) => cell.rowIndex === rowIndex && cell.columnIndex === columnIndex
7852
+ );
7853
+ target.cells = [
7854
+ ...(target.cells ?? []).filter((cell) => cell !== previous),
7855
+ { ...previous, rowIndex, columnIndex, rowSpan, colSpan }
7856
+ ];
7857
+ });
7858
+ return this;
7859
+ }
7860
+ /** 合并表头选区,保留左上角单元格标题。 */
7861
+ mergeTableHeaderCell(elementId, rowIndex, columnIndex, rowSpan, colSpan) {
7862
+ if (this.options.readonly) return this;
7863
+ const element = this.getActivePage().elements.find(
7864
+ (item) => item.id === elementId
7865
+ );
7866
+ if (element?.type !== "table" || element.locked || element.showHeader === false)
7867
+ return this;
7868
+ const depth = tableHeaderDepth(element.columns);
7869
+ const columns = tableLeafColumns(element.columns).filter(
7870
+ (item) => !item.hidden
7871
+ ).length;
7872
+ if (rowIndex < 0 || columnIndex < 0 || rowSpan < 1 || colSpan < 1 || rowIndex + rowSpan > depth || columnIndex + colSpan > columns)
7873
+ return this;
7874
+ if (rowSpan * colSpan > 1) {
7875
+ const cells = tableHeaderCells(element);
7876
+ const intersects = (cell) => cell.rowIndex < rowIndex + rowSpan && cell.rowIndex + cell.rowSpan > rowIndex && cell.columnIndex < columnIndex + colSpan && cell.columnIndex + cell.colSpan > columnIndex;
7877
+ if (!cells.some(
7878
+ (cell) => cell.rowIndex === rowIndex && cell.columnIndex === columnIndex
7879
+ ) || cells.some(
7880
+ (cell) => intersects(cell) && (cell.rowIndex < rowIndex || cell.columnIndex < columnIndex || cell.rowIndex + cell.rowSpan > rowIndex + rowSpan || cell.columnIndex + cell.colSpan > columnIndex + colSpan)
7881
+ ))
7882
+ return this;
7883
+ }
7884
+ this.commitMutation((document) => {
7885
+ const target = this.getPageIn(document).elements.find(
7886
+ (item) => item.id === elementId
7887
+ );
7888
+ if (target?.type !== "table") return;
7889
+ target.headerCells = [
7890
+ ...(target.headerCells ?? []).filter(
7891
+ (cell) => rowSpan * colSpan === 1 ? cell.rowIndex !== rowIndex || cell.columnIndex !== columnIndex : cell.rowIndex < rowIndex || cell.columnIndex < columnIndex || cell.rowIndex + (cell.rowSpan ?? 1) > rowIndex + rowSpan || cell.columnIndex + (cell.colSpan ?? 1) > columnIndex + colSpan
7892
+ ),
7893
+ ...rowSpan * colSpan > 1 ? [{ rowIndex, columnIndex, rowSpan, colSpan }] : []
7894
+ ];
7895
+ });
7896
+ return this;
7897
+ }
7898
+ /** 在静态表格指定数据行的上方或下方插入空行。 */
7899
+ insertTableRow(elementId, rowIndex, after = true) {
7900
+ if (this.options.readonly) return this;
7901
+ const element = this.getActivePage().elements.find(
7902
+ (item) => item.id === elementId
7903
+ );
7904
+ if (element?.type !== "table" || element.locked || element.dataMode === "binding")
7905
+ return this;
7906
+ const index = Math.max(
7907
+ 0,
7908
+ Math.min(element.dataSource.length, rowIndex + (after ? 1 : 0))
7909
+ );
7910
+ this.commitMutation((document) => {
7911
+ const target = this.getPageIn(document).elements.find(
7912
+ (item) => item.id === elementId
7913
+ );
7914
+ if (target?.type !== "table") return;
7915
+ target.dataSource.splice(index, 0, emptyTableRow(target));
7916
+ target.cells = shiftTableCells(target.cells, "row", index);
7917
+ });
7918
+ return this;
7919
+ }
7920
+ /** 在可见叶列的左侧或右侧插入空白列。 */
7921
+ insertTableColumn(elementId, columnIndex, after = true) {
7922
+ if (this.options.readonly) return this;
7923
+ const element = this.getActivePage().elements.find(
7924
+ (item) => item.id === elementId
7925
+ );
7926
+ if (element?.type !== "table" || element.locked) return this;
7927
+ const leaves = tableLeafColumns(element.columns).filter(
7928
+ (item) => !item.hidden
7929
+ );
7930
+ if (columnIndex < 0 || columnIndex >= leaves.length) return this;
7931
+ const index = columnIndex + (after ? 1 : 0);
7932
+ const key = createPrintDesignId("column");
7933
+ this.commitMutation((document) => {
7934
+ const target = this.getPageIn(document).elements.find(
7935
+ (item) => item.id === elementId
7936
+ );
7937
+ if (target?.type !== "table") return;
7938
+ target.columns = insertTableColumn(target.columns, index, {
7939
+ key,
7940
+ dataIndex: key,
7941
+ title: "\u65B0\u5217",
7942
+ width: leaves[columnIndex]?.width ?? 100
7943
+ });
7944
+ target.cells = shiftTableCells(target.cells, "column", index);
7945
+ target.headerCells = shiftTableCells(target.headerCells, "column", index);
7946
+ });
7947
+ return this;
7948
+ }
6582
7949
  /** 画布右键命中选区;点中已选项保留多选,空白清空选区。 */
6583
7950
  selectAtClientPoint(clientX, clientY) {
6584
7951
  const rect = this.canvas.getBoundingClientRect(), origin = this.pageOrigin();
@@ -6594,10 +7961,24 @@ var PrintDesignActions = class extends PrintDesignGrouping {
6594
7961
  this.options.data
6595
7962
  );
6596
7963
  if (hit) {
7964
+ if (hit.type === "table") {
7965
+ const cell = hitTableCell(
7966
+ hit,
7967
+ point,
7968
+ this.host.ownerDocument,
7969
+ this.options.data
7970
+ );
7971
+ const selected = this.tableCellSelection;
7972
+ if (cell && (!selected || selected.elementId !== hit.id || selected.area !== cell.area || cell.rowIndex < Math.min(selected.startRow, selected.endRow) || cell.rowIndex > Math.max(selected.startRow, selected.endRow) || cell.columnIndex < Math.min(selected.startColumn, selected.endColumn) || cell.columnIndex > Math.max(selected.startColumn, selected.endColumn)))
7973
+ this.selectTableCellAtPoint(hit, point);
7974
+ } else this.tableCellSelection = null;
6597
7975
  this.selectElements(
6598
7976
  this.selectedElementIds.includes(hit.id) ? this.selectedElementIds : [hit.id]
6599
7977
  );
6600
- } else this.selectElements([]);
7978
+ } else {
7979
+ this.tableCellSelection = null;
7980
+ this.selectElements([]);
7981
+ }
6601
7982
  return this;
6602
7983
  }
6603
7984
  /** 复制选中元素,保留组合、数据及样式,不使用系统剪贴板。 */
@@ -6738,12 +8119,50 @@ var PrintDesignActions = class extends PrintDesignGrouping {
6738
8119
 
6739
8120
  // src/engine/print-design.ts
6740
8121
  var PrintDesign = class extends PrintDesignActions {
8122
+ constructor() {
8123
+ super(...arguments);
8124
+ __publicField(this, "temporaryDocumentSnapshot", null);
8125
+ }
8126
+ /** 临时显示演示文档;原文档、选区与撤销记录保留在实例中。 */
8127
+ showTemporaryDocument(document, pageId, elementId) {
8128
+ this.temporaryDocumentSnapshot ?? (this.temporaryDocumentSnapshot = {
8129
+ document: this.getDocument(),
8130
+ activePageId: this.activePageId,
8131
+ elementIds: [...this.selectedElementIds],
8132
+ guide: this.selectedGuide ? { ...this.selectedGuide } : null
8133
+ });
8134
+ this.document = parsePrintDesignDocument(document);
8135
+ this.activePageId = pageId && this.document.pages.some((page) => page.id === pageId) ? pageId : this.document.pages[0].id;
8136
+ this.selectedElementIds = elementId && this.document.pages.find((page) => page.id === this.activePageId)?.elements.some((item) => item.id === elementId) ? [elementId] : [];
8137
+ this.selectedGuide = null;
8138
+ this.options.onActivePageChange?.(this.activePageId);
8139
+ this.emitSelection();
8140
+ this.emitChange();
8141
+ this.render();
8142
+ return this;
8143
+ }
8144
+ /** 恢复临时演示前的文档和选区,保留原有撤销记录。 */
8145
+ restoreTemporaryDocument() {
8146
+ const saved = this.temporaryDocumentSnapshot;
8147
+ if (!saved) return this;
8148
+ this.temporaryDocumentSnapshot = null;
8149
+ this.document = saved.document;
8150
+ this.activePageId = saved.activePageId;
8151
+ this.selectedElementIds = saved.elementIds;
8152
+ this.selectedGuide = saved.guide;
8153
+ this.options.onActivePageChange?.(this.activePageId);
8154
+ this.emitSelection();
8155
+ this.emitChange();
8156
+ this.render();
8157
+ return this;
8158
+ }
6741
8159
  /** 获取文档副本。 */
6742
8160
  getDocument() {
6743
8161
  return clonePrintDesignDocument(this.document);
6744
8162
  }
6745
8163
  /** 替换当前文档并清空撤销历史。 */
6746
8164
  setDocument(document) {
8165
+ this.temporaryDocumentSnapshot = null;
6747
8166
  this.document = parsePrintDesignDocument(document);
6748
8167
  this.activePageId = this.document.pages[0].id;
6749
8168
  this.selectedElementIds = [];
@@ -6871,6 +8290,22 @@ var PrintDesign = class extends PrintDesignActions {
6871
8290
  y: (y - origin.y) / origin.scale
6872
8291
  };
6873
8292
  }
8293
+ /** 获取当前页元素在浏览器视口中的包围盒,包含旋转后的占用范围。 */
8294
+ getElementClientRect(elementId) {
8295
+ const element = this.getActivePage().elements.find(
8296
+ (item) => item.id === elementId
8297
+ );
8298
+ if (!element || element.visible === false) return null;
8299
+ const bounds = elementBounds(element);
8300
+ const origin = this.pageOrigin();
8301
+ const canvas = this.canvas.getBoundingClientRect();
8302
+ return {
8303
+ left: canvas.left + origin.x + bounds.x * origin.scale,
8304
+ top: canvas.top + origin.y + bounds.y * origin.scale,
8305
+ width: bounds.width * origin.scale,
8306
+ height: bounds.height * origin.scale
8307
+ };
8308
+ }
6874
8309
  /** 新增元素并选中。 */
6875
8310
  addElement(element) {
6876
8311
  const next = { ...element, id: element.id ?? createPrintDesignId() };
@@ -7154,4 +8589,6 @@ exports.serializePrintDesignDocument = serializePrintDesignDocument;
7154
8589
  exports.tableLeafColumns = tableLeafColumns;
7155
8590
  exports.toDisplayUnit = toDisplayUnit;
7156
8591
  exports.transformPrintDesignToHtml = transformPrintDesignToHtml;
8592
+ exports.transformPrintDesignToHtmlAsync = transformPrintDesignToHtmlAsync;
8593
+ exports.transformPrintDesignToHtmlBatches = transformPrintDesignToHtmlBatches;
7157
8594
  exports.writeTableCell = writeTableCell;