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