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