@crazyhappyone/auto-graph 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.cjs +2588 -484
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +2588 -484
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +2234 -130
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +93 -1
- package/dist/index.d.ts +93 -1
- package/dist/index.js +2234 -130
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.cjs
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
var commander = require('commander');
|
|
5
|
-
var dagre = require('@dagrejs/dagre');
|
|
6
5
|
var module$1 = require('module');
|
|
7
6
|
var pretext = require('@chenglou/pretext');
|
|
7
|
+
var dagre = require('@dagrejs/dagre');
|
|
8
8
|
var buffer = require('buffer');
|
|
9
9
|
var yaml = require('yaml');
|
|
10
10
|
var zod = require('zod');
|
|
@@ -147,6 +147,15 @@ function exportExcalidraw(diagram, options = {}) {
|
|
|
147
147
|
elements.push(text);
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
+
for (const matrix of diagram.matrices ?? []) {
|
|
151
|
+
elements.push(...renderMatrixBlock(matrix));
|
|
152
|
+
}
|
|
153
|
+
for (const table of diagram.tables ?? []) {
|
|
154
|
+
elements.push(...renderTableBlock(table));
|
|
155
|
+
}
|
|
156
|
+
for (const panel of diagram.evidencePanels ?? []) {
|
|
157
|
+
elements.push(...renderEvidencePanel(panel));
|
|
158
|
+
}
|
|
150
159
|
for (const edge of diagram.edges) {
|
|
151
160
|
elements.push(renderArrow(edge));
|
|
152
161
|
}
|
|
@@ -179,6 +188,83 @@ function renderNode(node, groupIds) {
|
|
|
179
188
|
groupIds
|
|
180
189
|
};
|
|
181
190
|
}
|
|
191
|
+
function renderMatrixBlock(matrix) {
|
|
192
|
+
const containerId = `matrix:${matrix.id}`;
|
|
193
|
+
const groupIds = [containerId];
|
|
194
|
+
const label = blockText([
|
|
195
|
+
matrix.id,
|
|
196
|
+
`row | ${matrix.cols.join(" | ")}`,
|
|
197
|
+
...matrix.rows.map((rowId, rowIndex) => {
|
|
198
|
+
const row = matrix.cells[rowIndex] ?? [];
|
|
199
|
+
return `${rowId}: ${matrix.cols.map((_, columnIndex) => row[columnIndex]?.text ?? "").join(" | ")}`;
|
|
200
|
+
})
|
|
201
|
+
]);
|
|
202
|
+
return [
|
|
203
|
+
{
|
|
204
|
+
...baseElement(containerId, "rectangle", matrix.box),
|
|
205
|
+
backgroundColor: matrix.style?.fill ?? "#f8fafc",
|
|
206
|
+
strokeColor: matrix.style?.stroke ?? "#374151",
|
|
207
|
+
groupIds
|
|
208
|
+
},
|
|
209
|
+
renderTextBlock(
|
|
210
|
+
`matrix-text:${matrix.id}`,
|
|
211
|
+
label,
|
|
212
|
+
matrix.box,
|
|
213
|
+
containerId,
|
|
214
|
+
groupIds
|
|
215
|
+
)
|
|
216
|
+
];
|
|
217
|
+
}
|
|
218
|
+
function renderTableBlock(table) {
|
|
219
|
+
const containerId = `table:${table.id}`;
|
|
220
|
+
const groupIds = [containerId];
|
|
221
|
+
const label = blockText([
|
|
222
|
+
table.columns.map((column) => column.label.text).join(" | "),
|
|
223
|
+
...table.rows.map(
|
|
224
|
+
(row) => table.columns.map((column) => row.cells[column.id]?.text ?? "").join(" | ")
|
|
225
|
+
)
|
|
226
|
+
]);
|
|
227
|
+
return [
|
|
228
|
+
{
|
|
229
|
+
...baseElement(containerId, "rectangle", table.box),
|
|
230
|
+
backgroundColor: table.style?.fill ?? "#f8fafc",
|
|
231
|
+
strokeColor: table.style?.stroke ?? "#374151",
|
|
232
|
+
groupIds
|
|
233
|
+
},
|
|
234
|
+
renderTextBlock(
|
|
235
|
+
`table-text:${table.id}`,
|
|
236
|
+
label,
|
|
237
|
+
table.box,
|
|
238
|
+
containerId,
|
|
239
|
+
groupIds
|
|
240
|
+
)
|
|
241
|
+
];
|
|
242
|
+
}
|
|
243
|
+
function renderEvidencePanel(panel) {
|
|
244
|
+
const containerId = `evidence-panel:${panel.id}`;
|
|
245
|
+
const groupIds = [containerId];
|
|
246
|
+
const label = blockText([
|
|
247
|
+
`${panel.kind}: ${panel.id}`,
|
|
248
|
+
...panel.items.map(
|
|
249
|
+
(item) => item.detail?.text === void 0 ? item.label.text : `${item.label.text}: ${item.detail.text}`
|
|
250
|
+
)
|
|
251
|
+
]);
|
|
252
|
+
return [
|
|
253
|
+
{
|
|
254
|
+
...baseElement(containerId, "rectangle", panel.box),
|
|
255
|
+
backgroundColor: panel.style?.fill ?? panelKindFill(panel.kind),
|
|
256
|
+
strokeColor: panel.style?.stroke ?? "#374151",
|
|
257
|
+
groupIds
|
|
258
|
+
},
|
|
259
|
+
renderTextBlock(
|
|
260
|
+
`evidence-panel-text:${panel.id}`,
|
|
261
|
+
label,
|
|
262
|
+
panel.box,
|
|
263
|
+
containerId,
|
|
264
|
+
groupIds
|
|
265
|
+
)
|
|
266
|
+
];
|
|
267
|
+
}
|
|
182
268
|
function renderArrow(edge) {
|
|
183
269
|
const first = edge.points[0];
|
|
184
270
|
if (first === void 0) {
|
|
@@ -238,6 +324,34 @@ function renderText(id, label, box, containerId, groupIds) {
|
|
|
238
324
|
versionNonce: seedFor(`${id}:nonce`)
|
|
239
325
|
};
|
|
240
326
|
}
|
|
327
|
+
function renderTextBlock(id, text, box, containerId, groupIds) {
|
|
328
|
+
const fontSize = 12;
|
|
329
|
+
return {
|
|
330
|
+
...baseElement(id, "text", {
|
|
331
|
+
x: box.x + 8,
|
|
332
|
+
y: box.y + 8,
|
|
333
|
+
width: Math.max(0, box.width - 16),
|
|
334
|
+
height: Math.max(fontSize, box.height - 16)
|
|
335
|
+
}),
|
|
336
|
+
backgroundColor: "transparent",
|
|
337
|
+
strokeColor: "#111827",
|
|
338
|
+
groupIds,
|
|
339
|
+
text,
|
|
340
|
+
fontSize,
|
|
341
|
+
fontFamily: 1,
|
|
342
|
+
textAlign: "left",
|
|
343
|
+
verticalAlign: "top",
|
|
344
|
+
baseline: fontSize,
|
|
345
|
+
containerId,
|
|
346
|
+
originalText: text,
|
|
347
|
+
lineHeight: 1.25,
|
|
348
|
+
boundElements: null,
|
|
349
|
+
link: null,
|
|
350
|
+
locked: false,
|
|
351
|
+
seed: seedFor(id),
|
|
352
|
+
versionNonce: seedFor(`${id}:nonce`)
|
|
353
|
+
};
|
|
354
|
+
}
|
|
241
355
|
function baseElement(id, type, box) {
|
|
242
356
|
return {
|
|
243
357
|
id,
|
|
@@ -315,6 +429,21 @@ function groupGroupIds(groupId) {
|
|
|
315
429
|
function groupElementIdFor(groupId) {
|
|
316
430
|
return `group:${groupId}`;
|
|
317
431
|
}
|
|
432
|
+
function blockText(lines) {
|
|
433
|
+
return lines.filter((line) => line.length > 0).join("\n");
|
|
434
|
+
}
|
|
435
|
+
function panelKindFill(kind) {
|
|
436
|
+
switch (kind) {
|
|
437
|
+
case "legend":
|
|
438
|
+
return "#ecfdf5";
|
|
439
|
+
case "rule":
|
|
440
|
+
return "#eff6ff";
|
|
441
|
+
case "note":
|
|
442
|
+
return "#fffbeb";
|
|
443
|
+
case "verification":
|
|
444
|
+
return "#fef2f2";
|
|
445
|
+
}
|
|
446
|
+
}
|
|
318
447
|
function pointsBox(points) {
|
|
319
448
|
const xs = points.map((point2) => point2.x);
|
|
320
449
|
const ys = points.map((point2) => point2.y);
|
|
@@ -352,42 +481,253 @@ var GROUP_FILL = "#f9fafb";
|
|
|
352
481
|
var STROKE = "#374151";
|
|
353
482
|
var EDGE_STROKE = "#111827";
|
|
354
483
|
var FONT_FAMILY = "Arial, sans-serif";
|
|
484
|
+
var EVIDENCE_FILL = "#f8fafc";
|
|
485
|
+
var EVIDENCE_HEADER_FILL = "#e5e7eb";
|
|
486
|
+
var EVIDENCE_TEXT_FONT_SIZE = 10;
|
|
487
|
+
var EVIDENCE_TEXT_LINE_HEIGHT = 12;
|
|
488
|
+
var EVIDENCE_PANEL_KIND_FILL = {
|
|
489
|
+
legend: "#ecfdf5",
|
|
490
|
+
rule: "#eff6ff",
|
|
491
|
+
note: "#fffbeb",
|
|
492
|
+
verification: "#fef2f2"
|
|
493
|
+
};
|
|
355
494
|
function exportSvg(diagram, options = {}) {
|
|
356
495
|
const title = options.title ?? diagram.title;
|
|
357
|
-
const
|
|
496
|
+
const annotations = diagram.textAnnotations ?? [];
|
|
497
|
+
return `${[
|
|
358
498
|
`<svg xmlns="http://www.w3.org/2000/svg" role="img" viewBox="${formatBoxViewBox(diagram.bounds)}">`,
|
|
359
499
|
...title === void 0 ? [] : [` <title>${escapeXml(title)}</title>`],
|
|
360
500
|
` <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"/>`,
|
|
361
|
-
...diagram.frame === void 0 ? [] : [indent(renderFrame(diagram.frame))],
|
|
501
|
+
...diagram.frame === void 0 ? [] : [indent(renderFrame(diagram.frame, annotations))],
|
|
362
502
|
...(diagram.swimlanes ?? []).flatMap(
|
|
363
|
-
(swimlane) => renderSwimlane(swimlane)
|
|
503
|
+
(swimlane) => renderSwimlane(swimlane, annotations)
|
|
364
504
|
),
|
|
365
505
|
...diagram.groups.map((group) => indent(renderGroup2(group))),
|
|
506
|
+
...(diagram.matrices ?? []).flatMap(
|
|
507
|
+
(matrix) => indentLines(renderMatrixBlock2(matrix))
|
|
508
|
+
),
|
|
509
|
+
...(diagram.tables ?? []).flatMap(
|
|
510
|
+
(table) => indentLines(renderTableBlock2(table))
|
|
511
|
+
),
|
|
512
|
+
...(diagram.evidencePanels ?? []).flatMap(
|
|
513
|
+
(panel) => indentLines(renderEvidencePanel2(panel))
|
|
514
|
+
),
|
|
366
515
|
...diagram.edges.flatMap((edge) => {
|
|
367
516
|
const path = renderEdgePath(edge);
|
|
368
|
-
|
|
369
|
-
return [];
|
|
370
|
-
}
|
|
371
|
-
return [indent(path), indent(renderArrowhead(edge))];
|
|
517
|
+
return path === void 0 ? [] : [indent(path), indent(renderArrowhead(edge))];
|
|
372
518
|
}),
|
|
373
519
|
...diagram.nodes.map((node) => indent(renderNode2(node))),
|
|
374
|
-
...diagram.nodes.flatMap((node) => renderCompartments(node)),
|
|
375
|
-
...diagram.nodes.flatMap((node) => renderPorts(node)),
|
|
520
|
+
...diagram.nodes.flatMap((node) => renderCompartments(node, annotations)),
|
|
521
|
+
...diagram.nodes.flatMap((node) => renderPorts(node, annotations)),
|
|
376
522
|
...diagram.groups.flatMap(
|
|
377
|
-
(group) => renderLabel(group.label, group.box, group)
|
|
523
|
+
(group) => renderLabel(group.label, group.box, group, annotations, "group-label")
|
|
378
524
|
),
|
|
379
525
|
...diagram.nodes.flatMap(
|
|
380
|
-
(node) => node.compartments === void 0 ? renderLabel(node.label, node.box, node) : []
|
|
526
|
+
(node) => node.compartments === void 0 ? renderLabel(node.label, node.box, node, annotations, "node-label") : []
|
|
381
527
|
),
|
|
382
|
-
...diagram.edges.flatMap((edge) => renderEdgeLabel(edge)),
|
|
528
|
+
...diagram.edges.flatMap((edge) => renderEdgeLabel(edge, annotations)),
|
|
383
529
|
"</svg>"
|
|
384
|
-
]
|
|
385
|
-
return `${lines.join("\n")}
|
|
530
|
+
].join("\n")}
|
|
386
531
|
`;
|
|
387
532
|
}
|
|
388
533
|
function renderGroup2(group) {
|
|
389
534
|
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"/>`;
|
|
390
535
|
}
|
|
536
|
+
function renderMatrixBlock2(matrix) {
|
|
537
|
+
const columnCount = Math.max(1, matrix.cols.length);
|
|
538
|
+
const rowCount = matrix.rows.length;
|
|
539
|
+
const rowHeaderWidth = rowCount > 0 ? Math.min(96, matrix.box.width * 0.28) : 0;
|
|
540
|
+
const dataWidth = Math.max(0, matrix.box.width - rowHeaderWidth);
|
|
541
|
+
const cellWidth = dataWidth / columnCount;
|
|
542
|
+
const rowHeight = matrix.box.height / Math.max(1, rowCount + 1);
|
|
543
|
+
const lines = [
|
|
544
|
+
`<g class="matrix-block" data-id="${escapeAttribute(matrix.id)}" data-row-count="${rowCount}" data-column-count="${matrix.cols.length}">`,
|
|
545
|
+
` <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)}"/>`
|
|
546
|
+
];
|
|
547
|
+
if (rowHeaderWidth > 0) {
|
|
548
|
+
lines.push(
|
|
549
|
+
` <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}"/>`
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
for (let columnIndex = 0; columnIndex < matrix.cols.length; columnIndex += 1) {
|
|
553
|
+
const column = matrix.cols[columnIndex];
|
|
554
|
+
if (column === void 0) {
|
|
555
|
+
continue;
|
|
556
|
+
}
|
|
557
|
+
const x = matrix.box.x + rowHeaderWidth + columnIndex * cellWidth;
|
|
558
|
+
lines.push(
|
|
559
|
+
` <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}"/>`,
|
|
560
|
+
renderEvidenceText(
|
|
561
|
+
"matrix-column-label",
|
|
562
|
+
matrix.columnLabelLayouts?.[columnIndex]?.lines ?? [column],
|
|
563
|
+
{
|
|
564
|
+
x,
|
|
565
|
+
y: matrix.box.y,
|
|
566
|
+
width: cellWidth,
|
|
567
|
+
height: rowHeight
|
|
568
|
+
}
|
|
569
|
+
)
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
for (let rowIndex = 0; rowIndex < matrix.rows.length; rowIndex += 1) {
|
|
573
|
+
const row = matrix.rows[rowIndex];
|
|
574
|
+
const cells = matrix.cells[rowIndex] ?? [];
|
|
575
|
+
if (row === void 0) {
|
|
576
|
+
continue;
|
|
577
|
+
}
|
|
578
|
+
if (rowHeaderWidth > 0) {
|
|
579
|
+
const rowHeaderBox = {
|
|
580
|
+
x: matrix.box.x,
|
|
581
|
+
y: matrix.box.y + (rowIndex + 1) * rowHeight,
|
|
582
|
+
width: rowHeaderWidth,
|
|
583
|
+
height: rowHeight
|
|
584
|
+
};
|
|
585
|
+
lines.push(
|
|
586
|
+
` <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}"/>`,
|
|
587
|
+
renderEvidenceText(
|
|
588
|
+
"matrix-row-label",
|
|
589
|
+
matrix.rowLabelLayouts?.[rowIndex]?.lines ?? [row],
|
|
590
|
+
rowHeaderBox
|
|
591
|
+
)
|
|
592
|
+
);
|
|
593
|
+
}
|
|
594
|
+
for (let columnIndex = 0; columnIndex < matrix.cols.length; columnIndex += 1) {
|
|
595
|
+
const column = matrix.cols[columnIndex];
|
|
596
|
+
if (column === void 0) {
|
|
597
|
+
continue;
|
|
598
|
+
}
|
|
599
|
+
const cell2 = cells[columnIndex] ?? { text: "" };
|
|
600
|
+
const box = {
|
|
601
|
+
x: matrix.box.x + rowHeaderWidth + columnIndex * cellWidth,
|
|
602
|
+
y: matrix.box.y + (rowIndex + 1) * rowHeight,
|
|
603
|
+
width: cellWidth,
|
|
604
|
+
height: rowHeight
|
|
605
|
+
};
|
|
606
|
+
lines.push(
|
|
607
|
+
` <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)}"/>`,
|
|
608
|
+
renderEvidenceText(
|
|
609
|
+
"matrix-cell-label",
|
|
610
|
+
matrix.cellLabelLayouts?.[rowIndex]?.[columnIndex]?.lines ?? [
|
|
611
|
+
cell2.text
|
|
612
|
+
],
|
|
613
|
+
box
|
|
614
|
+
)
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
lines.push("</g>");
|
|
619
|
+
return lines;
|
|
620
|
+
}
|
|
621
|
+
function renderTableBlock2(table) {
|
|
622
|
+
const columnCount = Math.max(1, table.columns.length);
|
|
623
|
+
const rowHeight = table.box.height / Math.max(1, table.rows.length + 1);
|
|
624
|
+
const lines = [
|
|
625
|
+
`<g class="table-block" data-id="${escapeAttribute(table.id)}" data-row-count="${table.rows.length}" data-column-count="${table.columns.length}">`,
|
|
626
|
+
` <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)}"/>`,
|
|
627
|
+
` <g class="table-header" data-column-count="${table.columns.length}">`
|
|
628
|
+
];
|
|
629
|
+
for (let columnIndex = 0; columnIndex < table.columns.length; columnIndex += 1) {
|
|
630
|
+
const column = table.columns[columnIndex];
|
|
631
|
+
if (column === void 0) {
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
const columnBox = tableCellBox(
|
|
635
|
+
table,
|
|
636
|
+
columnIndex,
|
|
637
|
+
0,
|
|
638
|
+
rowHeight,
|
|
639
|
+
columnCount
|
|
640
|
+
);
|
|
641
|
+
lines.push(
|
|
642
|
+
` <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}"/>`,
|
|
643
|
+
` ${renderEvidenceText("table-header-label", table.columnLabelLayouts?.[columnIndex]?.lines ?? [column.label.text], columnBox)}`
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
lines.push(" </g>");
|
|
647
|
+
for (let rowIndex = 0; rowIndex < table.rows.length; rowIndex += 1) {
|
|
648
|
+
const row = table.rows[rowIndex];
|
|
649
|
+
if (row === void 0) {
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
const rowBox = {
|
|
653
|
+
x: table.box.x,
|
|
654
|
+
y: table.box.y + (rowIndex + 1) * rowHeight,
|
|
655
|
+
width: table.box.width,
|
|
656
|
+
height: rowHeight
|
|
657
|
+
};
|
|
658
|
+
const rowClass = rowIndex % 2 === 0 ? "table-row-even" : "table-row-odd";
|
|
659
|
+
lines.push(
|
|
660
|
+
` <g class="table-row ${rowClass}" data-row="${escapeAttribute(row.id)}">`,
|
|
661
|
+
` <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"/>`
|
|
662
|
+
);
|
|
663
|
+
for (let columnIndex = 0; columnIndex < table.columns.length; columnIndex += 1) {
|
|
664
|
+
const column = table.columns[columnIndex];
|
|
665
|
+
if (column === void 0) {
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
const cell2 = row.cells[column.id] ?? { text: "" };
|
|
669
|
+
const cellBox = tableCellBox(
|
|
670
|
+
table,
|
|
671
|
+
columnIndex,
|
|
672
|
+
rowIndex + 1,
|
|
673
|
+
rowHeight,
|
|
674
|
+
columnCount
|
|
675
|
+
);
|
|
676
|
+
lines.push(
|
|
677
|
+
` <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)}"/>`,
|
|
678
|
+
` ${renderEvidenceText("table-cell-label", table.cellLabelLayouts?.[rowIndex]?.[columnIndex]?.lines ?? [cell2.text], cellBox)}`
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
lines.push(" </g>");
|
|
682
|
+
}
|
|
683
|
+
lines.push("</g>");
|
|
684
|
+
return lines;
|
|
685
|
+
}
|
|
686
|
+
function renderEvidencePanel2(panel) {
|
|
687
|
+
const titleWidth = Math.min(panel.box.width * 0.36, 140);
|
|
688
|
+
const itemBox = {
|
|
689
|
+
x: panel.box.x + titleWidth,
|
|
690
|
+
y: panel.box.y,
|
|
691
|
+
width: panel.box.width - titleWidth,
|
|
692
|
+
height: panel.box.height
|
|
693
|
+
};
|
|
694
|
+
const titleBox = {
|
|
695
|
+
x: panel.box.x,
|
|
696
|
+
y: panel.box.y,
|
|
697
|
+
width: titleWidth,
|
|
698
|
+
height: panel.box.height
|
|
699
|
+
};
|
|
700
|
+
const itemHeight = panel.box.height / Math.max(1, panel.items.length);
|
|
701
|
+
const lines = [
|
|
702
|
+
`<g class="evidence-panel evidence-panel--${panel.kind}" data-id="${escapeAttribute(panel.id)}" data-kind="${escapeAttribute(panel.kind)}" data-item-count="${panel.items.length}">`,
|
|
703
|
+
` <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)}"/>`,
|
|
704
|
+
` <g class="evidence-panel-title-cell">`,
|
|
705
|
+
` <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}"/>`,
|
|
706
|
+
` ${renderEvidenceText("evidence-panel-title", panel.titleLayout?.lines ?? [`${panel.kind}: ${panel.id}`], titleBox)}`,
|
|
707
|
+
" </g>",
|
|
708
|
+
` <g class="evidence-panel-items-cell">`,
|
|
709
|
+
` <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}"/>`
|
|
710
|
+
];
|
|
711
|
+
for (let index = 0; index < panel.items.length; index += 1) {
|
|
712
|
+
const item = panel.items[index];
|
|
713
|
+
if (item === void 0) {
|
|
714
|
+
continue;
|
|
715
|
+
}
|
|
716
|
+
const text = panelItemText(item.label.text, item.detail?.text);
|
|
717
|
+
const box = {
|
|
718
|
+
x: itemBox.x,
|
|
719
|
+
y: itemBox.y + index * itemHeight,
|
|
720
|
+
width: itemBox.width,
|
|
721
|
+
height: itemHeight
|
|
722
|
+
};
|
|
723
|
+
lines.push(
|
|
724
|
+
` <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")}"/>`,
|
|
725
|
+
` ${renderEvidenceText("evidence-panel-item-label", panel.itemLayouts?.[index]?.lines ?? [text], box)}`
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
lines.push(" </g>", "</g>");
|
|
729
|
+
return lines;
|
|
730
|
+
}
|
|
391
731
|
function renderNode2(node) {
|
|
392
732
|
const fill = node.style?.fill ?? NODE_FILL;
|
|
393
733
|
const stroke = node.style?.stroke ?? STROKE;
|
|
@@ -407,18 +747,27 @@ function renderNode2(node) {
|
|
|
407
747
|
return `<path ${common} d="${formatCylinderPath(node.box)}"/>`;
|
|
408
748
|
}
|
|
409
749
|
}
|
|
410
|
-
function renderFrame(frame) {
|
|
750
|
+
function renderFrame(frame, annotations) {
|
|
411
751
|
const stroke = frame.style?.stroke ?? "#6b7280";
|
|
412
752
|
const fill = frame.style?.fill ?? "transparent";
|
|
413
753
|
return [
|
|
414
754
|
`<g class="sysml-frame" data-kind="${escapeAttribute(frame.kind)}">`,
|
|
415
755
|
` <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)}"/>`,
|
|
416
756
|
` <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)}"/>`,
|
|
417
|
-
|
|
757
|
+
...renderSolvedTextAnnotation(
|
|
758
|
+
findAnnotation(annotations, "frame-title", frame.kind),
|
|
759
|
+
`sysml-title-tab-label`,
|
|
760
|
+
{
|
|
761
|
+
indent: " ",
|
|
762
|
+
mode: "center",
|
|
763
|
+
fallbackText: frame.titleTab}
|
|
764
|
+
) ?? [
|
|
765
|
+
` <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>`
|
|
766
|
+
],
|
|
418
767
|
"</g>"
|
|
419
768
|
].join("\n");
|
|
420
769
|
}
|
|
421
|
-
function renderSwimlane(swimlane) {
|
|
770
|
+
function renderSwimlane(swimlane, annotations) {
|
|
422
771
|
if (swimlane.box === void 0) {
|
|
423
772
|
return [];
|
|
424
773
|
}
|
|
@@ -444,28 +793,49 @@ function renderSwimlane(swimlane) {
|
|
|
444
793
|
);
|
|
445
794
|
}
|
|
446
795
|
if (lane.label?.text !== void 0) {
|
|
447
|
-
const
|
|
448
|
-
|
|
796
|
+
const annotation = findAnnotation(
|
|
797
|
+
annotations,
|
|
798
|
+
"swimlane-label",
|
|
799
|
+
`${swimlane.id}.${lane.id}`
|
|
800
|
+
);
|
|
801
|
+
lines.push(
|
|
802
|
+
...annotation === void 0 ? [
|
|
803
|
+
renderSwimlaneLabel(
|
|
804
|
+
swimlane,
|
|
805
|
+
lane.label.text,
|
|
806
|
+
lane.headerBox ?? lane.box
|
|
807
|
+
)
|
|
808
|
+
] : renderSolvedTextAnnotation(annotation, "swimlane-label", {
|
|
809
|
+
indent: " ",
|
|
810
|
+
mode: "center",
|
|
811
|
+
rotate: swimlane.orientation === "horizontal"
|
|
812
|
+
}) ?? []
|
|
813
|
+
);
|
|
449
814
|
}
|
|
450
815
|
}
|
|
451
816
|
lines.push(" </g>");
|
|
452
817
|
return lines;
|
|
453
818
|
}
|
|
454
|
-
function
|
|
455
|
-
const x = labelBox.x + labelBox.width / 2;
|
|
456
|
-
const y = labelBox.y + labelBox.height / 2;
|
|
457
|
-
const transform = swimlane.orientation === "horizontal" ? ` transform="rotate(-90 ${formatNumber(x)} ${formatNumber(y)})"` : "";
|
|
458
|
-
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>`;
|
|
459
|
-
}
|
|
460
|
-
function renderPorts(node) {
|
|
819
|
+
function renderPorts(node, annotations) {
|
|
461
820
|
return (node.ports ?? []).flatMap((port) => [
|
|
462
821
|
` <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)}"/>`,
|
|
463
|
-
...port.label?.text === void 0 ? [] :
|
|
464
|
-
|
|
465
|
-
|
|
822
|
+
...port.label?.text === void 0 ? [] : (() => {
|
|
823
|
+
const annotation = findAnnotation(
|
|
824
|
+
annotations,
|
|
825
|
+
"port-label",
|
|
826
|
+
`${node.id}.${port.id}`
|
|
827
|
+
);
|
|
828
|
+
return annotation === void 0 ? [
|
|
829
|
+
` <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>`
|
|
830
|
+
] : renderSolvedTextAnnotation(annotation, "port-label", {
|
|
831
|
+
indent: " ",
|
|
832
|
+
mode: "center",
|
|
833
|
+
textAnchor: port.side === "left" ? "end" : "start"
|
|
834
|
+
}) ?? [];
|
|
835
|
+
})()
|
|
466
836
|
]);
|
|
467
837
|
}
|
|
468
|
-
function renderCompartments(node) {
|
|
838
|
+
function renderCompartments(node, annotations) {
|
|
469
839
|
const compartments2 = node.compartments;
|
|
470
840
|
if (compartments2 === void 0) {
|
|
471
841
|
return [];
|
|
@@ -485,7 +855,6 @@ function renderCompartments(node) {
|
|
|
485
855
|
text
|
|
486
856
|
}))
|
|
487
857
|
];
|
|
488
|
-
const lineHeight = 16;
|
|
489
858
|
const lines = [
|
|
490
859
|
` <g class="compartment" data-for="${escapeAttribute(node.id)}">`
|
|
491
860
|
];
|
|
@@ -494,32 +863,72 @@ function renderCompartments(node) {
|
|
|
494
863
|
if (row === void 0) {
|
|
495
864
|
continue;
|
|
496
865
|
}
|
|
497
|
-
const y = node.box.y + 18 + index *
|
|
866
|
+
const y = node.box.y + 18 + index * 16;
|
|
498
867
|
if (index > 1) {
|
|
499
868
|
lines.push(
|
|
500
869
|
` <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}"/>`
|
|
501
870
|
);
|
|
502
871
|
}
|
|
872
|
+
const annotation = findAnnotation(
|
|
873
|
+
annotations,
|
|
874
|
+
"compartment-row",
|
|
875
|
+
node.id,
|
|
876
|
+
index
|
|
877
|
+
);
|
|
503
878
|
lines.push(
|
|
504
|
-
|
|
879
|
+
...annotation === void 0 ? [
|
|
880
|
+
` <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>`
|
|
881
|
+
] : renderSolvedTextAnnotation(
|
|
882
|
+
annotation,
|
|
883
|
+
`compartment-${row.className}`,
|
|
884
|
+
{
|
|
885
|
+
indent: " ",
|
|
886
|
+
mode: "center"
|
|
887
|
+
}
|
|
888
|
+
) ?? []
|
|
505
889
|
);
|
|
506
890
|
}
|
|
507
891
|
lines.push(" </g>");
|
|
508
892
|
return lines;
|
|
509
893
|
}
|
|
510
|
-
function
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
894
|
+
function tableCellBox(table, columnIndex, rowIndex, rowHeight, columnCount) {
|
|
895
|
+
const x = table.columnXOffsets[columnIndex] ?? table.box.x + table.box.width / columnCount * columnIndex;
|
|
896
|
+
const nextX = table.columnXOffsets[columnIndex + 1] ?? table.box.x + table.box.width;
|
|
897
|
+
return {
|
|
898
|
+
x,
|
|
899
|
+
y: table.box.y + rowIndex * rowHeight,
|
|
900
|
+
width: nextX - x,
|
|
901
|
+
height: rowHeight
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
function renderEvidenceText(className, lines, box) {
|
|
905
|
+
const fontSize = EVIDENCE_TEXT_FONT_SIZE;
|
|
906
|
+
const lineHeight = EVIDENCE_TEXT_LINE_HEIGHT;
|
|
907
|
+
const x = box.x + box.width / 2;
|
|
908
|
+
if (lines.length <= 1) {
|
|
909
|
+
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>`;
|
|
516
910
|
}
|
|
517
|
-
|
|
911
|
+
const totalHeight = (lines.length - 1) * lineHeight;
|
|
912
|
+
const firstBaselineY = box.y + box.height / 2 - totalHeight / 2;
|
|
913
|
+
return [
|
|
914
|
+
`<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">`,
|
|
915
|
+
...lines.map(
|
|
916
|
+
(line, index) => ` <tspan x="${formatNumber(x)}" y="${formatNumber(firstBaselineY + index * lineHeight)}">${escapeXml(line)}</tspan>`
|
|
917
|
+
),
|
|
918
|
+
"</text>"
|
|
919
|
+
].join("\n");
|
|
518
920
|
}
|
|
519
|
-
function
|
|
520
|
-
return
|
|
921
|
+
function panelItemText(label, detail) {
|
|
922
|
+
return detail === void 0 ? label : `${label}: ${detail}`;
|
|
521
923
|
}
|
|
522
|
-
function renderLabel(label, box, item) {
|
|
924
|
+
function renderLabel(label, box, item, annotations, surfaceKind) {
|
|
925
|
+
const annotation = findAnnotation(annotations, surfaceKind, item.id);
|
|
926
|
+
if (annotation !== void 0) {
|
|
927
|
+
return renderSolvedTextAnnotation(annotation, "label", {
|
|
928
|
+
indent: " ",
|
|
929
|
+
mode: "center"
|
|
930
|
+
}) ?? [];
|
|
931
|
+
}
|
|
523
932
|
const labelLayout = item.labelLayout;
|
|
524
933
|
if (labelLayout?.lines !== void 0 && labelLayout.lines.length > 0) {
|
|
525
934
|
const offset = { x: box.x, y: box.y };
|
|
@@ -545,10 +954,17 @@ function renderEdgePath(edge) {
|
|
|
545
954
|
const dash = edge.style === "dashed" ? ' stroke-dasharray="6 4"' : "";
|
|
546
955
|
return `<path class="edge" data-id="${escapeAttribute(edge.id)}" d="${formatPath(pathPointsBeforeArrowhead(edge.points))}" fill="none" stroke="${EDGE_STROKE}" stroke-width="1.5"${dash}/>`;
|
|
547
956
|
}
|
|
548
|
-
function renderEdgeLabel(edge) {
|
|
957
|
+
function renderEdgeLabel(edge, annotations) {
|
|
549
958
|
if (edge.label?.text === void 0 || edge.points.length < 2) {
|
|
550
959
|
return [];
|
|
551
960
|
}
|
|
961
|
+
const annotation = findAnnotation(annotations, "edge-label", edge.id);
|
|
962
|
+
if (annotation !== void 0) {
|
|
963
|
+
return renderSolvedTextAnnotation(annotation, "edge-label", {
|
|
964
|
+
indent: " ",
|
|
965
|
+
mode: "center"
|
|
966
|
+
}) ?? [];
|
|
967
|
+
}
|
|
552
968
|
const placement = labelPlacementOnPolyline(edge.points);
|
|
553
969
|
if (placement === void 0) {
|
|
554
970
|
return [];
|
|
@@ -562,6 +978,85 @@ function renderArrowhead(edge) {
|
|
|
562
978
|
const fill = edge.arrowhead === "hollowTriangle" ? "none" : EDGE_STROKE;
|
|
563
979
|
return `<polygon class="edge-arrowhead" data-edge="${escapeAttribute(edge.id)}" points="${formatPoints([arrowhead.tip, arrowhead.left, arrowhead.right])}" fill="${fill}" stroke="${EDGE_STROKE}"/>`;
|
|
564
980
|
}
|
|
981
|
+
function renderSolvedTextAnnotation(annotation, className, options) {
|
|
982
|
+
if (annotation === void 0) {
|
|
983
|
+
return void 0;
|
|
984
|
+
}
|
|
985
|
+
const x = options.mode === "center" ? annotation.box.x + annotation.box.width / 2 : annotation.box.x;
|
|
986
|
+
const y = options.mode === "center" ? annotation.box.y + annotation.box.height / 2 : annotation.box.y + annotation.box.height;
|
|
987
|
+
const rotate = options.rotate ? ` transform="rotate(-90 ${formatNumber(x)} ${formatNumber(y)})"` : "";
|
|
988
|
+
const attrs = [
|
|
989
|
+
`class="${className}"`,
|
|
990
|
+
`data-for="${escapeAttribute(annotation.ownerId)}"`,
|
|
991
|
+
`data-text-surface="${escapeAttribute(annotation.surfaceKind)}"`,
|
|
992
|
+
`data-owner-id="${escapeAttribute(annotation.ownerId)}"`,
|
|
993
|
+
`data-text-backend="${escapeAttribute(annotation.textBackend ?? "deterministic")}"`,
|
|
994
|
+
`font-family="${FONT_FAMILY}"`,
|
|
995
|
+
`font-size="${formatNumber(annotation.fontSize)}"`,
|
|
996
|
+
`fill="#111827"`
|
|
997
|
+
];
|
|
998
|
+
if (options.mode === "center") {
|
|
999
|
+
attrs.push('text-anchor="middle"');
|
|
1000
|
+
} else {
|
|
1001
|
+
attrs.push(`text-anchor="${options.textAnchor ?? "start"}"`);
|
|
1002
|
+
}
|
|
1003
|
+
if (annotation.lines.length > 1) {
|
|
1004
|
+
return [
|
|
1005
|
+
`${options.indent}<text ${attrs.join(" ")}${rotate}>`,
|
|
1006
|
+
...annotation.lines.map(
|
|
1007
|
+
(line2) => `${options.indent} <tspan x="${formatNumber(textLineX(annotation, line2, options))}" y="${formatNumber(annotation.box.y + line2.baselineY)}">${escapeXml(line2.text)}</tspan>`
|
|
1008
|
+
),
|
|
1009
|
+
`${options.indent}</text>`
|
|
1010
|
+
];
|
|
1011
|
+
}
|
|
1012
|
+
const line = annotation.lines[0];
|
|
1013
|
+
const text = line?.text ?? options.fallbackText ?? annotation.text;
|
|
1014
|
+
const singleLineAttrs = options.mode === "center" ? [...attrs, 'dominant-baseline="middle"'] : attrs;
|
|
1015
|
+
return [
|
|
1016
|
+
`${options.indent}<text ${singleLineAttrs.join(" ")} x="${formatNumber(x)}" y="${formatNumber(y)}"${rotate}>${escapeXml(text)}</text>`
|
|
1017
|
+
];
|
|
1018
|
+
}
|
|
1019
|
+
function textLineX(annotation, line, options) {
|
|
1020
|
+
if (options.mode === "center") {
|
|
1021
|
+
return annotation.box.x + line.box.x + line.box.width / 2;
|
|
1022
|
+
}
|
|
1023
|
+
if ((options.textAnchor ?? "start") === "end") {
|
|
1024
|
+
return annotation.box.x + line.box.x + line.box.width;
|
|
1025
|
+
}
|
|
1026
|
+
return annotation.box.x + line.box.x;
|
|
1027
|
+
}
|
|
1028
|
+
function findAnnotation(annotations, surfaceKind, ownerId, index) {
|
|
1029
|
+
return annotations.find((annotation) => {
|
|
1030
|
+
if (annotation.surfaceKind !== surfaceKind) {
|
|
1031
|
+
return false;
|
|
1032
|
+
}
|
|
1033
|
+
if (annotation.ownerId !== ownerId) {
|
|
1034
|
+
return false;
|
|
1035
|
+
}
|
|
1036
|
+
if (index === void 0) {
|
|
1037
|
+
return annotation.surfaceIndex === void 0;
|
|
1038
|
+
}
|
|
1039
|
+
return annotation.surfaceIndex === index;
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
1042
|
+
function renderSwimlaneLabel(swimlane, text, labelBox) {
|
|
1043
|
+
const x = labelBox.x + labelBox.width / 2;
|
|
1044
|
+
const y = labelBox.y + labelBox.height / 2;
|
|
1045
|
+
const transform = swimlane.orientation === "horizontal" ? ` transform="rotate(-90 ${formatNumber(x)} ${formatNumber(y)})"` : "";
|
|
1046
|
+
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>`;
|
|
1047
|
+
}
|
|
1048
|
+
function renderRect(box, attributes) {
|
|
1049
|
+
return `<rect ${attributes} x="${formatNumber(box.x)}" y="${formatNumber(box.y)}" width="${formatNumber(box.width)}" height="${formatNumber(box.height)}"/>`;
|
|
1050
|
+
}
|
|
1051
|
+
function portLabelX(x, side) {
|
|
1052
|
+
if (side === "left") {
|
|
1053
|
+
return x - 8;
|
|
1054
|
+
}
|
|
1055
|
+
if (side === "right") {
|
|
1056
|
+
return x + 8;
|
|
1057
|
+
}
|
|
1058
|
+
return x + 8;
|
|
1059
|
+
}
|
|
565
1060
|
function labelPlacementOnPolyline(points) {
|
|
566
1061
|
const segments = nonZeroSegments(points);
|
|
567
1062
|
const totalLength = segments.reduce(
|
|
@@ -644,17 +1139,15 @@ function shapePoints(shape, box) {
|
|
|
644
1139
|
{ x: right - skew, y: bottom },
|
|
645
1140
|
{ x: left, y: bottom }
|
|
646
1141
|
];
|
|
647
|
-
case "hexagon":
|
|
648
|
-
const inset = Math.min(box.width * 0.2, 24);
|
|
1142
|
+
case "hexagon":
|
|
649
1143
|
return [
|
|
650
|
-
{ x: left +
|
|
651
|
-
{ x: right -
|
|
1144
|
+
{ x: left + skew, y: top },
|
|
1145
|
+
{ x: right - skew, y: top },
|
|
652
1146
|
{ x: right, y: midY },
|
|
653
|
-
{ x: right -
|
|
654
|
-
{ x: left +
|
|
1147
|
+
{ x: right - skew, y: bottom },
|
|
1148
|
+
{ x: left + skew, y: bottom },
|
|
655
1149
|
{ x: left, y: midY }
|
|
656
1150
|
];
|
|
657
|
-
}
|
|
658
1151
|
}
|
|
659
1152
|
}
|
|
660
1153
|
function formatCylinderPath(box) {
|
|
@@ -679,10 +1172,9 @@ function formatCylinderPath(box) {
|
|
|
679
1172
|
].join(" ");
|
|
680
1173
|
}
|
|
681
1174
|
function formatPath(points) {
|
|
682
|
-
return points.map(
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
}).join(" ");
|
|
1175
|
+
return points.map(
|
|
1176
|
+
(point2, index) => `${index === 0 ? "M" : "L"} ${formatNumber(point2.x)} ${formatNumber(point2.y)}`
|
|
1177
|
+
).join(" ");
|
|
686
1178
|
}
|
|
687
1179
|
function formatPoints(points) {
|
|
688
1180
|
return points.map((point2) => `${formatNumber(point2.x)},${formatNumber(point2.y)}`).join(" ");
|
|
@@ -705,6 +1197,9 @@ function escapeAttribute(value) {
|
|
|
705
1197
|
function indent(value) {
|
|
706
1198
|
return ` ${value}`;
|
|
707
1199
|
}
|
|
1200
|
+
function indentLines(values) {
|
|
1201
|
+
return values.map(indent);
|
|
1202
|
+
}
|
|
708
1203
|
|
|
709
1204
|
// src/geometry/boxes.ts
|
|
710
1205
|
function normalizeInsets(input = 0) {
|
|
@@ -1018,27 +1513,31 @@ function applyDistribute(constraints, boxes, locks, diagnostics) {
|
|
|
1018
1513
|
function repairOverlaps(input, boxes, locks, diagnostics) {
|
|
1019
1514
|
const spacing = input.overlapSpacing ?? 40;
|
|
1020
1515
|
const axis = input.direction === "LR" || input.direction === "RL" ? "x" : "y";
|
|
1516
|
+
const secondaryAxis = axis === "x" ? "y" : "x";
|
|
1021
1517
|
const ids = [...boxes.keys()].sort();
|
|
1022
|
-
for (
|
|
1023
|
-
for (const
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1518
|
+
for (let pass = 0; pass < 2; pass += 1) {
|
|
1519
|
+
for (const firstId of ids) {
|
|
1520
|
+
for (const secondId of ids) {
|
|
1521
|
+
if (firstId >= secondId) {
|
|
1522
|
+
continue;
|
|
1523
|
+
}
|
|
1524
|
+
const first = boxes.get(firstId);
|
|
1525
|
+
const second = boxes.get(secondId);
|
|
1526
|
+
if (first === void 0 || second === void 0 || !intersectsAabb(first, second)) {
|
|
1527
|
+
continue;
|
|
1528
|
+
}
|
|
1529
|
+
const firstLocked = locks.has(firstId);
|
|
1530
|
+
const secondLocked = locks.has(secondId);
|
|
1531
|
+
if (firstLocked && secondLocked) {
|
|
1532
|
+
continue;
|
|
1533
|
+
}
|
|
1534
|
+
const movingId = firstLocked ? secondId : secondLocked ? firstId : secondId;
|
|
1535
|
+
const moving = movingId === firstId ? first : second;
|
|
1536
|
+
const fixed = movingId === firstId ? second : first;
|
|
1537
|
+
const repairAxis = firstLocked === secondLocked && pass === 0 ? secondaryAxis : axis;
|
|
1538
|
+
const moved = movePastOverlap(moving, fixed, repairAxis, spacing);
|
|
1539
|
+
boxes.set(movingId, moved);
|
|
1036
1540
|
}
|
|
1037
|
-
const movingId = firstLocked ? secondId : firstId;
|
|
1038
|
-
const moving = firstLocked ? second : first;
|
|
1039
|
-
const fixed = firstLocked ? first : second;
|
|
1040
|
-
const moved = movePastOverlap(moving, fixed, axis, spacing);
|
|
1041
|
-
boxes.set(movingId, moved);
|
|
1042
1541
|
}
|
|
1043
1542
|
}
|
|
1044
1543
|
for (const firstId of ids) {
|
|
@@ -1403,42 +1902,392 @@ function validateSize(value, label) {
|
|
|
1403
1902
|
throw new TypeError(`${label} must be finite and non-negative`);
|
|
1404
1903
|
}
|
|
1405
1904
|
}
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1905
|
+
|
|
1906
|
+
// src/text/types.ts
|
|
1907
|
+
function assertFinitePositive(value, label) {
|
|
1908
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
1909
|
+
throw new TypeError(`${label} must be finite and positive`);
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
function assertFiniteNonNegative(value, label) {
|
|
1913
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
1914
|
+
throw new TypeError(`${label} must be a finite non-negative width`);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
function validateTextStyle(style2) {
|
|
1918
|
+
assertFinitePositive(style2.fontSize, "fontSize");
|
|
1919
|
+
if (style2.lineHeight !== void 0) {
|
|
1920
|
+
assertFinitePositive(style2.lineHeight, "lineHeight");
|
|
1921
|
+
}
|
|
1922
|
+
if (style2.letterSpacing !== void 0 && !Number.isFinite(style2.letterSpacing)) {
|
|
1923
|
+
throw new TypeError("letterSpacing must be finite");
|
|
1924
|
+
}
|
|
1925
|
+
}
|
|
1926
|
+
function resolveLineHeight(style2) {
|
|
1927
|
+
validateTextStyle(style2);
|
|
1928
|
+
return style2.lineHeight ?? style2.fontSize * 1.2;
|
|
1929
|
+
}
|
|
1930
|
+
function toCanvasFont(style2) {
|
|
1931
|
+
validateTextStyle(style2);
|
|
1932
|
+
const fontStyle = style2.fontStyle === "italic" ? "italic " : "";
|
|
1933
|
+
const fontWeight = style2.fontWeight ?? 400;
|
|
1934
|
+
return `${fontStyle}${fontWeight} ${style2.fontSize}px ${style2.fontFamily}`;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
// src/text/fallback.ts
|
|
1938
|
+
var DeterministicTextMeasurer = class {
|
|
1939
|
+
prepare(text, style2) {
|
|
1940
|
+
validateTextStyle(style2);
|
|
1941
|
+
return {
|
|
1942
|
+
text,
|
|
1943
|
+
font: toCanvasFont(style2),
|
|
1944
|
+
style: { ...style2 },
|
|
1945
|
+
backend: "deterministic"
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
layout(prepared, maxWidth, lineHeight = resolveLineHeight(prepared.style)) {
|
|
1949
|
+
assertFiniteNonNegative(maxWidth, "maxWidth");
|
|
1950
|
+
assertFinitePositiveLineHeight(lineHeight);
|
|
1951
|
+
const lines = this.wrap(prepared, maxWidth);
|
|
1952
|
+
const width = lines.reduce(
|
|
1953
|
+
(current, line) => Math.max(current, line.width),
|
|
1954
|
+
0
|
|
1955
|
+
);
|
|
1956
|
+
return {
|
|
1957
|
+
width,
|
|
1958
|
+
height: lines.length * lineHeight,
|
|
1959
|
+
lineHeight,
|
|
1960
|
+
lineCount: lines.length,
|
|
1961
|
+
lines,
|
|
1962
|
+
diagnostics: []
|
|
1963
|
+
};
|
|
1964
|
+
}
|
|
1965
|
+
naturalWidth(prepared) {
|
|
1966
|
+
const charWidth = getCharacterWidth(prepared.style);
|
|
1967
|
+
return prepared.text.split("\n").reduce((width, line) => {
|
|
1968
|
+
return Math.max(width, line.length * charWidth);
|
|
1969
|
+
}, 0);
|
|
1970
|
+
}
|
|
1971
|
+
wrap(prepared, maxWidth) {
|
|
1972
|
+
const charWidth = getCharacterWidth(prepared.style);
|
|
1973
|
+
const sourceLines = prepared.text.split("\n");
|
|
1974
|
+
const output = [];
|
|
1975
|
+
let segmentIndex = 0;
|
|
1976
|
+
for (const sourceLine of sourceLines) {
|
|
1977
|
+
if (sourceLine.length === 0) {
|
|
1978
|
+
output.push(createLine("", 0, segmentIndex, 0, 0));
|
|
1979
|
+
segmentIndex += 1;
|
|
1980
|
+
continue;
|
|
1981
|
+
}
|
|
1982
|
+
const maxChars = maxWidth <= 0 ? 1 : Math.max(1, Math.floor(maxWidth / charWidth));
|
|
1983
|
+
for (let start = 0; start < sourceLine.length; start += maxChars) {
|
|
1984
|
+
const text = sourceLine.slice(start, start + maxChars);
|
|
1985
|
+
output.push(
|
|
1986
|
+
createLine(
|
|
1987
|
+
text,
|
|
1988
|
+
text.length * charWidth,
|
|
1989
|
+
segmentIndex,
|
|
1990
|
+
start,
|
|
1991
|
+
start + text.length
|
|
1992
|
+
)
|
|
1993
|
+
);
|
|
1994
|
+
}
|
|
1995
|
+
segmentIndex += 1;
|
|
1996
|
+
}
|
|
1997
|
+
if (output.length === 0) {
|
|
1998
|
+
output.push(createLine("", 0, 0, 0, 0));
|
|
1999
|
+
}
|
|
2000
|
+
return output;
|
|
2001
|
+
}
|
|
2002
|
+
};
|
|
2003
|
+
function getCharacterWidth(style2) {
|
|
2004
|
+
const letterSpacing = style2.letterSpacing ?? 0;
|
|
2005
|
+
return Math.max(0, style2.fontSize * 0.6 + letterSpacing);
|
|
2006
|
+
}
|
|
2007
|
+
function createLine(text, width, segmentIndex, start, end) {
|
|
2008
|
+
return {
|
|
2009
|
+
text,
|
|
2010
|
+
width,
|
|
2011
|
+
start: {
|
|
2012
|
+
segmentIndex,
|
|
2013
|
+
graphemeIndex: start
|
|
2014
|
+
},
|
|
2015
|
+
end: {
|
|
2016
|
+
segmentIndex,
|
|
2017
|
+
graphemeIndex: end
|
|
2018
|
+
}
|
|
2019
|
+
};
|
|
2020
|
+
}
|
|
2021
|
+
function assertFinitePositiveLineHeight(lineHeight) {
|
|
2022
|
+
if (!Number.isFinite(lineHeight) || lineHeight <= 0) {
|
|
2023
|
+
throw new TypeError("lineHeight must be finite and positive");
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
var require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
2027
|
+
function installNodeCanvasRuntime(loadNodeCanvasModule = loadDefaultNodeCanvasModule) {
|
|
2028
|
+
if (typeof globalThis.OffscreenCanvas === "function") {
|
|
2029
|
+
return true;
|
|
2030
|
+
}
|
|
2031
|
+
try {
|
|
2032
|
+
const canvasModule = loadNodeCanvasModule();
|
|
2033
|
+
const { createCanvas } = canvasModule;
|
|
2034
|
+
const NodeOffscreenCanvas = class {
|
|
2035
|
+
canvas;
|
|
2036
|
+
constructor(width, height) {
|
|
2037
|
+
this.canvas = createCanvas(width, height);
|
|
2038
|
+
}
|
|
2039
|
+
getContext(contextId) {
|
|
2040
|
+
return contextId === "2d" ? this.canvas.getContext("2d") : null;
|
|
2041
|
+
}
|
|
2042
|
+
};
|
|
2043
|
+
globalThis.OffscreenCanvas = NodeOffscreenCanvas;
|
|
2044
|
+
return true;
|
|
2045
|
+
} catch {
|
|
2046
|
+
return false;
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
function loadDefaultNodeCanvasModule() {
|
|
2050
|
+
return require2("@napi-rs/canvas");
|
|
2051
|
+
}
|
|
2052
|
+
var RUNTIME_UNAVAILABLE = "text.pretext.runtime-unavailable";
|
|
2053
|
+
function isPretextRuntimeAvailable() {
|
|
2054
|
+
return typeof Intl.Segmenter === "function" && typeof globalThis.OffscreenCanvas === "function";
|
|
2055
|
+
}
|
|
2056
|
+
var PretextTextMeasurer = class {
|
|
2057
|
+
prepare(text, style2) {
|
|
2058
|
+
if (!isPretextRuntimeAvailable()) {
|
|
2059
|
+
throw new TypeError(RUNTIME_UNAVAILABLE);
|
|
2060
|
+
}
|
|
2061
|
+
validateTextStyle(style2);
|
|
2062
|
+
const font = toCanvasFont(style2);
|
|
2063
|
+
const options = {
|
|
2064
|
+
...style2.whiteSpace === void 0 ? {} : { whiteSpace: style2.whiteSpace },
|
|
2065
|
+
...style2.wordBreak === void 0 ? {} : { wordBreak: style2.wordBreak },
|
|
2066
|
+
...style2.letterSpacing === void 0 ? {} : { letterSpacing: style2.letterSpacing }
|
|
2067
|
+
};
|
|
2068
|
+
const prepared = pretext.prepareWithSegments(text, font, options);
|
|
2069
|
+
return {
|
|
2070
|
+
text,
|
|
2071
|
+
font,
|
|
2072
|
+
style: { ...style2 },
|
|
2073
|
+
backend: "pretext",
|
|
2074
|
+
pretextPrepared: prepared
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
layout(prepared, maxWidth, lineHeight = resolveLineHeight(prepared.style)) {
|
|
2078
|
+
assertFiniteNonNegative(maxWidth, "maxWidth");
|
|
2079
|
+
if (!Number.isFinite(lineHeight) || lineHeight <= 0) {
|
|
2080
|
+
throw new TypeError("lineHeight must be finite and positive");
|
|
2081
|
+
}
|
|
2082
|
+
const result = pretext.layoutWithLines(
|
|
2083
|
+
toInternalPrepared(prepared),
|
|
2084
|
+
maxWidth,
|
|
2085
|
+
lineHeight
|
|
2086
|
+
);
|
|
2087
|
+
const width = result.lines.reduce(
|
|
2088
|
+
(current, line) => Math.max(current, line.width),
|
|
2089
|
+
0
|
|
2090
|
+
);
|
|
2091
|
+
return {
|
|
2092
|
+
width,
|
|
2093
|
+
height: result.height,
|
|
2094
|
+
lineHeight,
|
|
2095
|
+
lineCount: result.lineCount,
|
|
2096
|
+
lines: result.lines.map((line) => ({
|
|
2097
|
+
text: line.text,
|
|
2098
|
+
width: line.width,
|
|
2099
|
+
start: {
|
|
2100
|
+
segmentIndex: line.start.segmentIndex,
|
|
2101
|
+
graphemeIndex: line.start.graphemeIndex
|
|
2102
|
+
},
|
|
2103
|
+
end: {
|
|
2104
|
+
segmentIndex: line.end.segmentIndex,
|
|
2105
|
+
graphemeIndex: line.end.graphemeIndex
|
|
2106
|
+
}
|
|
2107
|
+
})),
|
|
2108
|
+
diagnostics: []
|
|
2109
|
+
};
|
|
2110
|
+
}
|
|
2111
|
+
naturalWidth(prepared) {
|
|
2112
|
+
return pretext.measureNaturalWidth(toInternalPrepared(prepared));
|
|
2113
|
+
}
|
|
2114
|
+
};
|
|
2115
|
+
function toInternalPrepared(prepared) {
|
|
2116
|
+
if (prepared.backend !== "pretext" || !("pretextPrepared" in prepared)) {
|
|
2117
|
+
throw new TypeError("prepared text was not created by PretextTextMeasurer");
|
|
2118
|
+
}
|
|
2119
|
+
return prepared.pretextPrepared;
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
// src/text/default.ts
|
|
2123
|
+
function createDefaultTextMeasurer(options = {}) {
|
|
2124
|
+
const installRuntime = options.installNodeCanvasRuntime ?? installNodeCanvasRuntime;
|
|
2125
|
+
installRuntime();
|
|
2126
|
+
return isPretextRuntimeAvailable() ? new PretextTextMeasurer() : new DeterministicTextMeasurer();
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
// src/labels/fit.ts
|
|
2130
|
+
function fitLabel(text, options, measurer) {
|
|
2131
|
+
return computeLabelLayout(text, options, measurer);
|
|
2132
|
+
}
|
|
2133
|
+
function computeLabelLayout(text, options, measurer) {
|
|
2134
|
+
const padding = normalizeInsets(options.padding);
|
|
2135
|
+
const minSize = normalizeMinSize2(options.minSize);
|
|
2136
|
+
const lineHeight = resolveLineHeight(options.font);
|
|
2137
|
+
const maxWidth = normalizeMaxWidth(options.maxWidth);
|
|
2138
|
+
const prepared = measurer.prepare(text, options.font);
|
|
2139
|
+
const naturalTextWidth = measurer.naturalWidth(prepared);
|
|
2140
|
+
const contentMaxWidth = maxWidth === void 0 ? naturalTextWidth : Math.max(0, maxWidth - padding.left - padding.right);
|
|
2141
|
+
const textLayout = measurer.layout(prepared, contentMaxWidth, lineHeight);
|
|
2142
|
+
const naturalSize = {
|
|
2143
|
+
width: naturalTextWidth,
|
|
2144
|
+
height: textLayout.height
|
|
2145
|
+
};
|
|
2146
|
+
const contentWidth = Math.max(
|
|
2147
|
+
textLayout.width,
|
|
2148
|
+
minContentWidth(minSize, padding)
|
|
2149
|
+
);
|
|
2150
|
+
const contentHeight = Math.max(
|
|
2151
|
+
textLayout.height,
|
|
2152
|
+
minContentHeight(minSize, padding)
|
|
2153
|
+
);
|
|
2154
|
+
const idealWidth = contentWidth + padding.left + padding.right;
|
|
2155
|
+
const idealHeight = contentHeight + padding.top + padding.bottom;
|
|
2156
|
+
const fittedSize = {
|
|
2157
|
+
width: maxWidth === void 0 ? idealWidth : Math.min(maxWidth, idealWidth),
|
|
2158
|
+
height: idealHeight
|
|
2159
|
+
};
|
|
2160
|
+
const box = {
|
|
2161
|
+
x: 0,
|
|
2162
|
+
y: 0,
|
|
2163
|
+
width: fittedSize.width,
|
|
2164
|
+
height: fittedSize.height
|
|
2165
|
+
};
|
|
2166
|
+
const contentBox2 = {
|
|
2167
|
+
x: padding.left,
|
|
2168
|
+
y: padding.top,
|
|
2169
|
+
width: Math.max(0, box.width - padding.left - padding.right),
|
|
2170
|
+
height: Math.max(0, box.height - padding.top - padding.bottom)
|
|
2171
|
+
};
|
|
2172
|
+
const overflow = {
|
|
2173
|
+
horizontal: textLayout.width > contentBox2.width,
|
|
2174
|
+
vertical: textLayout.height > contentBox2.height || diagnosedHeightConstraintOverflow(textLayout.height, padding, minSize),
|
|
2175
|
+
truncated: options.overflow === "truncate" && textLayout.width > contentBox2.width
|
|
2176
|
+
};
|
|
2177
|
+
const diagnostics = buildDiagnostics(overflow, options.overflow);
|
|
2178
|
+
return {
|
|
2179
|
+
text,
|
|
2180
|
+
box,
|
|
2181
|
+
contentBox: contentBox2,
|
|
2182
|
+
naturalSize,
|
|
2183
|
+
fittedSize,
|
|
2184
|
+
padding,
|
|
2185
|
+
font: { ...options.font },
|
|
2186
|
+
textBackend: prepared.backend,
|
|
2187
|
+
lineHeight,
|
|
2188
|
+
lines: buildLines(textLayout, contentBox2, lineHeight),
|
|
2189
|
+
overflow,
|
|
2190
|
+
diagnostics
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
2193
|
+
function buildLines(textLayout, contentBox2, lineHeight) {
|
|
2194
|
+
return textLayout.lines.map((line, lineIndex) => ({
|
|
2195
|
+
text: line.text,
|
|
2196
|
+
box: {
|
|
2197
|
+
x: contentBox2.x,
|
|
2198
|
+
y: contentBox2.y + lineIndex * lineHeight,
|
|
2199
|
+
width: line.width,
|
|
2200
|
+
height: lineHeight
|
|
2201
|
+
},
|
|
2202
|
+
baselineY: contentBox2.y + lineIndex * lineHeight + lineHeight * 0.8,
|
|
2203
|
+
width: line.width,
|
|
2204
|
+
lineIndex,
|
|
2205
|
+
sourceStart: { ...line.start },
|
|
2206
|
+
sourceEnd: { ...line.end }
|
|
2207
|
+
}));
|
|
2208
|
+
}
|
|
2209
|
+
function normalizeMinSize2(minSize = {}) {
|
|
2210
|
+
if (minSize.width !== void 0) {
|
|
2211
|
+
assertFiniteNonNegative(minSize.width, "minSize.width");
|
|
2212
|
+
}
|
|
2213
|
+
if (minSize.height !== void 0) {
|
|
2214
|
+
assertFiniteNonNegative(minSize.height, "minSize.height");
|
|
2215
|
+
}
|
|
2216
|
+
return { ...minSize };
|
|
2217
|
+
}
|
|
2218
|
+
function normalizeMaxWidth(maxWidth) {
|
|
2219
|
+
if (maxWidth === void 0) {
|
|
2220
|
+
return void 0;
|
|
2221
|
+
}
|
|
2222
|
+
assertFiniteNonNegative(maxWidth, "maxWidth");
|
|
2223
|
+
return maxWidth;
|
|
2224
|
+
}
|
|
2225
|
+
function minContentWidth(minSize, padding) {
|
|
2226
|
+
return Math.max(0, (minSize.width ?? 0) - padding.left - padding.right);
|
|
2227
|
+
}
|
|
2228
|
+
function minContentHeight(minSize, padding) {
|
|
2229
|
+
return Math.max(0, (minSize.height ?? 0) - padding.top - padding.bottom);
|
|
2230
|
+
}
|
|
2231
|
+
function diagnosedHeightConstraintOverflow(textHeight, padding, minSize) {
|
|
2232
|
+
return minSize.height !== void 0 && textHeight + padding.top + padding.bottom > minSize.height;
|
|
2233
|
+
}
|
|
2234
|
+
function buildDiagnostics(overflow, mode = "allow") {
|
|
2235
|
+
if (mode !== "diagnose") {
|
|
2236
|
+
return [];
|
|
2237
|
+
}
|
|
2238
|
+
const diagnostics = [];
|
|
2239
|
+
if (overflow.horizontal) {
|
|
2240
|
+
diagnostics.push({
|
|
2241
|
+
severity: "warning",
|
|
2242
|
+
code: "label.overflow.horizontal",
|
|
2243
|
+
message: "Label text exceeds the fitted content width."
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
if (overflow.vertical) {
|
|
2247
|
+
diagnostics.push({
|
|
2248
|
+
severity: "warning",
|
|
2249
|
+
code: "label.overflow.vertical",
|
|
2250
|
+
message: "Label text exceeds the fitted content height."
|
|
2251
|
+
});
|
|
2252
|
+
}
|
|
2253
|
+
return diagnostics;
|
|
2254
|
+
}
|
|
2255
|
+
var DEFAULT_OPTIONS = {
|
|
2256
|
+
nodesep: 80,
|
|
2257
|
+
ranksep: 100,
|
|
2258
|
+
edgesep: 40,
|
|
2259
|
+
marginx: 0,
|
|
2260
|
+
marginy: 0,
|
|
2261
|
+
ranker: "network-simplex"
|
|
2262
|
+
};
|
|
2263
|
+
function runDagreInitialLayout(input) {
|
|
2264
|
+
const diagnostics = [];
|
|
2265
|
+
const boxes = /* @__PURE__ */ new Map();
|
|
2266
|
+
const validNodeIds = /* @__PURE__ */ new Set();
|
|
2267
|
+
const graph = new dagre.Graph({
|
|
2268
|
+
directed: true,
|
|
2269
|
+
multigraph: true,
|
|
2270
|
+
compound: false
|
|
2271
|
+
});
|
|
2272
|
+
const options = { ...DEFAULT_OPTIONS, ...input.options };
|
|
2273
|
+
graph.setGraph({
|
|
2274
|
+
rankdir: input.direction,
|
|
2275
|
+
nodesep: options.nodesep,
|
|
2276
|
+
ranksep: options.ranksep,
|
|
2277
|
+
edgesep: options.edgesep,
|
|
2278
|
+
marginx: options.marginx,
|
|
2279
|
+
marginy: options.marginy,
|
|
2280
|
+
ranker: options.ranker
|
|
2281
|
+
});
|
|
2282
|
+
graph.setDefaultEdgeLabel(() => ({}));
|
|
2283
|
+
for (const node of input.nodes) {
|
|
2284
|
+
if (!isValidDimension(node.size.width) || !isValidDimension(node.size.height)) {
|
|
2285
|
+
diagnostics.push({
|
|
2286
|
+
severity: "error",
|
|
2287
|
+
code: "layout.node-size.invalid",
|
|
2288
|
+
message: `Node ${node.id} has invalid layout dimensions.`,
|
|
2289
|
+
path: ["nodes", node.id, "size"],
|
|
2290
|
+
detail: { nodeId: node.id }
|
|
1442
2291
|
});
|
|
1443
2292
|
continue;
|
|
1444
2293
|
}
|
|
@@ -1504,37 +2353,112 @@ function isValidDimension(value) {
|
|
|
1504
2353
|
// src/routing/routes.ts
|
|
1505
2354
|
function routeEdge(input) {
|
|
1506
2355
|
const diagnostics = [];
|
|
2356
|
+
const softObstacles = input.obstacles ?? [];
|
|
2357
|
+
const hardObstacles = input.hardObstacles ?? [];
|
|
1507
2358
|
const defaultAnchors = defaultAnchorsForGeometry(
|
|
1508
2359
|
input.source.box,
|
|
1509
2360
|
input.target.box,
|
|
1510
2361
|
input.direction
|
|
1511
2362
|
);
|
|
1512
|
-
const source = getEdgePort(
|
|
1513
|
-
input.source,
|
|
1514
|
-
input.target.center,
|
|
1515
|
-
input.sourceAnchor ?? defaultAnchors.sourceAnchor
|
|
1516
|
-
);
|
|
1517
|
-
const target = getEdgePort(
|
|
1518
|
-
input.target,
|
|
1519
|
-
input.source.center,
|
|
1520
|
-
input.targetAnchor ?? defaultAnchors.targetAnchor
|
|
1521
|
-
);
|
|
1522
2363
|
if ((input.kind ?? "orthogonal") === "straight") {
|
|
1523
|
-
|
|
2364
|
+
const source = getEdgePort(
|
|
2365
|
+
input.source,
|
|
2366
|
+
input.target.center,
|
|
2367
|
+
input.sourceAnchor ?? defaultAnchors.sourceAnchor
|
|
2368
|
+
);
|
|
2369
|
+
const target = getEdgePort(
|
|
2370
|
+
input.target,
|
|
2371
|
+
input.source.center,
|
|
2372
|
+
input.targetAnchor ?? defaultAnchors.targetAnchor
|
|
2373
|
+
);
|
|
2374
|
+
const points = simplifyRoute([source, target]);
|
|
2375
|
+
if (routeCrossesBoxes(points, hardObstacles)) {
|
|
2376
|
+
diagnostics.push({
|
|
2377
|
+
severity: "error",
|
|
2378
|
+
code: "routing.evidence.crossing_forbidden",
|
|
2379
|
+
message: "Straight route crosses hard evidence block obstacles."
|
|
2380
|
+
});
|
|
2381
|
+
return { points, diagnostics };
|
|
2382
|
+
}
|
|
2383
|
+
if (routeCrossesBoxes(points, softObstacles)) {
|
|
2384
|
+
diagnostics.push({
|
|
2385
|
+
severity: "warning",
|
|
2386
|
+
code: "routing.obstacle.unavoidable",
|
|
2387
|
+
message: "Straight route crosses soft obstacles."
|
|
2388
|
+
});
|
|
2389
|
+
}
|
|
2390
|
+
return { points, diagnostics };
|
|
2391
|
+
}
|
|
2392
|
+
const routeLaneObstacles = [...softObstacles, ...hardObstacles];
|
|
2393
|
+
const anchorPairs = routeAnchorPairs(input, defaultAnchors);
|
|
2394
|
+
const candidateRoutes = anchorPairs.flatMap(
|
|
2395
|
+
({ sourceAnchor, targetAnchor }) => {
|
|
2396
|
+
const source = getEdgePort(
|
|
2397
|
+
input.source,
|
|
2398
|
+
input.target.center,
|
|
2399
|
+
sourceAnchor
|
|
2400
|
+
);
|
|
2401
|
+
const target = getEdgePort(
|
|
2402
|
+
input.target,
|
|
2403
|
+
input.source.center,
|
|
2404
|
+
targetAnchor
|
|
2405
|
+
);
|
|
2406
|
+
const routes = [
|
|
2407
|
+
...orthogonalCandidates(source, target, input.direction),
|
|
2408
|
+
...expandedObstacleCandidates(
|
|
2409
|
+
source,
|
|
2410
|
+
target,
|
|
2411
|
+
input.direction,
|
|
2412
|
+
routeLaneObstacles
|
|
2413
|
+
),
|
|
2414
|
+
...outerDoglegCandidates(
|
|
2415
|
+
source,
|
|
2416
|
+
target,
|
|
2417
|
+
input.direction,
|
|
2418
|
+
routeLaneObstacles
|
|
2419
|
+
)
|
|
2420
|
+
];
|
|
2421
|
+
const endpointObstacles = endpointObstaclesForAutoAnchors(input);
|
|
2422
|
+
return routes.map((points) => ({ points, endpointObstacles }));
|
|
2423
|
+
}
|
|
2424
|
+
);
|
|
2425
|
+
for (const candidate of candidateRoutes) {
|
|
2426
|
+
if (!routeIntersectsObstacles(candidate.points, softObstacles) && !routeIntersectsObstacles(candidate.points, hardObstacles) && !routeIntersectsEndpointInteriors(
|
|
2427
|
+
candidate.points,
|
|
2428
|
+
candidate.endpointObstacles
|
|
2429
|
+
)) {
|
|
2430
|
+
return { points: simplifyRoute(candidate.points), diagnostics };
|
|
2431
|
+
}
|
|
1524
2432
|
}
|
|
1525
|
-
const
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
target,
|
|
1530
|
-
input.direction,
|
|
1531
|
-
input.obstacles ?? []
|
|
2433
|
+
const hardClearCandidate = candidateRoutes.find(
|
|
2434
|
+
(candidate) => !routeIntersectsObstacles(candidate.points, hardObstacles) && !routeIntersectsEndpointInteriors(
|
|
2435
|
+
candidate.points,
|
|
2436
|
+
candidate.endpointObstacles
|
|
1532
2437
|
)
|
|
1533
2438
|
);
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
2439
|
+
if (hardClearCandidate !== void 0) {
|
|
2440
|
+
diagnostics.push({
|
|
2441
|
+
severity: "warning",
|
|
2442
|
+
code: "routing.obstacle.unavoidable",
|
|
2443
|
+
message: "No bounded orthogonal route candidate avoided all soft obstacles."
|
|
2444
|
+
});
|
|
2445
|
+
return {
|
|
2446
|
+
points: simplifyRoute(hardClearCandidate.points),
|
|
2447
|
+
diagnostics
|
|
2448
|
+
};
|
|
2449
|
+
}
|
|
2450
|
+
if (hardObstacles.length > 0) {
|
|
2451
|
+
diagnostics.push({
|
|
2452
|
+
severity: "error",
|
|
2453
|
+
code: "routing.evidence.crossing_forbidden",
|
|
2454
|
+
message: "No bounded orthogonal route candidate avoided hard evidence block obstacles."
|
|
2455
|
+
});
|
|
2456
|
+
return {
|
|
2457
|
+
points: simplifyRoute(
|
|
2458
|
+
candidateRoutes[0]?.points ?? fallbackRoute(input, defaultAnchors)
|
|
2459
|
+
),
|
|
2460
|
+
diagnostics
|
|
2461
|
+
};
|
|
1538
2462
|
}
|
|
1539
2463
|
diagnostics.push({
|
|
1540
2464
|
severity: "warning",
|
|
@@ -1542,10 +2466,111 @@ function routeEdge(input) {
|
|
|
1542
2466
|
message: "No bounded orthogonal route candidate avoided all obstacles."
|
|
1543
2467
|
});
|
|
1544
2468
|
return {
|
|
1545
|
-
points: simplifyRoute(
|
|
2469
|
+
points: simplifyRoute(
|
|
2470
|
+
candidateRoutes[0]?.points ?? fallbackRoute(input, defaultAnchors)
|
|
2471
|
+
),
|
|
1546
2472
|
diagnostics
|
|
1547
2473
|
};
|
|
1548
2474
|
}
|
|
2475
|
+
function endpointObstaclesForAutoAnchors(input) {
|
|
2476
|
+
const boxes = [];
|
|
2477
|
+
if (input.sourceAnchor === void 0 && hasDistinctAnchors(input.source)) {
|
|
2478
|
+
boxes.push(insetBox(input.source.box, 1));
|
|
2479
|
+
}
|
|
2480
|
+
if (input.targetAnchor === void 0 && hasDistinctAnchors(input.target)) {
|
|
2481
|
+
boxes.push(insetBox(input.target.box, 1));
|
|
2482
|
+
}
|
|
2483
|
+
return boxes.filter((box) => box.width > 0 && box.height > 0);
|
|
2484
|
+
}
|
|
2485
|
+
function hasDistinctAnchors(geometry) {
|
|
2486
|
+
const points = new Set(
|
|
2487
|
+
geometry.anchors.map((anchor) => `${anchor.point.x},${anchor.point.y}`)
|
|
2488
|
+
);
|
|
2489
|
+
return points.size > 1;
|
|
2490
|
+
}
|
|
2491
|
+
function insetBox(box, margin) {
|
|
2492
|
+
return {
|
|
2493
|
+
x: box.x + margin,
|
|
2494
|
+
y: box.y + margin,
|
|
2495
|
+
width: box.width - margin * 2,
|
|
2496
|
+
height: box.height - margin * 2
|
|
2497
|
+
};
|
|
2498
|
+
}
|
|
2499
|
+
function fallbackRoute(input, defaultAnchors) {
|
|
2500
|
+
return [
|
|
2501
|
+
getEdgePort(
|
|
2502
|
+
input.source,
|
|
2503
|
+
input.target.center,
|
|
2504
|
+
input.sourceAnchor ?? defaultAnchors.sourceAnchor
|
|
2505
|
+
),
|
|
2506
|
+
getEdgePort(
|
|
2507
|
+
input.target,
|
|
2508
|
+
input.source.center,
|
|
2509
|
+
input.targetAnchor ?? defaultAnchors.targetAnchor
|
|
2510
|
+
)
|
|
2511
|
+
];
|
|
2512
|
+
}
|
|
2513
|
+
function routeAnchorPairs(input, defaultAnchors) {
|
|
2514
|
+
const sourceAnchors = routeAnchorCandidates(
|
|
2515
|
+
input.sourceAnchor,
|
|
2516
|
+
defaultAnchors.sourceAnchor,
|
|
2517
|
+
input.source,
|
|
2518
|
+
input.target.center
|
|
2519
|
+
);
|
|
2520
|
+
const targetAnchors = routeAnchorCandidates(
|
|
2521
|
+
input.targetAnchor,
|
|
2522
|
+
defaultAnchors.targetAnchor,
|
|
2523
|
+
input.target,
|
|
2524
|
+
input.source.center
|
|
2525
|
+
);
|
|
2526
|
+
const pairs = sourceAnchors.flatMap(
|
|
2527
|
+
(sourceAnchor) => targetAnchors.map((targetAnchor) => ({ sourceAnchor, targetAnchor }))
|
|
2528
|
+
);
|
|
2529
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2530
|
+
return pairs.filter((pair) => {
|
|
2531
|
+
const key = `${pair.sourceAnchor}->${pair.targetAnchor}`;
|
|
2532
|
+
if (seen.has(key)) {
|
|
2533
|
+
return false;
|
|
2534
|
+
}
|
|
2535
|
+
seen.add(key);
|
|
2536
|
+
return true;
|
|
2537
|
+
});
|
|
2538
|
+
}
|
|
2539
|
+
function routeAnchorCandidates(explicitAnchor, defaultAnchor, geometry, toward) {
|
|
2540
|
+
if (explicitAnchor !== void 0) {
|
|
2541
|
+
return [explicitAnchor];
|
|
2542
|
+
}
|
|
2543
|
+
const ranked = rankedSideAnchors(geometry, toward);
|
|
2544
|
+
return [defaultAnchor, ...ranked].filter(
|
|
2545
|
+
(anchor, index, anchors) => anchors.indexOf(anchor) === index
|
|
2546
|
+
);
|
|
2547
|
+
}
|
|
2548
|
+
function rankedSideAnchors(geometry, toward) {
|
|
2549
|
+
const anchors = outwardSideAnchors(geometry.box, toward);
|
|
2550
|
+
return anchors.sort((left, right) => {
|
|
2551
|
+
const leftPoint = getEdgePort(geometry, toward, left);
|
|
2552
|
+
const rightPoint = getEdgePort(geometry, toward, right);
|
|
2553
|
+
const distance = squaredDistance2(leftPoint, toward) - squaredDistance2(rightPoint, toward);
|
|
2554
|
+
return distance === 0 ? left.localeCompare(right) : distance;
|
|
2555
|
+
});
|
|
2556
|
+
}
|
|
2557
|
+
function outwardSideAnchors(box, toward) {
|
|
2558
|
+
const center = {
|
|
2559
|
+
x: box.x + box.width / 2,
|
|
2560
|
+
y: box.y + box.height / 2
|
|
2561
|
+
};
|
|
2562
|
+
const dx = toward.x - center.x;
|
|
2563
|
+
const dy = toward.y - center.y;
|
|
2564
|
+
if (Math.abs(dx) >= Math.abs(dy)) {
|
|
2565
|
+
return dx >= 0 ? ["right", "top", "bottom"] : ["left", "top", "bottom"];
|
|
2566
|
+
}
|
|
2567
|
+
return dy >= 0 ? ["bottom", "left", "right"] : ["top", "left", "right"];
|
|
2568
|
+
}
|
|
2569
|
+
function squaredDistance2(a, b) {
|
|
2570
|
+
const dx = a.x - b.x;
|
|
2571
|
+
const dy = a.y - b.y;
|
|
2572
|
+
return dx * dx + dy * dy;
|
|
2573
|
+
}
|
|
1549
2574
|
function simplifyRoute(points) {
|
|
1550
2575
|
const withoutDuplicates = [];
|
|
1551
2576
|
for (const point2 of points) {
|
|
@@ -1670,6 +2695,44 @@ function expandedObstacleCandidates(source, target, direction, obstacles) {
|
|
|
1670
2695
|
}
|
|
1671
2696
|
return candidates;
|
|
1672
2697
|
}
|
|
2698
|
+
function outerDoglegCandidates(source, target, direction, obstacles) {
|
|
2699
|
+
if (obstacles.length === 0) {
|
|
2700
|
+
return [];
|
|
2701
|
+
}
|
|
2702
|
+
const margin = 24;
|
|
2703
|
+
const minX = Math.min(...obstacles.map((obstacle) => obstacle.x)) - margin;
|
|
2704
|
+
const maxX = Math.max(...obstacles.map((obstacle) => obstacle.x + obstacle.width)) + margin;
|
|
2705
|
+
const minY = Math.min(...obstacles.map((obstacle) => obstacle.y)) - margin;
|
|
2706
|
+
const maxY = Math.max(...obstacles.map((obstacle) => obstacle.y + obstacle.height)) + margin;
|
|
2707
|
+
if (direction === "TB" || direction === "BT") {
|
|
2708
|
+
const exit2 = exitDelta(source, target, "y");
|
|
2709
|
+
return sortedUniqueLanes([minX, maxX], (source.x + target.x) / 2).map(
|
|
2710
|
+
(laneX) => [
|
|
2711
|
+
source,
|
|
2712
|
+
{ x: source.x, y: source.y + exit2 },
|
|
2713
|
+
{ x: laneX, y: source.y + exit2 },
|
|
2714
|
+
{ x: laneX, y: target.y - exit2 },
|
|
2715
|
+
{ x: target.x, y: target.y - exit2 },
|
|
2716
|
+
target
|
|
2717
|
+
]
|
|
2718
|
+
);
|
|
2719
|
+
}
|
|
2720
|
+
const exit = exitDelta(source, target, "x");
|
|
2721
|
+
return sortedUniqueLanes([minY, maxY], (source.y + target.y) / 2).map(
|
|
2722
|
+
(laneY) => [
|
|
2723
|
+
source,
|
|
2724
|
+
{ x: source.x + exit, y: source.y },
|
|
2725
|
+
{ x: source.x + exit, y: laneY },
|
|
2726
|
+
{ x: target.x - exit, y: laneY },
|
|
2727
|
+
{ x: target.x - exit, y: target.y },
|
|
2728
|
+
target
|
|
2729
|
+
]
|
|
2730
|
+
);
|
|
2731
|
+
}
|
|
2732
|
+
function exitDelta(source, target, axis) {
|
|
2733
|
+
const delta = axis === "x" ? target.x - source.x : target.y - source.y;
|
|
2734
|
+
return (delta >= 0 ? 1 : -1) * 24;
|
|
2735
|
+
}
|
|
1673
2736
|
function sortedUniqueLanes(lanes, midpoint) {
|
|
1674
2737
|
return [...new Set(lanes)].filter((lane) => Number.isFinite(lane)).sort((left, right) => {
|
|
1675
2738
|
const distance = Math.abs(left - midpoint) - Math.abs(right - midpoint);
|
|
@@ -1693,6 +2756,72 @@ function routeIntersectsObstacles(points, obstacles) {
|
|
|
1693
2756
|
}
|
|
1694
2757
|
return false;
|
|
1695
2758
|
}
|
|
2759
|
+
function routeIntersectsEndpointInteriors(points, endpointInteriors) {
|
|
2760
|
+
for (let index = 0; index < points.length - 1; index += 1) {
|
|
2761
|
+
const a = points[index];
|
|
2762
|
+
const b = points[index + 1];
|
|
2763
|
+
if (a === void 0 || b === void 0) {
|
|
2764
|
+
continue;
|
|
2765
|
+
}
|
|
2766
|
+
const segment = segmentBox(a, b);
|
|
2767
|
+
for (const endpointInterior of endpointInteriors) {
|
|
2768
|
+
validateBox(endpointInterior);
|
|
2769
|
+
if (intersectsAabb(segment, endpointInterior)) {
|
|
2770
|
+
return true;
|
|
2771
|
+
}
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
return false;
|
|
2775
|
+
}
|
|
2776
|
+
function routeCrossesBoxes(points, obstacles) {
|
|
2777
|
+
for (let index = 0; index < points.length - 1; index += 1) {
|
|
2778
|
+
const a = points[index];
|
|
2779
|
+
const b = points[index + 1];
|
|
2780
|
+
if (a === void 0 || b === void 0) {
|
|
2781
|
+
continue;
|
|
2782
|
+
}
|
|
2783
|
+
for (const obstacle of obstacles) {
|
|
2784
|
+
validateBox(obstacle);
|
|
2785
|
+
if (segmentIntersectsBox(a, b, obstacle)) {
|
|
2786
|
+
return true;
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
}
|
|
2790
|
+
return false;
|
|
2791
|
+
}
|
|
2792
|
+
function segmentIntersectsBox(start, end, box) {
|
|
2793
|
+
const left = box.x;
|
|
2794
|
+
const right = box.x + box.width;
|
|
2795
|
+
const top = box.y;
|
|
2796
|
+
const bottom = box.y + box.height;
|
|
2797
|
+
if (pointInsideBox(start, box) || pointInsideBox(end, box)) {
|
|
2798
|
+
return true;
|
|
2799
|
+
}
|
|
2800
|
+
if (start.x === end.x) {
|
|
2801
|
+
return start.x > left && start.x < right && rangesOverlap(start.y, end.y, top, bottom);
|
|
2802
|
+
}
|
|
2803
|
+
if (start.y === end.y) {
|
|
2804
|
+
return start.y > top && start.y < bottom && rangesOverlap(start.x, end.x, left, right);
|
|
2805
|
+
}
|
|
2806
|
+
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);
|
|
2807
|
+
}
|
|
2808
|
+
function pointInsideBox(point2, box) {
|
|
2809
|
+
return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
|
|
2810
|
+
}
|
|
2811
|
+
function rangesOverlap(a, b, min, max) {
|
|
2812
|
+
const low = Math.min(a, b);
|
|
2813
|
+
const high = Math.max(a, b);
|
|
2814
|
+
return high > min && low < max;
|
|
2815
|
+
}
|
|
2816
|
+
function segmentIntersectsBoxEdge(start, end, x1, y1, x2, y2) {
|
|
2817
|
+
const denominator = (end.x - start.x) * (y2 - y1) - (end.y - start.y) * (x2 - x1);
|
|
2818
|
+
if (denominator === 0) {
|
|
2819
|
+
return false;
|
|
2820
|
+
}
|
|
2821
|
+
const t = ((x1 - start.x) * (y2 - y1) - (y1 - start.y) * (x2 - x1)) / denominator;
|
|
2822
|
+
const u = ((x1 - start.x) * (end.y - start.y) - (y1 - start.y) * (end.x - start.x)) / denominator;
|
|
2823
|
+
return t > 0 && t < 1 && u > 0 && u < 1;
|
|
2824
|
+
}
|
|
1696
2825
|
function segmentBox(a, b) {
|
|
1697
2826
|
const minX = Math.min(a.x, b.x);
|
|
1698
2827
|
const minY = Math.min(a.y, b.y);
|
|
@@ -1708,6 +2837,17 @@ function areCollinear(a, b, c) {
|
|
|
1708
2837
|
}
|
|
1709
2838
|
|
|
1710
2839
|
// src/solver/solve.ts
|
|
2840
|
+
var DEFAULT_MATRIX_CELL_SIZE = { width: 120, height: 36 };
|
|
2841
|
+
var DEFAULT_TABLE_CELL_SIZE = { width: 128, height: 34 };
|
|
2842
|
+
var DEFAULT_PANEL_WIDTH = 320;
|
|
2843
|
+
var DEFAULT_PANEL_ITEM_HEIGHT = 28;
|
|
2844
|
+
var DEFAULT_EVIDENCE_BLOCK_GAP = 24;
|
|
2845
|
+
var EDGE_LABEL_CLEARANCE = 8;
|
|
2846
|
+
var EVIDENCE_TEXT_FONT = {
|
|
2847
|
+
fontFamily: "Arial, sans-serif",
|
|
2848
|
+
fontSize: 10,
|
|
2849
|
+
lineHeight: 12
|
|
2850
|
+
};
|
|
1711
2851
|
function solveDiagram(diagram, options = {}) {
|
|
1712
2852
|
const diagnostics = [...diagram.diagnostics];
|
|
1713
2853
|
const nodes = stableById(diagram.nodes);
|
|
@@ -1772,19 +2912,21 @@ function solveDiagram(diagram, options = {}) {
|
|
|
1772
2912
|
constrained.boxes,
|
|
1773
2913
|
swimlaneContracts.layouts
|
|
1774
2914
|
);
|
|
2915
|
+
const coordinatedMatrices = coordinateMatrices(diagram.matrices ?? []);
|
|
2916
|
+
const coordinatedTables = coordinateTables(diagram.tables ?? []);
|
|
2917
|
+
const coordinatedEvidencePanels = coordinateEvidencePanels(
|
|
2918
|
+
diagram.evidencePanels ?? []
|
|
2919
|
+
);
|
|
1775
2920
|
const groupBoxes = new Map(
|
|
1776
2921
|
coordinatedGroups.map((group) => [group.id, group.box])
|
|
1777
2922
|
);
|
|
1778
|
-
const
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
diagnostics
|
|
1786
|
-
);
|
|
1787
|
-
const allBoxes = [
|
|
2923
|
+
const baseTextAnnotations = coordinateBaseTextAnnotations({
|
|
2924
|
+
nodes: coordinatedNodes,
|
|
2925
|
+
groups: coordinatedGroups,
|
|
2926
|
+
swimlanes: coordinatedSwimlanes,
|
|
2927
|
+
...options.textMeasurer === void 0 ? {} : { textMeasurer: options.textMeasurer }
|
|
2928
|
+
});
|
|
2929
|
+
const layoutBoxes = [
|
|
1788
2930
|
...coordinatedNodes.map((node) => node.box),
|
|
1789
2931
|
...coordinatedNodes.flatMap(
|
|
1790
2932
|
(node) => (node.ports ?? []).flatMap(
|
|
@@ -1794,10 +2936,111 @@ function solveDiagram(diagram, options = {}) {
|
|
|
1794
2936
|
...groupBoxes.values(),
|
|
1795
2937
|
...coordinatedSwimlanes.flatMap(
|
|
1796
2938
|
(swimlane) => swimlane.box === void 0 ? [] : [swimlane.box]
|
|
1797
|
-
)
|
|
2939
|
+
),
|
|
2940
|
+
...baseTextAnnotations.map((annotation) => annotation.box)
|
|
2941
|
+
];
|
|
2942
|
+
const initialContentBounds = layoutBoxes.length === 0 ? { x: 0, y: 0, width: 0} : unionBoxes(layoutBoxes);
|
|
2943
|
+
placeEvidenceBlocks(
|
|
2944
|
+
[
|
|
2945
|
+
...coordinatedMatrices,
|
|
2946
|
+
...coordinatedTables,
|
|
2947
|
+
...coordinatedEvidencePanels
|
|
2948
|
+
],
|
|
2949
|
+
initialContentBounds
|
|
2950
|
+
);
|
|
2951
|
+
refreshTableColumnXOffsets(coordinatedTables);
|
|
2952
|
+
measureEvidenceTextBlocks(
|
|
2953
|
+
coordinatedMatrices,
|
|
2954
|
+
coordinatedTables,
|
|
2955
|
+
coordinatedEvidencePanels,
|
|
2956
|
+
options.textMeasurer
|
|
2957
|
+
);
|
|
2958
|
+
const evidenceBoxes = [
|
|
2959
|
+
...coordinatedMatrices.map((matrix) => matrix.box),
|
|
2960
|
+
...coordinatedTables.map((table) => table.box),
|
|
2961
|
+
...coordinatedEvidencePanels.map((panel) => panel.box)
|
|
1798
2962
|
];
|
|
2963
|
+
diagnostics.push(
|
|
2964
|
+
...reportEvidenceBlockOverlaps(
|
|
2965
|
+
[
|
|
2966
|
+
...coordinatedMatrices.map((matrix) => ({
|
|
2967
|
+
id: matrix.id,
|
|
2968
|
+
kind: "matrix",
|
|
2969
|
+
...matrix.position === void 0 ? {} : { position: matrix.position },
|
|
2970
|
+
box: matrix.box
|
|
2971
|
+
})),
|
|
2972
|
+
...coordinatedTables.map((table) => ({
|
|
2973
|
+
id: table.id,
|
|
2974
|
+
kind: "table",
|
|
2975
|
+
...table.position === void 0 ? {} : { position: table.position },
|
|
2976
|
+
box: table.box
|
|
2977
|
+
})),
|
|
2978
|
+
...coordinatedEvidencePanels.map((panel) => ({
|
|
2979
|
+
id: panel.id,
|
|
2980
|
+
kind: "evidence-panel",
|
|
2981
|
+
...panel.position === void 0 ? {} : { position: panel.position },
|
|
2982
|
+
box: panel.box
|
|
2983
|
+
}))
|
|
2984
|
+
],
|
|
2985
|
+
[
|
|
2986
|
+
...coordinatedNodes.map((node) => ({
|
|
2987
|
+
id: node.id,
|
|
2988
|
+
kind: "node",
|
|
2989
|
+
box: node.box
|
|
2990
|
+
})),
|
|
2991
|
+
...coordinatedGroups.map((group) => ({
|
|
2992
|
+
id: group.id,
|
|
2993
|
+
kind: "group",
|
|
2994
|
+
box: group.box
|
|
2995
|
+
})),
|
|
2996
|
+
...coordinatedSwimlanes.flatMap(
|
|
2997
|
+
(swimlane) => swimlane.box === void 0 ? [] : [{ id: swimlane.id, kind: "swimlane", box: swimlane.box }]
|
|
2998
|
+
)
|
|
2999
|
+
]
|
|
3000
|
+
)
|
|
3001
|
+
);
|
|
3002
|
+
const allBoxes = [...layoutBoxes, ...evidenceBoxes];
|
|
1799
3003
|
const contentBounds = allBoxes.length === 0 ? { x: 0, y: 0, width: 0, height: 0 } : unionBoxes(allBoxes);
|
|
1800
3004
|
const frame = diagram.frame === void 0 ? void 0 : coordinateFrame(diagram.frame, contentBounds);
|
|
3005
|
+
const frameTextAnnotation = frame === void 0 ? [] : [coordinateFrameTextAnnotation(frame, options.textMeasurer)];
|
|
3006
|
+
const routingTextObstacles = [
|
|
3007
|
+
...baseTextAnnotations.filter(isPreRouteTextObstacle),
|
|
3008
|
+
...frameTextAnnotation.filter(isPreRouteTextObstacle)
|
|
3009
|
+
];
|
|
3010
|
+
const coordinatedEdges = coordinateEdges(
|
|
3011
|
+
edges,
|
|
3012
|
+
nodeGeometryById,
|
|
3013
|
+
coordinatedNodes,
|
|
3014
|
+
[...nodeGeometryById.values()].map((geometry) => geometry.obstacleBox),
|
|
3015
|
+
[
|
|
3016
|
+
...coordinatedTables.map((table) => table.box),
|
|
3017
|
+
...coordinatedEvidencePanels.map((panel) => panel.box)
|
|
3018
|
+
],
|
|
3019
|
+
routingTextObstacles,
|
|
3020
|
+
coordinatedMatrices.map((matrix) => matrix.box),
|
|
3021
|
+
diagram.direction,
|
|
3022
|
+
options,
|
|
3023
|
+
diagnostics
|
|
3024
|
+
);
|
|
3025
|
+
const edgeTextAnnotations = coordinateEdgeTextAnnotations(
|
|
3026
|
+
coordinatedEdges,
|
|
3027
|
+
options.textMeasurer
|
|
3028
|
+
);
|
|
3029
|
+
const textAnnotations = [
|
|
3030
|
+
...baseTextAnnotations,
|
|
3031
|
+
...frameTextAnnotation,
|
|
3032
|
+
...edgeTextAnnotations
|
|
3033
|
+
];
|
|
3034
|
+
diagnostics.push(...reportTextAnnotationCollisions(textAnnotations));
|
|
3035
|
+
diagnostics.push(
|
|
3036
|
+
...reportRouteTextClearance(coordinatedEdges, textAnnotations)
|
|
3037
|
+
);
|
|
3038
|
+
const edgePointBounds = edgeBounds(coordinatedEdges);
|
|
3039
|
+
const boundsBase = [
|
|
3040
|
+
contentBounds,
|
|
3041
|
+
...edgePointBounds,
|
|
3042
|
+
...edgeTextAnnotations.map((annotation) => annotation.box)
|
|
3043
|
+
];
|
|
1801
3044
|
return {
|
|
1802
3045
|
id: diagram.id,
|
|
1803
3046
|
...diagram.title === void 0 ? {} : { title: diagram.title },
|
|
@@ -1806,9 +3049,13 @@ function solveDiagram(diagram, options = {}) {
|
|
|
1806
3049
|
edges: coordinatedEdges,
|
|
1807
3050
|
groups: coordinatedGroups,
|
|
1808
3051
|
...coordinatedSwimlanes.length === 0 ? {} : { swimlanes: coordinatedSwimlanes },
|
|
3052
|
+
...coordinatedMatrices.length === 0 ? {} : { matrices: coordinatedMatrices },
|
|
3053
|
+
...coordinatedTables.length === 0 ? {} : { tables: coordinatedTables },
|
|
3054
|
+
...coordinatedEvidencePanels.length === 0 ? {} : { evidencePanels: coordinatedEvidencePanels },
|
|
1809
3055
|
diagnostics,
|
|
1810
|
-
bounds: frame === void 0 ?
|
|
3056
|
+
bounds: frame === void 0 ? unionBoxes(boundsBase) : unionBoxes([...boundsBase, frame.box, frame.titleBox]),
|
|
1811
3057
|
...frame === void 0 ? {} : { frame },
|
|
3058
|
+
...textAnnotations.length === 0 ? {} : { textAnnotations },
|
|
1812
3059
|
...diagram.metadata === void 0 ? {} : { metadata: diagram.metadata }
|
|
1813
3060
|
};
|
|
1814
3061
|
}
|
|
@@ -2703,7 +3950,364 @@ function coordinateGroups(groups, nodeBoxes, options, diagnostics) {
|
|
|
2703
3950
|
}
|
|
2704
3951
|
return coordinated;
|
|
2705
3952
|
}
|
|
2706
|
-
function
|
|
3953
|
+
function coordinateMatrices(matrices) {
|
|
3954
|
+
return matrices.map((block) => ({
|
|
3955
|
+
...block,
|
|
3956
|
+
box: blockBox(block, {
|
|
3957
|
+
width: defaultMatrixRowHeaderWidth(block) + Math.max(1, block.cols.length) * DEFAULT_MATRIX_CELL_SIZE.width,
|
|
3958
|
+
height: Math.max(1, block.rows.length + 1) * DEFAULT_MATRIX_CELL_SIZE.height
|
|
3959
|
+
})
|
|
3960
|
+
}));
|
|
3961
|
+
}
|
|
3962
|
+
function defaultMatrixRowHeaderWidth(block) {
|
|
3963
|
+
return block.rows.length === 0 ? 0 : Math.min(96, DEFAULT_MATRIX_CELL_SIZE.width);
|
|
3964
|
+
}
|
|
3965
|
+
function coordinateTables(tables) {
|
|
3966
|
+
return tables.map((table) => {
|
|
3967
|
+
const box = blockBox(table, {
|
|
3968
|
+
width: Math.max(1, table.columns.length) * DEFAULT_TABLE_CELL_SIZE.width,
|
|
3969
|
+
height: Math.max(1, table.rows.length + 1) * DEFAULT_TABLE_CELL_SIZE.height
|
|
3970
|
+
});
|
|
3971
|
+
return {
|
|
3972
|
+
...table,
|
|
3973
|
+
box,
|
|
3974
|
+
columnXOffsets: columnXOffsets(table, box)
|
|
3975
|
+
};
|
|
3976
|
+
});
|
|
3977
|
+
}
|
|
3978
|
+
function coordinateEvidencePanels(panels) {
|
|
3979
|
+
return panels.map((block) => ({
|
|
3980
|
+
...block,
|
|
3981
|
+
box: blockBox(block, {
|
|
3982
|
+
width: DEFAULT_PANEL_WIDTH,
|
|
3983
|
+
height: Math.max(1, block.items.length) * DEFAULT_PANEL_ITEM_HEIGHT
|
|
3984
|
+
})
|
|
3985
|
+
}));
|
|
3986
|
+
}
|
|
3987
|
+
function edgeBounds(edges) {
|
|
3988
|
+
return edges.flatMap((edge) => {
|
|
3989
|
+
if (edge.points.length === 0) {
|
|
3990
|
+
return [];
|
|
3991
|
+
}
|
|
3992
|
+
const minX = Math.min(...edge.points.map((point2) => point2.x));
|
|
3993
|
+
const minY = Math.min(...edge.points.map((point2) => point2.y));
|
|
3994
|
+
const maxX = Math.max(...edge.points.map((point2) => point2.x));
|
|
3995
|
+
const maxY = Math.max(...edge.points.map((point2) => point2.y));
|
|
3996
|
+
return [
|
|
3997
|
+
{
|
|
3998
|
+
x: minX,
|
|
3999
|
+
y: minY,
|
|
4000
|
+
width: maxX - minX,
|
|
4001
|
+
height: maxY - minY
|
|
4002
|
+
}
|
|
4003
|
+
];
|
|
4004
|
+
});
|
|
4005
|
+
}
|
|
4006
|
+
function blockBox(block, defaultSize) {
|
|
4007
|
+
return {
|
|
4008
|
+
x: block.position?.x ?? 0,
|
|
4009
|
+
y: block.position?.y ?? 0,
|
|
4010
|
+
width: block.size?.width ?? defaultSize.width,
|
|
4011
|
+
height: block.size?.height ?? defaultSize.height
|
|
4012
|
+
};
|
|
4013
|
+
}
|
|
4014
|
+
function placeEvidenceBlocks(blocks, contentBounds) {
|
|
4015
|
+
let nextY = contentBounds.y;
|
|
4016
|
+
const x = contentBounds.x + contentBounds.width + DEFAULT_EVIDENCE_BLOCK_GAP;
|
|
4017
|
+
for (const block of blocks) {
|
|
4018
|
+
if (block.position !== void 0) {
|
|
4019
|
+
continue;
|
|
4020
|
+
}
|
|
4021
|
+
block.box.x = x;
|
|
4022
|
+
block.box.y = nextY;
|
|
4023
|
+
nextY += block.box.height + DEFAULT_EVIDENCE_BLOCK_GAP;
|
|
4024
|
+
}
|
|
4025
|
+
}
|
|
4026
|
+
function columnXOffsets(table, box) {
|
|
4027
|
+
if (table.columns.length === 0) {
|
|
4028
|
+
return [];
|
|
4029
|
+
}
|
|
4030
|
+
const columnWidth = box.width / table.columns.length;
|
|
4031
|
+
return table.columns.map((_, index) => box.x + index * columnWidth);
|
|
4032
|
+
}
|
|
4033
|
+
function tableCellBox2(table, columnIndex, rowIndex, rowHeight, columnCount) {
|
|
4034
|
+
const x = table.columnXOffsets[columnIndex] ?? table.box.x + table.box.width / columnCount * columnIndex;
|
|
4035
|
+
const nextX = table.columnXOffsets[columnIndex + 1] ?? table.box.x + table.box.width;
|
|
4036
|
+
return {
|
|
4037
|
+
x,
|
|
4038
|
+
y: table.box.y + rowIndex * rowHeight,
|
|
4039
|
+
width: nextX - x,
|
|
4040
|
+
height: rowHeight
|
|
4041
|
+
};
|
|
4042
|
+
}
|
|
4043
|
+
function refreshTableColumnXOffsets(tables) {
|
|
4044
|
+
for (const table of tables) {
|
|
4045
|
+
table.columnXOffsets = columnXOffsets(table, table.box);
|
|
4046
|
+
}
|
|
4047
|
+
}
|
|
4048
|
+
function measureEvidenceTextBlocks(matrices, tables, panels, textMeasurer) {
|
|
4049
|
+
const measurer = textMeasurer ?? createDefaultTextMeasurer();
|
|
4050
|
+
for (const matrix of matrices) {
|
|
4051
|
+
const geometry = matrixGeometry(matrix);
|
|
4052
|
+
matrix.columnLabelLayouts = matrix.cols.map(
|
|
4053
|
+
(column) => measureEvidenceTextLayout(column, geometry.columnHeaderBox, measurer)
|
|
4054
|
+
);
|
|
4055
|
+
matrix.rowLabelLayouts = matrix.rows.map(
|
|
4056
|
+
(row, index) => measureEvidenceTextLayout(row, geometry.rowHeaderBox(index), measurer)
|
|
4057
|
+
);
|
|
4058
|
+
matrix.cellLabelLayouts = matrix.rows.map(
|
|
4059
|
+
(_, rowIndex) => matrix.cols.map((_2, columnIndex) => {
|
|
4060
|
+
const cell2 = matrix.cells[rowIndex]?.[columnIndex] ?? { text: "" };
|
|
4061
|
+
return measureEvidenceTextLayout(
|
|
4062
|
+
cell2.text,
|
|
4063
|
+
geometry.cellBox(rowIndex, columnIndex),
|
|
4064
|
+
measurer
|
|
4065
|
+
);
|
|
4066
|
+
})
|
|
4067
|
+
);
|
|
4068
|
+
}
|
|
4069
|
+
for (const table of tables) {
|
|
4070
|
+
const rowHeight = table.box.height / Math.max(1, table.rows.length + 1);
|
|
4071
|
+
const columnCount = Math.max(1, table.columns.length);
|
|
4072
|
+
table.columnLabelLayouts = table.columns.map(
|
|
4073
|
+
(column, columnIndex) => measureEvidenceTextLayout(
|
|
4074
|
+
column.label.text,
|
|
4075
|
+
tableCellBox2(table, columnIndex, 0, rowHeight, columnCount),
|
|
4076
|
+
measurer
|
|
4077
|
+
)
|
|
4078
|
+
);
|
|
4079
|
+
table.cellLabelLayouts = table.rows.map(
|
|
4080
|
+
(row, rowIndex) => table.columns.map((column, columnIndex) => {
|
|
4081
|
+
const cell2 = row.cells[column.id] ?? { text: "" };
|
|
4082
|
+
return measureEvidenceTextLayout(
|
|
4083
|
+
cell2.text,
|
|
4084
|
+
tableCellBox2(
|
|
4085
|
+
table,
|
|
4086
|
+
columnIndex,
|
|
4087
|
+
rowIndex + 1,
|
|
4088
|
+
rowHeight,
|
|
4089
|
+
columnCount
|
|
4090
|
+
),
|
|
4091
|
+
measurer
|
|
4092
|
+
);
|
|
4093
|
+
})
|
|
4094
|
+
);
|
|
4095
|
+
}
|
|
4096
|
+
for (const panel of panels) {
|
|
4097
|
+
const geometry = panelGeometry(panel);
|
|
4098
|
+
panel.titleLayout = measureEvidenceTextLayout(
|
|
4099
|
+
`${panel.kind}: ${panel.id}`,
|
|
4100
|
+
geometry.titleBox,
|
|
4101
|
+
measurer
|
|
4102
|
+
);
|
|
4103
|
+
panel.itemLayouts = panel.items.map(
|
|
4104
|
+
(item, index) => measureEvidenceTextLayout(
|
|
4105
|
+
panelItemText2(item.label.text, item.detail?.text),
|
|
4106
|
+
geometry.itemRowBox(index),
|
|
4107
|
+
measurer
|
|
4108
|
+
)
|
|
4109
|
+
);
|
|
4110
|
+
}
|
|
4111
|
+
}
|
|
4112
|
+
function measureEvidenceTextLayout(text, box, textMeasurer) {
|
|
4113
|
+
const lineHeight = EVIDENCE_TEXT_FONT.lineHeight;
|
|
4114
|
+
return {
|
|
4115
|
+
lines: wrapEvidenceText(text, {
|
|
4116
|
+
maxWidth: Math.max(0, box.width - 8),
|
|
4117
|
+
maxLines: Math.max(1, Math.floor((box.height - 4) / lineHeight)),
|
|
4118
|
+
textMeasurer
|
|
4119
|
+
})
|
|
4120
|
+
};
|
|
4121
|
+
}
|
|
4122
|
+
function wrapEvidenceText(text, options) {
|
|
4123
|
+
const normalized = text.trim().replace(/\s+/g, " ");
|
|
4124
|
+
if (normalized.length === 0) {
|
|
4125
|
+
return [""];
|
|
4126
|
+
}
|
|
4127
|
+
const lines = [];
|
|
4128
|
+
let current = "";
|
|
4129
|
+
let overflow = false;
|
|
4130
|
+
for (const word of normalized.split(" ")) {
|
|
4131
|
+
const chunks = chunkEvidenceWord(
|
|
4132
|
+
word,
|
|
4133
|
+
options.maxWidth,
|
|
4134
|
+
options.textMeasurer
|
|
4135
|
+
);
|
|
4136
|
+
for (const chunk of chunks) {
|
|
4137
|
+
const candidate = current.length === 0 ? chunk : `${current} ${chunk}`;
|
|
4138
|
+
if (measureEvidenceText(candidate, options.textMeasurer) <= options.maxWidth) {
|
|
4139
|
+
current = candidate;
|
|
4140
|
+
continue;
|
|
4141
|
+
}
|
|
4142
|
+
if (current.length > 0) {
|
|
4143
|
+
lines.push(current);
|
|
4144
|
+
current = chunk;
|
|
4145
|
+
} else {
|
|
4146
|
+
lines.push(chunk);
|
|
4147
|
+
current = "";
|
|
4148
|
+
}
|
|
4149
|
+
if (lines.length >= options.maxLines) {
|
|
4150
|
+
overflow = true;
|
|
4151
|
+
break;
|
|
4152
|
+
}
|
|
4153
|
+
}
|
|
4154
|
+
if (overflow) {
|
|
4155
|
+
break;
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
4158
|
+
if (!overflow && current.length > 0) {
|
|
4159
|
+
lines.push(current);
|
|
4160
|
+
}
|
|
4161
|
+
if (lines.length > options.maxLines) {
|
|
4162
|
+
overflow = true;
|
|
4163
|
+
lines.length = options.maxLines;
|
|
4164
|
+
}
|
|
4165
|
+
if (overflow || lines.length === options.maxLines) {
|
|
4166
|
+
const rendered = lines.join(" ");
|
|
4167
|
+
if (rendered.length < normalized.length) {
|
|
4168
|
+
lines[lines.length - 1] = ellipsizeMeasuredEvidenceLine(
|
|
4169
|
+
lines[lines.length - 1] ?? "",
|
|
4170
|
+
options.maxWidth,
|
|
4171
|
+
options.textMeasurer
|
|
4172
|
+
);
|
|
4173
|
+
}
|
|
4174
|
+
}
|
|
4175
|
+
return lines.length === 0 ? [""] : lines;
|
|
4176
|
+
}
|
|
4177
|
+
function chunkEvidenceWord(word, maxWidth, textMeasurer) {
|
|
4178
|
+
if (measureEvidenceText(word, textMeasurer) <= maxWidth) {
|
|
4179
|
+
return [word];
|
|
4180
|
+
}
|
|
4181
|
+
const chunks = [];
|
|
4182
|
+
let current = "";
|
|
4183
|
+
for (const char of Array.from(word)) {
|
|
4184
|
+
const candidate = `${current}${char}`;
|
|
4185
|
+
if (current.length > 0 && measureEvidenceText(candidate, textMeasurer) > maxWidth) {
|
|
4186
|
+
chunks.push(current);
|
|
4187
|
+
current = char;
|
|
4188
|
+
continue;
|
|
4189
|
+
}
|
|
4190
|
+
current = candidate;
|
|
4191
|
+
}
|
|
4192
|
+
if (current.length > 0) {
|
|
4193
|
+
chunks.push(current);
|
|
4194
|
+
}
|
|
4195
|
+
return chunks.length === 0 ? [word] : chunks;
|
|
4196
|
+
}
|
|
4197
|
+
function ellipsizeMeasuredEvidenceLine(line, maxWidth, textMeasurer) {
|
|
4198
|
+
const ellipsis = "...";
|
|
4199
|
+
if (measureEvidenceText(ellipsis, textMeasurer) > maxWidth) {
|
|
4200
|
+
return "";
|
|
4201
|
+
}
|
|
4202
|
+
let candidate = line.trimEnd();
|
|
4203
|
+
while (candidate.length > 0 && measureEvidenceText(`${candidate}${ellipsis}`, textMeasurer) > maxWidth) {
|
|
4204
|
+
candidate = Array.from(candidate).slice(0, -1).join("").trimEnd();
|
|
4205
|
+
}
|
|
4206
|
+
return `${candidate}${ellipsis}`;
|
|
4207
|
+
}
|
|
4208
|
+
function measureEvidenceText(text, textMeasurer) {
|
|
4209
|
+
return textMeasurer.naturalWidth(
|
|
4210
|
+
textMeasurer.prepare(text, EVIDENCE_TEXT_FONT)
|
|
4211
|
+
);
|
|
4212
|
+
}
|
|
4213
|
+
function matrixGeometry(matrix) {
|
|
4214
|
+
const columnCount = Math.max(1, matrix.cols.length);
|
|
4215
|
+
const rowCount = matrix.rows.length;
|
|
4216
|
+
const rowHeaderWidth = rowCount > 0 ? Math.min(96, matrix.box.width * 0.28) : 0;
|
|
4217
|
+
const dataWidth = Math.max(0, matrix.box.width - rowHeaderWidth);
|
|
4218
|
+
const cellWidth = dataWidth / columnCount;
|
|
4219
|
+
const rowHeight = matrix.box.height / Math.max(1, rowCount + 1);
|
|
4220
|
+
return {
|
|
4221
|
+
rowHeaderWidth,
|
|
4222
|
+
cellWidth,
|
|
4223
|
+
rowHeight,
|
|
4224
|
+
columnHeaderBox: {
|
|
4225
|
+
x: matrix.box.x + rowHeaderWidth,
|
|
4226
|
+
y: matrix.box.y,
|
|
4227
|
+
width: cellWidth,
|
|
4228
|
+
height: rowHeight
|
|
4229
|
+
},
|
|
4230
|
+
rowHeaderBox: (rowIndex) => ({
|
|
4231
|
+
x: matrix.box.x,
|
|
4232
|
+
y: matrix.box.y + (rowIndex + 1) * rowHeight,
|
|
4233
|
+
width: rowHeaderWidth,
|
|
4234
|
+
height: rowHeight
|
|
4235
|
+
}),
|
|
4236
|
+
cellBox: (rowIndex, columnIndex) => ({
|
|
4237
|
+
x: matrix.box.x + rowHeaderWidth + columnIndex * cellWidth,
|
|
4238
|
+
y: matrix.box.y + (rowIndex + 1) * rowHeight,
|
|
4239
|
+
width: cellWidth,
|
|
4240
|
+
height: rowHeight
|
|
4241
|
+
})
|
|
4242
|
+
};
|
|
4243
|
+
}
|
|
4244
|
+
function panelGeometry(panel) {
|
|
4245
|
+
const titleWidth = Math.min(panel.box.width * 0.36, 140);
|
|
4246
|
+
const itemBox = {
|
|
4247
|
+
x: panel.box.x + titleWidth,
|
|
4248
|
+
y: panel.box.y,
|
|
4249
|
+
width: panel.box.width - titleWidth,
|
|
4250
|
+
height: panel.box.height
|
|
4251
|
+
};
|
|
4252
|
+
const itemHeight = panel.box.height / Math.max(1, panel.items.length);
|
|
4253
|
+
return {
|
|
4254
|
+
titleBox: {
|
|
4255
|
+
x: panel.box.x,
|
|
4256
|
+
y: panel.box.y,
|
|
4257
|
+
width: titleWidth,
|
|
4258
|
+
height: panel.box.height
|
|
4259
|
+
},
|
|
4260
|
+
itemRowBox: (index) => ({
|
|
4261
|
+
x: itemBox.x,
|
|
4262
|
+
y: itemBox.y + index * itemHeight,
|
|
4263
|
+
width: itemBox.width,
|
|
4264
|
+
height: itemHeight
|
|
4265
|
+
})
|
|
4266
|
+
};
|
|
4267
|
+
}
|
|
4268
|
+
function panelItemText2(label, detail) {
|
|
4269
|
+
return detail === void 0 ? label : `${label}: ${detail}`;
|
|
4270
|
+
}
|
|
4271
|
+
function reportEvidenceBlockOverlaps(evidenceBlocks, contentBlocks) {
|
|
4272
|
+
const diagnostics = [];
|
|
4273
|
+
for (let index = 0; index < evidenceBlocks.length; index += 1) {
|
|
4274
|
+
const block = evidenceBlocks[index];
|
|
4275
|
+
if (block === void 0 || block.position === void 0) {
|
|
4276
|
+
continue;
|
|
4277
|
+
}
|
|
4278
|
+
for (const content of contentBlocks) {
|
|
4279
|
+
if (intersectsAabb(block.box, content.box)) {
|
|
4280
|
+
diagnostics.push(evidenceOverlapDiagnostic(block, content));
|
|
4281
|
+
}
|
|
4282
|
+
}
|
|
4283
|
+
for (let otherIndex = 0; otherIndex < evidenceBlocks.length; otherIndex += 1) {
|
|
4284
|
+
if (otherIndex === index) {
|
|
4285
|
+
continue;
|
|
4286
|
+
}
|
|
4287
|
+
const other = evidenceBlocks[otherIndex];
|
|
4288
|
+
if (other === void 0 || other.position !== void 0 && otherIndex < index || !intersectsAabb(block.box, other.box)) {
|
|
4289
|
+
continue;
|
|
4290
|
+
}
|
|
4291
|
+
diagnostics.push(evidenceOverlapDiagnostic(block, other));
|
|
4292
|
+
}
|
|
4293
|
+
}
|
|
4294
|
+
return diagnostics;
|
|
4295
|
+
}
|
|
4296
|
+
function evidenceOverlapDiagnostic(block, conflict) {
|
|
4297
|
+
return {
|
|
4298
|
+
severity: "warning",
|
|
4299
|
+
code: "constraints.overlap.unresolved",
|
|
4300
|
+
message: `Evidence block ${block.id} overlaps ${conflict.kind} ${conflict.id}.`,
|
|
4301
|
+
path: ["evidence", block.id],
|
|
4302
|
+
detail: {
|
|
4303
|
+
evidenceBlockId: block.id,
|
|
4304
|
+
evidenceBlockKind: block.kind,
|
|
4305
|
+
conflictingObjectId: conflict.id,
|
|
4306
|
+
conflictingObjectKind: conflict.kind
|
|
4307
|
+
}
|
|
4308
|
+
};
|
|
4309
|
+
}
|
|
4310
|
+
function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, softObstacles, textObstacles, hardObstacles, direction, options, diagnostics) {
|
|
2707
4311
|
const coordinated = [];
|
|
2708
4312
|
const coordinatedNodeById = new Map(
|
|
2709
4313
|
coordinatedNodes.map((node) => [node.id, node])
|
|
@@ -2727,6 +4331,8 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, direction, o
|
|
|
2727
4331
|
}
|
|
2728
4332
|
const sourcePort = coordinatedNodeById.get(edge.source.nodeId)?.ports?.find((port) => port.id === edge.source.portId);
|
|
2729
4333
|
const targetPort = coordinatedNodeById.get(edge.target.nodeId)?.ports?.find((port) => port.id === edge.target.portId);
|
|
4334
|
+
const connectedTextOwners = edgeConnectedTextOwnerIds(edge);
|
|
4335
|
+
const routeTextObstacles = textObstacles.filter((annotation) => !connectedTextOwners.has(annotation.ownerId)).map((annotation) => annotation.box);
|
|
2730
4336
|
const route = routeEdge({
|
|
2731
4337
|
kind: options.routeKind ?? "orthogonal",
|
|
2732
4338
|
direction,
|
|
@@ -2734,9 +4340,14 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, direction, o
|
|
|
2734
4340
|
target: portGeometry(target, targetPort),
|
|
2735
4341
|
...edge.source.anchor === void 0 ? {} : { sourceAnchor: edge.source.anchor },
|
|
2736
4342
|
...edge.target.anchor === void 0 ? {} : { targetAnchor: edge.target.anchor },
|
|
2737
|
-
obstacles:
|
|
2738
|
-
(
|
|
2739
|
-
|
|
4343
|
+
obstacles: [
|
|
4344
|
+
...obstacles.filter(
|
|
4345
|
+
(obstacle) => obstacle !== source.obstacleBox && obstacle !== target.obstacleBox
|
|
4346
|
+
),
|
|
4347
|
+
...softObstacles,
|
|
4348
|
+
...routeTextObstacles
|
|
4349
|
+
],
|
|
4350
|
+
hardObstacles
|
|
2740
4351
|
});
|
|
2741
4352
|
diagnostics.push(
|
|
2742
4353
|
...route.diagnostics.map((diagnostic) => ({
|
|
@@ -2751,387 +4362,581 @@ function coordinateEdges(edges, nodes, coordinatedNodes, obstacles, direction, o
|
|
|
2751
4362
|
}
|
|
2752
4363
|
return coordinated;
|
|
2753
4364
|
}
|
|
2754
|
-
function
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
return {
|
|
2759
|
-
...nodeGeometry,
|
|
2760
|
-
box: port.box,
|
|
2761
|
-
center: port.anchor,
|
|
2762
|
-
anchors: nodeGeometry.anchors.map((anchor) => ({
|
|
2763
|
-
name: anchor.name,
|
|
2764
|
-
point: port.anchor
|
|
2765
|
-
})),
|
|
2766
|
-
obstacleBox: port.box
|
|
2767
|
-
};
|
|
2768
|
-
}
|
|
2769
|
-
function stableById(items) {
|
|
2770
|
-
return [...items].sort((a, b) => a.id.localeCompare(b.id));
|
|
2771
|
-
}
|
|
2772
|
-
function stableByConstraintId(items) {
|
|
2773
|
-
return [...items].sort(
|
|
2774
|
-
(a, b) => `${a.id ?? a.kind}`.localeCompare(`${b.id ?? b.kind}`)
|
|
2775
|
-
);
|
|
2776
|
-
}
|
|
2777
|
-
function groupReferenceMissing(groupId, referenceKind, id) {
|
|
2778
|
-
return {
|
|
2779
|
-
severity: "error",
|
|
2780
|
-
code: "solver.group-reference.missing",
|
|
2781
|
-
message: `Group ${groupId} references a missing ${referenceKind}.`,
|
|
2782
|
-
path: ["groups", groupId],
|
|
2783
|
-
detail: id === void 0 ? { groupId } : { groupId, id }
|
|
2784
|
-
};
|
|
2785
|
-
}
|
|
2786
|
-
|
|
2787
|
-
// src/text/types.ts
|
|
2788
|
-
function assertFinitePositive(value, label) {
|
|
2789
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
2790
|
-
throw new TypeError(`${label} must be finite and positive`);
|
|
2791
|
-
}
|
|
2792
|
-
}
|
|
2793
|
-
function assertFiniteNonNegative(value, label) {
|
|
2794
|
-
if (!Number.isFinite(value) || value < 0) {
|
|
2795
|
-
throw new TypeError(`${label} must be a finite non-negative width`);
|
|
2796
|
-
}
|
|
2797
|
-
}
|
|
2798
|
-
function validateTextStyle(style2) {
|
|
2799
|
-
assertFinitePositive(style2.fontSize, "fontSize");
|
|
2800
|
-
if (style2.lineHeight !== void 0) {
|
|
2801
|
-
assertFinitePositive(style2.lineHeight, "lineHeight");
|
|
4365
|
+
function edgeConnectedTextOwnerIds(edge) {
|
|
4366
|
+
const owners = /* @__PURE__ */ new Set();
|
|
4367
|
+
if (edge.source.portId !== void 0) {
|
|
4368
|
+
owners.add(`${edge.source.nodeId}.${edge.source.portId}`);
|
|
2802
4369
|
}
|
|
2803
|
-
if (
|
|
2804
|
-
|
|
4370
|
+
if (edge.target.portId !== void 0) {
|
|
4371
|
+
owners.add(`${edge.target.nodeId}.${edge.target.portId}`);
|
|
2805
4372
|
}
|
|
4373
|
+
return owners;
|
|
2806
4374
|
}
|
|
2807
|
-
function
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
}
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
};
|
|
4375
|
+
function coordinateBaseTextAnnotations(input) {
|
|
4376
|
+
const measurer = input.textMeasurer ?? createDefaultTextMeasurer();
|
|
4377
|
+
const annotations = [];
|
|
4378
|
+
for (const node of input.nodes) {
|
|
4379
|
+
if (node.compartments !== void 0) {
|
|
4380
|
+
continue;
|
|
4381
|
+
}
|
|
4382
|
+
if (node.labelLayout === void 0 && node.label === void 0) {
|
|
4383
|
+
continue;
|
|
4384
|
+
}
|
|
4385
|
+
const layout2 = node.labelLayout ?? fallbackLabelLayout(node.label?.text ?? "");
|
|
4386
|
+
const buildAnnotation = node.labelLayout === void 0 ? buildAnchorCenteredTextAnnotation : buildTextAnnotation;
|
|
4387
|
+
annotations.push(
|
|
4388
|
+
buildAnnotation({
|
|
4389
|
+
ownerId: node.id,
|
|
4390
|
+
surfaceKind: "node-label",
|
|
4391
|
+
layout: layout2,
|
|
4392
|
+
anchor: node.box
|
|
4393
|
+
})
|
|
4394
|
+
);
|
|
2828
4395
|
}
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
const
|
|
2834
|
-
|
|
2835
|
-
|
|
4396
|
+
for (const group of input.groups) {
|
|
4397
|
+
if (group.labelLayout === void 0 && group.label === void 0) {
|
|
4398
|
+
continue;
|
|
4399
|
+
}
|
|
4400
|
+
const layout2 = group.labelLayout ?? fallbackLabelLayout(group.label?.text ?? "");
|
|
4401
|
+
const buildAnnotation = group.labelLayout === void 0 ? buildAnchorCenteredTextAnnotation : buildTextAnnotation;
|
|
4402
|
+
annotations.push(
|
|
4403
|
+
buildAnnotation({
|
|
4404
|
+
ownerId: group.id,
|
|
4405
|
+
surfaceKind: "group-label",
|
|
4406
|
+
layout: layout2,
|
|
4407
|
+
anchor: group.box
|
|
4408
|
+
})
|
|
2836
4409
|
);
|
|
2837
|
-
return {
|
|
2838
|
-
width,
|
|
2839
|
-
height: lines.length * lineHeight,
|
|
2840
|
-
lineHeight,
|
|
2841
|
-
lineCount: lines.length,
|
|
2842
|
-
lines,
|
|
2843
|
-
diagnostics: []
|
|
2844
|
-
};
|
|
2845
4410
|
}
|
|
2846
|
-
|
|
2847
|
-
const
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
4411
|
+
for (const node of input.nodes) {
|
|
4412
|
+
for (const port of node.ports ?? []) {
|
|
4413
|
+
if (port.label?.text === void 0) {
|
|
4414
|
+
continue;
|
|
4415
|
+
}
|
|
4416
|
+
const layout2 = fitLabel(
|
|
4417
|
+
port.label.text,
|
|
4418
|
+
{
|
|
4419
|
+
font: { fontFamily: "Arial", fontSize: 10, lineHeight: 12 },
|
|
4420
|
+
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
4421
|
+
minSize: { width: 0, height: 0 },
|
|
4422
|
+
maxWidth: 160
|
|
4423
|
+
},
|
|
4424
|
+
measurer
|
|
4425
|
+
);
|
|
4426
|
+
annotations.push(
|
|
4427
|
+
buildTextAnnotation({
|
|
4428
|
+
ownerId: `${node.id}.${port.id}`,
|
|
4429
|
+
surfaceKind: "port-label",
|
|
4430
|
+
layout: layout2,
|
|
4431
|
+
anchor: portLabelBox(port)
|
|
4432
|
+
})
|
|
4433
|
+
);
|
|
4434
|
+
}
|
|
2851
4435
|
}
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
for (
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
segmentIndex += 1;
|
|
4436
|
+
for (const node of input.nodes) {
|
|
4437
|
+
if (node.compartments === void 0) {
|
|
4438
|
+
continue;
|
|
4439
|
+
}
|
|
4440
|
+
const rows = compartmentRows(node);
|
|
4441
|
+
for (let index = 0; index < rows.length; index += 1) {
|
|
4442
|
+
const row = rows[index];
|
|
4443
|
+
if (row === void 0) {
|
|
2861
4444
|
continue;
|
|
2862
4445
|
}
|
|
2863
|
-
const
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
4446
|
+
const layout2 = fitLabel(
|
|
4447
|
+
row,
|
|
4448
|
+
{
|
|
4449
|
+
font: { fontFamily: "Arial", fontSize: 11, lineHeight: 13 },
|
|
4450
|
+
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
4451
|
+
minSize: { width: 0, height: 0 },
|
|
4452
|
+
maxWidth: node.box.width
|
|
4453
|
+
},
|
|
4454
|
+
measurer
|
|
4455
|
+
);
|
|
4456
|
+
annotations.push(
|
|
4457
|
+
buildAnchorCenteredTextAnnotation({
|
|
4458
|
+
ownerId: node.id,
|
|
4459
|
+
surfaceKind: "compartment-row",
|
|
4460
|
+
surfaceIndex: index,
|
|
4461
|
+
layout: layout2,
|
|
4462
|
+
anchor: {
|
|
4463
|
+
x: node.box.x,
|
|
4464
|
+
y: node.box.y + 18 + index * 16,
|
|
4465
|
+
width: node.box.width,
|
|
4466
|
+
height: 16
|
|
4467
|
+
}
|
|
4468
|
+
})
|
|
4469
|
+
);
|
|
4470
|
+
}
|
|
4471
|
+
}
|
|
4472
|
+
for (const swimlane of input.swimlanes) {
|
|
4473
|
+
for (const lane of swimlane.lanes) {
|
|
4474
|
+
if (lane.label?.text === void 0 || lane.box === void 0) {
|
|
4475
|
+
continue;
|
|
2875
4476
|
}
|
|
2876
|
-
|
|
4477
|
+
const labelBox = lane.headerBox ?? lane.box;
|
|
4478
|
+
const layout2 = fitLabel(
|
|
4479
|
+
lane.label.text,
|
|
4480
|
+
{
|
|
4481
|
+
font: { fontFamily: "Arial", fontSize: 12, lineHeight: 14 },
|
|
4482
|
+
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
4483
|
+
minSize: { width: 0, height: 0 },
|
|
4484
|
+
maxWidth: swimlane.orientation === "horizontal" ? labelBox.height : labelBox.width
|
|
4485
|
+
},
|
|
4486
|
+
measurer
|
|
4487
|
+
);
|
|
4488
|
+
annotations.push(
|
|
4489
|
+
buildAnchorCenteredTextAnnotation({
|
|
4490
|
+
ownerId: `${swimlane.id}.${lane.id}`,
|
|
4491
|
+
surfaceKind: "swimlane-label",
|
|
4492
|
+
layout: layout2,
|
|
4493
|
+
anchor: labelBox
|
|
4494
|
+
})
|
|
4495
|
+
);
|
|
2877
4496
|
}
|
|
2878
|
-
|
|
2879
|
-
|
|
4497
|
+
}
|
|
4498
|
+
return annotations;
|
|
4499
|
+
}
|
|
4500
|
+
function coordinateEdgeTextAnnotations(edges, textMeasurer) {
|
|
4501
|
+
const measurer = textMeasurer ?? createDefaultTextMeasurer();
|
|
4502
|
+
const annotations = [];
|
|
4503
|
+
for (const edge of edges) {
|
|
4504
|
+
if (edge.label?.text === void 0) {
|
|
4505
|
+
continue;
|
|
2880
4506
|
}
|
|
2881
|
-
|
|
4507
|
+
const layout2 = fitLabel(
|
|
4508
|
+
edge.label.text,
|
|
4509
|
+
{
|
|
4510
|
+
font: { fontFamily: "Arial", fontSize: 12, lineHeight: 14 },
|
|
4511
|
+
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
4512
|
+
minSize: { width: 0, height: 0 },
|
|
4513
|
+
maxWidth: 200
|
|
4514
|
+
},
|
|
4515
|
+
measurer
|
|
4516
|
+
);
|
|
4517
|
+
annotations.push(
|
|
4518
|
+
buildCenteredTextAnnotation({
|
|
4519
|
+
ownerId: edge.id,
|
|
4520
|
+
surfaceKind: "edge-label",
|
|
4521
|
+
layout: layout2,
|
|
4522
|
+
center: edgeLabelAnchor(edge, layout2, edges)
|
|
4523
|
+
})
|
|
4524
|
+
);
|
|
2882
4525
|
}
|
|
2883
|
-
|
|
2884
|
-
function getCharacterWidth(style2) {
|
|
2885
|
-
const letterSpacing = style2.letterSpacing ?? 0;
|
|
2886
|
-
return Math.max(0, style2.fontSize * 0.6 + letterSpacing);
|
|
4526
|
+
return annotations;
|
|
2887
4527
|
}
|
|
2888
|
-
function
|
|
4528
|
+
function coordinateFrameTextAnnotation(frame, textMeasurer) {
|
|
4529
|
+
const layout2 = fitLabel(
|
|
4530
|
+
frame.titleTab,
|
|
4531
|
+
{
|
|
4532
|
+
font: { fontFamily: "Arial", fontSize: 12, lineHeight: 14 },
|
|
4533
|
+
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
4534
|
+
minSize: { width: 0, height: 0 },
|
|
4535
|
+
maxWidth: frame.titleBox.width
|
|
4536
|
+
},
|
|
4537
|
+
textMeasurer ?? createDefaultTextMeasurer()
|
|
4538
|
+
);
|
|
4539
|
+
return buildAnchorCenteredTextAnnotation({
|
|
4540
|
+
ownerId: frame.kind,
|
|
4541
|
+
surfaceKind: "frame-title",
|
|
4542
|
+
layout: layout2,
|
|
4543
|
+
anchor: frame.titleBox
|
|
4544
|
+
});
|
|
4545
|
+
}
|
|
4546
|
+
function buildTextAnnotation(input) {
|
|
2889
4547
|
return {
|
|
2890
|
-
text,
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
4548
|
+
text: input.layout.text,
|
|
4549
|
+
ownerId: input.ownerId,
|
|
4550
|
+
surfaceKind: input.surfaceKind,
|
|
4551
|
+
...input.surfaceIndex === void 0 ? {} : { surfaceIndex: input.surfaceIndex },
|
|
4552
|
+
box: {
|
|
4553
|
+
x: input.anchor.x + input.layout.box.x,
|
|
4554
|
+
y: input.anchor.y + input.layout.box.y,
|
|
4555
|
+
width: input.layout.box.width,
|
|
4556
|
+
height: input.layout.box.height
|
|
2895
4557
|
},
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
4558
|
+
anchor: input.anchor,
|
|
4559
|
+
paddings: input.layout.padding,
|
|
4560
|
+
lines: input.layout.lines,
|
|
4561
|
+
fontSize: input.layout.font.fontSize,
|
|
4562
|
+
textBackend: input.layout.textBackend
|
|
2900
4563
|
};
|
|
2901
4564
|
}
|
|
2902
|
-
function
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
4565
|
+
function buildAnchorCenteredTextAnnotation(input) {
|
|
4566
|
+
return buildCenteredTextAnnotation({
|
|
4567
|
+
ownerId: input.ownerId,
|
|
4568
|
+
surfaceKind: input.surfaceKind,
|
|
4569
|
+
...input.surfaceIndex === void 0 ? {} : { surfaceIndex: input.surfaceIndex },
|
|
4570
|
+
layout: input.layout,
|
|
4571
|
+
center: {
|
|
4572
|
+
x: input.anchor.x + input.anchor.width / 2,
|
|
4573
|
+
y: input.anchor.y + input.anchor.height / 2
|
|
4574
|
+
},
|
|
4575
|
+
anchor: input.anchor
|
|
4576
|
+
});
|
|
2906
4577
|
}
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
4578
|
+
function buildCenteredTextAnnotation(input) {
|
|
4579
|
+
return {
|
|
4580
|
+
text: input.layout.text,
|
|
4581
|
+
ownerId: input.ownerId,
|
|
4582
|
+
surfaceKind: input.surfaceKind,
|
|
4583
|
+
...input.surfaceIndex === void 0 ? {} : { surfaceIndex: input.surfaceIndex },
|
|
4584
|
+
box: {
|
|
4585
|
+
x: input.center.x - input.layout.box.width / 2,
|
|
4586
|
+
y: input.center.y - input.layout.box.height / 2,
|
|
4587
|
+
width: input.layout.box.width,
|
|
4588
|
+
height: input.layout.box.height
|
|
4589
|
+
},
|
|
4590
|
+
anchor: input.anchor ?? input.center,
|
|
4591
|
+
paddings: input.layout.padding,
|
|
4592
|
+
lines: input.layout.lines,
|
|
4593
|
+
fontSize: input.layout.font.fontSize,
|
|
4594
|
+
textBackend: input.layout.textBackend
|
|
4595
|
+
};
|
|
4596
|
+
}
|
|
4597
|
+
function reportTextAnnotationCollisions(annotations) {
|
|
4598
|
+
const diagnostics = [];
|
|
4599
|
+
const relevantAnnotations = annotations.filter(
|
|
4600
|
+
(annotation) => isExternallyPlacedText(annotation.surfaceKind)
|
|
4601
|
+
);
|
|
4602
|
+
for (let annotationIndex = 0; annotationIndex < relevantAnnotations.length; annotationIndex += 1) {
|
|
4603
|
+
const annotation = relevantAnnotations[annotationIndex];
|
|
4604
|
+
if (annotation === void 0) {
|
|
4605
|
+
continue;
|
|
4606
|
+
}
|
|
4607
|
+
for (let otherIndex = annotationIndex + 1; otherIndex < relevantAnnotations.length; otherIndex += 1) {
|
|
4608
|
+
const other = relevantAnnotations[otherIndex];
|
|
4609
|
+
if (other === void 0) {
|
|
4610
|
+
continue;
|
|
4611
|
+
}
|
|
4612
|
+
if (!intersectsAabb(annotation.box, other.box)) {
|
|
4613
|
+
continue;
|
|
4614
|
+
}
|
|
4615
|
+
if (annotation.ownerId === other.ownerId && annotation.surfaceKind === other.surfaceKind) {
|
|
4616
|
+
continue;
|
|
4617
|
+
}
|
|
4618
|
+
diagnostics.push({
|
|
4619
|
+
severity: "warning",
|
|
4620
|
+
code: "constraints.overlap.unresolved",
|
|
4621
|
+
message: `Text surface ${annotation.surfaceKind} for ${annotation.ownerId} overlaps text surface ${other.surfaceKind} for ${other.ownerId}.`,
|
|
4622
|
+
path: ["textAnnotations", annotation.surfaceKind, annotation.ownerId],
|
|
4623
|
+
detail: compactDetail({
|
|
4624
|
+
textSurfaceKind: annotation.surfaceKind,
|
|
4625
|
+
ownerId: annotation.ownerId,
|
|
4626
|
+
conflictingObjectId: other.ownerId,
|
|
4627
|
+
conflictingObjectKind: other.surfaceKind,
|
|
4628
|
+
surfaceIndex: annotation.surfaceIndex,
|
|
4629
|
+
otherSurfaceKind: other.surfaceKind,
|
|
4630
|
+
otherSurfaceIndex: other.surfaceIndex,
|
|
4631
|
+
textBackend: annotation.textBackend
|
|
4632
|
+
})
|
|
4633
|
+
});
|
|
4634
|
+
}
|
|
2911
4635
|
}
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
4636
|
+
return diagnostics;
|
|
4637
|
+
}
|
|
4638
|
+
function reportRouteTextClearance(edges, annotations) {
|
|
4639
|
+
const diagnostics = [];
|
|
4640
|
+
const relevantAnnotations = annotations.filter(isRouteClearanceText);
|
|
4641
|
+
for (const edge of edges) {
|
|
4642
|
+
const connectedTextOwners = edgeConnectedTextOwnerIds(edge);
|
|
4643
|
+
for (const annotation of relevantAnnotations) {
|
|
4644
|
+
if (annotation.ownerId === edge.id || connectedTextOwners.has(annotation.ownerId)) {
|
|
4645
|
+
continue;
|
|
2919
4646
|
}
|
|
2920
|
-
|
|
2921
|
-
|
|
4647
|
+
if (!routeIntersectsTextBox(edge.points, annotation.box)) {
|
|
4648
|
+
continue;
|
|
2922
4649
|
}
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
4650
|
+
diagnostics.push({
|
|
4651
|
+
severity: "warning",
|
|
4652
|
+
code: "routing.text-clearance.unresolved",
|
|
4653
|
+
message: `Edge ${edge.id} intersects solved text surface ${annotation.surfaceKind} for ${annotation.ownerId}.`,
|
|
4654
|
+
path: ["edges", edge.id],
|
|
4655
|
+
detail: compactDetail({
|
|
4656
|
+
edgeId: edge.id,
|
|
4657
|
+
textSurfaceKind: annotation.surfaceKind,
|
|
4658
|
+
conflictingObjectId: annotation.ownerId,
|
|
4659
|
+
surfaceIndex: annotation.surfaceIndex,
|
|
4660
|
+
textBackend: annotation.textBackend
|
|
4661
|
+
})
|
|
4662
|
+
});
|
|
4663
|
+
}
|
|
4664
|
+
}
|
|
4665
|
+
return diagnostics;
|
|
4666
|
+
}
|
|
4667
|
+
function isPreRouteTextObstacle(annotation) {
|
|
4668
|
+
if (annotation.surfaceKind === "edge-label") {
|
|
2927
4669
|
return false;
|
|
2928
4670
|
}
|
|
4671
|
+
return isRouteClearanceText(annotation);
|
|
2929
4672
|
}
|
|
2930
|
-
function
|
|
2931
|
-
|
|
4673
|
+
function isRouteClearanceText(annotation) {
|
|
4674
|
+
switch (annotation.surfaceKind) {
|
|
4675
|
+
case "port-label":
|
|
4676
|
+
case "edge-label":
|
|
4677
|
+
case "swimlane-label":
|
|
4678
|
+
case "frame-title":
|
|
4679
|
+
return true;
|
|
4680
|
+
case "node-label":
|
|
4681
|
+
case "group-label":
|
|
4682
|
+
case "compartment-row":
|
|
4683
|
+
return textExtendsOutsideAnchor(annotation);
|
|
4684
|
+
}
|
|
2932
4685
|
}
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
4686
|
+
function textExtendsOutsideAnchor(annotation) {
|
|
4687
|
+
if (!("width" in annotation.anchor)) {
|
|
4688
|
+
return true;
|
|
4689
|
+
}
|
|
4690
|
+
const epsilon = 1e-3;
|
|
4691
|
+
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;
|
|
2936
4692
|
}
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
4693
|
+
function routeIntersectsTextBox(points, box) {
|
|
4694
|
+
for (let index = 0; index < points.length - 1; index += 1) {
|
|
4695
|
+
const start = points[index];
|
|
4696
|
+
const end = points[index + 1];
|
|
4697
|
+
if (start === void 0 || end === void 0) {
|
|
4698
|
+
continue;
|
|
2941
4699
|
}
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
const options = {
|
|
2945
|
-
...style2.whiteSpace === void 0 ? {} : { whiteSpace: style2.whiteSpace },
|
|
2946
|
-
...style2.wordBreak === void 0 ? {} : { wordBreak: style2.wordBreak },
|
|
2947
|
-
...style2.letterSpacing === void 0 ? {} : { letterSpacing: style2.letterSpacing }
|
|
2948
|
-
};
|
|
2949
|
-
const prepared = pretext.prepareWithSegments(text, font, options);
|
|
2950
|
-
return {
|
|
2951
|
-
text,
|
|
2952
|
-
font,
|
|
2953
|
-
style: { ...style2 },
|
|
2954
|
-
backend: "pretext",
|
|
2955
|
-
pretextPrepared: prepared
|
|
2956
|
-
};
|
|
2957
|
-
}
|
|
2958
|
-
layout(prepared, maxWidth, lineHeight = resolveLineHeight(prepared.style)) {
|
|
2959
|
-
assertFiniteNonNegative(maxWidth, "maxWidth");
|
|
2960
|
-
if (!Number.isFinite(lineHeight) || lineHeight <= 0) {
|
|
2961
|
-
throw new TypeError("lineHeight must be finite and positive");
|
|
4700
|
+
if (segmentIntersectsBox2(start, end, box)) {
|
|
4701
|
+
return true;
|
|
2962
4702
|
}
|
|
2963
|
-
const result = pretext.layoutWithLines(
|
|
2964
|
-
toInternalPrepared(prepared),
|
|
2965
|
-
maxWidth,
|
|
2966
|
-
lineHeight
|
|
2967
|
-
);
|
|
2968
|
-
const width = result.lines.reduce(
|
|
2969
|
-
(current, line) => Math.max(current, line.width),
|
|
2970
|
-
0
|
|
2971
|
-
);
|
|
2972
|
-
return {
|
|
2973
|
-
width,
|
|
2974
|
-
height: result.height,
|
|
2975
|
-
lineHeight,
|
|
2976
|
-
lineCount: result.lineCount,
|
|
2977
|
-
lines: result.lines.map((line) => ({
|
|
2978
|
-
text: line.text,
|
|
2979
|
-
width: line.width,
|
|
2980
|
-
start: {
|
|
2981
|
-
segmentIndex: line.start.segmentIndex,
|
|
2982
|
-
graphemeIndex: line.start.graphemeIndex
|
|
2983
|
-
},
|
|
2984
|
-
end: {
|
|
2985
|
-
segmentIndex: line.end.segmentIndex,
|
|
2986
|
-
graphemeIndex: line.end.graphemeIndex
|
|
2987
|
-
}
|
|
2988
|
-
})),
|
|
2989
|
-
diagnostics: []
|
|
2990
|
-
};
|
|
2991
4703
|
}
|
|
2992
|
-
|
|
2993
|
-
|
|
4704
|
+
return false;
|
|
4705
|
+
}
|
|
4706
|
+
function segmentIntersectsBox2(start, end, box) {
|
|
4707
|
+
const left = box.x;
|
|
4708
|
+
const right = box.x + box.width;
|
|
4709
|
+
const top = box.y;
|
|
4710
|
+
const bottom = box.y + box.height;
|
|
4711
|
+
if (pointInsideBox2(start, box) || pointInsideBox2(end, box)) {
|
|
4712
|
+
return true;
|
|
2994
4713
|
}
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
if (prepared.backend !== "pretext" || !("pretextPrepared" in prepared)) {
|
|
2998
|
-
throw new TypeError("prepared text was not created by PretextTextMeasurer");
|
|
4714
|
+
if (start.x === end.x) {
|
|
4715
|
+
return start.x > left && start.x < right && rangesOverlap2(start.y, end.y, top, bottom);
|
|
2999
4716
|
}
|
|
3000
|
-
|
|
4717
|
+
if (start.y === end.y) {
|
|
4718
|
+
return start.y > top && start.y < bottom && rangesOverlap2(start.x, end.x, left, right);
|
|
4719
|
+
}
|
|
4720
|
+
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);
|
|
3001
4721
|
}
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
function createDefaultTextMeasurer(options = {}) {
|
|
3005
|
-
const installRuntime = options.installNodeCanvasRuntime ?? installNodeCanvasRuntime;
|
|
3006
|
-
installRuntime();
|
|
3007
|
-
return isPretextRuntimeAvailable() ? new PretextTextMeasurer() : new DeterministicTextMeasurer();
|
|
4722
|
+
function pointInsideBox2(point2, box) {
|
|
4723
|
+
return point2.x > box.x && point2.x < box.x + box.width && point2.y > box.y && point2.y < box.y + box.height;
|
|
3008
4724
|
}
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
return
|
|
4725
|
+
function rangesOverlap2(a, b, min, max) {
|
|
4726
|
+
const low = Math.min(a, b);
|
|
4727
|
+
const high = Math.max(a, b);
|
|
4728
|
+
return high > min && low < max;
|
|
3013
4729
|
}
|
|
3014
|
-
function
|
|
3015
|
-
const
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
const
|
|
3020
|
-
const
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
textLayout.width,
|
|
3029
|
-
minContentWidth(minSize, padding)
|
|
3030
|
-
);
|
|
3031
|
-
const contentHeight = Math.max(
|
|
3032
|
-
textLayout.height,
|
|
3033
|
-
minContentHeight(minSize, padding)
|
|
4730
|
+
function segmentIntersectsBoxEdge2(start, end, x1, y1, x2, y2) {
|
|
4731
|
+
const denominator = (end.x - start.x) * (y2 - y1) - (end.y - start.y) * (x2 - x1);
|
|
4732
|
+
if (denominator === 0) {
|
|
4733
|
+
return false;
|
|
4734
|
+
}
|
|
4735
|
+
const t = ((x1 - start.x) * (y2 - y1) - (y1 - start.y) * (x2 - x1)) / denominator;
|
|
4736
|
+
const u = ((x1 - start.x) * (end.y - start.y) - (y1 - start.y) * (end.x - start.x)) / denominator;
|
|
4737
|
+
return t > 0 && t < 1 && u > 0 && u < 1;
|
|
4738
|
+
}
|
|
4739
|
+
function compactDetail(detail) {
|
|
4740
|
+
return Object.fromEntries(
|
|
4741
|
+
Object.entries(detail).filter(
|
|
4742
|
+
(entry) => entry[1] !== void 0
|
|
4743
|
+
)
|
|
3034
4744
|
);
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
const
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
4745
|
+
}
|
|
4746
|
+
function isExternallyPlacedText(surfaceKind) {
|
|
4747
|
+
switch (surfaceKind) {
|
|
4748
|
+
case "port-label":
|
|
4749
|
+
return true;
|
|
4750
|
+
case "edge-label":
|
|
4751
|
+
return false;
|
|
4752
|
+
case "swimlane-label":
|
|
4753
|
+
return true;
|
|
4754
|
+
case "frame-title":
|
|
4755
|
+
return true;
|
|
4756
|
+
case "node-label":
|
|
4757
|
+
case "group-label":
|
|
4758
|
+
case "compartment-row":
|
|
4759
|
+
return false;
|
|
4760
|
+
}
|
|
4761
|
+
}
|
|
4762
|
+
function fallbackLabelLayout(text) {
|
|
4763
|
+
const width = Math.max(0, text.length * 7);
|
|
4764
|
+
return {
|
|
4765
|
+
text,
|
|
4766
|
+
box: { x: 0, y: 0, width, height: 14 },
|
|
4767
|
+
contentBox: { x: 0, y: 0, width, height: 14 },
|
|
4768
|
+
naturalSize: { width, height: 14 },
|
|
4769
|
+
fittedSize: { width, height: 14 },
|
|
4770
|
+
padding: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
4771
|
+
font: { fontFamily: "Arial", fontSize: 12, lineHeight: 14 },
|
|
4772
|
+
lineHeight: 14,
|
|
4773
|
+
lines: [
|
|
4774
|
+
{
|
|
4775
|
+
text,
|
|
4776
|
+
box: { x: 0, y: 0, width, height: 14 },
|
|
4777
|
+
baselineY: 11.2,
|
|
4778
|
+
width,
|
|
4779
|
+
lineIndex: 0
|
|
4780
|
+
}
|
|
4781
|
+
],
|
|
4782
|
+
overflow: { horizontal: false, vertical: false, truncated: false },
|
|
4783
|
+
diagnostics: []
|
|
3072
4784
|
};
|
|
3073
4785
|
}
|
|
3074
|
-
function
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
4786
|
+
function edgeLabelAnchor(edge, layout2, edges) {
|
|
4787
|
+
const placement = labelPlacementOnPolyline2(edge.points);
|
|
4788
|
+
if (placement === void 0) {
|
|
4789
|
+
return { x: 0, y: 0 };
|
|
4790
|
+
}
|
|
4791
|
+
for (const candidate of edgeLabelAnchorCandidates(edge.points, placement)) {
|
|
4792
|
+
const labelBox = {
|
|
4793
|
+
x: candidate.x - layout2.box.width / 2,
|
|
4794
|
+
y: candidate.y - layout2.box.height / 2,
|
|
4795
|
+
width: layout2.box.width,
|
|
4796
|
+
height: layout2.box.height
|
|
4797
|
+
};
|
|
4798
|
+
if (routeIntersectsTextBox(edge.points, labelBox)) {
|
|
4799
|
+
continue;
|
|
4800
|
+
}
|
|
4801
|
+
const crossesOtherRoute = edges.some(
|
|
4802
|
+
(other) => other.id !== edge.id && routeIntersectsTextBox(other.points, labelBox)
|
|
4803
|
+
);
|
|
4804
|
+
if (!crossesOtherRoute) {
|
|
4805
|
+
return candidate;
|
|
4806
|
+
}
|
|
4807
|
+
}
|
|
4808
|
+
return placement;
|
|
3089
4809
|
}
|
|
3090
|
-
function
|
|
3091
|
-
|
|
3092
|
-
|
|
4810
|
+
function edgeLabelAnchorCandidates(points, placement) {
|
|
4811
|
+
const segment = labelSegmentOnPolyline(points);
|
|
4812
|
+
if (segment === void 0) {
|
|
4813
|
+
return [placement];
|
|
3093
4814
|
}
|
|
3094
|
-
if (
|
|
3095
|
-
|
|
4815
|
+
if (segment.start.y === segment.end.y) {
|
|
4816
|
+
return [
|
|
4817
|
+
placement,
|
|
4818
|
+
{ x: placement.x, y: placement.y - EDGE_LABEL_CLEARANCE },
|
|
4819
|
+
{ x: placement.x, y: placement.y + EDGE_LABEL_CLEARANCE },
|
|
4820
|
+
{ x: placement.x, y: placement.y - EDGE_LABEL_CLEARANCE * 2 },
|
|
4821
|
+
{ x: placement.x, y: placement.y + EDGE_LABEL_CLEARANCE * 2 }
|
|
4822
|
+
];
|
|
3096
4823
|
}
|
|
3097
|
-
|
|
4824
|
+
if (segment.start.x === segment.end.x) {
|
|
4825
|
+
return [
|
|
4826
|
+
placement,
|
|
4827
|
+
{ x: placement.x + EDGE_LABEL_CLEARANCE, y: placement.y },
|
|
4828
|
+
{ x: placement.x - EDGE_LABEL_CLEARANCE, y: placement.y },
|
|
4829
|
+
{ x: placement.x + EDGE_LABEL_CLEARANCE * 2, y: placement.y },
|
|
4830
|
+
{ x: placement.x - EDGE_LABEL_CLEARANCE * 2, y: placement.y }
|
|
4831
|
+
];
|
|
4832
|
+
}
|
|
4833
|
+
return [placement];
|
|
3098
4834
|
}
|
|
3099
|
-
function
|
|
3100
|
-
|
|
4835
|
+
function labelPlacementOnPolyline2(points) {
|
|
4836
|
+
return labelSegmentOnPolyline(points)?.placement;
|
|
4837
|
+
}
|
|
4838
|
+
function labelSegmentOnPolyline(points) {
|
|
4839
|
+
const segments = nonZeroSegments2(points);
|
|
4840
|
+
const totalLength = segments.reduce(
|
|
4841
|
+
(sum, segment) => sum + segment.length,
|
|
4842
|
+
0
|
|
4843
|
+
);
|
|
4844
|
+
if (totalLength <= 0) {
|
|
3101
4845
|
return void 0;
|
|
3102
4846
|
}
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
4847
|
+
let remaining = totalLength / 2;
|
|
4848
|
+
for (const segment of segments) {
|
|
4849
|
+
if (remaining <= segment.length) {
|
|
4850
|
+
const ratio = remaining / segment.length;
|
|
4851
|
+
const x = segment.start.x + (segment.end.x - segment.start.x) * ratio;
|
|
4852
|
+
const y = segment.start.y + (segment.end.y - segment.start.y) * ratio;
|
|
4853
|
+
const offset2 = labelOffset2(segment);
|
|
4854
|
+
return {
|
|
4855
|
+
start: segment.start,
|
|
4856
|
+
end: segment.end,
|
|
4857
|
+
placement: { x: x + offset2.x, y: y + offset2.y }
|
|
4858
|
+
};
|
|
4859
|
+
}
|
|
4860
|
+
remaining -= segment.length;
|
|
4861
|
+
}
|
|
4862
|
+
const last = segments.at(-1);
|
|
4863
|
+
if (last === void 0) {
|
|
4864
|
+
return void 0;
|
|
4865
|
+
}
|
|
4866
|
+
const offset = labelOffset2(last);
|
|
4867
|
+
return {
|
|
4868
|
+
start: last.start,
|
|
4869
|
+
end: last.end,
|
|
4870
|
+
placement: { x: last.end.x + offset.x, y: last.end.y + offset.y }
|
|
4871
|
+
};
|
|
3108
4872
|
}
|
|
3109
|
-
function
|
|
3110
|
-
|
|
4873
|
+
function nonZeroSegments2(points) {
|
|
4874
|
+
const segments = [];
|
|
4875
|
+
for (let index = 0; index < points.length - 1; index += 1) {
|
|
4876
|
+
const start = points[index];
|
|
4877
|
+
const end = points[index + 1];
|
|
4878
|
+
if (start === void 0 || end === void 0) {
|
|
4879
|
+
continue;
|
|
4880
|
+
}
|
|
4881
|
+
const length = Math.hypot(end.x - start.x, end.y - start.y);
|
|
4882
|
+
if (length > 0) {
|
|
4883
|
+
segments.push({ start, end, length });
|
|
4884
|
+
}
|
|
4885
|
+
}
|
|
4886
|
+
return segments;
|
|
3111
4887
|
}
|
|
3112
|
-
function
|
|
3113
|
-
|
|
4888
|
+
function labelOffset2(segment) {
|
|
4889
|
+
const offset = 10;
|
|
4890
|
+
const dx = segment.end.x - segment.start.x;
|
|
4891
|
+
const dy = segment.end.y - segment.start.y;
|
|
4892
|
+
return {
|
|
4893
|
+
x: -dy / segment.length * offset,
|
|
4894
|
+
y: dx / segment.length * offset
|
|
4895
|
+
};
|
|
3114
4896
|
}
|
|
3115
|
-
function
|
|
3116
|
-
|
|
4897
|
+
function compartmentRows(node) {
|
|
4898
|
+
const compartments2 = node.compartments;
|
|
4899
|
+
if (compartments2 === void 0) {
|
|
3117
4900
|
return [];
|
|
3118
4901
|
}
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
if (
|
|
3128
|
-
|
|
3129
|
-
severity: "warning",
|
|
3130
|
-
code: "label.overflow.vertical",
|
|
3131
|
-
message: "Label text exceeds the fitted content height."
|
|
3132
|
-
});
|
|
4902
|
+
return [
|
|
4903
|
+
...compartments2.stereotype === void 0 ? [] : [compartments2.stereotype],
|
|
4904
|
+
...compartments2.name === void 0 ? [node.label?.text ?? node.id] : [compartments2.name],
|
|
4905
|
+
...compartments2.properties ?? [],
|
|
4906
|
+
...compartments2.constraints ?? []
|
|
4907
|
+
];
|
|
4908
|
+
}
|
|
4909
|
+
function portGeometry(nodeGeometry, port) {
|
|
4910
|
+
if (port === void 0) {
|
|
4911
|
+
return nodeGeometry;
|
|
3133
4912
|
}
|
|
3134
|
-
return
|
|
4913
|
+
return {
|
|
4914
|
+
...nodeGeometry,
|
|
4915
|
+
box: port.box,
|
|
4916
|
+
center: port.anchor,
|
|
4917
|
+
anchors: nodeGeometry.anchors.map((anchor) => ({
|
|
4918
|
+
name: anchor.name,
|
|
4919
|
+
point: port.anchor
|
|
4920
|
+
})),
|
|
4921
|
+
obstacleBox: port.box
|
|
4922
|
+
};
|
|
4923
|
+
}
|
|
4924
|
+
function stableById(items) {
|
|
4925
|
+
return [...items].sort((a, b) => a.id.localeCompare(b.id));
|
|
4926
|
+
}
|
|
4927
|
+
function stableByConstraintId(items) {
|
|
4928
|
+
return [...items].sort(
|
|
4929
|
+
(a, b) => `${a.id ?? a.kind}`.localeCompare(`${b.id ?? b.kind}`)
|
|
4930
|
+
);
|
|
4931
|
+
}
|
|
4932
|
+
function groupReferenceMissing(groupId, referenceKind, id) {
|
|
4933
|
+
return {
|
|
4934
|
+
severity: "error",
|
|
4935
|
+
code: "solver.group-reference.missing",
|
|
4936
|
+
message: `Group ${groupId} references a missing ${referenceKind}.`,
|
|
4937
|
+
path: ["groups", groupId],
|
|
4938
|
+
detail: id === void 0 ? { groupId } : { groupId, id }
|
|
4939
|
+
};
|
|
3135
4940
|
}
|
|
3136
4941
|
|
|
3137
4942
|
// src/dsl/normalize.ts
|
|
@@ -3150,6 +4955,9 @@ var DEFAULT_GROUP_PADDING = {
|
|
|
3150
4955
|
};
|
|
3151
4956
|
var DEFAULT_LABEL_MAX_WIDTH = 160;
|
|
3152
4957
|
var DEFAULT_FONT = { fontFamily: "Arial", fontSize: 14, lineHeight: 18 };
|
|
4958
|
+
var DEFAULT_MATRIX_CELL_SIZE2 = { width: 120, height: 36 };
|
|
4959
|
+
var DEFAULT_TABLE_CELL_SIZE2 = { width: 128, height: 34 };
|
|
4960
|
+
var DEFAULT_PANEL_ITEM_HEIGHT2 = 28;
|
|
3153
4961
|
function normalizeDiagramDsl(dslValue, options = {}) {
|
|
3154
4962
|
const dsl = dslValue;
|
|
3155
4963
|
const diagnostics = validateReferences(dsl);
|
|
@@ -3163,6 +4971,9 @@ function normalizeDiagramDsl(dslValue, options = {}) {
|
|
|
3163
4971
|
const routeKind = dsl.routing?.kind ?? "orthogonal";
|
|
3164
4972
|
const portShifting = normalizePortShifting(dsl.routing?.portShifting);
|
|
3165
4973
|
const primaryReadingDirection = dsl.layout?.primaryReadingDirection;
|
|
4974
|
+
const matrices = normalizeMatrices(dsl);
|
|
4975
|
+
const tables = normalizeTables(dsl);
|
|
4976
|
+
const evidencePanels = normalizeEvidencePanels(dsl);
|
|
3166
4977
|
const diagram = {
|
|
3167
4978
|
id: options.id ?? dsl.id ?? "diagram",
|
|
3168
4979
|
...dsl.title === void 0 ? {} : { title: dsl.title },
|
|
@@ -3171,6 +4982,9 @@ function normalizeDiagramDsl(dslValue, options = {}) {
|
|
|
3171
4982
|
edges: normalizeEdges(dsl),
|
|
3172
4983
|
groups: normalizeGroups(dsl, measurer),
|
|
3173
4984
|
swimlanes: normalizeSwimlanes(dsl),
|
|
4985
|
+
...matrices === void 0 ? {} : { matrices },
|
|
4986
|
+
...tables === void 0 ? {} : { tables },
|
|
4987
|
+
...evidencePanels === void 0 ? {} : { evidencePanels },
|
|
3174
4988
|
constraints: normalizeConstraints(dsl),
|
|
3175
4989
|
diagnostics: [],
|
|
3176
4990
|
...dsl.frame === void 0 ? {} : { frame: normalizeFrame(dsl.frame) },
|
|
@@ -3240,7 +5054,7 @@ function compartmentHeight(value) {
|
|
|
3240
5054
|
);
|
|
3241
5055
|
}
|
|
3242
5056
|
function compartmentNaturalWidth(id, label, value, measurer) {
|
|
3243
|
-
const rows =
|
|
5057
|
+
const rows = compartmentRows2(id, label, value);
|
|
3244
5058
|
const maxRowWidth = rows.reduce((width, row) => {
|
|
3245
5059
|
const prepared = measurer.prepare(row, DEFAULT_FONT);
|
|
3246
5060
|
return Math.max(width, measurer.naturalWidth(prepared));
|
|
@@ -3249,7 +5063,7 @@ function compartmentNaturalWidth(id, label, value, measurer) {
|
|
|
3249
5063
|
maxRowWidth + DEFAULT_NODE_PADDING.left + DEFAULT_NODE_PADDING.right
|
|
3250
5064
|
);
|
|
3251
5065
|
}
|
|
3252
|
-
function
|
|
5066
|
+
function compartmentRows2(id, label, value) {
|
|
3253
5067
|
return [
|
|
3254
5068
|
...value.stereotype === void 0 ? [] : [value.stereotype],
|
|
3255
5069
|
value.name ?? label?.text ?? id,
|
|
@@ -3369,6 +5183,102 @@ function normalizeSwimlanes(dsl) {
|
|
|
3369
5183
|
};
|
|
3370
5184
|
});
|
|
3371
5185
|
}
|
|
5186
|
+
function normalizeMatrices(dsl) {
|
|
5187
|
+
if (dsl.matrices === void 0) {
|
|
5188
|
+
return void 0;
|
|
5189
|
+
}
|
|
5190
|
+
return dsl.matrices.map((matrix) => ({
|
|
5191
|
+
id: matrix.id,
|
|
5192
|
+
rows: [...matrix.rows],
|
|
5193
|
+
cols: [...matrix.cols],
|
|
5194
|
+
cells: matrix.cells.map((row) => row.map(cell)),
|
|
5195
|
+
...matrix.position === void 0 ? {} : { position: point(matrix.position) },
|
|
5196
|
+
size: matrix.size ?? {
|
|
5197
|
+
width: defaultMatrixRowHeaderWidth2(matrix) + Math.max(1, matrix.cols.length) * DEFAULT_MATRIX_CELL_SIZE2.width,
|
|
5198
|
+
height: Math.max(1, matrix.rows.length + 1) * DEFAULT_MATRIX_CELL_SIZE2.height
|
|
5199
|
+
},
|
|
5200
|
+
...matrix.style === void 0 ? {} : { style: style(matrix.style) }
|
|
5201
|
+
}));
|
|
5202
|
+
}
|
|
5203
|
+
function defaultMatrixRowHeaderWidth2(matrix) {
|
|
5204
|
+
return matrix.rows.length === 0 ? 0 : Math.min(96, DEFAULT_MATRIX_CELL_SIZE2.width);
|
|
5205
|
+
}
|
|
5206
|
+
function normalizeTables(dsl) {
|
|
5207
|
+
if (dsl.tables === void 0) {
|
|
5208
|
+
return void 0;
|
|
5209
|
+
}
|
|
5210
|
+
return dsl.tables.map((table) => ({
|
|
5211
|
+
id: table.id,
|
|
5212
|
+
columns: table.columns.map(tableColumn),
|
|
5213
|
+
rows: table.rows.map(tableRow),
|
|
5214
|
+
...table.position === void 0 ? {} : { position: point(table.position) },
|
|
5215
|
+
size: table.size ?? {
|
|
5216
|
+
width: Math.max(1, table.columns.length) * DEFAULT_TABLE_CELL_SIZE2.width,
|
|
5217
|
+
height: Math.max(1, table.rows.length + 1) * DEFAULT_TABLE_CELL_SIZE2.height
|
|
5218
|
+
},
|
|
5219
|
+
...table.style === void 0 ? {} : { style: style(table.style) }
|
|
5220
|
+
}));
|
|
5221
|
+
}
|
|
5222
|
+
function normalizeEvidencePanels(dsl) {
|
|
5223
|
+
if (dsl.evidencePanels === void 0) {
|
|
5224
|
+
return void 0;
|
|
5225
|
+
}
|
|
5226
|
+
return dsl.evidencePanels.map((panel) => ({
|
|
5227
|
+
id: panel.id,
|
|
5228
|
+
kind: panel.kind,
|
|
5229
|
+
items: panel.items.map(panelItem),
|
|
5230
|
+
...panel.position === void 0 ? {} : { position: point(panel.position) },
|
|
5231
|
+
size: panel.size ?? {
|
|
5232
|
+
width: 320,
|
|
5233
|
+
height: Math.max(1, panel.items.length) * DEFAULT_PANEL_ITEM_HEIGHT2
|
|
5234
|
+
},
|
|
5235
|
+
...panel.style === void 0 ? {} : { style: style(panel.style) }
|
|
5236
|
+
}));
|
|
5237
|
+
}
|
|
5238
|
+
function cell(value) {
|
|
5239
|
+
if (typeof value === "string") {
|
|
5240
|
+
return { text: value };
|
|
5241
|
+
}
|
|
5242
|
+
return {
|
|
5243
|
+
text: value.text,
|
|
5244
|
+
...value.style === void 0 && value.fill === void 0 && value.stroke === void 0 ? {} : {
|
|
5245
|
+
style: style({
|
|
5246
|
+
...value.style,
|
|
5247
|
+
...value.fill === void 0 ? {} : { fill: value.fill },
|
|
5248
|
+
...value.stroke === void 0 ? {} : { stroke: value.stroke }
|
|
5249
|
+
})
|
|
5250
|
+
}
|
|
5251
|
+
};
|
|
5252
|
+
}
|
|
5253
|
+
function tableColumn(value) {
|
|
5254
|
+
return {
|
|
5255
|
+
id: value.id,
|
|
5256
|
+
label: toLabel(value.label) ?? { text: value.id }
|
|
5257
|
+
};
|
|
5258
|
+
}
|
|
5259
|
+
function tableRow(value) {
|
|
5260
|
+
return {
|
|
5261
|
+
id: value.id,
|
|
5262
|
+
cells: Object.fromEntries(
|
|
5263
|
+
Object.keys(value.cells).map((columnId) => [
|
|
5264
|
+
columnId,
|
|
5265
|
+
cell(value.cells[columnId] ?? "")
|
|
5266
|
+
])
|
|
5267
|
+
)
|
|
5268
|
+
};
|
|
5269
|
+
}
|
|
5270
|
+
function panelItem(value) {
|
|
5271
|
+
if (typeof value === "string") {
|
|
5272
|
+
return { label: { text: value } };
|
|
5273
|
+
}
|
|
5274
|
+
const detail = toLabel(value.detail);
|
|
5275
|
+
return {
|
|
5276
|
+
...value.id === void 0 ? {} : { id: value.id },
|
|
5277
|
+
label: toLabel(value.label) ?? { text: "" },
|
|
5278
|
+
...detail === void 0 ? {} : { detail },
|
|
5279
|
+
...value.style === void 0 ? {} : { style: style(value.style) }
|
|
5280
|
+
};
|
|
5281
|
+
}
|
|
3372
5282
|
function normalizeGroups(dsl, measurer) {
|
|
3373
5283
|
return Object.keys(dsl.groups ?? {}).sort().map((id) => {
|
|
3374
5284
|
const group = dsl.groups?.[id];
|
|
@@ -3701,6 +5611,15 @@ var styleSchema = zod.z.object({
|
|
|
3701
5611
|
fill: zod.z.string().optional(),
|
|
3702
5612
|
stroke: zod.z.string().optional()
|
|
3703
5613
|
});
|
|
5614
|
+
var blockCellSchema = zod.z.union([
|
|
5615
|
+
zod.z.string(),
|
|
5616
|
+
zod.z.object({
|
|
5617
|
+
text: zod.z.string(),
|
|
5618
|
+
fill: zod.z.string().optional(),
|
|
5619
|
+
stroke: zod.z.string().optional(),
|
|
5620
|
+
style: styleSchema.optional()
|
|
5621
|
+
})
|
|
5622
|
+
]);
|
|
3704
5623
|
var portSideSchema = zod.z.enum(["top", "right", "bottom", "left"]);
|
|
3705
5624
|
var portKindSchema = zod.z.enum(["proxy", "flow"]);
|
|
3706
5625
|
var portSchema = zod.z.object({
|
|
@@ -3777,6 +5696,122 @@ var swimlaneSchema = zod.z.object({
|
|
|
3777
5696
|
})
|
|
3778
5697
|
)
|
|
3779
5698
|
});
|
|
5699
|
+
var matrixSchema = zod.z.object({
|
|
5700
|
+
id: zod.z.string(),
|
|
5701
|
+
rows: zod.z.array(zod.z.string()),
|
|
5702
|
+
cols: zod.z.array(zod.z.string()),
|
|
5703
|
+
cells: zod.z.array(zod.z.array(blockCellSchema)),
|
|
5704
|
+
position: pointSchema.optional(),
|
|
5705
|
+
size: zod.z.object({
|
|
5706
|
+
width: nonNegativeNumberSchema,
|
|
5707
|
+
height: nonNegativeNumberSchema
|
|
5708
|
+
}).optional(),
|
|
5709
|
+
style: styleSchema.optional()
|
|
5710
|
+
}).superRefine((matrix, context) => {
|
|
5711
|
+
checkDuplicateValues("matrix row", matrix.rows, context, (index) => [
|
|
5712
|
+
"rows",
|
|
5713
|
+
index
|
|
5714
|
+
]);
|
|
5715
|
+
checkDuplicateValues("matrix column", matrix.cols, context, (index) => [
|
|
5716
|
+
"cols",
|
|
5717
|
+
index
|
|
5718
|
+
]);
|
|
5719
|
+
if (matrix.cells.length !== matrix.rows.length) {
|
|
5720
|
+
context.addIssue({
|
|
5721
|
+
code: "custom",
|
|
5722
|
+
message: `Matrix cells must contain exactly ${matrix.rows.length} row(s).`,
|
|
5723
|
+
path: ["cells"]
|
|
5724
|
+
});
|
|
5725
|
+
}
|
|
5726
|
+
matrix.cells.forEach((row, rowIndex) => {
|
|
5727
|
+
if (row.length !== matrix.cols.length) {
|
|
5728
|
+
context.addIssue({
|
|
5729
|
+
code: "custom",
|
|
5730
|
+
message: `Matrix cell row must contain exactly ${matrix.cols.length} column(s).`,
|
|
5731
|
+
path: ["cells", rowIndex]
|
|
5732
|
+
});
|
|
5733
|
+
}
|
|
5734
|
+
});
|
|
5735
|
+
});
|
|
5736
|
+
var tableColumnSchema = zod.z.object({
|
|
5737
|
+
id: zod.z.string(),
|
|
5738
|
+
label: labelSchema
|
|
5739
|
+
});
|
|
5740
|
+
var tableRowSchema = zod.z.object({
|
|
5741
|
+
id: zod.z.string(),
|
|
5742
|
+
cells: zod.z.record(zod.z.string(), blockCellSchema)
|
|
5743
|
+
});
|
|
5744
|
+
var tableSchema = zod.z.object({
|
|
5745
|
+
id: zod.z.string(),
|
|
5746
|
+
columns: zod.z.array(tableColumnSchema),
|
|
5747
|
+
rows: zod.z.array(tableRowSchema),
|
|
5748
|
+
position: pointSchema.optional(),
|
|
5749
|
+
size: zod.z.object({
|
|
5750
|
+
width: nonNegativeNumberSchema,
|
|
5751
|
+
height: nonNegativeNumberSchema
|
|
5752
|
+
}).optional(),
|
|
5753
|
+
style: styleSchema.optional()
|
|
5754
|
+
}).superRefine((table, context) => {
|
|
5755
|
+
checkDuplicateIds("column", table.columns, context, (index) => [
|
|
5756
|
+
"columns",
|
|
5757
|
+
index,
|
|
5758
|
+
"id"
|
|
5759
|
+
]);
|
|
5760
|
+
checkDuplicateIds("row", table.rows, context, (index) => [
|
|
5761
|
+
"rows",
|
|
5762
|
+
index,
|
|
5763
|
+
"id"
|
|
5764
|
+
]);
|
|
5765
|
+
const columnIds = new Set(table.columns.map((column) => column.id));
|
|
5766
|
+
table.rows.forEach((row, rowIndex) => {
|
|
5767
|
+
for (const columnId of Object.keys(row.cells)) {
|
|
5768
|
+
if (!columnIds.has(columnId)) {
|
|
5769
|
+
context.addIssue({
|
|
5770
|
+
code: "custom",
|
|
5771
|
+
message: `Table row cell references undeclared column "${columnId}".`,
|
|
5772
|
+
path: ["rows", rowIndex, "cells", columnId]
|
|
5773
|
+
});
|
|
5774
|
+
}
|
|
5775
|
+
}
|
|
5776
|
+
});
|
|
5777
|
+
});
|
|
5778
|
+
var panelItemSchema = zod.z.union([
|
|
5779
|
+
zod.z.string(),
|
|
5780
|
+
zod.z.object({
|
|
5781
|
+
id: zod.z.string().optional(),
|
|
5782
|
+
label: labelSchema,
|
|
5783
|
+
detail: labelSchema.optional(),
|
|
5784
|
+
style: styleSchema.optional()
|
|
5785
|
+
})
|
|
5786
|
+
]);
|
|
5787
|
+
var evidencePanelSchema = zod.z.object({
|
|
5788
|
+
id: zod.z.string(),
|
|
5789
|
+
kind: zod.z.enum(["legend", "rule", "note", "verification"]),
|
|
5790
|
+
items: zod.z.array(panelItemSchema),
|
|
5791
|
+
position: pointSchema.optional(),
|
|
5792
|
+
size: zod.z.object({
|
|
5793
|
+
width: nonNegativeNumberSchema,
|
|
5794
|
+
height: nonNegativeNumberSchema
|
|
5795
|
+
}).optional(),
|
|
5796
|
+
style: styleSchema.optional()
|
|
5797
|
+
}).superRefine((panel, context) => {
|
|
5798
|
+
const firstIndexByItemId = /* @__PURE__ */ new Map();
|
|
5799
|
+
panel.items.forEach((item, index) => {
|
|
5800
|
+
if (typeof item === "string" || item.id === void 0) {
|
|
5801
|
+
return;
|
|
5802
|
+
}
|
|
5803
|
+
const firstIndex = firstIndexByItemId.get(item.id);
|
|
5804
|
+
if (firstIndex === void 0) {
|
|
5805
|
+
firstIndexByItemId.set(item.id, index);
|
|
5806
|
+
return;
|
|
5807
|
+
}
|
|
5808
|
+
context.addIssue({
|
|
5809
|
+
code: "custom",
|
|
5810
|
+
message: `Duplicate evidence panel item id "${item.id}".`,
|
|
5811
|
+
path: ["items", index, "id"]
|
|
5812
|
+
});
|
|
5813
|
+
});
|
|
5814
|
+
});
|
|
3780
5815
|
var exactPositionConstraintSchema = zod.z.object({
|
|
3781
5816
|
kind: zod.z.literal("exact-position"),
|
|
3782
5817
|
target: zod.z.string().optional(),
|
|
@@ -3848,6 +5883,9 @@ var diagramDslSchema = zod.z.object({
|
|
|
3848
5883
|
edges: zod.z.array(edgeSchema).optional(),
|
|
3849
5884
|
groups: zod.z.record(zod.z.string(), groupSchema).optional(),
|
|
3850
5885
|
swimlanes: zod.z.record(zod.z.string(), swimlaneSchema).optional(),
|
|
5886
|
+
matrices: zod.z.array(matrixSchema).optional(),
|
|
5887
|
+
tables: zod.z.array(tableSchema).optional(),
|
|
5888
|
+
evidencePanels: zod.z.array(evidencePanelSchema).optional(),
|
|
3851
5889
|
constraints: zod.z.array(constraintSchema).optional(),
|
|
3852
5890
|
frame: zod.z.object({
|
|
3853
5891
|
kind: zod.z.string(),
|
|
@@ -3859,6 +5897,15 @@ var diagramDslSchema = zod.z.object({
|
|
|
3859
5897
|
output: zod.z.object({
|
|
3860
5898
|
format: outputFormatSchema.optional()
|
|
3861
5899
|
}).optional()
|
|
5900
|
+
}).superRefine((diagram, context) => {
|
|
5901
|
+
checkDuplicateEvidenceBlockIds("matrices", diagram.matrices, context);
|
|
5902
|
+
checkDuplicateEvidenceBlockIds("tables", diagram.tables, context);
|
|
5903
|
+
checkDuplicateEvidenceBlockIds(
|
|
5904
|
+
"evidencePanels",
|
|
5905
|
+
diagram.evidencePanels,
|
|
5906
|
+
context
|
|
5907
|
+
);
|
|
5908
|
+
checkDuplicateEvidenceBlockIdsAcrossTypes(diagram, context);
|
|
3862
5909
|
});
|
|
3863
5910
|
function validateDiagramDsl(value) {
|
|
3864
5911
|
const result = diagramDslSchema.safeParse(value);
|
|
@@ -3878,6 +5925,62 @@ function toDiagnosticPath(path) {
|
|
|
3878
5925
|
(segment) => typeof segment === "string" || typeof segment === "number" ? [segment] : []
|
|
3879
5926
|
);
|
|
3880
5927
|
}
|
|
5928
|
+
function checkDuplicateEvidenceBlockIds(collection, blocks, context) {
|
|
5929
|
+
checkDuplicateIds(
|
|
5930
|
+
`evidence block id in ${collection}`,
|
|
5931
|
+
blocks ?? [],
|
|
5932
|
+
context,
|
|
5933
|
+
(index) => [collection, index, "id"]
|
|
5934
|
+
);
|
|
5935
|
+
}
|
|
5936
|
+
function checkDuplicateIds(label, items, context, pathForIndex) {
|
|
5937
|
+
const firstIndexById = /* @__PURE__ */ new Map();
|
|
5938
|
+
items.forEach((item, index) => {
|
|
5939
|
+
const firstIndex = firstIndexById.get(item.id);
|
|
5940
|
+
if (firstIndex === void 0) {
|
|
5941
|
+
firstIndexById.set(item.id, index);
|
|
5942
|
+
return;
|
|
5943
|
+
}
|
|
5944
|
+
context.addIssue({
|
|
5945
|
+
code: "custom",
|
|
5946
|
+
message: `Duplicate ${label} "${item.id}".`,
|
|
5947
|
+
path: pathForIndex(index)
|
|
5948
|
+
});
|
|
5949
|
+
});
|
|
5950
|
+
}
|
|
5951
|
+
function checkDuplicateValues(label, values, context, pathForIndex) {
|
|
5952
|
+
const firstIndexByValue = /* @__PURE__ */ new Map();
|
|
5953
|
+
values.forEach((value, index) => {
|
|
5954
|
+
const firstIndex = firstIndexByValue.get(value);
|
|
5955
|
+
if (firstIndex === void 0) {
|
|
5956
|
+
firstIndexByValue.set(value, index);
|
|
5957
|
+
return;
|
|
5958
|
+
}
|
|
5959
|
+
context.addIssue({
|
|
5960
|
+
code: "custom",
|
|
5961
|
+
message: `Duplicate ${label} "${value}".`,
|
|
5962
|
+
path: pathForIndex(index)
|
|
5963
|
+
});
|
|
5964
|
+
});
|
|
5965
|
+
}
|
|
5966
|
+
function checkDuplicateEvidenceBlockIdsAcrossTypes(diagram, context) {
|
|
5967
|
+
const firstById = /* @__PURE__ */ new Map();
|
|
5968
|
+
for (const collection of ["matrices", "tables", "evidencePanels"]) {
|
|
5969
|
+
const blocks = diagram[collection] ?? [];
|
|
5970
|
+
blocks.forEach((block, index) => {
|
|
5971
|
+
const first = firstById.get(block.id);
|
|
5972
|
+
if (first === void 0) {
|
|
5973
|
+
firstById.set(block.id, { collection, index });
|
|
5974
|
+
return;
|
|
5975
|
+
}
|
|
5976
|
+
context.addIssue({
|
|
5977
|
+
code: "custom",
|
|
5978
|
+
message: `Duplicate evidence block id "${block.id}" across ${first.collection} and ${collection}.`,
|
|
5979
|
+
path: [collection, index, "id"]
|
|
5980
|
+
});
|
|
5981
|
+
});
|
|
5982
|
+
}
|
|
5983
|
+
}
|
|
3881
5984
|
|
|
3882
5985
|
// src/dsl/parse.ts
|
|
3883
5986
|
var DEFAULT_DSL_MAX_BYTES = 1e6;
|
|
@@ -4053,7 +6156,8 @@ function renderDiagramDsl(source, options = {}) {
|
|
|
4053
6156
|
}
|
|
4054
6157
|
const solved = solveDiagram(normalized.diagram, {
|
|
4055
6158
|
routeKind: normalized.diagram.metadata?.routeKind === "straight" ? "straight" : "orthogonal",
|
|
4056
|
-
...solvePortShiftingOption(normalized.diagram.metadata?.portShifting)
|
|
6159
|
+
...solvePortShiftingOption(normalized.diagram.metadata?.portShifting),
|
|
6160
|
+
...options.textMeasurer === void 0 ? {} : { textMeasurer: options.textMeasurer }
|
|
4057
6161
|
});
|
|
4058
6162
|
const solveDiagnostics = solved.diagnostics.map(toSolveDiagnostic);
|
|
4059
6163
|
if (hasErrorDiagnostics2(solveDiagnostics)) {
|