@crazyhappyone/auto-graph 0.0.21 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -96,11 +96,21 @@ function applyLayoutConstraints(input) {
96
96
  const nodeById = new Map(input.nodes.map((node) => [node.id, node]));
97
97
  applyFixedPositionLocks(input.nodes, boxes, locks, diagnostics);
98
98
  applyExactPositions(input.constraints, boxes, locks, diagnostics, nodeById);
99
- applyContainment(input.constraints, boxes, locks, diagnostics);
99
+ applyContainment(input.constraints, boxes, locks, diagnostics, false);
100
100
  applyRelative(input.constraints, boxes, locks, diagnostics);
101
101
  applyAlign(input.constraints, boxes, locks, diagnostics);
102
102
  applyDistribute(input.constraints, boxes, locks, diagnostics);
103
- repairOverlaps(input, boxes, locks, diagnostics);
103
+ repairOverlaps(
104
+ input,
105
+ boxes,
106
+ locks,
107
+ diagnostics,
108
+ siblingOverlapKeys(input.constraints)
109
+ );
110
+ applyContainment(input.constraints, boxes, locks, diagnostics, true);
111
+ applyDistributeContained(input, boxes, locks, diagnostics);
112
+ reportOverlaps(boxes, diagnostics, containmentOverlapKeys(input.constraints));
113
+ reportIntraContainerOverflow(input, boxes, diagnostics);
104
114
  return { boxes, locks, diagnostics };
105
115
  }
106
116
  function cloneValidBoxes(input, diagnostics) {
@@ -188,7 +198,7 @@ function applyExactPositions(constraints, boxes, locks, diagnostics, nodeById) {
188
198
  locks.set(targetId, { nodeId: targetId, source: "exact-position" });
189
199
  }
190
200
  }
191
- function applyContainment(constraints, boxes, locks, diagnostics) {
201
+ function applyContainment(constraints, boxes, locks, diagnostics, reportOverflow) {
192
202
  for (const constraint of constraints) {
193
203
  if (constraint.kind !== "containment") {
194
204
  continue;
@@ -210,21 +220,23 @@ function applyContainment(constraints, boxes, locks, diagnostics) {
210
220
  continue;
211
221
  }
212
222
  if (locks.has(childId)) {
213
- diagnostics.push({
214
- severity: "warning",
215
- code: "constraints.locked-target-not-moved",
216
- message: `Locked child ${childId} was not moved into containment.`,
217
- path: ["constraints", constraint.id ?? constraint.containerId],
218
- detail: { nodeId: childId }
219
- });
220
- if (!isInside(child, content)) {
223
+ if (!reportOverflow) {
221
224
  diagnostics.push({
222
- severity: "error",
223
- code: "constraints.containment.impossible",
224
- message: `Locked child ${childId} cannot fit inside ${constraint.containerId}.`,
225
+ severity: "warning",
226
+ code: "constraints.locked-target-not-moved",
227
+ message: `Locked child ${childId} was not moved into containment.`,
225
228
  path: ["constraints", constraint.id ?? constraint.containerId],
226
- detail: { nodeId: childId, containerId: constraint.containerId }
229
+ detail: { nodeId: childId }
227
230
  });
231
+ if (!isInside(child, content)) {
232
+ diagnostics.push({
233
+ severity: "error",
234
+ code: "constraints.containment.impossible",
235
+ message: `Locked child ${childId} cannot fit inside ${constraint.containerId}.`,
236
+ path: ["constraints", constraint.id ?? constraint.containerId],
237
+ detail: { nodeId: childId, containerId: constraint.containerId }
238
+ });
239
+ }
228
240
  }
229
241
  continue;
230
242
  }
@@ -239,6 +251,15 @@ function applyContainment(constraints, boxes, locks, diagnostics) {
239
251
  continue;
240
252
  }
241
253
  boxes.set(childId, next);
254
+ if (reportOverflow) {
255
+ diagnostics.push({
256
+ severity: "warning",
257
+ code: "containment_overflow",
258
+ message: `Child ${childId} was clamped back inside ${constraint.containerId} after constraint solving.`,
259
+ path: ["constraints", constraint.id ?? constraint.containerId],
260
+ detail: { nodeId: childId, containerId: constraint.containerId }
261
+ });
262
+ }
242
263
  }
243
264
  }
244
265
  }
@@ -314,37 +335,69 @@ function applyDistribute(constraints, boxes, locks, diagnostics) {
314
335
  }
315
336
  }
316
337
  }
317
- function repairOverlaps(input, boxes, locks, diagnostics) {
338
+ function repairOverlaps(input, boxes, locks, diagnostics, siblingPairs) {
318
339
  const spacing = input.overlapSpacing ?? 40;
319
340
  const axis = input.direction === "LR" || input.direction === "RL" ? "x" : "y";
341
+ const secondaryAxis = axis === "x" ? "y" : "x";
342
+ const ignoredPairs = containmentOverlapKeys(input.constraints);
320
343
  const ids = [...boxes.keys()].sort();
321
- for (const firstId of ids) {
322
- for (const secondId of ids) {
323
- if (firstId >= secondId) {
324
- continue;
325
- }
326
- const first = boxes.get(firstId);
327
- const second = boxes.get(secondId);
328
- if (first === void 0 || second === void 0 || !intersectsAabb(first, second)) {
329
- continue;
330
- }
331
- const firstLocked = locks.has(firstId);
332
- const secondLocked = locks.has(secondId);
333
- if (firstLocked === secondLocked) {
334
- continue;
344
+ for (let pass = 0; pass < 2; pass += 1) {
345
+ for (const firstId of ids) {
346
+ for (const secondId of ids) {
347
+ if (firstId >= secondId) {
348
+ continue;
349
+ }
350
+ if (ignoredPairs.has(overlapKey(firstId, secondId))) {
351
+ continue;
352
+ }
353
+ const first = boxes.get(firstId);
354
+ const second = boxes.get(secondId);
355
+ if (first === void 0 || second === void 0 || !intersectsAabb(first, second)) {
356
+ continue;
357
+ }
358
+ const firstLocked = locks.has(firstId);
359
+ const secondLocked = locks.has(secondId);
360
+ if (firstLocked && secondLocked) {
361
+ continue;
362
+ }
363
+ const movingId = firstLocked ? secondId : secondLocked ? firstId : secondId;
364
+ const moving = movingId === firstId ? first : second;
365
+ const fixed = movingId === firstId ? second : first;
366
+ const repairAxis = firstLocked === secondLocked && pass === 0 ? secondaryAxis : axis;
367
+ const pairKey = overlapKey(firstId, secondId);
368
+ const effectiveSpacing = siblingPairs.has(pairKey) ? Math.max(spacing, input.minSiblingGap ?? 0) : spacing;
369
+ const moved = movePastOverlap(
370
+ moving,
371
+ fixed,
372
+ repairAxis,
373
+ effectiveSpacing
374
+ );
375
+ boxes.set(movingId, moved);
335
376
  }
336
- const movingId = firstLocked ? secondId : firstId;
337
- const moving = firstLocked ? second : first;
338
- const fixed = firstLocked ? first : second;
339
- const moved = movePastOverlap(moving, fixed, axis, spacing);
340
- boxes.set(movingId, moved);
341
377
  }
342
378
  }
379
+ reportOverlaps(boxes, diagnostics, ignoredPairs);
380
+ }
381
+ function reportOverlaps(boxes, diagnostics, ignoredPairs = /* @__PURE__ */ new Set()) {
382
+ const ids = [...boxes.keys()].sort();
383
+ const reported = new Set(
384
+ diagnostics.filter(
385
+ (diagnostic) => diagnostic.code === "constraints.overlap.unresolved"
386
+ ).map((diagnostic) => {
387
+ const firstId = diagnostic.detail?.firstId;
388
+ const secondId = diagnostic.detail?.secondId;
389
+ return typeof firstId === "string" && typeof secondId === "string" ? overlapKey(firstId, secondId) : void 0;
390
+ }).filter((key) => key !== void 0)
391
+ );
343
392
  for (const firstId of ids) {
344
393
  for (const secondId of ids) {
345
394
  if (firstId >= secondId) {
346
395
  continue;
347
396
  }
397
+ const key = overlapKey(firstId, secondId);
398
+ if (reported.has(key) || ignoredPairs.has(key)) {
399
+ continue;
400
+ }
348
401
  const first = boxes.get(firstId);
349
402
  const second = boxes.get(secondId);
350
403
  if (first !== void 0 && second !== void 0 && intersectsAabb(first, second)) {
@@ -355,9 +408,136 @@ function repairOverlaps(input, boxes, locks, diagnostics) {
355
408
  path: ["boxes"],
356
409
  detail: { firstId, secondId }
357
410
  });
411
+ reported.add(key);
412
+ }
413
+ }
414
+ }
415
+ }
416
+ function reportIntraContainerOverflow(input, boxes, diagnostics) {
417
+ if (input.minSiblingGap === void 0) {
418
+ return;
419
+ }
420
+ const minGap = input.minSiblingGap;
421
+ const axis = input.direction === "LR" || input.direction === "RL" ? "x" : "y";
422
+ for (const constraint of input.constraints) {
423
+ if (constraint.kind !== "containment") {
424
+ continue;
425
+ }
426
+ const container = boxes.get(constraint.containerId);
427
+ if (container === void 0) {
428
+ continue;
429
+ }
430
+ const children = [];
431
+ for (const childId of constraint.childIds) {
432
+ const child = boxes.get(childId);
433
+ if (child !== void 0) {
434
+ children.push(child);
435
+ }
436
+ }
437
+ if (children.length < 2) {
438
+ continue;
439
+ }
440
+ const sorted = [...children].sort((a, b) => a[axis] - b[axis]);
441
+ const mainDim = axis === "x" ? "width" : "height";
442
+ let overlapPairs = 0;
443
+ for (let i = 0; i < sorted.length; i += 1) {
444
+ const first = sorted[i];
445
+ if (first === void 0) {
446
+ continue;
447
+ }
448
+ for (let j = i + 1; j < sorted.length; j += 1) {
449
+ const second = sorted[j];
450
+ if (second === void 0) {
451
+ continue;
452
+ }
453
+ if (second[axis] >= first[axis] + first[mainDim]) {
454
+ break;
455
+ }
456
+ if (intersectsAabb(first, second)) {
457
+ overlapPairs += 1;
458
+ }
459
+ }
460
+ }
461
+ if (overlapPairs > 0) {
462
+ diagnostics.push({
463
+ severity: "warning",
464
+ code: "intra_container_overflow",
465
+ message: `${overlapPairs} sibling pair(s) overlap inside ${constraint.containerId}.`,
466
+ path: ["constraints", constraint.id ?? constraint.containerId],
467
+ detail: {
468
+ containerId: constraint.containerId,
469
+ overlapPairs,
470
+ minGap
471
+ }
472
+ });
473
+ }
474
+ const pad = constraint.padding ?? { top: 0, right: 0, bottom: 0, left: 0 };
475
+ const contentMain = mainDim === "width" ? Math.max(0, container.width - pad.left - pad.right) : Math.max(0, container.height - pad.top - pad.bottom);
476
+ let childStart = Infinity;
477
+ let childEnd = -Infinity;
478
+ for (const child of sorted) {
479
+ const start = child[axis];
480
+ const end = start + child[mainDim];
481
+ if (start < childStart) childStart = start;
482
+ if (end > childEnd) childEnd = end;
483
+ }
484
+ if (sorted.length === 0) {
485
+ childStart = 0;
486
+ childEnd = 0;
487
+ }
488
+ const actualExtent = childEnd - childStart;
489
+ if (actualExtent > contentMain) {
490
+ diagnostics.push({
491
+ severity: "error",
492
+ code: "intra_container_overflow_total",
493
+ message: `Container ${constraint.containerId} cannot fit ${sorted.length} siblings along ${axis} (extent ${actualExtent}, available ${contentMain}).`,
494
+ path: ["constraints", constraint.id ?? constraint.containerId],
495
+ detail: {
496
+ containerId: constraint.containerId,
497
+ axis,
498
+ needed: actualExtent,
499
+ available: contentMain,
500
+ siblingCount: sorted.length,
501
+ minGap
502
+ }
503
+ });
504
+ }
505
+ }
506
+ }
507
+ function overlapKey(firstId, secondId) {
508
+ return firstId < secondId ? `${firstId}\0${secondId}` : `${secondId}\0${firstId}`;
509
+ }
510
+ function containmentOverlapKeys(constraints) {
511
+ const keys = /* @__PURE__ */ new Set();
512
+ for (const constraint of constraints) {
513
+ if (constraint.kind !== "containment") {
514
+ continue;
515
+ }
516
+ for (const childId of constraint.childIds) {
517
+ keys.add(overlapKey(constraint.containerId, childId));
518
+ }
519
+ }
520
+ return keys;
521
+ }
522
+ function siblingOverlapKeys(constraints) {
523
+ const keys = /* @__PURE__ */ new Set();
524
+ for (const constraint of constraints) {
525
+ if (constraint.kind !== "containment") {
526
+ continue;
527
+ }
528
+ const { childIds } = constraint;
529
+ for (let i = 0; i < childIds.length; i += 1) {
530
+ for (let j = i + 1; j < childIds.length; j += 1) {
531
+ const a = childIds[i];
532
+ const b = childIds[j];
533
+ if (a === void 0 || b === void 0) {
534
+ continue;
535
+ }
536
+ keys.add(overlapKey(a, b));
358
537
  }
359
538
  }
360
539
  }
540
+ return keys;
361
541
  }
362
542
  function setUnlockedBox(id, next, boxes, locks, diagnostics, constraint) {
363
543
  const current = boxes.get(id);
@@ -475,10 +655,125 @@ function contentBox(container, padding) {
475
655
  return {
476
656
  x: container.x + margin.left,
477
657
  y: container.y + margin.top,
478
- width: container.width - margin.left - margin.right,
479
- height: container.height - margin.top - margin.bottom
658
+ width: Math.max(0, container.width - margin.left - margin.right),
659
+ height: Math.max(0, container.height - margin.top - margin.bottom)
480
660
  };
481
661
  }
662
+ function applyDistributeContained(input, boxes, locks, diagnostics) {
663
+ if (!input.distributeContainedChildren) {
664
+ return;
665
+ }
666
+ const axis = input.direction === "LR" || input.direction === "RL" ? "x" : "y";
667
+ const crossAxis = axis === "x" ? "y" : "x";
668
+ const mainSize = axis === "x" ? "width" : "height";
669
+ const crossSize = axis === "x" ? "height" : "width";
670
+ const minGap = input.minSiblingGap ?? 8;
671
+ for (const constraint of input.constraints) {
672
+ if (constraint.kind !== "containment") {
673
+ continue;
674
+ }
675
+ const container = boxes.get(constraint.containerId);
676
+ if (container === void 0) {
677
+ continue;
678
+ }
679
+ const content = contentBox(container, constraint.padding);
680
+ const unlocked = [];
681
+ const reserved = [];
682
+ for (const childId of constraint.childIds) {
683
+ const box = boxes.get(childId);
684
+ if (box === void 0) {
685
+ continue;
686
+ }
687
+ if (locks.has(childId)) {
688
+ diagnostics.push({
689
+ severity: "warning",
690
+ code: "constraints.locked-target-not-moved",
691
+ message: `Locked child ${childId} skipped during containment distribution.`,
692
+ path: ["constraints", constraint.id ?? constraint.containerId],
693
+ detail: { nodeId: childId }
694
+ });
695
+ reserved.push(intervalForBox(box, axis, mainSize));
696
+ continue;
697
+ }
698
+ unlocked.push({ id: childId, box });
699
+ }
700
+ if (unlocked.length < 2) {
701
+ continue;
702
+ }
703
+ const oversized = unlocked.filter(
704
+ (child) => child.box[mainSize] > content[mainSize] || child.box[crossSize] > content[crossSize]
705
+ );
706
+ if (oversized.length > 0) {
707
+ diagnostics.push({
708
+ severity: "warning",
709
+ code: "constraints.containment.impossible",
710
+ message: `Skipped ${oversized.length} oversized child(ren) during distribution in ${constraint.containerId}.`,
711
+ path: ["constraints", constraint.id ?? constraint.containerId],
712
+ detail: {
713
+ containerId: constraint.containerId,
714
+ oversized: oversized.map((c) => c.id)
715
+ }
716
+ });
717
+ }
718
+ for (const child of oversized) {
719
+ reserved.push(intervalForBox(child.box, axis, mainSize));
720
+ }
721
+ reserved.sort((a, b) => a.start - b.start || a.end - b.end);
722
+ const distributable = unlocked.filter(
723
+ (child) => child.box[mainSize] <= content[mainSize] && child.box[crossSize] <= content[crossSize]
724
+ );
725
+ if (distributable.length < 2) {
726
+ continue;
727
+ }
728
+ let pos = content[axis];
729
+ for (const child of distributable) {
730
+ pos = advancePastReserved(pos, child.box[mainSize], reserved, minGap);
731
+ const crossPos = content[crossAxis] + Math.max(0, (content[crossSize] - child.box[crossSize]) / 2);
732
+ const next = { ...child.box };
733
+ next[axis] = pos;
734
+ next[crossAxis] = crossPos;
735
+ const clamped = moveInside(next, content);
736
+ if (clamped[axis] !== next[axis]) {
737
+ diagnostics.push({
738
+ severity: "warning",
739
+ code: "intra_container_distributed_clamped",
740
+ message: `Distribution gap clamped for ${child.id} in ${constraint.containerId}.`,
741
+ path: ["constraints", constraint.id ?? constraint.containerId],
742
+ detail: { nodeId: child.id, containerId: constraint.containerId }
743
+ });
744
+ }
745
+ boxes.set(child.id, clamped);
746
+ pos = clamped[axis] + clamped[mainSize] + minGap;
747
+ }
748
+ diagnostics.push({
749
+ severity: "info",
750
+ code: "intra_container_distributed",
751
+ message: `Distributed ${distributable.length} children in ${constraint.containerId} along ${axis}.`,
752
+ path: ["constraints", constraint.id ?? constraint.containerId],
753
+ detail: {
754
+ containerId: constraint.containerId,
755
+ count: distributable.length,
756
+ axis
757
+ }
758
+ });
759
+ }
760
+ }
761
+ function intervalForBox(box, axis, mainSize) {
762
+ return { start: box[axis], end: box[axis] + box[mainSize] };
763
+ }
764
+ function advancePastReserved(pos, size, reserved, minGap) {
765
+ let next = pos;
766
+ for (const interval of reserved) {
767
+ if (next + size + minGap <= interval.start) {
768
+ break;
769
+ }
770
+ if (next >= interval.end + minGap) {
771
+ continue;
772
+ }
773
+ next = interval.end + minGap;
774
+ }
775
+ return next;
776
+ }
482
777
  function moveInside(child, container) {
483
778
  return {
484
779
  ...child,
@@ -1184,7 +1479,14 @@ var DEFAULT_GROUP_PADDING = {
1184
1479
  left: 16
1185
1480
  };
1186
1481
  var DEFAULT_LABEL_MAX_WIDTH = 160;
1187
- var DEFAULT_FONT = { fontFamily: "Arial", fontSize: 14, lineHeight: 18 };
1482
+ var DEFAULT_FONT = {
1483
+ fontFamily: "Arial",
1484
+ fontSize: 14,
1485
+ lineHeight: 18
1486
+ };
1487
+ var DEFAULT_MATRIX_CELL_SIZE = { width: 120, height: 36 };
1488
+ var DEFAULT_TABLE_CELL_SIZE = { width: 128, height: 34 };
1489
+ var DEFAULT_PANEL_ITEM_HEIGHT = 28;
1188
1490
  function normalizeDiagramDsl(dslValue, options = {}) {
1189
1491
  const dsl = dslValue;
1190
1492
  const diagnostics = validateReferences(dsl);
@@ -1197,6 +1499,10 @@ function normalizeDiagramDsl(dslValue, options = {}) {
1197
1499
  const measurer = options.textMeasurer ?? createDefaultTextMeasurer();
1198
1500
  const routeKind = dsl.routing?.kind ?? "orthogonal";
1199
1501
  const portShifting = normalizePortShifting(dsl.routing?.portShifting);
1502
+ const primaryReadingDirection = dsl.layout?.primaryReadingDirection;
1503
+ const matrices = normalizeMatrices(dsl);
1504
+ const tables = normalizeTables(dsl);
1505
+ const evidencePanels = normalizeEvidencePanels(dsl);
1200
1506
  const diagram = {
1201
1507
  id: options.id ?? dsl.id ?? "diagram",
1202
1508
  ...dsl.title === void 0 ? {} : { title: dsl.title },
@@ -1205,11 +1511,15 @@ function normalizeDiagramDsl(dslValue, options = {}) {
1205
1511
  edges: normalizeEdges(dsl),
1206
1512
  groups: normalizeGroups(dsl, measurer),
1207
1513
  swimlanes: normalizeSwimlanes(dsl),
1514
+ ...matrices === void 0 ? {} : { matrices },
1515
+ ...tables === void 0 ? {} : { tables },
1516
+ ...evidencePanels === void 0 ? {} : { evidencePanels },
1208
1517
  constraints: normalizeConstraints(dsl),
1209
1518
  diagnostics: [],
1210
1519
  ...dsl.frame === void 0 ? {} : { frame: normalizeFrame(dsl.frame) },
1211
1520
  metadata: {
1212
1521
  routeKind,
1522
+ ...primaryReadingDirection === void 0 ? {} : { primaryReadingDirection },
1213
1523
  ...portShifting === void 0 ? {} : { portShifting }
1214
1524
  }
1215
1525
  };
@@ -1349,7 +1659,9 @@ function endpoint(value, nodeIdOverride) {
1349
1659
  function style(value) {
1350
1660
  return {
1351
1661
  ...value.fill === void 0 ? {} : { fill: value.fill },
1352
- ...value.stroke === void 0 ? {} : { stroke: value.stroke }
1662
+ ...value.stroke === void 0 ? {} : { stroke: value.stroke },
1663
+ ...value.fontFamily === void 0 ? {} : { fontFamily: value.fontFamily },
1664
+ ...value.fontSize === void 0 ? {} : { fontSize: value.fontSize }
1353
1665
  };
1354
1666
  }
1355
1667
  function compartments(value) {
@@ -1366,6 +1678,10 @@ function normalizeFrame(frame) {
1366
1678
  ...frame.context === void 0 ? {} : { context: frame.context },
1367
1679
  ...frame.name === void 0 ? {} : { name: frame.name },
1368
1680
  titleTab: frame.titleTab,
1681
+ ...frame.headerHeight === void 0 ? {} : { headerHeight: frame.headerHeight },
1682
+ ...frame.padding === void 0 ? {} : { padding: frame.padding },
1683
+ ...frame.labelPosition === void 0 ? {} : { labelPosition: frame.labelPosition },
1684
+ ...frame.direction === void 0 ? {} : { direction: frame.direction },
1369
1685
  ...frame.style === void 0 ? {} : { style: style(frame.style) }
1370
1686
  };
1371
1687
  }
@@ -1380,14 +1696,17 @@ function formatCompartmentEntry(value) {
1380
1696
  return `${entry[0]}: ${entry[1]}`;
1381
1697
  }
1382
1698
  function normalizeSwimlanes(dsl) {
1383
- return Object.keys(dsl.swimlanes ?? {}).sort().map((id) => {
1699
+ return Object.keys(dsl.swimlanes ?? {}).map((id) => {
1384
1700
  const swimlane = dsl.swimlanes?.[id];
1385
1701
  const label = toLabel(swimlane?.label);
1386
1702
  return {
1387
1703
  id,
1388
1704
  ...label === void 0 ? {} : { label },
1389
1705
  orientation: swimlane?.orientation ?? "vertical",
1390
- lanes: Object.keys(swimlane?.lanes ?? {}).sort().map((laneId) => {
1706
+ layout: swimlane?.layout ?? "overlay",
1707
+ ...swimlane?.headerHeight === void 0 ? {} : { headerHeight: swimlane.headerHeight },
1708
+ ...swimlane?.padding === void 0 ? {} : { padding: swimlane.padding },
1709
+ lanes: Object.keys(swimlane?.lanes ?? {}).map((laneId) => {
1391
1710
  const lane = swimlane?.lanes[laneId];
1392
1711
  const laneLabel = toLabel(lane?.label);
1393
1712
  return {
@@ -1399,6 +1718,102 @@ function normalizeSwimlanes(dsl) {
1399
1718
  };
1400
1719
  });
1401
1720
  }
1721
+ function normalizeMatrices(dsl) {
1722
+ if (dsl.matrices === void 0) {
1723
+ return void 0;
1724
+ }
1725
+ return dsl.matrices.map((matrix) => ({
1726
+ id: matrix.id,
1727
+ rows: [...matrix.rows],
1728
+ cols: [...matrix.cols],
1729
+ cells: matrix.cells.map((row) => row.map(cell)),
1730
+ ...matrix.position === void 0 ? {} : { position: point(matrix.position) },
1731
+ size: matrix.size ?? {
1732
+ width: defaultMatrixRowHeaderWidth(matrix) + Math.max(1, matrix.cols.length) * DEFAULT_MATRIX_CELL_SIZE.width,
1733
+ height: Math.max(1, matrix.rows.length + 1) * DEFAULT_MATRIX_CELL_SIZE.height
1734
+ },
1735
+ ...matrix.style === void 0 ? {} : { style: style(matrix.style) }
1736
+ }));
1737
+ }
1738
+ function defaultMatrixRowHeaderWidth(matrix) {
1739
+ return matrix.rows.length === 0 ? 0 : Math.min(96, DEFAULT_MATRIX_CELL_SIZE.width);
1740
+ }
1741
+ function normalizeTables(dsl) {
1742
+ if (dsl.tables === void 0) {
1743
+ return void 0;
1744
+ }
1745
+ return dsl.tables.map((table) => ({
1746
+ id: table.id,
1747
+ columns: table.columns.map(tableColumn),
1748
+ rows: table.rows.map(tableRow),
1749
+ ...table.position === void 0 ? {} : { position: point(table.position) },
1750
+ size: table.size ?? {
1751
+ width: Math.max(1, table.columns.length) * DEFAULT_TABLE_CELL_SIZE.width,
1752
+ height: Math.max(1, table.rows.length + 1) * DEFAULT_TABLE_CELL_SIZE.height
1753
+ },
1754
+ ...table.style === void 0 ? {} : { style: style(table.style) }
1755
+ }));
1756
+ }
1757
+ function normalizeEvidencePanels(dsl) {
1758
+ if (dsl.evidencePanels === void 0) {
1759
+ return void 0;
1760
+ }
1761
+ return dsl.evidencePanels.map((panel) => ({
1762
+ id: panel.id,
1763
+ kind: panel.kind,
1764
+ items: panel.items.map(panelItem),
1765
+ ...panel.position === void 0 ? {} : { position: point(panel.position) },
1766
+ size: panel.size ?? {
1767
+ width: 320,
1768
+ height: Math.max(1, panel.items.length) * DEFAULT_PANEL_ITEM_HEIGHT
1769
+ },
1770
+ ...panel.style === void 0 ? {} : { style: style(panel.style) }
1771
+ }));
1772
+ }
1773
+ function cell(value) {
1774
+ if (typeof value === "string") {
1775
+ return { text: value };
1776
+ }
1777
+ return {
1778
+ text: value.text,
1779
+ ...value.style === void 0 && value.fill === void 0 && value.stroke === void 0 ? {} : {
1780
+ style: style({
1781
+ ...value.style,
1782
+ ...value.fill === void 0 ? {} : { fill: value.fill },
1783
+ ...value.stroke === void 0 ? {} : { stroke: value.stroke }
1784
+ })
1785
+ }
1786
+ };
1787
+ }
1788
+ function tableColumn(value) {
1789
+ return {
1790
+ id: value.id,
1791
+ label: toLabel(value.label) ?? { text: value.id }
1792
+ };
1793
+ }
1794
+ function tableRow(value) {
1795
+ return {
1796
+ id: value.id,
1797
+ cells: Object.fromEntries(
1798
+ Object.keys(value.cells).map((columnId) => [
1799
+ columnId,
1800
+ cell(value.cells[columnId] ?? "")
1801
+ ])
1802
+ )
1803
+ };
1804
+ }
1805
+ function panelItem(value) {
1806
+ if (typeof value === "string") {
1807
+ return { label: { text: value } };
1808
+ }
1809
+ const detail = toLabel(value.detail);
1810
+ return {
1811
+ ...value.id === void 0 ? {} : { id: value.id },
1812
+ label: toLabel(value.label) ?? { text: "" },
1813
+ ...detail === void 0 ? {} : { detail },
1814
+ ...value.style === void 0 ? {} : { style: style(value.style) }
1815
+ };
1816
+ }
1402
1817
  function normalizeGroups(dsl, measurer) {
1403
1818
  return Object.keys(dsl.groups ?? {}).sort().map((id) => {
1404
1819
  const group = dsl.groups?.[id];
@@ -1410,6 +1825,9 @@ function normalizeGroups(dsl, measurer) {
1410
1825
  nodeIds: [...group?.nodes ?? []],
1411
1826
  groupIds: [...group?.groups ?? []],
1412
1827
  padding: group?.padding ?? { ...DEFAULT_GROUP_PADDING },
1828
+ ...group?.headerHeight === void 0 ? {} : { headerHeight: group.headerHeight },
1829
+ ...group?.labelPosition === void 0 ? {} : { labelPosition: group.labelPosition },
1830
+ ...group?.direction === void 0 ? {} : { direction: group.direction },
1413
1831
  ...labelLayout === void 0 ? {} : { labelLayout }
1414
1832
  };
1415
1833
  });
@@ -1654,6 +2072,10 @@ var routeKindSchema = z.enum(["orthogonal", "straight"]);
1654
2072
  var outputFormatSchema = z.enum(["svg", "excalidraw"]);
1655
2073
  var edgeStrokeStyleSchema = z.enum(["solid", "dashed"]);
1656
2074
  var edgeArrowheadSchema = z.enum(["triangle", "hollowTriangle"]);
2075
+ var primaryReadingDirectionSchema = z.enum([
2076
+ "top_to_bottom",
2077
+ "top-to-bottom"
2078
+ ]);
1657
2079
  var nodeShapeSchema = z.enum([
1658
2080
  "rectangle",
1659
2081
  "rounded-rectangle",
@@ -1664,6 +2086,7 @@ var nodeShapeSchema = z.enum([
1664
2086
  "cylinder"
1665
2087
  ]);
1666
2088
  var finiteNumberSchema = z.number().finite();
2089
+ var nonNegativeNumberSchema = finiteNumberSchema.min(0);
1667
2090
  var pointSchema = z.object({
1668
2091
  x: finiteNumberSchema,
1669
2092
  y: finiteNumberSchema
@@ -1674,6 +2097,12 @@ var insetsSchema = z.object({
1674
2097
  bottom: finiteNumberSchema,
1675
2098
  left: finiteNumberSchema
1676
2099
  });
2100
+ var nonNegativeInsetsSchema = z.object({
2101
+ top: nonNegativeNumberSchema,
2102
+ right: nonNegativeNumberSchema,
2103
+ bottom: nonNegativeNumberSchema,
2104
+ left: nonNegativeNumberSchema
2105
+ });
1677
2106
  var labelSchema = z.union([
1678
2107
  z.string(),
1679
2108
  z.object({
@@ -1683,8 +2112,19 @@ var labelSchema = z.union([
1683
2112
  ]);
1684
2113
  var styleSchema = z.object({
1685
2114
  fill: z.string().optional(),
1686
- stroke: z.string().optional()
2115
+ stroke: z.string().optional(),
2116
+ fontFamily: z.string().optional(),
2117
+ fontSize: finiteNumberSchema.optional()
1687
2118
  });
2119
+ var blockCellSchema = z.union([
2120
+ z.string(),
2121
+ z.object({
2122
+ text: z.string(),
2123
+ fill: z.string().optional(),
2124
+ stroke: z.string().optional(),
2125
+ style: styleSchema.optional()
2126
+ })
2127
+ ]);
1688
2128
  var portSideSchema = z.enum(["top", "right", "bottom", "left"]);
1689
2129
  var portKindSchema = z.enum(["proxy", "flow"]);
1690
2130
  var portSchema = z.object({
@@ -1745,11 +2185,17 @@ var groupSchema = z.object({
1745
2185
  label: labelSchema.optional(),
1746
2186
  nodes: z.array(z.string()).optional(),
1747
2187
  groups: z.array(z.string()).optional(),
1748
- padding: insetsSchema.optional()
2188
+ padding: insetsSchema.optional(),
2189
+ headerHeight: nonNegativeNumberSchema.optional(),
2190
+ labelPosition: z.enum(["top", "inside", "outside"]).optional(),
2191
+ direction: z.enum(["horizontal", "vertical"]).optional()
1749
2192
  });
1750
2193
  var swimlaneSchema = z.object({
1751
2194
  label: labelSchema.optional(),
1752
2195
  orientation: z.enum(["vertical", "horizontal"]).optional(),
2196
+ layout: z.enum(["overlay", "contract"]).optional(),
2197
+ headerHeight: nonNegativeNumberSchema.optional(),
2198
+ padding: nonNegativeNumberSchema.optional(),
1753
2199
  lanes: z.record(
1754
2200
  z.string(),
1755
2201
  z.object({
@@ -1758,6 +2204,122 @@ var swimlaneSchema = z.object({
1758
2204
  })
1759
2205
  )
1760
2206
  });
2207
+ var matrixSchema = z.object({
2208
+ id: z.string(),
2209
+ rows: z.array(z.string()),
2210
+ cols: z.array(z.string()),
2211
+ cells: z.array(z.array(blockCellSchema)),
2212
+ position: pointSchema.optional(),
2213
+ size: z.object({
2214
+ width: nonNegativeNumberSchema,
2215
+ height: nonNegativeNumberSchema
2216
+ }).optional(),
2217
+ style: styleSchema.optional()
2218
+ }).superRefine((matrix, context) => {
2219
+ checkDuplicateValues("matrix row", matrix.rows, context, (index) => [
2220
+ "rows",
2221
+ index
2222
+ ]);
2223
+ checkDuplicateValues("matrix column", matrix.cols, context, (index) => [
2224
+ "cols",
2225
+ index
2226
+ ]);
2227
+ if (matrix.cells.length !== matrix.rows.length) {
2228
+ context.addIssue({
2229
+ code: "custom",
2230
+ message: `Matrix cells must contain exactly ${matrix.rows.length} row(s).`,
2231
+ path: ["cells"]
2232
+ });
2233
+ }
2234
+ matrix.cells.forEach((row, rowIndex) => {
2235
+ if (row.length !== matrix.cols.length) {
2236
+ context.addIssue({
2237
+ code: "custom",
2238
+ message: `Matrix cell row must contain exactly ${matrix.cols.length} column(s).`,
2239
+ path: ["cells", rowIndex]
2240
+ });
2241
+ }
2242
+ });
2243
+ });
2244
+ var tableColumnSchema = z.object({
2245
+ id: z.string(),
2246
+ label: labelSchema
2247
+ });
2248
+ var tableRowSchema = z.object({
2249
+ id: z.string(),
2250
+ cells: z.record(z.string(), blockCellSchema)
2251
+ });
2252
+ var tableSchema = z.object({
2253
+ id: z.string(),
2254
+ columns: z.array(tableColumnSchema),
2255
+ rows: z.array(tableRowSchema),
2256
+ position: pointSchema.optional(),
2257
+ size: z.object({
2258
+ width: nonNegativeNumberSchema,
2259
+ height: nonNegativeNumberSchema
2260
+ }).optional(),
2261
+ style: styleSchema.optional()
2262
+ }).superRefine((table, context) => {
2263
+ checkDuplicateIds("column", table.columns, context, (index) => [
2264
+ "columns",
2265
+ index,
2266
+ "id"
2267
+ ]);
2268
+ checkDuplicateIds("row", table.rows, context, (index) => [
2269
+ "rows",
2270
+ index,
2271
+ "id"
2272
+ ]);
2273
+ const columnIds = new Set(table.columns.map((column) => column.id));
2274
+ table.rows.forEach((row, rowIndex) => {
2275
+ for (const columnId of Object.keys(row.cells)) {
2276
+ if (!columnIds.has(columnId)) {
2277
+ context.addIssue({
2278
+ code: "custom",
2279
+ message: `Table row cell references undeclared column "${columnId}".`,
2280
+ path: ["rows", rowIndex, "cells", columnId]
2281
+ });
2282
+ }
2283
+ }
2284
+ });
2285
+ });
2286
+ var panelItemSchema = z.union([
2287
+ z.string(),
2288
+ z.object({
2289
+ id: z.string().optional(),
2290
+ label: labelSchema,
2291
+ detail: labelSchema.optional(),
2292
+ style: styleSchema.optional()
2293
+ })
2294
+ ]);
2295
+ var evidencePanelSchema = z.object({
2296
+ id: z.string(),
2297
+ kind: z.enum(["legend", "rule", "note", "verification"]),
2298
+ items: z.array(panelItemSchema),
2299
+ position: pointSchema.optional(),
2300
+ size: z.object({
2301
+ width: nonNegativeNumberSchema,
2302
+ height: nonNegativeNumberSchema
2303
+ }).optional(),
2304
+ style: styleSchema.optional()
2305
+ }).superRefine((panel, context) => {
2306
+ const firstIndexByItemId = /* @__PURE__ */ new Map();
2307
+ panel.items.forEach((item, index) => {
2308
+ if (typeof item === "string" || item.id === void 0) {
2309
+ return;
2310
+ }
2311
+ const firstIndex = firstIndexByItemId.get(item.id);
2312
+ if (firstIndex === void 0) {
2313
+ firstIndexByItemId.set(item.id, index);
2314
+ return;
2315
+ }
2316
+ context.addIssue({
2317
+ code: "custom",
2318
+ message: `Duplicate evidence panel item id "${item.id}".`,
2319
+ path: ["items", index, "id"]
2320
+ });
2321
+ });
2322
+ });
1761
2323
  var exactPositionConstraintSchema = z.object({
1762
2324
  kind: z.literal("exact-position"),
1763
2325
  target: z.string().optional(),
@@ -1815,7 +2377,8 @@ var diagramDslSchema = z.object({
1815
2377
  title: z.string().optional(),
1816
2378
  direction: directionSchema.optional(),
1817
2379
  layout: z.object({
1818
- direction: directionSchema.optional()
2380
+ direction: directionSchema.optional(),
2381
+ primaryReadingDirection: primaryReadingDirectionSchema.optional()
1819
2382
  }).optional(),
1820
2383
  routing: z.object({
1821
2384
  kind: routeKindSchema.optional(),
@@ -1828,17 +2391,33 @@ var diagramDslSchema = z.object({
1828
2391
  edges: z.array(edgeSchema).optional(),
1829
2392
  groups: z.record(z.string(), groupSchema).optional(),
1830
2393
  swimlanes: z.record(z.string(), swimlaneSchema).optional(),
2394
+ matrices: z.array(matrixSchema).optional(),
2395
+ tables: z.array(tableSchema).optional(),
2396
+ evidencePanels: z.array(evidencePanelSchema).optional(),
1831
2397
  constraints: z.array(constraintSchema).optional(),
1832
2398
  frame: z.object({
1833
2399
  kind: z.string(),
1834
2400
  context: z.string().optional(),
1835
2401
  name: z.string().optional(),
1836
2402
  titleTab: z.string(),
2403
+ headerHeight: nonNegativeNumberSchema.optional(),
2404
+ padding: z.union([nonNegativeNumberSchema, nonNegativeInsetsSchema]).optional(),
2405
+ labelPosition: z.enum(["top", "inside", "outside"]).optional(),
2406
+ direction: z.enum(["horizontal", "vertical"]).optional(),
1837
2407
  style: styleSchema.optional()
1838
2408
  }).optional(),
1839
2409
  output: z.object({
1840
2410
  format: outputFormatSchema.optional()
1841
2411
  }).optional()
2412
+ }).superRefine((diagram, context) => {
2413
+ checkDuplicateEvidenceBlockIds("matrices", diagram.matrices, context);
2414
+ checkDuplicateEvidenceBlockIds("tables", diagram.tables, context);
2415
+ checkDuplicateEvidenceBlockIds(
2416
+ "evidencePanels",
2417
+ diagram.evidencePanels,
2418
+ context
2419
+ );
2420
+ checkDuplicateEvidenceBlockIdsAcrossTypes(diagram, context);
1842
2421
  });
1843
2422
  function validateDiagramDsl(value) {
1844
2423
  const result = diagramDslSchema.safeParse(value);
@@ -1858,25 +2437,81 @@ function toDiagnosticPath(path) {
1858
2437
  (segment) => typeof segment === "string" || typeof segment === "number" ? [segment] : []
1859
2438
  );
1860
2439
  }
1861
-
1862
- // src/dsl/parse.ts
1863
- var DEFAULT_DSL_MAX_BYTES = 1e6;
1864
- function parseDiagramDsl(source, options = {}) {
1865
- const maxBytes = options.maxBytes ?? DEFAULT_DSL_MAX_BYTES;
1866
- if (Buffer.byteLength(source, "utf8") > maxBytes) {
1867
- return {
1868
- diagnostics: [
1869
- createParseDiagnostic(
1870
- "parse.input.too-large",
1871
- `Input exceeds the ${maxBytes} byte limit.`,
1872
- "Split the diagram into smaller inputs or raise maxBytes for trusted sources."
1873
- )
1874
- ]
1875
- };
1876
- }
1877
- const parsed = parseSource(source, options);
1878
- if (parsed.value === void 0 || hasErrorDiagnostics(parsed.diagnostics)) {
1879
- return { diagnostics: sortDslDiagnostics(parsed.diagnostics) };
2440
+ function checkDuplicateEvidenceBlockIds(collection, blocks, context) {
2441
+ checkDuplicateIds(
2442
+ `evidence block id in ${collection}`,
2443
+ blocks ?? [],
2444
+ context,
2445
+ (index) => [collection, index, "id"]
2446
+ );
2447
+ }
2448
+ function checkDuplicateIds(label, items, context, pathForIndex) {
2449
+ const firstIndexById = /* @__PURE__ */ new Map();
2450
+ items.forEach((item, index) => {
2451
+ const firstIndex = firstIndexById.get(item.id);
2452
+ if (firstIndex === void 0) {
2453
+ firstIndexById.set(item.id, index);
2454
+ return;
2455
+ }
2456
+ context.addIssue({
2457
+ code: "custom",
2458
+ message: `Duplicate ${label} "${item.id}".`,
2459
+ path: pathForIndex(index)
2460
+ });
2461
+ });
2462
+ }
2463
+ function checkDuplicateValues(label, values, context, pathForIndex) {
2464
+ const firstIndexByValue = /* @__PURE__ */ new Map();
2465
+ values.forEach((value, index) => {
2466
+ const firstIndex = firstIndexByValue.get(value);
2467
+ if (firstIndex === void 0) {
2468
+ firstIndexByValue.set(value, index);
2469
+ return;
2470
+ }
2471
+ context.addIssue({
2472
+ code: "custom",
2473
+ message: `Duplicate ${label} "${value}".`,
2474
+ path: pathForIndex(index)
2475
+ });
2476
+ });
2477
+ }
2478
+ function checkDuplicateEvidenceBlockIdsAcrossTypes(diagram, context) {
2479
+ const firstById = /* @__PURE__ */ new Map();
2480
+ for (const collection of ["matrices", "tables", "evidencePanels"]) {
2481
+ const blocks = diagram[collection] ?? [];
2482
+ blocks.forEach((block, index) => {
2483
+ const first = firstById.get(block.id);
2484
+ if (first === void 0) {
2485
+ firstById.set(block.id, { collection, index });
2486
+ return;
2487
+ }
2488
+ context.addIssue({
2489
+ code: "custom",
2490
+ message: `Duplicate evidence block id "${block.id}" across ${first.collection} and ${collection}.`,
2491
+ path: [collection, index, "id"]
2492
+ });
2493
+ });
2494
+ }
2495
+ }
2496
+
2497
+ // src/dsl/parse.ts
2498
+ var DEFAULT_DSL_MAX_BYTES = 1e6;
2499
+ function parseDiagramDsl(source, options = {}) {
2500
+ const maxBytes = options.maxBytes ?? DEFAULT_DSL_MAX_BYTES;
2501
+ if (Buffer.byteLength(source, "utf8") > maxBytes) {
2502
+ return {
2503
+ diagnostics: [
2504
+ createParseDiagnostic(
2505
+ "parse.input.too-large",
2506
+ `Input exceeds the ${maxBytes} byte limit.`,
2507
+ "Split the diagram into smaller inputs or raise maxBytes for trusted sources."
2508
+ )
2509
+ ]
2510
+ };
2511
+ }
2512
+ const parsed = parseSource(source, options);
2513
+ if (parsed.value === void 0 || hasErrorDiagnostics(parsed.diagnostics)) {
2514
+ return { diagnostics: sortDslDiagnostics(parsed.diagnostics) };
1880
2515
  }
1881
2516
  const expanded = expandEdgeShorthand(parsed.value);
1882
2517
  if (hasErrorDiagnostics(expanded.diagnostics)) {
@@ -2059,6 +2694,15 @@ function exportExcalidraw(diagram, options = {}) {
2059
2694
  elements.push(text);
2060
2695
  }
2061
2696
  }
2697
+ for (const matrix of diagram.matrices ?? []) {
2698
+ elements.push(...renderMatrixBlock(matrix));
2699
+ }
2700
+ for (const table of diagram.tables ?? []) {
2701
+ elements.push(...renderTableBlock(table));
2702
+ }
2703
+ for (const panel of diagram.evidencePanels ?? []) {
2704
+ elements.push(...renderEvidencePanel(panel));
2705
+ }
2062
2706
  for (const edge of diagram.edges) {
2063
2707
  elements.push(renderArrow(edge));
2064
2708
  }
@@ -2091,6 +2735,83 @@ function renderNode(node, groupIds) {
2091
2735
  groupIds
2092
2736
  };
2093
2737
  }
2738
+ function renderMatrixBlock(matrix) {
2739
+ const containerId = `matrix:${matrix.id}`;
2740
+ const groupIds = [containerId];
2741
+ const label = blockText([
2742
+ matrix.id,
2743
+ `row | ${matrix.cols.join(" | ")}`,
2744
+ ...matrix.rows.map((rowId, rowIndex) => {
2745
+ const row = matrix.cells[rowIndex] ?? [];
2746
+ return `${rowId}: ${matrix.cols.map((_, columnIndex) => row[columnIndex]?.text ?? "").join(" | ")}`;
2747
+ })
2748
+ ]);
2749
+ return [
2750
+ {
2751
+ ...baseElement(containerId, "rectangle", matrix.box),
2752
+ backgroundColor: matrix.style?.fill ?? "#f8fafc",
2753
+ strokeColor: matrix.style?.stroke ?? "#374151",
2754
+ groupIds
2755
+ },
2756
+ renderTextBlock(
2757
+ `matrix-text:${matrix.id}`,
2758
+ label,
2759
+ matrix.box,
2760
+ containerId,
2761
+ groupIds
2762
+ )
2763
+ ];
2764
+ }
2765
+ function renderTableBlock(table) {
2766
+ const containerId = `table:${table.id}`;
2767
+ const groupIds = [containerId];
2768
+ const label = blockText([
2769
+ table.columns.map((column) => column.label.text).join(" | "),
2770
+ ...table.rows.map(
2771
+ (row) => table.columns.map((column) => row.cells[column.id]?.text ?? "").join(" | ")
2772
+ )
2773
+ ]);
2774
+ return [
2775
+ {
2776
+ ...baseElement(containerId, "rectangle", table.box),
2777
+ backgroundColor: table.style?.fill ?? "#f8fafc",
2778
+ strokeColor: table.style?.stroke ?? "#374151",
2779
+ groupIds
2780
+ },
2781
+ renderTextBlock(
2782
+ `table-text:${table.id}`,
2783
+ label,
2784
+ table.box,
2785
+ containerId,
2786
+ groupIds
2787
+ )
2788
+ ];
2789
+ }
2790
+ function renderEvidencePanel(panel) {
2791
+ const containerId = `evidence-panel:${panel.id}`;
2792
+ const groupIds = [containerId];
2793
+ const label = blockText([
2794
+ `${panel.kind}: ${panel.id}`,
2795
+ ...panel.items.map(
2796
+ (item) => item.detail?.text === void 0 ? item.label.text : `${item.label.text}: ${item.detail.text}`
2797
+ )
2798
+ ]);
2799
+ return [
2800
+ {
2801
+ ...baseElement(containerId, "rectangle", panel.box),
2802
+ backgroundColor: panel.style?.fill ?? panelKindFill(panel.kind),
2803
+ strokeColor: panel.style?.stroke ?? "#374151",
2804
+ groupIds
2805
+ },
2806
+ renderTextBlock(
2807
+ `evidence-panel-text:${panel.id}`,
2808
+ label,
2809
+ panel.box,
2810
+ containerId,
2811
+ groupIds
2812
+ )
2813
+ ];
2814
+ }
2094
2815
  function renderArrow(edge) {
2095
2816
  const first = edge.points[0];
2096
2817
  if (first === void 0) {
@@ -2150,6 +2871,34 @@ function renderText(id, label, box, containerId, groupIds) {
2150
2871
  versionNonce: seedFor(`${id}:nonce`)
2151
2872
  };
2152
2873
  }
2874
+ function renderTextBlock(id, text, box, containerId, groupIds) {
2875
+ const fontSize = 12;
2876
+ return {
2877
+ ...baseElement(id, "text", {
2878
+ x: box.x + 8,
2879
+ y: box.y + 8,
2880
+ width: Math.max(0, box.width - 16),
2881
+ height: Math.max(fontSize, box.height - 16)
2882
+ }),
2883
+ backgroundColor: "transparent",
2884
+ strokeColor: "#111827",
2885
+ groupIds,
2886
+ text,
2887
+ fontSize,
2888
+ fontFamily: 1,
2889
+ textAlign: "left",
2890
+ verticalAlign: "top",
2891
+ baseline: fontSize,
2892
+ containerId,
2893
+ originalText: text,
2894
+ lineHeight: 1.25,
2895
+ boundElements: null,
2896
+ link: null,
2897
+ locked: false,
2898
+ seed: seedFor(id),
2899
+ versionNonce: seedFor(`${id}:nonce`)
2900
+ };
2901
+ }
2153
2902
  function baseElement(id, type, box) {
2154
2903
  return {
2155
2904
  id,
@@ -2227,6 +2976,21 @@ function groupGroupIds(groupId) {
2227
2976
  function groupElementIdFor(groupId) {
2228
2977
  return `group:${groupId}`;
2229
2978
  }
2979
+ function blockText(lines) {
2980
+ return lines.filter((line) => line.length > 0).join("\n");
2981
+ }
2982
+ function panelKindFill(kind) {
2983
+ switch (kind) {
2984
+ case "legend":
2985
+ return "#ecfdf5";
2986
+ case "rule":
2987
+ return "#eff6ff";
2988
+ case "note":
2989
+ return "#fffbeb";
2990
+ case "verification":
2991
+ return "#fef2f2";
2992
+ }
2993
+ }
2230
2994
  function pointsBox(points) {
2231
2995
  const xs = points.map((point2) => point2.x);
2232
2996
  const ys = points.map((point2) => point2.y);
@@ -2264,42 +3028,253 @@ var GROUP_FILL = "#f9fafb";
2264
3028
  var STROKE = "#374151";
2265
3029
  var EDGE_STROKE = "#111827";
2266
3030
  var FONT_FAMILY = "Arial, sans-serif";
3031
+ var EVIDENCE_FILL = "#f8fafc";
3032
+ var EVIDENCE_HEADER_FILL = "#e5e7eb";
3033
+ var EVIDENCE_TEXT_FONT_SIZE = 10;
3034
+ var EVIDENCE_TEXT_LINE_HEIGHT = 12;
3035
+ var EVIDENCE_PANEL_KIND_FILL = {
3036
+ legend: "#ecfdf5",
3037
+ rule: "#eff6ff",
3038
+ note: "#fffbeb",
3039
+ verification: "#fef2f2"
3040
+ };
2267
3041
  function exportSvg(diagram, options = {}) {
2268
3042
  const title = options.title ?? diagram.title;
2269
- const lines = [
3043
+ const annotations = diagram.textAnnotations ?? [];
3044
+ return `${[
2270
3045
  `<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="${formatBoxViewBox(diagram.bounds)}">`,
2271
3046
  ...title === void 0 ? [] : [` <title>${escapeXml(title)}</title>`],
2272
3047
  ` <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"/>`,
2273
- ...diagram.frame === void 0 ? [] : [indent(renderFrame(diagram.frame))],
3048
+ ...diagram.frame === void 0 ? [] : [indent(renderFrame(diagram.frame, annotations))],
2274
3049
  ...(diagram.swimlanes ?? []).flatMap(
2275
- (swimlane) => renderSwimlane(swimlane)
3050
+ (swimlane) => renderSwimlane(swimlane, annotations)
2276
3051
  ),
2277
3052
  ...diagram.groups.map((group) => indent(renderGroup2(group))),
3053
+ ...(diagram.matrices ?? []).flatMap(
3054
+ (matrix) => indentLines(renderMatrixBlock2(matrix))
3055
+ ),
3056
+ ...(diagram.tables ?? []).flatMap(
3057
+ (table) => indentLines(renderTableBlock2(table))
3058
+ ),
3059
+ ...(diagram.evidencePanels ?? []).flatMap(
3060
+ (panel) => indentLines(renderEvidencePanel2(panel))
3061
+ ),
2278
3062
  ...diagram.edges.flatMap((edge) => {
2279
3063
  const path = renderEdgePath(edge);
2280
- if (path === void 0) {
2281
- return [];
2282
- }
2283
- return [indent(path), indent(renderArrowhead(edge))];
3064
+ return path === void 0 ? [] : [indent(path), indent(renderArrowhead(edge))];
2284
3065
  }),
2285
3066
  ...diagram.nodes.map((node) => indent(renderNode2(node))),
2286
- ...diagram.nodes.flatMap((node) => renderCompartments(node)),
2287
- ...diagram.nodes.flatMap((node) => renderPorts(node)),
3067
+ ...diagram.nodes.flatMap((node) => renderCompartments(node, annotations)),
3068
+ ...diagram.nodes.flatMap((node) => renderPorts(node, annotations)),
2288
3069
  ...diagram.groups.flatMap(
2289
- (group) => renderLabel(group.label, group.box, group)
3070
+ (group) => renderLabel(group.label, group.box, group, annotations, "group-label")
2290
3071
  ),
2291
3072
  ...diagram.nodes.flatMap(
2292
- (node) => node.compartments === void 0 ? renderLabel(node.label, node.box, node) : []
3073
+ (node) => node.compartments === void 0 ? renderLabel(node.label, node.box, node, annotations, "node-label") : []
2293
3074
  ),
2294
- ...diagram.edges.flatMap((edge) => renderEdgeLabel(edge)),
3075
+ ...diagram.edges.flatMap((edge) => renderEdgeLabel(edge, annotations)),
2295
3076
  "</svg>"
2296
- ];
2297
- return `${lines.join("\n")}
3077
+ ].join("\n")}
2298
3078
  `;
2299
3079
  }
2300
3080
  function renderGroup2(group) {
2301
3081
  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"/>`;
2302
3082
  }
3083
+ function renderMatrixBlock2(matrix) {
3084
+ const columnCount = Math.max(1, matrix.cols.length);
3085
+ const rowCount = matrix.rows.length;
3086
+ const rowHeaderWidth = rowCount > 0 ? Math.min(96, matrix.box.width * 0.28) : 0;
3087
+ const dataWidth = Math.max(0, matrix.box.width - rowHeaderWidth);
3088
+ const cellWidth = dataWidth / columnCount;
3089
+ const rowHeight = matrix.box.height / Math.max(1, rowCount + 1);
3090
+ const lines = [
3091
+ `<g class="matrix-block" data-id="${escapeAttribute(matrix.id)}" data-row-count="${rowCount}" data-column-count="${matrix.cols.length}">`,
3092
+ ` <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)}"/>`
3093
+ ];
3094
+ if (rowHeaderWidth > 0) {
3095
+ lines.push(
3096
+ ` <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}"/>`
3097
+ );
3098
+ }
3099
+ for (let columnIndex = 0; columnIndex < matrix.cols.length; columnIndex += 1) {
3100
+ const column = matrix.cols[columnIndex];
3101
+ if (column === void 0) {
3102
+ continue;
3103
+ }
3104
+ const x = matrix.box.x + rowHeaderWidth + columnIndex * cellWidth;
3105
+ lines.push(
3106
+ ` <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}"/>`,
3107
+ renderEvidenceText(
3108
+ "matrix-column-label",
3109
+ matrix.columnLabelLayouts?.[columnIndex]?.lines ?? [column],
3110
+ {
3111
+ x,
3112
+ y: matrix.box.y,
3113
+ width: cellWidth,
3114
+ height: rowHeight
3115
+ }
3116
+ )
3117
+ );
3118
+ }
3119
+ for (let rowIndex = 0; rowIndex < matrix.rows.length; rowIndex += 1) {
3120
+ const row = matrix.rows[rowIndex];
3121
+ const cells = matrix.cells[rowIndex] ?? [];
3122
+ if (row === void 0) {
3123
+ continue;
3124
+ }
3125
+ if (rowHeaderWidth > 0) {
3126
+ const rowHeaderBox = {
3127
+ x: matrix.box.x,
3128
+ y: matrix.box.y + (rowIndex + 1) * rowHeight,
3129
+ width: rowHeaderWidth,
3130
+ height: rowHeight
3131
+ };
3132
+ lines.push(
3133
+ ` <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}"/>`,
3134
+ renderEvidenceText(
3135
+ "matrix-row-label",
3136
+ matrix.rowLabelLayouts?.[rowIndex]?.lines ?? [row],
3137
+ rowHeaderBox
3138
+ )
3139
+ );
3140
+ }
3141
+ for (let columnIndex = 0; columnIndex < matrix.cols.length; columnIndex += 1) {
3142
+ const column = matrix.cols[columnIndex];
3143
+ if (column === void 0) {
3144
+ continue;
3145
+ }
3146
+ const cell2 = cells[columnIndex] ?? { text: "" };
3147
+ const box = {
3148
+ x: matrix.box.x + rowHeaderWidth + columnIndex * cellWidth,
3149
+ y: matrix.box.y + (rowIndex + 1) * rowHeight,
3150
+ width: cellWidth,
3151
+ height: rowHeight
3152
+ };
3153
+ lines.push(
3154
+ ` <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)}"/>`,
3155
+ renderEvidenceText(
3156
+ "matrix-cell-label",
3157
+ matrix.cellLabelLayouts?.[rowIndex]?.[columnIndex]?.lines ?? [
3158
+ cell2.text
3159
+ ],
3160
+ box
3161
+ )
3162
+ );
3163
+ }
3164
+ }
3165
+ lines.push("</g>");
3166
+ return lines;
3167
+ }
3168
+ function renderTableBlock2(table) {
3169
+ const columnCount = Math.max(1, table.columns.length);
3170
+ const rowHeight = table.box.height / Math.max(1, table.rows.length + 1);
3171
+ const lines = [
3172
+ `<g class="table-block" data-id="${escapeAttribute(table.id)}" data-row-count="${table.rows.length}" data-column-count="${table.columns.length}">`,
3173
+ ` <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)}"/>`,
3174
+ ` <g class="table-header" data-column-count="${table.columns.length}">`
3175
+ ];
3176
+ for (let columnIndex = 0; columnIndex < table.columns.length; columnIndex += 1) {
3177
+ const column = table.columns[columnIndex];
3178
+ if (column === void 0) {
3179
+ continue;
3180
+ }
3181
+ const columnBox = tableCellBox(
3182
+ table,
3183
+ columnIndex,
3184
+ 0,
3185
+ rowHeight,
3186
+ columnCount
3187
+ );
3188
+ lines.push(
3189
+ ` <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}"/>`,
3190
+ ` ${renderEvidenceText("table-header-label", table.columnLabelLayouts?.[columnIndex]?.lines ?? [column.label.text], columnBox)}`
3191
+ );
3192
+ }
3193
+ lines.push(" </g>");
3194
+ for (let rowIndex = 0; rowIndex < table.rows.length; rowIndex += 1) {
3195
+ const row = table.rows[rowIndex];
3196
+ if (row === void 0) {
3197
+ continue;
3198
+ }
3199
+ const rowBox = {
3200
+ x: table.box.x,
3201
+ y: table.box.y + (rowIndex + 1) * rowHeight,
3202
+ width: table.box.width,
3203
+ height: rowHeight
3204
+ };
3205
+ const rowClass = rowIndex % 2 === 0 ? "table-row-even" : "table-row-odd";
3206
+ lines.push(
3207
+ ` <g class="table-row ${rowClass}" data-row="${escapeAttribute(row.id)}">`,
3208
+ ` <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"/>`
3209
+ );
3210
+ for (let columnIndex = 0; columnIndex < table.columns.length; columnIndex += 1) {
3211
+ const column = table.columns[columnIndex];
3212
+ if (column === void 0) {
3213
+ continue;
3214
+ }
3215
+ const cell2 = row.cells[column.id] ?? { text: "" };
3216
+ const cellBox = tableCellBox(
3217
+ table,
3218
+ columnIndex,
3219
+ rowIndex + 1,
3220
+ rowHeight,
3221
+ columnCount
3222
+ );
3223
+ lines.push(
3224
+ ` <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)}"/>`,
3225
+ ` ${renderEvidenceText("table-cell-label", table.cellLabelLayouts?.[rowIndex]?.[columnIndex]?.lines ?? [cell2.text], cellBox)}`
3226
+ );
3227
+ }
3228
+ lines.push(" </g>");
3229
+ }
3230
+ lines.push("</g>");
3231
+ return lines;
3232
+ }
3233
+ function renderEvidencePanel2(panel) {
3234
+ const titleWidth = Math.min(panel.box.width * 0.36, 140);
3235
+ const itemBox = {
3236
+ x: panel.box.x + titleWidth,
3237
+ y: panel.box.y,
3238
+ width: panel.box.width - titleWidth,
3239
+ height: panel.box.height
3240
+ };
3241
+ const titleBox = {
3242
+ x: panel.box.x,
3243
+ y: panel.box.y,
3244
+ width: titleWidth,
3245
+ height: panel.box.height
3246
+ };
3247
+ const itemHeight = panel.box.height / Math.max(1, panel.items.length);
3248
+ const lines = [
3249
+ `<g class="evidence-panel evidence-panel--${panel.kind}" data-id="${escapeAttribute(panel.id)}" data-kind="${escapeAttribute(panel.kind)}" data-item-count="${panel.items.length}">`,
3250
+ ` <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)}"/>`,
3251
+ ` <g class="evidence-panel-title-cell">`,
3252
+ ` <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}"/>`,
3253
+ ` ${renderEvidenceText("evidence-panel-title", panel.titleLayout?.lines ?? [`${panel.kind}: ${panel.id}`], titleBox)}`,
3254
+ " </g>",
3255
+ ` <g class="evidence-panel-items-cell">`,
3256
+ ` <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}"/>`
3257
+ ];
3258
+ for (let index = 0; index < panel.items.length; index += 1) {
3259
+ const item = panel.items[index];
3260
+ if (item === void 0) {
3261
+ continue;
3262
+ }
3263
+ const text = panelItemText(item.label.text, item.detail?.text);
3264
+ const box = {
3265
+ x: itemBox.x,
3266
+ y: itemBox.y + index * itemHeight,
3267
+ width: itemBox.width,
3268
+ height: itemHeight
3269
+ };
3270
+ lines.push(
3271
+ ` <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")}"/>`,
3272
+ ` ${renderEvidenceText("evidence-panel-item-label", panel.itemLayouts?.[index]?.lines ?? [text], box)}`
3273
+ );
3274
+ }
3275
+ lines.push(" </g>", "</g>");
3276
+ return lines;
3277
+ }
2303
3278
  function renderNode2(node) {
2304
3279
  const fill = node.style?.fill ?? NODE_FILL;
2305
3280
  const stroke = node.style?.stroke ?? STROKE;
@@ -2319,18 +3294,27 @@ function renderNode2(node) {
2319
3294
  return `<path ${common} d="${formatCylinderPath(node.box)}"/>`;
2320
3295
  }
2321
3296
  }
2322
- function renderFrame(frame) {
3297
+ function renderFrame(frame, annotations) {
2323
3298
  const stroke = frame.style?.stroke ?? "#6b7280";
2324
3299
  const fill = frame.style?.fill ?? "transparent";
2325
3300
  return [
2326
3301
  `<g class="sysml-frame" data-kind="${escapeAttribute(frame.kind)}">`,
2327
3302
  ` <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)}"/>`,
2328
3303
  ` <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)}"/>`,
2329
- ` <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>`,
3304
+ ...renderSolvedTextAnnotation(
3305
+ findAnnotation(annotations, "frame-title", frame.kind),
3306
+ `sysml-title-tab-label`,
3307
+ {
3308
+ indent: " ",
3309
+ mode: "center",
3310
+ fallbackText: frame.titleTab}
3311
+ ) ?? [
3312
+ ` <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>`
3313
+ ],
2330
3314
  "</g>"
2331
3315
  ].join("\n");
2332
3316
  }
2333
- function renderSwimlane(swimlane) {
3317
+ function renderSwimlane(swimlane, annotations) {
2334
3318
  if (swimlane.box === void 0) {
2335
3319
  return [];
2336
3320
  }
@@ -2345,24 +3329,60 @@ function renderSwimlane(swimlane) {
2345
3329
  lines.push(
2346
3330
  ` <rect class="swimlane-lane" data-lane="${escapeAttribute(`${swimlane.id}.${lane.id}`)}" x="${formatNumber(lane.box.x)}" y="${formatNumber(lane.box.y)}" width="${formatNumber(lane.box.width)}" height="${formatNumber(lane.box.height)}" fill="none" stroke="${STROKE}"/>`
2347
3331
  );
3332
+ if (lane.headerBox !== void 0) {
3333
+ lines.push(
3334
+ ` <rect class="swimlane-header" data-lane-header="${escapeAttribute(`${swimlane.id}.${lane.id}`)}" x="${formatNumber(lane.headerBox.x)}" y="${formatNumber(lane.headerBox.y)}" width="${formatNumber(lane.headerBox.width)}" height="${formatNumber(lane.headerBox.height)}" fill="#f3f4f6" stroke="${STROKE}"/>`
3335
+ );
3336
+ }
3337
+ if (lane.contentBox !== void 0) {
3338
+ lines.push(
3339
+ ` <rect class="swimlane-content" data-lane-content="${escapeAttribute(`${swimlane.id}.${lane.id}`)}" x="${formatNumber(lane.contentBox.x)}" y="${formatNumber(lane.contentBox.y)}" width="${formatNumber(lane.contentBox.width)}" height="${formatNumber(lane.contentBox.height)}" fill="none" stroke="none"/>`
3340
+ );
3341
+ }
2348
3342
  if (lane.label?.text !== void 0) {
3343
+ const annotation = findAnnotation(
3344
+ annotations,
3345
+ "swimlane-label",
3346
+ `${swimlane.id}.${lane.id}`
3347
+ );
2349
3348
  lines.push(
2350
- ` <text class="swimlane-label" x="${formatNumber(lane.box.x + lane.box.width / 2)}" y="${formatNumber(lane.box.y + 16)}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="12" fill="#111827">${escapeXml(lane.label.text)}</text>`
3349
+ ...annotation === void 0 ? [
3350
+ renderSwimlaneLabel(
3351
+ swimlane,
3352
+ lane.label.text,
3353
+ lane.headerBox ?? lane.box
3354
+ )
3355
+ ] : renderSolvedTextAnnotation(annotation, "swimlane-label", {
3356
+ indent: " ",
3357
+ mode: "center",
3358
+ rotate: swimlane.orientation === "horizontal"
3359
+ }) ?? []
2351
3360
  );
2352
3361
  }
2353
3362
  }
2354
3363
  lines.push(" </g>");
2355
3364
  return lines;
2356
3365
  }
2357
- function renderPorts(node) {
3366
+ function renderPorts(node, annotations) {
2358
3367
  return (node.ports ?? []).flatMap((port) => [
2359
3368
  ` <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)}"/>`,
2360
- ...port.label?.text === void 0 ? [] : [
2361
- ` <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>`
2362
- ]
3369
+ ...port.label?.text === void 0 ? [] : (() => {
3370
+ const annotation = findAnnotation(
3371
+ annotations,
3372
+ "port-label",
3373
+ `${node.id}.${port.id}`
3374
+ );
3375
+ return annotation === void 0 ? [
3376
+ ` <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>`
3377
+ ] : renderSolvedTextAnnotation(annotation, "port-label", {
3378
+ indent: " ",
3379
+ mode: "center",
3380
+ textAnchor: port.side === "left" ? "end" : "start"
3381
+ }) ?? [];
3382
+ })()
2363
3383
  ]);
2364
3384
  }
2365
- function renderCompartments(node) {
3385
+ function renderCompartments(node, annotations) {
2366
3386
  const compartments2 = node.compartments;
2367
3387
  if (compartments2 === void 0) {
2368
3388
  return [];
@@ -2382,7 +3402,6 @@ function renderCompartments(node) {
2382
3402
  text
2383
3403
  }))
2384
3404
  ];
2385
- const lineHeight = 16;
2386
3405
  const lines = [
2387
3406
  ` <g class="compartment" data-for="${escapeAttribute(node.id)}">`
2388
3407
  ];
@@ -2391,32 +3410,72 @@ function renderCompartments(node) {
2391
3410
  if (row === void 0) {
2392
3411
  continue;
2393
3412
  }
2394
- const y = node.box.y + 18 + index * lineHeight;
3413
+ const y = node.box.y + 18 + index * 16;
2395
3414
  if (index > 1) {
2396
3415
  lines.push(
2397
3416
  ` <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}"/>`
2398
3417
  );
2399
3418
  }
3419
+ const annotation = findAnnotation(
3420
+ annotations,
3421
+ "compartment-row",
3422
+ node.id,
3423
+ index
3424
+ );
2400
3425
  lines.push(
2401
- ` <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>`
3426
+ ...annotation === void 0 ? [
3427
+ ` <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>`
3428
+ ] : renderSolvedTextAnnotation(
3429
+ annotation,
3430
+ `compartment-${row.className}`,
3431
+ {
3432
+ indent: " ",
3433
+ mode: "center"
3434
+ }
3435
+ ) ?? []
2402
3436
  );
2403
3437
  }
2404
3438
  lines.push(" </g>");
2405
3439
  return lines;
2406
3440
  }
2407
- function portLabelX(x, side) {
2408
- if (side === "left") {
2409
- return x - 8;
2410
- }
2411
- if (side === "right") {
2412
- return x + 8;
3441
+ function tableCellBox(table, columnIndex, rowIndex, rowHeight, columnCount) {
3442
+ const x = table.columnXOffsets[columnIndex] ?? table.box.x + table.box.width / columnCount * columnIndex;
3443
+ const nextX = table.columnXOffsets[columnIndex + 1] ?? table.box.x + table.box.width;
3444
+ return {
3445
+ x,
3446
+ y: table.box.y + rowIndex * rowHeight,
3447
+ width: nextX - x,
3448
+ height: rowHeight
3449
+ };
3450
+ }
3451
+ function renderEvidenceText(className, lines, box) {
3452
+ const fontSize = EVIDENCE_TEXT_FONT_SIZE;
3453
+ const lineHeight = EVIDENCE_TEXT_LINE_HEIGHT;
3454
+ const x = box.x + box.width / 2;
3455
+ if (lines.length <= 1) {
3456
+ 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>`;
2413
3457
  }
2414
- return x + 8;
3458
+ const totalHeight = (lines.length - 1) * lineHeight;
3459
+ const firstBaselineY = box.y + box.height / 2 - totalHeight / 2;
3460
+ return [
3461
+ `<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">`,
3462
+ ...lines.map(
3463
+ (line, index) => ` <tspan x="${formatNumber(x)}" y="${formatNumber(firstBaselineY + index * lineHeight)}">${escapeXml(line)}</tspan>`
3464
+ ),
3465
+ "</text>"
3466
+ ].join("\n");
2415
3467
  }
2416
- function renderRect(box, attributes) {
2417
- return `<rect ${attributes} x="${formatNumber(box.x)}" y="${formatNumber(box.y)}" width="${formatNumber(box.width)}" height="${formatNumber(box.height)}"/>`;
3468
+ function panelItemText(label, detail) {
3469
+ return detail === void 0 ? label : `${label}: ${detail}`;
2418
3470
  }
2419
- function renderLabel(label, box, item) {
3471
+ function renderLabel(label, box, item, annotations, surfaceKind) {
3472
+ const annotation = findAnnotation(annotations, surfaceKind, item.id);
3473
+ if (annotation !== void 0) {
3474
+ return renderSolvedTextAnnotation(annotation, "label", {
3475
+ indent: " ",
3476
+ mode: "center"
3477
+ }) ?? [];
3478
+ }
2420
3479
  const labelLayout = item.labelLayout;
2421
3480
  if (labelLayout?.lines !== void 0 && labelLayout.lines.length > 0) {
2422
3481
  const offset = { x: box.x, y: box.y };
@@ -2442,10 +3501,17 @@ function renderEdgePath(edge) {
2442
3501
  const dash = edge.style === "dashed" ? ' stroke-dasharray="6 4"' : "";
2443
3502
  return `<path class="edge" data-id="${escapeAttribute(edge.id)}" d="${formatPath(pathPointsBeforeArrowhead(edge.points))}" fill="none" stroke="${EDGE_STROKE}" stroke-width="1.5"${dash}/>`;
2444
3503
  }
2445
- function renderEdgeLabel(edge) {
3504
+ function renderEdgeLabel(edge, annotations) {
2446
3505
  if (edge.label?.text === void 0 || edge.points.length < 2) {
2447
3506
  return [];
2448
3507
  }
3508
+ const annotation = findAnnotation(annotations, "edge-label", edge.id);
3509
+ if (annotation !== void 0) {
3510
+ return renderSolvedTextAnnotation(annotation, "edge-label", {
3511
+ indent: " ",
3512
+ mode: "center"
3513
+ }) ?? [];
3514
+ }
2449
3515
  const placement = labelPlacementOnPolyline(edge.points);
2450
3516
  if (placement === void 0) {
2451
3517
  return [];
@@ -2459,6 +3525,85 @@ function renderArrowhead(edge) {
2459
3525
  const fill = edge.arrowhead === "hollowTriangle" ? "none" : EDGE_STROKE;
2460
3526
  return `<polygon class="edge-arrowhead" data-edge="${escapeAttribute(edge.id)}" points="${formatPoints([arrowhead.tip, arrowhead.left, arrowhead.right])}" fill="${fill}" stroke="${EDGE_STROKE}"/>`;
2461
3527
  }
3528
+ function renderSolvedTextAnnotation(annotation, className, options) {
3529
+ if (annotation === void 0) {
3530
+ return void 0;
3531
+ }
3532
+ const x = options.mode === "center" ? annotation.box.x + annotation.box.width / 2 : annotation.box.x;
3533
+ const y = options.mode === "center" ? annotation.box.y + annotation.box.height / 2 : annotation.box.y + annotation.box.height;
3534
+ const rotate = options.rotate ? ` transform="rotate(-90 ${formatNumber(x)} ${formatNumber(y)})"` : "";
3535
+ const attrs = [
3536
+ `class="${className}"`,
3537
+ `data-for="${escapeAttribute(annotation.ownerId)}"`,
3538
+ `data-text-surface="${escapeAttribute(annotation.surfaceKind)}"`,
3539
+ `data-owner-id="${escapeAttribute(annotation.ownerId)}"`,
3540
+ `data-text-backend="${escapeAttribute(annotation.textBackend ?? "deterministic")}"`,
3541
+ `font-family="${escapeAttribute(annotation.fontFamily)}"`,
3542
+ `font-size="${formatNumber(annotation.fontSize)}"`,
3543
+ `fill="#111827"`
3544
+ ];
3545
+ if (options.mode === "center") {
3546
+ attrs.push('text-anchor="middle"');
3547
+ } else {
3548
+ attrs.push(`text-anchor="${options.textAnchor ?? "start"}"`);
3549
+ }
3550
+ if (annotation.lines.length > 1) {
3551
+ return [
3552
+ `${options.indent}<text ${attrs.join(" ")}${rotate}>`,
3553
+ ...annotation.lines.map(
3554
+ (line2) => `${options.indent} <tspan x="${formatNumber(textLineX(annotation, line2, options))}" y="${formatNumber(annotation.box.y + line2.baselineY)}">${escapeXml(line2.text)}</tspan>`
3555
+ ),
3556
+ `${options.indent}</text>`
3557
+ ];
3558
+ }
3559
+ const line = annotation.lines[0];
3560
+ const text = line?.text ?? options.fallbackText ?? annotation.text;
3561
+ const singleLineAttrs = options.mode === "center" ? [...attrs, 'dominant-baseline="middle"'] : attrs;
3562
+ return [
3563
+ `${options.indent}<text ${singleLineAttrs.join(" ")} x="${formatNumber(x)}" y="${formatNumber(y)}"${rotate}>${escapeXml(text)}</text>`
3564
+ ];
3565
+ }
3566
+ function textLineX(annotation, line, options) {
3567
+ if (options.mode === "center") {
3568
+ return annotation.box.x + line.box.x + line.box.width / 2;
3569
+ }
3570
+ if ((options.textAnchor ?? "start") === "end") {
3571
+ return annotation.box.x + line.box.x + line.box.width;
3572
+ }
3573
+ return annotation.box.x + line.box.x;
3574
+ }
3575
+ function findAnnotation(annotations, surfaceKind, ownerId, index) {
3576
+ return annotations.find((annotation) => {
3577
+ if (annotation.surfaceKind !== surfaceKind) {
3578
+ return false;
3579
+ }
3580
+ if (annotation.ownerId !== ownerId) {
3581
+ return false;
3582
+ }
3583
+ if (index === void 0) {
3584
+ return annotation.surfaceIndex === void 0;
3585
+ }
3586
+ return annotation.surfaceIndex === index;
3587
+ });
3588
+ }
3589
+ function renderSwimlaneLabel(swimlane, text, labelBox) {
3590
+ const x = labelBox.x + labelBox.width / 2;
3591
+ const y = labelBox.y + labelBox.height / 2;
3592
+ const transform = swimlane.orientation === "horizontal" ? ` transform="rotate(-90 ${formatNumber(x)} ${formatNumber(y)})"` : "";
3593
+ 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>`;
3594
+ }
3595
+ function renderRect(box, attributes) {
3596
+ return `<rect ${attributes} x="${formatNumber(box.x)}" y="${formatNumber(box.y)}" width="${formatNumber(box.width)}" height="${formatNumber(box.height)}"/>`;
3597
+ }
3598
+ function portLabelX(x, side) {
3599
+ if (side === "left") {
3600
+ return x - 8;
3601
+ }
3602
+ if (side === "right") {
3603
+ return x + 8;
3604
+ }
3605
+ return x + 8;
3606
+ }
2462
3607
  function labelPlacementOnPolyline(points) {
2463
3608
  const segments = nonZeroSegments(points);
2464
3609
  const totalLength = segments.reduce(
@@ -2541,17 +3686,15 @@ function shapePoints(shape, box) {
2541
3686
  { x: right - skew, y: bottom },
2542
3687
  { x: left, y: bottom }
2543
3688
  ];
2544
- case "hexagon": {
2545
- const inset = Math.min(box.width * 0.2, 24);
3689
+ case "hexagon":
2546
3690
  return [
2547
- { x: left + inset, y: top },
2548
- { x: right - inset, y: top },
3691
+ { x: left + skew, y: top },
3692
+ { x: right - skew, y: top },
2549
3693
  { x: right, y: midY },
2550
- { x: right - inset, y: bottom },
2551
- { x: left + inset, y: bottom },
3694
+ { x: right - skew, y: bottom },
3695
+ { x: left + skew, y: bottom },
2552
3696
  { x: left, y: midY }
2553
3697
  ];
2554
- }
2555
3698
  }
2556
3699
  }
2557
3700
  function formatCylinderPath(box) {
@@ -2576,10 +3719,9 @@ function formatCylinderPath(box) {
2576
3719
  ].join(" ");
2577
3720
  }
2578
3721
  function formatPath(points) {
2579
- return points.map((point2, index) => {
2580
- const command = index === 0 ? "M" : "L";
2581
- return `${command} ${formatNumber(point2.x)} ${formatNumber(point2.y)}`;
2582
- }).join(" ");
3722
+ return points.map(
3723
+ (point2, index) => `${index === 0 ? "M" : "L"} ${formatNumber(point2.x)} ${formatNumber(point2.y)}`
3724
+ ).join(" ");
2583
3725
  }
2584
3726
  function formatPoints(points) {
2585
3727
  return points.map((point2) => `${formatNumber(point2.x)},${formatNumber(point2.y)}`).join(" ");
@@ -2602,6 +3744,18 @@ function escapeAttribute(value) {
2602
3744
  function indent(value) {
2603
3745
  return ` ${value}`;
2604
3746
  }
3747
+ function indentLines(values) {
3748
+ return values.map(indent);
3749
+ }
3750
+
3751
+ // src/ir/diagnostics.ts
3752
+ var DELIVERABILITY_DIAGNOSTIC_CODES = /* @__PURE__ */ new Set([
3753
+ "constraints.locked-target-not-moved",
3754
+ "routing.evidence.crossing_forbidden",
3755
+ "routing.obstacle.unavoidable",
3756
+ "route_obstacle_fallback",
3757
+ "routing.text-clearance.unresolved"
3758
+ ]);
2605
3759
  var DEFAULT_OPTIONS = {
2606
3760
  nodesep: 80,
2607
3761
  ranksep: 100,
@@ -2703,48 +3857,374 @@ function isValidDimension(value) {
2703
3857
  // src/routing/routes.ts
2704
3858
  function routeEdge(input) {
2705
3859
  const diagnostics = [];
3860
+ const softObstacles = input.obstacles ?? [];
3861
+ const hardObstacles = input.hardObstacles ?? [];
2706
3862
  const defaultAnchors = defaultAnchorsForGeometry(
2707
3863
  input.source.box,
2708
3864
  input.target.box,
2709
3865
  input.direction
2710
3866
  );
2711
- const source = getEdgePort(
2712
- input.source,
2713
- input.target.center,
2714
- input.sourceAnchor ?? defaultAnchors.sourceAnchor
2715
- );
2716
- const target = getEdgePort(
2717
- input.target,
2718
- input.source.center,
2719
- input.targetAnchor ?? defaultAnchors.targetAnchor
2720
- );
2721
3867
  if ((input.kind ?? "orthogonal") === "straight") {
2722
- return { points: simplifyRoute([source, target]), diagnostics };
2723
- }
2724
- const candidates = orthogonalCandidates(source, target, input.direction);
2725
- candidates.push(
2726
- ...expandedObstacleCandidates(
2727
- source,
2728
- target,
2729
- input.direction,
2730
- input.obstacles ?? []
2731
- )
2732
- );
2733
- for (const candidate of candidates) {
2734
- if (!routeIntersectsObstacles(candidate, input.obstacles ?? [])) {
2735
- return { points: simplifyRoute(candidate), diagnostics };
3868
+ const source = getEdgePort(
3869
+ input.source,
3870
+ input.target.center,
3871
+ input.sourceAnchor ?? defaultAnchors.sourceAnchor
3872
+ );
3873
+ const target = getEdgePort(
3874
+ input.target,
3875
+ input.source.center,
3876
+ input.targetAnchor ?? defaultAnchors.targetAnchor
3877
+ );
3878
+ const points = finalizeRoute(
3879
+ [source, target],
3880
+ softObstacles,
3881
+ hardObstacles,
3882
+ diagnostics
3883
+ );
3884
+ if (routeCrossesBoxes(points, hardObstacles)) {
3885
+ diagnostics.push({
3886
+ severity: "error",
3887
+ code: "routing.evidence.crossing_forbidden",
3888
+ message: "Straight route crosses hard evidence block obstacles."
3889
+ });
3890
+ return { points, diagnostics };
2736
3891
  }
2737
- }
2738
- diagnostics.push({
2739
- severity: "warning",
2740
- code: "routing.obstacle.unavoidable",
3892
+ if (routeCrossesBoxes(points, softObstacles)) {
3893
+ diagnostics.push({
3894
+ severity: "warning",
3895
+ code: "routing.obstacle.unavoidable",
3896
+ message: "Straight route crosses soft obstacles."
3897
+ });
3898
+ }
3899
+ return { points, diagnostics };
3900
+ }
3901
+ const routeLaneObstacles = [...softObstacles, ...hardObstacles];
3902
+ const anchorPairs = routeAnchorPairs(input, defaultAnchors);
3903
+ const candidateRoutes = anchorPairs.flatMap(
3904
+ ({ sourceAnchor, targetAnchor }) => {
3905
+ const source = getEdgePort(
3906
+ input.source,
3907
+ input.target.center,
3908
+ sourceAnchor
3909
+ );
3910
+ const target = getEdgePort(
3911
+ input.target,
3912
+ input.source.center,
3913
+ targetAnchor
3914
+ );
3915
+ const routes = [
3916
+ ...orthogonalCandidates(source, target, input.direction),
3917
+ ...expandedObstacleCandidates(
3918
+ source,
3919
+ target,
3920
+ input.direction,
3921
+ routeLaneObstacles
3922
+ ),
3923
+ ...outerDoglegCandidates(
3924
+ source,
3925
+ target,
3926
+ input.direction,
3927
+ routeLaneObstacles
3928
+ )
3929
+ ];
3930
+ const endpointObstacles = endpointObstaclesForAutoAnchors(input);
3931
+ return routes.map((points) => ({ points, endpointObstacles }));
3932
+ }
3933
+ );
3934
+ for (const candidate of candidateRoutes) {
3935
+ if (!routeIntersectsObstacles(candidate.points, softObstacles) && !routeIntersectsObstacles(candidate.points, hardObstacles) && !routeIntersectsEndpointInteriors(
3936
+ candidate.points,
3937
+ candidate.endpointObstacles
3938
+ )) {
3939
+ return {
3940
+ points: finalizeRoute(
3941
+ candidate.points,
3942
+ softObstacles,
3943
+ hardObstacles,
3944
+ diagnostics
3945
+ ),
3946
+ diagnostics
3947
+ };
3948
+ }
3949
+ }
3950
+ const hardClearCandidate = candidateRoutes.find(
3951
+ (candidate) => !routeIntersectsObstacles(candidate.points, hardObstacles) && !routeIntersectsEndpointInteriors(
3952
+ candidate.points,
3953
+ candidate.endpointObstacles
3954
+ )
3955
+ );
3956
+ if (hardClearCandidate !== void 0) {
3957
+ diagnostics.push({
3958
+ severity: "warning",
3959
+ code: "routing.obstacle.unavoidable",
3960
+ message: "No bounded orthogonal route candidate avoided all soft obstacles."
3961
+ });
3962
+ return {
3963
+ points: finalizeRoute(
3964
+ hardClearCandidate.points,
3965
+ softObstacles,
3966
+ hardObstacles,
3967
+ diagnostics
3968
+ ),
3969
+ diagnostics
3970
+ };
3971
+ }
3972
+ if (hardObstacles.length > 0) {
3973
+ diagnostics.push({
3974
+ severity: "error",
3975
+ code: "routing.evidence.crossing_forbidden",
3976
+ message: "No bounded orthogonal route candidate avoided hard evidence block obstacles."
3977
+ });
3978
+ return {
3979
+ points: finalizeRoute(
3980
+ candidateRoutes[0]?.points ?? fallbackRoute(input, defaultAnchors),
3981
+ softObstacles,
3982
+ hardObstacles,
3983
+ diagnostics
3984
+ ),
3985
+ diagnostics
3986
+ };
3987
+ }
3988
+ diagnostics.push({
3989
+ severity: "warning",
3990
+ code: "routing.obstacle.unavoidable",
2741
3991
  message: "No bounded orthogonal route candidate avoided all obstacles."
2742
3992
  });
2743
3993
  return {
2744
- points: simplifyRoute(candidates[0] ?? [source, target]),
3994
+ points: finalizeRoute(
3995
+ candidateRoutes[0]?.points ?? fallbackRoute(input, defaultAnchors),
3996
+ softObstacles,
3997
+ hardObstacles,
3998
+ diagnostics
3999
+ ),
2745
4000
  diagnostics
2746
4001
  };
2747
4002
  }
4003
+ function finalizeRoute(points, softObstacles, hardObstacles, diagnostics) {
4004
+ const simplified = simplifyRoute(points);
4005
+ if (simplified.length >= 3) {
4006
+ return simplified;
4007
+ }
4008
+ const crossesHardObstacles = routeCrossesBoxes(simplified, hardObstacles);
4009
+ const crossesSoftObstacles = routeCrossesBoxes(simplified, softObstacles);
4010
+ if (!crossesHardObstacles && !crossesSoftObstacles) {
4011
+ return simplified;
4012
+ }
4013
+ const expanded = expandFallbackRoute(simplified, [
4014
+ ...softObstacles,
4015
+ ...hardObstacles
4016
+ ]);
4017
+ const expandedCrossesHard = routeCrossesBoxes(expanded, hardObstacles);
4018
+ const expandedCrossesSoft = routeCrossesBoxes(expanded, softObstacles);
4019
+ if (expandedCrossesHard || expandedCrossesSoft) {
4020
+ diagnostics.push({
4021
+ severity: expandedCrossesHard ? "error" : "warning",
4022
+ code: "route_obstacle_fallback",
4023
+ message: "Obstacle-aware routing fell back to fewer than three route points.",
4024
+ detail: { pointCount: simplified.length }
4025
+ });
4026
+ }
4027
+ return expanded;
4028
+ }
4029
+ function expandFallbackRoute(points, obstacles) {
4030
+ if (points.length !== 2) {
4031
+ return points.map((point2) => ({ ...point2 }));
4032
+ }
4033
+ const [source, target] = points;
4034
+ if (source === void 0 || target === void 0) {
4035
+ return points.map((point2) => ({ ...point2 }));
4036
+ }
4037
+ if (source.y === target.y) {
4038
+ const detourY = horizontalDetourLane(source, target, obstacles);
4039
+ return [
4040
+ { ...source },
4041
+ { x: source.x, y: detourY },
4042
+ { x: target.x, y: detourY },
4043
+ { ...target }
4044
+ ];
4045
+ }
4046
+ if (source.x === target.x) {
4047
+ const detourX = verticalDetourLane(source, target, obstacles);
4048
+ return [
4049
+ { ...source },
4050
+ { x: detourX, y: source.y },
4051
+ { x: detourX, y: target.y },
4052
+ { ...target }
4053
+ ];
4054
+ }
4055
+ const hv = diagonalDetourHV(source, target, obstacles);
4056
+ const vh = diagonalDetourVH(source, target, obstacles);
4057
+ const viable = [hv, vh].filter((c) => !routeCrossesBoxes(c, obstacles));
4058
+ const [firstViable, ...remainingViable] = viable;
4059
+ if (firstViable !== void 0) {
4060
+ const directLen = Math.hypot(target.x - source.x, target.y - source.y);
4061
+ let best = firstViable;
4062
+ for (const cand of remainingViable) {
4063
+ if (pathLength(cand) - directLen < pathLength(best) - directLen) {
4064
+ best = cand;
4065
+ }
4066
+ }
4067
+ return best;
4068
+ }
4069
+ return [
4070
+ { ...source },
4071
+ { x: (source.x + target.x) / 2, y: source.y },
4072
+ { x: (source.x + target.x) / 2, y: target.y },
4073
+ { ...target }
4074
+ ];
4075
+ }
4076
+ function horizontalDetourLane(source, target, obstacles) {
4077
+ const crossing = obstacles.filter(
4078
+ (obstacle) => segmentIntersectsBox(source, target, obstacle)
4079
+ );
4080
+ if (crossing.length === 0) {
4081
+ return source.y + (source.x <= target.x ? 1 : -1) * 24;
4082
+ }
4083
+ const margin = 24;
4084
+ const above = Math.min(...crossing.map((obstacle) => obstacle.y)) - margin;
4085
+ const below = Math.max(...crossing.map((obstacle) => obstacle.y + obstacle.height)) + margin;
4086
+ return Math.abs(above - source.y) <= Math.abs(below - source.y) ? above : below;
4087
+ }
4088
+ function verticalDetourLane(source, target, obstacles) {
4089
+ const crossing = obstacles.filter(
4090
+ (obstacle) => segmentIntersectsBox(source, target, obstacle)
4091
+ );
4092
+ if (crossing.length === 0) {
4093
+ return source.x + (source.y <= target.y ? 1 : -1) * 24;
4094
+ }
4095
+ const margin = 24;
4096
+ const left = Math.min(...crossing.map((obstacle) => obstacle.x)) - margin;
4097
+ const right = Math.max(...crossing.map((obstacle) => obstacle.x + obstacle.width)) + margin;
4098
+ return Math.abs(left - source.x) <= Math.abs(right - source.x) ? left : right;
4099
+ }
4100
+ function diagonalDetourHV(source, target, obstacles) {
4101
+ const detourY = horizontalDetourLane(source, target, obstacles);
4102
+ return [
4103
+ { ...source },
4104
+ { x: source.x, y: detourY },
4105
+ { x: target.x, y: detourY },
4106
+ { ...target }
4107
+ ];
4108
+ }
4109
+ function diagonalDetourVH(source, target, obstacles) {
4110
+ const detourX = verticalDetourLane(source, target, obstacles);
4111
+ return [
4112
+ { ...source },
4113
+ { x: detourX, y: source.y },
4114
+ { x: detourX, y: target.y },
4115
+ { ...target }
4116
+ ];
4117
+ }
4118
+ function pathLength(points) {
4119
+ let len = 0;
4120
+ for (let i = 1; i < points.length; i += 1) {
4121
+ const a = points[i - 1];
4122
+ const b = points[i];
4123
+ if (a !== void 0 && b !== void 0) {
4124
+ len += Math.hypot(b.x - a.x, b.y - a.y);
4125
+ }
4126
+ }
4127
+ return len;
4128
+ }
4129
+ function endpointObstaclesForAutoAnchors(input) {
4130
+ const boxes = [];
4131
+ if (input.sourceAnchor === void 0 && hasDistinctAnchors(input.source)) {
4132
+ boxes.push(insetBox(input.source.box, 1));
4133
+ }
4134
+ if (input.targetAnchor === void 0 && hasDistinctAnchors(input.target)) {
4135
+ boxes.push(insetBox(input.target.box, 1));
4136
+ }
4137
+ return boxes.filter((box) => box.width > 0 && box.height > 0);
4138
+ }
4139
+ function hasDistinctAnchors(geometry) {
4140
+ const points = new Set(
4141
+ geometry.anchors.map((anchor) => `${anchor.point.x},${anchor.point.y}`)
4142
+ );
4143
+ return points.size > 1;
4144
+ }
4145
+ function insetBox(box, margin) {
4146
+ return {
4147
+ x: box.x + margin,
4148
+ y: box.y + margin,
4149
+ width: box.width - margin * 2,
4150
+ height: box.height - margin * 2
4151
+ };
4152
+ }
4153
+ function fallbackRoute(input, defaultAnchors) {
4154
+ return [
4155
+ getEdgePort(
4156
+ input.source,
4157
+ input.target.center,
4158
+ input.sourceAnchor ?? defaultAnchors.sourceAnchor
4159
+ ),
4160
+ getEdgePort(
4161
+ input.target,
4162
+ input.source.center,
4163
+ input.targetAnchor ?? defaultAnchors.targetAnchor
4164
+ )
4165
+ ];
4166
+ }
4167
+ function routeAnchorPairs(input, defaultAnchors) {
4168
+ const sourceAnchors = routeAnchorCandidates(
4169
+ input.sourceAnchor,
4170
+ defaultAnchors.sourceAnchor,
4171
+ input.source,
4172
+ input.target.center
4173
+ );
4174
+ const targetAnchors = routeAnchorCandidates(
4175
+ input.targetAnchor,
4176
+ defaultAnchors.targetAnchor,
4177
+ input.target,
4178
+ input.source.center
4179
+ );
4180
+ const pairs = sourceAnchors.flatMap(
4181
+ (sourceAnchor) => targetAnchors.map((targetAnchor) => ({ sourceAnchor, targetAnchor }))
4182
+ );
4183
+ const seen = /* @__PURE__ */ new Set();
4184
+ return pairs.filter((pair) => {
4185
+ const key = `${pair.sourceAnchor}->${pair.targetAnchor}`;
4186
+ if (seen.has(key)) {
4187
+ return false;
4188
+ }
4189
+ seen.add(key);
4190
+ return true;
4191
+ });
4192
+ }
4193
+ function routeAnchorCandidates(explicitAnchor, defaultAnchor, geometry, toward) {
4194
+ if (explicitAnchor !== void 0) {
4195
+ return [explicitAnchor];
4196
+ }
4197
+ const ranked = rankedSideAnchors(geometry, toward);
4198
+ return [defaultAnchor, ...ranked].filter(
4199
+ (anchor, index, anchors) => anchors.indexOf(anchor) === index
4200
+ );
4201
+ }
4202
+ function rankedSideAnchors(geometry, toward) {
4203
+ const anchors = outwardSideAnchors(geometry.box, toward);
4204
+ return anchors.sort((left, right) => {
4205
+ const leftPoint = getEdgePort(geometry, toward, left);
4206
+ const rightPoint = getEdgePort(geometry, toward, right);
4207
+ const distance = squaredDistance2(leftPoint, toward) - squaredDistance2(rightPoint, toward);
4208
+ return distance === 0 ? left.localeCompare(right) : distance;
4209
+ });
4210
+ }
4211
+ function outwardSideAnchors(box, toward) {
4212
+ const center = {
4213
+ x: box.x + box.width / 2,
4214
+ y: box.y + box.height / 2
4215
+ };
4216
+ const dx = toward.x - center.x;
4217
+ const dy = toward.y - center.y;
4218
+ if (Math.abs(dx) >= Math.abs(dy)) {
4219
+ return dx >= 0 ? ["right", "top", "bottom"] : ["left", "top", "bottom"];
4220
+ }
4221
+ return dy >= 0 ? ["bottom", "left", "right"] : ["top", "left", "right"];
4222
+ }
4223
+ function squaredDistance2(a, b) {
4224
+ const dx = a.x - b.x;
4225
+ const dy = a.y - b.y;
4226
+ return dx * dx + dy * dy;
4227
+ }
2748
4228
  function simplifyRoute(points) {
2749
4229
  const withoutDuplicates = [];
2750
4230
  for (const point2 of points) {
@@ -2869,6 +4349,44 @@ function expandedObstacleCandidates(source, target, direction, obstacles) {
2869
4349
  }
2870
4350
  return candidates;
2871
4351
  }
4352
+ function outerDoglegCandidates(source, target, direction, obstacles) {
4353
+ if (obstacles.length === 0) {
4354
+ return [];
4355
+ }
4356
+ const margin = 24;
4357
+ const minX = Math.min(...obstacles.map((obstacle) => obstacle.x)) - margin;
4358
+ const maxX = Math.max(...obstacles.map((obstacle) => obstacle.x + obstacle.width)) + margin;
4359
+ const minY = Math.min(...obstacles.map((obstacle) => obstacle.y)) - margin;
4360
+ const maxY = Math.max(...obstacles.map((obstacle) => obstacle.y + obstacle.height)) + margin;
4361
+ if (direction === "TB" || direction === "BT") {
4362
+ const exit2 = exitDelta(source, target, "y");
4363
+ return sortedUniqueLanes([minX, maxX], (source.x + target.x) / 2).map(
4364
+ (laneX) => [
4365
+ source,
4366
+ { x: source.x, y: source.y + exit2 },
4367
+ { x: laneX, y: source.y + exit2 },
4368
+ { x: laneX, y: target.y - exit2 },
4369
+ { x: target.x, y: target.y - exit2 },
4370
+ target
4371
+ ]
4372
+ );
4373
+ }
4374
+ const exit = exitDelta(source, target, "x");
4375
+ return sortedUniqueLanes([minY, maxY], (source.y + target.y) / 2).map(
4376
+ (laneY) => [
4377
+ source,
4378
+ { x: source.x + exit, y: source.y },
4379
+ { x: source.x + exit, y: laneY },
4380
+ { x: target.x - exit, y: laneY },
4381
+ { x: target.x - exit, y: target.y },
4382
+ target
4383
+ ]
4384
+ );
4385
+ }
4386
+ function exitDelta(source, target, axis) {
4387
+ const delta = axis === "x" ? target.x - source.x : target.y - source.y;
4388
+ return (delta >= 0 ? 1 : -1) * 24;
4389
+ }
2872
4390
  function sortedUniqueLanes(lanes, midpoint) {
2873
4391
  return [...new Set(lanes)].filter((lane) => Number.isFinite(lane)).sort((left, right) => {
2874
4392
  const distance = Math.abs(left - midpoint) - Math.abs(right - midpoint);
@@ -2892,6 +4410,72 @@ function routeIntersectsObstacles(points, obstacles) {
2892
4410
  }
2893
4411
  return false;
2894
4412
  }
4413
+ function routeIntersectsEndpointInteriors(points, endpointInteriors) {
4414
+ for (let index = 0; index < points.length - 1; index += 1) {
4415
+ const a = points[index];
4416
+ const b = points[index + 1];
4417
+ if (a === void 0 || b === void 0) {
4418
+ continue;
4419
+ }
4420
+ const segment = segmentBox(a, b);
4421
+ for (const endpointInterior of endpointInteriors) {
4422
+ validateBox(endpointInterior);
4423
+ if (intersectsAabb(segment, endpointInterior)) {
4424
+ return true;
4425
+ }
4426
+ }
4427
+ }
4428
+ return false;
4429
+ }
4430
+ function routeCrossesBoxes(points, obstacles) {
4431
+ for (let index = 0; index < points.length - 1; index += 1) {
4432
+ const a = points[index];
4433
+ const b = points[index + 1];
4434
+ if (a === void 0 || b === void 0) {
4435
+ continue;
4436
+ }
4437
+ for (const obstacle of obstacles) {
4438
+ validateBox(obstacle);
4439
+ if (segmentIntersectsBox(a, b, obstacle)) {
4440
+ return true;
4441
+ }
4442
+ }
4443
+ }
4444
+ return false;
4445
+ }
4446
+ function segmentIntersectsBox(start, end, box) {
4447
+ const left = box.x;
4448
+ const right = box.x + box.width;
4449
+ const top = box.y;
4450
+ const bottom = box.y + box.height;
4451
+ if (pointInsideBox(start, box) || pointInsideBox(end, box)) {
4452
+ return true;
4453
+ }
4454
+ if (start.x === end.x) {
4455
+ return start.x > left && start.x < right && rangesOverlap(start.y, end.y, top, bottom);
4456
+ }
4457
+ if (start.y === end.y) {
4458
+ return start.y > top && start.y < bottom && rangesOverlap(start.x, end.x, left, right);
4459
+ }
4460
+ 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);
4461
+ }
4462
+ function pointInsideBox(point2, box) {
4463
+ return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
4464
+ }
4465
+ function rangesOverlap(a, b, min, max) {
4466
+ const low = Math.min(a, b);
4467
+ const high = Math.max(a, b);
4468
+ return high > min && low < max;
4469
+ }
4470
+ function segmentIntersectsBoxEdge(start, end, x1, y1, x2, y2) {
4471
+ const denominator = (end.x - start.x) * (y2 - y1) - (end.y - start.y) * (x2 - x1);
4472
+ if (denominator === 0) {
4473
+ return false;
4474
+ }
4475
+ const t = ((x1 - start.x) * (y2 - y1) - (y1 - start.y) * (x2 - x1)) / denominator;
4476
+ const u = ((x1 - start.x) * (end.y - start.y) - (y1 - start.y) * (end.x - start.x)) / denominator;
4477
+ return t > 0 && t < 1 && u > 0 && u < 1;
4478
+ }
2895
4479
  function segmentBox(a, b) {
2896
4480
  const minX = Math.min(a.x, b.x);
2897
4481
  const minY = Math.min(a.y, b.y);
@@ -2907,32 +4491,107 @@ function areCollinear(a, b, c) {
2907
4491
  }
2908
4492
 
2909
4493
  // src/solver/solve.ts
4494
+ var DEFAULT_MATRIX_CELL_SIZE2 = { width: 120, height: 36 };
4495
+ var DEFAULT_TABLE_CELL_SIZE2 = { width: 128, height: 34 };
4496
+ var DEFAULT_PANEL_WIDTH = 320;
4497
+ var DEFAULT_PANEL_ITEM_HEIGHT2 = 28;
4498
+ var DEFAULT_EVIDENCE_BLOCK_GAP = 24;
4499
+ var EDGE_LABEL_CLEARANCE = 8;
4500
+ var DEFAULT_CJK_FONT_FAMILY = "YaHei,SimSun,sans-serif";
4501
+ var DEFAULT_MIN_CJK_FONT_SIZE = 14;
4502
+ function prefitLabelFont(node, _options) {
4503
+ const cjk = labelCjkTypography(node.label?.metadata);
4504
+ const fontFamily = cjk.fontFamily ?? DEFAULT_FONT.fontFamily;
4505
+ const fontSize = cjk.fontSize ?? DEFAULT_FONT.fontSize;
4506
+ const lineHeight = fontSize !== DEFAULT_FONT.fontSize ? Math.max(DEFAULT_FONT.lineHeight ?? 18, fontSize * 1.2) : DEFAULT_FONT.lineHeight ?? 18;
4507
+ return { fontFamily, fontSize, lineHeight };
4508
+ }
4509
+ var EVIDENCE_TEXT_FONT = {
4510
+ fontFamily: "Arial, sans-serif",
4511
+ fontSize: 10,
4512
+ lineHeight: 12
4513
+ };
2910
4514
  function solveDiagram(diagram, options = {}) {
2911
4515
  const diagnostics = [...diagram.diagnostics];
2912
- const nodes = stableById(diagram.nodes);
2913
- const edges = stableById(diagram.edges);
2914
- const groups = stableById(diagram.groups);
4516
+ const nodes = stableUniqueById(
4517
+ diagram.nodes,
4518
+ diagnostics,
4519
+ "nodes",
4520
+ "duplicate_node_id"
4521
+ );
4522
+ const edges = stableUniqueById(
4523
+ diagram.edges,
4524
+ diagnostics,
4525
+ "edges",
4526
+ "duplicate_edge_id"
4527
+ );
4528
+ const groups = stableUniqueById(
4529
+ diagram.groups,
4530
+ diagnostics,
4531
+ "groups",
4532
+ "duplicate_group_id"
4533
+ );
4534
+ const cjkTypography = createCjkTypographyOptions(options);
4535
+ const cjkStyledNodes = nodes.map(
4536
+ (node) => enhanceNodeCjkTypography(node, cjkTypography, diagnostics)
4537
+ );
4538
+ const styledNodes = options.prefitLabelSize === true ? cjkStyledNodes.map(
4539
+ (node) => prefitNodeLabelSize(node, options, diagnostics)
4540
+ ) : cjkStyledNodes;
4541
+ const styledEdges = edges.map(
4542
+ (edge) => enhanceEdgeCjkTypography(edge, cjkTypography, diagnostics)
4543
+ );
4544
+ const styledGroups = groups.map(
4545
+ (group) => enhanceGroupCjkTypography(group, cjkTypography, diagnostics)
4546
+ );
4547
+ const styledSwimlanes = (diagram.swimlanes ?? []).map(
4548
+ (swimlane) => enhanceSwimlaneCjkTypography(swimlane, cjkTypography, diagnostics)
4549
+ );
2915
4550
  const constraints = stableByConstraintId(diagram.constraints);
2916
4551
  const layout2 = runDagreInitialLayout({
2917
4552
  direction: diagram.direction,
2918
- nodes: nodes.map((node) => ({ id: node.id, size: node.size })),
2919
- edges: edges.map((edge) => ({
4553
+ nodes: styledNodes.map((node) => ({ id: node.id, size: node.size })),
4554
+ edges: styledEdges.map((edge) => ({
2920
4555
  id: edge.id,
2921
4556
  sourceId: edge.source.nodeId,
2922
4557
  targetId: edge.target.nodeId
2923
4558
  }))
2924
4559
  });
2925
4560
  diagnostics.push(...layout2.diagnostics);
4561
+ const initialNodeBoxes = wrapVerticalStackIfNeeded(
4562
+ layout2.boxes,
4563
+ styledNodes,
4564
+ styledEdges,
4565
+ diagram.direction,
4566
+ options,
4567
+ diagnostics
4568
+ );
2926
4569
  const constrained = applyLayoutConstraints({
2927
4570
  direction: diagram.direction,
2928
4571
  overlapSpacing: options?.overlapSpacing ?? 40,
2929
- boxes: layout2.boxes,
2930
- nodes,
4572
+ ...options.minSiblingGap === void 0 ? {} : { minSiblingGap: options.minSiblingGap },
4573
+ ...options.distributeContainedChildren === void 0 ? {} : { distributeContainedChildren: options.distributeContainedChildren },
4574
+ boxes: initialNodeBoxes,
4575
+ nodes: styledNodes,
2931
4576
  constraints
2932
4577
  });
2933
4578
  diagnostics.push(...constrained.diagnostics);
4579
+ const swimlaneContracts = applySwimlaneLayoutContracts(
4580
+ styledSwimlanes,
4581
+ constraints,
4582
+ styledEdges,
4583
+ isTopToBottomReadingDirection(diagram.metadata?.primaryReadingDirection),
4584
+ constrained.boxes,
4585
+ constrained.locks,
4586
+ options?.overlapSpacing ?? 40,
4587
+ Math.max(0, options?.minLaneGutter ?? 0)
4588
+ );
4589
+ if (swimlaneContracts.layouts.size > 0) {
4590
+ removeResolvedOverlapDiagnostics(diagnostics, constrained.boxes);
4591
+ }
4592
+ diagnostics.push(...swimlaneContracts.diagnostics);
2934
4593
  const coordinatedNodes = coordinateNodes(
2935
- nodes,
4594
+ styledNodes,
2936
4595
  constrained.boxes,
2937
4596
  options,
2938
4597
  diagnostics
@@ -2948,28 +4607,31 @@ function solveDiagram(diagram, options = {}) {
2948
4607
  ])
2949
4608
  );
2950
4609
  const coordinatedGroups = coordinateGroups(
2951
- groups,
4610
+ styledGroups,
2952
4611
  constrained.boxes,
2953
4612
  options,
2954
4613
  diagnostics
2955
4614
  );
2956
4615
  const coordinatedSwimlanes = coordinateSwimlanes(
2957
- diagram.swimlanes ?? [],
2958
- constrained.boxes
4616
+ styledSwimlanes,
4617
+ constrained.boxes,
4618
+ swimlaneContracts.layouts
4619
+ );
4620
+ const coordinatedMatrices = coordinateMatrices(diagram.matrices ?? []);
4621
+ const coordinatedTables = coordinateTables(diagram.tables ?? []);
4622
+ const coordinatedEvidencePanels = coordinateEvidencePanels(
4623
+ diagram.evidencePanels ?? []
2959
4624
  );
2960
4625
  const groupBoxes = new Map(
2961
4626
  coordinatedGroups.map((group) => [group.id, group.box])
2962
4627
  );
2963
- const coordinatedEdges = coordinateEdges(
2964
- edges,
2965
- nodeGeometryById,
2966
- coordinatedNodes,
2967
- [...nodeGeometryById.values()].map((geometry) => geometry.obstacleBox),
2968
- diagram.direction,
2969
- options,
2970
- diagnostics
2971
- );
2972
- const allBoxes = [
4628
+ const baseTextAnnotations = coordinateBaseTextAnnotations({
4629
+ nodes: coordinatedNodes,
4630
+ groups: coordinatedGroups,
4631
+ swimlanes: coordinatedSwimlanes,
4632
+ ...options.textMeasurer === void 0 ? {} : { textMeasurer: options.textMeasurer }
4633
+ });
4634
+ const layoutBoxes = [
2973
4635
  ...coordinatedNodes.map((node) => node.box),
2974
4636
  ...coordinatedNodes.flatMap(
2975
4637
  (node) => (node.ports ?? []).flatMap(
@@ -2979,10 +4641,149 @@ function solveDiagram(diagram, options = {}) {
2979
4641
  ...groupBoxes.values(),
2980
4642
  ...coordinatedSwimlanes.flatMap(
2981
4643
  (swimlane) => swimlane.box === void 0 ? [] : [swimlane.box]
2982
- )
4644
+ ),
4645
+ ...baseTextAnnotations.map((annotation) => annotation.box)
4646
+ ];
4647
+ const initialContentBounds = layoutBoxes.length === 0 ? { x: 0, y: 0, width: 0} : unionBoxes(layoutBoxes);
4648
+ placeEvidenceBlocks(
4649
+ options.obstacleMargin ?? 0,
4650
+ [
4651
+ ...coordinatedMatrices,
4652
+ ...coordinatedTables,
4653
+ ...coordinatedEvidencePanels
4654
+ ],
4655
+ initialContentBounds
4656
+ );
4657
+ refreshTableColumnXOffsets(coordinatedTables);
4658
+ measureEvidenceTextBlocks(
4659
+ coordinatedMatrices,
4660
+ coordinatedTables,
4661
+ coordinatedEvidencePanels,
4662
+ options.textMeasurer
4663
+ );
4664
+ const evidenceBoxes = [
4665
+ ...coordinatedMatrices.map((matrix) => matrix.box),
4666
+ ...coordinatedTables.map((table) => table.box),
4667
+ ...coordinatedEvidencePanels.map((panel) => panel.box)
2983
4668
  ];
4669
+ diagnostics.push(
4670
+ ...reportEvidenceBlockOverlaps(
4671
+ [
4672
+ ...coordinatedMatrices.map((matrix) => ({
4673
+ id: matrix.id,
4674
+ kind: "matrix",
4675
+ ...matrix.position === void 0 ? {} : { position: matrix.position },
4676
+ box: matrix.box
4677
+ })),
4678
+ ...coordinatedTables.map((table) => ({
4679
+ id: table.id,
4680
+ kind: "table",
4681
+ ...table.position === void 0 ? {} : { position: table.position },
4682
+ box: table.box
4683
+ })),
4684
+ ...coordinatedEvidencePanels.map((panel) => ({
4685
+ id: panel.id,
4686
+ kind: "evidence-panel",
4687
+ ...panel.position === void 0 ? {} : { position: panel.position },
4688
+ box: panel.box
4689
+ }))
4690
+ ],
4691
+ [
4692
+ ...coordinatedNodes.map((node) => ({
4693
+ id: node.id,
4694
+ kind: "node",
4695
+ box: node.box
4696
+ })),
4697
+ ...coordinatedGroups.map((group) => ({
4698
+ id: group.id,
4699
+ kind: "group",
4700
+ box: group.box
4701
+ })),
4702
+ ...coordinatedSwimlanes.flatMap(
4703
+ (swimlane) => swimlane.box === void 0 ? [] : [{ id: swimlane.id, kind: "swimlane", box: swimlane.box }]
4704
+ )
4705
+ ]
4706
+ )
4707
+ );
4708
+ const allBoxes = [...layoutBoxes, ...evidenceBoxes];
2984
4709
  const contentBounds = allBoxes.length === 0 ? { x: 0, y: 0, width: 0, height: 0 } : unionBoxes(allBoxes);
2985
4710
  const frame = diagram.frame === void 0 ? void 0 : coordinateFrame(diagram.frame, contentBounds);
4711
+ const frameTextAnnotation = frame === void 0 ? [] : [coordinateFrameTextAnnotation(frame, options.textMeasurer)];
4712
+ const routingTextObstacles = [
4713
+ ...baseTextAnnotations.filter(isPreRouteTextObstacle),
4714
+ ...frameTextAnnotation.filter(isPreRouteTextObstacle)
4715
+ ];
4716
+ const margin = options.obstacleMargin ?? 0;
4717
+ const softObstacles = [
4718
+ ...coordinatedTables.map((table) => expandBox(table.box, margin)),
4719
+ ...coordinatedEvidencePanels.map((panel) => expandBox(panel.box, margin))
4720
+ ];
4721
+ const hardObstacles = coordinatedMatrices.map(
4722
+ (matrix) => expandBox(matrix.box, margin)
4723
+ );
4724
+ const titleBarObstacles = [];
4725
+ if (frame !== void 0) {
4726
+ titleBarObstacles.push(expandBox(frame.titleBox, margin));
4727
+ }
4728
+ for (const swimlane of coordinatedSwimlanes) {
4729
+ for (const lane of swimlane.lanes) {
4730
+ if (lane.headerBox !== void 0 && lane.headerBox.width > 0 && lane.headerBox.height > 0) {
4731
+ titleBarObstacles.push(expandBox(lane.headerBox, margin));
4732
+ }
4733
+ }
4734
+ }
4735
+ const coordinatedEdges = coordinateEdges(
4736
+ styledEdges,
4737
+ nodeGeometryById,
4738
+ coordinatedNodes,
4739
+ [...nodeGeometryById.values()].map((geometry) => geometry.obstacleBox),
4740
+ [...softObstacles, ...titleBarObstacles],
4741
+ routingTextObstacles,
4742
+ hardObstacles,
4743
+ diagram.direction,
4744
+ options,
4745
+ diagnostics
4746
+ );
4747
+ const edgeTextAnnotations = coordinateEdgeTextAnnotations(
4748
+ coordinatedEdges,
4749
+ [
4750
+ ...coordinatedNodes.map((node) => node.box),
4751
+ ...baseTextAnnotations.map((annotation) => annotation.box),
4752
+ ...frameTextAnnotation.map((annotation) => annotation.box)
4753
+ ],
4754
+ options.textMeasurer
4755
+ );
4756
+ const textAnnotations = [
4757
+ ...baseTextAnnotations,
4758
+ ...frameTextAnnotation,
4759
+ ...edgeTextAnnotations
4760
+ ];
4761
+ diagnostics.push(...reportTextAnnotationCollisions(textAnnotations));
4762
+ diagnostics.push(
4763
+ ...reportRouteTextClearance(coordinatedEdges, textAnnotations)
4764
+ );
4765
+ const edgePointBounds = edgeBounds(coordinatedEdges);
4766
+ const boundsBase = [
4767
+ contentBounds,
4768
+ ...edgePointBounds,
4769
+ ...edgeTextAnnotations.map((annotation) => annotation.box)
4770
+ ];
4771
+ diagnostics.push(
4772
+ ...reportPageOverflow(
4773
+ frame === void 0 ? unionBoxes(boundsBase) : unionBoxes([...boundsBase, frame.box, frame.titleBox]),
4774
+ options.pageBounds
4775
+ )
4776
+ );
4777
+ let degraded = false;
4778
+ const resultDiagnostics = diagnostics.map((diagnostic) => {
4779
+ if (DELIVERABILITY_DIAGNOSTIC_CODES.has(diagnostic.code)) {
4780
+ degraded = true;
4781
+ if (options.strict) {
4782
+ return { ...diagnostic, severity: "error" };
4783
+ }
4784
+ }
4785
+ return diagnostic;
4786
+ });
2986
4787
  return {
2987
4788
  id: diagram.id,
2988
4789
  ...diagram.title === void 0 ? {} : { title: diagram.title },
@@ -2991,36 +4792,1042 @@ function solveDiagram(diagram, options = {}) {
2991
4792
  edges: coordinatedEdges,
2992
4793
  groups: coordinatedGroups,
2993
4794
  ...coordinatedSwimlanes.length === 0 ? {} : { swimlanes: coordinatedSwimlanes },
2994
- diagnostics,
2995
- bounds: frame === void 0 ? contentBounds : unionBoxes([contentBounds, frame.box, frame.titleBox]),
4795
+ ...coordinatedMatrices.length === 0 ? {} : { matrices: coordinatedMatrices },
4796
+ ...coordinatedTables.length === 0 ? {} : { tables: coordinatedTables },
4797
+ ...coordinatedEvidencePanels.length === 0 ? {} : { evidencePanels: coordinatedEvidencePanels },
4798
+ diagnostics: resultDiagnostics,
4799
+ degraded,
4800
+ bounds: frame === void 0 ? unionBoxes(boundsBase) : unionBoxes([...boundsBase, frame.box, frame.titleBox]),
2996
4801
  ...frame === void 0 ? {} : { frame },
4802
+ ...textAnnotations.length === 0 ? {} : { textAnnotations },
2997
4803
  ...diagram.metadata === void 0 ? {} : { metadata: diagram.metadata }
2998
4804
  };
2999
4805
  }
3000
- function coordinateNodes(nodes, boxes, options, diagnostics) {
3001
- const coordinated = [];
3002
- for (const node of nodes) {
3003
- const box = boxes.get(node.id);
3004
- if (box === void 0) {
3005
- diagnostics.push({
3006
- severity: "error",
3007
- code: "solver.node-box.missing",
3008
- message: `Node ${node.id} has no solved box.`,
3009
- path: ["nodes", node.id],
3010
- detail: { nodeId: node.id }
3011
- });
3012
- continue;
3013
- }
3014
- const geometry = computeShapeGeometry({
3015
- shape: node.shape,
3016
- box,
3017
- obstacleMargin: options.obstacleMargin ?? 0
3018
- });
3019
- coordinated.push({
3020
- id: node.id,
3021
- ...node.label === void 0 ? {} : { label: node.label },
3022
- ...node.style === void 0 ? {} : { style: node.style },
3023
- ...node.ports === void 0 ? {} : { ports: coordinatePorts(node, box, options.portShifting) },
4806
+ function solveDiagramSafe(diagram, options = {}) {
4807
+ return solveDiagram(diagram, { ...options, prefitLabelSize: true });
4808
+ }
4809
+ function prefitNodeLabelSize(node, options, diagnostics) {
4810
+ if (node.label === void 0) {
4811
+ return node;
4812
+ }
4813
+ const measurer = options.textMeasurer ?? createDefaultTextMeasurer();
4814
+ const layout2 = fitLabel(
4815
+ node.label.text,
4816
+ {
4817
+ font: prefitLabelFont(node),
4818
+ padding: DEFAULT_NODE_PADDING,
4819
+ minSize: DEFAULT_NODE_MIN_SIZE,
4820
+ maxWidth: node.label.maxWidth ?? Math.max(node.size.width, DEFAULT_LABEL_MAX_WIDTH)
4821
+ },
4822
+ measurer
4823
+ );
4824
+ const width = Math.max(node.size.width, layout2.fittedSize.width);
4825
+ const height = Math.max(node.size.height, layout2.fittedSize.height);
4826
+ const resized = width !== node.size.width || height !== node.size.height;
4827
+ if (resized) {
4828
+ diagnostics.push({
4829
+ severity: "info",
4830
+ code: "prefit_label_resized",
4831
+ message: `Node ${node.id} size expanded to fit its label.`,
4832
+ path: ["nodes", node.id],
4833
+ detail: {
4834
+ nodeId: node.id,
4835
+ from: { width: node.size.width, height: node.size.height },
4836
+ to: { width, height }
4837
+ }
4838
+ });
4839
+ }
4840
+ const centeredLayout = expandLabelLayoutToNode(layout2, { width, height });
4841
+ return { ...node, size: { width, height }, labelLayout: centeredLayout };
4842
+ }
4843
+ function expandLabelLayoutToNode(layout2, nodeSize) {
4844
+ if (layout2.box.width >= nodeSize.width && layout2.box.height >= nodeSize.height) {
4845
+ return layout2;
4846
+ }
4847
+ const offsetX = Math.max(0, (nodeSize.width - layout2.box.width) / 2);
4848
+ const offsetY = Math.max(0, (nodeSize.height - layout2.box.height) / 2);
4849
+ if (offsetX === 0 && offsetY === 0) {
4850
+ return layout2;
4851
+ }
4852
+ return {
4853
+ ...layout2,
4854
+ box: {
4855
+ x: layout2.box.x + offsetX,
4856
+ y: layout2.box.y + offsetY,
4857
+ width: layout2.box.width,
4858
+ height: layout2.box.height
4859
+ },
4860
+ contentBox: {
4861
+ x: layout2.contentBox.x + offsetX,
4862
+ y: layout2.contentBox.y + offsetY,
4863
+ width: layout2.contentBox.width,
4864
+ height: layout2.contentBox.height
4865
+ },
4866
+ lines: layout2.lines.map((line) => ({
4867
+ ...line,
4868
+ box: {
4869
+ x: line.box.x + offsetX,
4870
+ y: line.box.y + offsetY,
4871
+ width: line.box.width,
4872
+ height: line.box.height
4873
+ }
4874
+ }))
4875
+ };
4876
+ }
4877
+ function reportPageOverflow(contentBounds, pageBounds) {
4878
+ if (pageBounds === void 0) {
4879
+ return [];
4880
+ }
4881
+ const overflowRight = Math.max(
4882
+ 0,
4883
+ contentBounds.x + contentBounds.width - pageBounds.width
4884
+ );
4885
+ const overflowBottom = Math.max(
4886
+ 0,
4887
+ contentBounds.y + contentBounds.height - pageBounds.height
4888
+ );
4889
+ const overflowLeft = Math.max(0, -contentBounds.x);
4890
+ const overflowTop = Math.max(0, -contentBounds.y);
4891
+ if (overflowRight === 0 && overflowBottom === 0 && overflowLeft === 0 && overflowTop === 0) {
4892
+ return [];
4893
+ }
4894
+ return [
4895
+ {
4896
+ severity: "warning",
4897
+ code: "page_overflow",
4898
+ message: `Content ${contentBounds.width}x${contentBounds.height} exceeds page ${pageBounds.width}x${pageBounds.height}.`,
4899
+ path: ["bounds"],
4900
+ detail: {
4901
+ page: { width: pageBounds.width, height: pageBounds.height },
4902
+ content: {
4903
+ width: contentBounds.width,
4904
+ height: contentBounds.height
4905
+ },
4906
+ overflow: {
4907
+ right: overflowRight,
4908
+ bottom: overflowBottom,
4909
+ left: overflowLeft,
4910
+ top: overflowTop
4911
+ }
4912
+ }
4913
+ }
4914
+ ];
4915
+ }
4916
+ function createCjkTypographyOptions(options) {
4917
+ const fontFamily = options.cjkFontFamily === false ? void 0 : options.cjkFontFamily ?? DEFAULT_CJK_FONT_FAMILY;
4918
+ const minFontSize = options.minCjkFontSize === false ? void 0 : options.minCjkFontSize ?? DEFAULT_MIN_CJK_FONT_SIZE;
4919
+ return {
4920
+ ...fontFamily === void 0 ? {} : { fontFamily },
4921
+ ...minFontSize === void 0 ? {} : { minFontSize }
4922
+ };
4923
+ }
4924
+ function enhanceNodeCjkTypography(node, options, diagnostics) {
4925
+ const nodeWithStyle = enhanceStyledLabelOwner(
4926
+ node,
4927
+ ["nodes", node.id],
4928
+ options,
4929
+ diagnostics
4930
+ );
4931
+ const ports = nodeWithStyle.ports === void 0 ? void 0 : nodeWithStyle.ports.map(
4932
+ (port) => enhanceStyledLabelOwner(
4933
+ port,
4934
+ ["nodes", node.id, "ports", port.id],
4935
+ options,
4936
+ diagnostics
4937
+ )
4938
+ );
4939
+ return ports === void 0 ? nodeWithStyle : { ...nodeWithStyle, ports };
4940
+ }
4941
+ function enhanceEdgeCjkTypography(edge, options, diagnostics) {
4942
+ return enhanceStyledLabelOwner(
4943
+ edge,
4944
+ ["edges", edge.id],
4945
+ options,
4946
+ diagnostics
4947
+ );
4948
+ }
4949
+ function enhanceGroupCjkTypography(group, options, diagnostics) {
4950
+ return enhanceStyledLabelOwner(
4951
+ group,
4952
+ ["groups", group.id],
4953
+ options,
4954
+ diagnostics
4955
+ );
4956
+ }
4957
+ function enhanceSwimlaneCjkTypography(swimlane, options, diagnostics) {
4958
+ const root = enhanceStyledLabelOwner(
4959
+ swimlane,
4960
+ ["swimlanes", swimlane.id],
4961
+ options,
4962
+ diagnostics
4963
+ );
4964
+ const lanes = root.lanes.map(
4965
+ (lane) => enhanceSwimlaneLaneCjkTypography(swimlane.id, lane, options, diagnostics)
4966
+ );
4967
+ return { ...root, lanes };
4968
+ }
4969
+ function enhanceSwimlaneLaneCjkTypography(swimlaneId, lane, options, diagnostics) {
4970
+ return enhanceStyledLabelOwner(
4971
+ lane,
4972
+ ["swimlanes", swimlaneId, "lanes", lane.id],
4973
+ options,
4974
+ diagnostics
4975
+ );
4976
+ }
4977
+ function enhanceStyledLabelOwner(owner, path, options, diagnostics) {
4978
+ const text = owner.label?.text;
4979
+ if (text === void 0 || !containsCjk(text)) {
4980
+ return owner;
4981
+ }
4982
+ const typography = cjkTypographyForOwner(owner, options);
4983
+ if (typography.fontFamily === void 0 && typography.fontSize === void 0) {
4984
+ return owner;
4985
+ }
4986
+ const label = owner.label;
4987
+ if (label === void 0) {
4988
+ return owner;
4989
+ }
4990
+ const nextLabel = {
4991
+ ...label,
4992
+ metadata: {
4993
+ ...metadataObject(label.metadata),
4994
+ cjkTypography: typography
4995
+ }
4996
+ };
4997
+ const nextOwner = { ...owner, label: nextLabel };
4998
+ const maybeStyled = nextOwner;
4999
+ const nextStyle = enhanceCjkStyle(maybeStyled.style, typography);
5000
+ reportCjkTypographyDiagnostics(
5001
+ path,
5002
+ typography,
5003
+ maybeStyled.style,
5004
+ diagnostics
5005
+ );
5006
+ return nextStyle === maybeStyled.style ? nextOwner : { ...nextOwner, style: nextStyle };
5007
+ }
5008
+ function cjkTypographyForOwner(owner, options) {
5009
+ const metadataTypography = labelCjkTypography(owner.label?.metadata);
5010
+ const fontFamily = metadataTypography.fontFamily ?? owner.style?.fontFamily ?? options.fontFamily;
5011
+ const fontSize = boostedCjkFontSize(
5012
+ metadataTypography.fontSize ?? owner.style?.fontSize,
5013
+ options.minFontSize
5014
+ );
5015
+ return {
5016
+ ...fontFamily === void 0 ? {} : { fontFamily },
5017
+ ...fontSize === void 0 ? {} : { fontSize }
5018
+ };
5019
+ }
5020
+ function labelCjkTypography(metadata) {
5021
+ const metadataRecord = metadataObject(metadata);
5022
+ if (metadataRecord === void 0) {
5023
+ return {};
5024
+ }
5025
+ const value = metadataRecord.cjkTypography;
5026
+ if (value === void 0 || value === null || typeof value !== "object") {
5027
+ return {};
5028
+ }
5029
+ const typography = value;
5030
+ const fontFamily = typeof typography.fontFamily === "string" ? typography.fontFamily : void 0;
5031
+ const fontSize = typeof typography.fontSize === "number" && Number.isFinite(typography.fontSize) && typography.fontSize > 0 ? typography.fontSize : void 0;
5032
+ return {
5033
+ ...fontFamily === void 0 ? {} : { fontFamily },
5034
+ ...fontSize === void 0 ? {} : { fontSize }
5035
+ };
5036
+ }
5037
+ function metadataObject(metadata) {
5038
+ if (metadata === void 0 || metadata === null || typeof metadata !== "object" || Array.isArray(metadata)) {
5039
+ return void 0;
5040
+ }
5041
+ return metadata;
5042
+ }
5043
+ function typographyForLabel(label) {
5044
+ return labelCjkTypography(label?.metadata);
5045
+ }
5046
+ function typographyTextStyle(label, base) {
5047
+ const typography = typographyForLabel(label);
5048
+ return {
5049
+ ...base,
5050
+ ...typography.fontFamily === void 0 ? {} : { fontFamily: typography.fontFamily },
5051
+ ...typography.fontSize === void 0 ? {} : {
5052
+ fontSize: typography.fontSize,
5053
+ lineHeight: Math.max(base.lineHeight ?? 0, typography.fontSize * 1.2)
5054
+ }
5055
+ };
5056
+ }
5057
+ function boostedCjkFontSize(current, minFontSize) {
5058
+ if (minFontSize === void 0) {
5059
+ return current;
5060
+ }
5061
+ if (current === void 0 || current < minFontSize) {
5062
+ return minFontSize;
5063
+ }
5064
+ return current;
5065
+ }
5066
+ function enhanceCjkStyle(style2, typography) {
5067
+ let next = style2;
5068
+ if (typography.fontFamily !== void 0 && next?.fontFamily === void 0) {
5069
+ next = { ...next, fontFamily: typography.fontFamily };
5070
+ }
5071
+ if (typography.fontSize !== void 0 && (next?.fontSize === void 0 || next.fontSize < typography.fontSize)) {
5072
+ next = { ...next, fontSize: typography.fontSize };
5073
+ }
5074
+ return next;
5075
+ }
5076
+ function reportCjkTypographyDiagnostics(path, typography, previousStyle, diagnostics) {
5077
+ if (typography.fontFamily !== void 0 && previousStyle?.fontFamily === void 0) {
5078
+ diagnostics.push({
5079
+ severity: "info",
5080
+ code: "cjk_font_family_applied",
5081
+ message: `Applied CJK font family ${typography.fontFamily}.`,
5082
+ path: [...path, "label", "metadata", "cjkTypography", "fontFamily"],
5083
+ detail: { fontFamily: typography.fontFamily }
5084
+ });
5085
+ }
5086
+ if (typography.fontSize !== void 0 && (previousStyle?.fontSize === void 0 || previousStyle.fontSize < typography.fontSize)) {
5087
+ diagnostics.push({
5088
+ severity: "info",
5089
+ code: "cjk_font_size_boosted",
5090
+ message: `Raised CJK font size to ${typography.fontSize}.`,
5091
+ path: [...path, "label", "metadata", "cjkTypography", "fontSize"],
5092
+ detail: {
5093
+ minFontSize: typography.fontSize,
5094
+ ...previousStyle?.fontSize === void 0 ? {} : { previousFontSize: previousStyle.fontSize }
5095
+ }
5096
+ });
5097
+ }
5098
+ }
5099
+ function containsCjk(value) {
5100
+ return /[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/u.test(value);
5101
+ }
5102
+ function applySwimlaneLayoutContracts(swimlanes, constraints, edges, topToBottomFlow, nodeBoxes, locks, overlapSpacing, laneGutter) {
5103
+ const layouts = /* @__PURE__ */ new Map();
5104
+ const diagnostics = [];
5105
+ const movedChildIds = /* @__PURE__ */ new Set();
5106
+ for (const swimlane of swimlanes) {
5107
+ if ((swimlane.layout ?? "overlay") !== "contract") {
5108
+ continue;
5109
+ }
5110
+ if (swimlane.lanes.length === 0) {
5111
+ continue;
5112
+ }
5113
+ const layout2 = applySingleSwimlaneContract(
5114
+ swimlane,
5115
+ edges,
5116
+ topToBottomFlow,
5117
+ nodeBoxes,
5118
+ locks,
5119
+ diagnostics,
5120
+ movedChildIds,
5121
+ laneGutter
5122
+ );
5123
+ if (layout2 !== void 0) {
5124
+ layouts.set(swimlane.id, layout2);
5125
+ }
5126
+ }
5127
+ if (layouts.size > 0) {
5128
+ diagnostics.push(
5129
+ ...reportSwimlaneOverlaps(nodeBoxes, locks, overlapSpacing),
5130
+ ...reportSwimlaneConstraintInvalidations(
5131
+ constraints,
5132
+ nodeBoxes,
5133
+ movedChildIds
5134
+ )
5135
+ );
5136
+ if (laneGutter > 0) {
5137
+ diagnostics.push({
5138
+ severity: "info",
5139
+ code: "lane_gutter_applied",
5140
+ message: `Applied ${laneGutter}px gutter between ${layouts.size} contract swimlane lane(s).`,
5141
+ path: ["swimlanes"],
5142
+ detail: { laneGutter, swimlaneCount: layouts.size }
5143
+ });
5144
+ }
5145
+ }
5146
+ return { layouts, diagnostics, movedChildIds };
5147
+ }
5148
+ function wrapVerticalStackIfNeeded(boxes, nodes, edges, direction, options, diagnostics) {
5149
+ const wrapped = new Map([...boxes].map(([id, box]) => [id, { ...box }]));
5150
+ const maxStackDepth = options.maxStackDepth;
5151
+ if (maxStackDepth === void 0 || maxStackDepth <= 0 || nodes.length <= maxStackDepth) {
5152
+ reportVerticalRunaway(
5153
+ wrapped,
5154
+ nodes,
5155
+ edges,
5156
+ direction,
5157
+ options,
5158
+ diagnostics
5159
+ );
5160
+ return wrapped;
5161
+ }
5162
+ if (edges.length > 0 || !isVerticalRunaway(wrapped, nodes, direction, options)) {
5163
+ reportVerticalRunaway(
5164
+ wrapped,
5165
+ nodes,
5166
+ edges,
5167
+ direction,
5168
+ options,
5169
+ diagnostics
5170
+ );
5171
+ return wrapped;
5172
+ }
5173
+ const ordered = nodes.map((node) => ({ node, box: wrapped.get(node.id) })).filter(
5174
+ (item) => item.box !== void 0
5175
+ ).sort((a, b) => {
5176
+ const delta = a.box.y - b.box.y;
5177
+ return delta === 0 ? a.node.id.localeCompare(b.node.id) : delta;
5178
+ });
5179
+ const columns = Math.ceil(ordered.length / maxStackDepth);
5180
+ const horizontalGap = options.overlapSpacing ?? 40;
5181
+ const verticalGap = Math.max(24, horizontalGap / 2);
5182
+ const columnWidths = Array.from(
5183
+ { length: columns },
5184
+ (_, column) => Math.max(
5185
+ 0,
5186
+ ...ordered.slice(column * maxStackDepth, (column + 1) * maxStackDepth).map((item) => item.box.width)
5187
+ )
5188
+ );
5189
+ const startX = Math.min(...ordered.map((item) => item.box.x));
5190
+ const startY = Math.min(...ordered.map((item) => item.box.y));
5191
+ let columnX = startX;
5192
+ for (let column = 0; column < columns; column += 1) {
5193
+ let y = startY;
5194
+ const items = ordered.slice(
5195
+ column * maxStackDepth,
5196
+ (column + 1) * maxStackDepth
5197
+ );
5198
+ for (const item of items) {
5199
+ wrapped.set(item.node.id, { ...item.box, x: columnX, y });
5200
+ y += item.box.height + verticalGap;
5201
+ }
5202
+ columnX += (columnWidths[column] ?? 0) + horizontalGap;
5203
+ }
5204
+ diagnostics.push({
5205
+ severity: "warning",
5206
+ code: "vertical_runaway",
5207
+ message: `Single-column layout exceeded maxStackDepth ${maxStackDepth}; wrapped into ${columns} columns.`,
5208
+ path: ["nodes"],
5209
+ detail: { nodeCount: ordered.length, maxStackDepth, columns }
5210
+ });
5211
+ return wrapped;
5212
+ }
5213
+ function reportVerticalRunaway(boxes, nodes, edges, direction, options, diagnostics) {
5214
+ if (!isVerticalRunaway(boxes, nodes, direction, options)) {
5215
+ return;
5216
+ }
5217
+ diagnostics.push({
5218
+ severity: "warning",
5219
+ code: "vertical_runaway",
5220
+ message: "Layout produced a tall vertical stack beyond the preferred aspect ratio.",
5221
+ path: ["nodes"],
5222
+ detail: {
5223
+ nodeCount: nodes.length,
5224
+ edgeCount: edges.length,
5225
+ ...options.preferredAspectRatio === void 0 ? {} : { preferredAspectRatio: options.preferredAspectRatio },
5226
+ ...options.maxStackDepth === void 0 ? {} : { maxStackDepth: options.maxStackDepth }
5227
+ }
5228
+ });
5229
+ }
5230
+ function isVerticalRunaway(boxes, nodes, direction, options) {
5231
+ if (options.maxStackDepth === void 0 && options.preferredAspectRatio === void 0) {
5232
+ return false;
5233
+ }
5234
+ if (nodes.length < 2 || direction !== "LR" && direction !== "RL") {
5235
+ return false;
5236
+ }
5237
+ const nodeBoxes = nodes.map((node) => boxes.get(node.id)).filter((box) => box !== void 0);
5238
+ if (nodeBoxes.length < 2) {
5239
+ return false;
5240
+ }
5241
+ const bounds = unionBoxes(nodeBoxes);
5242
+ const aspectRatio = bounds.width <= 0 ? Number.POSITIVE_INFINITY : bounds.height / bounds.width;
5243
+ const preferred = options.preferredAspectRatio ?? 3;
5244
+ if (aspectRatio < preferred) {
5245
+ return false;
5246
+ }
5247
+ const xCenters = nodeBoxes.map((box) => box.x + box.width / 2);
5248
+ const xSpread = Math.max(...xCenters) - Math.min(...xCenters);
5249
+ const maxWidth = Math.max(...nodeBoxes.map((box) => box.width));
5250
+ return xSpread <= Math.max(maxWidth, options.overlapSpacing ?? 40);
5251
+ }
5252
+ function applySingleSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, locks, diagnostics, movedChildIds, laneGutter) {
5253
+ const headerHeight = swimlane.headerHeight ?? 28;
5254
+ const padding = swimlane.padding ?? 16;
5255
+ const laneBounds = swimlane.lanes.map((lane) => {
5256
+ const childBoxes = lane.children.map((child) => nodeBoxes.get(child)).filter((box) => box !== void 0);
5257
+ return childBoxes.length === 0 ? void 0 : unionBoxes(childBoxes);
5258
+ });
5259
+ const populatedBounds = laneBounds.filter(
5260
+ (box) => box !== void 0
5261
+ );
5262
+ if (populatedBounds.length === 0) {
5263
+ return void 0;
5264
+ }
5265
+ if (swimlane.orientation === "vertical") {
5266
+ return applyVerticalSwimlaneContract(
5267
+ swimlane,
5268
+ edges,
5269
+ topToBottomFlow,
5270
+ nodeBoxes,
5271
+ laneBounds,
5272
+ headerHeight,
5273
+ padding,
5274
+ locks,
5275
+ diagnostics,
5276
+ movedChildIds,
5277
+ laneGutter
5278
+ );
5279
+ }
5280
+ return applyHorizontalSwimlaneContract(
5281
+ swimlane,
5282
+ nodeBoxes,
5283
+ laneBounds,
5284
+ headerHeight,
5285
+ padding,
5286
+ locks,
5287
+ diagnostics,
5288
+ movedChildIds,
5289
+ laneGutter
5290
+ );
5291
+ }
5292
+ function applyVerticalSwimlaneContract(swimlane, edges, topToBottomFlow, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter) {
5293
+ const populatedBounds = laneBounds.filter(
5294
+ (box) => box !== void 0
5295
+ );
5296
+ const top = Math.min(...populatedBounds.map((box) => box.y));
5297
+ const left = Math.min(...populatedBounds.map((box) => box.x));
5298
+ const maxChildHeight = Math.max(...populatedBounds.map((box) => box.height));
5299
+ const flowRanks = topToBottomFlow ? rankVerticalSwimlaneChildren(swimlane, edges) : /* @__PURE__ */ new Map();
5300
+ const maxRank = flowRanks.size === 0 ? 0 : Math.max(...Array.from(flowRanks.values()));
5301
+ const rankStackGap = Math.max(8, padding / 2);
5302
+ const maxRankStackHeight = maxVerticalRankStackHeight(
5303
+ swimlane,
5304
+ nodeBoxes,
5305
+ flowRanks,
5306
+ rankStackGap
5307
+ );
5308
+ const rankSpacing = Math.max(96, maxRankStackHeight + padding);
5309
+ const contentHeight = maxRank === 0 ? maxChildHeight : maxRankStackHeight + maxRank * rankSpacing;
5310
+ const slotWidth = Math.max(...populatedBounds.map((box) => box.width)) + padding * 2;
5311
+ const laneStep = slotWidth + laneGutter;
5312
+ const laneContentTop = top + headerHeight + padding;
5313
+ for (let index = 0; index < swimlane.lanes.length; index += 1) {
5314
+ const lane = swimlane.lanes[index];
5315
+ const bounds = laneBounds[index];
5316
+ if (lane === void 0 || bounds === void 0) {
5317
+ continue;
5318
+ }
5319
+ const target = {
5320
+ x: left + laneStep * index + padding,
5321
+ y: laneContentTop
5322
+ };
5323
+ if (maxRank === 0) {
5324
+ moveLaneChildren(
5325
+ lane.children,
5326
+ nodeBoxes,
5327
+ locks,
5328
+ diagnostics,
5329
+ movedChildIds,
5330
+ {
5331
+ x: target.x - bounds.x,
5332
+ y: target.y - bounds.y
5333
+ }
5334
+ );
5335
+ continue;
5336
+ }
5337
+ moveRankedVerticalLaneChildren(
5338
+ lane.children,
5339
+ nodeBoxes,
5340
+ locks,
5341
+ diagnostics,
5342
+ movedChildIds,
5343
+ flowRanks,
5344
+ rankSpacing,
5345
+ rankStackGap,
5346
+ {
5347
+ x: target.x - bounds.x,
5348
+ y: laneContentTop
5349
+ }
5350
+ );
5351
+ }
5352
+ return {
5353
+ box: {
5354
+ x: left,
5355
+ y: top,
5356
+ width: laneStep * (swimlane.lanes.length - 1) + slotWidth,
5357
+ height: contentHeight + padding * 2 + headerHeight
5358
+ },
5359
+ slotWidth,
5360
+ slotHeight: contentHeight + padding * 2 + headerHeight,
5361
+ laneStep
5362
+ };
5363
+ }
5364
+ function isTopToBottomReadingDirection(value) {
5365
+ return value === "top_to_bottom" || value === "top-to-bottom";
5366
+ }
5367
+ function rankVerticalSwimlaneChildren(swimlane, edges) {
5368
+ const childOrder = /* @__PURE__ */ new Map();
5369
+ for (const lane of swimlane.lanes) {
5370
+ for (const childId of lane.children) {
5371
+ if (!childOrder.has(childId)) {
5372
+ childOrder.set(childId, childOrder.size);
5373
+ }
5374
+ }
5375
+ }
5376
+ if (childOrder.size === 0) {
5377
+ return /* @__PURE__ */ new Map();
5378
+ }
5379
+ const childIds = new Set(childOrder.keys());
5380
+ const relevantEdges = edges.filter(
5381
+ (edge) => childIds.has(edge.source.nodeId) && childIds.has(edge.target.nodeId) && edge.source.nodeId !== edge.target.nodeId
5382
+ );
5383
+ if (relevantEdges.length === 0) {
5384
+ return /* @__PURE__ */ new Map();
5385
+ }
5386
+ const ranks = new Map([...childIds].map((id) => [id, 0]));
5387
+ const outgoing = /* @__PURE__ */ new Map();
5388
+ const inDegree = new Map([...childIds].map((id) => [id, 0]));
5389
+ for (const edge of relevantEdges) {
5390
+ const targets = outgoing.get(edge.source.nodeId) ?? [];
5391
+ targets.push(edge.target.nodeId);
5392
+ outgoing.set(edge.source.nodeId, targets);
5393
+ inDegree.set(
5394
+ edge.target.nodeId,
5395
+ (inDegree.get(edge.target.nodeId) ?? 0) + 1
5396
+ );
5397
+ }
5398
+ const queue = [...childIds].filter((id) => (inDegree.get(id) ?? 0) === 0).sort((a, b) => (childOrder.get(a) ?? 0) - (childOrder.get(b) ?? 0));
5399
+ let visited = 0;
5400
+ for (let cursor = 0; cursor < queue.length; cursor += 1) {
5401
+ const sourceId = queue[cursor];
5402
+ if (sourceId === void 0) {
5403
+ continue;
5404
+ }
5405
+ visited += 1;
5406
+ for (const targetId of outgoing.get(sourceId) ?? []) {
5407
+ ranks.set(
5408
+ targetId,
5409
+ Math.max(ranks.get(targetId) ?? 0, (ranks.get(sourceId) ?? 0) + 1)
5410
+ );
5411
+ const nextInDegree = (inDegree.get(targetId) ?? 0) - 1;
5412
+ inDegree.set(targetId, nextInDegree);
5413
+ if (nextInDegree === 0) {
5414
+ queue.push(targetId);
5415
+ }
5416
+ }
5417
+ }
5418
+ return visited === childIds.size ? ranks : rankCyclicSwimlaneChildren(childIds, relevantEdges);
5419
+ }
5420
+ function rankCyclicSwimlaneChildren(childIds, edges) {
5421
+ const maxRank = Math.max(0, childIds.size - 1);
5422
+ const ranks = new Map([...childIds].map((id) => [id, 0]));
5423
+ for (let iteration = 0; iteration < childIds.size; iteration += 1) {
5424
+ let changed = false;
5425
+ for (const edge of edges) {
5426
+ const nextRank = Math.min(
5427
+ maxRank,
5428
+ (ranks.get(edge.source.nodeId) ?? 0) + 1
5429
+ );
5430
+ if (nextRank > (ranks.get(edge.target.nodeId) ?? 0)) {
5431
+ ranks.set(edge.target.nodeId, nextRank);
5432
+ changed = true;
5433
+ }
5434
+ }
5435
+ if (!changed) {
5436
+ break;
5437
+ }
5438
+ }
5439
+ return ranks;
5440
+ }
5441
+ function maxVerticalRankStackHeight(swimlane, nodeBoxes, flowRanks, gap) {
5442
+ let maxHeight = 0;
5443
+ for (const lane of swimlane.lanes) {
5444
+ for (const stack of rankStacks(
5445
+ lane.children,
5446
+ nodeBoxes,
5447
+ flowRanks
5448
+ ).values()) {
5449
+ const height = stack.reduce(
5450
+ (total, item, index) => total + item.box.height + (index === 0 ? 0 : gap),
5451
+ 0
5452
+ );
5453
+ maxHeight = Math.max(maxHeight, height);
5454
+ }
5455
+ }
5456
+ return maxHeight;
5457
+ }
5458
+ function moveRankedVerticalLaneChildren(childIds, nodeBoxes, locks, diagnostics, movedChildIds, flowRanks, rankSpacing, rankStackGap, target) {
5459
+ for (const [rank, stack] of rankStacks(childIds, nodeBoxes, flowRanks)) {
5460
+ let yOffset = 0;
5461
+ for (const item of stack) {
5462
+ const { childId, box } = item;
5463
+ if (locks.has(childId)) {
5464
+ diagnostics.push({
5465
+ severity: "warning",
5466
+ code: "constraints.locked-target-not-moved",
5467
+ message: `Locked child ${childId} was not moved into contract swimlane slot.`,
5468
+ path: ["swimlanes"],
5469
+ detail: { nodeId: childId }
5470
+ });
5471
+ continue;
5472
+ }
5473
+ const next = {
5474
+ ...box,
5475
+ x: box.x + target.x,
5476
+ y: target.y + rank * rankSpacing + yOffset
5477
+ };
5478
+ if (next.x !== box.x || next.y !== box.y) {
5479
+ movedChildIds.add(childId);
5480
+ }
5481
+ nodeBoxes.set(childId, next);
5482
+ yOffset += box.height + rankStackGap;
5483
+ }
5484
+ }
5485
+ }
5486
+ function rankStacks(childIds, nodeBoxes, flowRanks) {
5487
+ const stacks = /* @__PURE__ */ new Map();
5488
+ for (const childId of childIds) {
5489
+ const box = nodeBoxes.get(childId);
5490
+ if (box === void 0) {
5491
+ continue;
5492
+ }
5493
+ const rank = flowRanks.get(childId) ?? 0;
5494
+ const stack = stacks.get(rank) ?? [];
5495
+ stack.push({ childId, box });
5496
+ stacks.set(rank, stack);
5497
+ }
5498
+ for (const stack of stacks.values()) {
5499
+ stack.sort((a, b) => {
5500
+ const deltaY = a.box.y - b.box.y;
5501
+ return deltaY === 0 ? a.childId.localeCompare(b.childId) : deltaY;
5502
+ });
5503
+ }
5504
+ return stacks;
5505
+ }
5506
+ function applyHorizontalSwimlaneContract(swimlane, nodeBoxes, laneBounds, headerHeight, padding, locks, diagnostics, movedChildIds, laneGutter) {
5507
+ const populatedBounds = laneBounds.filter(
5508
+ (box) => box !== void 0
5509
+ );
5510
+ const top = Math.min(...populatedBounds.map((box) => box.y));
5511
+ const left = Math.min(...populatedBounds.map((box) => box.x));
5512
+ const slotWidth = Math.max(...populatedBounds.map((box) => box.width)) + headerHeight + padding * 2;
5513
+ const slotHeight = Math.max(...populatedBounds.map((box) => box.height)) + padding * 2;
5514
+ const laneStep = slotHeight + laneGutter;
5515
+ for (let index = 0; index < swimlane.lanes.length; index += 1) {
5516
+ const lane = swimlane.lanes[index];
5517
+ const bounds = laneBounds[index];
5518
+ if (lane === void 0 || bounds === void 0) {
5519
+ continue;
5520
+ }
5521
+ const target = {
5522
+ x: left + headerHeight + padding,
5523
+ y: top + laneStep * index + padding
5524
+ };
5525
+ moveLaneChildren(
5526
+ lane.children,
5527
+ nodeBoxes,
5528
+ locks,
5529
+ diagnostics,
5530
+ movedChildIds,
5531
+ {
5532
+ x: target.x - bounds.x,
5533
+ y: target.y - bounds.y
5534
+ }
5535
+ );
5536
+ }
5537
+ return {
5538
+ box: {
5539
+ x: left,
5540
+ y: top,
5541
+ width: slotWidth,
5542
+ height: laneStep * (swimlane.lanes.length - 1) + slotHeight
5543
+ },
5544
+ slotWidth,
5545
+ slotHeight,
5546
+ laneStep
5547
+ };
5548
+ }
5549
+ function moveLaneChildren(childIds, nodeBoxes, locks, diagnostics, movedChildIds, offset) {
5550
+ for (const childId of childIds) {
5551
+ const box = nodeBoxes.get(childId);
5552
+ if (box === void 0) {
5553
+ continue;
5554
+ }
5555
+ if (locks.has(childId)) {
5556
+ diagnostics.push({
5557
+ severity: "warning",
5558
+ code: "constraints.locked-target-not-moved",
5559
+ message: `Locked child ${childId} was not moved into contract swimlane slot.`,
5560
+ path: ["swimlanes"],
5561
+ detail: { nodeId: childId }
5562
+ });
5563
+ continue;
5564
+ }
5565
+ if (offset.x !== 0 || offset.y !== 0) {
5566
+ movedChildIds.add(childId);
5567
+ }
5568
+ nodeBoxes.set(childId, {
5569
+ ...box,
5570
+ x: box.x + offset.x,
5571
+ y: box.y + offset.y
5572
+ });
5573
+ }
5574
+ }
5575
+ function removeResolvedOverlapDiagnostics(diagnostics, nodeBoxes) {
5576
+ for (let index = diagnostics.length - 1; index >= 0; index -= 1) {
5577
+ const diagnostic = diagnostics[index];
5578
+ if (diagnostic?.code !== "constraints.overlap.unresolved") {
5579
+ continue;
5580
+ }
5581
+ const firstId = detailString(diagnostic, "firstId");
5582
+ const secondId = detailString(diagnostic, "secondId");
5583
+ const first = firstId === void 0 ? void 0 : nodeBoxes.get(firstId);
5584
+ const second = secondId === void 0 ? void 0 : nodeBoxes.get(secondId);
5585
+ if (first !== void 0 && second !== void 0 && !intersectsAabb(first, second)) {
5586
+ diagnostics.splice(index, 1);
5587
+ }
5588
+ }
5589
+ }
5590
+ function reportSwimlaneConstraintInvalidations(constraints, nodeBoxes, movedChildIds) {
5591
+ const diagnostics = [];
5592
+ for (const constraint of constraints) {
5593
+ const invalidatedNodeIds = movedConstraintNodeIds(
5594
+ constraint,
5595
+ nodeBoxes,
5596
+ movedChildIds
5597
+ );
5598
+ if (invalidatedNodeIds.length === 0) {
5599
+ continue;
5600
+ }
5601
+ diagnostics.push({
5602
+ severity: "warning",
5603
+ code: "constraints.swimlane-contract.invalidated",
5604
+ message: `Contract swimlane placement moved node(s) after ${constraint.kind} constraint solving; final geometry no longer satisfies that constraint.`,
5605
+ path: ["swimlanes"],
5606
+ detail: {
5607
+ constraintKind: constraint.kind,
5608
+ ...constraint.id === void 0 ? {} : { constraintId: constraint.id },
5609
+ nodeIds: invalidatedNodeIds
5610
+ }
5611
+ });
5612
+ }
5613
+ return diagnostics;
5614
+ }
5615
+ function movedConstraintNodeIds(constraint, nodeBoxes, movedChildIds) {
5616
+ switch (constraint.kind) {
5617
+ case "exact-position":
5618
+ return [];
5619
+ case "containment":
5620
+ return movedContainmentViolations(constraint, nodeBoxes, movedChildIds);
5621
+ case "relative-position":
5622
+ return movedRelativeViolations(constraint, nodeBoxes, movedChildIds);
5623
+ case "align":
5624
+ return movedAlignViolations(constraint, nodeBoxes, movedChildIds);
5625
+ case "distribute":
5626
+ return movedDistributeViolations(constraint, nodeBoxes, movedChildIds);
5627
+ }
5628
+ }
5629
+ function movedContainmentViolations(constraint, nodeBoxes, movedChildIds) {
5630
+ const container = nodeBoxes.get(constraint.containerId);
5631
+ if (container === void 0) {
5632
+ return [];
5633
+ }
5634
+ const content = paddedContentBox(container, constraint.padding);
5635
+ return constraint.childIds.filter((childId) => {
5636
+ if (!movedChildIds.has(childId)) {
5637
+ return false;
5638
+ }
5639
+ const child = nodeBoxes.get(childId);
5640
+ return child !== void 0 && !boxInside(child, content);
5641
+ });
5642
+ }
5643
+ function movedRelativeViolations(constraint, nodeBoxes, movedChildIds) {
5644
+ if (!movedChildIds.has(constraint.sourceId) && !movedChildIds.has(constraint.referenceId)) {
5645
+ return [];
5646
+ }
5647
+ const source = nodeBoxes.get(constraint.sourceId);
5648
+ const reference = nodeBoxes.get(constraint.referenceId);
5649
+ if (source === void 0 || reference === void 0) {
5650
+ return [];
5651
+ }
5652
+ return sameBoxPosition(
5653
+ source,
5654
+ expectedRelativeBox(source, reference, constraint)
5655
+ ) ? [] : [constraint.sourceId];
5656
+ }
5657
+ function movedAlignViolations(constraint, nodeBoxes, movedChildIds) {
5658
+ if (!constraint.targetIds.some((id) => movedChildIds.has(id))) {
5659
+ return [];
5660
+ }
5661
+ const targets = constraint.targetIds.map((id) => ({ id, box: nodeBoxes.get(id) })).filter(
5662
+ (target) => target.box !== void 0
5663
+ );
5664
+ const anchor = targets[0];
5665
+ if (anchor === void 0) {
5666
+ return [];
5667
+ }
5668
+ const expected = alignmentValue2(anchor.box, constraint.axis);
5669
+ return targets.filter(
5670
+ (target) => movedChildIds.has(target.id) && !sameNumber(alignmentValue2(target.box, constraint.axis), expected)
5671
+ ).map((target) => target.id);
5672
+ }
5673
+ function movedDistributeViolations(constraint, nodeBoxes, movedChildIds) {
5674
+ if (!constraint.targetIds.some((id) => movedChildIds.has(id))) {
5675
+ return [];
5676
+ }
5677
+ const targets = constraint.targetIds.map((id) => ({ id, box: nodeBoxes.get(id) })).filter(
5678
+ (target) => target.box !== void 0
5679
+ ).sort((a, b) => {
5680
+ const delta = constraint.axis === "horizontal" ? a.box.x - b.box.x : a.box.y - b.box.y;
5681
+ return delta === 0 ? a.id.localeCompare(b.id) : delta;
5682
+ });
5683
+ if (targets.length < 3) {
5684
+ return [];
5685
+ }
5686
+ const first = targets[0];
5687
+ const last = targets.at(-1);
5688
+ if (first === void 0 || last === void 0) {
5689
+ return [];
5690
+ }
5691
+ const expectedSpacing = constraint.spacing ?? (distributionStart2(last.box, constraint.axis) - distributionStart2(first.box, constraint.axis)) / (targets.length - 1);
5692
+ return targets.slice(1).filter((target, index) => {
5693
+ const previous = targets[index];
5694
+ if (previous === void 0 || !movedChildIds.has(target.id)) {
5695
+ return false;
5696
+ }
5697
+ return !sameNumber(
5698
+ distributionStart2(target.box, constraint.axis) - distributionStart2(previous.box, constraint.axis),
5699
+ expectedSpacing
5700
+ );
5701
+ }).map((target) => target.id);
5702
+ }
5703
+ function expectedRelativeBox(source, reference, constraint) {
5704
+ const offset = constraint.offset ?? { x: 0, y: 0 };
5705
+ switch (constraint.relation) {
5706
+ case "above":
5707
+ return {
5708
+ ...source,
5709
+ x: reference.x + offset.x,
5710
+ y: reference.y - source.height + offset.y
5711
+ };
5712
+ case "right-of":
5713
+ return {
5714
+ ...source,
5715
+ x: reference.x + reference.width + offset.x,
5716
+ y: reference.y + offset.y
5717
+ };
5718
+ case "below":
5719
+ return {
5720
+ ...source,
5721
+ x: reference.x + offset.x,
5722
+ y: reference.y + reference.height + offset.y
5723
+ };
5724
+ case "left-of":
5725
+ return {
5726
+ ...source,
5727
+ x: reference.x - source.width + offset.x,
5728
+ y: reference.y + offset.y
5729
+ };
5730
+ }
5731
+ }
5732
+ function paddedContentBox(container, padding) {
5733
+ const margin = padding ?? { top: 0, right: 0, bottom: 0, left: 0 };
5734
+ return {
5735
+ x: container.x + margin.left,
5736
+ y: container.y + margin.top,
5737
+ width: container.width - margin.left - margin.right,
5738
+ height: container.height - margin.top - margin.bottom
5739
+ };
5740
+ }
5741
+ function boxInside(child, container) {
5742
+ return child.x >= container.x && child.y >= container.y && child.x + child.width <= container.x + container.width && child.y + child.height <= container.y + container.height;
5743
+ }
5744
+ function sameBoxPosition(first, second) {
5745
+ return sameNumber(first.x, second.x) && sameNumber(first.y, second.y);
5746
+ }
5747
+ function sameNumber(first, second) {
5748
+ return Math.abs(first - second) < 1e-3;
5749
+ }
5750
+ function alignmentValue2(box, axis) {
5751
+ switch (axis) {
5752
+ case "x":
5753
+ case "left":
5754
+ return box.x;
5755
+ case "y":
5756
+ case "top":
5757
+ return box.y;
5758
+ case "center-x":
5759
+ return box.x + box.width / 2;
5760
+ case "center-y":
5761
+ return box.y + box.height / 2;
5762
+ case "right":
5763
+ return box.x + box.width;
5764
+ case "bottom":
5765
+ return box.y + box.height;
5766
+ }
5767
+ }
5768
+ function distributionStart2(box, axis) {
5769
+ return axis === "horizontal" ? box.x : box.y;
5770
+ }
5771
+ function detailString(diagnostic, key) {
5772
+ const value = diagnostic.detail?.[key];
5773
+ return typeof value === "string" ? value : void 0;
5774
+ }
5775
+ function reportSwimlaneOverlaps(nodeBoxes, locks, overlapSpacing) {
5776
+ const diagnostics = [];
5777
+ const ids = [...nodeBoxes.keys()].sort();
5778
+ for (const firstId of ids) {
5779
+ for (const secondId of ids) {
5780
+ if (firstId >= secondId) {
5781
+ continue;
5782
+ }
5783
+ const first = nodeBoxes.get(firstId);
5784
+ const second = nodeBoxes.get(secondId);
5785
+ if (first === void 0 || second === void 0) {
5786
+ continue;
5787
+ }
5788
+ if (!intersectsAabb(first, second)) {
5789
+ continue;
5790
+ }
5791
+ diagnostics.push({
5792
+ severity: "warning",
5793
+ code: "constraints.overlap.unresolved",
5794
+ message: `Boxes ${firstId} and ${secondId} still overlap after contract swimlane placement with configured spacing ${overlapSpacing}.`,
5795
+ path: ["swimlanes"],
5796
+ detail: {
5797
+ firstId,
5798
+ secondId,
5799
+ firstLocked: locks.has(firstId),
5800
+ secondLocked: locks.has(secondId)
5801
+ }
5802
+ });
5803
+ }
5804
+ }
5805
+ return diagnostics;
5806
+ }
5807
+ function coordinateNodes(nodes, boxes, options, diagnostics) {
5808
+ const coordinated = [];
5809
+ for (const node of nodes) {
5810
+ const box = boxes.get(node.id);
5811
+ if (box === void 0) {
5812
+ diagnostics.push({
5813
+ severity: "error",
5814
+ code: "solver.node-box.missing",
5815
+ message: `Node ${node.id} has no solved box.`,
5816
+ path: ["nodes", node.id],
5817
+ detail: { nodeId: node.id }
5818
+ });
5819
+ continue;
5820
+ }
5821
+ const geometry = computeShapeGeometry({
5822
+ shape: node.shape,
5823
+ box,
5824
+ obstacleMargin: options.obstacleMargin ?? 0
5825
+ });
5826
+ coordinated.push({
5827
+ id: node.id,
5828
+ ...node.label === void 0 ? {} : { label: node.label },
5829
+ ...node.style === void 0 ? {} : { style: node.style },
5830
+ ...node.ports === void 0 ? {} : { ports: coordinatePorts(node, box, options.portShifting) },
3024
5831
  ...node.compartments === void 0 ? {} : { compartments: node.compartments },
3025
5832
  ...node.labelLayout === void 0 ? {} : { labelLayout: node.labelLayout },
3026
5833
  shape: node.shape,
@@ -3065,7 +5872,10 @@ function coordinatePorts(node, nodeBox, portShifting) {
3065
5872
  }
3066
5873
  function portAnchor(nodeBox, side, index, count, portShifting) {
3067
5874
  const shiftingEnabled = portShifting?.enabled ?? true;
3068
- const spacing = portShifting?.spacing ?? 24;
5875
+ const requestedSpacing = portShifting?.spacing ?? 24;
5876
+ const maxOffset = side === "left" || side === "right" ? nodeBox.height / 2 : nodeBox.width / 2;
5877
+ const availableSpan = 2 * maxOffset;
5878
+ const spacing = shiftingEnabled && count > 1 ? Math.min(requestedSpacing, availableSpan / (count - 1)) : requestedSpacing;
3069
5879
  const centeredOffset = shiftingEnabled ? (index - (count - 1) / 2) * spacing : 0;
3070
5880
  switch (side) {
3071
5881
  case "left":
@@ -3111,16 +5921,70 @@ function portLabelBox(port) {
3111
5921
  height
3112
5922
  };
3113
5923
  }
3114
- function coordinateSwimlanes(swimlanes, nodeBoxes) {
3115
- const titleSize = 28;
3116
- const padding = 16;
5924
+ function coordinateSwimlanes(swimlanes, nodeBoxes, layouts) {
3117
5925
  return swimlanes.map((swimlane) => {
3118
- const laneBoxes = swimlane.lanes.flatMap((lane) => {
5926
+ const layout2 = swimlane.layout ?? "overlay";
5927
+ const headerHeight = swimlane.headerHeight ?? 28;
5928
+ const padding = swimlane.padding ?? 16;
5929
+ const contractLayout = layouts.get(swimlane.id);
5930
+ if (layout2 === "contract" && contractLayout !== void 0) {
5931
+ const lanes2 = swimlane.lanes.map((lane, index) => {
5932
+ const box = swimlane.orientation === "vertical" ? {
5933
+ x: contractLayout.box.x + contractLayout.laneStep * index,
5934
+ y: contractLayout.box.y,
5935
+ width: contractLayout.slotWidth,
5936
+ height: contractLayout.box.height
5937
+ } : {
5938
+ x: contractLayout.box.x,
5939
+ y: contractLayout.box.y + contractLayout.laneStep * index,
5940
+ width: contractLayout.box.width,
5941
+ height: contractLayout.slotHeight
5942
+ };
5943
+ const headerBox = swimlane.orientation === "vertical" ? {
5944
+ x: box.x,
5945
+ y: box.y,
5946
+ width: box.width,
5947
+ height: headerHeight
5948
+ } : {
5949
+ x: box.x,
5950
+ y: box.y,
5951
+ width: headerHeight,
5952
+ height: box.height
5953
+ };
5954
+ const contentBox2 = swimlane.orientation === "vertical" ? {
5955
+ x: box.x,
5956
+ y: box.y + headerHeight,
5957
+ width: box.width,
5958
+ height: Math.max(0, box.height - headerHeight)
5959
+ } : {
5960
+ x: box.x + headerHeight,
5961
+ y: box.y,
5962
+ width: Math.max(0, box.width - headerHeight),
5963
+ height: box.height
5964
+ };
5965
+ return {
5966
+ ...lane,
5967
+ box,
5968
+ headerBox,
5969
+ contentBox: contentBox2
5970
+ };
5971
+ });
5972
+ return {
5973
+ ...swimlane,
5974
+ lanes: lanes2,
5975
+ box: contractLayout.box,
5976
+ ...headerHeight === void 0 ? {} : { headerHeight },
5977
+ ...padding === void 0 ? {} : { padding }
5978
+ };
5979
+ }
5980
+ const laneContentBoxes = swimlane.lanes.map((lane) => {
3119
5981
  const childBoxes = lane.children.map((child) => nodeBoxes.get(child)).filter((box) => box !== void 0);
3120
- return childBoxes.length === 0 ? [] : [unionBoxes(childBoxes)];
5982
+ return childBoxes.length === 0 ? void 0 : unionBoxes(childBoxes);
3121
5983
  });
3122
- const laneUnion = laneBoxes.length === 0 ? { x: 0, y: 0, width: 120, height: 80 } : unionBoxes(laneBoxes);
3123
- const outer = expand(laneUnion, padding, titleSize);
5984
+ const laneUnion = laneContentBoxes.filter((box) => box !== void 0).length === 0 ? { x: 0, y: 0, width: 120, height: 80 } : unionBoxes(
5985
+ laneContentBoxes.filter((box) => box !== void 0)
5986
+ );
5987
+ const outer = expand(laneUnion, padding, headerHeight);
3124
5988
  const laneCount = Math.max(1, swimlane.lanes.length);
3125
5989
  const lanes = swimlane.lanes.map((lane, index) => {
3126
5990
  const box = swimlane.orientation === "vertical" ? {
@@ -3134,23 +5998,58 @@ function coordinateSwimlanes(swimlanes, nodeBoxes) {
3134
5998
  width: outer.width,
3135
5999
  height: outer.height / laneCount
3136
6000
  };
3137
- return { ...lane, box };
6001
+ const headerBox = layout2 === "contract" ? swimlane.orientation === "vertical" ? {
6002
+ x: box.x,
6003
+ y: box.y,
6004
+ width: box.width,
6005
+ height: headerHeight
6006
+ } : {
6007
+ x: box.x,
6008
+ y: box.y,
6009
+ width: headerHeight,
6010
+ height: box.height
6011
+ } : void 0;
6012
+ const contentBox2 = layout2 === "contract" ? swimlane.orientation === "vertical" ? {
6013
+ x: box.x,
6014
+ y: box.y + headerHeight,
6015
+ width: box.width,
6016
+ height: Math.max(0, box.height - headerHeight)
6017
+ } : {
6018
+ x: box.x + headerHeight,
6019
+ y: box.y,
6020
+ width: Math.max(0, box.width - headerHeight),
6021
+ height: box.height
6022
+ } : void 0;
6023
+ return {
6024
+ ...lane,
6025
+ box,
6026
+ ...headerBox === void 0 ? {} : { headerBox },
6027
+ ...contentBox2 === void 0 ? {} : { contentBox: contentBox2 }
6028
+ };
3138
6029
  });
3139
- return { ...swimlane, lanes, box: outer };
6030
+ return {
6031
+ ...swimlane,
6032
+ lanes,
6033
+ box: outer,
6034
+ ...headerHeight === void 0 ? {} : { headerHeight },
6035
+ ...padding === void 0 ? {} : { padding }
6036
+ };
3140
6037
  });
3141
6038
  }
3142
6039
  function coordinateFrame(frame, contentBounds) {
3143
- const padding = 32;
3144
- const titleHeight = 28;
6040
+ const padding = framePadding(frame.padding);
6041
+ const titleHeight = frame.headerHeight ?? 28;
3145
6042
  const titleWidth = Math.max(180, frame.titleTab.length * 7);
3146
6043
  const box = {
3147
- x: contentBounds.x - padding,
3148
- y: contentBounds.y - padding - titleHeight,
3149
- width: contentBounds.width + padding * 2,
3150
- height: contentBounds.height + padding * 2 + titleHeight
6044
+ x: contentBounds.x - padding.left,
6045
+ y: contentBounds.y - padding.top - titleHeight,
6046
+ width: contentBounds.width + padding.left + padding.right,
6047
+ height: contentBounds.height + padding.top + padding.bottom + titleHeight
3151
6048
  };
3152
6049
  return {
3153
6050
  ...frame,
6051
+ headerHeight: titleHeight,
6052
+ padding: frame.padding ?? 32,
3154
6053
  box,
3155
6054
  titleBox: {
3156
6055
  x: box.x,
@@ -3160,6 +6059,9 @@ function coordinateFrame(frame, contentBounds) {
3160
6059
  }
3161
6060
  };
3162
6061
  }
6062
+ function framePadding(value) {
6063
+ return normalizeInsets(value ?? 32);
6064
+ }
3163
6065
  function expand(box, padding, titleSize) {
3164
6066
  return {
3165
6067
  x: box.x - padding,
@@ -3198,25 +6100,397 @@ function coordinateGroups(groups, nodeBoxes, options, diagnostics) {
3198
6100
  if (childBoxes.length === 0) {
3199
6101
  diagnostics.push(groupReferenceMissing(group.id, "child", void 0));
3200
6102
  }
3201
- continue;
6103
+ continue;
6104
+ }
6105
+ const geometry = computeContainerGeometry({
6106
+ id: group.id,
6107
+ childBoxes,
6108
+ padding: group.padding,
6109
+ ...group.labelLayout === void 0 ? {} : { labelLayout: group.labelLayout },
6110
+ obstacleMargin: options.obstacleMargin ?? 0
6111
+ });
6112
+ groupBoxes.set(group.id, geometry.box);
6113
+ diagnostics.push(...geometry.diagnostics);
6114
+ coordinated.push({
6115
+ ...group,
6116
+ box: geometry.box
6117
+ });
6118
+ }
6119
+ return coordinated;
6120
+ }
6121
+ function coordinateMatrices(matrices) {
6122
+ return matrices.map((block) => ({
6123
+ ...block,
6124
+ box: blockBox(block, {
6125
+ width: defaultMatrixRowHeaderWidth2(block) + Math.max(1, block.cols.length) * DEFAULT_MATRIX_CELL_SIZE2.width,
6126
+ height: Math.max(1, block.rows.length + 1) * DEFAULT_MATRIX_CELL_SIZE2.height
6127
+ })
6128
+ }));
6129
+ }
6130
+ function defaultMatrixRowHeaderWidth2(block) {
6131
+ return block.rows.length === 0 ? 0 : Math.min(96, DEFAULT_MATRIX_CELL_SIZE2.width);
6132
+ }
6133
+ function coordinateTables(tables) {
6134
+ return tables.map((table) => {
6135
+ const box = blockBox(table, {
6136
+ width: Math.max(1, table.columns.length) * DEFAULT_TABLE_CELL_SIZE2.width,
6137
+ height: Math.max(1, table.rows.length + 1) * DEFAULT_TABLE_CELL_SIZE2.height
6138
+ });
6139
+ return {
6140
+ ...table,
6141
+ box,
6142
+ columnXOffsets: columnXOffsets(table, box)
6143
+ };
6144
+ });
6145
+ }
6146
+ function coordinateEvidencePanels(panels) {
6147
+ return panels.map((block) => ({
6148
+ ...block,
6149
+ box: blockBox(block, {
6150
+ width: DEFAULT_PANEL_WIDTH,
6151
+ height: Math.max(1, block.items.length) * DEFAULT_PANEL_ITEM_HEIGHT2
6152
+ })
6153
+ }));
6154
+ }
6155
+ function edgeBounds(edges) {
6156
+ return edges.flatMap((edge) => {
6157
+ if (edge.points.length === 0) {
6158
+ return [];
6159
+ }
6160
+ const extraPoints = [];
6161
+ if (edge.points.length >= 2) {
6162
+ const arrowhead = computeArrowhead(edge.points);
6163
+ extraPoints.push(arrowhead.tip, arrowhead.left, arrowhead.right);
6164
+ }
6165
+ const allPoints = [...edge.points, ...extraPoints];
6166
+ const minX = Math.min(...allPoints.map((point2) => point2.x));
6167
+ const minY = Math.min(...allPoints.map((point2) => point2.y));
6168
+ const maxX = Math.max(...allPoints.map((point2) => point2.x));
6169
+ const maxY = Math.max(...allPoints.map((point2) => point2.y));
6170
+ return [
6171
+ {
6172
+ x: minX,
6173
+ y: minY,
6174
+ width: maxX - minX,
6175
+ height: maxY - minY
6176
+ }
6177
+ ];
6178
+ });
6179
+ }
6180
+ function blockBox(block, defaultSize) {
6181
+ return {
6182
+ x: block.position?.x ?? 0,
6183
+ y: block.position?.y ?? 0,
6184
+ width: block.size?.width ?? defaultSize.width,
6185
+ height: block.size?.height ?? defaultSize.height
6186
+ };
6187
+ }
6188
+ function placeEvidenceBlocks(obstacleMargin, blocks, contentBounds) {
6189
+ const margin = normalizeInsets(obstacleMargin);
6190
+ const horizontalGap = Math.max(
6191
+ DEFAULT_EVIDENCE_BLOCK_GAP,
6192
+ margin.right + margin.left
6193
+ );
6194
+ const verticalGap = Math.max(
6195
+ DEFAULT_EVIDENCE_BLOCK_GAP,
6196
+ margin.bottom + margin.top
6197
+ );
6198
+ let nextY = contentBounds.y;
6199
+ const x = contentBounds.x + contentBounds.width + horizontalGap;
6200
+ for (const block of blocks) {
6201
+ if (block.position !== void 0) {
6202
+ continue;
6203
+ }
6204
+ block.box.x = x;
6205
+ block.box.y = nextY;
6206
+ nextY += block.box.height + verticalGap;
6207
+ }
6208
+ }
6209
+ function columnXOffsets(table, box) {
6210
+ if (table.columns.length === 0) {
6211
+ return [];
6212
+ }
6213
+ const columnWidth = box.width / table.columns.length;
6214
+ return table.columns.map((_, index) => box.x + index * columnWidth);
6215
+ }
6216
+ function tableCellBox2(table, columnIndex, rowIndex, rowHeight, columnCount) {
6217
+ const x = table.columnXOffsets[columnIndex] ?? table.box.x + table.box.width / columnCount * columnIndex;
6218
+ const nextX = table.columnXOffsets[columnIndex + 1] ?? table.box.x + table.box.width;
6219
+ return {
6220
+ x,
6221
+ y: table.box.y + rowIndex * rowHeight,
6222
+ width: nextX - x,
6223
+ height: rowHeight
6224
+ };
6225
+ }
6226
+ function refreshTableColumnXOffsets(tables) {
6227
+ for (const table of tables) {
6228
+ table.columnXOffsets = columnXOffsets(table, table.box);
6229
+ }
6230
+ }
6231
+ function measureEvidenceTextBlocks(matrices, tables, panels, textMeasurer) {
6232
+ const measurer = textMeasurer ?? createDefaultTextMeasurer();
6233
+ for (const matrix of matrices) {
6234
+ const geometry = matrixGeometry(matrix);
6235
+ matrix.columnLabelLayouts = matrix.cols.map(
6236
+ (column) => measureEvidenceTextLayout(column, geometry.columnHeaderBox, measurer)
6237
+ );
6238
+ matrix.rowLabelLayouts = matrix.rows.map(
6239
+ (row, index) => measureEvidenceTextLayout(row, geometry.rowHeaderBox(index), measurer)
6240
+ );
6241
+ matrix.cellLabelLayouts = matrix.rows.map(
6242
+ (_, rowIndex) => matrix.cols.map((_2, columnIndex) => {
6243
+ const cell2 = matrix.cells[rowIndex]?.[columnIndex] ?? { text: "" };
6244
+ return measureEvidenceTextLayout(
6245
+ cell2.text,
6246
+ geometry.cellBox(rowIndex, columnIndex),
6247
+ measurer
6248
+ );
6249
+ })
6250
+ );
6251
+ }
6252
+ for (const table of tables) {
6253
+ const rowHeight = table.box.height / Math.max(1, table.rows.length + 1);
6254
+ const columnCount = Math.max(1, table.columns.length);
6255
+ table.columnLabelLayouts = table.columns.map(
6256
+ (column, columnIndex) => measureEvidenceTextLayout(
6257
+ column.label.text,
6258
+ tableCellBox2(table, columnIndex, 0, rowHeight, columnCount),
6259
+ measurer
6260
+ )
6261
+ );
6262
+ table.cellLabelLayouts = table.rows.map(
6263
+ (row, rowIndex) => table.columns.map((column, columnIndex) => {
6264
+ const cell2 = row.cells[column.id] ?? { text: "" };
6265
+ return measureEvidenceTextLayout(
6266
+ cell2.text,
6267
+ tableCellBox2(
6268
+ table,
6269
+ columnIndex,
6270
+ rowIndex + 1,
6271
+ rowHeight,
6272
+ columnCount
6273
+ ),
6274
+ measurer
6275
+ );
6276
+ })
6277
+ );
6278
+ }
6279
+ for (const panel of panels) {
6280
+ const geometry = panelGeometry(panel);
6281
+ panel.titleLayout = measureEvidenceTextLayout(
6282
+ `${panel.kind}: ${panel.id}`,
6283
+ geometry.titleBox,
6284
+ measurer
6285
+ );
6286
+ panel.itemLayouts = panel.items.map(
6287
+ (item, index) => measureEvidenceTextLayout(
6288
+ panelItemText2(item.label.text, item.detail?.text),
6289
+ geometry.itemRowBox(index),
6290
+ measurer
6291
+ )
6292
+ );
6293
+ }
6294
+ }
6295
+ function measureEvidenceTextLayout(text, box, textMeasurer) {
6296
+ const lineHeight = EVIDENCE_TEXT_FONT.lineHeight;
6297
+ return {
6298
+ lines: wrapEvidenceText(text, {
6299
+ maxWidth: Math.max(0, box.width - 8),
6300
+ maxLines: Math.max(1, Math.floor((box.height - 4) / lineHeight)),
6301
+ textMeasurer
6302
+ })
6303
+ };
6304
+ }
6305
+ function wrapEvidenceText(text, options) {
6306
+ const normalized = text.trim().replace(/\s+/g, " ");
6307
+ if (normalized.length === 0) {
6308
+ return [""];
6309
+ }
6310
+ const lines = [];
6311
+ let current = "";
6312
+ let overflow = false;
6313
+ for (const word of normalized.split(" ")) {
6314
+ const chunks = chunkEvidenceWord(
6315
+ word,
6316
+ options.maxWidth,
6317
+ options.textMeasurer
6318
+ );
6319
+ for (const chunk of chunks) {
6320
+ const candidate = current.length === 0 ? chunk : `${current} ${chunk}`;
6321
+ if (measureEvidenceText(candidate, options.textMeasurer) <= options.maxWidth) {
6322
+ current = candidate;
6323
+ continue;
6324
+ }
6325
+ if (current.length > 0) {
6326
+ lines.push(current);
6327
+ current = chunk;
6328
+ } else {
6329
+ lines.push(chunk);
6330
+ current = "";
6331
+ }
6332
+ if (lines.length >= options.maxLines) {
6333
+ overflow = true;
6334
+ break;
6335
+ }
6336
+ }
6337
+ if (overflow) {
6338
+ break;
6339
+ }
6340
+ }
6341
+ if (!overflow && current.length > 0) {
6342
+ lines.push(current);
6343
+ }
6344
+ if (lines.length > options.maxLines) {
6345
+ overflow = true;
6346
+ lines.length = options.maxLines;
6347
+ }
6348
+ if (overflow || lines.length === options.maxLines) {
6349
+ const rendered = lines.join(" ");
6350
+ if (rendered.length < normalized.length) {
6351
+ lines[lines.length - 1] = ellipsizeMeasuredEvidenceLine(
6352
+ lines[lines.length - 1] ?? "",
6353
+ options.maxWidth,
6354
+ options.textMeasurer
6355
+ );
6356
+ }
6357
+ }
6358
+ return lines.length === 0 ? [""] : lines;
6359
+ }
6360
+ function chunkEvidenceWord(word, maxWidth, textMeasurer) {
6361
+ if (measureEvidenceText(word, textMeasurer) <= maxWidth) {
6362
+ return [word];
6363
+ }
6364
+ const chunks = [];
6365
+ let current = "";
6366
+ for (const char of Array.from(word)) {
6367
+ const candidate = `${current}${char}`;
6368
+ if (current.length > 0 && measureEvidenceText(candidate, textMeasurer) > maxWidth) {
6369
+ chunks.push(current);
6370
+ current = char;
6371
+ continue;
6372
+ }
6373
+ current = candidate;
6374
+ }
6375
+ if (current.length > 0) {
6376
+ chunks.push(current);
6377
+ }
6378
+ return chunks.length === 0 ? [word] : chunks;
6379
+ }
6380
+ function ellipsizeMeasuredEvidenceLine(line, maxWidth, textMeasurer) {
6381
+ const ellipsis = "...";
6382
+ if (measureEvidenceText(ellipsis, textMeasurer) > maxWidth) {
6383
+ return "";
6384
+ }
6385
+ let candidate = line.trimEnd();
6386
+ while (candidate.length > 0 && measureEvidenceText(`${candidate}${ellipsis}`, textMeasurer) > maxWidth) {
6387
+ candidate = Array.from(candidate).slice(0, -1).join("").trimEnd();
6388
+ }
6389
+ return `${candidate}${ellipsis}`;
6390
+ }
6391
+ function measureEvidenceText(text, textMeasurer) {
6392
+ return textMeasurer.naturalWidth(
6393
+ textMeasurer.prepare(text, EVIDENCE_TEXT_FONT)
6394
+ );
6395
+ }
6396
+ function matrixGeometry(matrix) {
6397
+ const columnCount = Math.max(1, matrix.cols.length);
6398
+ const rowCount = matrix.rows.length;
6399
+ const rowHeaderWidth = rowCount > 0 ? Math.min(96, matrix.box.width * 0.28) : 0;
6400
+ const dataWidth = Math.max(0, matrix.box.width - rowHeaderWidth);
6401
+ const cellWidth = dataWidth / columnCount;
6402
+ const rowHeight = matrix.box.height / Math.max(1, rowCount + 1);
6403
+ return {
6404
+ rowHeaderWidth,
6405
+ cellWidth,
6406
+ rowHeight,
6407
+ columnHeaderBox: {
6408
+ x: matrix.box.x + rowHeaderWidth,
6409
+ y: matrix.box.y,
6410
+ width: cellWidth,
6411
+ height: rowHeight
6412
+ },
6413
+ rowHeaderBox: (rowIndex) => ({
6414
+ x: matrix.box.x,
6415
+ y: matrix.box.y + (rowIndex + 1) * rowHeight,
6416
+ width: rowHeaderWidth,
6417
+ height: rowHeight
6418
+ }),
6419
+ cellBox: (rowIndex, columnIndex) => ({
6420
+ x: matrix.box.x + rowHeaderWidth + columnIndex * cellWidth,
6421
+ y: matrix.box.y + (rowIndex + 1) * rowHeight,
6422
+ width: cellWidth,
6423
+ height: rowHeight
6424
+ })
6425
+ };
6426
+ }
6427
+ function panelGeometry(panel) {
6428
+ const titleWidth = Math.min(panel.box.width * 0.36, 140);
6429
+ const itemBox = {
6430
+ x: panel.box.x + titleWidth,
6431
+ y: panel.box.y,
6432
+ width: panel.box.width - titleWidth,
6433
+ height: panel.box.height
6434
+ };
6435
+ const itemHeight = panel.box.height / Math.max(1, panel.items.length);
6436
+ return {
6437
+ titleBox: {
6438
+ x: panel.box.x,
6439
+ y: panel.box.y,
6440
+ width: titleWidth,
6441
+ height: panel.box.height
6442
+ },
6443
+ itemRowBox: (index) => ({
6444
+ x: itemBox.x,
6445
+ y: itemBox.y + index * itemHeight,
6446
+ width: itemBox.width,
6447
+ height: itemHeight
6448
+ })
6449
+ };
6450
+ }
6451
+ function panelItemText2(label, detail) {
6452
+ return detail === void 0 ? label : `${label}: ${detail}`;
6453
+ }
6454
+ function reportEvidenceBlockOverlaps(evidenceBlocks, contentBlocks) {
6455
+ const diagnostics = [];
6456
+ for (let index = 0; index < evidenceBlocks.length; index += 1) {
6457
+ const block = evidenceBlocks[index];
6458
+ if (block === void 0 || block.position === void 0) {
6459
+ continue;
6460
+ }
6461
+ for (const content of contentBlocks) {
6462
+ if (intersectsAabb(block.box, content.box)) {
6463
+ diagnostics.push(evidenceOverlapDiagnostic(block, content));
6464
+ }
6465
+ }
6466
+ for (let otherIndex = 0; otherIndex < evidenceBlocks.length; otherIndex += 1) {
6467
+ if (otherIndex === index) {
6468
+ continue;
6469
+ }
6470
+ const other = evidenceBlocks[otherIndex];
6471
+ if (other === void 0 || other.position !== void 0 && otherIndex < index || !intersectsAabb(block.box, other.box)) {
6472
+ continue;
6473
+ }
6474
+ diagnostics.push(evidenceOverlapDiagnostic(block, other));
3202
6475
  }
3203
- const geometry = computeContainerGeometry({
3204
- id: group.id,
3205
- childBoxes,
3206
- padding: group.padding,
3207
- ...group.labelLayout === void 0 ? {} : { labelLayout: group.labelLayout },
3208
- obstacleMargin: options.obstacleMargin ?? 0
3209
- });
3210
- groupBoxes.set(group.id, geometry.box);
3211
- diagnostics.push(...geometry.diagnostics);
3212
- coordinated.push({
3213
- ...group,
3214
- box: geometry.box
3215
- });
3216
6476
  }
3217
- return coordinated;
6477
+ return diagnostics;
6478
+ }
6479
+ function evidenceOverlapDiagnostic(block, conflict) {
6480
+ return {
6481
+ severity: "warning",
6482
+ code: "constraints.overlap.unresolved",
6483
+ message: `Evidence block ${block.id} overlaps ${conflict.kind} ${conflict.id}.`,
6484
+ path: ["evidence", block.id],
6485
+ detail: {
6486
+ evidenceBlockId: block.id,
6487
+ evidenceBlockKind: block.kind,
6488
+ conflictingObjectId: conflict.id,
6489
+ conflictingObjectKind: conflict.kind
6490
+ }
6491
+ };
3218
6492
  }
3219
- function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, direction, options, diagnostics) {
6493
+ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics) {
3220
6494
  const coordinated = [];
3221
6495
  const coordinatedNodeById = new Map(
3222
6496
  coordinatedNodes.map((node) => [node.id, node])
@@ -3240,6 +6514,8 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, direction, o
3240
6514
  }
3241
6515
  const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
3242
6516
  const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
6517
+ const connectedTextOwners = edgeConnectedTextOwnerIds(edge);
6518
+ const routeTextObstacles = textObstacles.filter((annotation) => !connectedTextOwners.has(annotation.ownerId)).map((annotation) => annotation.box);
3243
6519
  const route = routeEdge({
3244
6520
  kind: options.routeKind ?? "orthogonal",
3245
6521
  direction,
@@ -3247,9 +6523,14 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, direction, o
3247
6523
  target: portGeometry(target, targetPort),
3248
6524
  ...edge.source.anchor === void 0 ? {} : { sourceAnchor: edge.source.anchor },
3249
6525
  ...edge.target.anchor === void 0 ? {} : { targetAnchor: edge.target.anchor },
3250
- obstacles: obstacles.filter(
3251
- (obstacle) => obstacle !== source.obstacleBox && obstacle !== target.obstacleBox
3252
- )
6526
+ obstacles: [
6527
+ ...obstacles.filter(
6528
+ (obstacle) => obstacle !== source.obstacleBox && obstacle !== target.obstacleBox
6529
+ ),
6530
+ ...softObstacles,
6531
+ ...routeTextObstacles
6532
+ ],
6533
+ hardObstacles
3253
6534
  });
3254
6535
  diagnostics.push(
3255
6536
  ...route.diagnostics.map((diagnostic) => ({
@@ -3264,6 +6545,610 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, direction, o
3264
6545
  }
3265
6546
  return coordinated;
3266
6547
  }
6548
+ function edgeConnectedTextOwnerIds(edge) {
6549
+ const owners = /* @__PURE__ */ new Set();
6550
+ if (edge.source.portId !== void 0) {
6551
+ owners.add(`${edge.source.nodeId}.${edge.source.portId}`);
6552
+ }
6553
+ if (edge.target.portId !== void 0) {
6554
+ owners.add(`${edge.target.nodeId}.${edge.target.portId}`);
6555
+ }
6556
+ return owners;
6557
+ }
6558
+ function coordinateBaseTextAnnotations(input) {
6559
+ const measurer = input.textMeasurer ?? createDefaultTextMeasurer();
6560
+ const annotations = [];
6561
+ for (const node of input.nodes) {
6562
+ if (node.compartments !== void 0) {
6563
+ continue;
6564
+ }
6565
+ if (node.labelLayout === void 0 && node.label === void 0) {
6566
+ continue;
6567
+ }
6568
+ const layout2 = node.labelLayout ?? fallbackLabelLayout(node.label?.text ?? "");
6569
+ const buildAnnotation = node.labelLayout === void 0 ? buildAnchorCenteredTextAnnotation : buildTextAnnotation;
6570
+ annotations.push(
6571
+ buildAnnotation({
6572
+ ownerId: node.id,
6573
+ surfaceKind: "node-label",
6574
+ layout: layout2,
6575
+ typography: typographyForLabel(node.label),
6576
+ anchor: node.box
6577
+ })
6578
+ );
6579
+ }
6580
+ for (const group of input.groups) {
6581
+ if (group.labelLayout === void 0 && group.label === void 0) {
6582
+ continue;
6583
+ }
6584
+ const layout2 = group.labelLayout ?? fallbackLabelLayout(group.label?.text ?? "");
6585
+ const buildAnnotation = group.labelLayout === void 0 ? buildAnchorCenteredTextAnnotation : buildTextAnnotation;
6586
+ annotations.push(
6587
+ buildAnnotation({
6588
+ ownerId: group.id,
6589
+ surfaceKind: "group-label",
6590
+ layout: layout2,
6591
+ typography: typographyForLabel(group.label),
6592
+ anchor: group.box
6593
+ })
6594
+ );
6595
+ }
6596
+ for (const node of input.nodes) {
6597
+ for (const port of node.ports ?? []) {
6598
+ if (port.label?.text === void 0) {
6599
+ continue;
6600
+ }
6601
+ const layout2 = fitLabel(
6602
+ port.label.text,
6603
+ {
6604
+ font: typographyTextStyle(port.label, {
6605
+ fontFamily: "Arial",
6606
+ fontSize: 10,
6607
+ lineHeight: 12
6608
+ }),
6609
+ padding: { top: 0, right: 0, bottom: 0, left: 0 },
6610
+ minSize: { width: 0, height: 0 },
6611
+ maxWidth: 160
6612
+ },
6613
+ measurer
6614
+ );
6615
+ annotations.push(
6616
+ buildTextAnnotation({
6617
+ ownerId: `${node.id}.${port.id}`,
6618
+ surfaceKind: "port-label",
6619
+ layout: layout2,
6620
+ typography: typographyForLabel(port.label),
6621
+ anchor: portLabelBox(port)
6622
+ })
6623
+ );
6624
+ }
6625
+ }
6626
+ for (const node of input.nodes) {
6627
+ if (node.compartments === void 0) {
6628
+ continue;
6629
+ }
6630
+ const rows = compartmentRows2(node);
6631
+ for (let index = 0; index < rows.length; index += 1) {
6632
+ const row = rows[index];
6633
+ if (row === void 0) {
6634
+ continue;
6635
+ }
6636
+ const layout2 = fitLabel(
6637
+ row,
6638
+ {
6639
+ font: { fontFamily: "Arial", fontSize: 11, lineHeight: 13 },
6640
+ padding: { top: 0, right: 0, bottom: 0, left: 0 },
6641
+ minSize: { width: 0, height: 0 },
6642
+ maxWidth: node.box.width
6643
+ },
6644
+ measurer
6645
+ );
6646
+ annotations.push(
6647
+ buildAnchorCenteredTextAnnotation({
6648
+ ownerId: node.id,
6649
+ surfaceKind: "compartment-row",
6650
+ surfaceIndex: index,
6651
+ layout: layout2,
6652
+ anchor: {
6653
+ x: node.box.x,
6654
+ y: node.box.y + 18 + index * 16,
6655
+ width: node.box.width,
6656
+ height: 16
6657
+ }
6658
+ })
6659
+ );
6660
+ }
6661
+ }
6662
+ for (const swimlane of input.swimlanes) {
6663
+ for (const lane of swimlane.lanes) {
6664
+ if (lane.label?.text === void 0 || lane.box === void 0) {
6665
+ continue;
6666
+ }
6667
+ const labelBox = lane.headerBox ?? lane.box;
6668
+ const layout2 = fitLabel(
6669
+ lane.label.text,
6670
+ {
6671
+ font: typographyTextStyle(lane.label, {
6672
+ fontFamily: "Arial",
6673
+ fontSize: 12,
6674
+ lineHeight: 14
6675
+ }),
6676
+ padding: { top: 0, right: 0, bottom: 0, left: 0 },
6677
+ minSize: { width: 0, height: 0 },
6678
+ maxWidth: swimlane.orientation === "horizontal" ? labelBox.height : labelBox.width
6679
+ },
6680
+ measurer
6681
+ );
6682
+ annotations.push(
6683
+ buildAnchorCenteredTextAnnotation({
6684
+ ownerId: `${swimlane.id}.${lane.id}`,
6685
+ surfaceKind: "swimlane-label",
6686
+ layout: layout2,
6687
+ typography: typographyForLabel(lane.label),
6688
+ anchor: labelBox
6689
+ })
6690
+ );
6691
+ }
6692
+ }
6693
+ return annotations;
6694
+ }
6695
+ function coordinateEdgeTextAnnotations(edges, obstacleBoxes, textMeasurer) {
6696
+ const measurer = textMeasurer ?? createDefaultTextMeasurer();
6697
+ const annotations = [];
6698
+ const placedLabelBoxes = [];
6699
+ for (const edge of edges) {
6700
+ if (edge.label?.text === void 0) {
6701
+ continue;
6702
+ }
6703
+ const layout2 = fitLabel(
6704
+ edge.label.text,
6705
+ {
6706
+ font: typographyTextStyle(edge.label, {
6707
+ fontFamily: "Arial",
6708
+ fontSize: 12,
6709
+ lineHeight: 14
6710
+ }),
6711
+ padding: { top: 0, right: 0, bottom: 0, left: 0 },
6712
+ minSize: { width: 0, height: 0 },
6713
+ maxWidth: 200
6714
+ },
6715
+ measurer
6716
+ );
6717
+ const center = edgeLabelAnchor(
6718
+ edge,
6719
+ layout2,
6720
+ edges,
6721
+ obstacleBoxes,
6722
+ placedLabelBoxes
6723
+ );
6724
+ placedLabelBoxes.push({
6725
+ x: center.x - layout2.box.width / 2,
6726
+ y: center.y - layout2.box.height / 2,
6727
+ width: layout2.box.width,
6728
+ height: layout2.box.height
6729
+ });
6730
+ annotations.push(
6731
+ buildCenteredTextAnnotation({
6732
+ ownerId: edge.id,
6733
+ surfaceKind: "edge-label",
6734
+ layout: layout2,
6735
+ typography: typographyForLabel(edge.label),
6736
+ center
6737
+ })
6738
+ );
6739
+ }
6740
+ return annotations;
6741
+ }
6742
+ function coordinateFrameTextAnnotation(frame, textMeasurer) {
6743
+ const layout2 = fitLabel(
6744
+ frame.titleTab,
6745
+ {
6746
+ font: { fontFamily: "Arial", fontSize: 12, lineHeight: 14 },
6747
+ padding: { top: 0, right: 0, bottom: 0, left: 0 },
6748
+ minSize: { width: 0, height: 0 },
6749
+ maxWidth: frame.titleBox.width
6750
+ },
6751
+ textMeasurer ?? createDefaultTextMeasurer()
6752
+ );
6753
+ return buildAnchorCenteredTextAnnotation({
6754
+ ownerId: frame.kind,
6755
+ surfaceKind: "frame-title",
6756
+ layout: layout2,
6757
+ anchor: frame.titleBox
6758
+ });
6759
+ }
6760
+ function buildTextAnnotation(input) {
6761
+ return {
6762
+ text: input.layout.text,
6763
+ ownerId: input.ownerId,
6764
+ surfaceKind: input.surfaceKind,
6765
+ ...input.surfaceIndex === void 0 ? {} : { surfaceIndex: input.surfaceIndex },
6766
+ box: {
6767
+ x: input.anchor.x + input.layout.box.x,
6768
+ y: input.anchor.y + input.layout.box.y,
6769
+ width: input.layout.box.width,
6770
+ height: input.layout.box.height
6771
+ },
6772
+ anchor: input.anchor,
6773
+ paddings: input.layout.padding,
6774
+ lines: input.layout.lines,
6775
+ fontFamily: input.typography?.fontFamily ?? normalizeOutputFontFamily(input.layout.font),
6776
+ fontSize: input.typography?.fontSize ?? input.layout.font.fontSize,
6777
+ textBackend: input.layout.textBackend
6778
+ };
6779
+ }
6780
+ function buildAnchorCenteredTextAnnotation(input) {
6781
+ return buildCenteredTextAnnotation({
6782
+ ownerId: input.ownerId,
6783
+ surfaceKind: input.surfaceKind,
6784
+ ...input.surfaceIndex === void 0 ? {} : { surfaceIndex: input.surfaceIndex },
6785
+ layout: input.layout,
6786
+ ...input.typography === void 0 ? {} : { typography: input.typography },
6787
+ center: {
6788
+ x: input.anchor.x + input.anchor.width / 2,
6789
+ y: input.anchor.y + input.anchor.height / 2
6790
+ },
6791
+ anchor: input.anchor
6792
+ });
6793
+ }
6794
+ function buildCenteredTextAnnotation(input) {
6795
+ return {
6796
+ text: input.layout.text,
6797
+ ownerId: input.ownerId,
6798
+ surfaceKind: input.surfaceKind,
6799
+ ...input.surfaceIndex === void 0 ? {} : { surfaceIndex: input.surfaceIndex },
6800
+ box: {
6801
+ x: input.center.x - input.layout.box.width / 2,
6802
+ y: input.center.y - input.layout.box.height / 2,
6803
+ width: input.layout.box.width,
6804
+ height: input.layout.box.height
6805
+ },
6806
+ anchor: input.anchor ?? input.center,
6807
+ paddings: input.layout.padding,
6808
+ lines: input.layout.lines,
6809
+ fontFamily: input.typography?.fontFamily ?? normalizeOutputFontFamily(input.layout.font),
6810
+ fontSize: input.typography?.fontSize ?? input.layout.font.fontSize,
6811
+ textBackend: input.layout.textBackend
6812
+ };
6813
+ }
6814
+ function normalizeOutputFontFamily(font) {
6815
+ return font.fontFamily === "Arial" ? "Arial, sans-serif" : font.fontFamily;
6816
+ }
6817
+ function reportTextAnnotationCollisions(annotations) {
6818
+ const diagnostics = [];
6819
+ const relevantAnnotations = annotations.filter(
6820
+ (annotation) => isExternallyPlacedText(annotation.surfaceKind)
6821
+ );
6822
+ for (let annotationIndex = 0; annotationIndex < relevantAnnotations.length; annotationIndex += 1) {
6823
+ const annotation = relevantAnnotations[annotationIndex];
6824
+ if (annotation === void 0) {
6825
+ continue;
6826
+ }
6827
+ for (let otherIndex = annotationIndex + 1; otherIndex < relevantAnnotations.length; otherIndex += 1) {
6828
+ const other = relevantAnnotations[otherIndex];
6829
+ if (other === void 0) {
6830
+ continue;
6831
+ }
6832
+ if (!intersectsAabb(annotation.box, other.box)) {
6833
+ continue;
6834
+ }
6835
+ if (annotation.ownerId === other.ownerId && annotation.surfaceKind === other.surfaceKind) {
6836
+ continue;
6837
+ }
6838
+ diagnostics.push({
6839
+ severity: "warning",
6840
+ code: "constraints.overlap.unresolved",
6841
+ message: `Text surface ${annotation.surfaceKind} for ${annotation.ownerId} overlaps text surface ${other.surfaceKind} for ${other.ownerId}.`,
6842
+ path: ["textAnnotations", annotation.surfaceKind, annotation.ownerId],
6843
+ detail: compactDetail({
6844
+ textSurfaceKind: annotation.surfaceKind,
6845
+ ownerId: annotation.ownerId,
6846
+ conflictingObjectId: other.ownerId,
6847
+ conflictingObjectKind: other.surfaceKind,
6848
+ surfaceIndex: annotation.surfaceIndex,
6849
+ otherSurfaceKind: other.surfaceKind,
6850
+ otherSurfaceIndex: other.surfaceIndex,
6851
+ textBackend: annotation.textBackend
6852
+ })
6853
+ });
6854
+ }
6855
+ }
6856
+ return diagnostics;
6857
+ }
6858
+ function reportRouteTextClearance(edges, annotations) {
6859
+ const diagnostics = [];
6860
+ const relevantAnnotations = annotations.filter(isRouteClearanceText);
6861
+ for (const edge of edges) {
6862
+ const connectedTextOwners = edgeConnectedTextOwnerIds(edge);
6863
+ for (const annotation of relevantAnnotations) {
6864
+ if (annotation.ownerId === edge.id || connectedTextOwners.has(annotation.ownerId)) {
6865
+ continue;
6866
+ }
6867
+ if (!routeIntersectsTextBox(edge.points, annotation.box)) {
6868
+ continue;
6869
+ }
6870
+ diagnostics.push({
6871
+ severity: "warning",
6872
+ code: "routing.text-clearance.unresolved",
6873
+ message: `Edge ${edge.id} intersects solved text surface ${annotation.surfaceKind} for ${annotation.ownerId}.`,
6874
+ path: ["edges", edge.id],
6875
+ detail: compactDetail({
6876
+ edgeId: edge.id,
6877
+ textSurfaceKind: annotation.surfaceKind,
6878
+ conflictingObjectId: annotation.ownerId,
6879
+ surfaceIndex: annotation.surfaceIndex,
6880
+ textBackend: annotation.textBackend
6881
+ })
6882
+ });
6883
+ }
6884
+ }
6885
+ return diagnostics;
6886
+ }
6887
+ function isPreRouteTextObstacle(annotation) {
6888
+ if (annotation.surfaceKind === "edge-label") {
6889
+ return false;
6890
+ }
6891
+ return isRouteClearanceText(annotation);
6892
+ }
6893
+ function isRouteClearanceText(annotation) {
6894
+ switch (annotation.surfaceKind) {
6895
+ case "port-label":
6896
+ case "edge-label":
6897
+ case "swimlane-label":
6898
+ case "frame-title":
6899
+ return true;
6900
+ case "node-label":
6901
+ case "group-label":
6902
+ case "compartment-row":
6903
+ return textExtendsOutsideAnchor(annotation);
6904
+ }
6905
+ }
6906
+ function textExtendsOutsideAnchor(annotation) {
6907
+ if (!("width" in annotation.anchor)) {
6908
+ return true;
6909
+ }
6910
+ const epsilon = 1e-3;
6911
+ 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;
6912
+ }
6913
+ function routeIntersectsTextBox(points, box) {
6914
+ for (let index = 0; index < points.length - 1; index += 1) {
6915
+ const start = points[index];
6916
+ const end = points[index + 1];
6917
+ if (start === void 0 || end === void 0) {
6918
+ continue;
6919
+ }
6920
+ if (segmentIntersectsBox2(start, end, box)) {
6921
+ return true;
6922
+ }
6923
+ }
6924
+ return false;
6925
+ }
6926
+ function segmentIntersectsBox2(start, end, box) {
6927
+ const left = box.x;
6928
+ const right = box.x + box.width;
6929
+ const top = box.y;
6930
+ const bottom = box.y + box.height;
6931
+ if (pointInsideBox2(start, box) || pointInsideBox2(end, box)) {
6932
+ return true;
6933
+ }
6934
+ if (start.x === end.x) {
6935
+ return start.x > left && start.x < right && rangesOverlap2(start.y, end.y, top, bottom);
6936
+ }
6937
+ if (start.y === end.y) {
6938
+ return start.y > top && start.y < bottom && rangesOverlap2(start.x, end.x, left, right);
6939
+ }
6940
+ 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);
6941
+ }
6942
+ function pointInsideBox2(point2, box) {
6943
+ return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
6944
+ }
6945
+ function rangesOverlap2(a, b, min, max) {
6946
+ const low = Math.min(a, b);
6947
+ const high = Math.max(a, b);
6948
+ return high > min && low < max;
6949
+ }
6950
+ function segmentIntersectsBoxEdge2(start, end, x1, y1, x2, y2) {
6951
+ const denominator = (end.x - start.x) * (y2 - y1) - (end.y - start.y) * (x2 - x1);
6952
+ if (denominator === 0) {
6953
+ return false;
6954
+ }
6955
+ const t = ((x1 - start.x) * (y2 - y1) - (y1 - start.y) * (x2 - x1)) / denominator;
6956
+ const u = ((x1 - start.x) * (end.y - start.y) - (y1 - start.y) * (end.x - start.x)) / denominator;
6957
+ return t > 0 && t < 1 && u > 0 && u < 1;
6958
+ }
6959
+ function compactDetail(detail) {
6960
+ return Object.fromEntries(
6961
+ Object.entries(detail).filter(
6962
+ (entry) => entry[1] !== void 0
6963
+ )
6964
+ );
6965
+ }
6966
+ function isExternallyPlacedText(surfaceKind) {
6967
+ switch (surfaceKind) {
6968
+ case "port-label":
6969
+ return true;
6970
+ case "edge-label":
6971
+ return false;
6972
+ case "swimlane-label":
6973
+ return true;
6974
+ case "frame-title":
6975
+ return true;
6976
+ case "node-label":
6977
+ case "group-label":
6978
+ case "compartment-row":
6979
+ return false;
6980
+ }
6981
+ }
6982
+ function fallbackLabelLayout(text) {
6983
+ const width = Math.max(0, text.length * 7);
6984
+ return {
6985
+ text,
6986
+ box: { x: 0, y: 0, width, height: 14 },
6987
+ contentBox: { x: 0, y: 0, width, height: 14 },
6988
+ naturalSize: { width, height: 14 },
6989
+ fittedSize: { width, height: 14 },
6990
+ padding: { top: 0, right: 0, bottom: 0, left: 0 },
6991
+ font: { fontFamily: "Arial", fontSize: 12, lineHeight: 14 },
6992
+ lineHeight: 14,
6993
+ lines: [
6994
+ {
6995
+ text,
6996
+ box: { x: 0, y: 0, width, height: 14 },
6997
+ baselineY: 11.2,
6998
+ width,
6999
+ lineIndex: 0
7000
+ }
7001
+ ],
7002
+ overflow: { horizontal: false, vertical: false, truncated: false },
7003
+ diagnostics: []
7004
+ };
7005
+ }
7006
+ function edgeLabelAnchor(edge, layout2, edges, obstacleBoxes, placedLabelBoxes) {
7007
+ const placement = labelPlacementOnPolyline2(edge.points);
7008
+ if (placement === void 0) {
7009
+ return { x: 0, y: 0 };
7010
+ }
7011
+ for (const candidate of edgeLabelAnchorCandidates(
7012
+ edge.points,
7013
+ placement,
7014
+ layout2
7015
+ )) {
7016
+ const labelBox = {
7017
+ x: candidate.x - layout2.box.width / 2,
7018
+ y: candidate.y - layout2.box.height / 2,
7019
+ width: layout2.box.width,
7020
+ height: layout2.box.height
7021
+ };
7022
+ if (routeIntersectsTextBox(edge.points, labelBox)) {
7023
+ continue;
7024
+ }
7025
+ const crossesOtherRoute = edges.some(
7026
+ (other) => other.id !== edge.id && routeIntersectsTextBox(other.points, labelBox)
7027
+ );
7028
+ if (crossesOtherRoute) {
7029
+ continue;
7030
+ }
7031
+ const overlapsNode = obstacleBoxes.some(
7032
+ (box) => intersectsAabb(labelBox, box)
7033
+ );
7034
+ if (overlapsNode) {
7035
+ continue;
7036
+ }
7037
+ const overlapsPlacedLabel = placedLabelBoxes.some(
7038
+ (box) => intersectsAabb(labelBox, box)
7039
+ );
7040
+ if (!overlapsPlacedLabel) {
7041
+ return candidate;
7042
+ }
7043
+ }
7044
+ return placement;
7045
+ }
7046
+ function edgeLabelAnchorCandidates(points, placement, layout2) {
7047
+ const segment = labelSegmentOnPolyline(points);
7048
+ if (segment === void 0) {
7049
+ return [placement];
7050
+ }
7051
+ const candidates = [placement];
7052
+ if (segment.start.y === segment.end.y) {
7053
+ const needed = layout2.box.height / 2 + EDGE_LABEL_CLEARANCE;
7054
+ const maxSteps = Math.max(12, Math.ceil(needed / EDGE_LABEL_CLEARANCE));
7055
+ for (let step = 1; step <= maxSteps; step += 1) {
7056
+ const offset = EDGE_LABEL_CLEARANCE * step;
7057
+ candidates.push(
7058
+ { x: placement.x, y: placement.y - offset },
7059
+ { x: placement.x, y: placement.y + offset }
7060
+ );
7061
+ }
7062
+ return candidates;
7063
+ }
7064
+ if (segment.start.x === segment.end.x) {
7065
+ const needed = layout2.box.width / 2 + EDGE_LABEL_CLEARANCE;
7066
+ const maxSteps = Math.max(12, Math.ceil(needed / EDGE_LABEL_CLEARANCE));
7067
+ for (let step = 1; step <= maxSteps; step += 1) {
7068
+ const offset = EDGE_LABEL_CLEARANCE * step;
7069
+ candidates.push(
7070
+ { x: placement.x + offset, y: placement.y },
7071
+ { x: placement.x - offset, y: placement.y }
7072
+ );
7073
+ }
7074
+ return candidates;
7075
+ }
7076
+ return candidates;
7077
+ }
7078
+ function labelPlacementOnPolyline2(points) {
7079
+ return labelSegmentOnPolyline(points)?.placement;
7080
+ }
7081
+ function labelSegmentOnPolyline(points) {
7082
+ const segments = nonZeroSegments2(points);
7083
+ const totalLength = segments.reduce(
7084
+ (sum, segment) => sum + segment.length,
7085
+ 0
7086
+ );
7087
+ if (totalLength <= 0) {
7088
+ return void 0;
7089
+ }
7090
+ let remaining = totalLength / 2;
7091
+ for (const segment of segments) {
7092
+ if (remaining <= segment.length) {
7093
+ const ratio = remaining / segment.length;
7094
+ const x = segment.start.x + (segment.end.x - segment.start.x) * ratio;
7095
+ const y = segment.start.y + (segment.end.y - segment.start.y) * ratio;
7096
+ const offset2 = labelOffset2(segment);
7097
+ return {
7098
+ start: segment.start,
7099
+ end: segment.end,
7100
+ placement: { x: x + offset2.x, y: y + offset2.y }
7101
+ };
7102
+ }
7103
+ remaining -= segment.length;
7104
+ }
7105
+ const last = segments.at(-1);
7106
+ if (last === void 0) {
7107
+ return void 0;
7108
+ }
7109
+ const offset = labelOffset2(last);
7110
+ return {
7111
+ start: last.start,
7112
+ end: last.end,
7113
+ placement: { x: last.end.x + offset.x, y: last.end.y + offset.y }
7114
+ };
7115
+ }
7116
+ function nonZeroSegments2(points) {
7117
+ const segments = [];
7118
+ for (let index = 0; index < points.length - 1; index += 1) {
7119
+ const start = points[index];
7120
+ const end = points[index + 1];
7121
+ if (start === void 0 || end === void 0) {
7122
+ continue;
7123
+ }
7124
+ const length = Math.hypot(end.x - start.x, end.y - start.y);
7125
+ if (length > 0) {
7126
+ segments.push({ start, end, length });
7127
+ }
7128
+ }
7129
+ return segments;
7130
+ }
7131
+ function labelOffset2(segment) {
7132
+ const offset = 10;
7133
+ const dx = segment.end.x - segment.start.x;
7134
+ const dy = segment.end.y - segment.start.y;
7135
+ return {
7136
+ x: -dy / segment.length * offset,
7137
+ y: dx / segment.length * offset
7138
+ };
7139
+ }
7140
+ function compartmentRows2(node) {
7141
+ const compartments2 = node.compartments;
7142
+ if (compartments2 === void 0) {
7143
+ return [];
7144
+ }
7145
+ return [
7146
+ ...compartments2.stereotype === void 0 ? [] : [compartments2.stereotype],
7147
+ ...compartments2.name === void 0 ? [node.label?.text ?? node.id] : [compartments2.name],
7148
+ ...compartments2.properties ?? [],
7149
+ ...compartments2.constraints ?? []
7150
+ ];
7151
+ }
3267
7152
  function portGeometry(nodeGeometry, port) {
3268
7153
  if (port === void 0) {
3269
7154
  return nodeGeometry;
@@ -3279,8 +7164,26 @@ function portGeometry(nodeGeometry, port) {
3279
7164
  obstacleBox: port.box
3280
7165
  };
3281
7166
  }
3282
- function stableById(items) {
3283
- return [...items].sort((a, b) => a.id.localeCompare(b.id));
7167
+ function stableUniqueById(items, diagnostics, pathRoot, code) {
7168
+ const firstById = /* @__PURE__ */ new Map();
7169
+ for (let index = 0; index < items.length; index += 1) {
7170
+ const item = items[index];
7171
+ if (item === void 0) {
7172
+ continue;
7173
+ }
7174
+ if (firstById.has(item.id)) {
7175
+ diagnostics.push({
7176
+ severity: "error",
7177
+ code,
7178
+ message: `Duplicate ${pathRoot.slice(0, -1)} id ${item.id} was ignored; first occurrence was kept.`,
7179
+ path: [pathRoot, index, "id"],
7180
+ detail: { id: item.id, duplicateIndex: index }
7181
+ });
7182
+ continue;
7183
+ }
7184
+ firstById.set(item.id, item);
7185
+ }
7186
+ return [...firstById.values()].sort((a, b) => a.id.localeCompare(b.id));
3284
7187
  }
3285
7188
  function stableByConstraintId(items) {
3286
7189
  return [...items].sort(
@@ -3340,7 +7243,8 @@ function renderDiagramDsl(source, options = {}) {
3340
7243
  }
3341
7244
  const solved = solveDiagram(normalized.diagram, {
3342
7245
  routeKind: normalized.diagram.metadata?.routeKind === "straight" ? "straight" : "orthogonal",
3343
- ...solvePortShiftingOption(normalized.diagram.metadata?.portShifting)
7246
+ ...solvePortShiftingOption(normalized.diagram.metadata?.portShifting),
7247
+ ...options.textMeasurer === void 0 ? {} : { textMeasurer: options.textMeasurer }
3344
7248
  });
3345
7249
  const solveDiagnostics = solved.diagnostics.map(toSolveDiagnostic);
3346
7250
  if (hasErrorDiagnostics2(solveDiagnostics)) {
@@ -3528,6 +7432,6 @@ function isPointLikeRecord(value) {
3528
7432
  return isPlainObject(value) && typeof value.x === "number" && typeof value.y === "number";
3529
7433
  }
3530
7434
 
3531
- export { DEFAULT_CANONICAL_PRECISION, DEFAULT_DSL_MAX_BYTES, DeterministicTextMeasurer, LabelFitter, PretextTextMeasurer, applyLayoutConstraints, assertFiniteNonNegative, assertFinitePositive, boxCenter, canonicalize, computeArrowhead, computeContainerGeometry, computeShapeGeometry, createDefaultTextMeasurer, expandBox, exportExcalidraw, exportSvg, fitLabel, getEdgePort, installNodeCanvasRuntime, intersectsAabb, isPretextRuntimeAvailable, normalizeDiagramDsl, normalizeInsets, parseDiagramDsl, parseEdgeShorthand, renderDiagramDsl, resolveLineHeight, resolveOutputFormat, routeEdge, runDagreInitialLayout, simplifyRoute, solveDiagram, sortDslDiagnostics, stringifyCanonical, toCanvasFont, unionBoxes, validateBox, validateTextStyle };
7435
+ export { DEFAULT_CANONICAL_PRECISION, DEFAULT_DSL_MAX_BYTES, DELIVERABILITY_DIAGNOSTIC_CODES, DeterministicTextMeasurer, LabelFitter, PretextTextMeasurer, applyLayoutConstraints, assertFiniteNonNegative, assertFinitePositive, boxCenter, canonicalize, computeArrowhead, computeContainerGeometry, computeShapeGeometry, createDefaultTextMeasurer, expandBox, exportExcalidraw, exportSvg, fitLabel, getEdgePort, installNodeCanvasRuntime, intersectsAabb, isPretextRuntimeAvailable, normalizeDiagramDsl, normalizeInsets, parseDiagramDsl, parseEdgeShorthand, renderDiagramDsl, resolveLineHeight, resolveOutputFormat, routeEdge, runDagreInitialLayout, simplifyRoute, solveDiagram, solveDiagramSafe, sortDslDiagnostics, stringifyCanonical, toCanvasFont, unionBoxes, validateBox, validateTextStyle };
3532
7436
  //# sourceMappingURL=index.js.map
3533
7437
  //# sourceMappingURL=index.js.map