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