compasso 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +115 -5
  2. package/dist/{chunk-ZBDABVIO.js → chunk-2NDET6O5.js} +3 -3
  3. package/dist/{chunk-ZBDABVIO.js.map → chunk-2NDET6O5.js.map} +1 -1
  4. package/dist/{chunk-Q6DVTCXD.js → chunk-3RGYLVTN.js} +18 -6
  5. package/dist/chunk-3RGYLVTN.js.map +1 -0
  6. package/dist/chunk-BM7UJBK5.js +680 -0
  7. package/dist/chunk-BM7UJBK5.js.map +1 -0
  8. package/dist/chunk-DVLWT565.js +372 -0
  9. package/dist/chunk-DVLWT565.js.map +1 -0
  10. package/dist/{chunk-F47C6ZEB.js → chunk-JBDA7E2O.js} +3 -3
  11. package/dist/{chunk-F47C6ZEB.js.map → chunk-JBDA7E2O.js.map} +1 -1
  12. package/dist/chunk-MIJTBYX2.js +982 -0
  13. package/dist/chunk-MIJTBYX2.js.map +1 -0
  14. package/dist/{chunk-JP4N42AY.js → chunk-PJHLWSGD.js} +3 -3
  15. package/dist/{chunk-JP4N42AY.js.map → chunk-PJHLWSGD.js.map} +1 -1
  16. package/dist/{chunk-LRHHUJFZ.js → chunk-RDH4XHA2.js} +3 -3
  17. package/dist/{chunk-LRHHUJFZ.js.map → chunk-RDH4XHA2.js.map} +1 -1
  18. package/dist/{chunk-UJVU7B44.js → chunk-WEHUSHVI.js} +31 -51
  19. package/dist/chunk-WEHUSHVI.js.map +1 -0
  20. package/dist/{chunk-RWPGGWO5.js → chunk-Z66YUOUM.js} +34 -10
  21. package/dist/chunk-Z66YUOUM.js.map +1 -0
  22. package/dist/core/index.cjs +247 -0
  23. package/dist/core/index.cjs.map +1 -1
  24. package/dist/core/index.d.cts +94 -2
  25. package/dist/core/index.d.ts +94 -2
  26. package/dist/core/index.js +1 -1
  27. package/dist/ecomap/index.cjs +34 -11
  28. package/dist/ecomap/index.cjs.map +1 -1
  29. package/dist/ecomap/index.d.cts +12 -0
  30. package/dist/ecomap/index.d.ts +12 -0
  31. package/dist/ecomap/index.js +2 -2
  32. package/dist/fault-tree/index.d.cts +2 -2
  33. package/dist/fault-tree/index.d.ts +2 -2
  34. package/dist/fault-tree/index.js +2 -2
  35. package/dist/fishbone/index.js +2 -2
  36. package/dist/genogram/index.cjs +57 -7
  37. package/dist/genogram/index.cjs.map +1 -1
  38. package/dist/genogram/index.d.cts +22 -6
  39. package/dist/genogram/index.d.ts +22 -6
  40. package/dist/genogram/index.js +2 -2
  41. package/dist/geometry-P-XGqGe7.d.cts +8 -0
  42. package/dist/geometry-P-XGqGe7.d.ts +8 -0
  43. package/dist/grid-BMgUSly1.d.cts +79 -0
  44. package/dist/grid-BMgUSly1.d.ts +79 -0
  45. package/dist/index.cjs +2360 -395
  46. package/dist/index.cjs.map +1 -1
  47. package/dist/index.d.cts +14 -7
  48. package/dist/index.d.ts +14 -7
  49. package/dist/index.js +10 -8
  50. package/dist/{kinship-DqEklrDN.d.ts → kinship-BF90HyyS.d.ts} +1 -1
  51. package/dist/{kinship-Dy_ijjJV.d.cts → kinship-BOUss5cT.d.cts} +1 -1
  52. package/dist/{labels-RtFw9tX1.d.cts → labels-B0aOMbHy.d.cts} +12 -0
  53. package/dist/{labels-RtFw9tX1.d.ts → labels-B0aOMbHy.d.ts} +12 -0
  54. package/dist/labels-Br8yjc3C.d.cts +29 -0
  55. package/dist/labels-Br8yjc3C.d.ts +29 -0
  56. package/dist/{labels-DNqRkWuI.d.ts → labels-CuLbFyrz.d.ts} +1 -1
  57. package/dist/labels-D1v1RWZd.d.cts +97 -0
  58. package/dist/labels-D1v1RWZd.d.ts +97 -0
  59. package/dist/{labels-CBQ_3Ec9.d.cts → labels-DhQe7I8m.d.cts} +1 -1
  60. package/dist/layered-DmZluAqe.d.cts +72 -0
  61. package/dist/layered-DmZluAqe.d.ts +72 -0
  62. package/dist/locales/pt-br.cjs +53 -0
  63. package/dist/locales/pt-br.cjs.map +1 -1
  64. package/dist/locales/pt-br.d.cts +11 -5
  65. package/dist/locales/pt-br.d.ts +11 -5
  66. package/dist/locales/pt-br.js +50 -1
  67. package/dist/locales/pt-br.js.map +1 -1
  68. package/dist/org-chart/index.cjs +138 -94
  69. package/dist/org-chart/index.cjs.map +1 -1
  70. package/dist/org-chart/index.d.cts +24 -33
  71. package/dist/org-chart/index.d.ts +24 -33
  72. package/dist/org-chart/index.js +2 -2
  73. package/dist/pedigree/index.d.cts +6 -6
  74. package/dist/pedigree/index.d.ts +6 -6
  75. package/dist/pedigree/index.js +2 -2
  76. package/dist/phylo/index.d.cts +2 -2
  77. package/dist/phylo/index.d.ts +2 -2
  78. package/dist/phylo/index.js +2 -2
  79. package/dist/prisma/index.cjs +882 -0
  80. package/dist/prisma/index.cjs.map +1 -0
  81. package/dist/prisma/index.d.cts +174 -0
  82. package/dist/prisma/index.d.ts +174 -0
  83. package/dist/prisma/index.js +4 -0
  84. package/dist/prisma/index.js.map +1 -0
  85. package/dist/{text-DuO_PwYw.d.cts → text-DDVzpwPZ.d.cts} +1 -8
  86. package/dist/{text-DuO_PwYw.d.ts → text-DDVzpwPZ.d.ts} +1 -8
  87. package/dist/{types-BnMG7TCd.d.cts → types-jE2fdM1t.d.cts} +8 -0
  88. package/dist/{types-BnMG7TCd.d.ts → types-jE2fdM1t.d.ts} +8 -0
  89. package/dist/uml/index.cjs +1214 -0
  90. package/dist/uml/index.cjs.map +1 -0
  91. package/dist/uml/index.d.cts +189 -0
  92. package/dist/uml/index.d.ts +189 -0
  93. package/dist/uml/index.js +4 -0
  94. package/dist/uml/index.js.map +1 -0
  95. package/package.json +28 -2
  96. package/dist/chunk-O3BT2O42.js +0 -145
  97. package/dist/chunk-O3BT2O42.js.map +0 -1
  98. package/dist/chunk-Q6DVTCXD.js.map +0 -1
  99. package/dist/chunk-RWPGGWO5.js.map +0 -1
  100. package/dist/chunk-UJVU7B44.js.map +0 -1
@@ -0,0 +1,982 @@
1
+ import { packGrid, allocateLanes, legendBlock, xmlEscape, measureCompartmentBox, glyphInset, estimateTextWidth, FONT_FAMILY, EDGE_STROKE, LEGEND_SWATCH_W, diamondPoints, trianglePoints, arrowOpenPoints, GLYPH_TRI_HALF } from './chunk-DVLWT565.js';
2
+
3
+ // src/uml/types.ts
4
+ var UML_VISIBILITIES = ["public", "private", "protected", "package"];
5
+ var UML_VIS_GLYPH = {
6
+ public: "+",
7
+ private: "-",
8
+ protected: "#",
9
+ package: "~"
10
+ };
11
+ var UML_RELATIONSHIPS = [
12
+ "association",
13
+ "directed-association",
14
+ "aggregation",
15
+ "composition",
16
+ "generalization",
17
+ "realization",
18
+ "dependency"
19
+ ];
20
+ var RELATION_GLYPHS = {
21
+ association: { sourceEnd: "none", targetEnd: "none", line: "solid" },
22
+ "directed-association": { sourceEnd: "none", targetEnd: "open-arrow", line: "solid" },
23
+ aggregation: { sourceEnd: "hollow-diamond", targetEnd: "none", line: "solid" },
24
+ composition: { sourceEnd: "solid-diamond", targetEnd: "none", line: "solid" },
25
+ generalization: { sourceEnd: "none", targetEnd: "hollow-triangle", line: "solid" },
26
+ realization: { sourceEnd: "none", targetEnd: "hollow-triangle", line: "dashed" },
27
+ dependency: { sourceEnd: "none", targetEnd: "open-arrow", line: "dashed" }
28
+ };
29
+
30
+ // src/uml/validate.ts
31
+ var UmlValidationError = class extends Error {
32
+ issues;
33
+ constructor(issues) {
34
+ super(`invalid UML class diagram: ${issues.map((i) => i.message).join("; ")}`);
35
+ this.name = "UmlValidationError";
36
+ this.issues = issues;
37
+ }
38
+ };
39
+ function sortIssues(issues) {
40
+ const unique = /* @__PURE__ */ new Map();
41
+ for (const issue of issues) unique.set(`${issue.code} ${issue.message}`, issue);
42
+ return [...unique.values()].sort(
43
+ (a, b) => a.code !== b.code ? a.code < b.code ? -1 : 1 : a.message < b.message ? -1 : a.message > b.message ? 1 : 0
44
+ );
45
+ }
46
+ var GRAPH_BLOCKING = /* @__PURE__ */ new Set([
47
+ "duplicate-id",
48
+ "unknown-endpoint"
49
+ ]);
50
+ var INHERITANCE_KINDS = /* @__PURE__ */ new Set([
51
+ "generalization",
52
+ "realization"
53
+ ]);
54
+ function umlIssues(input) {
55
+ if (input.classes.length === 0 && input.relationships.length === 0) return [];
56
+ const issues = [];
57
+ const push = (code, message) => {
58
+ issues.push({ code, message });
59
+ };
60
+ const classById = /* @__PURE__ */ new Map();
61
+ const dupClassIds = /* @__PURE__ */ new Set();
62
+ for (const c of input.classes) {
63
+ if (classById.has(c.id)) dupClassIds.add(c.id);
64
+ else classById.set(c.id, c);
65
+ }
66
+ for (const id of [...dupClassIds].sort((a, b) => a - b)) {
67
+ push("duplicate-id", `duplicate class id ${id}`);
68
+ }
69
+ const relById = /* @__PURE__ */ new Map();
70
+ const dupRelIds = /* @__PURE__ */ new Set();
71
+ for (const r of input.relationships) {
72
+ if (relById.has(r.id)) dupRelIds.add(r.id);
73
+ else relById.set(r.id, r);
74
+ }
75
+ for (const id of [...dupRelIds].sort((a, b) => a - b)) {
76
+ push("duplicate-id", `duplicate relationship id ${id}`);
77
+ }
78
+ if (issues.length > 0) return sortIssues(issues);
79
+ const rels = [...relById.values()].sort((a, b) => a.id - b.id);
80
+ for (const r of rels) {
81
+ if (!classById.has(r.sourceId)) {
82
+ push("unknown-endpoint", `relationship ${r.id} sourceId ${r.sourceId} is not a declared class`);
83
+ }
84
+ if (!classById.has(r.targetId)) {
85
+ push("unknown-endpoint", `relationship ${r.id} targetId ${r.targetId} is not a declared class`);
86
+ }
87
+ }
88
+ for (const c of [...classById.values()].sort((a, b) => a.id - b.id)) {
89
+ if (!Number.isInteger(c.col) || c.col < 0) {
90
+ push("negative-cell", `class ${c.id} col ${c.col} must be a non-negative integer`);
91
+ }
92
+ if (!Number.isInteger(c.row) || c.row < 0) {
93
+ push("negative-cell", `class ${c.id} row ${c.row} must be a non-negative integer`);
94
+ }
95
+ }
96
+ const cellKey = (c) => `${c.col},${c.row}`;
97
+ const cellToClasses = /* @__PURE__ */ new Map();
98
+ for (const c of [...classById.values()].sort((a, b) => a.id - b.id)) {
99
+ const key = cellKey(c);
100
+ const arr = cellToClasses.get(key) ?? [];
101
+ arr.push(c.id);
102
+ cellToClasses.set(key, arr);
103
+ }
104
+ for (const [key, ids] of [...cellToClasses.entries()].sort()) {
105
+ if (ids.length > 1) {
106
+ push(
107
+ "cell-collision",
108
+ `classes ${ids.sort((a, b) => a - b).join(", ")} all declare cell (${key})`
109
+ );
110
+ }
111
+ }
112
+ for (const r of rels) {
113
+ if ((r.kind === "generalization" || r.kind === "realization") && r.sourceId === r.targetId) {
114
+ push(
115
+ "self-generalization",
116
+ `relationship ${r.id} is a ${r.kind} from class ${r.sourceId} to itself`
117
+ );
118
+ }
119
+ }
120
+ if (!issues.some((i) => GRAPH_BLOCKING.has(i.code))) {
121
+ const inheritanceChildren = /* @__PURE__ */ new Map();
122
+ for (const r of rels) {
123
+ if (!INHERITANCE_KINDS.has(r.kind)) continue;
124
+ if (r.sourceId === r.targetId) continue;
125
+ const arr = inheritanceChildren.get(r.sourceId) ?? [];
126
+ arr.push(r.targetId);
127
+ inheritanceChildren.set(r.sourceId, arr);
128
+ }
129
+ for (const arr of inheritanceChildren.values()) arr.sort((a, b) => a - b);
130
+ const color = /* @__PURE__ */ new Map();
131
+ const seenCycles = /* @__PURE__ */ new Set();
132
+ const dfs = (start) => {
133
+ const stack = [{ id: start, nextChild: 0 }];
134
+ color.set(start, 1);
135
+ while (stack.length > 0) {
136
+ const frame = stack[stack.length - 1];
137
+ const children = inheritanceChildren.get(frame.id) ?? [];
138
+ if (frame.nextChild >= children.length) {
139
+ color.set(frame.id, 2);
140
+ stack.pop();
141
+ continue;
142
+ }
143
+ const child = children[frame.nextChild];
144
+ frame.nextChild += 1;
145
+ const c = color.get(child) ?? 0;
146
+ if (c === 1) {
147
+ const from = stack.findIndex((f) => f.id === child);
148
+ const cycle = stack.slice(from).map((f) => f.id);
149
+ const minIdx = cycle.indexOf(Math.min(...cycle));
150
+ const rotated = [...cycle.slice(minIdx), ...cycle.slice(0, minIdx)];
151
+ const key = rotated.join(">");
152
+ if (!seenCycles.has(key)) {
153
+ seenCycles.add(key);
154
+ push(
155
+ "generalization-cycle",
156
+ `generalization cycle: ${[...rotated, rotated[0]].join(" \u2192 ")}`
157
+ );
158
+ }
159
+ } else if (c === 0) {
160
+ color.set(child, 1);
161
+ stack.push({ id: child, nextChild: 0 });
162
+ }
163
+ }
164
+ };
165
+ for (const c of [...classById.values()].sort((a, b) => a.id - b.id)) {
166
+ if ((color.get(c.id) ?? 0) === 0) dfs(c.id);
167
+ }
168
+ }
169
+ return sortIssues(issues);
170
+ }
171
+ function validateUml(input) {
172
+ const issues = umlIssues(input);
173
+ if (issues.length > 0) throw new UmlValidationError(issues);
174
+ }
175
+ function sideCapacity(boxH, rowGap, pitch, glyphHalf) {
176
+ const clear = boxH / 2 + rowGap / 2 - glyphHalf;
177
+ const k = Math.max(0, Math.floor(clear / pitch));
178
+ return 2 * k + 1;
179
+ }
180
+ function sideCapacityIssue(classId, side, count, capacity) {
181
+ return {
182
+ code: "too-many-side-edges",
183
+ message: `class ${classId} has ${count} edges on its ${side} side \u2014 at most ${capacity} fit without a port stub spilling through a neighbouring box`
184
+ };
185
+ }
186
+
187
+ // src/uml/labels.ts
188
+ var UML_TITLE_LABELS_EN = {
189
+ kinds: {
190
+ association: "association",
191
+ "directed-association": "directed association",
192
+ aggregation: "aggregation",
193
+ composition: "composition",
194
+ generalization: "generalization",
195
+ realization: "realization",
196
+ dependency: "dependency"
197
+ },
198
+ visibilities: {
199
+ public: "public",
200
+ private: "private",
201
+ protected: "protected",
202
+ package: "package"
203
+ }
204
+ };
205
+ var UML_SVG_LABELS_EN = {
206
+ legend: {
207
+ association: "Association",
208
+ "directed-association": "Directed association",
209
+ aggregation: "Aggregation",
210
+ composition: "Composition",
211
+ generalization: "Generalization",
212
+ realization: "Realization",
213
+ dependency: "Dependency"
214
+ },
215
+ ariaLabel: "UML class diagram"
216
+ };
217
+
218
+ // src/uml/layout.ts
219
+ var UML_PADDING = 32;
220
+ var UML_COL_GAP = 64;
221
+ var UML_ROW_GAP = 56;
222
+ var UML_BOX_PAD_X = 10;
223
+ var UML_BOX_PAD_Y = 8;
224
+ var UML_LINE_H = 15;
225
+ var UML_MIN_BOX_W = 120;
226
+ var UML_NAME_FONT = 12;
227
+ var UML_FEAT_FONT = 11;
228
+ var UML_SEP_PAD = 4;
229
+ var UML_PORT_PITCH = 12;
230
+ var UML_SELF_LOOP_W = 28;
231
+ var UML_LABEL_FONT = 10;
232
+ var UML_ASSOC_ROUTE_BASE = 1e6;
233
+ var UML_GLYPH_BASE = 2e6;
234
+ var UML_SELF_LOOP_BASE = 3e6;
235
+ var MIN_TERMINAL_STUB = 4;
236
+ var round = (n) => Math.round(n * 100) / 100;
237
+ function featureLine(f) {
238
+ const prefix = f.visibility !== null ? UML_VIS_GLYPH[f.visibility] + " " : "";
239
+ return prefix + f.text;
240
+ }
241
+ function measureClass(cls) {
242
+ const nameLines = [];
243
+ if (cls.stereotype !== null) nameLines.push(cls.stereotype);
244
+ nameLines.push(cls.name);
245
+ const attrLines = cls.attributes.map(featureLine);
246
+ const opLines = cls.operations.map(featureLine);
247
+ return measureCompartmentBox(
248
+ [
249
+ { lines: nameLines, font: UML_NAME_FONT },
250
+ { lines: attrLines, font: UML_FEAT_FONT },
251
+ { lines: opLines, font: UML_FEAT_FONT }
252
+ ],
253
+ {
254
+ padX: UML_BOX_PAD_X,
255
+ padY: UML_BOX_PAD_Y,
256
+ lineH: UML_LINE_H,
257
+ minW: UML_MIN_BOX_W,
258
+ sepPad: UML_SEP_PAD
259
+ }
260
+ );
261
+ }
262
+ function boxEdges(node) {
263
+ const left = round(node.cx - node.boxW / 2);
264
+ const right = round(left + node.boxW);
265
+ const top = round(node.cy - node.boxH / 2);
266
+ const bottom = round(top + node.boxH);
267
+ return { left, right, top, bottom };
268
+ }
269
+ function glyphInsetForKind(gk) {
270
+ switch (gk) {
271
+ case "none":
272
+ return 0;
273
+ case "open-arrow":
274
+ return glyphInset("right", "arrow");
275
+ case "hollow-triangle":
276
+ return glyphInset("right", "triangle");
277
+ case "hollow-diamond":
278
+ case "solid-diamond":
279
+ return glyphInset("right", "diamond");
280
+ }
281
+ }
282
+ var MAX_GLYPH_HALF = GLYPH_TRI_HALF;
283
+ var GLYPH_SLOT_PITCH = Math.max(UML_PORT_PITCH, MAX_GLYPH_HALF * 2 + 2);
284
+ function portOffset(slotIndex, slotSize) {
285
+ if (slotIndex === 0) return 0;
286
+ const sign = slotIndex % 2 === 1 ? 1 : -1;
287
+ const dist = Math.ceil(slotIndex / 2);
288
+ return sign * dist * slotSize;
289
+ }
290
+ function sidePort(node, side, dy) {
291
+ const { left, right } = boxEdges(node);
292
+ const x = side === "right" ? right : left;
293
+ return { x, y: round(node.cy + dy) };
294
+ }
295
+ function vGutterLeft(colIdx, colX, grid) {
296
+ return round(colX[colIdx] + grid.colW[colIdx]);
297
+ }
298
+ var V_GUTTER_MARGIN = 6;
299
+ function horizontalGutterY(rowIdx, rowY, grid) {
300
+ return round(rowY[rowIdx] + grid.rowH[rowIdx] + UML_ROW_GAP / 2);
301
+ }
302
+ function moveAlongDir(p, dir, dist) {
303
+ switch (dir) {
304
+ case "right":
305
+ return { x: round(p.x + dist), y: p.y };
306
+ case "left":
307
+ return { x: round(p.x - dist), y: p.y };
308
+ case "down":
309
+ return { x: p.x, y: round(p.y + dist) };
310
+ case "up":
311
+ return { x: p.x, y: round(p.y - dist) };
312
+ }
313
+ }
314
+ function simplifyWaypoints(pts) {
315
+ const dedup = [pts[0]];
316
+ for (let i = 1; i < pts.length; i++) {
317
+ const prev = dedup[dedup.length - 1];
318
+ const cur = pts[i];
319
+ if (Math.abs(cur.x - prev.x) > 1e-6 || Math.abs(cur.y - prev.y) > 1e-6) dedup.push(cur);
320
+ }
321
+ if (dedup.length <= 2) return dedup;
322
+ const out = [dedup[0]];
323
+ for (let i = 1; i < dedup.length - 1; i++) {
324
+ const a = out[out.length - 1];
325
+ const b = dedup[i];
326
+ const c = dedup[i + 1];
327
+ const abH = Math.abs(a.y - b.y) <= 1e-6;
328
+ const bcH = Math.abs(b.y - c.y) <= 1e-6;
329
+ const abV = Math.abs(a.x - b.x) <= 1e-6;
330
+ const bcV = Math.abs(b.x - c.x) <= 1e-6;
331
+ if (abH && bcH || abV && bcV) continue;
332
+ out.push(b);
333
+ }
334
+ out.push(dedup[dedup.length - 1]);
335
+ return out;
336
+ }
337
+ function chooseSides(srcNode, tgtNode, maxCol) {
338
+ const sc = srcNode.col;
339
+ const tc = tgtNode.col;
340
+ if (tc > sc) return { srcSide: "right", tgtSide: "left", srcVCol: sc, tgtVCol: tc - 1 };
341
+ if (tc < sc) return { srcSide: "left", tgtSide: "right", srcVCol: sc - 1, tgtVCol: tc };
342
+ if (sc < maxCol) return { srcSide: "right", tgtSide: "right", srcVCol: sc, tgtVCol: sc };
343
+ if (sc > 0) return { srcSide: "left", tgtSide: "left", srcVCol: sc - 1, tgtVCol: sc - 1 };
344
+ return { srcSide: "right", tgtSide: "right", srcVCol: sc, tgtVCol: sc };
345
+ }
346
+ function hBaseY(plan, rowY, grid) {
347
+ if (plan.hKind === "pad-top") return round(UML_PADDING / 2);
348
+ return horizontalGutterY(plan.hRow, rowY, grid);
349
+ }
350
+ function hBandBounds(plan, rowY, grid) {
351
+ if (plan.hKind === "pad-top") return { lo: 0, hi: round(rowY[0] ?? UML_PADDING) };
352
+ const lo = round(rowY[plan.hRow] + grid.rowH[plan.hRow]);
353
+ const hi = round(rowY[plan.hRow + 1]);
354
+ return { lo, hi };
355
+ }
356
+ var LANE_EPS = 0.5;
357
+ var X_OVERLAP_EPS = 1e-4;
358
+ function allocateHorizontal(runs, base, usableHalf) {
359
+ const floating = runs.filter((r) => r.y === null);
360
+ if (floating.length === 0) return;
361
+ const pinned = runs.filter((r) => r.y !== null);
362
+ const trackCount = floating.length + pinned.length + 1;
363
+ const candidates = trackCount <= 1 || usableHalf <= 0 ? [base] : Array.from(
364
+ { length: trackCount },
365
+ (_, k) => round(base - usableHalf + (k + 0.5) * (2 * usableHalf) / trackCount)
366
+ );
367
+ const placed = pinned.map((r) => ({ lo: r.lo, hi: r.hi, y: r.y }));
368
+ const ordered = [...floating].sort((a, b) => a.lo - b.lo || a.hi - b.hi);
369
+ for (const run of ordered) {
370
+ let chosen = candidates[0];
371
+ for (const cy of candidates) {
372
+ const collides = placed.some(
373
+ (q) => Math.abs(q.y - cy) <= LANE_EPS && Math.min(run.hi, q.hi) - Math.max(run.lo, q.lo) > X_OVERLAP_EPS
374
+ );
375
+ if (!collides) {
376
+ chosen = cy;
377
+ break;
378
+ }
379
+ }
380
+ run.set(chosen);
381
+ placed.push({ lo: run.lo, hi: run.hi, y: chosen });
382
+ }
383
+ }
384
+ function applyGlyphInsets(pts, srcInset, tgtInset) {
385
+ if (pts.length < 2) return pts;
386
+ const out = pts.map((p) => ({ ...p }));
387
+ if (srcInset > 0) {
388
+ const p0 = out[0];
389
+ const p1 = out[1];
390
+ const dir = p1.x >= p0.x ? "right" : "left";
391
+ out[0] = moveAlongDir(p0, dir, srcInset);
392
+ }
393
+ if (tgtInset > 0) {
394
+ const pn = out[out.length - 1];
395
+ const pm = out[out.length - 2];
396
+ const dir = pm.x >= pn.x ? "right" : "left";
397
+ out[out.length - 1] = moveAlongDir(pn, dir, tgtInset);
398
+ }
399
+ return out;
400
+ }
401
+ function reserveTextBox(relId, slot, text, anchorX, anchorY, anchor) {
402
+ const w = round(estimateTextWidth(text, UML_LABEL_FONT));
403
+ const h = round(UML_LABEL_FONT * 1.2);
404
+ const x = anchor === "middle" ? round(anchorX - w / 2) : round(anchorX);
405
+ const y = round(anchorY);
406
+ return { relId, slot, text, x, y, w, h, anchor };
407
+ }
408
+ function computeUmlLayout(input, labels = UML_TITLE_LABELS_EN) {
409
+ validateUml(input);
410
+ const relTitle = (rel) => rel.title ?? labels.kinds[rel.kind];
411
+ if (input.classes.length === 0) {
412
+ return {
413
+ nodes: [],
414
+ elements: [],
415
+ textBoxes: [],
416
+ canvasW: UML_PADDING * 2,
417
+ canvasH: UML_PADDING * 2,
418
+ grid: { cellX: [], cellY: [], colW: [], rowH: [] },
419
+ colX: [],
420
+ rowY: []
421
+ };
422
+ }
423
+ const classes = [...input.classes].sort((a, b) => a.id - b.id);
424
+ const rels = [...input.relationships].sort((a, b) => a.id - b.id);
425
+ const metricsMap = /* @__PURE__ */ new Map();
426
+ for (const cls of classes) metricsMap.set(cls.id, measureClass(cls));
427
+ const gridCells = classes.map((cls) => ({
428
+ col: cls.col,
429
+ row: cls.row,
430
+ w: metricsMap.get(cls.id).boxW,
431
+ h: metricsMap.get(cls.id).boxH
432
+ }));
433
+ const grid = packGrid(gridCells, { colGap: UML_COL_GAP, rowGap: UML_ROW_GAP });
434
+ const colX = [];
435
+ {
436
+ let x = UML_PADDING;
437
+ for (let c = 0; c < grid.colW.length; c++) {
438
+ colX.push(round(x));
439
+ x = round(x + grid.colW[c] + UML_COL_GAP);
440
+ }
441
+ }
442
+ const rowY = [];
443
+ {
444
+ let y = UML_PADDING;
445
+ for (let r = 0; r < grid.rowH.length; r++) {
446
+ rowY.push(round(y));
447
+ y = round(y + grid.rowH[r] + UML_ROW_GAP);
448
+ }
449
+ }
450
+ const nodeByClassId = /* @__PURE__ */ new Map();
451
+ const nodes = [];
452
+ for (const cls of classes) {
453
+ const metrics = metricsMap.get(cls.id);
454
+ const cx = round(colX[cls.col] + grid.colW[cls.col] / 2);
455
+ const cy = round(rowY[cls.row] + grid.rowH[cls.row] / 2);
456
+ const title = cls.title ?? (cls.stereotype !== null ? `\xAB${cls.stereotype}\xBB ${cls.name}` : cls.name);
457
+ const node = {
458
+ classId: cls.id,
459
+ cx,
460
+ cy,
461
+ boxW: metrics.boxW,
462
+ boxH: metrics.boxH,
463
+ metrics,
464
+ title,
465
+ col: cls.col,
466
+ row: cls.row
467
+ };
468
+ nodes.push(node);
469
+ nodeByClassId.set(cls.id, node);
470
+ }
471
+ const maxCol = grid.colW.length - 1;
472
+ const maxRow = grid.rowH.length - 1;
473
+ const sideCount = /* @__PURE__ */ new Map();
474
+ const bumpSide = (classId, side) => {
475
+ const key = `${classId}:${side}`;
476
+ sideCount.set(key, (sideCount.get(key) ?? 0) + 1);
477
+ };
478
+ for (const rel of rels) {
479
+ const srcNode = nodeByClassId.get(rel.sourceId);
480
+ const tgtNode = nodeByClassId.get(rel.targetId);
481
+ if (!srcNode || !tgtNode) continue;
482
+ if (rel.sourceId === rel.targetId) {
483
+ bumpSide(srcNode.classId, "right");
484
+ bumpSide(srcNode.classId, "right");
485
+ continue;
486
+ }
487
+ const { srcSide, tgtSide } = chooseSides(srcNode, tgtNode, maxCol);
488
+ bumpSide(srcNode.classId, srcSide);
489
+ bumpSide(tgtNode.classId, tgtSide);
490
+ }
491
+ {
492
+ const capIssues = [];
493
+ for (const node of nodes) {
494
+ const cap = sideCapacity(node.boxH, UML_ROW_GAP, GLYPH_SLOT_PITCH, MAX_GLYPH_HALF);
495
+ for (const side of ["left", "right"]) {
496
+ const count = sideCount.get(`${node.classId}:${side}`) ?? 0;
497
+ if (count > cap) capIssues.push(sideCapacityIssue(node.classId, side, count, cap));
498
+ }
499
+ }
500
+ if (capIssues.length > 0) {
501
+ throw new UmlValidationError(capIssues);
502
+ }
503
+ }
504
+ const sidePortSlot = /* @__PURE__ */ new Map();
505
+ const nextSidePort = (classId, side) => {
506
+ const key = `${classId}:${side}`;
507
+ const idx = sidePortSlot.get(key) ?? 0;
508
+ sidePortSlot.set(key, idx + 1);
509
+ return idx;
510
+ };
511
+ const elements = [];
512
+ const textBoxes = [];
513
+ const plans = [];
514
+ for (const rel of rels) {
515
+ const srcNode = nodeByClassId.get(rel.sourceId);
516
+ const tgtNode = nodeByClassId.get(rel.targetId);
517
+ if (!srcNode || !tgtNode) continue;
518
+ const glyphs = RELATION_GLYPHS[rel.kind];
519
+ const srcInset = glyphInsetForKind(glyphs.sourceEnd);
520
+ const tgtInset = glyphInsetForKind(glyphs.targetEnd);
521
+ if (rel.sourceId === rel.targetId) {
522
+ const vCol = srcNode.col;
523
+ const armDy1 = portOffset(nextSidePort(srcNode.classId, "right"), GLYPH_SLOT_PITCH);
524
+ const armDy2 = portOffset(nextSidePort(srcNode.classId, "right"), GLYPH_SLOT_PITCH);
525
+ plans.push({
526
+ rel,
527
+ srcNode,
528
+ tgtNode,
529
+ srcSide: "right",
530
+ tgtSide: "right",
531
+ srcVCol: vCol,
532
+ tgtVCol: vCol,
533
+ srcInset,
534
+ tgtInset,
535
+ srcPortY: round(srcNode.cy + armDy1),
536
+ tgtPortY: round(srcNode.cy + armDy2),
537
+ hKind: "row",
538
+ hRow: 0,
539
+ // unused for self-loops (no H-leg)
540
+ isSelfLoop: true,
541
+ srcVx: 0,
542
+ tgtVx: 0,
543
+ hY: 0
544
+ });
545
+ continue;
546
+ }
547
+ const { srcSide, tgtSide, srcVCol, tgtVCol } = chooseSides(srcNode, tgtNode, maxCol);
548
+ const srcPortDy = portOffset(nextSidePort(srcNode.classId, srcSide), GLYPH_SLOT_PITCH);
549
+ const tgtPortDy = portOffset(nextSidePort(tgtNode.classId, tgtSide), GLYPH_SLOT_PITCH);
550
+ const sr = srcNode.row;
551
+ const tr = tgtNode.row;
552
+ let hKind;
553
+ let hRow;
554
+ if (sr !== tr) {
555
+ hKind = "row";
556
+ hRow = Math.min(sr, tr);
557
+ } else if (maxRow >= 1) {
558
+ hKind = "row";
559
+ hRow = sr < maxRow ? sr : sr - 1;
560
+ } else {
561
+ hKind = "pad-top";
562
+ hRow = -1;
563
+ }
564
+ plans.push({
565
+ rel,
566
+ srcNode,
567
+ tgtNode,
568
+ srcSide,
569
+ tgtSide,
570
+ srcVCol,
571
+ tgtVCol,
572
+ srcInset,
573
+ tgtInset,
574
+ srcPortY: round(srcNode.cy + srcPortDy),
575
+ tgtPortY: round(tgtNode.cy + tgtPortDy),
576
+ hKind,
577
+ hRow,
578
+ isSelfLoop: false,
579
+ srcVx: 0,
580
+ tgtVx: 0,
581
+ hY: 0
582
+ });
583
+ }
584
+ const hHalfBand = (p) => p.hKind === "pad-top" ? round(UML_PADDING / 2) : round(UML_ROW_GAP / 2);
585
+ const vByKey = /* @__PURE__ */ new Map();
586
+ const pushV = (col, side, portY, hBase, half, inset, set) => {
587
+ const lo = Math.min(portY, round(hBase - half));
588
+ const hi = Math.max(portY, round(hBase + half));
589
+ const key = `${col}:${side}`;
590
+ const arr = vByKey.get(key) ?? [];
591
+ arr.push({ lo, hi, inset, lane: 0, set });
592
+ vByKey.set(key, arr);
593
+ };
594
+ for (const p of plans) {
595
+ if (p.isSelfLoop) {
596
+ const lo = Math.min(p.srcPortY, p.tgtPortY);
597
+ const hi = Math.max(p.srcPortY, p.tgtPortY);
598
+ const key = `${p.srcVCol}:right`;
599
+ const arr = vByKey.get(key) ?? [];
600
+ arr.push({ lo, hi, inset: Math.max(p.srcInset, p.tgtInset), lane: 0, set: (x) => {
601
+ p.srcVx = x;
602
+ p.tgtVx = x;
603
+ } });
604
+ vByKey.set(key, arr);
605
+ continue;
606
+ }
607
+ if (p.srcVCol === p.tgtVCol && p.srcSide === p.tgtSide) {
608
+ const lo = Math.min(p.srcPortY, p.tgtPortY);
609
+ const hi = Math.max(p.srcPortY, p.tgtPortY);
610
+ const key = `${p.srcVCol}:${p.srcSide}`;
611
+ const arr = vByKey.get(key) ?? [];
612
+ arr.push({ lo, hi, inset: Math.max(p.srcInset, p.tgtInset), lane: 0, set: (x) => {
613
+ p.srcVx = x;
614
+ p.tgtVx = x;
615
+ } });
616
+ vByKey.set(key, arr);
617
+ continue;
618
+ }
619
+ const hBase = hBaseY(p, rowY, grid);
620
+ const half = hHalfBand(p);
621
+ pushV(p.srcVCol, p.srcSide, p.srcPortY, hBase, half, p.srcInset, (x) => {
622
+ p.srcVx = x;
623
+ });
624
+ pushV(p.tgtVCol, p.tgtSide, p.tgtPortY, hBase, half, p.tgtInset, (x) => {
625
+ p.tgtVx = x;
626
+ });
627
+ }
628
+ for (const [key, items] of vByKey.entries()) {
629
+ const side = key.endsWith(":right") ? "right" : "left";
630
+ const col = Number(key.slice(0, key.lastIndexOf(":")));
631
+ const laneCount = allocateLanes(
632
+ items.map((it) => ({ lo: it.lo, hi: it.hi, set: (l) => {
633
+ it.lane = l;
634
+ } }))
635
+ );
636
+ const left = vGutterLeft(col, colX, grid);
637
+ const right = round(left + UML_COL_GAP);
638
+ const center = round((left + right) / 2);
639
+ const maxBaseGap = Math.max(...items.map((it) => Math.max(V_GUTTER_MARGIN, it.inset + MIN_TERMINAL_STUB)));
640
+ for (const it of items) {
641
+ let x;
642
+ if (side === "right") {
643
+ const lo = left + maxBaseGap;
644
+ const hi = center - 1;
645
+ x = laneCount <= 1 ? lo : lo + it.lane * (hi - lo) / (laneCount - 1);
646
+ } else {
647
+ const hi = right - maxBaseGap;
648
+ const lo = center + 1;
649
+ x = laneCount <= 1 ? hi : hi - it.lane * (hi - lo) / (laneCount - 1);
650
+ }
651
+ it.set(round(x));
652
+ }
653
+ }
654
+ const usableHalfOf = (kind) => Math.max(0, (kind === "pad-top" ? round(UML_PADDING / 2) : round(UML_ROW_GAP / 2)) - 3);
655
+ const gutterKey = (p) => p.hKind === "pad-top" ? "pad-top" : `row:${p.hRow}`;
656
+ const gutterMeta = /* @__PURE__ */ new Map();
657
+ for (const p of plans) {
658
+ if (p.isSelfLoop) continue;
659
+ const key = gutterKey(p);
660
+ if (!gutterMeta.has(key)) gutterMeta.set(key, { base: hBaseY(p, rowY, grid), usableHalf: usableHalfOf(p.hKind) });
661
+ }
662
+ const hRunsByKey = /* @__PURE__ */ new Map();
663
+ const pushRun = (key, run) => {
664
+ const arr = hRunsByKey.get(key) ?? [];
665
+ arr.push(run);
666
+ hRunsByKey.set(key, arr);
667
+ };
668
+ const pinStub = (yy, a, b) => {
669
+ for (const [gk, meta] of gutterMeta.entries()) {
670
+ if (yy >= meta.base - meta.usableHalf - LANE_EPS && yy <= meta.base + meta.usableHalf + LANE_EPS) {
671
+ pushRun(gk, { lo: Math.min(a, b), hi: Math.max(a, b), y: yy, set: () => {
672
+ } });
673
+ }
674
+ }
675
+ };
676
+ for (const p of plans) {
677
+ if (p.isSelfLoop) {
678
+ const sideX = p.srcSide === "right" ? boxEdges(p.srcNode).right : boxEdges(p.srcNode).left;
679
+ pinStub(p.srcPortY, sideX, p.srcVx);
680
+ pinStub(p.tgtPortY, sideX, p.tgtVx);
681
+ continue;
682
+ }
683
+ const key = gutterKey(p);
684
+ if (Math.abs(p.srcVx - p.tgtVx) > LANE_EPS) {
685
+ pushRun(key, { lo: Math.min(p.srcVx, p.tgtVx), hi: Math.max(p.srcVx, p.tgtVx), y: null, set: (y) => {
686
+ p.hY = round(y);
687
+ } });
688
+ } else {
689
+ p.hY = p.srcPortY;
690
+ }
691
+ const srcPortX = p.srcSide === "right" ? boxEdges(p.srcNode).right : boxEdges(p.srcNode).left;
692
+ const tgtPortX = p.tgtSide === "right" ? boxEdges(p.tgtNode).right : boxEdges(p.tgtNode).left;
693
+ pinStub(p.srcPortY, srcPortX, p.srcVx);
694
+ pinStub(p.tgtPortY, tgtPortX, p.tgtVx);
695
+ }
696
+ for (const [key, runs] of hRunsByKey.entries()) {
697
+ const meta = gutterMeta.get(key);
698
+ allocateHorizontal(runs, meta.base, meta.usableHalf);
699
+ }
700
+ for (const p of plans) {
701
+ const srcPort = sidePort(p.srcNode, p.srcSide, p.srcPortY - p.srcNode.cy);
702
+ const tgtPort = sidePort(p.tgtNode, p.tgtSide, p.tgtPortY - p.tgtNode.cy);
703
+ if (p.isSelfLoop) {
704
+ const raw2 = [
705
+ srcPort,
706
+ { x: p.srcVx, y: srcPort.y },
707
+ { x: p.srcVx, y: tgtPort.y },
708
+ tgtPort
709
+ ];
710
+ const portPts2 = simplifyWaypoints(raw2);
711
+ const pts2 = applyGlyphInsets(portPts2, p.srcInset, p.tgtInset);
712
+ const edgeId2 = UML_SELF_LOOP_BASE + p.rel.id;
713
+ elements.push({ edgeId: edgeId2, relId: p.rel.id, points: pts2, kind: "self-loop", title: relTitle(p.rel) });
714
+ reserveSelfLoopText(p.rel, p, textBoxes);
715
+ continue;
716
+ }
717
+ const raw = [
718
+ srcPort,
719
+ { x: p.srcVx, y: srcPort.y },
720
+ { x: p.srcVx, y: p.hY },
721
+ { x: p.tgtVx, y: p.hY },
722
+ { x: p.tgtVx, y: tgtPort.y },
723
+ tgtPort
724
+ ];
725
+ const portPts = simplifyWaypoints(raw);
726
+ const pts = applyGlyphInsets(portPts, p.srcInset, p.tgtInset);
727
+ const edgeId = UML_ASSOC_ROUTE_BASE + p.rel.id;
728
+ elements.push({ edgeId, relId: p.rel.id, points: pts, kind: "route", title: relTitle(p.rel) });
729
+ reserveRouteText(p.rel, p, hBandBounds(p, rowY, grid), textBoxes);
730
+ }
731
+ let maxRight = 0;
732
+ let maxBottom = 0;
733
+ for (const node of nodes) {
734
+ const { right, bottom } = boxEdges(node);
735
+ if (right > maxRight) maxRight = right;
736
+ if (bottom > maxBottom) maxBottom = bottom;
737
+ }
738
+ for (const el of elements) {
739
+ for (const pt of el.points) {
740
+ if (pt.x > maxRight) maxRight = pt.x;
741
+ if (pt.y > maxBottom) maxBottom = pt.y;
742
+ }
743
+ }
744
+ for (const tb of textBoxes) {
745
+ if (tb.x + tb.w > maxRight) maxRight = tb.x + tb.w;
746
+ if (tb.y + tb.h > maxBottom) maxBottom = tb.y + tb.h;
747
+ }
748
+ const canvasW = round(maxRight + UML_PADDING);
749
+ const canvasH = round(maxBottom + UML_PADDING);
750
+ return { nodes, elements, textBoxes, canvasW, canvasH, grid, colX, rowY };
751
+ }
752
+ function reserveRouteText(rel, p, band, out) {
753
+ const textH = round(UML_LABEL_FONT * 1.2);
754
+ const clampY = (y) => round(Math.max(band.lo + 1, Math.min(y, band.hi - 1 - textH)));
755
+ const above = clampY(p.hY - textH - 1);
756
+ const below = clampY(p.hY + 1);
757
+ const srcX = round(p.srcVx + 3);
758
+ const tgtX = round(p.tgtVx + 3);
759
+ if (rel.sourceMultiplicity !== null) {
760
+ out.push(reserveTextBox(rel.id, "src-mult", rel.sourceMultiplicity, srcX, above, "start"));
761
+ }
762
+ if (rel.sourceRole !== null) {
763
+ out.push(reserveTextBox(rel.id, "src-role", rel.sourceRole, srcX, below, "start"));
764
+ }
765
+ if (rel.targetMultiplicity !== null) {
766
+ out.push(reserveTextBox(rel.id, "tgt-mult", rel.targetMultiplicity, tgtX, above, "start"));
767
+ }
768
+ if (rel.targetRole !== null) {
769
+ out.push(reserveTextBox(rel.id, "tgt-role", rel.targetRole, tgtX, below, "start"));
770
+ }
771
+ if (rel.label !== null) {
772
+ const midX = round((p.srcVx + p.tgtVx) / 2);
773
+ out.push(reserveTextBox(rel.id, "label", rel.label, midX, above, "middle"));
774
+ }
775
+ }
776
+ function reserveSelfLoopText(rel, p, out) {
777
+ const x0 = round(p.srcVx + 3);
778
+ const srcY = p.srcPortY;
779
+ const tgtY = p.tgtPortY;
780
+ if (rel.sourceMultiplicity !== null) {
781
+ out.push(reserveTextBox(rel.id, "src-mult", rel.sourceMultiplicity, x0, round(srcY - UML_LABEL_FONT - 1), "start"));
782
+ }
783
+ if (rel.sourceRole !== null) {
784
+ out.push(reserveTextBox(rel.id, "src-role", rel.sourceRole, x0, round(srcY + 1), "start"));
785
+ }
786
+ if (rel.targetMultiplicity !== null) {
787
+ out.push(reserveTextBox(rel.id, "tgt-mult", rel.targetMultiplicity, x0, round(tgtY - UML_LABEL_FONT - 1), "start"));
788
+ }
789
+ if (rel.targetRole !== null) {
790
+ out.push(reserveTextBox(rel.id, "tgt-role", rel.targetRole, x0, round(tgtY + 1), "start"));
791
+ }
792
+ if (rel.label !== null) {
793
+ out.push(reserveTextBox(rel.id, "label", rel.label, x0, round((srcY + tgtY) / 2 - UML_LABEL_FONT / 2), "start"));
794
+ }
795
+ }
796
+
797
+ // src/uml/svg.ts
798
+ var GLYPH_STROKE = "#52525b";
799
+ var LABEL_FILL = "#3f3f46";
800
+ var EDGE_INK = "#71717a";
801
+ var round2 = (n) => Math.round(n * 100) / 100;
802
+ function emitGlyph(gk, tip, dir) {
803
+ switch (gk) {
804
+ case "none":
805
+ return "";
806
+ case "open-arrow": {
807
+ const pts = arrowOpenPoints(tip, dir);
808
+ return `<polyline points="${pts}" fill="none" stroke="${GLYPH_STROKE}" stroke-width="1.5"/>`;
809
+ }
810
+ case "hollow-triangle": {
811
+ const pts = trianglePoints(tip, dir);
812
+ return `<polygon points="${pts}" fill="#fff" stroke="${GLYPH_STROKE}" stroke-width="1.5"/>`;
813
+ }
814
+ case "hollow-diamond": {
815
+ const pts = diamondPoints(tip, dir);
816
+ return `<polygon points="${pts}" fill="#fff" stroke="${GLYPH_STROKE}" stroke-width="1.5"/>`;
817
+ }
818
+ case "solid-diamond": {
819
+ const pts = diamondPoints(tip, dir);
820
+ return `<polygon points="${pts}" fill="${GLYPH_STROKE}" stroke="${GLYPH_STROKE}" stroke-width="1.5"/>`;
821
+ }
822
+ }
823
+ }
824
+ function glyphDir(firstPt, secondPt) {
825
+ const dx = secondPt.x - firstPt.x;
826
+ const dy = secondPt.y - firstPt.y;
827
+ if (Math.abs(dx) >= Math.abs(dy)) {
828
+ return dx >= 0 ? "left" : "right";
829
+ }
830
+ return dy >= 0 ? "up" : "down";
831
+ }
832
+ function featureSvg(f, x, y, font) {
833
+ const prefix = f.visibility !== null ? UML_VIS_GLYPH[f.visibility] + " " : "";
834
+ const text = prefix + f.text;
835
+ const baseline = round2(y + font * 0.8);
836
+ let out = `<text x="${x}" y="${baseline}" font-family="${FONT_FAMILY}" font-size="${font}" fill="${LABEL_FILL}">${xmlEscape(text)}</text>`;
837
+ if (f.isStatic) {
838
+ const lineY = round2(baseline + 1);
839
+ const approxW = round2(text.length * font * 0.6);
840
+ out += `<line x1="${x}" y1="${lineY}" x2="${round2(x + approxW)}" y2="${lineY}" stroke="${LABEL_FILL}" stroke-width="0.8"/>`;
841
+ }
842
+ return out;
843
+ }
844
+ function boxSvg(node, cls) {
845
+ const left = round2(node.cx - node.boxW / 2);
846
+ const top = round2(node.cy - node.boxH / 2);
847
+ const metrics = node.metrics;
848
+ const pieces = [];
849
+ pieces.push(`<title>${xmlEscape(node.title)}</title>`);
850
+ pieces.push(
851
+ `<rect x="${left}" y="${top}" width="${node.boxW}" height="${node.boxH}" rx="2" fill="#fff" stroke="${GLYPH_STROKE}" stroke-width="1.5"/>`
852
+ );
853
+ for (const dy of metrics.dividerYs) {
854
+ const y = round2(top + dy);
855
+ pieces.push(
856
+ `<line x1="${left}" y1="${y}" x2="${round2(left + node.boxW)}" y2="${y}" stroke="${GLYPH_STROKE}" stroke-width="0.75"/>`
857
+ );
858
+ }
859
+ const compartment0 = metrics.rows[0];
860
+ const textX = round2(left + node.boxW / 2);
861
+ let lineIdx = 0;
862
+ if (cls.stereotype !== null) {
863
+ const stereoY = round2(top + compartment0.top + lineIdx * UML_LINE_H + UML_NAME_FONT * 0.8);
864
+ pieces.push(
865
+ `<text x="${textX}" y="${stereoY}" font-family="${FONT_FAMILY}" font-size="${UML_NAME_FONT}" fill="${LABEL_FILL}" text-anchor="middle">${xmlEscape(cls.stereotype)}</text>`
866
+ );
867
+ lineIdx++;
868
+ }
869
+ const nameY = round2(top + compartment0.top + lineIdx * UML_LINE_H + UML_NAME_FONT * 0.8);
870
+ const nameStyle = cls.isAbstract ? ` font-style="italic"` : "";
871
+ const nameText = metrics.rows[0].lines[lineIdx] ?? "";
872
+ pieces.push(
873
+ `<text x="${textX}" y="${nameY}" font-family="${FONT_FAMILY}" font-size="${UML_NAME_FONT}" fill="${LABEL_FILL}" text-anchor="middle"${nameStyle}>${xmlEscape(nameText)}</text>`
874
+ );
875
+ const attrCompartment = metrics.rows[1];
876
+ const attrX = round2(left + UML_BOX_PAD_X);
877
+ for (let i = 0; i < cls.attributes.length; i++) {
878
+ const f = cls.attributes[i];
879
+ const fy = round2(top + attrCompartment.top + i * UML_LINE_H);
880
+ pieces.push(featureSvg(f, attrX, fy, UML_FEAT_FONT));
881
+ }
882
+ const opCompartment = metrics.rows[2];
883
+ const opX = round2(left + UML_BOX_PAD_X);
884
+ for (let i = 0; i < cls.operations.length; i++) {
885
+ const f = cls.operations[i];
886
+ const fy = round2(top + opCompartment.top + i * UML_LINE_H);
887
+ pieces.push(featureSvg(f, opX, fy, UML_FEAT_FONT));
888
+ }
889
+ return `<g data-node-id="c${node.classId}">${pieces.join("")}</g>`;
890
+ }
891
+ function edgeSvg(el, rel, textBoxes) {
892
+ const glyphs = RELATION_GLYPHS[rel.kind];
893
+ const isDashed = glyphs.line === "dashed";
894
+ const stroke = isDashed ? EDGE_STROKE.distant : { width: 1.5, opacity: 0.7 };
895
+ const strokeAttrs = isDashed ? `stroke="${EDGE_INK}" stroke-width="${stroke.width}" stroke-dasharray="4,4" opacity="${stroke.opacity}"` : `stroke="${EDGE_INK}" stroke-width="${stroke.width}" opacity="${stroke.opacity}"`;
896
+ const pts = el.points;
897
+ const pieces = [`<title>${xmlEscape(el.title)}</title>`];
898
+ if (pts.length >= 2) {
899
+ pieces.push(`<polyline points="${pts.map((p) => `${p.x},${p.y}`).join(" ")}" fill="none" ${strokeAttrs}/>`);
900
+ if (glyphs.sourceEnd !== "none") {
901
+ const tip = pts[0];
902
+ const next = pts[1];
903
+ const dir = glyphDir(tip, next);
904
+ pieces.push(emitGlyph(glyphs.sourceEnd, tip, dir));
905
+ }
906
+ if (glyphs.targetEnd !== "none") {
907
+ const tip = pts[pts.length - 1];
908
+ const prev = pts[pts.length - 2];
909
+ const dir = glyphDir(tip, prev);
910
+ pieces.push(emitGlyph(glyphs.targetEnd, tip, dir));
911
+ }
912
+ for (const tb of textBoxes) {
913
+ if (tb.relId !== rel.id) continue;
914
+ const italic = tb.slot === "src-role" || tb.slot === "tgt-role" ? ` font-style="italic"` : "";
915
+ const anchor = tb.anchor === "middle" ? ` text-anchor="middle"` : "";
916
+ const drawX = tb.anchor === "middle" ? round2(tb.x + tb.w / 2) : tb.x;
917
+ const baseline = round2(tb.y + UML_LABEL_FONT * 0.8);
918
+ pieces.push(
919
+ `<text x="${drawX}" y="${baseline}" font-family="${FONT_FAMILY}" font-size="${UML_LABEL_FONT}" fill="${LABEL_FILL}"${anchor}${italic}>${xmlEscape(tb.text)}</text>`
920
+ );
921
+ }
922
+ }
923
+ return `<g data-edge-id="${el.edgeId}">${pieces.join("")}</g>`;
924
+ }
925
+ function makeSwatchLine(isDashed) {
926
+ return (x, y) => {
927
+ const attrs = isDashed ? `stroke="${EDGE_INK}" stroke-width="1.5" stroke-dasharray="4,4"` : `stroke="${EDGE_INK}" stroke-width="1.5"`;
928
+ return `<line x1="${x}" y1="${y}" x2="${x + LEGEND_SWATCH_W}" y2="${y}" ${attrs}/>`;
929
+ };
930
+ }
931
+ function umlLayoutSvg(layout, input, opts = {}) {
932
+ const labels = opts.labels ?? UML_SVG_LABELS_EN;
933
+ const showLegend = opts.legend !== false;
934
+ const classMap = new Map(input.classes.map((c) => [c.id, c]));
935
+ const relMap = new Map(input.relationships.map((r) => [r.id, r]));
936
+ const parts = [];
937
+ for (const node of layout.nodes) {
938
+ const cls = classMap.get(node.classId);
939
+ parts.push(boxSvg(node, cls));
940
+ }
941
+ for (const el of layout.elements) {
942
+ const rel = relMap.get(el.relId);
943
+ if (!rel) continue;
944
+ parts.push(edgeSvg(el, rel, layout.textBoxes));
945
+ }
946
+ let canvasW = layout.canvasW;
947
+ let canvasH = layout.canvasH;
948
+ if (showLegend) {
949
+ const usedKinds = new Set(
950
+ input.relationships.map((r) => r.kind)
951
+ );
952
+ const entries = [];
953
+ for (const kind of UML_RELATIONSHIPS) {
954
+ if (!usedKinds.has(kind)) continue;
955
+ const isDashed = RELATION_GLYPHS[kind].line === "dashed";
956
+ entries.push({
957
+ swatch: makeSwatchLine(isDashed),
958
+ label: labels.legend[kind]
959
+ });
960
+ }
961
+ if (entries.length > 0) {
962
+ const block = legendBlock(entries, canvasH);
963
+ parts.push(block.svg);
964
+ canvasH = block.height;
965
+ canvasW = Math.max(canvasW, block.width);
966
+ }
967
+ }
968
+ const roundedW = round2(canvasW);
969
+ const roundedH = round2(canvasH);
970
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${roundedW}" height="${roundedH}" viewBox="0 0 ${roundedW} ${roundedH}" role="img" aria-label="${xmlEscape(labels.ariaLabel)}">` + parts.join("") + `</svg>`;
971
+ }
972
+
973
+ // src/uml/render.ts
974
+ function umlSvg(input, opts = {}) {
975
+ const layout = computeUmlLayout(input, opts.titleLabels ?? UML_TITLE_LABELS_EN);
976
+ const svg = umlLayoutSvg(layout, input, opts);
977
+ return { svg, layout };
978
+ }
979
+
980
+ export { RELATION_GLYPHS, UML_ASSOC_ROUTE_BASE, UML_BOX_PAD_X, UML_BOX_PAD_Y, UML_COL_GAP, UML_FEAT_FONT, UML_GLYPH_BASE, UML_LABEL_FONT, UML_LINE_H, UML_MIN_BOX_W, UML_NAME_FONT, UML_PADDING, UML_PORT_PITCH, UML_RELATIONSHIPS, UML_ROW_GAP, UML_SELF_LOOP_BASE, UML_SELF_LOOP_W, UML_SEP_PAD, UML_SVG_LABELS_EN, UML_TITLE_LABELS_EN, UML_VISIBILITIES, UML_VIS_GLYPH, UmlValidationError, computeUmlLayout, sideCapacity, sideCapacityIssue, umlIssues, umlLayoutSvg, umlSvg, validateUml };
981
+ //# sourceMappingURL=chunk-MIJTBYX2.js.map
982
+ //# sourceMappingURL=chunk-MIJTBYX2.js.map