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