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