@crazyhappyone/auto-graph 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.cjs +2588 -484
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +2588 -484
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +2234 -130
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +93 -1
- package/dist/index.d.ts +93 -1
- package/dist/index.js +2234 -130
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -317,27 +317,31 @@ function applyDistribute(constraints, boxes, locks, diagnostics) {
|
|
|
317
317
|
function repairOverlaps(input, boxes, locks, diagnostics) {
|
|
318
318
|
const spacing = input.overlapSpacing ?? 40;
|
|
319
319
|
const axis = input.direction === "LR" || input.direction === "RL" ? "x" : "y";
|
|
320
|
+
const secondaryAxis = axis === "x" ? "y" : "x";
|
|
320
321
|
const ids = [...boxes.keys()].sort();
|
|
321
|
-
for (
|
|
322
|
-
for (const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
322
|
+
for (let pass = 0; pass < 2; pass += 1) {
|
|
323
|
+
for (const firstId of ids) {
|
|
324
|
+
for (const secondId of ids) {
|
|
325
|
+
if (firstId >= secondId) {
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
const first = boxes.get(firstId);
|
|
329
|
+
const second = boxes.get(secondId);
|
|
330
|
+
if (first === void 0 || second === void 0 || !intersectsAabb(first, second)) {
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
const firstLocked = locks.has(firstId);
|
|
334
|
+
const secondLocked = locks.has(secondId);
|
|
335
|
+
if (firstLocked && secondLocked) {
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
const movingId = firstLocked ? secondId : secondLocked ? firstId : secondId;
|
|
339
|
+
const moving = movingId === firstId ? first : second;
|
|
340
|
+
const fixed = movingId === firstId ? second : first;
|
|
341
|
+
const repairAxis = firstLocked === secondLocked && pass === 0 ? secondaryAxis : axis;
|
|
342
|
+
const moved = movePastOverlap(moving, fixed, repairAxis, spacing);
|
|
343
|
+
boxes.set(movingId, moved);
|
|
335
344
|
}
|
|
336
|
-
const movingId = firstLocked ? secondId : firstId;
|
|
337
|
-
const moving = firstLocked ? second : first;
|
|
338
|
-
const fixed = firstLocked ? first : second;
|
|
339
|
-
const moved = movePastOverlap(moving, fixed, axis, spacing);
|
|
340
|
-
boxes.set(movingId, moved);
|
|
341
345
|
}
|
|
342
346
|
}
|
|
343
347
|
for (const firstId of ids) {
|
|
@@ -1185,6 +1189,9 @@ var DEFAULT_GROUP_PADDING = {
|
|
|
1185
1189
|
};
|
|
1186
1190
|
var DEFAULT_LABEL_MAX_WIDTH = 160;
|
|
1187
1191
|
var DEFAULT_FONT = { fontFamily: "Arial", fontSize: 14, lineHeight: 18 };
|
|
1192
|
+
var DEFAULT_MATRIX_CELL_SIZE = { width: 120, height: 36 };
|
|
1193
|
+
var DEFAULT_TABLE_CELL_SIZE = { width: 128, height: 34 };
|
|
1194
|
+
var DEFAULT_PANEL_ITEM_HEIGHT = 28;
|
|
1188
1195
|
function normalizeDiagramDsl(dslValue, options = {}) {
|
|
1189
1196
|
const dsl = dslValue;
|
|
1190
1197
|
const diagnostics = validateReferences(dsl);
|
|
@@ -1198,6 +1205,9 @@ function normalizeDiagramDsl(dslValue, options = {}) {
|
|
|
1198
1205
|
const routeKind = dsl.routing?.kind ?? "orthogonal";
|
|
1199
1206
|
const portShifting = normalizePortShifting(dsl.routing?.portShifting);
|
|
1200
1207
|
const primaryReadingDirection = dsl.layout?.primaryReadingDirection;
|
|
1208
|
+
const matrices = normalizeMatrices(dsl);
|
|
1209
|
+
const tables = normalizeTables(dsl);
|
|
1210
|
+
const evidencePanels = normalizeEvidencePanels(dsl);
|
|
1201
1211
|
const diagram = {
|
|
1202
1212
|
id: options.id ?? dsl.id ?? "diagram",
|
|
1203
1213
|
...dsl.title === void 0 ? {} : { title: dsl.title },
|
|
@@ -1206,6 +1216,9 @@ function normalizeDiagramDsl(dslValue, options = {}) {
|
|
|
1206
1216
|
edges: normalizeEdges(dsl),
|
|
1207
1217
|
groups: normalizeGroups(dsl, measurer),
|
|
1208
1218
|
swimlanes: normalizeSwimlanes(dsl),
|
|
1219
|
+
...matrices === void 0 ? {} : { matrices },
|
|
1220
|
+
...tables === void 0 ? {} : { tables },
|
|
1221
|
+
...evidencePanels === void 0 ? {} : { evidencePanels },
|
|
1209
1222
|
constraints: normalizeConstraints(dsl),
|
|
1210
1223
|
diagnostics: [],
|
|
1211
1224
|
...dsl.frame === void 0 ? {} : { frame: normalizeFrame(dsl.frame) },
|
|
@@ -1404,6 +1417,102 @@ function normalizeSwimlanes(dsl) {
|
|
|
1404
1417
|
};
|
|
1405
1418
|
});
|
|
1406
1419
|
}
|
|
1420
|
+
function normalizeMatrices(dsl) {
|
|
1421
|
+
if (dsl.matrices === void 0) {
|
|
1422
|
+
return void 0;
|
|
1423
|
+
}
|
|
1424
|
+
return dsl.matrices.map((matrix) => ({
|
|
1425
|
+
id: matrix.id,
|
|
1426
|
+
rows: [...matrix.rows],
|
|
1427
|
+
cols: [...matrix.cols],
|
|
1428
|
+
cells: matrix.cells.map((row) => row.map(cell)),
|
|
1429
|
+
...matrix.position === void 0 ? {} : { position: point(matrix.position) },
|
|
1430
|
+
size: matrix.size ?? {
|
|
1431
|
+
width: defaultMatrixRowHeaderWidth(matrix) + Math.max(1, matrix.cols.length) * DEFAULT_MATRIX_CELL_SIZE.width,
|
|
1432
|
+
height: Math.max(1, matrix.rows.length + 1) * DEFAULT_MATRIX_CELL_SIZE.height
|
|
1433
|
+
},
|
|
1434
|
+
...matrix.style === void 0 ? {} : { style: style(matrix.style) }
|
|
1435
|
+
}));
|
|
1436
|
+
}
|
|
1437
|
+
function defaultMatrixRowHeaderWidth(matrix) {
|
|
1438
|
+
return matrix.rows.length === 0 ? 0 : Math.min(96, DEFAULT_MATRIX_CELL_SIZE.width);
|
|
1439
|
+
}
|
|
1440
|
+
function normalizeTables(dsl) {
|
|
1441
|
+
if (dsl.tables === void 0) {
|
|
1442
|
+
return void 0;
|
|
1443
|
+
}
|
|
1444
|
+
return dsl.tables.map((table) => ({
|
|
1445
|
+
id: table.id,
|
|
1446
|
+
columns: table.columns.map(tableColumn),
|
|
1447
|
+
rows: table.rows.map(tableRow),
|
|
1448
|
+
...table.position === void 0 ? {} : { position: point(table.position) },
|
|
1449
|
+
size: table.size ?? {
|
|
1450
|
+
width: Math.max(1, table.columns.length) * DEFAULT_TABLE_CELL_SIZE.width,
|
|
1451
|
+
height: Math.max(1, table.rows.length + 1) * DEFAULT_TABLE_CELL_SIZE.height
|
|
1452
|
+
},
|
|
1453
|
+
...table.style === void 0 ? {} : { style: style(table.style) }
|
|
1454
|
+
}));
|
|
1455
|
+
}
|
|
1456
|
+
function normalizeEvidencePanels(dsl) {
|
|
1457
|
+
if (dsl.evidencePanels === void 0) {
|
|
1458
|
+
return void 0;
|
|
1459
|
+
}
|
|
1460
|
+
return dsl.evidencePanels.map((panel) => ({
|
|
1461
|
+
id: panel.id,
|
|
1462
|
+
kind: panel.kind,
|
|
1463
|
+
items: panel.items.map(panelItem),
|
|
1464
|
+
...panel.position === void 0 ? {} : { position: point(panel.position) },
|
|
1465
|
+
size: panel.size ?? {
|
|
1466
|
+
width: 320,
|
|
1467
|
+
height: Math.max(1, panel.items.length) * DEFAULT_PANEL_ITEM_HEIGHT
|
|
1468
|
+
},
|
|
1469
|
+
...panel.style === void 0 ? {} : { style: style(panel.style) }
|
|
1470
|
+
}));
|
|
1471
|
+
}
|
|
1472
|
+
function cell(value) {
|
|
1473
|
+
if (typeof value === "string") {
|
|
1474
|
+
return { text: value };
|
|
1475
|
+
}
|
|
1476
|
+
return {
|
|
1477
|
+
text: value.text,
|
|
1478
|
+
...value.style === void 0 && value.fill === void 0 && value.stroke === void 0 ? {} : {
|
|
1479
|
+
style: style({
|
|
1480
|
+
...value.style,
|
|
1481
|
+
...value.fill === void 0 ? {} : { fill: value.fill },
|
|
1482
|
+
...value.stroke === void 0 ? {} : { stroke: value.stroke }
|
|
1483
|
+
})
|
|
1484
|
+
}
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
function tableColumn(value) {
|
|
1488
|
+
return {
|
|
1489
|
+
id: value.id,
|
|
1490
|
+
label: toLabel(value.label) ?? { text: value.id }
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
function tableRow(value) {
|
|
1494
|
+
return {
|
|
1495
|
+
id: value.id,
|
|
1496
|
+
cells: Object.fromEntries(
|
|
1497
|
+
Object.keys(value.cells).map((columnId) => [
|
|
1498
|
+
columnId,
|
|
1499
|
+
cell(value.cells[columnId] ?? "")
|
|
1500
|
+
])
|
|
1501
|
+
)
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
function panelItem(value) {
|
|
1505
|
+
if (typeof value === "string") {
|
|
1506
|
+
return { label: { text: value } };
|
|
1507
|
+
}
|
|
1508
|
+
const detail = toLabel(value.detail);
|
|
1509
|
+
return {
|
|
1510
|
+
...value.id === void 0 ? {} : { id: value.id },
|
|
1511
|
+
label: toLabel(value.label) ?? { text: "" },
|
|
1512
|
+
...detail === void 0 ? {} : { detail },
|
|
1513
|
+
...value.style === void 0 ? {} : { style: style(value.style) }
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1407
1516
|
function normalizeGroups(dsl, measurer) {
|
|
1408
1517
|
return Object.keys(dsl.groups ?? {}).sort().map((id) => {
|
|
1409
1518
|
const group = dsl.groups?.[id];
|
|
@@ -1695,6 +1804,15 @@ var styleSchema = z.object({
|
|
|
1695
1804
|
fill: z.string().optional(),
|
|
1696
1805
|
stroke: z.string().optional()
|
|
1697
1806
|
});
|
|
1807
|
+
var blockCellSchema = z.union([
|
|
1808
|
+
z.string(),
|
|
1809
|
+
z.object({
|
|
1810
|
+
text: z.string(),
|
|
1811
|
+
fill: z.string().optional(),
|
|
1812
|
+
stroke: z.string().optional(),
|
|
1813
|
+
style: styleSchema.optional()
|
|
1814
|
+
})
|
|
1815
|
+
]);
|
|
1698
1816
|
var portSideSchema = z.enum(["top", "right", "bottom", "left"]);
|
|
1699
1817
|
var portKindSchema = z.enum(["proxy", "flow"]);
|
|
1700
1818
|
var portSchema = z.object({
|
|
@@ -1771,6 +1889,122 @@ var swimlaneSchema = z.object({
|
|
|
1771
1889
|
})
|
|
1772
1890
|
)
|
|
1773
1891
|
});
|
|
1892
|
+
var matrixSchema = z.object({
|
|
1893
|
+
id: z.string(),
|
|
1894
|
+
rows: z.array(z.string()),
|
|
1895
|
+
cols: z.array(z.string()),
|
|
1896
|
+
cells: z.array(z.array(blockCellSchema)),
|
|
1897
|
+
position: pointSchema.optional(),
|
|
1898
|
+
size: z.object({
|
|
1899
|
+
width: nonNegativeNumberSchema,
|
|
1900
|
+
height: nonNegativeNumberSchema
|
|
1901
|
+
}).optional(),
|
|
1902
|
+
style: styleSchema.optional()
|
|
1903
|
+
}).superRefine((matrix, context) => {
|
|
1904
|
+
checkDuplicateValues("matrix row", matrix.rows, context, (index) => [
|
|
1905
|
+
"rows",
|
|
1906
|
+
index
|
|
1907
|
+
]);
|
|
1908
|
+
checkDuplicateValues("matrix column", matrix.cols, context, (index) => [
|
|
1909
|
+
"cols",
|
|
1910
|
+
index
|
|
1911
|
+
]);
|
|
1912
|
+
if (matrix.cells.length !== matrix.rows.length) {
|
|
1913
|
+
context.addIssue({
|
|
1914
|
+
code: "custom",
|
|
1915
|
+
message: `Matrix cells must contain exactly ${matrix.rows.length} row(s).`,
|
|
1916
|
+
path: ["cells"]
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
matrix.cells.forEach((row, rowIndex) => {
|
|
1920
|
+
if (row.length !== matrix.cols.length) {
|
|
1921
|
+
context.addIssue({
|
|
1922
|
+
code: "custom",
|
|
1923
|
+
message: `Matrix cell row must contain exactly ${matrix.cols.length} column(s).`,
|
|
1924
|
+
path: ["cells", rowIndex]
|
|
1925
|
+
});
|
|
1926
|
+
}
|
|
1927
|
+
});
|
|
1928
|
+
});
|
|
1929
|
+
var tableColumnSchema = z.object({
|
|
1930
|
+
id: z.string(),
|
|
1931
|
+
label: labelSchema
|
|
1932
|
+
});
|
|
1933
|
+
var tableRowSchema = z.object({
|
|
1934
|
+
id: z.string(),
|
|
1935
|
+
cells: z.record(z.string(), blockCellSchema)
|
|
1936
|
+
});
|
|
1937
|
+
var tableSchema = z.object({
|
|
1938
|
+
id: z.string(),
|
|
1939
|
+
columns: z.array(tableColumnSchema),
|
|
1940
|
+
rows: z.array(tableRowSchema),
|
|
1941
|
+
position: pointSchema.optional(),
|
|
1942
|
+
size: z.object({
|
|
1943
|
+
width: nonNegativeNumberSchema,
|
|
1944
|
+
height: nonNegativeNumberSchema
|
|
1945
|
+
}).optional(),
|
|
1946
|
+
style: styleSchema.optional()
|
|
1947
|
+
}).superRefine((table, context) => {
|
|
1948
|
+
checkDuplicateIds("column", table.columns, context, (index) => [
|
|
1949
|
+
"columns",
|
|
1950
|
+
index,
|
|
1951
|
+
"id"
|
|
1952
|
+
]);
|
|
1953
|
+
checkDuplicateIds("row", table.rows, context, (index) => [
|
|
1954
|
+
"rows",
|
|
1955
|
+
index,
|
|
1956
|
+
"id"
|
|
1957
|
+
]);
|
|
1958
|
+
const columnIds = new Set(table.columns.map((column) => column.id));
|
|
1959
|
+
table.rows.forEach((row, rowIndex) => {
|
|
1960
|
+
for (const columnId of Object.keys(row.cells)) {
|
|
1961
|
+
if (!columnIds.has(columnId)) {
|
|
1962
|
+
context.addIssue({
|
|
1963
|
+
code: "custom",
|
|
1964
|
+
message: `Table row cell references undeclared column "${columnId}".`,
|
|
1965
|
+
path: ["rows", rowIndex, "cells", columnId]
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
});
|
|
1970
|
+
});
|
|
1971
|
+
var panelItemSchema = z.union([
|
|
1972
|
+
z.string(),
|
|
1973
|
+
z.object({
|
|
1974
|
+
id: z.string().optional(),
|
|
1975
|
+
label: labelSchema,
|
|
1976
|
+
detail: labelSchema.optional(),
|
|
1977
|
+
style: styleSchema.optional()
|
|
1978
|
+
})
|
|
1979
|
+
]);
|
|
1980
|
+
var evidencePanelSchema = z.object({
|
|
1981
|
+
id: z.string(),
|
|
1982
|
+
kind: z.enum(["legend", "rule", "note", "verification"]),
|
|
1983
|
+
items: z.array(panelItemSchema),
|
|
1984
|
+
position: pointSchema.optional(),
|
|
1985
|
+
size: z.object({
|
|
1986
|
+
width: nonNegativeNumberSchema,
|
|
1987
|
+
height: nonNegativeNumberSchema
|
|
1988
|
+
}).optional(),
|
|
1989
|
+
style: styleSchema.optional()
|
|
1990
|
+
}).superRefine((panel, context) => {
|
|
1991
|
+
const firstIndexByItemId = /* @__PURE__ */ new Map();
|
|
1992
|
+
panel.items.forEach((item, index) => {
|
|
1993
|
+
if (typeof item === "string" || item.id === void 0) {
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1996
|
+
const firstIndex = firstIndexByItemId.get(item.id);
|
|
1997
|
+
if (firstIndex === void 0) {
|
|
1998
|
+
firstIndexByItemId.set(item.id, index);
|
|
1999
|
+
return;
|
|
2000
|
+
}
|
|
2001
|
+
context.addIssue({
|
|
2002
|
+
code: "custom",
|
|
2003
|
+
message: `Duplicate evidence panel item id "${item.id}".`,
|
|
2004
|
+
path: ["items", index, "id"]
|
|
2005
|
+
});
|
|
2006
|
+
});
|
|
2007
|
+
});
|
|
1774
2008
|
var exactPositionConstraintSchema = z.object({
|
|
1775
2009
|
kind: z.literal("exact-position"),
|
|
1776
2010
|
target: z.string().optional(),
|
|
@@ -1842,6 +2076,9 @@ var diagramDslSchema = z.object({
|
|
|
1842
2076
|
edges: z.array(edgeSchema).optional(),
|
|
1843
2077
|
groups: z.record(z.string(), groupSchema).optional(),
|
|
1844
2078
|
swimlanes: z.record(z.string(), swimlaneSchema).optional(),
|
|
2079
|
+
matrices: z.array(matrixSchema).optional(),
|
|
2080
|
+
tables: z.array(tableSchema).optional(),
|
|
2081
|
+
evidencePanels: z.array(evidencePanelSchema).optional(),
|
|
1845
2082
|
constraints: z.array(constraintSchema).optional(),
|
|
1846
2083
|
frame: z.object({
|
|
1847
2084
|
kind: z.string(),
|
|
@@ -1853,6 +2090,15 @@ var diagramDslSchema = z.object({
|
|
|
1853
2090
|
output: z.object({
|
|
1854
2091
|
format: outputFormatSchema.optional()
|
|
1855
2092
|
}).optional()
|
|
2093
|
+
}).superRefine((diagram, context) => {
|
|
2094
|
+
checkDuplicateEvidenceBlockIds("matrices", diagram.matrices, context);
|
|
2095
|
+
checkDuplicateEvidenceBlockIds("tables", diagram.tables, context);
|
|
2096
|
+
checkDuplicateEvidenceBlockIds(
|
|
2097
|
+
"evidencePanels",
|
|
2098
|
+
diagram.evidencePanels,
|
|
2099
|
+
context
|
|
2100
|
+
);
|
|
2101
|
+
checkDuplicateEvidenceBlockIdsAcrossTypes(diagram, context);
|
|
1856
2102
|
});
|
|
1857
2103
|
function validateDiagramDsl(value) {
|
|
1858
2104
|
const result = diagramDslSchema.safeParse(value);
|
|
@@ -1872,6 +2118,62 @@ function toDiagnosticPath(path) {
|
|
|
1872
2118
|
(segment) => typeof segment === "string" || typeof segment === "number" ? [segment] : []
|
|
1873
2119
|
);
|
|
1874
2120
|
}
|
|
2121
|
+
function checkDuplicateEvidenceBlockIds(collection, blocks, context) {
|
|
2122
|
+
checkDuplicateIds(
|
|
2123
|
+
`evidence block id in ${collection}`,
|
|
2124
|
+
blocks ?? [],
|
|
2125
|
+
context,
|
|
2126
|
+
(index) => [collection, index, "id"]
|
|
2127
|
+
);
|
|
2128
|
+
}
|
|
2129
|
+
function checkDuplicateIds(label, items, context, pathForIndex) {
|
|
2130
|
+
const firstIndexById = /* @__PURE__ */ new Map();
|
|
2131
|
+
items.forEach((item, index) => {
|
|
2132
|
+
const firstIndex = firstIndexById.get(item.id);
|
|
2133
|
+
if (firstIndex === void 0) {
|
|
2134
|
+
firstIndexById.set(item.id, index);
|
|
2135
|
+
return;
|
|
2136
|
+
}
|
|
2137
|
+
context.addIssue({
|
|
2138
|
+
code: "custom",
|
|
2139
|
+
message: `Duplicate ${label} "${item.id}".`,
|
|
2140
|
+
path: pathForIndex(index)
|
|
2141
|
+
});
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
function checkDuplicateValues(label, values, context, pathForIndex) {
|
|
2145
|
+
const firstIndexByValue = /* @__PURE__ */ new Map();
|
|
2146
|
+
values.forEach((value, index) => {
|
|
2147
|
+
const firstIndex = firstIndexByValue.get(value);
|
|
2148
|
+
if (firstIndex === void 0) {
|
|
2149
|
+
firstIndexByValue.set(value, index);
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
context.addIssue({
|
|
2153
|
+
code: "custom",
|
|
2154
|
+
message: `Duplicate ${label} "${value}".`,
|
|
2155
|
+
path: pathForIndex(index)
|
|
2156
|
+
});
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
2159
|
+
function checkDuplicateEvidenceBlockIdsAcrossTypes(diagram, context) {
|
|
2160
|
+
const firstById = /* @__PURE__ */ new Map();
|
|
2161
|
+
for (const collection of ["matrices", "tables", "evidencePanels"]) {
|
|
2162
|
+
const blocks = diagram[collection] ?? [];
|
|
2163
|
+
blocks.forEach((block, index) => {
|
|
2164
|
+
const first = firstById.get(block.id);
|
|
2165
|
+
if (first === void 0) {
|
|
2166
|
+
firstById.set(block.id, { collection, index });
|
|
2167
|
+
return;
|
|
2168
|
+
}
|
|
2169
|
+
context.addIssue({
|
|
2170
|
+
code: "custom",
|
|
2171
|
+
message: `Duplicate evidence block id "${block.id}" across ${first.collection} and ${collection}.`,
|
|
2172
|
+
path: [collection, index, "id"]
|
|
2173
|
+
});
|
|
2174
|
+
});
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
1875
2177
|
|
|
1876
2178
|
// src/dsl/parse.ts
|
|
1877
2179
|
var DEFAULT_DSL_MAX_BYTES = 1e6;
|
|
@@ -2073,6 +2375,15 @@ function exportExcalidraw(diagram, options = {}) {
|
|
|
2073
2375
|
elements.push(text);
|
|
2074
2376
|
}
|
|
2075
2377
|
}
|
|
2378
|
+
for (const matrix of diagram.matrices ?? []) {
|
|
2379
|
+
elements.push(...renderMatrixBlock(matrix));
|
|
2380
|
+
}
|
|
2381
|
+
for (const table of diagram.tables ?? []) {
|
|
2382
|
+
elements.push(...renderTableBlock(table));
|
|
2383
|
+
}
|
|
2384
|
+
for (const panel of diagram.evidencePanels ?? []) {
|
|
2385
|
+
elements.push(...renderEvidencePanel(panel));
|
|
2386
|
+
}
|
|
2076
2387
|
for (const edge of diagram.edges) {
|
|
2077
2388
|
elements.push(renderArrow(edge));
|
|
2078
2389
|
}
|
|
@@ -2105,6 +2416,83 @@ function renderNode(node, groupIds) {
|
|
|
2105
2416
|
groupIds
|
|
2106
2417
|
};
|
|
2107
2418
|
}
|
|
2419
|
+
function renderMatrixBlock(matrix) {
|
|
2420
|
+
const containerId = `matrix:${matrix.id}`;
|
|
2421
|
+
const groupIds = [containerId];
|
|
2422
|
+
const label = blockText([
|
|
2423
|
+
matrix.id,
|
|
2424
|
+
`row | ${matrix.cols.join(" | ")}`,
|
|
2425
|
+
...matrix.rows.map((rowId, rowIndex) => {
|
|
2426
|
+
const row = matrix.cells[rowIndex] ?? [];
|
|
2427
|
+
return `${rowId}: ${matrix.cols.map((_, columnIndex) => row[columnIndex]?.text ?? "").join(" | ")}`;
|
|
2428
|
+
})
|
|
2429
|
+
]);
|
|
2430
|
+
return [
|
|
2431
|
+
{
|
|
2432
|
+
...baseElement(containerId, "rectangle", matrix.box),
|
|
2433
|
+
backgroundColor: matrix.style?.fill ?? "#f8fafc",
|
|
2434
|
+
strokeColor: matrix.style?.stroke ?? "#374151",
|
|
2435
|
+
groupIds
|
|
2436
|
+
},
|
|
2437
|
+
renderTextBlock(
|
|
2438
|
+
`matrix-text:${matrix.id}`,
|
|
2439
|
+
label,
|
|
2440
|
+
matrix.box,
|
|
2441
|
+
containerId,
|
|
2442
|
+
groupIds
|
|
2443
|
+
)
|
|
2444
|
+
];
|
|
2445
|
+
}
|
|
2446
|
+
function renderTableBlock(table) {
|
|
2447
|
+
const containerId = `table:${table.id}`;
|
|
2448
|
+
const groupIds = [containerId];
|
|
2449
|
+
const label = blockText([
|
|
2450
|
+
table.columns.map((column) => column.label.text).join(" | "),
|
|
2451
|
+
...table.rows.map(
|
|
2452
|
+
(row) => table.columns.map((column) => row.cells[column.id]?.text ?? "").join(" | ")
|
|
2453
|
+
)
|
|
2454
|
+
]);
|
|
2455
|
+
return [
|
|
2456
|
+
{
|
|
2457
|
+
...baseElement(containerId, "rectangle", table.box),
|
|
2458
|
+
backgroundColor: table.style?.fill ?? "#f8fafc",
|
|
2459
|
+
strokeColor: table.style?.stroke ?? "#374151",
|
|
2460
|
+
groupIds
|
|
2461
|
+
},
|
|
2462
|
+
renderTextBlock(
|
|
2463
|
+
`table-text:${table.id}`,
|
|
2464
|
+
label,
|
|
2465
|
+
table.box,
|
|
2466
|
+
containerId,
|
|
2467
|
+
groupIds
|
|
2468
|
+
)
|
|
2469
|
+
];
|
|
2470
|
+
}
|
|
2471
|
+
function renderEvidencePanel(panel) {
|
|
2472
|
+
const containerId = `evidence-panel:${panel.id}`;
|
|
2473
|
+
const groupIds = [containerId];
|
|
2474
|
+
const label = blockText([
|
|
2475
|
+
`${panel.kind}: ${panel.id}`,
|
|
2476
|
+
...panel.items.map(
|
|
2477
|
+
(item) => item.detail?.text === void 0 ? item.label.text : `${item.label.text}: ${item.detail.text}`
|
|
2478
|
+
)
|
|
2479
|
+
]);
|
|
2480
|
+
return [
|
|
2481
|
+
{
|
|
2482
|
+
...baseElement(containerId, "rectangle", panel.box),
|
|
2483
|
+
backgroundColor: panel.style?.fill ?? panelKindFill(panel.kind),
|
|
2484
|
+
strokeColor: panel.style?.stroke ?? "#374151",
|
|
2485
|
+
groupIds
|
|
2486
|
+
},
|
|
2487
|
+
renderTextBlock(
|
|
2488
|
+
`evidence-panel-text:${panel.id}`,
|
|
2489
|
+
label,
|
|
2490
|
+
panel.box,
|
|
2491
|
+
containerId,
|
|
2492
|
+
groupIds
|
|
2493
|
+
)
|
|
2494
|
+
];
|
|
2495
|
+
}
|
|
2108
2496
|
function renderArrow(edge) {
|
|
2109
2497
|
const first = edge.points[0];
|
|
2110
2498
|
if (first === void 0) {
|
|
@@ -2164,6 +2552,34 @@ function renderText(id, label, box, containerId, groupIds) {
|
|
|
2164
2552
|
versionNonce: seedFor(`${id}:nonce`)
|
|
2165
2553
|
};
|
|
2166
2554
|
}
|
|
2555
|
+
function renderTextBlock(id, text, box, containerId, groupIds) {
|
|
2556
|
+
const fontSize = 12;
|
|
2557
|
+
return {
|
|
2558
|
+
...baseElement(id, "text", {
|
|
2559
|
+
x: box.x + 8,
|
|
2560
|
+
y: box.y + 8,
|
|
2561
|
+
width: Math.max(0, box.width - 16),
|
|
2562
|
+
height: Math.max(fontSize, box.height - 16)
|
|
2563
|
+
}),
|
|
2564
|
+
backgroundColor: "transparent",
|
|
2565
|
+
strokeColor: "#111827",
|
|
2566
|
+
groupIds,
|
|
2567
|
+
text,
|
|
2568
|
+
fontSize,
|
|
2569
|
+
fontFamily: 1,
|
|
2570
|
+
textAlign: "left",
|
|
2571
|
+
verticalAlign: "top",
|
|
2572
|
+
baseline: fontSize,
|
|
2573
|
+
containerId,
|
|
2574
|
+
originalText: text,
|
|
2575
|
+
lineHeight: 1.25,
|
|
2576
|
+
boundElements: null,
|
|
2577
|
+
link: null,
|
|
2578
|
+
locked: false,
|
|
2579
|
+
seed: seedFor(id),
|
|
2580
|
+
versionNonce: seedFor(`${id}:nonce`)
|
|
2581
|
+
};
|
|
2582
|
+
}
|
|
2167
2583
|
function baseElement(id, type, box) {
|
|
2168
2584
|
return {
|
|
2169
2585
|
id,
|
|
@@ -2241,6 +2657,21 @@ function groupGroupIds(groupId) {
|
|
|
2241
2657
|
function groupElementIdFor(groupId) {
|
|
2242
2658
|
return `group:${groupId}`;
|
|
2243
2659
|
}
|
|
2660
|
+
function blockText(lines) {
|
|
2661
|
+
return lines.filter((line) => line.length > 0).join("\n");
|
|
2662
|
+
}
|
|
2663
|
+
function panelKindFill(kind) {
|
|
2664
|
+
switch (kind) {
|
|
2665
|
+
case "legend":
|
|
2666
|
+
return "#ecfdf5";
|
|
2667
|
+
case "rule":
|
|
2668
|
+
return "#eff6ff";
|
|
2669
|
+
case "note":
|
|
2670
|
+
return "#fffbeb";
|
|
2671
|
+
case "verification":
|
|
2672
|
+
return "#fef2f2";
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2244
2675
|
function pointsBox(points) {
|
|
2245
2676
|
const xs = points.map((point2) => point2.x);
|
|
2246
2677
|
const ys = points.map((point2) => point2.y);
|
|
@@ -2278,42 +2709,253 @@ var GROUP_FILL = "#f9fafb";
|
|
|
2278
2709
|
var STROKE = "#374151";
|
|
2279
2710
|
var EDGE_STROKE = "#111827";
|
|
2280
2711
|
var FONT_FAMILY = "Arial, sans-serif";
|
|
2712
|
+
var EVIDENCE_FILL = "#f8fafc";
|
|
2713
|
+
var EVIDENCE_HEADER_FILL = "#e5e7eb";
|
|
2714
|
+
var EVIDENCE_TEXT_FONT_SIZE = 10;
|
|
2715
|
+
var EVIDENCE_TEXT_LINE_HEIGHT = 12;
|
|
2716
|
+
var EVIDENCE_PANEL_KIND_FILL = {
|
|
2717
|
+
legend: "#ecfdf5",
|
|
2718
|
+
rule: "#eff6ff",
|
|
2719
|
+
note: "#fffbeb",
|
|
2720
|
+
verification: "#fef2f2"
|
|
2721
|
+
};
|
|
2281
2722
|
function exportSvg(diagram, options = {}) {
|
|
2282
2723
|
const title = options.title ?? diagram.title;
|
|
2283
|
-
const
|
|
2724
|
+
const annotations = diagram.textAnnotations ?? [];
|
|
2725
|
+
return `${[
|
|
2284
2726
|
`<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="${formatBoxViewBox(diagram.bounds)}">`,
|
|
2285
2727
|
...title === void 0 ? [] : [` <title>${escapeXml(title)}</title>`],
|
|
2286
2728
|
` <rect class="background" x="${formatNumber(diagram.bounds.x)}" y="${formatNumber(diagram.bounds.y)}" width="${formatNumber(diagram.bounds.width)}" height="${formatNumber(diagram.bounds.height)}" fill="#ffffff"/>`,
|
|
2287
|
-
...diagram.frame === void 0 ? [] : [indent(renderFrame(diagram.frame))],
|
|
2729
|
+
...diagram.frame === void 0 ? [] : [indent(renderFrame(diagram.frame, annotations))],
|
|
2288
2730
|
...(diagram.swimlanes ?? []).flatMap(
|
|
2289
|
-
(swimlane) => renderSwimlane(swimlane)
|
|
2731
|
+
(swimlane) => renderSwimlane(swimlane, annotations)
|
|
2290
2732
|
),
|
|
2291
2733
|
...diagram.groups.map((group) => indent(renderGroup2(group))),
|
|
2734
|
+
...(diagram.matrices ?? []).flatMap(
|
|
2735
|
+
(matrix) => indentLines(renderMatrixBlock2(matrix))
|
|
2736
|
+
),
|
|
2737
|
+
...(diagram.tables ?? []).flatMap(
|
|
2738
|
+
(table) => indentLines(renderTableBlock2(table))
|
|
2739
|
+
),
|
|
2740
|
+
...(diagram.evidencePanels ?? []).flatMap(
|
|
2741
|
+
(panel) => indentLines(renderEvidencePanel2(panel))
|
|
2742
|
+
),
|
|
2292
2743
|
...diagram.edges.flatMap((edge) => {
|
|
2293
2744
|
const path = renderEdgePath(edge);
|
|
2294
|
-
|
|
2295
|
-
return [];
|
|
2296
|
-
}
|
|
2297
|
-
return [indent(path), indent(renderArrowhead(edge))];
|
|
2745
|
+
return path === void 0 ? [] : [indent(path), indent(renderArrowhead(edge))];
|
|
2298
2746
|
}),
|
|
2299
2747
|
...diagram.nodes.map((node) => indent(renderNode2(node))),
|
|
2300
|
-
...diagram.nodes.flatMap((node) => renderCompartments(node)),
|
|
2301
|
-
...diagram.nodes.flatMap((node) => renderPorts(node)),
|
|
2748
|
+
...diagram.nodes.flatMap((node) => renderCompartments(node, annotations)),
|
|
2749
|
+
...diagram.nodes.flatMap((node) => renderPorts(node, annotations)),
|
|
2302
2750
|
...diagram.groups.flatMap(
|
|
2303
|
-
(group) => renderLabel(group.label, group.box, group)
|
|
2751
|
+
(group) => renderLabel(group.label, group.box, group, annotations, "group-label")
|
|
2304
2752
|
),
|
|
2305
2753
|
...diagram.nodes.flatMap(
|
|
2306
|
-
(node) => node.compartments === void 0 ? renderLabel(node.label, node.box, node) : []
|
|
2754
|
+
(node) => node.compartments === void 0 ? renderLabel(node.label, node.box, node, annotations, "node-label") : []
|
|
2307
2755
|
),
|
|
2308
|
-
...diagram.edges.flatMap((edge) => renderEdgeLabel(edge)),
|
|
2756
|
+
...diagram.edges.flatMap((edge) => renderEdgeLabel(edge, annotations)),
|
|
2309
2757
|
"</svg>"
|
|
2310
|
-
]
|
|
2311
|
-
return `${lines.join("\n")}
|
|
2758
|
+
].join("\n")}
|
|
2312
2759
|
`;
|
|
2313
2760
|
}
|
|
2314
2761
|
function renderGroup2(group) {
|
|
2315
2762
|
return `<rect class="group" data-id="${escapeAttribute(group.id)}" x="${formatNumber(group.box.x)}" y="${formatNumber(group.box.y)}" width="${formatNumber(group.box.width)}" height="${formatNumber(group.box.height)}" fill="${GROUP_FILL}" stroke="${STROKE}" stroke-dasharray="6 4"/>`;
|
|
2316
2763
|
}
|
|
2764
|
+
function renderMatrixBlock2(matrix) {
|
|
2765
|
+
const columnCount = Math.max(1, matrix.cols.length);
|
|
2766
|
+
const rowCount = matrix.rows.length;
|
|
2767
|
+
const rowHeaderWidth = rowCount > 0 ? Math.min(96, matrix.box.width * 0.28) : 0;
|
|
2768
|
+
const dataWidth = Math.max(0, matrix.box.width - rowHeaderWidth);
|
|
2769
|
+
const cellWidth = dataWidth / columnCount;
|
|
2770
|
+
const rowHeight = matrix.box.height / Math.max(1, rowCount + 1);
|
|
2771
|
+
const lines = [
|
|
2772
|
+
`<g class="matrix-block" data-id="${escapeAttribute(matrix.id)}" data-row-count="${rowCount}" data-column-count="${matrix.cols.length}">`,
|
|
2773
|
+
` <rect class="matrix-frame" x="${formatNumber(matrix.box.x)}" y="${formatNumber(matrix.box.y)}" width="${formatNumber(matrix.box.width)}" height="${formatNumber(matrix.box.height)}" fill="${escapeAttribute(matrix.style?.fill ?? EVIDENCE_FILL)}" stroke="${escapeAttribute(matrix.style?.stroke ?? STROKE)}"/>`
|
|
2774
|
+
];
|
|
2775
|
+
if (rowHeaderWidth > 0) {
|
|
2776
|
+
lines.push(
|
|
2777
|
+
` <rect class="matrix-corner-header" x="${formatNumber(matrix.box.x)}" y="${formatNumber(matrix.box.y)}" width="${formatNumber(rowHeaderWidth)}" height="${formatNumber(rowHeight)}" fill="${EVIDENCE_HEADER_FILL}" stroke="${STROKE}"/>`
|
|
2778
|
+
);
|
|
2779
|
+
}
|
|
2780
|
+
for (let columnIndex = 0; columnIndex < matrix.cols.length; columnIndex += 1) {
|
|
2781
|
+
const column = matrix.cols[columnIndex];
|
|
2782
|
+
if (column === void 0) {
|
|
2783
|
+
continue;
|
|
2784
|
+
}
|
|
2785
|
+
const x = matrix.box.x + rowHeaderWidth + columnIndex * cellWidth;
|
|
2786
|
+
lines.push(
|
|
2787
|
+
` <rect class="matrix-column-header" data-col="${escapeAttribute(column)}" x="${formatNumber(x)}" y="${formatNumber(matrix.box.y)}" width="${formatNumber(cellWidth)}" height="${formatNumber(rowHeight)}" fill="${EVIDENCE_HEADER_FILL}" stroke="${STROKE}"/>`,
|
|
2788
|
+
renderEvidenceText(
|
|
2789
|
+
"matrix-column-label",
|
|
2790
|
+
matrix.columnLabelLayouts?.[columnIndex]?.lines ?? [column],
|
|
2791
|
+
{
|
|
2792
|
+
x,
|
|
2793
|
+
y: matrix.box.y,
|
|
2794
|
+
width: cellWidth,
|
|
2795
|
+
height: rowHeight
|
|
2796
|
+
}
|
|
2797
|
+
)
|
|
2798
|
+
);
|
|
2799
|
+
}
|
|
2800
|
+
for (let rowIndex = 0; rowIndex < matrix.rows.length; rowIndex += 1) {
|
|
2801
|
+
const row = matrix.rows[rowIndex];
|
|
2802
|
+
const cells = matrix.cells[rowIndex] ?? [];
|
|
2803
|
+
if (row === void 0) {
|
|
2804
|
+
continue;
|
|
2805
|
+
}
|
|
2806
|
+
if (rowHeaderWidth > 0) {
|
|
2807
|
+
const rowHeaderBox = {
|
|
2808
|
+
x: matrix.box.x,
|
|
2809
|
+
y: matrix.box.y + (rowIndex + 1) * rowHeight,
|
|
2810
|
+
width: rowHeaderWidth,
|
|
2811
|
+
height: rowHeight
|
|
2812
|
+
};
|
|
2813
|
+
lines.push(
|
|
2814
|
+
` <rect class="matrix-row-header" data-row="${escapeAttribute(row)}" x="${formatNumber(rowHeaderBox.x)}" y="${formatNumber(rowHeaderBox.y)}" width="${formatNumber(rowHeaderBox.width)}" height="${formatNumber(rowHeaderBox.height)}" fill="${EVIDENCE_HEADER_FILL}" stroke="${STROKE}"/>`,
|
|
2815
|
+
renderEvidenceText(
|
|
2816
|
+
"matrix-row-label",
|
|
2817
|
+
matrix.rowLabelLayouts?.[rowIndex]?.lines ?? [row],
|
|
2818
|
+
rowHeaderBox
|
|
2819
|
+
)
|
|
2820
|
+
);
|
|
2821
|
+
}
|
|
2822
|
+
for (let columnIndex = 0; columnIndex < matrix.cols.length; columnIndex += 1) {
|
|
2823
|
+
const column = matrix.cols[columnIndex];
|
|
2824
|
+
if (column === void 0) {
|
|
2825
|
+
continue;
|
|
2826
|
+
}
|
|
2827
|
+
const cell2 = cells[columnIndex] ?? { text: "" };
|
|
2828
|
+
const box = {
|
|
2829
|
+
x: matrix.box.x + rowHeaderWidth + columnIndex * cellWidth,
|
|
2830
|
+
y: matrix.box.y + (rowIndex + 1) * rowHeight,
|
|
2831
|
+
width: cellWidth,
|
|
2832
|
+
height: rowHeight
|
|
2833
|
+
};
|
|
2834
|
+
lines.push(
|
|
2835
|
+
` <rect class="matrix-cell" data-row="${escapeAttribute(row)}" data-col="${escapeAttribute(column)}" x="${formatNumber(box.x)}" y="${formatNumber(box.y)}" width="${formatNumber(box.width)}" height="${formatNumber(box.height)}" fill="${escapeAttribute(cell2.style?.fill ?? "#ffffff")}" stroke="${escapeAttribute(cell2.style?.stroke ?? STROKE)}"/>`,
|
|
2836
|
+
renderEvidenceText(
|
|
2837
|
+
"matrix-cell-label",
|
|
2838
|
+
matrix.cellLabelLayouts?.[rowIndex]?.[columnIndex]?.lines ?? [
|
|
2839
|
+
cell2.text
|
|
2840
|
+
],
|
|
2841
|
+
box
|
|
2842
|
+
)
|
|
2843
|
+
);
|
|
2844
|
+
}
|
|
2845
|
+
}
|
|
2846
|
+
lines.push("</g>");
|
|
2847
|
+
return lines;
|
|
2848
|
+
}
|
|
2849
|
+
function renderTableBlock2(table) {
|
|
2850
|
+
const columnCount = Math.max(1, table.columns.length);
|
|
2851
|
+
const rowHeight = table.box.height / Math.max(1, table.rows.length + 1);
|
|
2852
|
+
const lines = [
|
|
2853
|
+
`<g class="table-block" data-id="${escapeAttribute(table.id)}" data-row-count="${table.rows.length}" data-column-count="${table.columns.length}">`,
|
|
2854
|
+
` <rect class="table-frame" x="${formatNumber(table.box.x)}" y="${formatNumber(table.box.y)}" width="${formatNumber(table.box.width)}" height="${formatNumber(table.box.height)}" fill="${escapeAttribute(table.style?.fill ?? EVIDENCE_FILL)}" stroke="${escapeAttribute(table.style?.stroke ?? STROKE)}"/>`,
|
|
2855
|
+
` <g class="table-header" data-column-count="${table.columns.length}">`
|
|
2856
|
+
];
|
|
2857
|
+
for (let columnIndex = 0; columnIndex < table.columns.length; columnIndex += 1) {
|
|
2858
|
+
const column = table.columns[columnIndex];
|
|
2859
|
+
if (column === void 0) {
|
|
2860
|
+
continue;
|
|
2861
|
+
}
|
|
2862
|
+
const columnBox = tableCellBox(
|
|
2863
|
+
table,
|
|
2864
|
+
columnIndex,
|
|
2865
|
+
0,
|
|
2866
|
+
rowHeight,
|
|
2867
|
+
columnCount
|
|
2868
|
+
);
|
|
2869
|
+
lines.push(
|
|
2870
|
+
` <rect class="table-header-cell" data-col="${escapeAttribute(column.id)}" x="${formatNumber(columnBox.x)}" y="${formatNumber(columnBox.y)}" width="${formatNumber(columnBox.width)}" height="${formatNumber(columnBox.height)}" fill="${EVIDENCE_HEADER_FILL}" stroke="${STROKE}"/>`,
|
|
2871
|
+
` ${renderEvidenceText("table-header-label", table.columnLabelLayouts?.[columnIndex]?.lines ?? [column.label.text], columnBox)}`
|
|
2872
|
+
);
|
|
2873
|
+
}
|
|
2874
|
+
lines.push(" </g>");
|
|
2875
|
+
for (let rowIndex = 0; rowIndex < table.rows.length; rowIndex += 1) {
|
|
2876
|
+
const row = table.rows[rowIndex];
|
|
2877
|
+
if (row === void 0) {
|
|
2878
|
+
continue;
|
|
2879
|
+
}
|
|
2880
|
+
const rowBox = {
|
|
2881
|
+
x: table.box.x,
|
|
2882
|
+
y: table.box.y + (rowIndex + 1) * rowHeight,
|
|
2883
|
+
width: table.box.width,
|
|
2884
|
+
height: rowHeight
|
|
2885
|
+
};
|
|
2886
|
+
const rowClass = rowIndex % 2 === 0 ? "table-row-even" : "table-row-odd";
|
|
2887
|
+
lines.push(
|
|
2888
|
+
` <g class="table-row ${rowClass}" data-row="${escapeAttribute(row.id)}">`,
|
|
2889
|
+
` <rect class="${rowClass}" data-row="${escapeAttribute(row.id)}" x="${formatNumber(rowBox.x)}" y="${formatNumber(rowBox.y)}" width="${formatNumber(rowBox.width)}" height="${formatNumber(rowBox.height)}" fill="${rowIndex % 2 === 0 ? "#ffffff" : "#f3f4f6"}" stroke="none"/>`
|
|
2890
|
+
);
|
|
2891
|
+
for (let columnIndex = 0; columnIndex < table.columns.length; columnIndex += 1) {
|
|
2892
|
+
const column = table.columns[columnIndex];
|
|
2893
|
+
if (column === void 0) {
|
|
2894
|
+
continue;
|
|
2895
|
+
}
|
|
2896
|
+
const cell2 = row.cells[column.id] ?? { text: "" };
|
|
2897
|
+
const cellBox = tableCellBox(
|
|
2898
|
+
table,
|
|
2899
|
+
columnIndex,
|
|
2900
|
+
rowIndex + 1,
|
|
2901
|
+
rowHeight,
|
|
2902
|
+
columnCount
|
|
2903
|
+
);
|
|
2904
|
+
lines.push(
|
|
2905
|
+
` <rect class="table-cell" data-col="${escapeAttribute(column.id)}" x="${formatNumber(cellBox.x)}" y="${formatNumber(cellBox.y)}" width="${formatNumber(cellBox.width)}" height="${formatNumber(cellBox.height)}" fill="${escapeAttribute(cell2.style?.fill ?? "transparent")}" stroke="${escapeAttribute(cell2.style?.stroke ?? STROKE)}"/>`,
|
|
2906
|
+
` ${renderEvidenceText("table-cell-label", table.cellLabelLayouts?.[rowIndex]?.[columnIndex]?.lines ?? [cell2.text], cellBox)}`
|
|
2907
|
+
);
|
|
2908
|
+
}
|
|
2909
|
+
lines.push(" </g>");
|
|
2910
|
+
}
|
|
2911
|
+
lines.push("</g>");
|
|
2912
|
+
return lines;
|
|
2913
|
+
}
|
|
2914
|
+
function renderEvidencePanel2(panel) {
|
|
2915
|
+
const titleWidth = Math.min(panel.box.width * 0.36, 140);
|
|
2916
|
+
const itemBox = {
|
|
2917
|
+
x: panel.box.x + titleWidth,
|
|
2918
|
+
y: panel.box.y,
|
|
2919
|
+
width: panel.box.width - titleWidth,
|
|
2920
|
+
height: panel.box.height
|
|
2921
|
+
};
|
|
2922
|
+
const titleBox = {
|
|
2923
|
+
x: panel.box.x,
|
|
2924
|
+
y: panel.box.y,
|
|
2925
|
+
width: titleWidth,
|
|
2926
|
+
height: panel.box.height
|
|
2927
|
+
};
|
|
2928
|
+
const itemHeight = panel.box.height / Math.max(1, panel.items.length);
|
|
2929
|
+
const lines = [
|
|
2930
|
+
`<g class="evidence-panel evidence-panel--${panel.kind}" data-id="${escapeAttribute(panel.id)}" data-kind="${escapeAttribute(panel.kind)}" data-item-count="${panel.items.length}">`,
|
|
2931
|
+
` <rect class="evidence-panel-frame" x="${formatNumber(panel.box.x)}" y="${formatNumber(panel.box.y)}" width="${formatNumber(panel.box.width)}" height="${formatNumber(panel.box.height)}" fill="${escapeAttribute(panel.style?.fill ?? EVIDENCE_PANEL_KIND_FILL[panel.kind])}" stroke="${escapeAttribute(panel.style?.stroke ?? STROKE)}"/>`,
|
|
2932
|
+
` <g class="evidence-panel-title-cell">`,
|
|
2933
|
+
` <rect class="evidence-panel-title-bg" x="${formatNumber(titleBox.x)}" y="${formatNumber(titleBox.y)}" width="${formatNumber(titleBox.width)}" height="${formatNumber(titleBox.height)}" fill="${EVIDENCE_HEADER_FILL}" stroke="${STROKE}"/>`,
|
|
2934
|
+
` ${renderEvidenceText("evidence-panel-title", panel.titleLayout?.lines ?? [`${panel.kind}: ${panel.id}`], titleBox)}`,
|
|
2935
|
+
" </g>",
|
|
2936
|
+
` <g class="evidence-panel-items-cell">`,
|
|
2937
|
+
` <rect class="evidence-panel-items-bg" x="${formatNumber(itemBox.x)}" y="${formatNumber(itemBox.y)}" width="${formatNumber(itemBox.width)}" height="${formatNumber(itemBox.height)}" fill="transparent" stroke="${STROKE}"/>`
|
|
2938
|
+
];
|
|
2939
|
+
for (let index = 0; index < panel.items.length; index += 1) {
|
|
2940
|
+
const item = panel.items[index];
|
|
2941
|
+
if (item === void 0) {
|
|
2942
|
+
continue;
|
|
2943
|
+
}
|
|
2944
|
+
const text = panelItemText(item.label.text, item.detail?.text);
|
|
2945
|
+
const box = {
|
|
2946
|
+
x: itemBox.x,
|
|
2947
|
+
y: itemBox.y + index * itemHeight,
|
|
2948
|
+
width: itemBox.width,
|
|
2949
|
+
height: itemHeight
|
|
2950
|
+
};
|
|
2951
|
+
lines.push(
|
|
2952
|
+
` <rect class="evidence-panel-item" data-item="${escapeAttribute(item.id ?? String(index))}" x="${formatNumber(box.x)}" y="${formatNumber(box.y)}" width="${formatNumber(box.width)}" height="${formatNumber(box.height)}" fill="${escapeAttribute(item.style?.fill ?? "transparent")}" stroke="${escapeAttribute(item.style?.stroke ?? "none")}"/>`,
|
|
2953
|
+
` ${renderEvidenceText("evidence-panel-item-label", panel.itemLayouts?.[index]?.lines ?? [text], box)}`
|
|
2954
|
+
);
|
|
2955
|
+
}
|
|
2956
|
+
lines.push(" </g>", "</g>");
|
|
2957
|
+
return lines;
|
|
2958
|
+
}
|
|
2317
2959
|
function renderNode2(node) {
|
|
2318
2960
|
const fill = node.style?.fill ?? NODE_FILL;
|
|
2319
2961
|
const stroke = node.style?.stroke ?? STROKE;
|
|
@@ -2333,18 +2975,27 @@ function renderNode2(node) {
|
|
|
2333
2975
|
return `<path ${common} d="${formatCylinderPath(node.box)}"/>`;
|
|
2334
2976
|
}
|
|
2335
2977
|
}
|
|
2336
|
-
function renderFrame(frame) {
|
|
2978
|
+
function renderFrame(frame, annotations) {
|
|
2337
2979
|
const stroke = frame.style?.stroke ?? "#6b7280";
|
|
2338
2980
|
const fill = frame.style?.fill ?? "transparent";
|
|
2339
2981
|
return [
|
|
2340
2982
|
`<g class="sysml-frame" data-kind="${escapeAttribute(frame.kind)}">`,
|
|
2341
2983
|
` <rect class="sysml-frame-border" x="${formatNumber(frame.box.x)}" y="${formatNumber(frame.box.y)}" width="${formatNumber(frame.box.width)}" height="${formatNumber(frame.box.height)}" fill="${escapeAttribute(fill)}" stroke="${escapeAttribute(stroke)}"/>`,
|
|
2342
2984
|
` <path class="sysml-title-tab" d="M ${formatNumber(frame.titleBox.x)} ${formatNumber(frame.titleBox.y + frame.titleBox.height)} L ${formatNumber(frame.titleBox.x)} ${formatNumber(frame.titleBox.y)} L ${formatNumber(frame.titleBox.x + frame.titleBox.width - 16)} ${formatNumber(frame.titleBox.y)} L ${formatNumber(frame.titleBox.x + frame.titleBox.width)} ${formatNumber(frame.titleBox.y + frame.titleBox.height)} Z" fill="#f3f4f6" stroke="${escapeAttribute(stroke)}"/>`,
|
|
2343
|
-
|
|
2985
|
+
...renderSolvedTextAnnotation(
|
|
2986
|
+
findAnnotation(annotations, "frame-title", frame.kind),
|
|
2987
|
+
`sysml-title-tab-label`,
|
|
2988
|
+
{
|
|
2989
|
+
indent: " ",
|
|
2990
|
+
mode: "center",
|
|
2991
|
+
fallbackText: frame.titleTab}
|
|
2992
|
+
) ?? [
|
|
2993
|
+
` <text class="sysml-title-tab-label" x="${formatNumber(frame.titleBox.x + 8)}" y="${formatNumber(frame.titleBox.y + frame.titleBox.height / 2)}" dominant-baseline="middle" font-family="${FONT_FAMILY}" font-size="12" fill="#111827">${escapeXml(frame.titleTab)}</text>`
|
|
2994
|
+
],
|
|
2344
2995
|
"</g>"
|
|
2345
2996
|
].join("\n");
|
|
2346
2997
|
}
|
|
2347
|
-
function renderSwimlane(swimlane) {
|
|
2998
|
+
function renderSwimlane(swimlane, annotations) {
|
|
2348
2999
|
if (swimlane.box === void 0) {
|
|
2349
3000
|
return [];
|
|
2350
3001
|
}
|
|
@@ -2370,28 +3021,49 @@ function renderSwimlane(swimlane) {
|
|
|
2370
3021
|
);
|
|
2371
3022
|
}
|
|
2372
3023
|
if (lane.label?.text !== void 0) {
|
|
2373
|
-
const
|
|
2374
|
-
|
|
3024
|
+
const annotation = findAnnotation(
|
|
3025
|
+
annotations,
|
|
3026
|
+
"swimlane-label",
|
|
3027
|
+
`${swimlane.id}.${lane.id}`
|
|
3028
|
+
);
|
|
3029
|
+
lines.push(
|
|
3030
|
+
...annotation === void 0 ? [
|
|
3031
|
+
renderSwimlaneLabel(
|
|
3032
|
+
swimlane,
|
|
3033
|
+
lane.label.text,
|
|
3034
|
+
lane.headerBox ?? lane.box
|
|
3035
|
+
)
|
|
3036
|
+
] : renderSolvedTextAnnotation(annotation, "swimlane-label", {
|
|
3037
|
+
indent: " ",
|
|
3038
|
+
mode: "center",
|
|
3039
|
+
rotate: swimlane.orientation === "horizontal"
|
|
3040
|
+
}) ?? []
|
|
3041
|
+
);
|
|
2375
3042
|
}
|
|
2376
3043
|
}
|
|
2377
3044
|
lines.push(" </g>");
|
|
2378
3045
|
return lines;
|
|
2379
3046
|
}
|
|
2380
|
-
function
|
|
2381
|
-
const x = labelBox.x + labelBox.width / 2;
|
|
2382
|
-
const y = labelBox.y + labelBox.height / 2;
|
|
2383
|
-
const transform = swimlane.orientation === "horizontal" ? ` transform="rotate(-90 ${formatNumber(x)} ${formatNumber(y)})"` : "";
|
|
2384
|
-
return ` <text class="swimlane-label" x="${formatNumber(x)}" y="${formatNumber(y)}" text-anchor="middle" dominant-baseline="middle"${transform} font-family="${FONT_FAMILY}" font-size="12" fill="#111827">${escapeXml(text)}</text>`;
|
|
2385
|
-
}
|
|
2386
|
-
function renderPorts(node) {
|
|
3047
|
+
function renderPorts(node, annotations) {
|
|
2387
3048
|
return (node.ports ?? []).flatMap((port) => [
|
|
2388
3049
|
` <rect class="port" data-kind="${escapeAttribute(port.kind)}" data-port="${escapeAttribute(`${node.id}.${port.id}`)}" x="${formatNumber(port.box.x)}" y="${formatNumber(port.box.y)}" width="${formatNumber(port.box.width)}" height="${formatNumber(port.box.height)}" fill="${escapeAttribute(port.style?.fill ?? "#d9ead3")}" stroke="${escapeAttribute(port.style?.stroke ?? STROKE)}"/>`,
|
|
2389
|
-
...port.label?.text === void 0 ? [] :
|
|
2390
|
-
|
|
2391
|
-
|
|
3050
|
+
...port.label?.text === void 0 ? [] : (() => {
|
|
3051
|
+
const annotation = findAnnotation(
|
|
3052
|
+
annotations,
|
|
3053
|
+
"port-label",
|
|
3054
|
+
`${node.id}.${port.id}`
|
|
3055
|
+
);
|
|
3056
|
+
return annotation === void 0 ? [
|
|
3057
|
+
` <text class="port-label" data-for="${escapeAttribute(`${node.id}.${port.id}`)}" x="${formatNumber(portLabelX(port.anchor.x, port.side))}" y="${formatNumber(port.anchor.y - 8)}" text-anchor="${port.side === "left" ? "end" : "start"}" font-family="${FONT_FAMILY}" font-size="10" fill="#111827">${escapeXml(port.label.text)}</text>`
|
|
3058
|
+
] : renderSolvedTextAnnotation(annotation, "port-label", {
|
|
3059
|
+
indent: " ",
|
|
3060
|
+
mode: "center",
|
|
3061
|
+
textAnchor: port.side === "left" ? "end" : "start"
|
|
3062
|
+
}) ?? [];
|
|
3063
|
+
})()
|
|
2392
3064
|
]);
|
|
2393
3065
|
}
|
|
2394
|
-
function renderCompartments(node) {
|
|
3066
|
+
function renderCompartments(node, annotations) {
|
|
2395
3067
|
const compartments2 = node.compartments;
|
|
2396
3068
|
if (compartments2 === void 0) {
|
|
2397
3069
|
return [];
|
|
@@ -2411,7 +3083,6 @@ function renderCompartments(node) {
|
|
|
2411
3083
|
text
|
|
2412
3084
|
}))
|
|
2413
3085
|
];
|
|
2414
|
-
const lineHeight = 16;
|
|
2415
3086
|
const lines = [
|
|
2416
3087
|
` <g class="compartment" data-for="${escapeAttribute(node.id)}">`
|
|
2417
3088
|
];
|
|
@@ -2420,32 +3091,72 @@ function renderCompartments(node) {
|
|
|
2420
3091
|
if (row === void 0) {
|
|
2421
3092
|
continue;
|
|
2422
3093
|
}
|
|
2423
|
-
const y = node.box.y + 18 + index *
|
|
3094
|
+
const y = node.box.y + 18 + index * 16;
|
|
2424
3095
|
if (index > 1) {
|
|
2425
3096
|
lines.push(
|
|
2426
3097
|
` <line class="compartment-separator" x1="${formatNumber(node.box.x)}" y1="${formatNumber(y - 12)}" x2="${formatNumber(node.box.x + node.box.width)}" y2="${formatNumber(y - 12)}" stroke="${STROKE}"/>`
|
|
2427
3098
|
);
|
|
2428
3099
|
}
|
|
3100
|
+
const annotation = findAnnotation(
|
|
3101
|
+
annotations,
|
|
3102
|
+
"compartment-row",
|
|
3103
|
+
node.id,
|
|
3104
|
+
index
|
|
3105
|
+
);
|
|
2429
3106
|
lines.push(
|
|
2430
|
-
|
|
3107
|
+
...annotation === void 0 ? [
|
|
3108
|
+
` <text class="compartment-${row.className}" x="${formatNumber(node.box.x + node.box.width / 2)}" y="${formatNumber(y)}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="11" fill="#111827">${escapeXml(row.text)}</text>`
|
|
3109
|
+
] : renderSolvedTextAnnotation(
|
|
3110
|
+
annotation,
|
|
3111
|
+
`compartment-${row.className}`,
|
|
3112
|
+
{
|
|
3113
|
+
indent: " ",
|
|
3114
|
+
mode: "center"
|
|
3115
|
+
}
|
|
3116
|
+
) ?? []
|
|
2431
3117
|
);
|
|
2432
3118
|
}
|
|
2433
3119
|
lines.push(" </g>");
|
|
2434
3120
|
return lines;
|
|
2435
3121
|
}
|
|
2436
|
-
function
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
3122
|
+
function tableCellBox(table, columnIndex, rowIndex, rowHeight, columnCount) {
|
|
3123
|
+
const x = table.columnXOffsets[columnIndex] ?? table.box.x + table.box.width / columnCount * columnIndex;
|
|
3124
|
+
const nextX = table.columnXOffsets[columnIndex + 1] ?? table.box.x + table.box.width;
|
|
3125
|
+
return {
|
|
3126
|
+
x,
|
|
3127
|
+
y: table.box.y + rowIndex * rowHeight,
|
|
3128
|
+
width: nextX - x,
|
|
3129
|
+
height: rowHeight
|
|
3130
|
+
};
|
|
3131
|
+
}
|
|
3132
|
+
function renderEvidenceText(className, lines, box) {
|
|
3133
|
+
const fontSize = EVIDENCE_TEXT_FONT_SIZE;
|
|
3134
|
+
const lineHeight = EVIDENCE_TEXT_LINE_HEIGHT;
|
|
3135
|
+
const x = box.x + box.width / 2;
|
|
3136
|
+
if (lines.length <= 1) {
|
|
3137
|
+
return `<text class="${className}" x="${formatNumber(x)}" y="${formatNumber(box.y + box.height / 2)}" text-anchor="middle" dominant-baseline="middle" font-family="${FONT_FAMILY}" font-size="${formatNumber(fontSize)}" fill="#111827">${escapeXml(lines[0] ?? "")}</text>`;
|
|
2442
3138
|
}
|
|
2443
|
-
|
|
3139
|
+
const totalHeight = (lines.length - 1) * lineHeight;
|
|
3140
|
+
const firstBaselineY = box.y + box.height / 2 - totalHeight / 2;
|
|
3141
|
+
return [
|
|
3142
|
+
`<text class="${className}" x="${formatNumber(x)}" y="${formatNumber(firstBaselineY)}" text-anchor="middle" dominant-baseline="middle" font-family="${FONT_FAMILY}" font-size="${formatNumber(fontSize)}" fill="#111827">`,
|
|
3143
|
+
...lines.map(
|
|
3144
|
+
(line, index) => ` <tspan x="${formatNumber(x)}" y="${formatNumber(firstBaselineY + index * lineHeight)}">${escapeXml(line)}</tspan>`
|
|
3145
|
+
),
|
|
3146
|
+
"</text>"
|
|
3147
|
+
].join("\n");
|
|
2444
3148
|
}
|
|
2445
|
-
function
|
|
2446
|
-
return
|
|
3149
|
+
function panelItemText(label, detail) {
|
|
3150
|
+
return detail === void 0 ? label : `${label}: ${detail}`;
|
|
2447
3151
|
}
|
|
2448
|
-
function renderLabel(label, box, item) {
|
|
3152
|
+
function renderLabel(label, box, item, annotations, surfaceKind) {
|
|
3153
|
+
const annotation = findAnnotation(annotations, surfaceKind, item.id);
|
|
3154
|
+
if (annotation !== void 0) {
|
|
3155
|
+
return renderSolvedTextAnnotation(annotation, "label", {
|
|
3156
|
+
indent: " ",
|
|
3157
|
+
mode: "center"
|
|
3158
|
+
}) ?? [];
|
|
3159
|
+
}
|
|
2449
3160
|
const labelLayout = item.labelLayout;
|
|
2450
3161
|
if (labelLayout?.lines !== void 0 && labelLayout.lines.length > 0) {
|
|
2451
3162
|
const offset = { x: box.x, y: box.y };
|
|
@@ -2471,10 +3182,17 @@ function renderEdgePath(edge) {
|
|
|
2471
3182
|
const dash = edge.style === "dashed" ? ' stroke-dasharray="6 4"' : "";
|
|
2472
3183
|
return `<path class="edge" data-id="${escapeAttribute(edge.id)}" d="${formatPath(pathPointsBeforeArrowhead(edge.points))}" fill="none" stroke="${EDGE_STROKE}" stroke-width="1.5"${dash}/>`;
|
|
2473
3184
|
}
|
|
2474
|
-
function renderEdgeLabel(edge) {
|
|
3185
|
+
function renderEdgeLabel(edge, annotations) {
|
|
2475
3186
|
if (edge.label?.text === void 0 || edge.points.length < 2) {
|
|
2476
3187
|
return [];
|
|
2477
3188
|
}
|
|
3189
|
+
const annotation = findAnnotation(annotations, "edge-label", edge.id);
|
|
3190
|
+
if (annotation !== void 0) {
|
|
3191
|
+
return renderSolvedTextAnnotation(annotation, "edge-label", {
|
|
3192
|
+
indent: " ",
|
|
3193
|
+
mode: "center"
|
|
3194
|
+
}) ?? [];
|
|
3195
|
+
}
|
|
2478
3196
|
const placement = labelPlacementOnPolyline(edge.points);
|
|
2479
3197
|
if (placement === void 0) {
|
|
2480
3198
|
return [];
|
|
@@ -2488,6 +3206,85 @@ function renderArrowhead(edge) {
|
|
|
2488
3206
|
const fill = edge.arrowhead === "hollowTriangle" ? "none" : EDGE_STROKE;
|
|
2489
3207
|
return `<polygon class="edge-arrowhead" data-edge="${escapeAttribute(edge.id)}" points="${formatPoints([arrowhead.tip, arrowhead.left, arrowhead.right])}" fill="${fill}" stroke="${EDGE_STROKE}"/>`;
|
|
2490
3208
|
}
|
|
3209
|
+
function renderSolvedTextAnnotation(annotation, className, options) {
|
|
3210
|
+
if (annotation === void 0) {
|
|
3211
|
+
return void 0;
|
|
3212
|
+
}
|
|
3213
|
+
const x = options.mode === "center" ? annotation.box.x + annotation.box.width / 2 : annotation.box.x;
|
|
3214
|
+
const y = options.mode === "center" ? annotation.box.y + annotation.box.height / 2 : annotation.box.y + annotation.box.height;
|
|
3215
|
+
const rotate = options.rotate ? ` transform="rotate(-90 ${formatNumber(x)} ${formatNumber(y)})"` : "";
|
|
3216
|
+
const attrs = [
|
|
3217
|
+
`class="${className}"`,
|
|
3218
|
+
`data-for="${escapeAttribute(annotation.ownerId)}"`,
|
|
3219
|
+
`data-text-surface="${escapeAttribute(annotation.surfaceKind)}"`,
|
|
3220
|
+
`data-owner-id="${escapeAttribute(annotation.ownerId)}"`,
|
|
3221
|
+
`data-text-backend="${escapeAttribute(annotation.textBackend ?? "deterministic")}"`,
|
|
3222
|
+
`font-family="${FONT_FAMILY}"`,
|
|
3223
|
+
`font-size="${formatNumber(annotation.fontSize)}"`,
|
|
3224
|
+
`fill="#111827"`
|
|
3225
|
+
];
|
|
3226
|
+
if (options.mode === "center") {
|
|
3227
|
+
attrs.push('text-anchor="middle"');
|
|
3228
|
+
} else {
|
|
3229
|
+
attrs.push(`text-anchor="${options.textAnchor ?? "start"}"`);
|
|
3230
|
+
}
|
|
3231
|
+
if (annotation.lines.length > 1) {
|
|
3232
|
+
return [
|
|
3233
|
+
`${options.indent}<text ${attrs.join(" ")}${rotate}>`,
|
|
3234
|
+
...annotation.lines.map(
|
|
3235
|
+
(line2) => `${options.indent} <tspan x="${formatNumber(textLineX(annotation, line2, options))}" y="${formatNumber(annotation.box.y + line2.baselineY)}">${escapeXml(line2.text)}</tspan>`
|
|
3236
|
+
),
|
|
3237
|
+
`${options.indent}</text>`
|
|
3238
|
+
];
|
|
3239
|
+
}
|
|
3240
|
+
const line = annotation.lines[0];
|
|
3241
|
+
const text = line?.text ?? options.fallbackText ?? annotation.text;
|
|
3242
|
+
const singleLineAttrs = options.mode === "center" ? [...attrs, 'dominant-baseline="middle"'] : attrs;
|
|
3243
|
+
return [
|
|
3244
|
+
`${options.indent}<text ${singleLineAttrs.join(" ")} x="${formatNumber(x)}" y="${formatNumber(y)}"${rotate}>${escapeXml(text)}</text>`
|
|
3245
|
+
];
|
|
3246
|
+
}
|
|
3247
|
+
function textLineX(annotation, line, options) {
|
|
3248
|
+
if (options.mode === "center") {
|
|
3249
|
+
return annotation.box.x + line.box.x + line.box.width / 2;
|
|
3250
|
+
}
|
|
3251
|
+
if ((options.textAnchor ?? "start") === "end") {
|
|
3252
|
+
return annotation.box.x + line.box.x + line.box.width;
|
|
3253
|
+
}
|
|
3254
|
+
return annotation.box.x + line.box.x;
|
|
3255
|
+
}
|
|
3256
|
+
function findAnnotation(annotations, surfaceKind, ownerId, index) {
|
|
3257
|
+
return annotations.find((annotation) => {
|
|
3258
|
+
if (annotation.surfaceKind !== surfaceKind) {
|
|
3259
|
+
return false;
|
|
3260
|
+
}
|
|
3261
|
+
if (annotation.ownerId !== ownerId) {
|
|
3262
|
+
return false;
|
|
3263
|
+
}
|
|
3264
|
+
if (index === void 0) {
|
|
3265
|
+
return annotation.surfaceIndex === void 0;
|
|
3266
|
+
}
|
|
3267
|
+
return annotation.surfaceIndex === index;
|
|
3268
|
+
});
|
|
3269
|
+
}
|
|
3270
|
+
function renderSwimlaneLabel(swimlane, text, labelBox) {
|
|
3271
|
+
const x = labelBox.x + labelBox.width / 2;
|
|
3272
|
+
const y = labelBox.y + labelBox.height / 2;
|
|
3273
|
+
const transform = swimlane.orientation === "horizontal" ? ` transform="rotate(-90 ${formatNumber(x)} ${formatNumber(y)})"` : "";
|
|
3274
|
+
return ` <text class="swimlane-label" x="${formatNumber(x)}" y="${formatNumber(y)}" text-anchor="middle" dominant-baseline="middle"${transform} font-family="${FONT_FAMILY}" font-size="12" fill="#111827">${escapeXml(text)}</text>`;
|
|
3275
|
+
}
|
|
3276
|
+
function renderRect(box, attributes) {
|
|
3277
|
+
return `<rect ${attributes} x="${formatNumber(box.x)}" y="${formatNumber(box.y)}" width="${formatNumber(box.width)}" height="${formatNumber(box.height)}"/>`;
|
|
3278
|
+
}
|
|
3279
|
+
function portLabelX(x, side) {
|
|
3280
|
+
if (side === "left") {
|
|
3281
|
+
return x - 8;
|
|
3282
|
+
}
|
|
3283
|
+
if (side === "right") {
|
|
3284
|
+
return x + 8;
|
|
3285
|
+
}
|
|
3286
|
+
return x + 8;
|
|
3287
|
+
}
|
|
2491
3288
|
function labelPlacementOnPolyline(points) {
|
|
2492
3289
|
const segments = nonZeroSegments(points);
|
|
2493
3290
|
const totalLength = segments.reduce(
|
|
@@ -2570,17 +3367,15 @@ function shapePoints(shape, box) {
|
|
|
2570
3367
|
{ x: right - skew, y: bottom },
|
|
2571
3368
|
{ x: left, y: bottom }
|
|
2572
3369
|
];
|
|
2573
|
-
case "hexagon":
|
|
2574
|
-
const inset = Math.min(box.width * 0.2, 24);
|
|
3370
|
+
case "hexagon":
|
|
2575
3371
|
return [
|
|
2576
|
-
{ x: left +
|
|
2577
|
-
{ x: right -
|
|
3372
|
+
{ x: left + skew, y: top },
|
|
3373
|
+
{ x: right - skew, y: top },
|
|
2578
3374
|
{ x: right, y: midY },
|
|
2579
|
-
{ x: right -
|
|
2580
|
-
{ x: left +
|
|
3375
|
+
{ x: right - skew, y: bottom },
|
|
3376
|
+
{ x: left + skew, y: bottom },
|
|
2581
3377
|
{ x: left, y: midY }
|
|
2582
3378
|
];
|
|
2583
|
-
}
|
|
2584
3379
|
}
|
|
2585
3380
|
}
|
|
2586
3381
|
function formatCylinderPath(box) {
|
|
@@ -2605,10 +3400,9 @@ function formatCylinderPath(box) {
|
|
|
2605
3400
|
].join(" ");
|
|
2606
3401
|
}
|
|
2607
3402
|
function formatPath(points) {
|
|
2608
|
-
return points.map(
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
}).join(" ");
|
|
3403
|
+
return points.map(
|
|
3404
|
+
(point2, index) => `${index === 0 ? "M" : "L"} ${formatNumber(point2.x)} ${formatNumber(point2.y)}`
|
|
3405
|
+
).join(" ");
|
|
2612
3406
|
}
|
|
2613
3407
|
function formatPoints(points) {
|
|
2614
3408
|
return points.map((point2) => `${formatNumber(point2.x)},${formatNumber(point2.y)}`).join(" ");
|
|
@@ -2631,6 +3425,9 @@ function escapeAttribute(value) {
|
|
|
2631
3425
|
function indent(value) {
|
|
2632
3426
|
return ` ${value}`;
|
|
2633
3427
|
}
|
|
3428
|
+
function indentLines(values) {
|
|
3429
|
+
return values.map(indent);
|
|
3430
|
+
}
|
|
2634
3431
|
var DEFAULT_OPTIONS = {
|
|
2635
3432
|
nodesep: 80,
|
|
2636
3433
|
ranksep: 100,
|
|
@@ -2732,37 +3529,112 @@ function isValidDimension(value) {
|
|
|
2732
3529
|
// src/routing/routes.ts
|
|
2733
3530
|
function routeEdge(input) {
|
|
2734
3531
|
const diagnostics = [];
|
|
3532
|
+
const softObstacles = input.obstacles ?? [];
|
|
3533
|
+
const hardObstacles = input.hardObstacles ?? [];
|
|
2735
3534
|
const defaultAnchors = defaultAnchorsForGeometry(
|
|
2736
3535
|
input.source.box,
|
|
2737
3536
|
input.target.box,
|
|
2738
3537
|
input.direction
|
|
2739
3538
|
);
|
|
2740
|
-
const source = getEdgePort(
|
|
2741
|
-
input.source,
|
|
2742
|
-
input.target.center,
|
|
2743
|
-
input.sourceAnchor ?? defaultAnchors.sourceAnchor
|
|
2744
|
-
);
|
|
2745
|
-
const target = getEdgePort(
|
|
2746
|
-
input.target,
|
|
2747
|
-
input.source.center,
|
|
2748
|
-
input.targetAnchor ?? defaultAnchors.targetAnchor
|
|
2749
|
-
);
|
|
2750
3539
|
if ((input.kind ?? "orthogonal") === "straight") {
|
|
2751
|
-
|
|
3540
|
+
const source = getEdgePort(
|
|
3541
|
+
input.source,
|
|
3542
|
+
input.target.center,
|
|
3543
|
+
input.sourceAnchor ?? defaultAnchors.sourceAnchor
|
|
3544
|
+
);
|
|
3545
|
+
const target = getEdgePort(
|
|
3546
|
+
input.target,
|
|
3547
|
+
input.source.center,
|
|
3548
|
+
input.targetAnchor ?? defaultAnchors.targetAnchor
|
|
3549
|
+
);
|
|
3550
|
+
const points = simplifyRoute([source, target]);
|
|
3551
|
+
if (routeCrossesBoxes(points, hardObstacles)) {
|
|
3552
|
+
diagnostics.push({
|
|
3553
|
+
severity: "error",
|
|
3554
|
+
code: "routing.evidence.crossing_forbidden",
|
|
3555
|
+
message: "Straight route crosses hard evidence block obstacles."
|
|
3556
|
+
});
|
|
3557
|
+
return { points, diagnostics };
|
|
3558
|
+
}
|
|
3559
|
+
if (routeCrossesBoxes(points, softObstacles)) {
|
|
3560
|
+
diagnostics.push({
|
|
3561
|
+
severity: "warning",
|
|
3562
|
+
code: "routing.obstacle.unavoidable",
|
|
3563
|
+
message: "Straight route crosses soft obstacles."
|
|
3564
|
+
});
|
|
3565
|
+
}
|
|
3566
|
+
return { points, diagnostics };
|
|
3567
|
+
}
|
|
3568
|
+
const routeLaneObstacles = [...softObstacles, ...hardObstacles];
|
|
3569
|
+
const anchorPairs = routeAnchorPairs(input, defaultAnchors);
|
|
3570
|
+
const candidateRoutes = anchorPairs.flatMap(
|
|
3571
|
+
({ sourceAnchor, targetAnchor }) => {
|
|
3572
|
+
const source = getEdgePort(
|
|
3573
|
+
input.source,
|
|
3574
|
+
input.target.center,
|
|
3575
|
+
sourceAnchor
|
|
3576
|
+
);
|
|
3577
|
+
const target = getEdgePort(
|
|
3578
|
+
input.target,
|
|
3579
|
+
input.source.center,
|
|
3580
|
+
targetAnchor
|
|
3581
|
+
);
|
|
3582
|
+
const routes = [
|
|
3583
|
+
...orthogonalCandidates(source, target, input.direction),
|
|
3584
|
+
...expandedObstacleCandidates(
|
|
3585
|
+
source,
|
|
3586
|
+
target,
|
|
3587
|
+
input.direction,
|
|
3588
|
+
routeLaneObstacles
|
|
3589
|
+
),
|
|
3590
|
+
...outerDoglegCandidates(
|
|
3591
|
+
source,
|
|
3592
|
+
target,
|
|
3593
|
+
input.direction,
|
|
3594
|
+
routeLaneObstacles
|
|
3595
|
+
)
|
|
3596
|
+
];
|
|
3597
|
+
const endpointObstacles = endpointObstaclesForAutoAnchors(input);
|
|
3598
|
+
return routes.map((points) => ({ points, endpointObstacles }));
|
|
3599
|
+
}
|
|
3600
|
+
);
|
|
3601
|
+
for (const candidate of candidateRoutes) {
|
|
3602
|
+
if (!routeIntersectsObstacles(candidate.points, softObstacles) && !routeIntersectsObstacles(candidate.points, hardObstacles) && !routeIntersectsEndpointInteriors(
|
|
3603
|
+
candidate.points,
|
|
3604
|
+
candidate.endpointObstacles
|
|
3605
|
+
)) {
|
|
3606
|
+
return { points: simplifyRoute(candidate.points), diagnostics };
|
|
3607
|
+
}
|
|
2752
3608
|
}
|
|
2753
|
-
const
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
target,
|
|
2758
|
-
input.direction,
|
|
2759
|
-
input.obstacles ?? []
|
|
3609
|
+
const hardClearCandidate = candidateRoutes.find(
|
|
3610
|
+
(candidate) => !routeIntersectsObstacles(candidate.points, hardObstacles) && !routeIntersectsEndpointInteriors(
|
|
3611
|
+
candidate.points,
|
|
3612
|
+
candidate.endpointObstacles
|
|
2760
3613
|
)
|
|
2761
3614
|
);
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
3615
|
+
if (hardClearCandidate !== void 0) {
|
|
3616
|
+
diagnostics.push({
|
|
3617
|
+
severity: "warning",
|
|
3618
|
+
code: "routing.obstacle.unavoidable",
|
|
3619
|
+
message: "No bounded orthogonal route candidate avoided all soft obstacles."
|
|
3620
|
+
});
|
|
3621
|
+
return {
|
|
3622
|
+
points: simplifyRoute(hardClearCandidate.points),
|
|
3623
|
+
diagnostics
|
|
3624
|
+
};
|
|
3625
|
+
}
|
|
3626
|
+
if (hardObstacles.length > 0) {
|
|
3627
|
+
diagnostics.push({
|
|
3628
|
+
severity: "error",
|
|
3629
|
+
code: "routing.evidence.crossing_forbidden",
|
|
3630
|
+
message: "No bounded orthogonal route candidate avoided hard evidence block obstacles."
|
|
3631
|
+
});
|
|
3632
|
+
return {
|
|
3633
|
+
points: simplifyRoute(
|
|
3634
|
+
candidateRoutes[0]?.points ?? fallbackRoute(input, defaultAnchors)
|
|
3635
|
+
),
|
|
3636
|
+
diagnostics
|
|
3637
|
+
};
|
|
2766
3638
|
}
|
|
2767
3639
|
diagnostics.push({
|
|
2768
3640
|
severity: "warning",
|
|
@@ -2770,10 +3642,111 @@ function routeEdge(input) {
|
|
|
2770
3642
|
message: "No bounded orthogonal route candidate avoided all obstacles."
|
|
2771
3643
|
});
|
|
2772
3644
|
return {
|
|
2773
|
-
points: simplifyRoute(
|
|
3645
|
+
points: simplifyRoute(
|
|
3646
|
+
candidateRoutes[0]?.points ?? fallbackRoute(input, defaultAnchors)
|
|
3647
|
+
),
|
|
2774
3648
|
diagnostics
|
|
2775
3649
|
};
|
|
2776
3650
|
}
|
|
3651
|
+
function endpointObstaclesForAutoAnchors(input) {
|
|
3652
|
+
const boxes = [];
|
|
3653
|
+
if (input.sourceAnchor === void 0 && hasDistinctAnchors(input.source)) {
|
|
3654
|
+
boxes.push(insetBox(input.source.box, 1));
|
|
3655
|
+
}
|
|
3656
|
+
if (input.targetAnchor === void 0 && hasDistinctAnchors(input.target)) {
|
|
3657
|
+
boxes.push(insetBox(input.target.box, 1));
|
|
3658
|
+
}
|
|
3659
|
+
return boxes.filter((box) => box.width > 0 && box.height > 0);
|
|
3660
|
+
}
|
|
3661
|
+
function hasDistinctAnchors(geometry) {
|
|
3662
|
+
const points = new Set(
|
|
3663
|
+
geometry.anchors.map((anchor) => `${anchor.point.x},${anchor.point.y}`)
|
|
3664
|
+
);
|
|
3665
|
+
return points.size > 1;
|
|
3666
|
+
}
|
|
3667
|
+
function insetBox(box, margin) {
|
|
3668
|
+
return {
|
|
3669
|
+
x: box.x + margin,
|
|
3670
|
+
y: box.y + margin,
|
|
3671
|
+
width: box.width - margin * 2,
|
|
3672
|
+
height: box.height - margin * 2
|
|
3673
|
+
};
|
|
3674
|
+
}
|
|
3675
|
+
function fallbackRoute(input, defaultAnchors) {
|
|
3676
|
+
return [
|
|
3677
|
+
getEdgePort(
|
|
3678
|
+
input.source,
|
|
3679
|
+
input.target.center,
|
|
3680
|
+
input.sourceAnchor ?? defaultAnchors.sourceAnchor
|
|
3681
|
+
),
|
|
3682
|
+
getEdgePort(
|
|
3683
|
+
input.target,
|
|
3684
|
+
input.source.center,
|
|
3685
|
+
input.targetAnchor ?? defaultAnchors.targetAnchor
|
|
3686
|
+
)
|
|
3687
|
+
];
|
|
3688
|
+
}
|
|
3689
|
+
function routeAnchorPairs(input, defaultAnchors) {
|
|
3690
|
+
const sourceAnchors = routeAnchorCandidates(
|
|
3691
|
+
input.sourceAnchor,
|
|
3692
|
+
defaultAnchors.sourceAnchor,
|
|
3693
|
+
input.source,
|
|
3694
|
+
input.target.center
|
|
3695
|
+
);
|
|
3696
|
+
const targetAnchors = routeAnchorCandidates(
|
|
3697
|
+
input.targetAnchor,
|
|
3698
|
+
defaultAnchors.targetAnchor,
|
|
3699
|
+
input.target,
|
|
3700
|
+
input.source.center
|
|
3701
|
+
);
|
|
3702
|
+
const pairs = sourceAnchors.flatMap(
|
|
3703
|
+
(sourceAnchor) => targetAnchors.map((targetAnchor) => ({ sourceAnchor, targetAnchor }))
|
|
3704
|
+
);
|
|
3705
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3706
|
+
return pairs.filter((pair) => {
|
|
3707
|
+
const key = `${pair.sourceAnchor}->${pair.targetAnchor}`;
|
|
3708
|
+
if (seen.has(key)) {
|
|
3709
|
+
return false;
|
|
3710
|
+
}
|
|
3711
|
+
seen.add(key);
|
|
3712
|
+
return true;
|
|
3713
|
+
});
|
|
3714
|
+
}
|
|
3715
|
+
function routeAnchorCandidates(explicitAnchor, defaultAnchor, geometry, toward) {
|
|
3716
|
+
if (explicitAnchor !== void 0) {
|
|
3717
|
+
return [explicitAnchor];
|
|
3718
|
+
}
|
|
3719
|
+
const ranked = rankedSideAnchors(geometry, toward);
|
|
3720
|
+
return [defaultAnchor, ...ranked].filter(
|
|
3721
|
+
(anchor, index, anchors) => anchors.indexOf(anchor) === index
|
|
3722
|
+
);
|
|
3723
|
+
}
|
|
3724
|
+
function rankedSideAnchors(geometry, toward) {
|
|
3725
|
+
const anchors = outwardSideAnchors(geometry.box, toward);
|
|
3726
|
+
return anchors.sort((left, right) => {
|
|
3727
|
+
const leftPoint = getEdgePort(geometry, toward, left);
|
|
3728
|
+
const rightPoint = getEdgePort(geometry, toward, right);
|
|
3729
|
+
const distance = squaredDistance2(leftPoint, toward) - squaredDistance2(rightPoint, toward);
|
|
3730
|
+
return distance === 0 ? left.localeCompare(right) : distance;
|
|
3731
|
+
});
|
|
3732
|
+
}
|
|
3733
|
+
function outwardSideAnchors(box, toward) {
|
|
3734
|
+
const center = {
|
|
3735
|
+
x: box.x + box.width / 2,
|
|
3736
|
+
y: box.y + box.height / 2
|
|
3737
|
+
};
|
|
3738
|
+
const dx = toward.x - center.x;
|
|
3739
|
+
const dy = toward.y - center.y;
|
|
3740
|
+
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
3741
|
+
return dx >= 0 ? ["right", "top", "bottom"] : ["left", "top", "bottom"];
|
|
3742
|
+
}
|
|
3743
|
+
return dy >= 0 ? ["bottom", "left", "right"] : ["top", "left", "right"];
|
|
3744
|
+
}
|
|
3745
|
+
function squaredDistance2(a, b) {
|
|
3746
|
+
const dx = a.x - b.x;
|
|
3747
|
+
const dy = a.y - b.y;
|
|
3748
|
+
return dx * dx + dy * dy;
|
|
3749
|
+
}
|
|
2777
3750
|
function simplifyRoute(points) {
|
|
2778
3751
|
const withoutDuplicates = [];
|
|
2779
3752
|
for (const point2 of points) {
|
|
@@ -2898,6 +3871,44 @@ function expandedObstacleCandidates(source, target, direction, obstacles) {
|
|
|
2898
3871
|
}
|
|
2899
3872
|
return candidates;
|
|
2900
3873
|
}
|
|
3874
|
+
function outerDoglegCandidates(source, target, direction, obstacles) {
|
|
3875
|
+
if (obstacles.length === 0) {
|
|
3876
|
+
return [];
|
|
3877
|
+
}
|
|
3878
|
+
const margin = 24;
|
|
3879
|
+
const minX = Math.min(...obstacles.map((obstacle) => obstacle.x)) - margin;
|
|
3880
|
+
const maxX = Math.max(...obstacles.map((obstacle) => obstacle.x + obstacle.width)) + margin;
|
|
3881
|
+
const minY = Math.min(...obstacles.map((obstacle) => obstacle.y)) - margin;
|
|
3882
|
+
const maxY = Math.max(...obstacles.map((obstacle) => obstacle.y + obstacle.height)) + margin;
|
|
3883
|
+
if (direction === "TB" || direction === "BT") {
|
|
3884
|
+
const exit2 = exitDelta(source, target, "y");
|
|
3885
|
+
return sortedUniqueLanes([minX, maxX], (source.x + target.x) / 2).map(
|
|
3886
|
+
(laneX) => [
|
|
3887
|
+
source,
|
|
3888
|
+
{ x: source.x, y: source.y + exit2 },
|
|
3889
|
+
{ x: laneX, y: source.y + exit2 },
|
|
3890
|
+
{ x: laneX, y: target.y - exit2 },
|
|
3891
|
+
{ x: target.x, y: target.y - exit2 },
|
|
3892
|
+
target
|
|
3893
|
+
]
|
|
3894
|
+
);
|
|
3895
|
+
}
|
|
3896
|
+
const exit = exitDelta(source, target, "x");
|
|
3897
|
+
return sortedUniqueLanes([minY, maxY], (source.y + target.y) / 2).map(
|
|
3898
|
+
(laneY) => [
|
|
3899
|
+
source,
|
|
3900
|
+
{ x: source.x + exit, y: source.y },
|
|
3901
|
+
{ x: source.x + exit, y: laneY },
|
|
3902
|
+
{ x: target.x - exit, y: laneY },
|
|
3903
|
+
{ x: target.x - exit, y: target.y },
|
|
3904
|
+
target
|
|
3905
|
+
]
|
|
3906
|
+
);
|
|
3907
|
+
}
|
|
3908
|
+
function exitDelta(source, target, axis) {
|
|
3909
|
+
const delta = axis === "x" ? target.x - source.x : target.y - source.y;
|
|
3910
|
+
return (delta >= 0 ? 1 : -1) * 24;
|
|
3911
|
+
}
|
|
2901
3912
|
function sortedUniqueLanes(lanes, midpoint) {
|
|
2902
3913
|
return [...new Set(lanes)].filter((lane) => Number.isFinite(lane)).sort((left, right) => {
|
|
2903
3914
|
const distance = Math.abs(left - midpoint) - Math.abs(right - midpoint);
|
|
@@ -2921,6 +3932,72 @@ function routeIntersectsObstacles(points, obstacles) {
|
|
|
2921
3932
|
}
|
|
2922
3933
|
return false;
|
|
2923
3934
|
}
|
|
3935
|
+
function routeIntersectsEndpointInteriors(points, endpointInteriors) {
|
|
3936
|
+
for (let index = 0; index < points.length - 1; index += 1) {
|
|
3937
|
+
const a = points[index];
|
|
3938
|
+
const b = points[index + 1];
|
|
3939
|
+
if (a === void 0 || b === void 0) {
|
|
3940
|
+
continue;
|
|
3941
|
+
}
|
|
3942
|
+
const segment = segmentBox(a, b);
|
|
3943
|
+
for (const endpointInterior of endpointInteriors) {
|
|
3944
|
+
validateBox(endpointInterior);
|
|
3945
|
+
if (intersectsAabb(segment, endpointInterior)) {
|
|
3946
|
+
return true;
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
return false;
|
|
3951
|
+
}
|
|
3952
|
+
function routeCrossesBoxes(points, obstacles) {
|
|
3953
|
+
for (let index = 0; index < points.length - 1; index += 1) {
|
|
3954
|
+
const a = points[index];
|
|
3955
|
+
const b = points[index + 1];
|
|
3956
|
+
if (a === void 0 || b === void 0) {
|
|
3957
|
+
continue;
|
|
3958
|
+
}
|
|
3959
|
+
for (const obstacle of obstacles) {
|
|
3960
|
+
validateBox(obstacle);
|
|
3961
|
+
if (segmentIntersectsBox(a, b, obstacle)) {
|
|
3962
|
+
return true;
|
|
3963
|
+
}
|
|
3964
|
+
}
|
|
3965
|
+
}
|
|
3966
|
+
return false;
|
|
3967
|
+
}
|
|
3968
|
+
function segmentIntersectsBox(start, end, box) {
|
|
3969
|
+
const left = box.x;
|
|
3970
|
+
const right = box.x + box.width;
|
|
3971
|
+
const top = box.y;
|
|
3972
|
+
const bottom = box.y + box.height;
|
|
3973
|
+
if (pointInsideBox(start, box) || pointInsideBox(end, box)) {
|
|
3974
|
+
return true;
|
|
3975
|
+
}
|
|
3976
|
+
if (start.x === end.x) {
|
|
3977
|
+
return start.x > left && start.x < right && rangesOverlap(start.y, end.y, top, bottom);
|
|
3978
|
+
}
|
|
3979
|
+
if (start.y === end.y) {
|
|
3980
|
+
return start.y > top && start.y < bottom && rangesOverlap(start.x, end.x, left, right);
|
|
3981
|
+
}
|
|
3982
|
+
return segmentIntersectsBoxEdge(start, end, left, top, right, top) || segmentIntersectsBoxEdge(start, end, right, top, right, bottom) || segmentIntersectsBoxEdge(start, end, right, bottom, left, bottom) || segmentIntersectsBoxEdge(start, end, left, bottom, left, top);
|
|
3983
|
+
}
|
|
3984
|
+
function pointInsideBox(point2, box) {
|
|
3985
|
+
return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
|
|
3986
|
+
}
|
|
3987
|
+
function rangesOverlap(a, b, min, max) {
|
|
3988
|
+
const low = Math.min(a, b);
|
|
3989
|
+
const high = Math.max(a, b);
|
|
3990
|
+
return high > min && low < max;
|
|
3991
|
+
}
|
|
3992
|
+
function segmentIntersectsBoxEdge(start, end, x1, y1, x2, y2) {
|
|
3993
|
+
const denominator = (end.x - start.x) * (y2 - y1) - (end.y - start.y) * (x2 - x1);
|
|
3994
|
+
if (denominator === 0) {
|
|
3995
|
+
return false;
|
|
3996
|
+
}
|
|
3997
|
+
const t = ((x1 - start.x) * (y2 - y1) - (y1 - start.y) * (x2 - x1)) / denominator;
|
|
3998
|
+
const u = ((x1 - start.x) * (end.y - start.y) - (y1 - start.y) * (end.x - start.x)) / denominator;
|
|
3999
|
+
return t > 0 && t < 1 && u > 0 && u < 1;
|
|
4000
|
+
}
|
|
2924
4001
|
function segmentBox(a, b) {
|
|
2925
4002
|
const minX = Math.min(a.x, b.x);
|
|
2926
4003
|
const minY = Math.min(a.y, b.y);
|
|
@@ -2936,6 +4013,17 @@ function areCollinear(a, b, c) {
|
|
|
2936
4013
|
}
|
|
2937
4014
|
|
|
2938
4015
|
// src/solver/solve.ts
|
|
4016
|
+
var DEFAULT_MATRIX_CELL_SIZE2 = { width: 120, height: 36 };
|
|
4017
|
+
var DEFAULT_TABLE_CELL_SIZE2 = { width: 128, height: 34 };
|
|
4018
|
+
var DEFAULT_PANEL_WIDTH = 320;
|
|
4019
|
+
var DEFAULT_PANEL_ITEM_HEIGHT2 = 28;
|
|
4020
|
+
var DEFAULT_EVIDENCE_BLOCK_GAP = 24;
|
|
4021
|
+
var EDGE_LABEL_CLEARANCE = 8;
|
|
4022
|
+
var EVIDENCE_TEXT_FONT = {
|
|
4023
|
+
fontFamily: "Arial, sans-serif",
|
|
4024
|
+
fontSize: 10,
|
|
4025
|
+
lineHeight: 12
|
|
4026
|
+
};
|
|
2939
4027
|
function solveDiagram(diagram, options = {}) {
|
|
2940
4028
|
const diagnostics = [...diagram.diagnostics];
|
|
2941
4029
|
const nodes = stableById(diagram.nodes);
|
|
@@ -3000,19 +4088,21 @@ function solveDiagram(diagram, options = {}) {
|
|
|
3000
4088
|
constrained.boxes,
|
|
3001
4089
|
swimlaneContracts.layouts
|
|
3002
4090
|
);
|
|
4091
|
+
const coordinatedMatrices = coordinateMatrices(diagram.matrices ?? []);
|
|
4092
|
+
const coordinatedTables = coordinateTables(diagram.tables ?? []);
|
|
4093
|
+
const coordinatedEvidencePanels = coordinateEvidencePanels(
|
|
4094
|
+
diagram.evidencePanels ?? []
|
|
4095
|
+
);
|
|
3003
4096
|
const groupBoxes = new Map(
|
|
3004
4097
|
coordinatedGroups.map((group) => [group.id, group.box])
|
|
3005
4098
|
);
|
|
3006
|
-
const
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
diagnostics
|
|
3014
|
-
);
|
|
3015
|
-
const allBoxes = [
|
|
4099
|
+
const baseTextAnnotations = coordinateBaseTextAnnotations({
|
|
4100
|
+
nodes: coordinatedNodes,
|
|
4101
|
+
groups: coordinatedGroups,
|
|
4102
|
+
swimlanes: coordinatedSwimlanes,
|
|
4103
|
+
...options.textMeasurer === void 0 ? {} : { textMeasurer: options.textMeasurer }
|
|
4104
|
+
});
|
|
4105
|
+
const layoutBoxes = [
|
|
3016
4106
|
...coordinatedNodes.map((node) => node.box),
|
|
3017
4107
|
...coordinatedNodes.flatMap(
|
|
3018
4108
|
(node) => (node.ports ?? []).flatMap(
|
|
@@ -3022,10 +4112,111 @@ function solveDiagram(diagram, options = {}) {
|
|
|
3022
4112
|
...groupBoxes.values(),
|
|
3023
4113
|
...coordinatedSwimlanes.flatMap(
|
|
3024
4114
|
(swimlane) => swimlane.box === void 0 ? [] : [swimlane.box]
|
|
3025
|
-
)
|
|
4115
|
+
),
|
|
4116
|
+
...baseTextAnnotations.map((annotation) => annotation.box)
|
|
4117
|
+
];
|
|
4118
|
+
const initialContentBounds = layoutBoxes.length === 0 ? { x: 0, y: 0, width: 0} : unionBoxes(layoutBoxes);
|
|
4119
|
+
placeEvidenceBlocks(
|
|
4120
|
+
[
|
|
4121
|
+
...coordinatedMatrices,
|
|
4122
|
+
...coordinatedTables,
|
|
4123
|
+
...coordinatedEvidencePanels
|
|
4124
|
+
],
|
|
4125
|
+
initialContentBounds
|
|
4126
|
+
);
|
|
4127
|
+
refreshTableColumnXOffsets(coordinatedTables);
|
|
4128
|
+
measureEvidenceTextBlocks(
|
|
4129
|
+
coordinatedMatrices,
|
|
4130
|
+
coordinatedTables,
|
|
4131
|
+
coordinatedEvidencePanels,
|
|
4132
|
+
options.textMeasurer
|
|
4133
|
+
);
|
|
4134
|
+
const evidenceBoxes = [
|
|
4135
|
+
...coordinatedMatrices.map((matrix) => matrix.box),
|
|
4136
|
+
...coordinatedTables.map((table) => table.box),
|
|
4137
|
+
...coordinatedEvidencePanels.map((panel) => panel.box)
|
|
3026
4138
|
];
|
|
4139
|
+
diagnostics.push(
|
|
4140
|
+
...reportEvidenceBlockOverlaps(
|
|
4141
|
+
[
|
|
4142
|
+
...coordinatedMatrices.map((matrix) => ({
|
|
4143
|
+
id: matrix.id,
|
|
4144
|
+
kind: "matrix",
|
|
4145
|
+
...matrix.position === void 0 ? {} : { position: matrix.position },
|
|
4146
|
+
box: matrix.box
|
|
4147
|
+
})),
|
|
4148
|
+
...coordinatedTables.map((table) => ({
|
|
4149
|
+
id: table.id,
|
|
4150
|
+
kind: "table",
|
|
4151
|
+
...table.position === void 0 ? {} : { position: table.position },
|
|
4152
|
+
box: table.box
|
|
4153
|
+
})),
|
|
4154
|
+
...coordinatedEvidencePanels.map((panel) => ({
|
|
4155
|
+
id: panel.id,
|
|
4156
|
+
kind: "evidence-panel",
|
|
4157
|
+
...panel.position === void 0 ? {} : { position: panel.position },
|
|
4158
|
+
box: panel.box
|
|
4159
|
+
}))
|
|
4160
|
+
],
|
|
4161
|
+
[
|
|
4162
|
+
...coordinatedNodes.map((node) => ({
|
|
4163
|
+
id: node.id,
|
|
4164
|
+
kind: "node",
|
|
4165
|
+
box: node.box
|
|
4166
|
+
})),
|
|
4167
|
+
...coordinatedGroups.map((group) => ({
|
|
4168
|
+
id: group.id,
|
|
4169
|
+
kind: "group",
|
|
4170
|
+
box: group.box
|
|
4171
|
+
})),
|
|
4172
|
+
...coordinatedSwimlanes.flatMap(
|
|
4173
|
+
(swimlane) => swimlane.box === void 0 ? [] : [{ id: swimlane.id, kind: "swimlane", box: swimlane.box }]
|
|
4174
|
+
)
|
|
4175
|
+
]
|
|
4176
|
+
)
|
|
4177
|
+
);
|
|
4178
|
+
const allBoxes = [...layoutBoxes, ...evidenceBoxes];
|
|
3027
4179
|
const contentBounds = allBoxes.length === 0 ? { x: 0, y: 0, width: 0, height: 0 } : unionBoxes(allBoxes);
|
|
3028
4180
|
const frame = diagram.frame === void 0 ? void 0 : coordinateFrame(diagram.frame, contentBounds);
|
|
4181
|
+
const frameTextAnnotation = frame === void 0 ? [] : [coordinateFrameTextAnnotation(frame, options.textMeasurer)];
|
|
4182
|
+
const routingTextObstacles = [
|
|
4183
|
+
...baseTextAnnotations.filter(isPreRouteTextObstacle),
|
|
4184
|
+
...frameTextAnnotation.filter(isPreRouteTextObstacle)
|
|
4185
|
+
];
|
|
4186
|
+
const coordinatedEdges = coordinateEdges(
|
|
4187
|
+
edges,
|
|
4188
|
+
nodeGeometryById,
|
|
4189
|
+
coordinatedNodes,
|
|
4190
|
+
[...nodeGeometryById.values()].map((geometry) => geometry.obstacleBox),
|
|
4191
|
+
[
|
|
4192
|
+
...coordinatedTables.map((table) => table.box),
|
|
4193
|
+
...coordinatedEvidencePanels.map((panel) => panel.box)
|
|
4194
|
+
],
|
|
4195
|
+
routingTextObstacles,
|
|
4196
|
+
coordinatedMatrices.map((matrix) => matrix.box),
|
|
4197
|
+
diagram.direction,
|
|
4198
|
+
options,
|
|
4199
|
+
diagnostics
|
|
4200
|
+
);
|
|
4201
|
+
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
4202
|
+
coordinatedEdges,
|
|
4203
|
+
options.textMeasurer
|
|
4204
|
+
);
|
|
4205
|
+
const textAnnotations = [
|
|
4206
|
+
...baseTextAnnotations,
|
|
4207
|
+
...frameTextAnnotation,
|
|
4208
|
+
...edgeTextAnnotations
|
|
4209
|
+
];
|
|
4210
|
+
diagnostics.push(...reportTextAnnotationCollisions(textAnnotations));
|
|
4211
|
+
diagnostics.push(
|
|
4212
|
+
...reportRouteTextClearance(coordinatedEdges, textAnnotations)
|
|
4213
|
+
);
|
|
4214
|
+
const edgePointBounds = edgeBounds(coordinatedEdges);
|
|
4215
|
+
const boundsBase = [
|
|
4216
|
+
contentBounds,
|
|
4217
|
+
...edgePointBounds,
|
|
4218
|
+
...edgeTextAnnotations.map((annotation) => annotation.box)
|
|
4219
|
+
];
|
|
3029
4220
|
return {
|
|
3030
4221
|
id: diagram.id,
|
|
3031
4222
|
...diagram.title === void 0 ? {} : { title: diagram.title },
|
|
@@ -3034,9 +4225,13 @@ function solveDiagram(diagram, options = {}) {
|
|
|
3034
4225
|
edges: coordinatedEdges,
|
|
3035
4226
|
groups: coordinatedGroups,
|
|
3036
4227
|
...coordinatedSwimlanes.length === 0 ? {} : { swimlanes: coordinatedSwimlanes },
|
|
4228
|
+
...coordinatedMatrices.length === 0 ? {} : { matrices: coordinatedMatrices },
|
|
4229
|
+
...coordinatedTables.length === 0 ? {} : { tables: coordinatedTables },
|
|
4230
|
+
...coordinatedEvidencePanels.length === 0 ? {} : { evidencePanels: coordinatedEvidencePanels },
|
|
3037
4231
|
diagnostics,
|
|
3038
|
-
bounds: frame === void 0 ?
|
|
4232
|
+
bounds: frame === void 0 ? unionBoxes(boundsBase) : unionBoxes([...boundsBase, frame.box, frame.titleBox]),
|
|
3039
4233
|
...frame === void 0 ? {} : { frame },
|
|
4234
|
+
...textAnnotations.length === 0 ? {} : { textAnnotations },
|
|
3040
4235
|
...diagram.metadata === void 0 ? {} : { metadata: diagram.metadata }
|
|
3041
4236
|
};
|
|
3042
4237
|
}
|
|
@@ -3931,23 +5126,380 @@ function coordinateGroups(groups, nodeBoxes, options, diagnostics) {
|
|
|
3931
5126
|
}
|
|
3932
5127
|
return coordinated;
|
|
3933
5128
|
}
|
|
3934
|
-
function
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
5129
|
+
function coordinateMatrices(matrices) {
|
|
5130
|
+
return matrices.map((block) => ({
|
|
5131
|
+
...block,
|
|
5132
|
+
box: blockBox(block, {
|
|
5133
|
+
width: defaultMatrixRowHeaderWidth2(block) + Math.max(1, block.cols.length) * DEFAULT_MATRIX_CELL_SIZE2.width,
|
|
5134
|
+
height: Math.max(1, block.rows.length + 1) * DEFAULT_MATRIX_CELL_SIZE2.height
|
|
5135
|
+
})
|
|
5136
|
+
}));
|
|
5137
|
+
}
|
|
5138
|
+
function defaultMatrixRowHeaderWidth2(block) {
|
|
5139
|
+
return block.rows.length === 0 ? 0 : Math.min(96, DEFAULT_MATRIX_CELL_SIZE2.width);
|
|
5140
|
+
}
|
|
5141
|
+
function coordinateTables(tables) {
|
|
5142
|
+
return tables.map((table) => {
|
|
5143
|
+
const box = blockBox(table, {
|
|
5144
|
+
width: Math.max(1, table.columns.length) * DEFAULT_TABLE_CELL_SIZE2.width,
|
|
5145
|
+
height: Math.max(1, table.rows.length + 1) * DEFAULT_TABLE_CELL_SIZE2.height
|
|
5146
|
+
});
|
|
5147
|
+
return {
|
|
5148
|
+
...table,
|
|
5149
|
+
box,
|
|
5150
|
+
columnXOffsets: columnXOffsets(table, box)
|
|
5151
|
+
};
|
|
5152
|
+
});
|
|
5153
|
+
}
|
|
5154
|
+
function coordinateEvidencePanels(panels) {
|
|
5155
|
+
return panels.map((block) => ({
|
|
5156
|
+
...block,
|
|
5157
|
+
box: blockBox(block, {
|
|
5158
|
+
width: DEFAULT_PANEL_WIDTH,
|
|
5159
|
+
height: Math.max(1, block.items.length) * DEFAULT_PANEL_ITEM_HEIGHT2
|
|
5160
|
+
})
|
|
5161
|
+
}));
|
|
5162
|
+
}
|
|
5163
|
+
function edgeBounds(edges) {
|
|
5164
|
+
return edges.flatMap((edge) => {
|
|
5165
|
+
if (edge.points.length === 0) {
|
|
5166
|
+
return [];
|
|
5167
|
+
}
|
|
5168
|
+
const minX = Math.min(...edge.points.map((point2) => point2.x));
|
|
5169
|
+
const minY = Math.min(...edge.points.map((point2) => point2.y));
|
|
5170
|
+
const maxX = Math.max(...edge.points.map((point2) => point2.x));
|
|
5171
|
+
const maxY = Math.max(...edge.points.map((point2) => point2.y));
|
|
5172
|
+
return [
|
|
5173
|
+
{
|
|
5174
|
+
x: minX,
|
|
5175
|
+
y: minY,
|
|
5176
|
+
width: maxX - minX,
|
|
5177
|
+
height: maxY - minY
|
|
5178
|
+
}
|
|
5179
|
+
];
|
|
5180
|
+
});
|
|
5181
|
+
}
|
|
5182
|
+
function blockBox(block, defaultSize) {
|
|
5183
|
+
return {
|
|
5184
|
+
x: block.position?.x ?? 0,
|
|
5185
|
+
y: block.position?.y ?? 0,
|
|
5186
|
+
width: block.size?.width ?? defaultSize.width,
|
|
5187
|
+
height: block.size?.height ?? defaultSize.height
|
|
5188
|
+
};
|
|
5189
|
+
}
|
|
5190
|
+
function placeEvidenceBlocks(blocks, contentBounds) {
|
|
5191
|
+
let nextY = contentBounds.y;
|
|
5192
|
+
const x = contentBounds.x + contentBounds.width + DEFAULT_EVIDENCE_BLOCK_GAP;
|
|
5193
|
+
for (const block of blocks) {
|
|
5194
|
+
if (block.position !== void 0) {
|
|
5195
|
+
continue;
|
|
5196
|
+
}
|
|
5197
|
+
block.box.x = x;
|
|
5198
|
+
block.box.y = nextY;
|
|
5199
|
+
nextY += block.box.height + DEFAULT_EVIDENCE_BLOCK_GAP;
|
|
5200
|
+
}
|
|
5201
|
+
}
|
|
5202
|
+
function columnXOffsets(table, box) {
|
|
5203
|
+
if (table.columns.length === 0) {
|
|
5204
|
+
return [];
|
|
5205
|
+
}
|
|
5206
|
+
const columnWidth = box.width / table.columns.length;
|
|
5207
|
+
return table.columns.map((_, index) => box.x + index * columnWidth);
|
|
5208
|
+
}
|
|
5209
|
+
function tableCellBox2(table, columnIndex, rowIndex, rowHeight, columnCount) {
|
|
5210
|
+
const x = table.columnXOffsets[columnIndex] ?? table.box.x + table.box.width / columnCount * columnIndex;
|
|
5211
|
+
const nextX = table.columnXOffsets[columnIndex + 1] ?? table.box.x + table.box.width;
|
|
5212
|
+
return {
|
|
5213
|
+
x,
|
|
5214
|
+
y: table.box.y + rowIndex * rowHeight,
|
|
5215
|
+
width: nextX - x,
|
|
5216
|
+
height: rowHeight
|
|
5217
|
+
};
|
|
5218
|
+
}
|
|
5219
|
+
function refreshTableColumnXOffsets(tables) {
|
|
5220
|
+
for (const table of tables) {
|
|
5221
|
+
table.columnXOffsets = columnXOffsets(table, table.box);
|
|
5222
|
+
}
|
|
5223
|
+
}
|
|
5224
|
+
function measureEvidenceTextBlocks(matrices, tables, panels, textMeasurer) {
|
|
5225
|
+
const measurer = textMeasurer ?? createDefaultTextMeasurer();
|
|
5226
|
+
for (const matrix of matrices) {
|
|
5227
|
+
const geometry = matrixGeometry(matrix);
|
|
5228
|
+
matrix.columnLabelLayouts = matrix.cols.map(
|
|
5229
|
+
(column) => measureEvidenceTextLayout(column, geometry.columnHeaderBox, measurer)
|
|
5230
|
+
);
|
|
5231
|
+
matrix.rowLabelLayouts = matrix.rows.map(
|
|
5232
|
+
(row, index) => measureEvidenceTextLayout(row, geometry.rowHeaderBox(index), measurer)
|
|
5233
|
+
);
|
|
5234
|
+
matrix.cellLabelLayouts = matrix.rows.map(
|
|
5235
|
+
(_, rowIndex) => matrix.cols.map((_2, columnIndex) => {
|
|
5236
|
+
const cell2 = matrix.cells[rowIndex]?.[columnIndex] ?? { text: "" };
|
|
5237
|
+
return measureEvidenceTextLayout(
|
|
5238
|
+
cell2.text,
|
|
5239
|
+
geometry.cellBox(rowIndex, columnIndex),
|
|
5240
|
+
measurer
|
|
5241
|
+
);
|
|
5242
|
+
})
|
|
5243
|
+
);
|
|
5244
|
+
}
|
|
5245
|
+
for (const table of tables) {
|
|
5246
|
+
const rowHeight = table.box.height / Math.max(1, table.rows.length + 1);
|
|
5247
|
+
const columnCount = Math.max(1, table.columns.length);
|
|
5248
|
+
table.columnLabelLayouts = table.columns.map(
|
|
5249
|
+
(column, columnIndex) => measureEvidenceTextLayout(
|
|
5250
|
+
column.label.text,
|
|
5251
|
+
tableCellBox2(table, columnIndex, 0, rowHeight, columnCount),
|
|
5252
|
+
measurer
|
|
5253
|
+
)
|
|
5254
|
+
);
|
|
5255
|
+
table.cellLabelLayouts = table.rows.map(
|
|
5256
|
+
(row, rowIndex) => table.columns.map((column, columnIndex) => {
|
|
5257
|
+
const cell2 = row.cells[column.id] ?? { text: "" };
|
|
5258
|
+
return measureEvidenceTextLayout(
|
|
5259
|
+
cell2.text,
|
|
5260
|
+
tableCellBox2(
|
|
5261
|
+
table,
|
|
5262
|
+
columnIndex,
|
|
5263
|
+
rowIndex + 1,
|
|
5264
|
+
rowHeight,
|
|
5265
|
+
columnCount
|
|
5266
|
+
),
|
|
5267
|
+
measurer
|
|
5268
|
+
);
|
|
5269
|
+
})
|
|
5270
|
+
);
|
|
5271
|
+
}
|
|
5272
|
+
for (const panel of panels) {
|
|
5273
|
+
const geometry = panelGeometry(panel);
|
|
5274
|
+
panel.titleLayout = measureEvidenceTextLayout(
|
|
5275
|
+
`${panel.kind}: ${panel.id}`,
|
|
5276
|
+
geometry.titleBox,
|
|
5277
|
+
measurer
|
|
5278
|
+
);
|
|
5279
|
+
panel.itemLayouts = panel.items.map(
|
|
5280
|
+
(item, index) => measureEvidenceTextLayout(
|
|
5281
|
+
panelItemText2(item.label.text, item.detail?.text),
|
|
5282
|
+
geometry.itemRowBox(index),
|
|
5283
|
+
measurer
|
|
5284
|
+
)
|
|
5285
|
+
);
|
|
5286
|
+
}
|
|
5287
|
+
}
|
|
5288
|
+
function measureEvidenceTextLayout(text, box, textMeasurer) {
|
|
5289
|
+
const lineHeight = EVIDENCE_TEXT_FONT.lineHeight;
|
|
5290
|
+
return {
|
|
5291
|
+
lines: wrapEvidenceText(text, {
|
|
5292
|
+
maxWidth: Math.max(0, box.width - 8),
|
|
5293
|
+
maxLines: Math.max(1, Math.floor((box.height - 4) / lineHeight)),
|
|
5294
|
+
textMeasurer
|
|
5295
|
+
})
|
|
5296
|
+
};
|
|
5297
|
+
}
|
|
5298
|
+
function wrapEvidenceText(text, options) {
|
|
5299
|
+
const normalized = text.trim().replace(/\s+/g, " ");
|
|
5300
|
+
if (normalized.length === 0) {
|
|
5301
|
+
return [""];
|
|
5302
|
+
}
|
|
5303
|
+
const lines = [];
|
|
5304
|
+
let current = "";
|
|
5305
|
+
let overflow = false;
|
|
5306
|
+
for (const word of normalized.split(" ")) {
|
|
5307
|
+
const chunks = chunkEvidenceWord(
|
|
5308
|
+
word,
|
|
5309
|
+
options.maxWidth,
|
|
5310
|
+
options.textMeasurer
|
|
5311
|
+
);
|
|
5312
|
+
for (const chunk of chunks) {
|
|
5313
|
+
const candidate = current.length === 0 ? chunk : `${current} ${chunk}`;
|
|
5314
|
+
if (measureEvidenceText(candidate, options.textMeasurer) <= options.maxWidth) {
|
|
5315
|
+
current = candidate;
|
|
5316
|
+
continue;
|
|
5317
|
+
}
|
|
5318
|
+
if (current.length > 0) {
|
|
5319
|
+
lines.push(current);
|
|
5320
|
+
current = chunk;
|
|
5321
|
+
} else {
|
|
5322
|
+
lines.push(chunk);
|
|
5323
|
+
current = "";
|
|
5324
|
+
}
|
|
5325
|
+
if (lines.length >= options.maxLines) {
|
|
5326
|
+
overflow = true;
|
|
5327
|
+
break;
|
|
5328
|
+
}
|
|
5329
|
+
}
|
|
5330
|
+
if (overflow) {
|
|
5331
|
+
break;
|
|
5332
|
+
}
|
|
5333
|
+
}
|
|
5334
|
+
if (!overflow && current.length > 0) {
|
|
5335
|
+
lines.push(current);
|
|
5336
|
+
}
|
|
5337
|
+
if (lines.length > options.maxLines) {
|
|
5338
|
+
overflow = true;
|
|
5339
|
+
lines.length = options.maxLines;
|
|
5340
|
+
}
|
|
5341
|
+
if (overflow || lines.length === options.maxLines) {
|
|
5342
|
+
const rendered = lines.join(" ");
|
|
5343
|
+
if (rendered.length < normalized.length) {
|
|
5344
|
+
lines[lines.length - 1] = ellipsizeMeasuredEvidenceLine(
|
|
5345
|
+
lines[lines.length - 1] ?? "",
|
|
5346
|
+
options.maxWidth,
|
|
5347
|
+
options.textMeasurer
|
|
5348
|
+
);
|
|
5349
|
+
}
|
|
5350
|
+
}
|
|
5351
|
+
return lines.length === 0 ? [""] : lines;
|
|
5352
|
+
}
|
|
5353
|
+
function chunkEvidenceWord(word, maxWidth, textMeasurer) {
|
|
5354
|
+
if (measureEvidenceText(word, textMeasurer) <= maxWidth) {
|
|
5355
|
+
return [word];
|
|
5356
|
+
}
|
|
5357
|
+
const chunks = [];
|
|
5358
|
+
let current = "";
|
|
5359
|
+
for (const char of Array.from(word)) {
|
|
5360
|
+
const candidate = `${current}${char}`;
|
|
5361
|
+
if (current.length > 0 && measureEvidenceText(candidate, textMeasurer) > maxWidth) {
|
|
5362
|
+
chunks.push(current);
|
|
5363
|
+
current = char;
|
|
5364
|
+
continue;
|
|
5365
|
+
}
|
|
5366
|
+
current = candidate;
|
|
5367
|
+
}
|
|
5368
|
+
if (current.length > 0) {
|
|
5369
|
+
chunks.push(current);
|
|
5370
|
+
}
|
|
5371
|
+
return chunks.length === 0 ? [word] : chunks;
|
|
5372
|
+
}
|
|
5373
|
+
function ellipsizeMeasuredEvidenceLine(line, maxWidth, textMeasurer) {
|
|
5374
|
+
const ellipsis = "...";
|
|
5375
|
+
if (measureEvidenceText(ellipsis, textMeasurer) > maxWidth) {
|
|
5376
|
+
return "";
|
|
5377
|
+
}
|
|
5378
|
+
let candidate = line.trimEnd();
|
|
5379
|
+
while (candidate.length > 0 && measureEvidenceText(`${candidate}${ellipsis}`, textMeasurer) > maxWidth) {
|
|
5380
|
+
candidate = Array.from(candidate).slice(0, -1).join("").trimEnd();
|
|
5381
|
+
}
|
|
5382
|
+
return `${candidate}${ellipsis}`;
|
|
5383
|
+
}
|
|
5384
|
+
function measureEvidenceText(text, textMeasurer) {
|
|
5385
|
+
return textMeasurer.naturalWidth(
|
|
5386
|
+
textMeasurer.prepare(text, EVIDENCE_TEXT_FONT)
|
|
5387
|
+
);
|
|
5388
|
+
}
|
|
5389
|
+
function matrixGeometry(matrix) {
|
|
5390
|
+
const columnCount = Math.max(1, matrix.cols.length);
|
|
5391
|
+
const rowCount = matrix.rows.length;
|
|
5392
|
+
const rowHeaderWidth = rowCount > 0 ? Math.min(96, matrix.box.width * 0.28) : 0;
|
|
5393
|
+
const dataWidth = Math.max(0, matrix.box.width - rowHeaderWidth);
|
|
5394
|
+
const cellWidth = dataWidth / columnCount;
|
|
5395
|
+
const rowHeight = matrix.box.height / Math.max(1, rowCount + 1);
|
|
5396
|
+
return {
|
|
5397
|
+
rowHeaderWidth,
|
|
5398
|
+
cellWidth,
|
|
5399
|
+
rowHeight,
|
|
5400
|
+
columnHeaderBox: {
|
|
5401
|
+
x: matrix.box.x + rowHeaderWidth,
|
|
5402
|
+
y: matrix.box.y,
|
|
5403
|
+
width: cellWidth,
|
|
5404
|
+
height: rowHeight
|
|
5405
|
+
},
|
|
5406
|
+
rowHeaderBox: (rowIndex) => ({
|
|
5407
|
+
x: matrix.box.x,
|
|
5408
|
+
y: matrix.box.y + (rowIndex + 1) * rowHeight,
|
|
5409
|
+
width: rowHeaderWidth,
|
|
5410
|
+
height: rowHeight
|
|
5411
|
+
}),
|
|
5412
|
+
cellBox: (rowIndex, columnIndex) => ({
|
|
5413
|
+
x: matrix.box.x + rowHeaderWidth + columnIndex * cellWidth,
|
|
5414
|
+
y: matrix.box.y + (rowIndex + 1) * rowHeight,
|
|
5415
|
+
width: cellWidth,
|
|
5416
|
+
height: rowHeight
|
|
5417
|
+
})
|
|
5418
|
+
};
|
|
5419
|
+
}
|
|
5420
|
+
function panelGeometry(panel) {
|
|
5421
|
+
const titleWidth = Math.min(panel.box.width * 0.36, 140);
|
|
5422
|
+
const itemBox = {
|
|
5423
|
+
x: panel.box.x + titleWidth,
|
|
5424
|
+
y: panel.box.y,
|
|
5425
|
+
width: panel.box.width - titleWidth,
|
|
5426
|
+
height: panel.box.height
|
|
5427
|
+
};
|
|
5428
|
+
const itemHeight = panel.box.height / Math.max(1, panel.items.length);
|
|
5429
|
+
return {
|
|
5430
|
+
titleBox: {
|
|
5431
|
+
x: panel.box.x,
|
|
5432
|
+
y: panel.box.y,
|
|
5433
|
+
width: titleWidth,
|
|
5434
|
+
height: panel.box.height
|
|
5435
|
+
},
|
|
5436
|
+
itemRowBox: (index) => ({
|
|
5437
|
+
x: itemBox.x,
|
|
5438
|
+
y: itemBox.y + index * itemHeight,
|
|
5439
|
+
width: itemBox.width,
|
|
5440
|
+
height: itemHeight
|
|
5441
|
+
})
|
|
5442
|
+
};
|
|
5443
|
+
}
|
|
5444
|
+
function panelItemText2(label, detail) {
|
|
5445
|
+
return detail === void 0 ? label : `${label}: ${detail}`;
|
|
5446
|
+
}
|
|
5447
|
+
function reportEvidenceBlockOverlaps(evidenceBlocks, contentBlocks) {
|
|
5448
|
+
const diagnostics = [];
|
|
5449
|
+
for (let index = 0; index < evidenceBlocks.length; index += 1) {
|
|
5450
|
+
const block = evidenceBlocks[index];
|
|
5451
|
+
if (block === void 0 || block.position === void 0) {
|
|
5452
|
+
continue;
|
|
5453
|
+
}
|
|
5454
|
+
for (const content of contentBlocks) {
|
|
5455
|
+
if (intersectsAabb(block.box, content.box)) {
|
|
5456
|
+
diagnostics.push(evidenceOverlapDiagnostic(block, content));
|
|
5457
|
+
}
|
|
5458
|
+
}
|
|
5459
|
+
for (let otherIndex = 0; otherIndex < evidenceBlocks.length; otherIndex += 1) {
|
|
5460
|
+
if (otherIndex === index) {
|
|
5461
|
+
continue;
|
|
5462
|
+
}
|
|
5463
|
+
const other = evidenceBlocks[otherIndex];
|
|
5464
|
+
if (other === void 0 || other.position !== void 0 && otherIndex < index || !intersectsAabb(block.box, other.box)) {
|
|
5465
|
+
continue;
|
|
5466
|
+
}
|
|
5467
|
+
diagnostics.push(evidenceOverlapDiagnostic(block, other));
|
|
5468
|
+
}
|
|
5469
|
+
}
|
|
5470
|
+
return diagnostics;
|
|
5471
|
+
}
|
|
5472
|
+
function evidenceOverlapDiagnostic(block, conflict) {
|
|
5473
|
+
return {
|
|
5474
|
+
severity: "warning",
|
|
5475
|
+
code: "constraints.overlap.unresolved",
|
|
5476
|
+
message: `Evidence block ${block.id} overlaps ${conflict.kind} ${conflict.id}.`,
|
|
5477
|
+
path: ["evidence", block.id],
|
|
5478
|
+
detail: {
|
|
5479
|
+
evidenceBlockId: block.id,
|
|
5480
|
+
evidenceBlockKind: block.kind,
|
|
5481
|
+
conflictingObjectId: conflict.id,
|
|
5482
|
+
conflictingObjectKind: conflict.kind
|
|
5483
|
+
}
|
|
5484
|
+
};
|
|
5485
|
+
}
|
|
5486
|
+
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics) {
|
|
5487
|
+
const coordinated = [];
|
|
5488
|
+
const coordinatedNodeById = new Map(
|
|
5489
|
+
coordinatedNodes.map((node) => [node.id, node])
|
|
5490
|
+
);
|
|
5491
|
+
for (const edge of edges) {
|
|
5492
|
+
const source = nodes.get(edge.source.nodeId);
|
|
5493
|
+
const target = nodes.get(edge.target.nodeId);
|
|
5494
|
+
if (source === void 0 || target === void 0) {
|
|
5495
|
+
diagnostics.push({
|
|
5496
|
+
severity: "error",
|
|
5497
|
+
code: "solver.edge-reference.missing",
|
|
5498
|
+
message: `Edge ${edge.id} references a missing coordinated node.`,
|
|
5499
|
+
path: ["edges", edge.id],
|
|
5500
|
+
detail: {
|
|
5501
|
+
edgeId: edge.id,
|
|
5502
|
+
sourceId: edge.source.nodeId,
|
|
3951
5503
|
targetId: edge.target.nodeId
|
|
3952
5504
|
}
|
|
3953
5505
|
});
|
|
@@ -3955,6 +5507,8 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, direction, o
|
|
|
3955
5507
|
}
|
|
3956
5508
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
3957
5509
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
5510
|
+
const connectedTextOwners = edgeConnectedTextOwnerIds(edge);
|
|
5511
|
+
const routeTextObstacles = textObstacles.filter((annotation) => !connectedTextOwners.has(annotation.ownerId)).map((annotation) => annotation.box);
|
|
3958
5512
|
const route = routeEdge({
|
|
3959
5513
|
kind: options.routeKind ?? "orthogonal",
|
|
3960
5514
|
direction,
|
|
@@ -3962,9 +5516,14 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, direction, o
|
|
|
3962
5516
|
target: portGeometry(target, targetPort),
|
|
3963
5517
|
...edge.source.anchor === void 0 ? {} : { sourceAnchor: edge.source.anchor },
|
|
3964
5518
|
...edge.target.anchor === void 0 ? {} : { targetAnchor: edge.target.anchor },
|
|
3965
|
-
obstacles:
|
|
3966
|
-
(
|
|
3967
|
-
|
|
5519
|
+
obstacles: [
|
|
5520
|
+
...obstacles.filter(
|
|
5521
|
+
(obstacle) => obstacle !== source.obstacleBox && obstacle !== target.obstacleBox
|
|
5522
|
+
),
|
|
5523
|
+
...softObstacles,
|
|
5524
|
+
...routeTextObstacles
|
|
5525
|
+
],
|
|
5526
|
+
hardObstacles
|
|
3968
5527
|
});
|
|
3969
5528
|
diagnostics.push(
|
|
3970
5529
|
...route.diagnostics.map((diagnostic) => ({
|
|
@@ -3979,6 +5538,550 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, direction, o
|
|
|
3979
5538
|
}
|
|
3980
5539
|
return coordinated;
|
|
3981
5540
|
}
|
|
5541
|
+
function edgeConnectedTextOwnerIds(edge) {
|
|
5542
|
+
const owners = /* @__PURE__ */ new Set();
|
|
5543
|
+
if (edge.source.portId !== void 0) {
|
|
5544
|
+
owners.add(`${edge.source.nodeId}.${edge.source.portId}`);
|
|
5545
|
+
}
|
|
5546
|
+
if (edge.target.portId !== void 0) {
|
|
5547
|
+
owners.add(`${edge.target.nodeId}.${edge.target.portId}`);
|
|
5548
|
+
}
|
|
5549
|
+
return owners;
|
|
5550
|
+
}
|
|
5551
|
+
function coordinateBaseTextAnnotations(input) {
|
|
5552
|
+
const measurer = input.textMeasurer ?? createDefaultTextMeasurer();
|
|
5553
|
+
const annotations = [];
|
|
5554
|
+
for (const node of input.nodes) {
|
|
5555
|
+
if (node.compartments !== void 0) {
|
|
5556
|
+
continue;
|
|
5557
|
+
}
|
|
5558
|
+
if (node.labelLayout === void 0 && node.label === void 0) {
|
|
5559
|
+
continue;
|
|
5560
|
+
}
|
|
5561
|
+
const layout2 = node.labelLayout ?? fallbackLabelLayout(node.label?.text ?? "");
|
|
5562
|
+
const buildAnnotation = node.labelLayout === void 0 ? buildAnchorCenteredTextAnnotation : buildTextAnnotation;
|
|
5563
|
+
annotations.push(
|
|
5564
|
+
buildAnnotation({
|
|
5565
|
+
ownerId: node.id,
|
|
5566
|
+
surfaceKind: "node-label",
|
|
5567
|
+
layout: layout2,
|
|
5568
|
+
anchor: node.box
|
|
5569
|
+
})
|
|
5570
|
+
);
|
|
5571
|
+
}
|
|
5572
|
+
for (const group of input.groups) {
|
|
5573
|
+
if (group.labelLayout === void 0 && group.label === void 0) {
|
|
5574
|
+
continue;
|
|
5575
|
+
}
|
|
5576
|
+
const layout2 = group.labelLayout ?? fallbackLabelLayout(group.label?.text ?? "");
|
|
5577
|
+
const buildAnnotation = group.labelLayout === void 0 ? buildAnchorCenteredTextAnnotation : buildTextAnnotation;
|
|
5578
|
+
annotations.push(
|
|
5579
|
+
buildAnnotation({
|
|
5580
|
+
ownerId: group.id,
|
|
5581
|
+
surfaceKind: "group-label",
|
|
5582
|
+
layout: layout2,
|
|
5583
|
+
anchor: group.box
|
|
5584
|
+
})
|
|
5585
|
+
);
|
|
5586
|
+
}
|
|
5587
|
+
for (const node of input.nodes) {
|
|
5588
|
+
for (const port of node.ports ?? []) {
|
|
5589
|
+
if (port.label?.text === void 0) {
|
|
5590
|
+
continue;
|
|
5591
|
+
}
|
|
5592
|
+
const layout2 = fitLabel(
|
|
5593
|
+
port.label.text,
|
|
5594
|
+
{
|
|
5595
|
+
font: { fontFamily: "Arial", fontSize: 10, lineHeight: 12 },
|
|
5596
|
+
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
5597
|
+
minSize: { width: 0, height: 0 },
|
|
5598
|
+
maxWidth: 160
|
|
5599
|
+
},
|
|
5600
|
+
measurer
|
|
5601
|
+
);
|
|
5602
|
+
annotations.push(
|
|
5603
|
+
buildTextAnnotation({
|
|
5604
|
+
ownerId: `${node.id}.${port.id}`,
|
|
5605
|
+
surfaceKind: "port-label",
|
|
5606
|
+
layout: layout2,
|
|
5607
|
+
anchor: portLabelBox(port)
|
|
5608
|
+
})
|
|
5609
|
+
);
|
|
5610
|
+
}
|
|
5611
|
+
}
|
|
5612
|
+
for (const node of input.nodes) {
|
|
5613
|
+
if (node.compartments === void 0) {
|
|
5614
|
+
continue;
|
|
5615
|
+
}
|
|
5616
|
+
const rows = compartmentRows2(node);
|
|
5617
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
5618
|
+
const row = rows[index];
|
|
5619
|
+
if (row === void 0) {
|
|
5620
|
+
continue;
|
|
5621
|
+
}
|
|
5622
|
+
const layout2 = fitLabel(
|
|
5623
|
+
row,
|
|
5624
|
+
{
|
|
5625
|
+
font: { fontFamily: "Arial", fontSize: 11, lineHeight: 13 },
|
|
5626
|
+
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
5627
|
+
minSize: { width: 0, height: 0 },
|
|
5628
|
+
maxWidth: node.box.width
|
|
5629
|
+
},
|
|
5630
|
+
measurer
|
|
5631
|
+
);
|
|
5632
|
+
annotations.push(
|
|
5633
|
+
buildAnchorCenteredTextAnnotation({
|
|
5634
|
+
ownerId: node.id,
|
|
5635
|
+
surfaceKind: "compartment-row",
|
|
5636
|
+
surfaceIndex: index,
|
|
5637
|
+
layout: layout2,
|
|
5638
|
+
anchor: {
|
|
5639
|
+
x: node.box.x,
|
|
5640
|
+
y: node.box.y + 18 + index * 16,
|
|
5641
|
+
width: node.box.width,
|
|
5642
|
+
height: 16
|
|
5643
|
+
}
|
|
5644
|
+
})
|
|
5645
|
+
);
|
|
5646
|
+
}
|
|
5647
|
+
}
|
|
5648
|
+
for (const swimlane of input.swimlanes) {
|
|
5649
|
+
for (const lane of swimlane.lanes) {
|
|
5650
|
+
if (lane.label?.text === void 0 || lane.box === void 0) {
|
|
5651
|
+
continue;
|
|
5652
|
+
}
|
|
5653
|
+
const labelBox = lane.headerBox ?? lane.box;
|
|
5654
|
+
const layout2 = fitLabel(
|
|
5655
|
+
lane.label.text,
|
|
5656
|
+
{
|
|
5657
|
+
font: { fontFamily: "Arial", fontSize: 12, lineHeight: 14 },
|
|
5658
|
+
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
5659
|
+
minSize: { width: 0, height: 0 },
|
|
5660
|
+
maxWidth: swimlane.orientation === "horizontal" ? labelBox.height : labelBox.width
|
|
5661
|
+
},
|
|
5662
|
+
measurer
|
|
5663
|
+
);
|
|
5664
|
+
annotations.push(
|
|
5665
|
+
buildAnchorCenteredTextAnnotation({
|
|
5666
|
+
ownerId: `${swimlane.id}.${lane.id}`,
|
|
5667
|
+
surfaceKind: "swimlane-label",
|
|
5668
|
+
layout: layout2,
|
|
5669
|
+
anchor: labelBox
|
|
5670
|
+
})
|
|
5671
|
+
);
|
|
5672
|
+
}
|
|
5673
|
+
}
|
|
5674
|
+
return annotations;
|
|
5675
|
+
}
|
|
5676
|
+
function coordinateEdgeTextAnnotations(edges, textMeasurer) {
|
|
5677
|
+
const measurer = textMeasurer ?? createDefaultTextMeasurer();
|
|
5678
|
+
const annotations = [];
|
|
5679
|
+
for (const edge of edges) {
|
|
5680
|
+
if (edge.label?.text === void 0) {
|
|
5681
|
+
continue;
|
|
5682
|
+
}
|
|
5683
|
+
const layout2 = fitLabel(
|
|
5684
|
+
edge.label.text,
|
|
5685
|
+
{
|
|
5686
|
+
font: { fontFamily: "Arial", fontSize: 12, lineHeight: 14 },
|
|
5687
|
+
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
5688
|
+
minSize: { width: 0, height: 0 },
|
|
5689
|
+
maxWidth: 200
|
|
5690
|
+
},
|
|
5691
|
+
measurer
|
|
5692
|
+
);
|
|
5693
|
+
annotations.push(
|
|
5694
|
+
buildCenteredTextAnnotation({
|
|
5695
|
+
ownerId: edge.id,
|
|
5696
|
+
surfaceKind: "edge-label",
|
|
5697
|
+
layout: layout2,
|
|
5698
|
+
center: edgeLabelAnchor(edge, layout2, edges)
|
|
5699
|
+
})
|
|
5700
|
+
);
|
|
5701
|
+
}
|
|
5702
|
+
return annotations;
|
|
5703
|
+
}
|
|
5704
|
+
function coordinateFrameTextAnnotation(frame, textMeasurer) {
|
|
5705
|
+
const layout2 = fitLabel(
|
|
5706
|
+
frame.titleTab,
|
|
5707
|
+
{
|
|
5708
|
+
font: { fontFamily: "Arial", fontSize: 12, lineHeight: 14 },
|
|
5709
|
+
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
5710
|
+
minSize: { width: 0, height: 0 },
|
|
5711
|
+
maxWidth: frame.titleBox.width
|
|
5712
|
+
},
|
|
5713
|
+
textMeasurer ?? createDefaultTextMeasurer()
|
|
5714
|
+
);
|
|
5715
|
+
return buildAnchorCenteredTextAnnotation({
|
|
5716
|
+
ownerId: frame.kind,
|
|
5717
|
+
surfaceKind: "frame-title",
|
|
5718
|
+
layout: layout2,
|
|
5719
|
+
anchor: frame.titleBox
|
|
5720
|
+
});
|
|
5721
|
+
}
|
|
5722
|
+
function buildTextAnnotation(input) {
|
|
5723
|
+
return {
|
|
5724
|
+
text: input.layout.text,
|
|
5725
|
+
ownerId: input.ownerId,
|
|
5726
|
+
surfaceKind: input.surfaceKind,
|
|
5727
|
+
...input.surfaceIndex === void 0 ? {} : { surfaceIndex: input.surfaceIndex },
|
|
5728
|
+
box: {
|
|
5729
|
+
x: input.anchor.x + input.layout.box.x,
|
|
5730
|
+
y: input.anchor.y + input.layout.box.y,
|
|
5731
|
+
width: input.layout.box.width,
|
|
5732
|
+
height: input.layout.box.height
|
|
5733
|
+
},
|
|
5734
|
+
anchor: input.anchor,
|
|
5735
|
+
paddings: input.layout.padding,
|
|
5736
|
+
lines: input.layout.lines,
|
|
5737
|
+
fontSize: input.layout.font.fontSize,
|
|
5738
|
+
textBackend: input.layout.textBackend
|
|
5739
|
+
};
|
|
5740
|
+
}
|
|
5741
|
+
function buildAnchorCenteredTextAnnotation(input) {
|
|
5742
|
+
return buildCenteredTextAnnotation({
|
|
5743
|
+
ownerId: input.ownerId,
|
|
5744
|
+
surfaceKind: input.surfaceKind,
|
|
5745
|
+
...input.surfaceIndex === void 0 ? {} : { surfaceIndex: input.surfaceIndex },
|
|
5746
|
+
layout: input.layout,
|
|
5747
|
+
center: {
|
|
5748
|
+
x: input.anchor.x + input.anchor.width / 2,
|
|
5749
|
+
y: input.anchor.y + input.anchor.height / 2
|
|
5750
|
+
},
|
|
5751
|
+
anchor: input.anchor
|
|
5752
|
+
});
|
|
5753
|
+
}
|
|
5754
|
+
function buildCenteredTextAnnotation(input) {
|
|
5755
|
+
return {
|
|
5756
|
+
text: input.layout.text,
|
|
5757
|
+
ownerId: input.ownerId,
|
|
5758
|
+
surfaceKind: input.surfaceKind,
|
|
5759
|
+
...input.surfaceIndex === void 0 ? {} : { surfaceIndex: input.surfaceIndex },
|
|
5760
|
+
box: {
|
|
5761
|
+
x: input.center.x - input.layout.box.width / 2,
|
|
5762
|
+
y: input.center.y - input.layout.box.height / 2,
|
|
5763
|
+
width: input.layout.box.width,
|
|
5764
|
+
height: input.layout.box.height
|
|
5765
|
+
},
|
|
5766
|
+
anchor: input.anchor ?? input.center,
|
|
5767
|
+
paddings: input.layout.padding,
|
|
5768
|
+
lines: input.layout.lines,
|
|
5769
|
+
fontSize: input.layout.font.fontSize,
|
|
5770
|
+
textBackend: input.layout.textBackend
|
|
5771
|
+
};
|
|
5772
|
+
}
|
|
5773
|
+
function reportTextAnnotationCollisions(annotations) {
|
|
5774
|
+
const diagnostics = [];
|
|
5775
|
+
const relevantAnnotations = annotations.filter(
|
|
5776
|
+
(annotation) => isExternallyPlacedText(annotation.surfaceKind)
|
|
5777
|
+
);
|
|
5778
|
+
for (let annotationIndex = 0; annotationIndex < relevantAnnotations.length; annotationIndex += 1) {
|
|
5779
|
+
const annotation = relevantAnnotations[annotationIndex];
|
|
5780
|
+
if (annotation === void 0) {
|
|
5781
|
+
continue;
|
|
5782
|
+
}
|
|
5783
|
+
for (let otherIndex = annotationIndex + 1; otherIndex < relevantAnnotations.length; otherIndex += 1) {
|
|
5784
|
+
const other = relevantAnnotations[otherIndex];
|
|
5785
|
+
if (other === void 0) {
|
|
5786
|
+
continue;
|
|
5787
|
+
}
|
|
5788
|
+
if (!intersectsAabb(annotation.box, other.box)) {
|
|
5789
|
+
continue;
|
|
5790
|
+
}
|
|
5791
|
+
if (annotation.ownerId === other.ownerId && annotation.surfaceKind === other.surfaceKind) {
|
|
5792
|
+
continue;
|
|
5793
|
+
}
|
|
5794
|
+
diagnostics.push({
|
|
5795
|
+
severity: "warning",
|
|
5796
|
+
code: "constraints.overlap.unresolved",
|
|
5797
|
+
message: `Text surface ${annotation.surfaceKind} for ${annotation.ownerId} overlaps text surface ${other.surfaceKind} for ${other.ownerId}.`,
|
|
5798
|
+
path: ["textAnnotations", annotation.surfaceKind, annotation.ownerId],
|
|
5799
|
+
detail: compactDetail({
|
|
5800
|
+
textSurfaceKind: annotation.surfaceKind,
|
|
5801
|
+
ownerId: annotation.ownerId,
|
|
5802
|
+
conflictingObjectId: other.ownerId,
|
|
5803
|
+
conflictingObjectKind: other.surfaceKind,
|
|
5804
|
+
surfaceIndex: annotation.surfaceIndex,
|
|
5805
|
+
otherSurfaceKind: other.surfaceKind,
|
|
5806
|
+
otherSurfaceIndex: other.surfaceIndex,
|
|
5807
|
+
textBackend: annotation.textBackend
|
|
5808
|
+
})
|
|
5809
|
+
});
|
|
5810
|
+
}
|
|
5811
|
+
}
|
|
5812
|
+
return diagnostics;
|
|
5813
|
+
}
|
|
5814
|
+
function reportRouteTextClearance(edges, annotations) {
|
|
5815
|
+
const diagnostics = [];
|
|
5816
|
+
const relevantAnnotations = annotations.filter(isRouteClearanceText);
|
|
5817
|
+
for (const edge of edges) {
|
|
5818
|
+
const connectedTextOwners = edgeConnectedTextOwnerIds(edge);
|
|
5819
|
+
for (const annotation of relevantAnnotations) {
|
|
5820
|
+
if (annotation.ownerId === edge.id || connectedTextOwners.has(annotation.ownerId)) {
|
|
5821
|
+
continue;
|
|
5822
|
+
}
|
|
5823
|
+
if (!routeIntersectsTextBox(edge.points, annotation.box)) {
|
|
5824
|
+
continue;
|
|
5825
|
+
}
|
|
5826
|
+
diagnostics.push({
|
|
5827
|
+
severity: "warning",
|
|
5828
|
+
code: "routing.text-clearance.unresolved",
|
|
5829
|
+
message: `Edge ${edge.id} intersects solved text surface ${annotation.surfaceKind} for ${annotation.ownerId}.`,
|
|
5830
|
+
path: ["edges", edge.id],
|
|
5831
|
+
detail: compactDetail({
|
|
5832
|
+
edgeId: edge.id,
|
|
5833
|
+
textSurfaceKind: annotation.surfaceKind,
|
|
5834
|
+
conflictingObjectId: annotation.ownerId,
|
|
5835
|
+
surfaceIndex: annotation.surfaceIndex,
|
|
5836
|
+
textBackend: annotation.textBackend
|
|
5837
|
+
})
|
|
5838
|
+
});
|
|
5839
|
+
}
|
|
5840
|
+
}
|
|
5841
|
+
return diagnostics;
|
|
5842
|
+
}
|
|
5843
|
+
function isPreRouteTextObstacle(annotation) {
|
|
5844
|
+
if (annotation.surfaceKind === "edge-label") {
|
|
5845
|
+
return false;
|
|
5846
|
+
}
|
|
5847
|
+
return isRouteClearanceText(annotation);
|
|
5848
|
+
}
|
|
5849
|
+
function isRouteClearanceText(annotation) {
|
|
5850
|
+
switch (annotation.surfaceKind) {
|
|
5851
|
+
case "port-label":
|
|
5852
|
+
case "edge-label":
|
|
5853
|
+
case "swimlane-label":
|
|
5854
|
+
case "frame-title":
|
|
5855
|
+
return true;
|
|
5856
|
+
case "node-label":
|
|
5857
|
+
case "group-label":
|
|
5858
|
+
case "compartment-row":
|
|
5859
|
+
return textExtendsOutsideAnchor(annotation);
|
|
5860
|
+
}
|
|
5861
|
+
}
|
|
5862
|
+
function textExtendsOutsideAnchor(annotation) {
|
|
5863
|
+
if (!("width" in annotation.anchor)) {
|
|
5864
|
+
return true;
|
|
5865
|
+
}
|
|
5866
|
+
const epsilon = 1e-3;
|
|
5867
|
+
return annotation.box.x < annotation.anchor.x - epsilon || annotation.box.y < annotation.anchor.y - epsilon || annotation.box.x + annotation.box.width > annotation.anchor.x + annotation.anchor.width + epsilon || annotation.box.y + annotation.box.height > annotation.anchor.y + annotation.anchor.height + epsilon;
|
|
5868
|
+
}
|
|
5869
|
+
function routeIntersectsTextBox(points, box) {
|
|
5870
|
+
for (let index = 0; index < points.length - 1; index += 1) {
|
|
5871
|
+
const start = points[index];
|
|
5872
|
+
const end = points[index + 1];
|
|
5873
|
+
if (start === void 0 || end === void 0) {
|
|
5874
|
+
continue;
|
|
5875
|
+
}
|
|
5876
|
+
if (segmentIntersectsBox2(start, end, box)) {
|
|
5877
|
+
return true;
|
|
5878
|
+
}
|
|
5879
|
+
}
|
|
5880
|
+
return false;
|
|
5881
|
+
}
|
|
5882
|
+
function segmentIntersectsBox2(start, end, box) {
|
|
5883
|
+
const left = box.x;
|
|
5884
|
+
const right = box.x + box.width;
|
|
5885
|
+
const top = box.y;
|
|
5886
|
+
const bottom = box.y + box.height;
|
|
5887
|
+
if (pointInsideBox2(start, box) || pointInsideBox2(end, box)) {
|
|
5888
|
+
return true;
|
|
5889
|
+
}
|
|
5890
|
+
if (start.x === end.x) {
|
|
5891
|
+
return start.x > left && start.x < right && rangesOverlap2(start.y, end.y, top, bottom);
|
|
5892
|
+
}
|
|
5893
|
+
if (start.y === end.y) {
|
|
5894
|
+
return start.y > top && start.y < bottom && rangesOverlap2(start.x, end.x, left, right);
|
|
5895
|
+
}
|
|
5896
|
+
return segmentIntersectsBoxEdge2(start, end, left, top, right, top) || segmentIntersectsBoxEdge2(start, end, right, top, right, bottom) || segmentIntersectsBoxEdge2(start, end, right, bottom, left, bottom) || segmentIntersectsBoxEdge2(start, end, left, bottom, left, top);
|
|
5897
|
+
}
|
|
5898
|
+
function pointInsideBox2(point2, box) {
|
|
5899
|
+
return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
|
|
5900
|
+
}
|
|
5901
|
+
function rangesOverlap2(a, b, min, max) {
|
|
5902
|
+
const low = Math.min(a, b);
|
|
5903
|
+
const high = Math.max(a, b);
|
|
5904
|
+
return high > min && low < max;
|
|
5905
|
+
}
|
|
5906
|
+
function segmentIntersectsBoxEdge2(start, end, x1, y1, x2, y2) {
|
|
5907
|
+
const denominator = (end.x - start.x) * (y2 - y1) - (end.y - start.y) * (x2 - x1);
|
|
5908
|
+
if (denominator === 0) {
|
|
5909
|
+
return false;
|
|
5910
|
+
}
|
|
5911
|
+
const t = ((x1 - start.x) * (y2 - y1) - (y1 - start.y) * (x2 - x1)) / denominator;
|
|
5912
|
+
const u = ((x1 - start.x) * (end.y - start.y) - (y1 - start.y) * (end.x - start.x)) / denominator;
|
|
5913
|
+
return t > 0 && t < 1 && u > 0 && u < 1;
|
|
5914
|
+
}
|
|
5915
|
+
function compactDetail(detail) {
|
|
5916
|
+
return Object.fromEntries(
|
|
5917
|
+
Object.entries(detail).filter(
|
|
5918
|
+
(entry) => entry[1] !== void 0
|
|
5919
|
+
)
|
|
5920
|
+
);
|
|
5921
|
+
}
|
|
5922
|
+
function isExternallyPlacedText(surfaceKind) {
|
|
5923
|
+
switch (surfaceKind) {
|
|
5924
|
+
case "port-label":
|
|
5925
|
+
return true;
|
|
5926
|
+
case "edge-label":
|
|
5927
|
+
return false;
|
|
5928
|
+
case "swimlane-label":
|
|
5929
|
+
return true;
|
|
5930
|
+
case "frame-title":
|
|
5931
|
+
return true;
|
|
5932
|
+
case "node-label":
|
|
5933
|
+
case "group-label":
|
|
5934
|
+
case "compartment-row":
|
|
5935
|
+
return false;
|
|
5936
|
+
}
|
|
5937
|
+
}
|
|
5938
|
+
function fallbackLabelLayout(text) {
|
|
5939
|
+
const width = Math.max(0, text.length * 7);
|
|
5940
|
+
return {
|
|
5941
|
+
text,
|
|
5942
|
+
box: { x: 0, y: 0, width, height: 14 },
|
|
5943
|
+
contentBox: { x: 0, y: 0, width, height: 14 },
|
|
5944
|
+
naturalSize: { width, height: 14 },
|
|
5945
|
+
fittedSize: { width, height: 14 },
|
|
5946
|
+
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
5947
|
+
font: { fontFamily: "Arial", fontSize: 12, lineHeight: 14 },
|
|
5948
|
+
lineHeight: 14,
|
|
5949
|
+
lines: [
|
|
5950
|
+
{
|
|
5951
|
+
text,
|
|
5952
|
+
box: { x: 0, y: 0, width, height: 14 },
|
|
5953
|
+
baselineY: 11.2,
|
|
5954
|
+
width,
|
|
5955
|
+
lineIndex: 0
|
|
5956
|
+
}
|
|
5957
|
+
],
|
|
5958
|
+
overflow: { horizontal: false, vertical: false, truncated: false },
|
|
5959
|
+
diagnostics: []
|
|
5960
|
+
};
|
|
5961
|
+
}
|
|
5962
|
+
function edgeLabelAnchor(edge, layout2, edges) {
|
|
5963
|
+
const placement = labelPlacementOnPolyline2(edge.points);
|
|
5964
|
+
if (placement === void 0) {
|
|
5965
|
+
return { x: 0, y: 0 };
|
|
5966
|
+
}
|
|
5967
|
+
for (const candidate of edgeLabelAnchorCandidates(edge.points, placement)) {
|
|
5968
|
+
const labelBox = {
|
|
5969
|
+
x: candidate.x - layout2.box.width / 2,
|
|
5970
|
+
y: candidate.y - layout2.box.height / 2,
|
|
5971
|
+
width: layout2.box.width,
|
|
5972
|
+
height: layout2.box.height
|
|
5973
|
+
};
|
|
5974
|
+
if (routeIntersectsTextBox(edge.points, labelBox)) {
|
|
5975
|
+
continue;
|
|
5976
|
+
}
|
|
5977
|
+
const crossesOtherRoute = edges.some(
|
|
5978
|
+
(other) => other.id !== edge.id && routeIntersectsTextBox(other.points, labelBox)
|
|
5979
|
+
);
|
|
5980
|
+
if (!crossesOtherRoute) {
|
|
5981
|
+
return candidate;
|
|
5982
|
+
}
|
|
5983
|
+
}
|
|
5984
|
+
return placement;
|
|
5985
|
+
}
|
|
5986
|
+
function edgeLabelAnchorCandidates(points, placement) {
|
|
5987
|
+
const segment = labelSegmentOnPolyline(points);
|
|
5988
|
+
if (segment === void 0) {
|
|
5989
|
+
return [placement];
|
|
5990
|
+
}
|
|
5991
|
+
if (segment.start.y === segment.end.y) {
|
|
5992
|
+
return [
|
|
5993
|
+
placement,
|
|
5994
|
+
{ x: placement.x, y: placement.y - EDGE_LABEL_CLEARANCE },
|
|
5995
|
+
{ x: placement.x, y: placement.y + EDGE_LABEL_CLEARANCE },
|
|
5996
|
+
{ x: placement.x, y: placement.y - EDGE_LABEL_CLEARANCE * 2 },
|
|
5997
|
+
{ x: placement.x, y: placement.y + EDGE_LABEL_CLEARANCE * 2 }
|
|
5998
|
+
];
|
|
5999
|
+
}
|
|
6000
|
+
if (segment.start.x === segment.end.x) {
|
|
6001
|
+
return [
|
|
6002
|
+
placement,
|
|
6003
|
+
{ x: placement.x + EDGE_LABEL_CLEARANCE, y: placement.y },
|
|
6004
|
+
{ x: placement.x - EDGE_LABEL_CLEARANCE, y: placement.y },
|
|
6005
|
+
{ x: placement.x + EDGE_LABEL_CLEARANCE * 2, y: placement.y },
|
|
6006
|
+
{ x: placement.x - EDGE_LABEL_CLEARANCE * 2, y: placement.y }
|
|
6007
|
+
];
|
|
6008
|
+
}
|
|
6009
|
+
return [placement];
|
|
6010
|
+
}
|
|
6011
|
+
function labelPlacementOnPolyline2(points) {
|
|
6012
|
+
return labelSegmentOnPolyline(points)?.placement;
|
|
6013
|
+
}
|
|
6014
|
+
function labelSegmentOnPolyline(points) {
|
|
6015
|
+
const segments = nonZeroSegments2(points);
|
|
6016
|
+
const totalLength = segments.reduce(
|
|
6017
|
+
(sum, segment) => sum + segment.length,
|
|
6018
|
+
0
|
|
6019
|
+
);
|
|
6020
|
+
if (totalLength <= 0) {
|
|
6021
|
+
return void 0;
|
|
6022
|
+
}
|
|
6023
|
+
let remaining = totalLength / 2;
|
|
6024
|
+
for (const segment of segments) {
|
|
6025
|
+
if (remaining <= segment.length) {
|
|
6026
|
+
const ratio = remaining / segment.length;
|
|
6027
|
+
const x = segment.start.x + (segment.end.x - segment.start.x) * ratio;
|
|
6028
|
+
const y = segment.start.y + (segment.end.y - segment.start.y) * ratio;
|
|
6029
|
+
const offset2 = labelOffset2(segment);
|
|
6030
|
+
return {
|
|
6031
|
+
start: segment.start,
|
|
6032
|
+
end: segment.end,
|
|
6033
|
+
placement: { x: x + offset2.x, y: y + offset2.y }
|
|
6034
|
+
};
|
|
6035
|
+
}
|
|
6036
|
+
remaining -= segment.length;
|
|
6037
|
+
}
|
|
6038
|
+
const last = segments.at(-1);
|
|
6039
|
+
if (last === void 0) {
|
|
6040
|
+
return void 0;
|
|
6041
|
+
}
|
|
6042
|
+
const offset = labelOffset2(last);
|
|
6043
|
+
return {
|
|
6044
|
+
start: last.start,
|
|
6045
|
+
end: last.end,
|
|
6046
|
+
placement: { x: last.end.x + offset.x, y: last.end.y + offset.y }
|
|
6047
|
+
};
|
|
6048
|
+
}
|
|
6049
|
+
function nonZeroSegments2(points) {
|
|
6050
|
+
const segments = [];
|
|
6051
|
+
for (let index = 0; index < points.length - 1; index += 1) {
|
|
6052
|
+
const start = points[index];
|
|
6053
|
+
const end = points[index + 1];
|
|
6054
|
+
if (start === void 0 || end === void 0) {
|
|
6055
|
+
continue;
|
|
6056
|
+
}
|
|
6057
|
+
const length = Math.hypot(end.x - start.x, end.y - start.y);
|
|
6058
|
+
if (length > 0) {
|
|
6059
|
+
segments.push({ start, end, length });
|
|
6060
|
+
}
|
|
6061
|
+
}
|
|
6062
|
+
return segments;
|
|
6063
|
+
}
|
|
6064
|
+
function labelOffset2(segment) {
|
|
6065
|
+
const offset = 10;
|
|
6066
|
+
const dx = segment.end.x - segment.start.x;
|
|
6067
|
+
const dy = segment.end.y - segment.start.y;
|
|
6068
|
+
return {
|
|
6069
|
+
x: -dy / segment.length * offset,
|
|
6070
|
+
y: dx / segment.length * offset
|
|
6071
|
+
};
|
|
6072
|
+
}
|
|
6073
|
+
function compartmentRows2(node) {
|
|
6074
|
+
const compartments2 = node.compartments;
|
|
6075
|
+
if (compartments2 === void 0) {
|
|
6076
|
+
return [];
|
|
6077
|
+
}
|
|
6078
|
+
return [
|
|
6079
|
+
...compartments2.stereotype === void 0 ? [] : [compartments2.stereotype],
|
|
6080
|
+
...compartments2.name === void 0 ? [node.label?.text ?? node.id] : [compartments2.name],
|
|
6081
|
+
...compartments2.properties ?? [],
|
|
6082
|
+
...compartments2.constraints ?? []
|
|
6083
|
+
];
|
|
6084
|
+
}
|
|
3982
6085
|
function portGeometry(nodeGeometry, port) {
|
|
3983
6086
|
if (port === void 0) {
|
|
3984
6087
|
return nodeGeometry;
|
|
@@ -4055,7 +6158,8 @@ function renderDiagramDsl(source, options = {}) {
|
|
|
4055
6158
|
}
|
|
4056
6159
|
const solved = solveDiagram(normalized.diagram, {
|
|
4057
6160
|
routeKind: normalized.diagram.metadata?.routeKind === "straight" ? "straight" : "orthogonal",
|
|
4058
|
-
...solvePortShiftingOption(normalized.diagram.metadata?.portShifting)
|
|
6161
|
+
...solvePortShiftingOption(normalized.diagram.metadata?.portShifting),
|
|
6162
|
+
...options.textMeasurer === void 0 ? {} : { textMeasurer: options.textMeasurer }
|
|
4059
6163
|
});
|
|
4060
6164
|
const solveDiagnostics = solved.diagnostics.map(toSolveDiagnostic);
|
|
4061
6165
|
if (hasErrorDiagnostics2(solveDiagnostics)) {
|