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