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