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