compasso 0.4.1 → 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 (84) hide show
  1. package/README.md +86 -5
  2. package/dist/{chunk-WJYYBGZW.js → chunk-2NDET6O5.js} +3 -3
  3. package/dist/{chunk-WJYYBGZW.js.map → chunk-2NDET6O5.js.map} +1 -1
  4. package/dist/{chunk-LR7BXUWM.js → chunk-3RGYLVTN.js} +3 -3
  5. package/dist/{chunk-LR7BXUWM.js.map → chunk-3RGYLVTN.js.map} +1 -1
  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-IPE7JZO5.js → chunk-JBDA7E2O.js} +3 -3
  11. package/dist/{chunk-IPE7JZO5.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-PGUMLTIM.js → chunk-PJHLWSGD.js} +3 -3
  15. package/dist/{chunk-PGUMLTIM.js.map → chunk-PJHLWSGD.js.map} +1 -1
  16. package/dist/{chunk-M4WA6ME7.js → chunk-RDH4XHA2.js} +3 -3
  17. package/dist/{chunk-M4WA6ME7.js.map → chunk-RDH4XHA2.js.map} +1 -1
  18. package/dist/{chunk-TAE2UB7D.js → chunk-WEHUSHVI.js} +4 -40
  19. package/dist/chunk-WEHUSHVI.js.map +1 -0
  20. package/dist/{chunk-FYBABYC7.js → chunk-Z66YUOUM.js} +3 -3
  21. package/dist/{chunk-FYBABYC7.js.map → chunk-Z66YUOUM.js.map} +1 -1
  22. package/dist/core/index.cjs +217 -0
  23. package/dist/core/index.cjs.map +1 -1
  24. package/dist/core/index.d.cts +79 -3
  25. package/dist/core/index.d.ts +79 -3
  26. package/dist/core/index.js +1 -1
  27. package/dist/ecomap/index.js +2 -2
  28. package/dist/fault-tree/index.d.cts +2 -2
  29. package/dist/fault-tree/index.d.ts +2 -2
  30. package/dist/fault-tree/index.js +2 -2
  31. package/dist/fishbone/index.js +2 -2
  32. package/dist/genogram/index.d.cts +2 -2
  33. package/dist/genogram/index.d.ts +2 -2
  34. package/dist/genogram/index.js +2 -2
  35. package/dist/geometry-P-XGqGe7.d.cts +8 -0
  36. package/dist/geometry-P-XGqGe7.d.ts +8 -0
  37. package/dist/grid-BMgUSly1.d.cts +79 -0
  38. package/dist/grid-BMgUSly1.d.ts +79 -0
  39. package/dist/index.cjs +2263 -380
  40. package/dist/index.cjs.map +1 -1
  41. package/dist/index.d.cts +10 -3
  42. package/dist/index.d.ts +10 -3
  43. package/dist/index.js +10 -8
  44. package/dist/labels-Br8yjc3C.d.cts +29 -0
  45. package/dist/labels-Br8yjc3C.d.ts +29 -0
  46. package/dist/labels-D1v1RWZd.d.cts +97 -0
  47. package/dist/labels-D1v1RWZd.d.ts +97 -0
  48. package/dist/layered-DmZluAqe.d.cts +72 -0
  49. package/dist/layered-DmZluAqe.d.ts +72 -0
  50. package/dist/locales/pt-br.cjs +53 -0
  51. package/dist/locales/pt-br.cjs.map +1 -1
  52. package/dist/locales/pt-br.d.cts +7 -1
  53. package/dist/locales/pt-br.d.ts +7 -1
  54. package/dist/locales/pt-br.js +50 -1
  55. package/dist/locales/pt-br.js.map +1 -1
  56. package/dist/org-chart/index.cjs +38 -36
  57. package/dist/org-chart/index.cjs.map +1 -1
  58. package/dist/org-chart/index.d.cts +5 -31
  59. package/dist/org-chart/index.d.ts +5 -31
  60. package/dist/org-chart/index.js +2 -2
  61. package/dist/pedigree/index.d.cts +2 -2
  62. package/dist/pedigree/index.d.ts +2 -2
  63. package/dist/pedigree/index.js +2 -2
  64. package/dist/phylo/index.d.cts +2 -2
  65. package/dist/phylo/index.d.ts +2 -2
  66. package/dist/phylo/index.js +2 -2
  67. package/dist/prisma/index.cjs +882 -0
  68. package/dist/prisma/index.cjs.map +1 -0
  69. package/dist/prisma/index.d.cts +174 -0
  70. package/dist/prisma/index.d.ts +174 -0
  71. package/dist/prisma/index.js +4 -0
  72. package/dist/prisma/index.js.map +1 -0
  73. package/dist/{text-DuO_PwYw.d.cts → text-DDVzpwPZ.d.cts} +1 -8
  74. package/dist/{text-DuO_PwYw.d.ts → text-DDVzpwPZ.d.ts} +1 -8
  75. package/dist/uml/index.cjs +1214 -0
  76. package/dist/uml/index.cjs.map +1 -0
  77. package/dist/uml/index.d.cts +189 -0
  78. package/dist/uml/index.d.ts +189 -0
  79. package/dist/uml/index.js +4 -0
  80. package/dist/uml/index.js.map +1 -0
  81. package/package.json +28 -2
  82. package/dist/chunk-SD4NTRBM.js +0 -171
  83. package/dist/chunk-SD4NTRBM.js.map +0 -1
  84. package/dist/chunk-TAE2UB7D.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -168,6 +168,207 @@ function annotationSwatch(x, yCenter) {
168
168
  return annotationDot(x + LEGEND_SWATCH_W / 2, yCenter);
169
169
  }
170
170
 
171
+ // src/core/layered.ts
172
+ function packSubtree(node, gaps) {
173
+ const { ownHalfL, ownHalfR, children } = node;
174
+ if (children.length === 0) {
175
+ return { halfL: ownHalfL, halfR: ownHalfR, offsets: [] };
176
+ }
177
+ const xs = [0];
178
+ for (let i = 1; i < children.length; i++) {
179
+ xs.push(xs[i - 1] + children[i - 1].halfR + gaps.siblingGap + children[i].halfL);
180
+ }
181
+ const first = children[0];
182
+ const last = children[children.length - 1];
183
+ const axis = (xs[0] + xs[xs.length - 1]) / 2;
184
+ const offsets = xs.map((x) => x - axis);
185
+ const halfL = Math.max(ownHalfL, axis - (xs[0] - first.halfL));
186
+ const halfR = Math.max(ownHalfR, xs[xs.length - 1] + last.halfR - axis);
187
+ return { halfL, halfR, offsets };
188
+ }
189
+ function allocateLanes(items) {
190
+ const lanes = [];
191
+ for (const it of items) {
192
+ let chosen = -1;
193
+ for (let l = 0; l < lanes.length; l++) {
194
+ if (lanes[l].every((o) => it.hi <= o.lo || it.lo >= o.hi)) {
195
+ chosen = l;
196
+ break;
197
+ }
198
+ }
199
+ if (chosen === -1) {
200
+ chosen = lanes.length;
201
+ lanes.push([]);
202
+ }
203
+ lanes[chosen].push({ lo: it.lo, hi: it.hi });
204
+ it.set(chosen);
205
+ }
206
+ return lanes.length;
207
+ }
208
+ function bandStack(rows, opts) {
209
+ const { padding, corridor } = opts;
210
+ if (rows.length === 0) {
211
+ return { rowTop: [], busY: () => 0, height: padding * 2 };
212
+ }
213
+ const rowTop = [padding];
214
+ for (let d = 0; d < rows.length - 1; d++) {
215
+ rowTop.push(rowTop[d] + rows[d].rowH + rows[d].zoneH + corridor);
216
+ }
217
+ const last = rows.length - 1;
218
+ const busY = (d) => rowTop[d + 1] - corridor / 2;
219
+ const height = rowTop[last] + rows[last].rowH + rows[last].zoneH + padding;
220
+ return { rowTop, busY, height };
221
+ }
222
+
223
+ // src/core/glyph.ts
224
+ function dirVec(dir) {
225
+ switch (dir) {
226
+ case "right":
227
+ return [1, 0];
228
+ case "left":
229
+ return [-1, 0];
230
+ case "down":
231
+ return [0, 1];
232
+ case "up":
233
+ return [0, -1];
234
+ }
235
+ }
236
+ function r(n) {
237
+ return Math.round(n * 100) / 100;
238
+ }
239
+ function pt(x, y) {
240
+ return `${r(x)},${r(y)}`;
241
+ }
242
+ var GLYPH_ARROW_LEN = 10;
243
+ var GLYPH_ARROW_HALF = 5;
244
+ var GLYPH_TRI_LEN = 14;
245
+ var GLYPH_TRI_HALF = 8;
246
+ var GLYPH_DIAMOND_LEN = 16;
247
+ var GLYPH_DIAMOND_HALF = 6;
248
+ function arrowOpenPoints(tip, dir, len = GLYPH_ARROW_LEN, half = GLYPH_ARROW_HALF) {
249
+ return _arrowPoints(tip, dir, len, half);
250
+ }
251
+ function arrowFilledPoints(tip, dir, len = GLYPH_ARROW_LEN, half = GLYPH_ARROW_HALF) {
252
+ return _arrowPoints(tip, dir, len, half);
253
+ }
254
+ function _arrowPoints(tip, dir, len, half) {
255
+ const [ux, uy] = dirVec(dir);
256
+ const tx = tip.x;
257
+ const ty = tip.y;
258
+ const bx = tx - ux * len;
259
+ const by = ty - uy * len;
260
+ const px = -uy;
261
+ const py = ux;
262
+ return [
263
+ pt(tx, ty),
264
+ pt(bx + px * half, by + py * half),
265
+ pt(bx - px * half, by - py * half)
266
+ ].join(" ");
267
+ }
268
+ function trianglePoints(tip, dir, len = GLYPH_TRI_LEN, half = GLYPH_TRI_HALF) {
269
+ return _arrowPoints(tip, dir, len, half);
270
+ }
271
+ function diamondPoints(tip, dir, len = GLYPH_DIAMOND_LEN, half = GLYPH_DIAMOND_HALF) {
272
+ const [ux, uy] = dirVec(dir);
273
+ const tx = tip.x;
274
+ const ty = tip.y;
275
+ const mx = tx + ux * (len / 2);
276
+ const my = ty + uy * (len / 2);
277
+ const basex = tx + ux * len;
278
+ const basey = ty + uy * len;
279
+ const px = -uy;
280
+ const py = ux;
281
+ return [
282
+ pt(tx, ty),
283
+ pt(mx - px * half, my - py * half),
284
+ pt(basex, basey),
285
+ pt(mx + px * half, my + py * half)
286
+ ].join(" ");
287
+ }
288
+ function glyphInset(_dir, kind) {
289
+ switch (kind) {
290
+ case "arrow":
291
+ return GLYPH_ARROW_LEN;
292
+ case "triangle":
293
+ return GLYPH_TRI_LEN;
294
+ case "diamond":
295
+ return GLYPH_DIAMOND_LEN;
296
+ }
297
+ }
298
+
299
+ // src/core/compartment.ts
300
+ function round2(n) {
301
+ return Math.round(n * 100) / 100;
302
+ }
303
+ function measureCompartmentBox(compartments, opts) {
304
+ const { padX, padY, lineH, minW } = opts;
305
+ let maxLineW = 0;
306
+ for (const c of compartments) {
307
+ for (const line of c.lines) {
308
+ const w = estimateTextWidth(line, c.font);
309
+ if (w > maxLineW) maxLineW = w;
310
+ }
311
+ }
312
+ const boxW = round2(Math.max(minW, maxLineW + 2 * padX));
313
+ const rows = [];
314
+ const dividerYs = [];
315
+ let y = 0;
316
+ for (let i = 0; i < compartments.length; i++) {
317
+ const c = compartments[i];
318
+ const lineCount = c.lines.length > 0 ? c.lines.length : 1;
319
+ const compartmentH = padY + lineCount * lineH + padY;
320
+ rows.push({
321
+ lines: c.lines,
322
+ top: round2(y + padY),
323
+ font: c.font
324
+ });
325
+ y += compartmentH;
326
+ if (i < compartments.length - 1) {
327
+ dividerYs.push(round2(y));
328
+ }
329
+ }
330
+ const boxH = round2(y);
331
+ return { boxW, boxH, rows, dividerYs };
332
+ }
333
+
334
+ // src/core/grid.ts
335
+ function round3(n) {
336
+ return Math.round(n * 100) / 100;
337
+ }
338
+ function packGrid(cells, gaps) {
339
+ if (cells.length === 0) {
340
+ return { cellX: [], cellY: [], colW: [], rowH: [] };
341
+ }
342
+ const { colGap, rowGap } = gaps;
343
+ let maxCol = 0;
344
+ let maxRow = 0;
345
+ for (const c of cells) {
346
+ if (c.col > maxCol) maxCol = c.col;
347
+ if (c.row > maxRow) maxRow = c.row;
348
+ }
349
+ const colW = Array.from({ length: maxCol + 1 }, () => 0);
350
+ const rowH = Array.from({ length: maxRow + 1 }, () => 0);
351
+ for (const c of cells) {
352
+ if (c.w > colW[c.col]) colW[c.col] = c.w;
353
+ if (c.h > rowH[c.row]) rowH[c.row] = c.h;
354
+ }
355
+ const colX = Array.from({ length: maxCol + 1 }, () => 0);
356
+ for (let c = 1; c <= maxCol; c++) {
357
+ colX[c] = round3(colX[c - 1] + colW[c - 1] + colGap);
358
+ }
359
+ const rowY = Array.from({ length: maxRow + 1 }, () => 0);
360
+ for (let r2 = 1; r2 <= maxRow; r2++) {
361
+ rowY[r2] = round3(rowY[r2 - 1] + rowH[r2 - 1] + rowGap);
362
+ }
363
+ const cellX = cells.map(
364
+ (c) => round3(colX[c.col] + colW[c.col] / 2)
365
+ );
366
+ const cellY = cells.map(
367
+ (c) => round3(rowY[c.row] + rowH[c.row] / 2)
368
+ );
369
+ return { cellX, cellY, colW, rowH };
370
+ }
371
+
171
372
  // src/genogram/types.ts
172
373
  var UNION_STATUSES = [
173
374
  "married",
@@ -302,7 +503,7 @@ function arrivalOffset(slot, total) {
302
503
  const sign = slot % 2 === 1 ? -1 : 1;
303
504
  return sign * Math.ceil(slot / 2) * step;
304
505
  }
305
- function allocateLanes(items) {
506
+ function allocateLanes2(items) {
306
507
  const lanes = [];
307
508
  for (const it of items) {
308
509
  let chosen = -1;
@@ -333,12 +534,12 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
333
534
  const validUnions = unions.filter((u) => ids.has(u.personAId) && ids.has(u.personBId) && u.personAId !== u.personBId).sort((a, b) => a.id - b.id);
334
535
  const coupleUnions = validUnions.filter((u) => u.status !== "coparental");
335
536
  const validLinks = parentLinks.filter((l) => ids.has(l.parentId) && ids.has(l.childId) && l.parentId !== l.childId).sort((a, b) => a.id - b.id);
336
- const validRels = relationships.filter((r) => ids.has(r.fromPersonId) && ids.has(r.toPersonId) && r.fromPersonId !== r.toPersonId).sort((a, b) => a.id - b.id);
537
+ const validRels = relationships.filter((r2) => ids.has(r2.fromPersonId) && ids.has(r2.toPersonId) && r2.fromPersonId !== r2.toPersonId).sort((a, b) => a.id - b.id);
337
538
  const relClass = new Map(
338
- validRels.map((r) => [r.id, classifyRelationshipType(r.type, kinship)])
539
+ validRels.map((r2) => [r2.id, classifyRelationshipType(r2.type, kinship)])
339
540
  );
340
- const bondRels = validRels.filter((r) => relClass.get(r.id) === "bond");
341
- const parentageRels = validRels.filter((r) => relClass.get(r.id) === "parentage");
541
+ const bondRels = validRels.filter((r2) => relClass.get(r2.id) === "bond");
542
+ const parentageRels = validRels.filter((r2) => relClass.get(r2.id) === "parentage");
342
543
  const realLinks = validLinks.map((l) => ({
343
544
  parentId: l.parentId,
344
545
  childId: l.childId,
@@ -348,22 +549,22 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
348
549
  }));
349
550
  const declaredPairs = new Set(validLinks.map((l) => pairKey(l.parentId, l.childId)));
350
551
  const promotedByPair = /* @__PURE__ */ new Map();
351
- for (const r of parentageRels) {
352
- const key = pairKey(r.fromPersonId, r.toPersonId);
552
+ for (const r2 of parentageRels) {
553
+ const key = pairKey(r2.fromPersonId, r2.toPersonId);
353
554
  if (declaredPairs.has(key)) continue;
354
555
  if (promotedByPair.has(key)) continue;
355
- const ga = genById.get(r.fromPersonId) ?? null;
356
- const gb = genById.get(r.toPersonId) ?? null;
556
+ const ga = genById.get(r2.fromPersonId) ?? null;
557
+ const gb = genById.get(r2.toPersonId) ?? null;
357
558
  let parentId;
358
559
  let childId;
359
560
  if (ga !== null && gb !== null && ga !== gb) {
360
- [parentId, childId] = ga < gb ? [r.fromPersonId, r.toPersonId] : [r.toPersonId, r.fromPersonId];
561
+ [parentId, childId] = ga < gb ? [r2.fromPersonId, r2.toPersonId] : [r2.toPersonId, r2.fromPersonId];
361
562
  } else {
362
- const fromIsChild = relationshipTypeTokens(r.type).some((t) => kinship.childWords.has(t));
363
- [parentId, childId] = fromIsChild ? [r.toPersonId, r.fromPersonId] : [r.fromPersonId, r.toPersonId];
563
+ const fromIsChild = relationshipTypeTokens(r2.type).some((t) => kinship.childWords.has(t));
564
+ [parentId, childId] = fromIsChild ? [r2.toPersonId, r2.fromPersonId] : [r2.fromPersonId, r2.toPersonId];
364
565
  }
365
566
  if (parentId === childId) continue;
366
- promotedByPair.set(key, { parentId, childId, quality: r.quality, edgeId: PROMOTED_REL_ID_BASE + r.id, annotated: r.annotated ?? false });
567
+ promotedByPair.set(key, { parentId, childId, quality: r2.quality, edgeId: PROMOTED_REL_ID_BASE + r2.id, annotated: r2.annotated ?? false });
367
568
  }
368
569
  const allLinks = [...realLinks, ...promotedByPair.values()].sort((a, b) => a.edgeId - b.edgeId);
369
570
  const coupleByPair = /* @__PURE__ */ new Map();
@@ -385,9 +586,9 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
385
586
  hasTie.add(l.parentId);
386
587
  hasTie.add(l.childId);
387
588
  }
388
- for (const r of bondRels) {
389
- hasTie.add(r.fromPersonId);
390
- hasTie.add(r.toPersonId);
589
+ for (const r2 of bondRels) {
590
+ hasTie.add(r2.fromPersonId);
591
+ hasTie.add(r2.toPersonId);
391
592
  }
392
593
  const isIsolated = (id) => !hasTie.has(id);
393
594
  const byGen = /* @__PURE__ */ new Map();
@@ -403,13 +604,13 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
403
604
  });
404
605
  const rowCount = genKeys.length;
405
606
  const rowOfPerson = /* @__PURE__ */ new Map();
406
- genKeys.forEach((g, r) => byGen.get(g).forEach((p) => rowOfPerson.set(p.id, r)));
407
- const rowBlocks = genKeys.map((g, r) => {
607
+ genKeys.forEach((g, r2) => byGen.get(g).forEach((p) => rowOfPerson.set(p.id, r2)));
608
+ const rowBlocks = genKeys.map((g, r2) => {
408
609
  const members = byGen.get(g).map((p) => p.id);
409
610
  const adj = /* @__PURE__ */ new Map();
410
611
  for (const id of members) adj.set(id, []);
411
612
  for (const u of coupleUnions) {
412
- if (rowOfPerson.get(u.personAId) === r && rowOfPerson.get(u.personBId) === r) {
613
+ if (rowOfPerson.get(u.personAId) === r2 && rowOfPerson.get(u.personBId) === r2) {
413
614
  adj.get(u.personAId).push(u.personBId);
414
615
  adj.get(u.personBId).push(u.personAId);
415
616
  }
@@ -465,9 +666,9 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
465
666
  for (const rl of bondRels) if (rl.fromPersonId === id && rl.toPersonId === other || rl.toPersonId === id && rl.fromPersonId === other) w = Math.max(w, 0.5);
466
667
  return w;
467
668
  };
468
- const sweep = (r, ref) => {
669
+ const sweep = (r2, ref) => {
469
670
  const pos = flattened();
470
- const blocks = rowBlocks[r];
671
+ const blocks = rowBlocks[r2];
471
672
  const bcOf = (b) => {
472
673
  let sum = 0;
473
674
  let wsum = 0;
@@ -483,23 +684,23 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
483
684
  };
484
685
  const keyed = blocks.map((b) => ({ b, bc: bcOf(b), minId: Math.min(...b) }));
485
686
  keyed.sort((x, y) => x.bc !== y.bc ? x.bc - y.bc : x.minId - y.minId);
486
- rowBlocks[r] = keyed.map((k) => k.b);
687
+ rowBlocks[r2] = keyed.map((k) => k.b);
487
688
  };
488
689
  if (rowCount >= 2) {
489
- for (let r = 1; r < rowCount; r++) sweep(r, r - 1);
490
- for (let r = rowCount - 2; r >= 0; r--) sweep(r, r + 1);
690
+ for (let r2 = 1; r2 < rowCount; r2++) sweep(r2, r2 - 1);
691
+ for (let r2 = rowCount - 2; r2 >= 0; r2--) sweep(r2, r2 + 1);
491
692
  }
492
- for (let r = 0; r < rowCount; r++) {
493
- const blocks = rowBlocks[r];
693
+ for (let r2 = 0; r2 < rowCount; r2++) {
694
+ const blocks = rowBlocks[r2];
494
695
  const connected = blocks.filter((b) => !(b.length === 1 && isIsolated(b[0])));
495
696
  const isolated = blocks.filter((b) => b.length === 1 && isIsolated(b[0])).sort((a, b) => a[0] - b[0]);
496
- rowBlocks[r] = [...connected, ...isolated];
697
+ rowBlocks[r2] = [...connected, ...isolated];
497
698
  }
498
699
  const colOf = /* @__PURE__ */ new Map();
499
700
  let colCount = 0;
500
- for (let r = 0; r < rowCount; r++) {
701
+ for (let r2 = 0; r2 < rowCount; r2++) {
501
702
  let c = 0;
502
- for (const b of rowBlocks[r]) for (const id of b) colOf.set(id, c++);
703
+ for (const b of rowBlocks[r2]) for (const id of b) colOf.set(id, c++);
503
704
  colCount = Math.max(colCount, c);
504
705
  }
505
706
  const colOrThrow = (id) => colOf.get(id);
@@ -526,8 +727,8 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
526
727
  }
527
728
  const rowMaxLines = Array.from({ length: rowCount }, () => 1);
528
729
  for (const p of people) {
529
- const r = rowOfPerson.get(p.id);
530
- rowMaxLines[r] = Math.max(rowMaxLines[r], measured.get(p.id).lines.length);
730
+ const r2 = rowOfPerson.get(p.id);
731
+ rowMaxLines[r2] = Math.max(rowMaxLines[r2], measured.get(p.id).lines.length);
531
732
  }
532
733
  const planned = [];
533
734
  const gutterReqs = [];
@@ -646,10 +847,10 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
646
847
  {
647
848
  const byRow = /* @__PURE__ */ new Map();
648
849
  for (const u of barUnions) {
649
- const r = rowOfPerson.get(u.personAId);
650
- (byRow.get(r) ?? byRow.set(r, []).get(r)).push(u);
850
+ const r2 = rowOfPerson.get(u.personAId);
851
+ (byRow.get(r2) ?? byRow.set(r2, []).get(r2)).push(u);
651
852
  }
652
- for (const [r, rowUnions] of byRow) {
853
+ for (const [r2, rowUnions] of byRow) {
653
854
  const unionsOfPerson = /* @__PURE__ */ new Map();
654
855
  for (const u of rowUnions) {
655
856
  (unionsOfPerson.get(u.personAId) ?? unionsOfPerson.set(u.personAId, []).get(u.personAId)).push(u);
@@ -675,7 +876,7 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
675
876
  }
676
877
  comp.sort((a, b) => a.id - b.id);
677
878
  comp.forEach((u, i) => unionDipLevel.set(u.id, i));
678
- rowDipLevels[r] = Math.max(rowDipLevels[r], comp.length - 1);
879
+ rowDipLevels[r2] = Math.max(rowDipLevels[r2], comp.length - 1);
679
880
  }
680
881
  }
681
882
  }
@@ -900,19 +1101,19 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
900
1101
  });
901
1102
  }
902
1103
  const bondGroups = /* @__PURE__ */ new Map();
903
- for (const r of bondRels) {
904
- const style = qualityLineStyle(r.quality, opts.qualityLexicon);
905
- const lo = Math.min(r.fromPersonId, r.toPersonId);
906
- const hi = Math.max(r.fromPersonId, r.toPersonId);
1104
+ for (const r2 of bondRels) {
1105
+ const style = qualityLineStyle(r2.quality, opts.qualityLexicon);
1106
+ const lo = Math.min(r2.fromPersonId, r2.toPersonId);
1107
+ const hi = Math.max(r2.fromPersonId, r2.toPersonId);
907
1108
  const key = `${lo}|${hi}|${style}`;
908
- const title = r.quality !== null ? `${r.type} \xB7 ${r.quality}` : r.type;
1109
+ const title = r2.quality !== null ? `${r2.type} \xB7 ${r2.quality}` : r2.type;
909
1110
  const g = bondGroups.get(key);
910
1111
  if (g === void 0) {
911
- bondGroups.set(key, { relIds: [r.id], titles: [title], style, aId: r.fromPersonId, bId: r.toPersonId, annotated: r.annotated ?? false });
1112
+ bondGroups.set(key, { relIds: [r2.id], titles: [title], style, aId: r2.fromPersonId, bId: r2.toPersonId, annotated: r2.annotated ?? false });
912
1113
  } else {
913
- g.relIds.push(r.id);
1114
+ g.relIds.push(r2.id);
914
1115
  g.titles.push(title);
915
- if (r.annotated) g.annotated = true;
1116
+ if (r2.annotated) g.annotated = true;
916
1117
  }
917
1118
  }
918
1119
  const bondList = [...bondGroups.values()].sort((a, b) => Math.max(...a.relIds) - Math.max(...b.relIds));
@@ -947,7 +1148,7 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
947
1148
  const laneCount = (gutter, side) => gutterLaneCount.get(`${gutter}:${side}`) ?? 0;
948
1149
  const geo = {
949
1150
  cx: (c) => colCenterX[c],
950
- cy: (r) => rowCenterY[r],
1151
+ cy: (r2) => rowCenterY[r2],
951
1152
  glyphRight: (id) => colCenterX[colOrThrow(id)] + NODE_SIZE / 2,
952
1153
  glyphLeft: (id) => colCenterX[colOrThrow(id)] - NODE_SIZE / 2,
953
1154
  gutterCenterX: (g) => {
@@ -965,7 +1166,7 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
965
1166
  },
966
1167
  corridorLaneY: (req) => corridorTopY[req.corridor] + CORRIDOR_BASE / 2 + req.lane * LANE_H,
967
1168
  /** Y of a dipped serial-union bar (level ≥ 1): below the row's glyph bottom edge. */
968
- unionDipY: (r, level) => rowCenterY[r] + NODE_SIZE / 2 + level * UNION_STAGGER
1169
+ unionDipY: (r2, level) => rowCenterY[r2] + NODE_SIZE / 2 + level * UNION_STAGGER
969
1170
  };
970
1171
  const gutterGroups = /* @__PURE__ */ new Map();
971
1172
  for (const req of gutterReqs) {
@@ -975,7 +1176,7 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
975
1176
  for (const [k, reqs] of gutterGroups) {
976
1177
  gutterLaneCount.set(
977
1178
  k,
978
- allocateLanes(reqs.map((req) => ({ lo: req.rowLo, hi: req.rowHi, set: (lane) => req.lane = lane })))
1179
+ allocateLanes2(reqs.map((req) => ({ lo: req.rowLo, hi: req.rowHi, set: (lane) => req.lane = lane })))
979
1180
  );
980
1181
  }
981
1182
  const gutterWidth = (g) => {
@@ -996,7 +1197,7 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
996
1197
  const corridorGroups = /* @__PURE__ */ new Map();
997
1198
  for (const req of corridorReqs) (corridorGroups.get(req.corridor) ?? corridorGroups.set(req.corridor, []).get(req.corridor)).push(req);
998
1199
  for (const [k, reqs] of corridorGroups) {
999
- corridorLaneCount[k] = allocateLanes(
1200
+ corridorLaneCount[k] = allocateLanes2(
1000
1201
  reqs.map((req) => {
1001
1202
  const [lo, hi] = req.xRange();
1002
1203
  return { lo, hi, set: (lane) => req.lane = lane };
@@ -1008,20 +1209,20 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
1008
1209
  if (lanes > 0) return CORRIDOR_BASE + lanes * LANE_H;
1009
1210
  return k < rowCount - 1 ? CORRIDOR_BASE : 0;
1010
1211
  };
1011
- const rowDipReserve = (r) => rowDipLevels[r] > 0 ? rowDipLevels[r] * UNION_STAGGER + 6 : 0;
1212
+ const rowDipReserve = (r2) => rowDipLevels[r2] > 0 ? rowDipLevels[r2] * UNION_STAGGER + 6 : 0;
1012
1213
  let cursorY = PADDING;
1013
- for (let r = 0; r < rowCount; r++) {
1014
- rowCenterY[r] = cursorY + NODE_SIZE / 2;
1015
- rowLabelTopY[r] = cursorY + NODE_SIZE + rowDipReserve(r) + LABEL_GAP_TOP;
1016
- cursorY = rowLabelTopY[r] + rowMaxLines[r] * LABEL_LINE_H;
1017
- corridorTopY[r] = cursorY;
1018
- cursorY += corridorHeight(r);
1214
+ for (let r2 = 0; r2 < rowCount; r2++) {
1215
+ rowCenterY[r2] = cursorY + NODE_SIZE / 2;
1216
+ rowLabelTopY[r2] = cursorY + NODE_SIZE + rowDipReserve(r2) + LABEL_GAP_TOP;
1217
+ cursorY = rowLabelTopY[r2] + rowMaxLines[r2] * LABEL_LINE_H;
1218
+ corridorTopY[r2] = cursorY;
1219
+ cursorY += corridorHeight(r2);
1019
1220
  }
1020
1221
  const width = dims.width;
1021
1222
  const height = cursorY + PADDING;
1022
1223
  const nodes = [...people].sort((a, b) => a.id - b.id).map((p) => {
1023
1224
  const m = measured.get(p.id);
1024
- const r = rowOfPerson.get(p.id);
1225
+ const r2 = rowOfPerson.get(p.id);
1025
1226
  const c = colOrThrow(p.id);
1026
1227
  return {
1027
1228
  personId: p.id,
@@ -1030,10 +1231,10 @@ function computeGenogramLayout(people, unions, parentLinks, relationships, opts
1030
1231
  deceased: p.deceased,
1031
1232
  annotated: p.annotated ?? false,
1032
1233
  cx: colCenterX[c],
1033
- cy: rowCenterY[r],
1234
+ cy: rowCenterY[r2],
1034
1235
  size: NODE_SIZE,
1035
1236
  labelLines: m.lines,
1036
- labelTop: rowLabelTopY[r]
1237
+ labelTop: rowLabelTopY[r2]
1037
1238
  };
1038
1239
  });
1039
1240
  const elements = planned.map((pl) => ({
@@ -1318,7 +1519,7 @@ var SINGLE_RING_MAX = 8;
1318
1519
  var NODE_STROKE = "#52525b";
1319
1520
  var LABEL_FILL = "#3f3f46";
1320
1521
  var EDGE_INK2 = "#71717a";
1321
- var round2 = (n) => Math.round(n * 100) / 100;
1522
+ var round4 = (n) => Math.round(n * 100) / 100;
1322
1523
  function arrowHead(tipX, tipY, ux, uy, opacity) {
1323
1524
  const LEN = 9;
1324
1525
  const HALF_W = 4.5;
@@ -1327,9 +1528,9 @@ function arrowHead(tipX, tipY, ux, uy, opacity) {
1327
1528
  const px = -uy;
1328
1529
  const py = ux;
1329
1530
  const points = [
1330
- `${round2(tipX)},${round2(tipY)}`,
1331
- `${round2(bx + px * HALF_W)},${round2(by + py * HALF_W)}`,
1332
- `${round2(bx - px * HALF_W)},${round2(by - py * HALF_W)}`
1531
+ `${round4(tipX)},${round4(tipY)}`,
1532
+ `${round4(bx + px * HALF_W)},${round4(by + py * HALF_W)}`,
1533
+ `${round4(bx - px * HALF_W)},${round4(by - py * HALF_W)}`
1333
1534
  ].join(" ");
1334
1535
  return `<polygon points="${points}" fill="${EDGE_INK2}" fill-opacity="${opacity}"/>`;
1335
1536
  }
@@ -1375,16 +1576,16 @@ function ecomapSvg(input, opts = {}) {
1375
1576
  sameRingRadius(twoRings ? 2 : 1)
1376
1577
  );
1377
1578
  if (twoRings) {
1378
- const crossDist = (r) => Math.sqrt(r * r + (r + ringStep) ** 2 - 2 * r * (r + ringStep) * Math.cos(stepAngle));
1579
+ const crossDist = (r2) => Math.sqrt(r2 * r2 + (r2 + ringStep) ** 2 - 2 * r2 * (r2 + ringStep) * Math.cos(stepAngle));
1379
1580
  while (crossDist(innerR) < safeDist) innerR += 8;
1380
1581
  }
1381
1582
  const outerR = innerR + ringStep;
1382
1583
  for (let i = 0; i < n; i++) {
1383
1584
  const s = sats[i];
1384
1585
  s.angle = -Math.PI / 2 + i * Math.PI * 2 / n;
1385
- const r = twoRings && i % 2 === 1 ? outerR : innerR;
1386
- s.x = r * Math.cos(s.angle);
1387
- s.y = r * Math.sin(s.angle);
1586
+ const r2 = twoRings && i % 2 === 1 ? outerR : innerR;
1587
+ s.x = r2 * Math.cos(s.angle);
1588
+ s.y = r2 * Math.sin(s.angle);
1388
1589
  }
1389
1590
  let minX = -centerR;
1390
1591
  let minY = -centerR;
@@ -1419,7 +1620,7 @@ function ecomapSvg(input, opts = {}) {
1419
1620
  const dashAttr = ink.dash === null ? "" : ` stroke-dasharray="${ink.dash[0]} ${ink.dash[1]}"`;
1420
1621
  const title = s.tie.title ?? (s.tie.quality !== null ? `${s.tie.label} \xB7 ${s.tie.quality}` : s.tie.label);
1421
1622
  const body = [
1422
- `<line x1="${round2(x1)}" y1="${round2(y1)}" x2="${round2(x2)}" y2="${round2(y2)}" stroke="${EDGE_INK2}" stroke-width="${ink.width}" stroke-opacity="${ink.opacity}"${dashAttr}/>`
1623
+ `<line x1="${round4(x1)}" y1="${round4(y1)}" x2="${round4(x2)}" y2="${round4(y2)}" stroke="${EDGE_INK2}" stroke-width="${ink.width}" stroke-opacity="${ink.opacity}"${dashAttr}/>`
1423
1624
  ];
1424
1625
  if (s.tie.direction === "in" || s.tie.direction === "both") {
1425
1626
  body.push(arrowHead(x2, y2, ux, uy, ink.opacity));
@@ -1431,19 +1632,19 @@ function ecomapSvg(input, opts = {}) {
1431
1632
  }
1432
1633
  {
1433
1634
  const tspans = centerLines.map(
1434
- (line, i) => `<tspan x="${round2(cx)}" y="${round2(cy - (centerLines.length - 1) * LINE_H / 2 + i * LINE_H + fontSize * 0.32)}">${xmlEscape(line)}</tspan>`
1635
+ (line, i) => `<tspan x="${round4(cx)}" y="${round4(cy - (centerLines.length - 1) * LINE_H / 2 + i * LINE_H + fontSize * 0.32)}">${xmlEscape(line)}</tspan>`
1435
1636
  ).join("");
1436
1637
  parts.push(
1437
- `<g data-individual-id="center"><title>${xmlEscape(input.centerLabel)}</title><circle cx="${round2(cx)}" cy="${round2(cy)}" r="${round2(centerR)}" fill="transparent" stroke="${NODE_STROKE}" stroke-width="2"/><text text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${fontSize}" fill="${LABEL_FILL}">${tspans}</text></g>`
1638
+ `<g data-individual-id="center"><title>${xmlEscape(input.centerLabel)}</title><circle cx="${round4(cx)}" cy="${round4(cy)}" r="${round4(centerR)}" fill="transparent" stroke="${NODE_STROKE}" stroke-width="2"/><text text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${fontSize}" fill="${LABEL_FILL}">${tspans}</text></g>`
1438
1639
  );
1439
1640
  }
1440
1641
  for (const s of sats) {
1441
1642
  const tspans = s.lines.map(
1442
- (line, i) => `<tspan x="${round2(s.x)}" y="${round2(s.y - (s.lines.length - 1) * LINE_H / 2 + i * LINE_H + fontSize * 0.32)}">${xmlEscape(line)}</tspan>`
1643
+ (line, i) => `<tspan x="${round4(s.x)}" y="${round4(s.y - (s.lines.length - 1) * LINE_H / 2 + i * LINE_H + fontSize * 0.32)}">${xmlEscape(line)}</tspan>`
1443
1644
  ).join("");
1444
1645
  const pieces = [
1445
1646
  `<title>${xmlEscape(s.tie.label)}</title>`,
1446
- `<ellipse cx="${round2(s.x)}" cy="${round2(s.y)}" rx="${round2(s.rx)}" ry="${round2(s.ry)}" fill="transparent" stroke="${NODE_STROKE}" stroke-width="1.5"/>`,
1647
+ `<ellipse cx="${round4(s.x)}" cy="${round4(s.y)}" rx="${round4(s.rx)}" ry="${round4(s.ry)}" fill="transparent" stroke="${NODE_STROKE}" stroke-width="1.5"/>`,
1447
1648
  `<text text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${fontSize}" fill="${LABEL_FILL}">${tspans}</text>`
1448
1649
  ];
1449
1650
  if (s.tie.annotated) {
@@ -1654,7 +1855,7 @@ function faultTreeIssues(input) {
1654
1855
  );
1655
1856
  }
1656
1857
  }
1657
- const GRAPH_BLOCKING2 = /* @__PURE__ */ new Set([
1858
+ const GRAPH_BLOCKING3 = /* @__PURE__ */ new Set([
1658
1859
  "duplicate-id",
1659
1860
  "unknown-input",
1660
1861
  "gate-on-non-intermediate",
@@ -1663,7 +1864,7 @@ function faultTreeIssues(input) {
1663
1864
  "inhibit-condition",
1664
1865
  "top-not-intermediate"
1665
1866
  ]);
1666
- if (!issues.some((i) => GRAPH_BLOCKING2.has(i.code))) {
1867
+ if (!issues.some((i) => GRAPH_BLOCKING3.has(i.code))) {
1667
1868
  const gateOf = (eventId) => gates.find((g) => g.eventId === eventId);
1668
1869
  const reachable = /* @__PURE__ */ new Set();
1669
1870
  const queue = [input.topId];
@@ -1743,7 +1944,7 @@ var FT_DROP_ID_BASE = 2e6;
1743
1944
  var FT_BUS_ID_BASE = 3e6;
1744
1945
  var FT_RISER_ID_BASE = 4e6;
1745
1946
  var FT_CONDITION_ID_BASE = 5e6;
1746
- var round3 = (n) => Math.round(n * 100) / 100;
1947
+ var round5 = (n) => Math.round(n * 100) / 100;
1747
1948
  function wrapLeafLabel(displayLabel) {
1748
1949
  const perLine = Math.min(20, Math.max(12, Math.ceil(displayLabel.length / 2) + 2));
1749
1950
  return wrapLabel(displayLabel, perLine);
@@ -1930,14 +2131,14 @@ function computeFaultTreeLayout(input, opts = {}) {
1930
2131
  eventId: inst.event.id,
1931
2132
  kind: inst.event.kind,
1932
2133
  instance: instanceOf(inst),
1933
- cx: round3(inst.cx),
1934
- top: round3(top),
1935
- nodeW: round3(inst.m.nodeW),
1936
- nodeH: round3(inst.m.nodeH),
1937
- glyphW: round3(inst.m.glyphW),
1938
- glyphH: round3(inst.m.glyphH),
2134
+ cx: round5(inst.cx),
2135
+ top: round5(top),
2136
+ nodeW: round5(inst.m.nodeW),
2137
+ nodeH: round5(inst.m.nodeH),
2138
+ glyphW: round5(inst.m.glyphW),
2139
+ glyphH: round5(inst.m.glyphH),
1939
2140
  labelLines: inst.m.labelLines,
1940
- labelTop: labelTop === null ? null : round3(labelTop),
2141
+ labelTop: labelTop === null ? null : round5(labelTop),
1941
2142
  code: inst.m.code,
1942
2143
  depth: inst.depth,
1943
2144
  title: inst.title
@@ -1955,8 +2156,8 @@ function computeFaultTreeLayout(input, opts = {}) {
1955
2156
  gates.push({
1956
2157
  gateId: g.id,
1957
2158
  type: g.type,
1958
- cx: round3(inst.cx),
1959
- top: round3(gateTop),
2159
+ cx: round5(inst.cx),
2160
+ top: round5(gateTop),
1960
2161
  glyphW,
1961
2162
  glyphH,
1962
2163
  title,
@@ -1973,8 +2174,8 @@ function computeFaultTreeLayout(input, opts = {}) {
1973
2174
  edgeId: FT_CONDITION_ID_BASE + g.id,
1974
2175
  kind: "condition",
1975
2176
  points: [
1976
- { x: round3(inst.cx + INHIBIT_W / 2), y: round3(gateTop + INHIBIT_CY) },
1977
- { x: round3(ovalLeft), y: round3(gateTop + INHIBIT_CY) }
2177
+ { x: round5(inst.cx + INHIBIT_W / 2), y: round5(gateTop + INHIBIT_CY) },
2178
+ { x: round5(ovalLeft), y: round5(gateTop + INHIBIT_CY) }
1978
2179
  ],
1979
2180
  instance: null,
1980
2181
  title: titleLabels.condition
@@ -1984,8 +2185,8 @@ function computeFaultTreeLayout(input, opts = {}) {
1984
2185
  edgeId: FT_STEM_ID_BASE + g.id,
1985
2186
  kind: "stem",
1986
2187
  points: [
1987
- { x: round3(inst.cx), y: round3(rowTop[d] + inst.m.nodeH) },
1988
- { x: round3(inst.cx), y: round3(gateTop) }
2188
+ { x: round5(inst.cx), y: round5(rowTop[d] + inst.m.nodeH) },
2189
+ { x: round5(inst.cx), y: round5(gateTop) }
1989
2190
  ],
1990
2191
  instance: null,
1991
2192
  title
@@ -1995,8 +2196,8 @@ function computeFaultTreeLayout(input, opts = {}) {
1995
2196
  edgeId: FT_DROP_ID_BASE + g.id,
1996
2197
  kind: "drop",
1997
2198
  points: [
1998
- { x: round3(inst.cx), y: round3(gateTop + glyphH) },
1999
- { x: round3(inst.cx), y: round3(by) }
2199
+ { x: round5(inst.cx), y: round5(gateTop + glyphH) },
2200
+ { x: round5(inst.cx), y: round5(by) }
2000
2201
  ],
2001
2202
  instance: null,
2002
2203
  title
@@ -2007,8 +2208,8 @@ function computeFaultTreeLayout(input, opts = {}) {
2007
2208
  edgeId: FT_BUS_ID_BASE + g.id,
2008
2209
  kind: "bus",
2009
2210
  points: [
2010
- { x: round3(Math.min(...xs)), y: round3(by) },
2011
- { x: round3(Math.max(...xs)), y: round3(by) }
2211
+ { x: round5(Math.min(...xs)), y: round5(by) },
2212
+ { x: round5(Math.max(...xs)), y: round5(by) }
2012
2213
  ],
2013
2214
  instance: null,
2014
2215
  title
@@ -2019,8 +2220,8 @@ function computeFaultTreeLayout(input, opts = {}) {
2019
2220
  edgeId: FT_RISER_ID_BASE + c.event.id,
2020
2221
  kind: "riser",
2021
2222
  points: [
2022
- { x: round3(c.cx), y: round3(by) },
2023
- { x: round3(c.cx), y: round3(rowTop[d + 1]) }
2223
+ { x: round5(c.cx), y: round5(by) },
2224
+ { x: round5(c.cx), y: round5(rowTop[d + 1]) }
2024
2225
  ],
2025
2226
  instance: instanceOf(c),
2026
2227
  title
@@ -2037,42 +2238,42 @@ var GLYPH_STROKE2 = "#52525b";
2037
2238
  var LABEL_FILL2 = "#3f3f46";
2038
2239
  var EDGE_INK3 = "#71717a";
2039
2240
  var GLYPH_ATTRS = `fill="transparent" stroke="${GLYPH_STROKE2}" stroke-width="2"`;
2040
- var round4 = (n) => Math.round(n * 100) / 100;
2241
+ var round6 = (n) => Math.round(n * 100) / 100;
2041
2242
  function eventGlyph(n) {
2042
2243
  const cx = n.cx;
2043
2244
  const top = n.top;
2044
2245
  if (n.kind === "intermediate") {
2045
- return `<rect x="${round4(cx - n.nodeW / 2)}" y="${top}" width="${n.nodeW}" height="${n.nodeH}" rx="2" ${GLYPH_ATTRS}/>`;
2246
+ return `<rect x="${round6(cx - n.nodeW / 2)}" y="${top}" width="${n.nodeW}" height="${n.nodeH}" rx="2" ${GLYPH_ATTRS}/>`;
2046
2247
  }
2047
2248
  if (n.kind === "basic") {
2048
- return `<circle cx="${cx}" cy="${round4(top + 22)}" r="22" ${GLYPH_ATTRS}/>`;
2249
+ return `<circle cx="${cx}" cy="${round6(top + 22)}" r="22" ${GLYPH_ATTRS}/>`;
2049
2250
  }
2050
2251
  if (n.kind === "undeveloped") {
2051
- const pts2 = `${cx},${top} ${round4(cx + 24)},${round4(top + 24)} ${cx},${round4(top + 48)} ${round4(cx - 24)},${round4(top + 24)}`;
2252
+ const pts2 = `${cx},${top} ${round6(cx + 24)},${round6(top + 24)} ${cx},${round6(top + 48)} ${round6(cx - 24)},${round6(top + 24)}`;
2052
2253
  return `<polygon points="${pts2}" ${GLYPH_ATTRS}/>`;
2053
2254
  }
2054
2255
  if (n.kind === "house") {
2055
- const yB = round4(top + 40);
2056
- const eave = round4(top + 16);
2057
- const pts2 = `${round4(cx - 22)},${yB} ${round4(cx - 22)},${eave} ${cx},${top} ${round4(cx + 22)},${eave} ${round4(cx + 22)},${yB}`;
2256
+ const yB = round6(top + 40);
2257
+ const eave = round6(top + 16);
2258
+ const pts2 = `${round6(cx - 22)},${yB} ${round6(cx - 22)},${eave} ${cx},${top} ${round6(cx + 22)},${eave} ${round6(cx + 22)},${yB}`;
2058
2259
  return `<polygon points="${pts2}" ${GLYPH_ATTRS}/>`;
2059
2260
  }
2060
2261
  if (n.kind === "conditioning") {
2061
- return `<ellipse cx="${cx}" cy="${round4(top + 16)}" rx="${round4(n.glyphW / 2)}" ry="16" ${GLYPH_ATTRS}/>`;
2262
+ return `<ellipse cx="${cx}" cy="${round6(top + 16)}" rx="${round6(n.glyphW / 2)}" ry="16" ${GLYPH_ATTRS}/>`;
2062
2263
  }
2063
- const pts = `${cx},${top} ${round4(cx + 22)},${round4(top + 35)} ${round4(cx - 22)},${round4(top + 35)}`;
2264
+ const pts = `${cx},${top} ${round6(cx + 22)},${round6(top + 35)} ${round6(cx - 22)},${round6(top + 35)}`;
2064
2265
  return `<polygon points="${pts}" ${GLYPH_ATTRS}/>`;
2065
2266
  }
2066
2267
  function nodeSvg(n) {
2067
2268
  const pieces = [`<title>${xmlEscape(n.title)}</title>`, eventGlyph(n)];
2068
2269
  if (n.code !== null && n.kind !== "intermediate") {
2069
2270
  pieces.push(
2070
- `<text x="${n.cx}" y="${round4(n.top + n.glyphH / 2 + CODE_FONT * 0.32)}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${CODE_FONT}" fill="${LABEL_FILL2}">${xmlEscape(n.code)}</text>`
2271
+ `<text x="${n.cx}" y="${round6(n.top + n.glyphH / 2 + CODE_FONT * 0.32)}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${CODE_FONT}" fill="${LABEL_FILL2}">${xmlEscape(n.code)}</text>`
2071
2272
  );
2072
2273
  }
2073
2274
  if (n.labelLines.length > 0) {
2074
- const firstBaseline = n.labelTop === null ? round4(n.top + 19) : round4(n.labelTop + 10);
2075
- const tspans = n.labelLines.map((line, i) => `<tspan x="${n.cx}" y="${round4(firstBaseline + i * FT_LABEL_LINE_H)}">${xmlEscape(line)}</tspan>`).join("");
2275
+ const firstBaseline = n.labelTop === null ? round6(n.top + 19) : round6(n.labelTop + 10);
2276
+ const tspans = n.labelLines.map((line, i) => `<tspan x="${n.cx}" y="${round6(firstBaseline + i * FT_LABEL_LINE_H)}">${xmlEscape(line)}</tspan>`).join("");
2076
2277
  pieces.push(
2077
2278
  `<text text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${FT_LABEL_FONT}" fill="${LABEL_FILL2}">${tspans}</text>`
2078
2279
  );
@@ -2081,28 +2282,28 @@ function nodeSvg(n) {
2081
2282
  return `<g data-node-id="e${n.eventId}"${instance}>${pieces.join("")}</g>`;
2082
2283
  }
2083
2284
  function orBodyPath(cx, top, yB) {
2084
- return `M ${round4(cx - 22)} ${yB} Q ${cx} ${round4(yB - 14)} ${round4(cx + 22)} ${yB} Q ${round4(cx + 22)} ${round4(top + 14)} ${cx} ${top} Q ${round4(cx - 22)} ${round4(top + 14)} ${round4(cx - 22)} ${yB} Z`;
2285
+ return `M ${round6(cx - 22)} ${yB} Q ${cx} ${round6(yB - 14)} ${round6(cx + 22)} ${yB} Q ${round6(cx + 22)} ${round6(top + 14)} ${cx} ${top} Q ${round6(cx - 22)} ${round6(top + 14)} ${round6(cx - 22)} ${yB} Z`;
2085
2286
  }
2086
2287
  function gateGlyph(g) {
2087
2288
  const cx = g.cx;
2088
2289
  const top = g.top;
2089
- const yB = round4(top + 36);
2290
+ const yB = round6(top + 36);
2090
2291
  if (g.type === "and") {
2091
- const d = `M ${round4(cx - 22)} ${yB} L ${round4(cx - 22)} ${round4(yB - 14)} A 22 22 0 0 1 ${round4(cx + 22)} ${round4(yB - 14)} L ${round4(cx + 22)} ${yB} Z`;
2292
+ const d = `M ${round6(cx - 22)} ${yB} L ${round6(cx - 22)} ${round6(yB - 14)} A 22 22 0 0 1 ${round6(cx + 22)} ${round6(yB - 14)} L ${round6(cx + 22)} ${yB} Z`;
2092
2293
  return `<path d="${d}" ${GLYPH_ATTRS}/>`;
2093
2294
  }
2094
2295
  if (g.type === "or") {
2095
2296
  return `<path d="${orBodyPath(cx, top, yB)}" ${GLYPH_ATTRS}/>`;
2096
2297
  }
2097
2298
  if (g.type === "xor") {
2098
- const arc = `M ${round4(cx - 22)} ${round4(yB + 5)} Q ${cx} ${round4(yB - 9)} ${round4(cx + 22)} ${round4(yB + 5)}`;
2299
+ const arc = `M ${round6(cx - 22)} ${round6(yB + 5)} Q ${cx} ${round6(yB - 9)} ${round6(cx + 22)} ${round6(yB + 5)}`;
2099
2300
  return `<path d="${orBodyPath(cx, top, yB)}" ${GLYPH_ATTRS}/><path d="${arc}" ${GLYPH_ATTRS}/>`;
2100
2301
  }
2101
2302
  if (g.type === "inhibit") {
2102
- const pts = `${cx},${top} ${round4(cx + 18)},${round4(top + 12)} ${round4(cx + 18)},${round4(top + 34)} ${cx},${round4(top + 46)} ${round4(cx - 18)},${round4(top + 34)} ${round4(cx - 18)},${round4(top + 12)}`;
2303
+ const pts = `${cx},${top} ${round6(cx + 18)},${round6(top + 12)} ${round6(cx + 18)},${round6(top + 34)} ${cx},${round6(top + 46)} ${round6(cx - 18)},${round6(top + 34)} ${round6(cx - 18)},${round6(top + 12)}`;
2103
2304
  return `<polygon points="${pts}" ${GLYPH_ATTRS}/>`;
2104
2305
  }
2105
- return `<path d="${orBodyPath(cx, top, yB)}" ${GLYPH_ATTRS}/><text x="${cx}" y="${round4(yB - 10)}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="9" fill="${LABEL_FILL2}">${xmlEscape(g.voteText ?? "")}</text>`;
2306
+ return `<path d="${orBodyPath(cx, top, yB)}" ${GLYPH_ATTRS}/><text x="${cx}" y="${round6(yB - 10)}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="9" fill="${LABEL_FILL2}">${xmlEscape(g.voteText ?? "")}</text>`;
2106
2307
  }
2107
2308
  function gateSvg(g) {
2108
2309
  return `<g data-node-id="g${g.gateId}"><title>${xmlEscape(g.title)}</title>${gateGlyph(g)}</g>`;
@@ -2115,34 +2316,34 @@ function elementSvg2(el) {
2115
2316
  }
2116
2317
  var MINI_ATTRS = `fill="transparent" stroke="${GLYPH_STROKE2}" stroke-width="1.5"`;
2117
2318
  function miniEventSwatch(kind, x, y) {
2118
- const cx = round4(x + LEGEND_SWATCH_W / 2);
2319
+ const cx = round6(x + LEGEND_SWATCH_W / 2);
2119
2320
  if (kind === "intermediate") {
2120
- return `<rect x="${round4(cx - 7)}" y="${round4(y - 4.5)}" width="14" height="9" rx="1" ${MINI_ATTRS}/>`;
2321
+ return `<rect x="${round6(cx - 7)}" y="${round6(y - 4.5)}" width="14" height="9" rx="1" ${MINI_ATTRS}/>`;
2121
2322
  }
2122
2323
  if (kind === "basic") return `<circle cx="${cx}" cy="${y}" r="6" ${MINI_ATTRS}/>`;
2123
2324
  if (kind === "undeveloped") {
2124
- return `<polygon points="${cx},${round4(y - 7)} ${round4(cx + 7)},${y} ${cx},${round4(y + 7)} ${round4(cx - 7)},${y}" ${MINI_ATTRS}/>`;
2325
+ return `<polygon points="${cx},${round6(y - 7)} ${round6(cx + 7)},${y} ${cx},${round6(y + 7)} ${round6(cx - 7)},${y}" ${MINI_ATTRS}/>`;
2125
2326
  }
2126
2327
  if (kind === "house") {
2127
- return `<polygon points="${round4(cx - 6)},${round4(y + 5.5)} ${round4(cx - 6)},${round4(y - 1)} ${cx},${round4(y - 5.5)} ${round4(cx + 6)},${round4(y - 1)} ${round4(cx + 6)},${round4(y + 5.5)}" ${MINI_ATTRS}/>`;
2328
+ return `<polygon points="${round6(cx - 6)},${round6(y + 5.5)} ${round6(cx - 6)},${round6(y - 1)} ${cx},${round6(y - 5.5)} ${round6(cx + 6)},${round6(y - 1)} ${round6(cx + 6)},${round6(y + 5.5)}" ${MINI_ATTRS}/>`;
2128
2329
  }
2129
2330
  if (kind === "conditioning") return `<ellipse cx="${cx}" cy="${y}" rx="9" ry="5.5" ${MINI_ATTRS}/>`;
2130
- return `<polygon points="${cx},${round4(y - 5)} ${round4(cx + 6)},${round4(y + 5)} ${round4(cx - 6)},${round4(y + 5)}" ${MINI_ATTRS}/>`;
2331
+ return `<polygon points="${cx},${round6(y - 5)} ${round6(cx + 6)},${round6(y + 5)} ${round6(cx - 6)},${round6(y + 5)}" ${MINI_ATTRS}/>`;
2131
2332
  }
2132
2333
  function miniOrPath(cx, y) {
2133
- return `M ${round4(cx - 7)} ${round4(y + 5.5)} Q ${cx} ${round4(y + 1)} ${round4(cx + 7)} ${round4(y + 5.5)} Q ${round4(cx + 7)} ${round4(y - 1.5)} ${cx} ${round4(y - 5.5)} Q ${round4(cx - 7)} ${round4(y - 1.5)} ${round4(cx - 7)} ${round4(y + 5.5)} Z`;
2334
+ return `M ${round6(cx - 7)} ${round6(y + 5.5)} Q ${cx} ${round6(y + 1)} ${round6(cx + 7)} ${round6(y + 5.5)} Q ${round6(cx + 7)} ${round6(y - 1.5)} ${cx} ${round6(y - 5.5)} Q ${round6(cx - 7)} ${round6(y - 1.5)} ${round6(cx - 7)} ${round6(y + 5.5)} Z`;
2134
2335
  }
2135
2336
  function miniGateSwatch(type, x, y) {
2136
- const cx = round4(x + LEGEND_SWATCH_W / 2);
2337
+ const cx = round6(x + LEGEND_SWATCH_W / 2);
2137
2338
  if (type === "and") {
2138
- const d = `M ${round4(cx - 7)} ${round4(y + 5.5)} L ${round4(cx - 7)} ${round4(y + 1.5)} A 7 7 0 0 1 ${round4(cx + 7)} ${round4(y + 1.5)} L ${round4(cx + 7)} ${round4(y + 5.5)} Z`;
2339
+ const d = `M ${round6(cx - 7)} ${round6(y + 5.5)} L ${round6(cx - 7)} ${round6(y + 1.5)} A 7 7 0 0 1 ${round6(cx + 7)} ${round6(y + 1.5)} L ${round6(cx + 7)} ${round6(y + 5.5)} Z`;
2139
2340
  return `<path d="${d}" ${MINI_ATTRS}/>`;
2140
2341
  }
2141
2342
  if (type === "xor") {
2142
- return `<path d="${miniOrPath(cx, y)}" ${MINI_ATTRS}/><path d="M ${round4(cx - 7)} ${round4(y + 7.5)} Q ${cx} ${round4(y + 3)} ${round4(cx + 7)} ${round4(y + 7.5)}" ${MINI_ATTRS}/>`;
2343
+ return `<path d="${miniOrPath(cx, y)}" ${MINI_ATTRS}/><path d="M ${round6(cx - 7)} ${round6(y + 7.5)} Q ${cx} ${round6(y + 3)} ${round6(cx + 7)} ${round6(y + 7.5)}" ${MINI_ATTRS}/>`;
2143
2344
  }
2144
2345
  if (type === "inhibit") {
2145
- return `<polygon points="${cx},${round4(y - 5.5)} ${round4(cx + 4.5)},${round4(y - 2.5)} ${round4(cx + 4.5)},${round4(y + 2.5)} ${cx},${round4(y + 5.5)} ${round4(cx - 4.5)},${round4(y + 2.5)} ${round4(cx - 4.5)},${round4(y - 2.5)}" ${MINI_ATTRS}/>`;
2346
+ return `<polygon points="${cx},${round6(y - 5.5)} ${round6(cx + 4.5)},${round6(y - 2.5)} ${round6(cx + 4.5)},${round6(y + 2.5)} ${cx},${round6(y + 5.5)} ${round6(cx - 4.5)},${round6(y + 2.5)} ${round6(cx - 4.5)},${round6(y - 2.5)}" ${MINI_ATTRS}/>`;
2146
2347
  }
2147
2348
  return `<path d="${miniOrPath(cx, y)}" ${MINI_ATTRS}/>`;
2148
2349
  }
@@ -2271,7 +2472,7 @@ var TWIG_W = 1.5;
2271
2472
  var TWIG_OP = 0.75;
2272
2473
  var SUB_W = 1.2;
2273
2474
  var SUB_OP = 0.7;
2274
- var round5 = (n) => Math.round(n * 100) / 100;
2475
+ var round7 = (n) => Math.round(n * 100) / 100;
2275
2476
  function arrowHead2(tipX, tipY, ux, uy, opacity) {
2276
2477
  const LEN = 9;
2277
2478
  const HALF_W = 4.5;
@@ -2280,14 +2481,14 @@ function arrowHead2(tipX, tipY, ux, uy, opacity) {
2280
2481
  const px = -uy;
2281
2482
  const py = ux;
2282
2483
  const points = [
2283
- `${round5(tipX)},${round5(tipY)}`,
2284
- `${round5(bx + px * HALF_W)},${round5(by + py * HALF_W)}`,
2285
- `${round5(bx - px * HALF_W)},${round5(by - py * HALF_W)}`
2484
+ `${round7(tipX)},${round7(tipY)}`,
2485
+ `${round7(bx + px * HALF_W)},${round7(by + py * HALF_W)}`,
2486
+ `${round7(bx - px * HALF_W)},${round7(by - py * HALF_W)}`
2286
2487
  ].join(" ");
2287
2488
  return `<polygon points="${points}" fill="${EDGE_INK4}" fill-opacity="${opacity}"/>`;
2288
2489
  }
2289
2490
  function lineEl(x1, y1, x2, y2, w, op) {
2290
- return `<line x1="${round5(x1)}" y1="${round5(y1)}" x2="${round5(x2)}" y2="${round5(y2)}" stroke="${EDGE_INK4}" stroke-width="${w}" stroke-opacity="${op}"/>`;
2491
+ return `<line x1="${round7(x1)}" y1="${round7(y1)}" x2="${round7(x2)}" y2="${round7(y2)}" stroke="${EDGE_INK4}" stroke-width="${w}" stroke-opacity="${op}"/>`;
2291
2492
  }
2292
2493
  function fishboneSvg(input, opts = {}) {
2293
2494
  validateIds(input);
@@ -2368,7 +2569,7 @@ function fishboneSvg(input, opts = {}) {
2368
2569
  { length: n },
2369
2570
  (_, k) => up ? spineY - (e + twigGap + (n - 1 - k) * lineH) : spineY + e + twigGap + ascent + k * lineH
2370
2571
  );
2371
- const textBlock = (anchor, x, ys, lines) => `<text text-anchor="${anchor}" font-family="${FONT_FAMILY}" font-size="${fontSize}" fill="${LABEL_FILL3}">` + lines.map((line, i) => `<tspan x="${round5(x)}" y="${round5(ys[i])}">${xmlEscape(line)}</tspan>`).join("") + `</text>`;
2572
+ const textBlock = (anchor, x, ys, lines) => `<text text-anchor="${anchor}" font-family="${FONT_FAMILY}" font-size="${fontSize}" fill="${LABEL_FILL3}">` + lines.map((line, i) => `<tspan x="${round7(x)}" y="${round7(ys[i])}">${xmlEscape(line)}</tspan>`).join("") + `</text>`;
2372
2573
  const parts = [];
2373
2574
  {
2374
2575
  const body = [lineEl(tailX + dx, spineY, headLeft + dx, spineY, SPINE_W, SPINE_OP)];
@@ -2383,7 +2584,7 @@ function fishboneSvg(input, opts = {}) {
2383
2584
  if (arrows) body.push(arrowHead2(ax, spineY, COS60, -sgn * SIN60, BONE_OP));
2384
2585
  const boxTop = bone.up ? spineY - bone.B - CAT_GAP - bone.boxH : spineY + bone.B + CAT_GAP;
2385
2586
  body.push(
2386
- `<rect x="${round5(tipX - bone.boxW / 2)}" y="${round5(boxTop)}" width="${round5(bone.boxW)}" height="${round5(bone.boxH)}" rx="2" fill="transparent" stroke="${BOX_STROKE}" stroke-width="1.5"/>`
2587
+ `<rect x="${round7(tipX - bone.boxW / 2)}" y="${round7(boxTop)}" width="${round7(bone.boxW)}" height="${round7(bone.boxH)}" rx="2" fill="transparent" stroke="${BOX_STROKE}" stroke-width="1.5"/>`
2387
2588
  );
2388
2589
  body.push(textBlock("middle", tipX, centeredYs(boxTop + bone.boxH / 2, bone.catLines.length), bone.catLines));
2389
2590
  parts.push(
@@ -2413,7 +2614,7 @@ function fishboneSvg(input, opts = {}) {
2413
2614
  {
2414
2615
  const x = headLeft + dx;
2415
2616
  parts.push(
2416
- `<g data-node-id="head"><title>${xmlEscape(input.effectLabel)}</title><rect x="${round5(x)}" y="${round5(spineY - headH / 2)}" width="${round5(headW)}" height="${round5(headH)}" rx="2" fill="transparent" stroke="${BOX_STROKE}" stroke-width="2"/>` + textBlock("middle", x + headW / 2, centeredYs(spineY, effLines.length), effLines) + `</g>`
2617
+ `<g data-node-id="head"><title>${xmlEscape(input.effectLabel)}</title><rect x="${round7(x)}" y="${round7(spineY - headH / 2)}" width="${round7(headW)}" height="${round7(headH)}" rx="2" fill="transparent" stroke="${BOX_STROKE}" stroke-width="2"/>` + textBlock("middle", x + headW / 2, centeredYs(spineY, effLines.length), effLines) + `</g>`
2417
2618
  );
2418
2619
  }
2419
2620
  const anySubs = input.categories.some((c) => c.causes.some((k) => k.subCauses.length > 0));
@@ -2627,13 +2828,13 @@ function pedigreeIssues(input) {
2627
2828
  );
2628
2829
  }
2629
2830
  }
2630
- const GRAPH_BLOCKING2 = /* @__PURE__ */ new Set([
2831
+ const GRAPH_BLOCKING3 = /* @__PURE__ */ new Set([
2631
2832
  "duplicate-id",
2632
2833
  "unknown-partner",
2633
2834
  "unknown-sibship-mating",
2634
2835
  "unknown-child"
2635
2836
  ]);
2636
- if (!issues.some((i) => GRAPH_BLOCKING2.has(i.code))) {
2837
+ if (!issues.some((i) => GRAPH_BLOCKING3.has(i.code))) {
2637
2838
  for (const s of sibships) {
2638
2839
  const mating = matingById.get(s.matingId);
2639
2840
  if (mating === void 0) continue;
@@ -2681,7 +2882,7 @@ var PED_DESCENT_ID_BASE = 2e6;
2681
2882
  var PED_SIBBAR_ID_BASE = 3e6;
2682
2883
  var PED_RISER_ID_BASE = 4e6;
2683
2884
  var PED_TWINBAR_ID_BASE = 5e6;
2684
- var round6 = (n) => Math.round(n * 100) / 100;
2885
+ var round8 = (n) => Math.round(n * 100) / 100;
2685
2886
  function shapeForSex2(sex) {
2686
2887
  if (sex === "male") return "square";
2687
2888
  if (sex === "female") return "circle";
@@ -2920,8 +3121,8 @@ function computePedigreeLayout(input, opts = {}) {
2920
3121
  rowHeight[row] = Math.max(rowHeight[row], h);
2921
3122
  }
2922
3123
  const rowTop = [PADDING5];
2923
- for (let r = 0; r < rowCount - 1; r++) {
2924
- rowTop.push(rowTop[r] + rowHeight[r] + CORRIDOR2);
3124
+ for (let r2 = 0; r2 < rowCount - 1; r2++) {
3125
+ rowTop.push(rowTop[r2] + rowHeight[r2] + CORRIDOR2);
2925
3126
  }
2926
3127
  const glyphCyOfRow = (row) => rowTop[row] + PED_GLYPH / 2;
2927
3128
  const addressByIndividual = /* @__PURE__ */ new Map();
@@ -2947,8 +3148,8 @@ function computePedigreeLayout(input, opts = {}) {
2947
3148
  nodes.push({
2948
3149
  individualId: ind.id,
2949
3150
  shape: shapeForSex2(ind.sex),
2950
- cx: round6(placed.cx),
2951
- cy: round6(cy),
3151
+ cx: round8(placed.cx),
3152
+ cy: round8(cy),
2952
3153
  size: PED_GLYPH,
2953
3154
  deceased: ind.deceased,
2954
3155
  carrier: ind.carrier,
@@ -2958,7 +3159,7 @@ function computePedigreeLayout(input, opts = {}) {
2958
3159
  labelLines: lines,
2959
3160
  // Labels sit BELOW the row's serial-union dip band (rowDipBand) so a hub's bridge dips
2960
3161
  // never cross a label box.
2961
- labelTop: round6(cy + PED_GLYPH / 2 + PED_LABEL_GAP + rowDipBand[row]),
3162
+ labelTop: round8(cy + PED_GLYPH / 2 + PED_LABEL_GAP + rowDipBand[row]),
2962
3163
  addressLabel: address,
2963
3164
  title: individualTitle(ind, address, conditionLabelById, titleLabels)
2964
3165
  });
@@ -3001,23 +3202,23 @@ function computePedigreeLayout(input, opts = {}) {
3001
3202
  edgeId: PED_MATING_ID_BASE + m.id,
3002
3203
  kind: "mating-elbow",
3003
3204
  points: [
3004
- { x: round6(hubStubX), y: round6(glyphBottom) },
3005
- { x: round6(hubStubX), y: round6(dipY) },
3006
- { x: round6(spouseStubX), y: round6(dipY) },
3007
- { x: round6(spouseStubX), y: round6(glyphBottom) }
3205
+ { x: round8(hubStubX), y: round8(glyphBottom) },
3206
+ { x: round8(hubStubX), y: round8(dipY) },
3207
+ { x: round8(spouseStubX), y: round8(dipY) },
3208
+ { x: round8(spouseStubX), y: round8(glyphBottom) }
3008
3209
  ],
3009
3210
  consanguineous: m.consanguineous,
3010
3211
  title
3011
3212
  });
3012
- matingMidpoint.set(m.id, { x: round6((hubStubX + spouseStubX) / 2), y: round6(dipY) });
3213
+ matingMidpoint.set(m.id, { x: round8((hubStubX + spouseStubX) / 2), y: round8(dipY) });
3013
3214
  } else if (ay === by) {
3014
3215
  const y = ay;
3015
3216
  elements.push({
3016
3217
  edgeId: PED_MATING_ID_BASE + m.id,
3017
3218
  kind: "mating",
3018
3219
  points: [
3019
- { x: round6(lx), y: round6(y) },
3020
- { x: round6(rx), y: round6(y) }
3220
+ { x: round8(lx), y: round8(y) },
3221
+ { x: round8(rx), y: round8(y) }
3021
3222
  ],
3022
3223
  consanguineous: m.consanguineous,
3023
3224
  title
@@ -3027,7 +3228,7 @@ function computePedigreeLayout(input, opts = {}) {
3027
3228
  const channelRight = cxOf(rightId) - nodeHalfWidth(individualById.get(rightId));
3028
3229
  const barCenter = matingBarCenter.get(m.id);
3029
3230
  const originX = barCenter !== void 0 && barCenter >= channelLeft && barCenter <= channelRight ? barCenter : midX;
3030
- matingMidpoint.set(m.id, { x: round6(originX), y: round6(y) });
3231
+ matingMidpoint.set(m.id, { x: round8(originX), y: round8(y) });
3031
3232
  } else {
3032
3233
  const upId = ay <= by ? aId : bId;
3033
3234
  const downId = ay <= by ? bId : aId;
@@ -3046,32 +3247,32 @@ function computePedigreeLayout(input, opts = {}) {
3046
3247
  edgeId: PED_MATING_ID_BASE + m.id,
3047
3248
  kind: "mating-elbow",
3048
3249
  points: [
3049
- { x: round6(upX + (downRight ? -GLYPH_HALF : GLYPH_HALF)), y: round6(upY) },
3050
- { x: round6(upExitX), y: round6(upY) },
3051
- { x: round6(upExitX), y: round6(laneY) },
3052
- { x: round6(downFarX), y: round6(laneY) },
3053
- { x: round6(downFarX), y: round6(downY) },
3054
- { x: round6(downSideX), y: round6(downY) }
3250
+ { x: round8(upX + (downRight ? -GLYPH_HALF : GLYPH_HALF)), y: round8(upY) },
3251
+ { x: round8(upExitX), y: round8(upY) },
3252
+ { x: round8(upExitX), y: round8(laneY) },
3253
+ { x: round8(downFarX), y: round8(laneY) },
3254
+ { x: round8(downFarX), y: round8(downY) },
3255
+ { x: round8(downSideX), y: round8(downY) }
3055
3256
  ],
3056
3257
  consanguineous: m.consanguineous,
3057
3258
  title
3058
3259
  });
3059
- matingMidpoint.set(m.id, { x: round6(downFarX), y: round6(downY) });
3260
+ matingMidpoint.set(m.id, { x: round8(downFarX), y: round8(downY) });
3060
3261
  } else {
3061
3262
  const elbowX = (upX + downX) / 2;
3062
3263
  elements.push({
3063
3264
  edgeId: PED_MATING_ID_BASE + m.id,
3064
3265
  kind: "mating-elbow",
3065
3266
  points: [
3066
- { x: round6(upX + (downX >= upX ? GLYPH_HALF : -GLYPH_HALF)), y: round6(upY) },
3067
- { x: round6(elbowX), y: round6(upY) },
3068
- { x: round6(elbowX), y: round6(downY) },
3069
- { x: round6(downX + (upX >= downX ? GLYPH_HALF : -GLYPH_HALF)), y: round6(downY) }
3267
+ { x: round8(upX + (downX >= upX ? GLYPH_HALF : -GLYPH_HALF)), y: round8(upY) },
3268
+ { x: round8(elbowX), y: round8(upY) },
3269
+ { x: round8(elbowX), y: round8(downY) },
3270
+ { x: round8(downX + (upX >= downX ? GLYPH_HALF : -GLYPH_HALF)), y: round8(downY) }
3070
3271
  ],
3071
3272
  consanguineous: m.consanguineous,
3072
3273
  title
3073
3274
  });
3074
- matingMidpoint.set(m.id, { x: round6(elbowX), y: round6(downY) });
3275
+ matingMidpoint.set(m.id, { x: round8(elbowX), y: round8(downY) });
3075
3276
  }
3076
3277
  }
3077
3278
  }
@@ -3093,13 +3294,13 @@ function computePedigreeLayout(input, opts = {}) {
3093
3294
  const barCenterX = (barLeft + barRight) / 2;
3094
3295
  const bendY = barY - 2 - lane % 3 * 3;
3095
3296
  const descentPoints = Math.abs(mid.x - barCenterX) < 0.01 ? [
3096
- { x: round6(mid.x), y: round6(mid.y) },
3097
- { x: round6(mid.x), y: round6(barY) }
3297
+ { x: round8(mid.x), y: round8(mid.y) },
3298
+ { x: round8(mid.x), y: round8(barY) }
3098
3299
  ] : [
3099
- { x: round6(mid.x), y: round6(mid.y) },
3100
- { x: round6(mid.x), y: round6(bendY) },
3101
- { x: round6(barCenterX), y: round6(bendY) },
3102
- { x: round6(barCenterX), y: round6(barY) }
3300
+ { x: round8(mid.x), y: round8(mid.y) },
3301
+ { x: round8(mid.x), y: round8(bendY) },
3302
+ { x: round8(barCenterX), y: round8(bendY) },
3303
+ { x: round8(barCenterX), y: round8(barY) }
3103
3304
  ];
3104
3305
  elements.push({
3105
3306
  edgeId: PED_DESCENT_ID_BASE + s.id,
@@ -3114,8 +3315,8 @@ function computePedigreeLayout(input, opts = {}) {
3114
3315
  edgeId: PED_SIBBAR_ID_BASE + s.id,
3115
3316
  kind: "sibship-bar",
3116
3317
  points: [
3117
- { x: round6(barLeft), y: round6(barY) },
3118
- { x: round6(barRight), y: round6(barY) }
3318
+ { x: round8(barLeft), y: round8(barY) },
3319
+ { x: round8(barRight), y: round8(barY) }
3119
3320
  ],
3120
3321
  consanguineous: false,
3121
3322
  title: titleLabels.sibship
@@ -3136,8 +3337,8 @@ function computePedigreeLayout(input, opts = {}) {
3136
3337
  edgeId: PED_RISER_ID_BASE + childId,
3137
3338
  kind: "riser",
3138
3339
  points: [
3139
- { x: round6(cx), y: round6(barY) },
3140
- { x: round6(cx), y: round6(childTop) }
3340
+ { x: round8(cx), y: round8(barY) },
3341
+ { x: round8(cx), y: round8(childTop) }
3141
3342
  ],
3142
3343
  consanguineous: false,
3143
3344
  title: titleLabels.sibship
@@ -3151,15 +3352,15 @@ function computePedigreeLayout(input, opts = {}) {
3151
3352
  if (!emittedTwinOrdinals.has(tg.ordinal)) {
3152
3353
  emittedTwinOrdinals.add(tg.ordinal);
3153
3354
  if (tg.zygosity === "unknown") {
3154
- unknownTwinJunctions.push({ x: round6(junctionX), y: round6(junctionY) });
3355
+ unknownTwinJunctions.push({ x: round8(junctionX), y: round8(junctionY) });
3155
3356
  }
3156
3357
  elements.push({
3157
3358
  edgeId: PED_RISER_ID_BASE + childId,
3158
3359
  // anchored on the first member for a stable id
3159
3360
  kind: "riser",
3160
3361
  points: [
3161
- { x: round6(junctionX), y: round6(barY) },
3162
- { x: round6(junctionX), y: round6(junctionY) }
3362
+ { x: round8(junctionX), y: round8(barY) },
3363
+ { x: round8(junctionX), y: round8(junctionY) }
3163
3364
  ],
3164
3365
  consanguineous: false,
3165
3366
  title: titleLabels.twins[tg.zygosity]
@@ -3170,8 +3371,8 @@ function computePedigreeLayout(input, opts = {}) {
3170
3371
  edgeId: PED_TWINBAR_ID_BASE + s.id * 100 + tg.ordinal,
3171
3372
  kind: "twin-bar",
3172
3373
  points: [
3173
- { x: round6(Math.min(...memberXs)), y: round6(tieY) },
3174
- { x: round6(Math.max(...memberXs)), y: round6(tieY) }
3374
+ { x: round8(Math.min(...memberXs)), y: round8(tieY) },
3375
+ { x: round8(Math.max(...memberXs)), y: round8(tieY) }
3175
3376
  ],
3176
3377
  consanguineous: false,
3177
3378
  title: titleLabels.twins.mz
@@ -3179,12 +3380,12 @@ function computePedigreeLayout(input, opts = {}) {
3179
3380
  }
3180
3381
  }
3181
3382
  const horizontalThenDown = Math.abs(cx - junctionX) < 0.01 ? [
3182
- { x: round6(cx), y: round6(junctionY) },
3183
- { x: round6(cx), y: round6(childTop) }
3383
+ { x: round8(cx), y: round8(junctionY) },
3384
+ { x: round8(cx), y: round8(childTop) }
3184
3385
  ] : [
3185
- { x: round6(junctionX), y: round6(junctionY) },
3186
- { x: round6(cx), y: round6(junctionY) },
3187
- { x: round6(cx), y: round6(childTop) }
3386
+ { x: round8(junctionX), y: round8(junctionY) },
3387
+ { x: round8(cx), y: round8(junctionY) },
3388
+ { x: round8(cx), y: round8(childTop) }
3188
3389
  ];
3189
3390
  elements.push({
3190
3391
  edgeId: PED_RISER_ID_BASE + childId,
@@ -3212,7 +3413,7 @@ function computePedigreeLayout(input, opts = {}) {
3212
3413
  }
3213
3414
  const generations = [];
3214
3415
  for (let row = 0; row < rowCount; row++) {
3215
- generations.push({ roman: romanNumeral(row + 1), y: round6(glyphCyOfRow(row)) });
3416
+ generations.push({ roman: romanNumeral(row + 1), y: round8(glyphCyOfRow(row)) });
3216
3417
  }
3217
3418
  const ZYGOSITY_ORDER = ["mz", "dz", "unknown"];
3218
3419
  const twinZygositiesUsed = ZYGOSITY_ORDER.filter((z) => twinZygositiesUsedSet.has(z));
@@ -3236,15 +3437,15 @@ var EDGE_INK5 = "#71717a";
3236
3437
  var GLYPH_ATTRS2 = `fill="transparent" stroke="${GLYPH_STROKE3}" stroke-width="2"`;
3237
3438
  var CONSANG_GAP = 3;
3238
3439
  var ZYGOSITIES = ["mz", "dz", "unknown"];
3239
- var round7 = (n) => Math.round(n * 100) / 100;
3440
+ var round9 = (n) => Math.round(n * 100) / 100;
3240
3441
  function glyphOutline(shape, cx, cy, half) {
3241
3442
  if (shape === "square") {
3242
- return `<rect x="${round7(cx - half)}" y="${round7(cy - half)}" width="${half * 2}" height="${half * 2}" ${GLYPH_ATTRS2}/>`;
3443
+ return `<rect x="${round9(cx - half)}" y="${round9(cy - half)}" width="${half * 2}" height="${half * 2}" ${GLYPH_ATTRS2}/>`;
3243
3444
  }
3244
3445
  if (shape === "circle") {
3245
3446
  return `<circle cx="${cx}" cy="${cy}" r="${half}" ${GLYPH_ATTRS2}/>`;
3246
3447
  }
3247
- return `<polygon points="${cx},${round7(cy - half)} ${round7(cx + half)},${cy} ${cx},${round7(cy + half)} ${round7(cx - half)},${cy}" ${GLYPH_ATTRS2}/>`;
3448
+ return `<polygon points="${cx},${round9(cy - half)} ${round9(cx + half)},${cy} ${cx},${round9(cy + half)} ${round9(cx - half)},${cy}" ${GLYPH_ATTRS2}/>`;
3248
3449
  }
3249
3450
  function glyphVerticalExtentAt(shape, cy, half, dx) {
3250
3451
  const ax = Math.min(Math.abs(dx), half);
@@ -3270,8 +3471,8 @@ function fillPartitions(n, half, inkByCondition) {
3270
3471
  for (let s = 0; s <= SAMPLES; s++) {
3271
3472
  const x = left + sliceW * s / SAMPLES;
3272
3473
  const [yt, yb] = glyphVerticalExtentAt(n.shape, cy, half, x - cx);
3273
- top.push(`${round7(x)},${round7(yt)}`);
3274
- bottom.push(`${round7(x)},${round7(yb)}`);
3474
+ top.push(`${round9(x)},${round9(yt)}`);
3475
+ bottom.push(`${round9(x)},${round9(yb)}`);
3275
3476
  }
3276
3477
  const pts = [...top, ...bottom.reverse()].join(" ");
3277
3478
  const ink = inkByCondition.get(id) ?? GLYPH_STROKE3;
@@ -3285,24 +3486,24 @@ function carrierDot(n) {
3285
3486
  function deceasedSlash(n, half) {
3286
3487
  if (!n.deceased) return "";
3287
3488
  const ext = half + 4;
3288
- return `<line x1="${round7(n.cx - ext)}" y1="${round7(n.cy - ext)}" x2="${round7(n.cx + ext)}" y2="${round7(n.cy + ext)}" stroke="${GLYPH_STROKE3}" stroke-width="2"/>`;
3489
+ return `<line x1="${round9(n.cx - ext)}" y1="${round9(n.cy - ext)}" x2="${round9(n.cx + ext)}" y2="${round9(n.cy + ext)}" stroke="${GLYPH_STROKE3}" stroke-width="2"/>`;
3289
3490
  }
3290
3491
  function probandArrow(n, half) {
3291
3492
  if (n.role === null) return "";
3292
- const tipX = round7(n.cx - half);
3293
- const tipY = round7(n.cy + half);
3294
- const tailX = round7(tipX - 16);
3295
- const tailY = round7(tipY + 16);
3493
+ const tipX = round9(n.cx - half);
3494
+ const tipY = round9(n.cy + half);
3495
+ const tailX = round9(tipX - 16);
3496
+ const tailY = round9(tipY + 16);
3296
3497
  const filled = n.role === "proband";
3297
3498
  const fill = filled ? GLYPH_STROKE3 : "transparent";
3298
3499
  const shaft = `<line x1="${tailX}" y1="${tailY}" x2="${tipX}" y2="${tipY}" stroke="${GLYPH_STROKE3}" stroke-width="2"/>`;
3299
- const head = `<polygon points="${tipX},${tipY} ${round7(tipX - 8)},${round7(tipY + 2)} ${round7(tipX - 2)},${round7(tipY + 8)}" fill="${fill}" stroke="${GLYPH_STROKE3}" stroke-width="1.5"/>`;
3500
+ const head = `<polygon points="${tipX},${tipY} ${round9(tipX - 8)},${round9(tipY + 2)} ${round9(tipX - 2)},${round9(tipY + 8)}" fill="${fill}" stroke="${GLYPH_STROKE3}" stroke-width="1.5"/>`;
3300
3501
  return shaft + head;
3301
3502
  }
3302
3503
  function stillbirthMark(n, half) {
3303
3504
  if (!n.stillbirth) return "";
3304
- const y = round7(n.cy + half + PED_ADDRESS_FONT);
3305
- return `<text x="${round7(n.cx - half - 2)}" y="${y}" text-anchor="end" font-family="${FONT_FAMILY}" font-size="${PED_ADDRESS_FONT}" font-weight="bold" fill="${LABEL_FILL4}">SB</text>`;
3505
+ const y = round9(n.cy + half + PED_ADDRESS_FONT);
3506
+ return `<text x="${round9(n.cx - half - 2)}" y="${y}" text-anchor="end" font-family="${FONT_FAMILY}" font-size="${PED_ADDRESS_FONT}" font-weight="bold" fill="${LABEL_FILL4}">SB</text>`;
3306
3507
  }
3307
3508
  function nodeSvg2(n, inkByCondition) {
3308
3509
  const half = n.size / 2;
@@ -3316,13 +3517,13 @@ function nodeSvg2(n, inkByCondition) {
3316
3517
  probandArrow(n, half),
3317
3518
  stillbirthMark(n, half)
3318
3519
  ];
3319
- const addressY = round7(n.labelTop + PED_ADDRESS_FONT);
3520
+ const addressY = round9(n.labelTop + PED_ADDRESS_FONT);
3320
3521
  pieces.push(
3321
3522
  `<text x="${n.cx}" y="${addressY}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${PED_ADDRESS_FONT}" fill="${LABEL_FILL4}">${xmlEscape(n.addressLabel)}</text>`
3322
3523
  );
3323
3524
  if (n.labelLines.length > 0) {
3324
- const firstBaseline = round7(n.labelTop + PED_LABEL_LINE_H + 10);
3325
- const tspans = n.labelLines.map((line, i) => `<tspan x="${n.cx}" y="${round7(firstBaseline + i * PED_LABEL_LINE_H)}">${xmlEscape(line)}</tspan>`).join("");
3525
+ const firstBaseline = round9(n.labelTop + PED_LABEL_LINE_H + 10);
3526
+ const tspans = n.labelLines.map((line, i) => `<tspan x="${n.cx}" y="${round9(firstBaseline + i * PED_LABEL_LINE_H)}">${xmlEscape(line)}</tspan>`).join("");
3326
3527
  pieces.push(
3327
3528
  `<text text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${PED_LABEL_FONT}" fill="${LABEL_FILL4}">${tspans}</text>`
3328
3529
  );
@@ -3337,7 +3538,7 @@ function elementSvg3(el) {
3337
3538
  const title = `<title>${xmlEscape(el.title)}</title>`;
3338
3539
  const stroke = `stroke="${EDGE_INK5}" stroke-width="1.5" stroke-opacity="0.75"`;
3339
3540
  const draw = (offsetY) => {
3340
- const shifted = pts.map((p) => ({ x: p.x, y: round7(p.y + offsetY) }));
3541
+ const shifted = pts.map((p) => ({ x: p.x, y: round9(p.y + offsetY) }));
3341
3542
  if (shifted.length === 2) {
3342
3543
  return `<line x1="${shifted[0].x}" y1="${shifted[0].y}" x2="${shifted[1].x}" y2="${shifted[1].y}" ${stroke}/>`;
3343
3544
  }
@@ -3348,51 +3549,51 @@ function elementSvg3(el) {
3348
3549
  }
3349
3550
  function unknownTwinMarks(layout) {
3350
3551
  return layout.unknownTwinJunctions.map(
3351
- (p) => `<text x="${round7(p.x + 6)}" y="${round7(p.y + 4)}" font-family="${FONT_FAMILY}" font-size="12" font-weight="bold" fill="${LABEL_FILL4}">?</text>`
3552
+ (p) => `<text x="${round9(p.x + 6)}" y="${round9(p.y + 4)}" font-family="${FONT_FAMILY}" font-size="12" font-weight="bold" fill="${LABEL_FILL4}">?</text>`
3352
3553
  ).join("");
3353
3554
  }
3354
3555
  var MINI_ATTRS2 = `fill="transparent" stroke="${GLYPH_STROKE3}" stroke-width="1.5"`;
3355
3556
  function miniShapeSwatch(shape, x, y) {
3356
- const cx = round7(x + LEGEND_SWATCH_W / 2);
3357
- if (shape === "square") return `<rect x="${round7(cx - 6)}" y="${round7(y - 6)}" width="12" height="12" ${MINI_ATTRS2}/>`;
3557
+ const cx = round9(x + LEGEND_SWATCH_W / 2);
3558
+ if (shape === "square") return `<rect x="${round9(cx - 6)}" y="${round9(y - 6)}" width="12" height="12" ${MINI_ATTRS2}/>`;
3358
3559
  if (shape === "circle") return `<circle cx="${cx}" cy="${y}" r="6" ${MINI_ATTRS2}/>`;
3359
- return `<polygon points="${cx},${round7(y - 7)} ${round7(cx + 7)},${y} ${cx},${round7(y + 7)} ${round7(cx - 7)},${y}" ${MINI_ATTRS2}/>`;
3560
+ return `<polygon points="${cx},${round9(y - 7)} ${round9(cx + 7)},${y} ${cx},${round9(y + 7)} ${round9(cx - 7)},${y}" ${MINI_ATTRS2}/>`;
3360
3561
  }
3361
3562
  function miniSwatchCircle(filled, ink, x, y) {
3362
- const cx = round7(x + LEGEND_SWATCH_W / 2);
3563
+ const cx = round9(x + LEGEND_SWATCH_W / 2);
3363
3564
  return `<circle cx="${cx}" cy="${y}" r="6" fill="${filled ? ink : "transparent"}" stroke="${GLYPH_STROKE3}" stroke-width="1.5"/>`;
3364
3565
  }
3365
3566
  function miniCarrierSwatch(x, y) {
3366
- const cx = round7(x + LEGEND_SWATCH_W / 2);
3567
+ const cx = round9(x + LEGEND_SWATCH_W / 2);
3367
3568
  return `<circle cx="${cx}" cy="${y}" r="6" ${MINI_ATTRS2}/><circle cx="${cx}" cy="${y}" r="2" fill="${GLYPH_STROKE3}" stroke="none"/>`;
3368
3569
  }
3369
3570
  function miniDeceasedSwatch(x, y) {
3370
- const cx = round7(x + LEGEND_SWATCH_W / 2);
3371
- return `<circle cx="${cx}" cy="${y}" r="6" ${MINI_ATTRS2}/><line x1="${round7(cx - 7)}" y1="${round7(y - 7)}" x2="${round7(cx + 7)}" y2="${round7(y + 7)}" stroke="${GLYPH_STROKE3}" stroke-width="1.5"/>`;
3571
+ const cx = round9(x + LEGEND_SWATCH_W / 2);
3572
+ return `<circle cx="${cx}" cy="${y}" r="6" ${MINI_ATTRS2}/><line x1="${round9(cx - 7)}" y1="${round9(y - 7)}" x2="${round9(cx + 7)}" y2="${round9(y + 7)}" stroke="${GLYPH_STROKE3}" stroke-width="1.5"/>`;
3372
3573
  }
3373
3574
  function miniArrowSwatch(filled, x, y) {
3374
- const cx = round7(x + LEGEND_SWATCH_W / 2);
3375
- const tipX = round7(cx - 2);
3376
- const tipY = round7(y + 2);
3377
- return `<line x1="${round7(tipX - 8)}" y1="${round7(tipY + 8)}" x2="${tipX}" y2="${tipY}" stroke="${GLYPH_STROKE3}" stroke-width="1.5"/><polygon points="${tipX},${tipY} ${round7(tipX - 5)},${round7(tipY + 1)} ${round7(tipX - 1)},${round7(tipY + 5)}" fill="${filled ? GLYPH_STROKE3 : "transparent"}" stroke="${GLYPH_STROKE3}" stroke-width="1"/>`;
3575
+ const cx = round9(x + LEGEND_SWATCH_W / 2);
3576
+ const tipX = round9(cx - 2);
3577
+ const tipY = round9(y + 2);
3578
+ return `<line x1="${round9(tipX - 8)}" y1="${round9(tipY + 8)}" x2="${tipX}" y2="${tipY}" stroke="${GLYPH_STROKE3}" stroke-width="1.5"/><polygon points="${tipX},${tipY} ${round9(tipX - 5)},${round9(tipY + 1)} ${round9(tipX - 1)},${round9(tipY + 5)}" fill="${filled ? GLYPH_STROKE3 : "transparent"}" stroke="${GLYPH_STROKE3}" stroke-width="1"/>`;
3378
3579
  }
3379
3580
  function miniConsanguineousSwatch(x, y) {
3380
- const x1 = round7(x + 2);
3381
- const x2 = round7(x + LEGEND_SWATCH_W - 2);
3382
- return `<line x1="${x1}" y1="${round7(y - 1.5)}" x2="${x2}" y2="${round7(y - 1.5)}" stroke="${EDGE_INK5}" stroke-width="1.5"/><line x1="${x1}" y1="${round7(y + 1.5)}" x2="${x2}" y2="${round7(y + 1.5)}" stroke="${EDGE_INK5}" stroke-width="1.5"/>`;
3581
+ const x1 = round9(x + 2);
3582
+ const x2 = round9(x + LEGEND_SWATCH_W - 2);
3583
+ return `<line x1="${x1}" y1="${round9(y - 1.5)}" x2="${x2}" y2="${round9(y - 1.5)}" stroke="${EDGE_INK5}" stroke-width="1.5"/><line x1="${x1}" y1="${round9(y + 1.5)}" x2="${x2}" y2="${round9(y + 1.5)}" stroke="${EDGE_INK5}" stroke-width="1.5"/>`;
3383
3584
  }
3384
3585
  function miniTwinSwatch(zygosity, x, y) {
3385
- const cx = round7(x + LEGEND_SWATCH_W / 2);
3386
- const apexY = round7(y - 6);
3387
- const baseY = round7(y + 6);
3388
- const left = round7(cx - 6);
3389
- const right = round7(cx + 6);
3586
+ const cx = round9(x + LEGEND_SWATCH_W / 2);
3587
+ const apexY = round9(y - 6);
3588
+ const baseY = round9(y + 6);
3589
+ const left = round9(cx - 6);
3590
+ const right = round9(cx + 6);
3390
3591
  const stub = `<line x1="${cx}" y1="${apexY}" x2="${cx}" y2="${y}" stroke="${EDGE_INK5}" stroke-width="1.5"/><line x1="${left}" y1="${y}" x2="${right}" y2="${y}" stroke="${EDGE_INK5}" stroke-width="1.5"/><line x1="${left}" y1="${y}" x2="${left}" y2="${baseY}" stroke="${EDGE_INK5}" stroke-width="1.5"/><line x1="${right}" y1="${y}" x2="${right}" y2="${baseY}" stroke="${EDGE_INK5}" stroke-width="1.5"/>`;
3391
3592
  if (zygosity === "mz") {
3392
- return stub + `<line x1="${left}" y1="${round7(y + 3)}" x2="${right}" y2="${round7(y + 3)}" stroke="${EDGE_INK5}" stroke-width="1.5"/>`;
3593
+ return stub + `<line x1="${left}" y1="${round9(y + 3)}" x2="${right}" y2="${round9(y + 3)}" stroke="${EDGE_INK5}" stroke-width="1.5"/>`;
3393
3594
  }
3394
3595
  if (zygosity === "unknown") {
3395
- return stub + `<text x="${round7(cx + 8)}" y="${round7(y + 3)}" font-family="${FONT_FAMILY}" font-size="9" fill="${LABEL_FILL4}">?</text>`;
3596
+ return stub + `<text x="${round9(cx + 8)}" y="${round9(y + 3)}" font-family="${FONT_FAMILY}" font-size="9" fill="${LABEL_FILL4}">?</text>`;
3396
3597
  }
3397
3598
  return stub;
3398
3599
  }
@@ -3405,7 +3606,7 @@ function pedigreeLayoutSvg(layout, opts = {}) {
3405
3606
  for (const n of layout.nodes) parts.push(nodeSvg2(n, inkByCondition));
3406
3607
  for (const g of layout.generations) {
3407
3608
  parts.push(
3408
- `<text x="${round7(16)}" y="${round7(g.y + PED_LABEL_FONT * 0.32)}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${PED_LABEL_FONT}" font-weight="bold" fill="${LABEL_FILL4}">${xmlEscape(g.roman)}</text>`
3609
+ `<text x="${round9(16)}" y="${round9(g.y + PED_LABEL_FONT * 0.32)}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${PED_LABEL_FONT}" font-weight="bold" fill="${LABEL_FILL4}">${xmlEscape(g.roman)}</text>`
3409
3610
  );
3410
3611
  }
3411
3612
  let width = layout.width;
@@ -3571,13 +3772,13 @@ function phyloIssues(input) {
3571
3772
  push("tip-with-children", `node ${n.id} is declared internal but has no children`);
3572
3773
  }
3573
3774
  }
3574
- const GRAPH_BLOCKING2 = /* @__PURE__ */ new Set([
3775
+ const GRAPH_BLOCKING3 = /* @__PURE__ */ new Set([
3575
3776
  "duplicate-id",
3576
3777
  "unknown-endpoint",
3577
3778
  "unknown-root",
3578
3779
  "multiple-parents"
3579
3780
  ]);
3580
- if (!issues.some((i) => GRAPH_BLOCKING2.has(i.code))) {
3781
+ if (!issues.some((i) => GRAPH_BLOCKING3.has(i.code))) {
3581
3782
  const childrenOf = /* @__PURE__ */ new Map();
3582
3783
  for (const e of [...input.edges].sort((a, b) => a.id - b.id)) {
3583
3784
  const arr = childrenOf.get(e.parentId) ?? [];
@@ -3655,7 +3856,7 @@ var PHYLO_BRANCH_ID_BASE = 2e6;
3655
3856
  var PHYLO_EXTENSION_ID_BASE = 3e6;
3656
3857
  var PHYLO_ROOTSTUB_ID_BASE = 4e6;
3657
3858
  var PHYLO_SCALEBAR_ID = 5e6;
3658
- var round8 = (n) => Math.round(n * 100) / 100;
3859
+ var round10 = (n) => Math.round(n * 100) / 100;
3659
3860
  function niceScaleStep(maxLen) {
3660
3861
  if (!(maxLen > 0)) return 0;
3661
3862
  const target = maxLen / 4;
@@ -3781,8 +3982,8 @@ function computePhyloLayout(input, opts = {}) {
3781
3982
  edgeId: PHYLO_ROOTSTUB_ID_BASE + root.node.id,
3782
3983
  kind: "root-stub",
3783
3984
  points: [
3784
- { x: round8(Math.max(PADDING6 / 2, rootDrawX - ROOT_STUB)), y: round8(root.cy) },
3785
- { x: round8(rootDrawX), y: round8(root.cy) }
3985
+ { x: round10(Math.max(PADDING6 / 2, rootDrawX - ROOT_STUB)), y: round10(root.cy) },
3986
+ { x: round10(rootDrawX), y: round10(root.cy) }
3786
3987
  ],
3787
3988
  dotted: false,
3788
3989
  length: null,
@@ -3797,11 +3998,11 @@ function computePhyloLayout(input, opts = {}) {
3797
3998
  nodes.push({
3798
3999
  nodeId: w.node.id,
3799
4000
  isTip: tip,
3800
- cx: round8(drawX),
3801
- cy: round8(w.cy),
4001
+ cx: round10(drawX),
4002
+ cy: round10(w.cy),
3802
4003
  support,
3803
4004
  labelLines: labelLine,
3804
- labelLeft: round8(drawX + PHYLO_TIP_R + PHYLO_LABEL_GAP),
4005
+ labelLeft: round10(drawX + PHYLO_TIP_R + PHYLO_LABEL_GAP),
3805
4006
  depth: w.depth,
3806
4007
  title: nodeTitle(w.node, tip, isRoot, w.inLength, titleLabels)
3807
4008
  });
@@ -3810,8 +4011,8 @@ function computePhyloLayout(input, opts = {}) {
3810
4011
  edgeId: PHYLO_EXTENSION_ID_BASE + w.node.id,
3811
4012
  kind: "extension",
3812
4013
  points: [
3813
- { x: round8(w.cx), y: round8(w.cy) },
3814
- { x: round8(alignX), y: round8(w.cy) }
4014
+ { x: round10(w.cx), y: round10(w.cy) },
4015
+ { x: round10(alignX), y: round10(w.cy) }
3815
4016
  ],
3816
4017
  dotted: true,
3817
4018
  length: null,
@@ -3827,8 +4028,8 @@ function computePhyloLayout(input, opts = {}) {
3827
4028
  edgeId: PHYLO_CLADEBAR_ID_BASE + w.node.id,
3828
4029
  kind: "clade-bar",
3829
4030
  points: [
3830
- { x: round8(w.cx), y: round8(barTop) },
3831
- { x: round8(w.cx), y: round8(barBottom) }
4031
+ { x: round10(w.cx), y: round10(barTop) },
4032
+ { x: round10(w.cx), y: round10(barBottom) }
3832
4033
  ],
3833
4034
  dotted: false,
3834
4035
  length: null,
@@ -3840,8 +4041,8 @@ function computePhyloLayout(input, opts = {}) {
3840
4041
  edgeId: PHYLO_BRANCH_ID_BASE + c.node.id,
3841
4042
  kind: "branch",
3842
4043
  points: [
3843
- { x: round8(w.cx), y: round8(c.cy) },
3844
- { x: round8(c.cx), y: round8(c.cy) }
4044
+ { x: round10(w.cx), y: round10(c.cy) },
4045
+ { x: round10(c.cx), y: round10(c.cy) }
3845
4046
  ],
3846
4047
  dotted: false,
3847
4048
  length: c.inLength,
@@ -3858,17 +4059,17 @@ function computePhyloLayout(input, opts = {}) {
3858
4059
  const barY = treeBottom + SCALEBAR_ZONE / 2;
3859
4060
  scaleBar = {
3860
4061
  length: stepLen,
3861
- x: round8(barX),
3862
- y: round8(barY),
3863
- pxLength: round8(scaleBarPx),
4062
+ x: round10(barX),
4063
+ y: round10(barY),
4064
+ pxLength: round10(scaleBarPx),
3864
4065
  valueLabel: trimNumber(stepLen)
3865
4066
  };
3866
4067
  elements.push({
3867
4068
  edgeId: PHYLO_SCALEBAR_ID,
3868
4069
  kind: "scale-bar",
3869
4070
  points: [
3870
- { x: round8(barX), y: round8(barY) },
3871
- { x: round8(barX + scaleBarPx), y: round8(barY) }
4071
+ { x: round10(barX), y: round10(barY) },
4072
+ { x: round10(barX + scaleBarPx), y: round10(barY) }
3872
4073
  ],
3873
4074
  dotted: false,
3874
4075
  length: stepLen,
@@ -3882,18 +4083,18 @@ function computePhyloLayout(input, opts = {}) {
3882
4083
  var GLYPH_STROKE4 = "#52525b";
3883
4084
  var LABEL_FILL5 = "#3f3f46";
3884
4085
  var EDGE_INK6 = "#71717a";
3885
- var round9 = (n) => Math.round(n * 100) / 100;
4086
+ var round11 = (n) => Math.round(n * 100) / 100;
3886
4087
  function nodeSvg3(n, showSupport) {
3887
4088
  const pieces = [`<title>${xmlEscape(n.title)}</title>`];
3888
4089
  pieces.push(`<circle cx="${n.cx}" cy="${n.cy}" r="2.5" fill="${GLYPH_STROKE4}"/>`);
3889
4090
  if (n.labelLines.length > 0) {
3890
4091
  pieces.push(
3891
- `<text x="${n.labelLeft}" y="${round9(n.cy + PHYLO_LABEL_FONT * 0.32)}" font-family="${FONT_FAMILY}" font-size="${PHYLO_LABEL_FONT}" fill="${LABEL_FILL5}">${xmlEscape(n.labelLines[0])}</text>`
4092
+ `<text x="${n.labelLeft}" y="${round11(n.cy + PHYLO_LABEL_FONT * 0.32)}" font-family="${FONT_FAMILY}" font-size="${PHYLO_LABEL_FONT}" fill="${LABEL_FILL5}">${xmlEscape(n.labelLines[0])}</text>`
3892
4093
  );
3893
4094
  }
3894
4095
  if (showSupport && !n.isTip && n.support !== null) {
3895
4096
  pieces.push(
3896
- `<text x="${round9(n.cx - 4)}" y="${round9(n.cy - 3)}" text-anchor="end" font-family="${FONT_FAMILY}" font-size="${PHYLO_SUPPORT_FONT}" fill="${LABEL_FILL5}">${xmlEscape(String(n.support))}</text>`
4097
+ `<text x="${round11(n.cx - 4)}" y="${round11(n.cy - 3)}" text-anchor="end" font-family="${FONT_FAMILY}" font-size="${PHYLO_SUPPORT_FONT}" fill="${LABEL_FILL5}">${xmlEscape(String(n.support))}</text>`
3897
4098
  );
3898
4099
  }
3899
4100
  return `<g data-node-id="n${n.nodeId}">${pieces.join("")}</g>`;
@@ -3908,18 +4109,18 @@ function elementSvg4(el) {
3908
4109
  function scaleBarLabelSvg(layout) {
3909
4110
  const bar = layout.scaleBar;
3910
4111
  if (bar === null) return "";
3911
- const tick = (x) => `<line x1="${x}" y1="${round9(bar.y - 3)}" x2="${x}" y2="${round9(bar.y + 3)}" stroke="${EDGE_INK6}" stroke-width="1.5" stroke-opacity="0.75"/>`;
3912
- return tick(bar.x) + tick(round9(bar.x + bar.pxLength)) + `<text x="${round9(bar.x + bar.pxLength / 2)}" y="${round9(bar.y - 6)}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${PHYLO_SUPPORT_FONT}" fill="${LABEL_FILL5}">${xmlEscape(bar.valueLabel)}</text>`;
4112
+ const tick = (x) => `<line x1="${x}" y1="${round11(bar.y - 3)}" x2="${x}" y2="${round11(bar.y + 3)}" stroke="${EDGE_INK6}" stroke-width="1.5" stroke-opacity="0.75"/>`;
4113
+ return tick(bar.x) + tick(round11(bar.x + bar.pxLength)) + `<text x="${round11(bar.x + bar.pxLength / 2)}" y="${round11(bar.y - 6)}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${PHYLO_SUPPORT_FONT}" fill="${LABEL_FILL5}">${xmlEscape(bar.valueLabel)}</text>`;
3913
4114
  }
3914
4115
  function supportSwatch(x, y) {
3915
- const cx = round9(x + LEGEND_SWATCH_W / 2);
3916
- return `<circle cx="${cx}" cy="${y}" r="2.5" fill="${GLYPH_STROKE4}"/><text x="${round9(cx + 5)}" y="${round9(y + PHYLO_SUPPORT_FONT * 0.32)}" font-family="${FONT_FAMILY}" font-size="${PHYLO_SUPPORT_FONT}" fill="${LABEL_FILL5}">95</text>`;
4116
+ const cx = round11(x + LEGEND_SWATCH_W / 2);
4117
+ return `<circle cx="${cx}" cy="${y}" r="2.5" fill="${GLYPH_STROKE4}"/><text x="${round11(cx + 5)}" y="${round11(y + PHYLO_SUPPORT_FONT * 0.32)}" font-family="${FONT_FAMILY}" font-size="${PHYLO_SUPPORT_FONT}" fill="${LABEL_FILL5}">95</text>`;
3917
4118
  }
3918
4119
  function scaleSwatch(x, y) {
3919
- return `<line x1="${round9(x)}" y1="${y}" x2="${round9(x + LEGEND_SWATCH_W)}" y2="${y}" stroke="${EDGE_INK6}" stroke-width="1.5" stroke-opacity="0.75"/>`;
4120
+ return `<line x1="${round11(x)}" y1="${y}" x2="${round11(x + LEGEND_SWATCH_W)}" y2="${y}" stroke="${EDGE_INK6}" stroke-width="1.5" stroke-opacity="0.75"/>`;
3920
4121
  }
3921
4122
  function alignedTipSwatch(x, y) {
3922
- return `<line x1="${round9(x)}" y1="${y}" x2="${round9(x + LEGEND_SWATCH_W)}" y2="${y}" stroke="${EDGE_INK6}" stroke-width="1.5" stroke-opacity="0.5" stroke-dasharray="2,3"/>`;
4123
+ return `<line x1="${round11(x)}" y1="${y}" x2="${round11(x + LEGEND_SWATCH_W)}" y2="${y}" stroke="${EDGE_INK6}" stroke-width="1.5" stroke-opacity="0.5" stroke-dasharray="2,3"/>`;
3923
4124
  }
3924
4125
  function phyloLayoutSvg(layout, opts = {}) {
3925
4126
  const labels = opts.labels ?? PHYLO_SVG_LABELS_EN;
@@ -4024,9 +4225,9 @@ function orgChartIssues(input) {
4024
4225
  }
4025
4226
  const reportById = /* @__PURE__ */ new Map();
4026
4227
  const dupReportIds = /* @__PURE__ */ new Set();
4027
- for (const r of input.reports) {
4028
- if (reportById.has(r.id)) dupReportIds.add(r.id);
4029
- else reportById.set(r.id, r);
4228
+ for (const r2 of input.reports) {
4229
+ if (reportById.has(r2.id)) dupReportIds.add(r2.id);
4230
+ else reportById.set(r2.id, r2);
4030
4231
  }
4031
4232
  for (const id of [...dupReportIds].sort((a, b) => a - b)) {
4032
4233
  push("duplicate-id", `duplicate report id ${id}`);
@@ -4039,32 +4240,32 @@ function orgChartIssues(input) {
4039
4240
  const assistantReportEdge = /* @__PURE__ */ new Map();
4040
4241
  const solidManagedEdges = /* @__PURE__ */ new Map();
4041
4242
  const dottedDegree = /* @__PURE__ */ new Map();
4042
- for (const r of reports) {
4043
- const hasManager = positionById.has(r.managerId);
4044
- const hasReport = positionById.has(r.reportId);
4243
+ for (const r2 of reports) {
4244
+ const hasManager = positionById.has(r2.managerId);
4245
+ const hasReport = positionById.has(r2.reportId);
4045
4246
  if (!hasManager) {
4046
- push("unknown-manager", `report ${r.id} managerId ${r.managerId} is not a declared position`);
4247
+ push("unknown-manager", `report ${r2.id} managerId ${r2.managerId} is not a declared position`);
4047
4248
  }
4048
4249
  if (!hasReport) {
4049
- push("unknown-report", `report ${r.id} reportId ${r.reportId} is not a declared position`);
4250
+ push("unknown-report", `report ${r2.id} reportId ${r2.reportId} is not a declared position`);
4050
4251
  }
4051
- if (r.managerId === r.reportId) {
4052
- push("self-report", `report ${r.id} has managerId === reportId (${r.managerId})`);
4252
+ if (r2.managerId === r2.reportId) {
4253
+ push("self-report", `report ${r2.id} has managerId === reportId (${r2.managerId})`);
4053
4254
  }
4054
- if (r.kind !== "dotted") {
4055
- const arr = solidParents.get(r.reportId) ?? [];
4056
- arr.push(r.id);
4057
- solidParents.set(r.reportId, arr);
4058
- const managed = solidManagedEdges.get(r.managerId) ?? [];
4059
- managed.push(r.id);
4060
- solidManagedEdges.set(r.managerId, managed);
4255
+ if (r2.kind !== "dotted") {
4256
+ const arr = solidParents.get(r2.reportId) ?? [];
4257
+ arr.push(r2.id);
4258
+ solidParents.set(r2.reportId, arr);
4259
+ const managed = solidManagedEdges.get(r2.managerId) ?? [];
4260
+ managed.push(r2.id);
4261
+ solidManagedEdges.set(r2.managerId, managed);
4061
4262
  }
4062
- if (r.kind === "assistant" && r.managerId !== r.reportId && !assistantReportEdge.has(r.reportId)) {
4063
- assistantReportEdge.set(r.reportId, r.id);
4263
+ if (r2.kind === "assistant" && r2.managerId !== r2.reportId && !assistantReportEdge.has(r2.reportId)) {
4264
+ assistantReportEdge.set(r2.reportId, r2.id);
4064
4265
  }
4065
- if (r.kind === "dotted" && r.managerId !== r.reportId) {
4066
- if (positionById.has(r.managerId)) dottedDegree.set(r.managerId, (dottedDegree.get(r.managerId) ?? 0) + 1);
4067
- if (positionById.has(r.reportId)) dottedDegree.set(r.reportId, (dottedDegree.get(r.reportId) ?? 0) + 1);
4266
+ if (r2.kind === "dotted" && r2.managerId !== r2.reportId) {
4267
+ if (positionById.has(r2.managerId)) dottedDegree.set(r2.managerId, (dottedDegree.get(r2.managerId) ?? 0) + 1);
4268
+ if (positionById.has(r2.reportId)) dottedDegree.set(r2.reportId, (dottedDegree.get(r2.reportId) ?? 0) + 1);
4068
4269
  }
4069
4270
  }
4070
4271
  for (const [reportId, edgeIds] of [...solidParents.entries()].sort((a, b) => a[0] - b[0])) {
@@ -4095,12 +4296,12 @@ function orgChartIssues(input) {
4095
4296
  }
4096
4297
  if (!issues.some((i) => GRAPH_BLOCKING.has(i.code))) {
4097
4298
  const solidChildren = /* @__PURE__ */ new Map();
4098
- for (const r of reports) {
4099
- if (r.kind === "dotted") continue;
4100
- if (r.managerId === r.reportId) continue;
4101
- const arr = solidChildren.get(r.managerId) ?? [];
4102
- arr.push(r.reportId);
4103
- solidChildren.set(r.managerId, arr);
4299
+ for (const r2 of reports) {
4300
+ if (r2.kind === "dotted") continue;
4301
+ if (r2.managerId === r2.reportId) continue;
4302
+ const arr = solidChildren.get(r2.managerId) ?? [];
4303
+ arr.push(r2.reportId);
4304
+ solidChildren.set(r2.managerId, arr);
4104
4305
  }
4105
4306
  for (const arr of solidChildren.values()) arr.sort((a, b) => a - b);
4106
4307
  const color = /* @__PURE__ */ new Map();
@@ -4167,26 +4368,9 @@ var ORG_BUS_ID_BASE = 2e6;
4167
4368
  var ORG_DROP_ID_BASE = 3e6;
4168
4369
  var ORG_ASSIST_ID_BASE = 4e6;
4169
4370
  var ORG_DOTTED_ID_BASE = 5e6;
4170
- var round10 = (n) => Math.round(n * 100) / 100;
4171
- var drawnLeftEdge = (cx, boxW) => round10(round10(cx) - round10(boxW) / 2);
4172
- var drawnRightEdge = (cx, boxW) => drawnLeftEdge(cx, boxW) + round10(boxW);
4173
- function packSubtree(node, gaps) {
4174
- const { ownHalfL, ownHalfR, children } = node;
4175
- if (children.length === 0) {
4176
- return { halfL: ownHalfL, halfR: ownHalfR, offsets: [] };
4177
- }
4178
- const xs = [0];
4179
- for (let i = 1; i < children.length; i++) {
4180
- xs.push(xs[i - 1] + children[i - 1].halfR + gaps.siblingGap + children[i].halfL);
4181
- }
4182
- const first = children[0];
4183
- const last = children[children.length - 1];
4184
- const axis = (xs[0] + xs[xs.length - 1]) / 2;
4185
- const offsets = xs.map((x) => x - axis);
4186
- const halfL = Math.max(ownHalfL, axis - (xs[0] - first.halfL));
4187
- const halfR = Math.max(ownHalfR, xs[xs.length - 1] + last.halfR - axis);
4188
- return { halfL, halfR, offsets };
4189
- }
4371
+ var round12 = (n) => Math.round(n * 100) / 100;
4372
+ var drawnLeftEdge = (cx, boxW) => round12(round12(cx) - round12(boxW) / 2);
4373
+ var drawnRightEdge = (cx, boxW) => drawnLeftEdge(cx, boxW) + round12(boxW);
4190
4374
  function measurePosition(position, maxLabelChars, vacantWord) {
4191
4375
  const nameLines = position.name === "" ? [] : wrapLabelBalanced(clampLabel(position.name, maxLabelChars), 2);
4192
4376
  const titleLines = position.title === null ? [] : wrapLabelBalanced(clampLabel(position.title, maxLabelChars), 2);
@@ -4219,16 +4403,16 @@ function computeOrgChartLayout(input, opts = {}) {
4219
4403
  const vacantWord = titleLabels.vacant;
4220
4404
  const positionById = new Map(input.positions.map((p) => [p.id, p]));
4221
4405
  const reports = [...input.reports].sort((a, b) => a.id - b.id);
4222
- const solidReports = reports.filter((r) => r.kind !== "dotted");
4406
+ const solidReports = reports.filter((r2) => r2.kind !== "dotted");
4223
4407
  const lineChildIds = /* @__PURE__ */ new Map();
4224
4408
  const assistChildIds = /* @__PURE__ */ new Map();
4225
4409
  const solidParentReportId = /* @__PURE__ */ new Set();
4226
- for (const r of solidReports) {
4227
- solidParentReportId.add(r.reportId);
4228
- const bucket = r.kind === "assistant" ? assistChildIds : lineChildIds;
4229
- const arr = bucket.get(r.managerId) ?? [];
4230
- arr.push(r);
4231
- bucket.set(r.managerId, arr);
4410
+ for (const r2 of solidReports) {
4411
+ solidParentReportId.add(r2.reportId);
4412
+ const bucket = r2.kind === "assistant" ? assistChildIds : lineChildIds;
4413
+ const arr = bucket.get(r2.managerId) ?? [];
4414
+ arr.push(r2);
4415
+ bucket.set(r2.managerId, arr);
4232
4416
  }
4233
4417
  for (const arr of lineChildIds.values()) arr.sort((a, b) => a.reportId - b.reportId);
4234
4418
  for (const arr of assistChildIds.values()) arr.sort((a, b) => a.reportId - b.reportId);
@@ -4258,12 +4442,12 @@ function computeOrgChartLayout(input, opts = {}) {
4258
4442
  const position = positionById.get(positionId);
4259
4443
  const inst = newInst(position, depth, assistReport);
4260
4444
  allInsts.push(inst);
4261
- for (const r of assistChildIds.get(positionId) ?? []) {
4262
- const a = build(r.reportId, depth + 1, r);
4263
- inst.assistants.push({ inst: a, report: r });
4445
+ for (const r2 of assistChildIds.get(positionId) ?? []) {
4446
+ const a = build(r2.reportId, depth + 1, r2);
4447
+ inst.assistants.push({ inst: a, report: r2 });
4264
4448
  }
4265
- for (const r of lineChildIds.get(positionId) ?? []) {
4266
- inst.lineChildren.push(build(r.reportId, depth + 1, null));
4449
+ for (const r2 of lineChildIds.get(positionId) ?? []) {
4450
+ inst.lineChildren.push(build(r2.reportId, depth + 1, null));
4267
4451
  }
4268
4452
  return inst;
4269
4453
  };
@@ -4337,10 +4521,10 @@ function computeOrgChartLayout(input, opts = {}) {
4337
4521
  const pushNode = (inst) => {
4338
4522
  nodes.push({
4339
4523
  positionId: inst.position.id,
4340
- cx: round10(inst.cx),
4341
- top: round10(rowTop[inst.depth]),
4342
- boxW: round10(inst.m.boxW),
4343
- boxH: round10(inst.m.boxH),
4524
+ cx: round12(inst.cx),
4525
+ top: round12(rowTop[inst.depth]),
4526
+ boxW: round12(inst.m.boxW),
4527
+ boxH: round12(inst.m.boxH),
4344
4528
  style: inst.style,
4345
4529
  nameLines: inst.m.nameLines,
4346
4530
  titleLines: inst.m.titleLines,
@@ -4363,10 +4547,10 @@ function computeOrgChartLayout(input, opts = {}) {
4363
4547
  const assistMidY = assistTop + a.m.boxH / 2;
4364
4548
  nodes.push({
4365
4549
  positionId: a.position.id,
4366
- cx: round10(a.cx),
4367
- top: round10(assistTop),
4368
- boxW: round10(a.m.boxW),
4369
- boxH: round10(a.m.boxH),
4550
+ cx: round12(a.cx),
4551
+ top: round12(assistTop),
4552
+ boxW: round12(a.m.boxW),
4553
+ boxH: round12(a.m.boxH),
4370
4554
  style: a.style,
4371
4555
  nameLines: a.m.nameLines,
4372
4556
  titleLines: a.m.titleLines,
@@ -4381,8 +4565,8 @@ function computeOrgChartLayout(input, opts = {}) {
4381
4565
  edgeId: ORG_ASSIST_ID_BASE + report.reportId,
4382
4566
  kind: "assist",
4383
4567
  points: [
4384
- { x: round10(inst.cx), y: round10(assistMidY) },
4385
- { x: drawnRightEdge(a.cx, a.m.boxW), y: round10(assistMidY) }
4568
+ { x: round12(inst.cx), y: round12(assistMidY) },
4569
+ { x: drawnRightEdge(a.cx, a.m.boxW), y: round12(assistMidY) }
4386
4570
  ],
4387
4571
  dashed: false,
4388
4572
  title: reportTitle("assistant", report.label),
@@ -4398,8 +4582,8 @@ function computeOrgChartLayout(input, opts = {}) {
4398
4582
  edgeId: ORG_STEM_ID_BASE + inst.position.id,
4399
4583
  kind: "stem",
4400
4584
  points: [
4401
- { x: round10(inst.cx), y: round10(boxBottom) },
4402
- { x: round10(inst.cx), y: round10(by) }
4585
+ { x: round12(inst.cx), y: round12(boxBottom) },
4586
+ { x: round12(inst.cx), y: round12(by) }
4403
4587
  ],
4404
4588
  dashed: false,
4405
4589
  title: reportTitle("line", null),
@@ -4411,8 +4595,8 @@ function computeOrgChartLayout(input, opts = {}) {
4411
4595
  edgeId: ORG_BUS_ID_BASE + inst.position.id,
4412
4596
  kind: "bus",
4413
4597
  points: [
4414
- { x: round10(Math.min(...childCxs)), y: round10(by) },
4415
- { x: round10(Math.max(...childCxs)), y: round10(by) }
4598
+ { x: round12(Math.min(...childCxs)), y: round12(by) },
4599
+ { x: round12(Math.max(...childCxs)), y: round12(by) }
4416
4600
  ],
4417
4601
  dashed: false,
4418
4602
  title: reportTitle("line", null),
@@ -4420,15 +4604,15 @@ function computeOrgChartLayout(input, opts = {}) {
4420
4604
  });
4421
4605
  }
4422
4606
  const lineReports = lineChildIds.get(inst.position.id) ?? [];
4423
- const lineReportByChildId = new Map(lineReports.map((r) => [r.reportId, r]));
4607
+ const lineReportByChildId = new Map(lineReports.map((r2) => [r2.reportId, r2]));
4424
4608
  for (const c of inst.lineChildren) {
4425
4609
  const lineReport = lineReportByChildId.get(c.position.id);
4426
4610
  elements.push({
4427
4611
  edgeId: ORG_DROP_ID_BASE + c.position.id,
4428
4612
  kind: "drop",
4429
4613
  points: [
4430
- { x: round10(c.cx), y: round10(by) },
4431
- { x: round10(c.cx), y: round10(rowTop[c.depth]) }
4614
+ { x: round12(c.cx), y: round12(by) },
4615
+ { x: round12(c.cx), y: round12(rowTop[c.depth]) }
4432
4616
  ],
4433
4617
  dashed: false,
4434
4618
  title: reportTitle("line", null),
@@ -4466,7 +4650,7 @@ function computeOrgChartLayout(input, opts = {}) {
4466
4650
  }
4467
4651
  const nodeById = new Map(nodes.map((n) => [n.positionId, n]));
4468
4652
  const dottedReports = reports.filter(
4469
- (r) => r.kind === "dotted" && nodeById.has(r.managerId) && nodeById.has(r.reportId) && r.managerId !== r.reportId
4653
+ (r2) => r2.kind === "dotted" && nodeById.has(r2.managerId) && nodeById.has(r2.reportId) && r2.managerId !== r2.reportId
4470
4654
  );
4471
4655
  let maxBoxBottom = PADDING7;
4472
4656
  for (const n of nodes) maxBoxBottom = Math.max(maxBoxBottom, n.top + n.boxH);
@@ -4474,14 +4658,14 @@ function computeOrgChartLayout(input, opts = {}) {
4474
4658
  let channelLanes = 0;
4475
4659
  if (dottedReports.length > 0) {
4476
4660
  const laneOf = /* @__PURE__ */ new Map();
4477
- laneCount = allocateLanes2(
4478
- dottedReports.map((r) => {
4479
- const rep = escapeByPos.get(r.reportId);
4480
- const mgr = escapeByPos.get(r.managerId);
4661
+ laneCount = allocateLanes(
4662
+ dottedReports.map((r2) => {
4663
+ const rep = escapeByPos.get(r2.reportId);
4664
+ const mgr = escapeByPos.get(r2.managerId);
4481
4665
  return {
4482
4666
  lo: Math.min(rep.midY, mgr.midY),
4483
4667
  hi: Math.max(rep.midY, mgr.midY),
4484
- set: (lane) => laneOf.set(r.id, lane)
4668
+ set: (lane) => laneOf.set(r2.id, lane)
4485
4669
  };
4486
4670
  })
4487
4671
  );
@@ -4496,19 +4680,19 @@ function computeOrgChartLayout(input, opts = {}) {
4496
4680
  const sign = slot % 2 === 1 ? -1 : 1;
4497
4681
  return mid + sign * Math.ceil(slot / 2) * step;
4498
4682
  };
4499
- const geoms = dottedReports.map((r, i) => {
4500
- const rep = escapeByPos.get(r.reportId);
4501
- const mgr = escapeByPos.get(r.managerId);
4683
+ const geoms = dottedReports.map((r2, i) => {
4684
+ const rep = escapeByPos.get(r2.reportId);
4685
+ const mgr = escapeByPos.get(r2.managerId);
4502
4686
  return {
4503
- r,
4687
+ r: r2,
4504
4688
  i,
4505
- repMidY: round10(nextExitY(r.reportId, rep.midY, rep.boxH)),
4506
- mgrMidY: round10(nextExitY(r.managerId, mgr.midY, mgr.boxH)),
4507
- repBoxEdge: round10(rep.boxEdge),
4508
- mgrBoxEdge: round10(mgr.boxEdge),
4509
- repChY: round10(channelTop + 2 * i * ORG_MATRIX_LANE_PITCH),
4510
- mgrChY: round10(channelTop + (2 * i + 1) * ORG_MATRIX_LANE_PITCH),
4511
- laneX: round10(canvasRight + ORG_MATRIX_GUTTER_GAP + laneOf.get(r.id) * ORG_MATRIX_LANE_PITCH),
4689
+ repMidY: round12(nextExitY(r2.reportId, rep.midY, rep.boxH)),
4690
+ mgrMidY: round12(nextExitY(r2.managerId, mgr.midY, mgr.boxH)),
4691
+ repBoxEdge: round12(rep.boxEdge),
4692
+ mgrBoxEdge: round12(mgr.boxEdge),
4693
+ repChY: round12(channelTop + 2 * i * ORG_MATRIX_LANE_PITCH),
4694
+ mgrChY: round12(channelTop + (2 * i + 1) * ORG_MATRIX_LANE_PITCH),
4695
+ laneX: round12(canvasRight + ORG_MATRIX_GUTTER_GAP + laneOf.get(r2.id) * ORG_MATRIX_LANE_PITCH),
4512
4696
  repEscX: 0,
4513
4697
  mgrEscX: 0
4514
4698
  };
@@ -4546,14 +4730,14 @@ function computeOrgChartLayout(input, opts = {}) {
4546
4730
  else if (v.side === -1 && bRight < v.escCol - VERT_EPS) band = Math.min(band, v.escCol - bRight);
4547
4731
  }
4548
4732
  let lane = 0;
4549
- let candidate = round10(v.escCol);
4733
+ let candidate = round12(v.escCol);
4550
4734
  while (placed.some((p) => Math.abs(p.x - candidate) < VERT_EPS && yOverlap(p.lo, p.hi, v.lo, v.hi))) {
4551
4735
  lane += 1;
4552
- candidate = round10(v.escCol + v.side * lane * ORG_MATRIX_ESCAPE_STEP);
4736
+ candidate = round12(v.escCol + v.side * lane * ORG_MATRIX_ESCAPE_STEP);
4553
4737
  }
4554
4738
  if (lane > 0 && lane * ORG_MATRIX_ESCAPE_STEP >= band - VERT_EPS) {
4555
4739
  throw new Error(
4556
- `org-chart: escape-column-overflow \u2014 escape vertical nudged ${lane * ORG_MATRIX_ESCAPE_STEP}px from its base column exceeds the ${round10(band)}px box-free band`
4740
+ `org-chart: escape-column-overflow \u2014 escape vertical nudged ${lane * ORG_MATRIX_ESCAPE_STEP}px from its base column exceeds the ${round12(band)}px box-free band`
4557
4741
  );
4558
4742
  }
4559
4743
  placed.push({ x: candidate, lo: v.lo, hi: v.hi });
@@ -4599,25 +4783,6 @@ function collapseDegenerate(points) {
4599
4783
  }
4600
4784
  return out;
4601
4785
  }
4602
- function allocateLanes2(items) {
4603
- const lanes = [];
4604
- for (const it of items) {
4605
- let chosen = -1;
4606
- for (let l = 0; l < lanes.length; l++) {
4607
- if (lanes[l].every((o) => it.hi <= o.lo || it.lo >= o.hi)) {
4608
- chosen = l;
4609
- break;
4610
- }
4611
- }
4612
- if (chosen === -1) {
4613
- chosen = lanes.length;
4614
- lanes.push([]);
4615
- }
4616
- lanes[chosen].push({ lo: it.lo, hi: it.hi });
4617
- it.set(chosen);
4618
- }
4619
- return lanes.length;
4620
- }
4621
4786
 
4622
4787
  // src/org-chart/svg.ts
4623
4788
  var GLYPH_STROKE5 = "#52525b";
@@ -4626,25 +4791,25 @@ var EDGE_INK7 = "#71717a";
4626
4791
  var GLYPH_ATTRS3 = `fill="transparent" stroke="${GLYPH_STROKE5}" stroke-width="2"`;
4627
4792
  var VACANT_DASH = `stroke-dasharray="6,4"`;
4628
4793
  var DOTTED = EDGE_STROKE.distant;
4629
- var round11 = (n) => Math.round(n * 100) / 100;
4794
+ var round13 = (n) => Math.round(n * 100) / 100;
4630
4795
  var ORG_BOX_PAD_Y2 = 9;
4631
4796
  function boxSvg(n) {
4632
- const left = round11(n.cx - n.boxW / 2);
4797
+ const left = round13(n.cx - n.boxW / 2);
4633
4798
  const pieces = [`<title>${xmlEscape(n.title)}</title>`];
4634
4799
  if (n.style === "dashed") {
4635
4800
  pieces.push(
4636
4801
  `<rect x="${left}" y="${n.top}" width="${n.boxW}" height="${n.boxH}" rx="2" ${GLYPH_ATTRS3} ${VACANT_DASH}/>`
4637
4802
  );
4638
- const ix = round11(left + 3);
4639
- const iy = round11(n.top + 3);
4640
- const iw = round11(n.boxW - 6);
4641
- const ih = round11(n.boxH - 6);
4803
+ const ix = round13(left + 3);
4804
+ const iy = round13(n.top + 3);
4805
+ const iw = round13(n.boxW - 6);
4806
+ const ih = round13(n.boxH - 6);
4642
4807
  pieces.push(`<rect x="${ix}" y="${iy}" width="${iw}" height="${ih}" rx="2" ${GLYPH_ATTRS3} ${VACANT_DASH}/>`);
4643
4808
  } else {
4644
4809
  pieces.push(`<rect x="${left}" y="${n.top}" width="${n.boxW}" height="${n.boxH}" rx="2" ${GLYPH_ATTRS3}/>`);
4645
4810
  }
4646
4811
  let lineIndex = 0;
4647
- const firstBaseline = (i) => round11(n.top + ORG_BOX_PAD_Y2 + ORG_LABEL_LINE_H / 2 + i * ORG_LABEL_LINE_H + ORG_LABEL_FONT * 0.32);
4812
+ const firstBaseline = (i) => round13(n.top + ORG_BOX_PAD_Y2 + ORG_LABEL_LINE_H / 2 + i * ORG_LABEL_LINE_H + ORG_LABEL_FONT * 0.32);
4648
4813
  const textBlock = (lines, font) => {
4649
4814
  if (lines.length === 0) return "";
4650
4815
  const tspans = lines.map((line, i) => `<tspan x="${n.cx}" y="${firstBaseline(lineIndex + i)}">${xmlEscape(line)}</tspan>`).join("");
@@ -4655,7 +4820,7 @@ function boxSvg(n) {
4655
4820
  pieces.push(textBlock(n.titleLines, ORG_TITLE_FONT));
4656
4821
  pieces.push(textBlock(n.subtitleLines, ORG_TITLE_FONT));
4657
4822
  if (n.vacantMarker !== null) pieces.push(textBlock([n.vacantMarker], ORG_TITLE_FONT));
4658
- if (n.annotated) pieces.push(annotationDot(round11(n.cx + n.boxW / 2) - 4, n.top + 4));
4823
+ if (n.annotated) pieces.push(annotationDot(round13(n.cx + n.boxW / 2) - 4, n.top + 4));
4659
4824
  return `<g data-node-id="p${n.positionId}">${pieces.filter((p) => p !== "").join("")}</g>`;
4660
4825
  }
4661
4826
  function elementSvg5(el) {
@@ -4674,26 +4839,26 @@ function elementSvg5(el) {
4674
4839
  }
4675
4840
  var MINI_ATTRS3 = `fill="transparent" stroke="${GLYPH_STROKE5}" stroke-width="1.5"`;
4676
4841
  function lineSwatch(x, y) {
4677
- const x1 = round11(x + 2);
4678
- const x2 = round11(x + LEGEND_SWATCH_W - 2);
4842
+ const x1 = round13(x + 2);
4843
+ const x2 = round13(x + LEGEND_SWATCH_W - 2);
4679
4844
  return `<line x1="${x1}" y1="${y}" x2="${x2}" y2="${y}" stroke="${EDGE_INK7}" stroke-width="1.5" stroke-opacity="0.75"/>`;
4680
4845
  }
4681
4846
  function assistantSwatch(x, y) {
4682
- const stemX = round11(x + LEGEND_SWATCH_W - 4);
4683
- const boxX = round11(x + 1);
4684
- return `<line x1="${stemX}" y1="${round11(y - 4)}" x2="${stemX}" y2="${y}" stroke="${EDGE_INK7}" stroke-width="1.5" stroke-opacity="0.75"/><line x1="${stemX}" y1="${y}" x2="${round11(boxX + 8)}" y2="${y}" stroke="${EDGE_INK7}" stroke-width="1.5" stroke-opacity="0.75"/><rect x="${boxX}" y="${round11(y - 3.5)}" width="8" height="7" rx="1" ${MINI_ATTRS3}/>`;
4847
+ const stemX = round13(x + LEGEND_SWATCH_W - 4);
4848
+ const boxX = round13(x + 1);
4849
+ return `<line x1="${stemX}" y1="${round13(y - 4)}" x2="${stemX}" y2="${y}" stroke="${EDGE_INK7}" stroke-width="1.5" stroke-opacity="0.75"/><line x1="${stemX}" y1="${y}" x2="${round13(boxX + 8)}" y2="${y}" stroke="${EDGE_INK7}" stroke-width="1.5" stroke-opacity="0.75"/><rect x="${boxX}" y="${round13(y - 3.5)}" width="8" height="7" rx="1" ${MINI_ATTRS3}/>`;
4685
4850
  }
4686
4851
  function dottedSwatch(x, y) {
4687
- const x1 = round11(x + 2);
4688
- const x2 = round11(x + LEGEND_SWATCH_W - 2);
4852
+ const x1 = round13(x + 2);
4853
+ const x2 = round13(x + LEGEND_SWATCH_W - 2);
4689
4854
  const dash = DOTTED.dash === null ? "" : ` stroke-dasharray="${DOTTED.dash[0]},${DOTTED.dash[1]}"`;
4690
4855
  return `<line x1="${x1}" y1="${y}" x2="${x2}" y2="${y}" stroke="${EDGE_INK7}" stroke-width="${DOTTED.width}" stroke-opacity="${DOTTED.opacity}"${dash}/>`;
4691
4856
  }
4692
4857
  function vacantSwatch(x, y) {
4693
- const cx = round11(x + LEGEND_SWATCH_W / 2);
4694
- const bx = round11(cx - 8);
4695
- const by = round11(y - 5);
4696
- return `<rect x="${bx}" y="${by}" width="16" height="10" rx="1" ${MINI_ATTRS3} stroke-dasharray="3,2"/><rect x="${round11(bx + 2)}" y="${round11(by + 2)}" width="12" height="6" rx="1" ${MINI_ATTRS3} stroke-dasharray="3,2"/>`;
4858
+ const cx = round13(x + LEGEND_SWATCH_W / 2);
4859
+ const bx = round13(cx - 8);
4860
+ const by = round13(y - 5);
4861
+ return `<rect x="${bx}" y="${by}" width="16" height="10" rx="1" ${MINI_ATTRS3} stroke-dasharray="3,2"/><rect x="${round13(bx + 2)}" y="${round13(by + 2)}" width="12" height="6" rx="1" ${MINI_ATTRS3} stroke-dasharray="3,2"/>`;
4697
4862
  }
4698
4863
  function orgChartLayoutSvg(layout, opts = {}) {
4699
4864
  const labels = opts.labels ?? ORG_CHART_SVG_LABELS_EN;
@@ -4741,6 +4906,1654 @@ function orgChartSvg(input, opts = {}) {
4741
4906
  return { svg, layout };
4742
4907
  }
4743
4908
 
4909
+ // src/prisma/types.ts
4910
+ var PRISMA_PHASES = ["identification", "screening", "included"];
4911
+ var PRISMA_BOX_KINDS = ["flow", "exclusion"];
4912
+ var PRISMA_COLUMNS = ["main", "new", "previous"];
4913
+
4914
+ // src/prisma/validate.ts
4915
+ var PrismaValidationError = class extends Error {
4916
+ issues;
4917
+ constructor(issues) {
4918
+ super(`invalid PRISMA diagram: ${issues.map((i) => i.message).join("; ")}`);
4919
+ this.name = "PrismaValidationError";
4920
+ this.issues = issues;
4921
+ }
4922
+ };
4923
+ function sortIssues5(issues) {
4924
+ const unique = /* @__PURE__ */ new Map();
4925
+ for (const issue of issues) unique.set(`${issue.code} ${issue.message}`, issue);
4926
+ return [...unique.values()].sort(
4927
+ (a, b) => a.code !== b.code ? a.code < b.code ? -1 : 1 : a.message < b.message ? -1 : a.message > b.message ? 1 : 0
4928
+ );
4929
+ }
4930
+ function prismaIssues(input) {
4931
+ if (input.boxes.length === 0 && input.arrows.length === 0) return [];
4932
+ const issues = [];
4933
+ const push = (code, message) => {
4934
+ issues.push({ code, message });
4935
+ };
4936
+ const boxById = /* @__PURE__ */ new Map();
4937
+ const dupBoxIds = /* @__PURE__ */ new Set();
4938
+ for (const b of input.boxes) {
4939
+ if (boxById.has(b.id)) dupBoxIds.add(b.id);
4940
+ else boxById.set(b.id, b);
4941
+ }
4942
+ for (const id of [...dupBoxIds].sort((a, b) => a - b)) {
4943
+ push("duplicate-id", `duplicate box id ${id}`);
4944
+ }
4945
+ const arrowById = /* @__PURE__ */ new Map();
4946
+ const dupArrowIds = /* @__PURE__ */ new Set();
4947
+ for (const a of input.arrows) {
4948
+ if (arrowById.has(a.id)) dupArrowIds.add(a.id);
4949
+ else arrowById.set(a.id, a);
4950
+ }
4951
+ for (const id of [...dupArrowIds].sort((a, b) => a - b)) {
4952
+ push("duplicate-id", `duplicate arrow id ${id}`);
4953
+ }
4954
+ if (issues.length > 0) return sortIssues5(issues);
4955
+ for (const arrow of [...arrowById.values()].sort((a, b) => a.id - b.id)) {
4956
+ if (!boxById.has(arrow.fromId)) {
4957
+ push("unknown-arrow-endpoint", `arrow ${arrow.id} fromId ${arrow.fromId} is not a declared box`);
4958
+ }
4959
+ if (!boxById.has(arrow.toId)) {
4960
+ push("unknown-arrow-endpoint", `arrow ${arrow.id} toId ${arrow.toId} is not a declared box`);
4961
+ }
4962
+ if (arrow.fromId === arrow.toId) {
4963
+ push("self-arrow", `arrow ${arrow.id} has fromId === toId (${arrow.fromId})`);
4964
+ }
4965
+ }
4966
+ for (const box of [...boxById.values()].sort((a, b) => a.id - b.id)) {
4967
+ for (const count of box.counts) {
4968
+ if (count.n !== null && !Number.isFinite(count.n)) {
4969
+ push("non-finite-count", `box ${box.id} count "${count.label}" has non-finite n: ${String(count.n)}`);
4970
+ }
4971
+ }
4972
+ }
4973
+ if (input.variant !== "flow-with-prior") {
4974
+ for (const box of [...boxById.values()].sort((a, b) => a.id - b.id)) {
4975
+ if (box.column === "previous") {
4976
+ push(
4977
+ "invalid-variant-column",
4978
+ `box ${box.id} has column "previous" but variant is "${input.variant}"`
4979
+ );
4980
+ }
4981
+ }
4982
+ }
4983
+ const GRAPH_BLOCKING3 = /* @__PURE__ */ new Set([
4984
+ "duplicate-id",
4985
+ "unknown-arrow-endpoint"
4986
+ ]);
4987
+ if (!issues.some((i) => GRAPH_BLOCKING3.has(i.code))) {
4988
+ const phaseIndex = /* @__PURE__ */ new Map();
4989
+ for (let i = 0; i < PRISMA_PHASES.length; i++) phaseIndex.set(PRISMA_PHASES[i], i);
4990
+ for (const arrow of [...arrowById.values()].sort((a, b) => a.id - b.id)) {
4991
+ const fromBox = boxById.get(arrow.fromId);
4992
+ const toBox = boxById.get(arrow.toId);
4993
+ const fromPhaseIdx = phaseIndex.get(fromBox.phase);
4994
+ const toPhaseIdx = phaseIndex.get(toBox.phase);
4995
+ if (fromBox.kind === "flow" && toBox.kind === "exclusion" && fromBox.phase === toBox.phase) {
4996
+ continue;
4997
+ }
4998
+ if (toPhaseIdx < fromPhaseIdx) {
4999
+ push(
5000
+ "arrow-skips-backward",
5001
+ `arrow ${arrow.id} goes backward: from phase "${fromBox.phase}" to "${toBox.phase}"`
5002
+ );
5003
+ }
5004
+ }
5005
+ }
5006
+ return sortIssues5(issues);
5007
+ }
5008
+ function validatePrisma(input) {
5009
+ const issues = prismaIssues(input);
5010
+ if (issues.length > 0) throw new PrismaValidationError(issues);
5011
+ }
5012
+
5013
+ // src/prisma/labels.ts
5014
+ var PRISMA_TITLE_LABELS_EN = {
5015
+ phases: {
5016
+ identification: "Identification",
5017
+ screening: "Screening",
5018
+ included: "Included"
5019
+ },
5020
+ nullCount: "n = \u2014",
5021
+ arrowKinds: {
5022
+ flow: "Flow",
5023
+ exclusion: "Exclusion",
5024
+ merge: "Merge"
5025
+ }
5026
+ };
5027
+ var PRISMA_SVG_LABELS_EN = {
5028
+ legend: {
5029
+ flow: "Main flow",
5030
+ exclusion: "Excluded"
5031
+ },
5032
+ ariaLabel: "PRISMA 2020 flow diagram"
5033
+ };
5034
+
5035
+ // src/prisma/layout.ts
5036
+ var PRISMA_PADDING = 32;
5037
+ var PRISMA_BAND_LABEL_W = 40;
5038
+ var PRISMA_COL_GAP = 56;
5039
+ var PRISMA_CORRIDOR = 30;
5040
+ var PRISMA_BOX_PAD_X = 12;
5041
+ var PRISMA_BOX_PAD_Y = 10;
5042
+ var PRISMA_LINE_H = 14;
5043
+ var PRISMA_MIN_BOX_W = 200;
5044
+ var PRISMA_EXCL_GAP = 48;
5045
+ var PRISMA_HEADING_FONT = 12;
5046
+ var PRISMA_COUNT_FONT = 10;
5047
+ var PRISMA_ROUTE_MARGIN = 10;
5048
+ var PRISMA_ROUTE_PITCH = 10;
5049
+ var PRISMA_FLOW_ARROW_BASE = 1e6;
5050
+ var PRISMA_EXCL_ARROW_BASE = 2e6;
5051
+ var PRISMA_MERGE_ARROW_BASE = 3e6;
5052
+ function round14(n) {
5053
+ return Math.round(n * 100) / 100;
5054
+ }
5055
+ function wrapHeading(h) {
5056
+ if (h === null || h === "") return [];
5057
+ return wrapLabelBalanced(h);
5058
+ }
5059
+ function countLine(label, n, nullToken) {
5060
+ const nStr = n === null ? nullToken : String(n);
5061
+ return `${label}: n = ${nStr}`;
5062
+ }
5063
+ function buildCountLines(counts, nullToken) {
5064
+ return counts.map((c) => countLine(c.label, c.n, nullToken));
5065
+ }
5066
+ function boxTitle(box, nullToken) {
5067
+ if (box.title !== void 0) return box.title;
5068
+ const parts = [];
5069
+ if (box.heading !== null && box.heading !== "") parts.push(box.heading);
5070
+ for (const c of box.counts) parts.push(countLine(c.label, c.n, nullToken));
5071
+ return parts.join("; ") || "(box)";
5072
+ }
5073
+ function computePrismaLayout(input, opts = {}) {
5074
+ if (opts.validate !== false) validatePrisma(input);
5075
+ const labels = opts.labels ?? PRISMA_TITLE_LABELS_EN;
5076
+ const nullToken = labels.nullCount;
5077
+ const COMP_OPTS = {
5078
+ padX: PRISMA_BOX_PAD_X,
5079
+ padY: PRISMA_BOX_PAD_Y,
5080
+ lineH: PRISMA_LINE_H,
5081
+ minW: PRISMA_MIN_BOX_W};
5082
+ const boxMetrics = /* @__PURE__ */ new Map();
5083
+ for (const b of input.boxes) {
5084
+ const headingLines = wrapHeading(b.heading);
5085
+ const cLines = buildCountLines(b.counts, nullToken);
5086
+ const metrics = measureCompartmentBox(
5087
+ [
5088
+ { lines: headingLines, font: PRISMA_HEADING_FONT },
5089
+ { lines: cLines, font: PRISMA_COUNT_FONT }
5090
+ ],
5091
+ COMP_OPTS
5092
+ );
5093
+ boxMetrics.set(b.id, {
5094
+ boxW: metrics.boxW,
5095
+ boxH: metrics.boxH,
5096
+ rows: metrics.rows,
5097
+ dividerYs: metrics.dividerYs,
5098
+ headingLines,
5099
+ countLines: cLines
5100
+ });
5101
+ }
5102
+ const boxesByKey = /* @__PURE__ */ new Map();
5103
+ for (const b of input.boxes) {
5104
+ const key = `${b.phase}:${b.column}:${b.kind}`;
5105
+ const arr = boxesByKey.get(key) ?? [];
5106
+ arr.push(b);
5107
+ boxesByKey.set(key, arr);
5108
+ }
5109
+ for (const arr of boxesByKey.values()) {
5110
+ arr.sort((a, b) => a.rank !== b.rank ? a.rank - b.rank : a.id - b.id);
5111
+ }
5112
+ const groupOf = (phase, col, kind) => boxesByKey.get(`${phase}:${col}:${kind}`) ?? [];
5113
+ const flowCols = input.variant === "flow-with-prior" ? ["new", "previous"] : input.variant === "flow-other-methods" ? ["main", "new"] : ["main"];
5114
+ const contentLeft = PRISMA_PADDING + PRISMA_BAND_LABEL_W;
5115
+ function maxColW(col, kind) {
5116
+ let w = 0;
5117
+ for (const phase of PRISMA_PHASES) {
5118
+ for (const b of groupOf(phase, col, kind)) {
5119
+ const bw = boxMetrics.get(b.id).boxW;
5120
+ if (bw > w) w = bw;
5121
+ }
5122
+ }
5123
+ return w;
5124
+ }
5125
+ const colHasFlow = (col) => PRISMA_PHASES.some((phase) => groupOf(phase, col, "flow").length > 0);
5126
+ const colHasExcl = (col) => PRISMA_PHASES.some((phase) => groupOf(phase, col, "exclusion").length > 0);
5127
+ const exclOnlyCols = ["main", "new", "previous"].filter(
5128
+ (col) => !flowCols.includes(col) && colHasExcl(col)
5129
+ );
5130
+ const placedCols = [...flowCols, ...exclOnlyCols];
5131
+ const colGeom = /* @__PURE__ */ new Map();
5132
+ for (const col of placedCols) {
5133
+ const flowW = colHasFlow(col) ? Math.max(PRISMA_MIN_BOX_W, maxColW(col, "flow")) : 0;
5134
+ const exclW = maxColW(col, "exclusion");
5135
+ const blockW = flowW > 0 && exclW > 0 ? flowW + PRISMA_EXCL_GAP + exclW : flowW > 0 ? flowW : exclW;
5136
+ colGeom.set(col, { flowW, exclW, blockW, flowCx: 0, exclCx: 0 });
5137
+ }
5138
+ function placeBlock(col, blockLeft) {
5139
+ const g = colGeom.get(col);
5140
+ if (g.flowW > 0) g.flowCx = round14(blockLeft + g.flowW / 2);
5141
+ if (g.exclW > 0) {
5142
+ const exclLeft = g.flowW > 0 ? blockLeft + g.flowW + PRISMA_EXCL_GAP : blockLeft;
5143
+ g.exclCx = round14(exclLeft + g.exclW / 2);
5144
+ }
5145
+ }
5146
+ if (placedCols.length === 1) {
5147
+ placeBlock(placedCols[0], contentLeft);
5148
+ } else {
5149
+ const packed = packSubtree(
5150
+ {
5151
+ ownHalfL: 0,
5152
+ ownHalfR: 0,
5153
+ children: placedCols.map((col) => {
5154
+ const half = colGeom.get(col).blockW / 2;
5155
+ return { halfL: half, halfR: half };
5156
+ })
5157
+ },
5158
+ { siblingGap: PRISMA_COL_GAP }
5159
+ );
5160
+ const totalW = packed.halfL + packed.halfR;
5161
+ const blockCx = round14(contentLeft + totalW / 2);
5162
+ placedCols.forEach((col, i) => {
5163
+ const w = colGeom.get(col).blockW;
5164
+ placeBlock(col, round14(blockCx + packed.offsets[i] - w / 2));
5165
+ });
5166
+ }
5167
+ let totalRight = contentLeft;
5168
+ for (const col of placedCols) {
5169
+ const g = colGeom.get(col);
5170
+ const r2 = g.exclW > 0 ? round14(g.exclCx + g.exclW / 2) : round14(g.flowCx + g.flowW / 2);
5171
+ if (r2 > totalRight) totalRight = r2;
5172
+ }
5173
+ const rowsByCol = /* @__PURE__ */ new Map();
5174
+ const rowsKey = (phase, col) => `${phase}:${col}`;
5175
+ function buildRankRows(phase, col) {
5176
+ const byRank = /* @__PURE__ */ new Map();
5177
+ for (const kind of ["flow", "exclusion"]) {
5178
+ for (const b of groupOf(phase, col, kind)) {
5179
+ const h = boxMetrics.get(b.id).boxH;
5180
+ const prev = byRank.get(b.rank) ?? 0;
5181
+ if (h > prev) byRank.set(b.rank, h);
5182
+ }
5183
+ }
5184
+ const ranks = [...byRank.keys()].sort((a, b) => a - b);
5185
+ return ranks.map((rank) => ({ rank, rowH: byRank.get(rank), top: 0 }));
5186
+ }
5187
+ for (const phase of PRISMA_PHASES) {
5188
+ for (const col of placedCols) {
5189
+ rowsByCol.set(rowsKey(phase, col), buildRankRows(phase, col));
5190
+ }
5191
+ }
5192
+ function colStackH(rows) {
5193
+ if (rows.length === 0) return 0;
5194
+ let h = 0;
5195
+ for (const r2 of rows) h += r2.rowH + PRISMA_BOX_PAD_Y;
5196
+ return h - PRISMA_BOX_PAD_Y;
5197
+ }
5198
+ function bandRowH(phase) {
5199
+ let maxH = 0;
5200
+ let any = false;
5201
+ for (const col of placedCols) {
5202
+ const rows = rowsByCol.get(rowsKey(phase, col));
5203
+ if (rows.length > 0) any = true;
5204
+ const h = colStackH(rows);
5205
+ if (h > maxH) maxH = h;
5206
+ }
5207
+ if (!any) return PRISMA_LINE_H * 2 + PRISMA_BOX_PAD_Y * 4;
5208
+ return maxH;
5209
+ }
5210
+ const bandRowHs = PRISMA_PHASES.map((phase) => ({
5211
+ rowH: bandRowH(phase),
5212
+ zoneH: 0
5213
+ }));
5214
+ const stacked = bandStack(bandRowHs, { padding: PRISMA_PADDING, corridor: PRISMA_CORRIDOR });
5215
+ const layoutBoxes = [];
5216
+ const lboxById = /* @__PURE__ */ new Map();
5217
+ function placeBox(b, phase, kind, cx, top) {
5218
+ const m = boxMetrics.get(b.id);
5219
+ const t = round14(top);
5220
+ const title = boxTitle(b, nullToken);
5221
+ const lbox = {
5222
+ id: b.id,
5223
+ phase,
5224
+ kind,
5225
+ cx: round14(cx),
5226
+ top: t,
5227
+ boxW: m.boxW,
5228
+ boxH: m.boxH,
5229
+ rows: m.rows.map((r2) => ({ ...r2, top: round14(t + r2.top) })),
5230
+ dividerYs: m.dividerYs.map((d) => round14(t + d)),
5231
+ heading: b.heading,
5232
+ countLines: m.countLines,
5233
+ title
5234
+ };
5235
+ layoutBoxes.push(lbox);
5236
+ lboxById.set(b.id, lbox);
5237
+ }
5238
+ for (let phaseIdx = 0; phaseIdx < PRISMA_PHASES.length; phaseIdx++) {
5239
+ const phase = PRISMA_PHASES[phaseIdx];
5240
+ const bandTop = stacked.rowTop[phaseIdx];
5241
+ for (const col of placedCols) {
5242
+ const g = colGeom.get(col);
5243
+ const rows = rowsByCol.get(rowsKey(phase, col));
5244
+ const topByRank = /* @__PURE__ */ new Map();
5245
+ let y = bandTop;
5246
+ for (const r2 of rows) {
5247
+ r2.top = round14(y);
5248
+ topByRank.set(r2.rank, r2.top);
5249
+ y = round14(y + r2.rowH + PRISMA_BOX_PAD_Y);
5250
+ }
5251
+ if (g.flowW > 0) {
5252
+ for (const b of groupOf(phase, col, "flow")) {
5253
+ placeBox(b, phase, "flow", g.flowCx, topByRank.get(b.rank));
5254
+ }
5255
+ }
5256
+ if (g.exclW > 0) {
5257
+ for (const b of groupOf(phase, col, "exclusion")) {
5258
+ placeBox(b, phase, "exclusion", g.exclCx, topByRank.get(b.rank));
5259
+ }
5260
+ }
5261
+ }
5262
+ }
5263
+ const elements = [];
5264
+ const boxById = new Map(input.boxes.map((b) => [b.id, b]));
5265
+ const phaseIndexMap = new Map(
5266
+ PRISMA_PHASES.map((p, i) => [p, i])
5267
+ );
5268
+ function boxBottom(lb) {
5269
+ return round14(lb.top + lb.boxH);
5270
+ }
5271
+ function bandBottom(phase) {
5272
+ const idx = PRISMA_PHASES.indexOf(phase);
5273
+ return round14(stacked.rowTop[idx] + bandRowHs[idx].rowH);
5274
+ }
5275
+ const arrowsToBox = /* @__PURE__ */ new Map();
5276
+ for (const arrow of input.arrows) {
5277
+ const toBox = boxById.get(arrow.toId);
5278
+ const fromBox = boxById.get(arrow.fromId);
5279
+ if (toBox === void 0 || fromBox === void 0) continue;
5280
+ if (fromBox.kind === "flow" && toBox.kind === "flow") {
5281
+ const arr = arrowsToBox.get(arrow.toId) ?? [];
5282
+ arr.push(arrow.id);
5283
+ arrowsToBox.set(arrow.toId, arr);
5284
+ }
5285
+ }
5286
+ const arrowToBoxArrivalIdx = /* @__PURE__ */ new Map();
5287
+ const exclArrowIndexBySource = /* @__PURE__ */ new Map();
5288
+ for (const arrow of input.arrows) {
5289
+ const fromLBox = lboxById.get(arrow.fromId);
5290
+ const toLBox = lboxById.get(arrow.toId);
5291
+ if (fromLBox === void 0 || toLBox === void 0) continue;
5292
+ const fromBox = boxById.get(arrow.fromId);
5293
+ const toBox = boxById.get(arrow.toId);
5294
+ const isExclusion = fromBox.kind === "flow" && toBox.kind === "exclusion";
5295
+ const isFlowToFlow = fromBox.kind === "flow" && toBox.kind === "flow";
5296
+ if (isExclusion) {
5297
+ const idx = exclArrowIndexBySource.get(arrow.fromId) ?? 0;
5298
+ exclArrowIndexBySource.set(arrow.fromId, idx + 1);
5299
+ const srcRight = round14(fromLBox.cx + fromLBox.boxW / 2);
5300
+ const exclMidY = round14(toLBox.top + toLBox.boxH / 2);
5301
+ const eLeft = round14(toLBox.cx - toLBox.boxW / 2);
5302
+ const sameColumn = fromBox.column === toBox.column;
5303
+ let points;
5304
+ if (sameColumn) {
5305
+ points = [
5306
+ { x: round14(srcRight), y: round14(exclMidY) },
5307
+ { x: round14(eLeft), y: round14(exclMidY) }
5308
+ ];
5309
+ } else {
5310
+ const colGapStep = round14(PRISMA_COL_GAP / 8);
5311
+ const exclGapStep = round14(PRISMA_EXCL_GAP / 8);
5312
+ const fenceX = round14(srcRight + (idx + 1) * colGapStep);
5313
+ const exclFenceX = round14(eLeft - (idx + 1) * exclGapStep);
5314
+ const bBotY = bandBottom(fromBox.phase);
5315
+ const corridorY = round14(bBotY + PRISMA_ROUTE_MARGIN + idx * PRISMA_ROUTE_PITCH);
5316
+ points = [
5317
+ { x: round14(srcRight), y: round14(exclMidY) },
5318
+ // exit source right at excl center y
5319
+ { x: round14(fenceX), y: round14(exclMidY) },
5320
+ // horizontal into inter-col gap (unique y)
5321
+ { x: round14(fenceX), y: round14(corridorY) },
5322
+ // vertical at unique x in inter-col gap
5323
+ { x: round14(exclFenceX), y: round14(corridorY) },
5324
+ // horizontal in corridor (unique y)
5325
+ { x: round14(exclFenceX), y: round14(exclMidY) },
5326
+ // vertical at unique x left of excl box
5327
+ { x: round14(eLeft), y: round14(exclMidY) }
5328
+ // horizontal into excl box (unique y)
5329
+ ];
5330
+ }
5331
+ elements.push({
5332
+ edgeId: PRISMA_EXCL_ARROW_BASE + arrow.id,
5333
+ kind: "exclusion",
5334
+ points,
5335
+ title: `${labels.arrowKinds.exclusion}: ${fromLBox.title} \u2192 ${toLBox.title}`,
5336
+ headDir: "right"
5337
+ });
5338
+ } else if (isFlowToFlow) {
5339
+ const fromPhaseIdx = phaseIndexMap.get(fromBox.phase);
5340
+ const toPhaseIdx = phaseIndexMap.get(toBox.phase);
5341
+ const isSamePhase = fromBox.phase === toBox.phase;
5342
+ const isSameCol = fromBox.column === toBox.column;
5343
+ const isForward = toPhaseIdx >= fromPhaseIdx;
5344
+ const arrivals = arrowsToBox.get(arrow.toId) ?? [];
5345
+ let arrivalIdx = arrowToBoxArrivalIdx.get(arrow.id) ?? 0;
5346
+ if (!arrowToBoxArrivalIdx.has(arrow.id)) {
5347
+ arrivalIdx = arrivals.indexOf(arrow.id);
5348
+ arrowToBoxArrivalIdx.set(arrow.id, arrivalIdx);
5349
+ }
5350
+ const nArrivals = arrivals.length;
5351
+ const APPROACH_SPREAD = round14(toLBox.boxW / 4);
5352
+ const spreadStep = nArrivals > 1 ? round14(APPROACH_SPREAD / (nArrivals - 1)) : 0;
5353
+ const approachX = round14(toLBox.cx - APPROACH_SPREAD / 2 + arrivalIdx * spreadStep);
5354
+ const toTop = toLBox.top;
5355
+ if (isSamePhase && isSameCol) {
5356
+ const fromBottom = boxBottom(fromLBox);
5357
+ const fromCx = round14(fromLBox.cx);
5358
+ const toCx = round14(toLBox.cx);
5359
+ const dropX = fromCx === toCx ? fromCx : toCx;
5360
+ elements.push({
5361
+ edgeId: PRISMA_FLOW_ARROW_BASE + arrow.id,
5362
+ kind: "flow",
5363
+ points: [
5364
+ { x: fromCx, y: round14(fromBottom) },
5365
+ { x: dropX, y: round14(toTop) }
5366
+ ],
5367
+ title: `${labels.arrowKinds.flow}: ${fromLBox.title} \u2192 ${toLBox.title}`,
5368
+ headDir: "down"
5369
+ });
5370
+ } else if (isSameCol && isForward) {
5371
+ const fromBottom = boxBottom(fromLBox);
5372
+ if (nArrivals <= 1 && round14(fromLBox.cx) === round14(toLBox.cx)) {
5373
+ elements.push({
5374
+ edgeId: PRISMA_FLOW_ARROW_BASE + arrow.id,
5375
+ kind: "flow",
5376
+ points: [
5377
+ { x: round14(fromLBox.cx), y: round14(fromBottom) },
5378
+ { x: round14(toLBox.cx), y: round14(toTop) }
5379
+ ],
5380
+ title: `${labels.arrowKinds.flow}: ${fromLBox.title} \u2192 ${toLBox.title}`,
5381
+ headDir: "down"
5382
+ });
5383
+ } else {
5384
+ const phIdx = phaseIndexMap.get(fromBox.phase);
5385
+ const elbowY = round14(stacked.rowTop[phIdx] + bandRowHs[phIdx].rowH + PRISMA_CORRIDOR / 2);
5386
+ elements.push({
5387
+ edgeId: PRISMA_FLOW_ARROW_BASE + arrow.id,
5388
+ kind: "flow",
5389
+ points: [
5390
+ { x: round14(fromLBox.cx), y: round14(fromBottom) },
5391
+ { x: round14(fromLBox.cx), y: round14(elbowY) },
5392
+ { x: round14(approachX), y: round14(elbowY) },
5393
+ { x: round14(approachX), y: round14(toTop) }
5394
+ ],
5395
+ title: `${labels.arrowKinds.flow}: ${fromLBox.title} \u2192 ${toLBox.title}`,
5396
+ headDir: "down"
5397
+ });
5398
+ }
5399
+ } else {
5400
+ const fromBottom = boxBottom(fromLBox);
5401
+ const phIdx = phaseIndexMap.get(fromBox.phase);
5402
+ const elbowY = round14(stacked.rowTop[phIdx] + bandRowHs[phIdx].rowH + PRISMA_CORRIDOR / 2);
5403
+ const fromCx = round14(fromLBox.cx);
5404
+ if (round14(fromCx) === round14(approachX)) {
5405
+ elements.push({
5406
+ edgeId: PRISMA_MERGE_ARROW_BASE + arrow.id,
5407
+ kind: "merge",
5408
+ points: [
5409
+ { x: fromCx, y: round14(fromBottom) },
5410
+ { x: round14(approachX), y: round14(toTop) }
5411
+ ],
5412
+ title: `${labels.arrowKinds.merge}: ${fromLBox.title} \u2192 ${toLBox.title}`,
5413
+ headDir: "down"
5414
+ });
5415
+ } else {
5416
+ elements.push({
5417
+ edgeId: PRISMA_MERGE_ARROW_BASE + arrow.id,
5418
+ kind: "merge",
5419
+ points: [
5420
+ { x: fromCx, y: round14(fromBottom) },
5421
+ { x: fromCx, y: round14(elbowY) },
5422
+ { x: round14(approachX), y: round14(elbowY) },
5423
+ { x: round14(approachX), y: round14(toTop) }
5424
+ ],
5425
+ title: `${labels.arrowKinds.merge}: ${fromLBox.title} \u2192 ${toLBox.title}`,
5426
+ headDir: "down"
5427
+ });
5428
+ }
5429
+ }
5430
+ }
5431
+ }
5432
+ const canvasH = stacked.height;
5433
+ const canvasW = round14(totalRight + PRISMA_PADDING);
5434
+ const bands = PRISMA_PHASES.map((phase, i) => {
5435
+ const rowTop = stacked.rowTop[i];
5436
+ const rowH = bandRowHs[i].rowH;
5437
+ return {
5438
+ phase,
5439
+ rowTop,
5440
+ rowH,
5441
+ labelX: PRISMA_PADDING,
5442
+ centerY: round14(rowTop + rowH / 2)
5443
+ };
5444
+ });
5445
+ return {
5446
+ width: canvasW,
5447
+ height: canvasH,
5448
+ boxes: layoutBoxes,
5449
+ elements,
5450
+ bands,
5451
+ variant: input.variant
5452
+ };
5453
+ }
5454
+
5455
+ // src/prisma/svg.ts
5456
+ var BOX_STROKE2 = "#52525b";
5457
+ var BOX_STROKE_W = 2;
5458
+ var BOX_FILL = "#fff";
5459
+ var BOX_RX = 2;
5460
+ var HEADING_FILL = "#3f3f46";
5461
+ var COUNT_FILL = "#52525b";
5462
+ var ARROW_STROKE = "#71717a";
5463
+ var ARROW_STROKE_W = 1.5;
5464
+ var ARROW_FILL = "#71717a";
5465
+ var BAND_LABEL_FILL = "#52525b";
5466
+ var BAND_LABEL_FONT = 11;
5467
+ function round15(n) {
5468
+ return Math.round(n * 100) / 100;
5469
+ }
5470
+ function emitBox(b) {
5471
+ const x = round15(b.cx - b.boxW / 2);
5472
+ const y = b.top;
5473
+ const parts = [];
5474
+ parts.push(
5475
+ `<rect x="${x}" y="${y}" width="${b.boxW}" height="${b.boxH}" rx="${BOX_RX}" fill="${BOX_FILL}" stroke="${BOX_STROKE2}" stroke-width="${BOX_STROKE_W}"/>`
5476
+ );
5477
+ for (const dy of b.dividerYs) {
5478
+ parts.push(
5479
+ `<line x1="${x}" y1="${dy}" x2="${round15(x + b.boxW)}" y2="${dy}" stroke="${BOX_STROKE2}" stroke-width="1"/>`
5480
+ );
5481
+ }
5482
+ for (let ri = 0; ri < b.rows.length; ri++) {
5483
+ const row = b.rows[ri];
5484
+ const isHeading = ri === 0;
5485
+ const font = row.font;
5486
+ const fill = isHeading ? HEADING_FILL : COUNT_FILL;
5487
+ const textX = isHeading ? round15(b.cx) : round15(x + 8);
5488
+ for (let li = 0; li < row.lines.length; li++) {
5489
+ const line = row.lines[li];
5490
+ const textY = round15(row.top + li * 14 + font);
5491
+ const anchorAttr = isHeading ? ` text-anchor="middle"` : "";
5492
+ const weightAttr = isHeading ? ` font-weight="bold"` : "";
5493
+ parts.push(
5494
+ `<text x="${textX}" y="${textY}" font-family="${FONT_FAMILY}" font-size="${font}" fill="${fill}"${anchorAttr}${weightAttr}>${xmlEscape(line)}</text>`
5495
+ );
5496
+ }
5497
+ }
5498
+ return parts.join("");
5499
+ }
5500
+ function emitArrow(el) {
5501
+ const points = el.points;
5502
+ if (points.length < 2) return "";
5503
+ const inset = glyphInset(el.headDir, "arrow");
5504
+ const last = points[points.length - 1];
5505
+ let lineEndX = last.x;
5506
+ let lineEndY = last.y;
5507
+ if (el.headDir === "down") lineEndY = round15(last.y - inset);
5508
+ else if (el.headDir === "right") lineEndX = round15(last.x - inset);
5509
+ else if (el.headDir === "left") lineEndX = round15(last.x + inset);
5510
+ else lineEndY = round15(last.y + inset);
5511
+ const linePoints = [
5512
+ ...points.slice(0, points.length - 1).map((p) => ({ x: p.x, y: p.y })),
5513
+ { x: lineEndX, y: lineEndY }
5514
+ ];
5515
+ const pathStr = pathData(linePoints);
5516
+ const arrowPts = arrowFilledPoints({ x: last.x, y: last.y }, el.headDir);
5517
+ return `<title>${xmlEscape(el.title)}</title><path d="${pathStr}" stroke="${ARROW_STROKE}" stroke-width="${ARROW_STROKE_W}" fill="none"/><polygon points="${arrowPts}" fill="${ARROW_FILL}" stroke="none"/>`;
5518
+ }
5519
+ function emitBandLabel(phase, labelX, centerY, bandTitleLabels) {
5520
+ const text = bandTitleLabels.phases[phase];
5521
+ const gutterCx = round15(labelX + PRISMA_BAND_LABEL_W / 2);
5522
+ return `<text x="${gutterCx}" y="${round15(centerY)}" font-family="${FONT_FAMILY}" font-size="${BAND_LABEL_FONT}" fill="${BAND_LABEL_FILL}" text-anchor="middle" transform="rotate(-90,${gutterCx},${round15(centerY)})">${xmlEscape(text)}</text>`;
5523
+ }
5524
+ function buildLegendEntries(layout, svgLabels) {
5525
+ const hasFlow = layout.boxes.some((b) => b.kind === "flow");
5526
+ const hasExcl = layout.boxes.some((b) => b.kind === "exclusion");
5527
+ const entries = [];
5528
+ if (hasFlow) {
5529
+ entries.push({
5530
+ label: svgLabels.legend.flow,
5531
+ swatch: (x, cy) => `<rect x="${x}" y="${round15(cy - 6)}" width="22" height="12" rx="2" fill="${BOX_FILL}" stroke="${BOX_STROKE2}" stroke-width="2"/>`
5532
+ });
5533
+ }
5534
+ if (hasExcl) {
5535
+ entries.push({
5536
+ label: svgLabels.legend.exclusion,
5537
+ swatch: (x, cy) => `<rect x="${x}" y="${round15(cy - 6)}" width="22" height="12" rx="2" fill="${BOX_FILL}" stroke="${BOX_STROKE2}" stroke-width="2" stroke-dasharray="4,2"/>`
5538
+ });
5539
+ }
5540
+ return entries;
5541
+ }
5542
+ function prismaLayoutSvg(layout, opts = {}) {
5543
+ const svgLabels = opts.svgLabels ?? PRISMA_SVG_LABELS_EN;
5544
+ const bandTitleLabels = opts.titleLabels ?? PRISMA_TITLE_LABELS_EN;
5545
+ const doLegend = opts.legend !== false;
5546
+ const parts = [];
5547
+ for (const band of layout.bands) {
5548
+ parts.push(emitBandLabel(band.phase, band.labelX, band.centerY, bandTitleLabels));
5549
+ }
5550
+ for (const b of layout.boxes) {
5551
+ const boxSvg3 = emitBox(b);
5552
+ parts.push(`<g data-node-id="b${b.id}"><title>${xmlEscape(b.title)}</title>${boxSvg3}</g>`);
5553
+ }
5554
+ for (const el of layout.elements) {
5555
+ const arrowSvg = emitArrow(el);
5556
+ parts.push(`<g data-edge-id="${el.edgeId}">${arrowSvg}</g>`);
5557
+ }
5558
+ let w = layout.width;
5559
+ let h = layout.height;
5560
+ let legendSvg = "";
5561
+ if (doLegend) {
5562
+ const entries = buildLegendEntries(layout, svgLabels);
5563
+ const lb = legendBlock(entries, h);
5564
+ legendSvg = lb.svg;
5565
+ if (entries.length > 0) {
5566
+ w = Math.max(w, lb.width + 32 * 2);
5567
+ h = lb.height;
5568
+ }
5569
+ }
5570
+ w = round15(w);
5571
+ h = round15(h);
5572
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}" role="img" aria-label="${xmlEscape(svgLabels.ariaLabel)}">` + parts.join("") + legendSvg + `</svg>`;
5573
+ }
5574
+
5575
+ // src/prisma/render.ts
5576
+ function prismaSvg(input, opts = {}) {
5577
+ const layout = computePrismaLayout(input, opts);
5578
+ const svg = prismaLayoutSvg(layout, opts);
5579
+ return { svg, layout };
5580
+ }
5581
+
5582
+ // src/uml/types.ts
5583
+ var UML_VISIBILITIES = ["public", "private", "protected", "package"];
5584
+ var UML_VIS_GLYPH = {
5585
+ public: "+",
5586
+ private: "-",
5587
+ protected: "#",
5588
+ package: "~"
5589
+ };
5590
+ var UML_RELATIONSHIPS = [
5591
+ "association",
5592
+ "directed-association",
5593
+ "aggregation",
5594
+ "composition",
5595
+ "generalization",
5596
+ "realization",
5597
+ "dependency"
5598
+ ];
5599
+ var RELATION_GLYPHS = {
5600
+ association: { sourceEnd: "none", targetEnd: "none", line: "solid" },
5601
+ "directed-association": { sourceEnd: "none", targetEnd: "open-arrow", line: "solid" },
5602
+ aggregation: { sourceEnd: "hollow-diamond", targetEnd: "none", line: "solid" },
5603
+ composition: { sourceEnd: "solid-diamond", targetEnd: "none", line: "solid" },
5604
+ generalization: { sourceEnd: "none", targetEnd: "hollow-triangle", line: "solid" },
5605
+ realization: { sourceEnd: "none", targetEnd: "hollow-triangle", line: "dashed" },
5606
+ dependency: { sourceEnd: "none", targetEnd: "open-arrow", line: "dashed" }
5607
+ };
5608
+
5609
+ // src/uml/validate.ts
5610
+ var UmlValidationError = class extends Error {
5611
+ issues;
5612
+ constructor(issues) {
5613
+ super(`invalid UML class diagram: ${issues.map((i) => i.message).join("; ")}`);
5614
+ this.name = "UmlValidationError";
5615
+ this.issues = issues;
5616
+ }
5617
+ };
5618
+ function sortIssues6(issues) {
5619
+ const unique = /* @__PURE__ */ new Map();
5620
+ for (const issue of issues) unique.set(`${issue.code} ${issue.message}`, issue);
5621
+ return [...unique.values()].sort(
5622
+ (a, b) => a.code !== b.code ? a.code < b.code ? -1 : 1 : a.message < b.message ? -1 : a.message > b.message ? 1 : 0
5623
+ );
5624
+ }
5625
+ var GRAPH_BLOCKING2 = /* @__PURE__ */ new Set([
5626
+ "duplicate-id",
5627
+ "unknown-endpoint"
5628
+ ]);
5629
+ var INHERITANCE_KINDS = /* @__PURE__ */ new Set([
5630
+ "generalization",
5631
+ "realization"
5632
+ ]);
5633
+ function umlIssues(input) {
5634
+ if (input.classes.length === 0 && input.relationships.length === 0) return [];
5635
+ const issues = [];
5636
+ const push = (code, message) => {
5637
+ issues.push({ code, message });
5638
+ };
5639
+ const classById = /* @__PURE__ */ new Map();
5640
+ const dupClassIds = /* @__PURE__ */ new Set();
5641
+ for (const c of input.classes) {
5642
+ if (classById.has(c.id)) dupClassIds.add(c.id);
5643
+ else classById.set(c.id, c);
5644
+ }
5645
+ for (const id of [...dupClassIds].sort((a, b) => a - b)) {
5646
+ push("duplicate-id", `duplicate class id ${id}`);
5647
+ }
5648
+ const relById = /* @__PURE__ */ new Map();
5649
+ const dupRelIds = /* @__PURE__ */ new Set();
5650
+ for (const r2 of input.relationships) {
5651
+ if (relById.has(r2.id)) dupRelIds.add(r2.id);
5652
+ else relById.set(r2.id, r2);
5653
+ }
5654
+ for (const id of [...dupRelIds].sort((a, b) => a - b)) {
5655
+ push("duplicate-id", `duplicate relationship id ${id}`);
5656
+ }
5657
+ if (issues.length > 0) return sortIssues6(issues);
5658
+ const rels = [...relById.values()].sort((a, b) => a.id - b.id);
5659
+ for (const r2 of rels) {
5660
+ if (!classById.has(r2.sourceId)) {
5661
+ push("unknown-endpoint", `relationship ${r2.id} sourceId ${r2.sourceId} is not a declared class`);
5662
+ }
5663
+ if (!classById.has(r2.targetId)) {
5664
+ push("unknown-endpoint", `relationship ${r2.id} targetId ${r2.targetId} is not a declared class`);
5665
+ }
5666
+ }
5667
+ for (const c of [...classById.values()].sort((a, b) => a.id - b.id)) {
5668
+ if (!Number.isInteger(c.col) || c.col < 0) {
5669
+ push("negative-cell", `class ${c.id} col ${c.col} must be a non-negative integer`);
5670
+ }
5671
+ if (!Number.isInteger(c.row) || c.row < 0) {
5672
+ push("negative-cell", `class ${c.id} row ${c.row} must be a non-negative integer`);
5673
+ }
5674
+ }
5675
+ const cellKey = (c) => `${c.col},${c.row}`;
5676
+ const cellToClasses = /* @__PURE__ */ new Map();
5677
+ for (const c of [...classById.values()].sort((a, b) => a.id - b.id)) {
5678
+ const key = cellKey(c);
5679
+ const arr = cellToClasses.get(key) ?? [];
5680
+ arr.push(c.id);
5681
+ cellToClasses.set(key, arr);
5682
+ }
5683
+ for (const [key, ids] of [...cellToClasses.entries()].sort()) {
5684
+ if (ids.length > 1) {
5685
+ push(
5686
+ "cell-collision",
5687
+ `classes ${ids.sort((a, b) => a - b).join(", ")} all declare cell (${key})`
5688
+ );
5689
+ }
5690
+ }
5691
+ for (const r2 of rels) {
5692
+ if ((r2.kind === "generalization" || r2.kind === "realization") && r2.sourceId === r2.targetId) {
5693
+ push(
5694
+ "self-generalization",
5695
+ `relationship ${r2.id} is a ${r2.kind} from class ${r2.sourceId} to itself`
5696
+ );
5697
+ }
5698
+ }
5699
+ if (!issues.some((i) => GRAPH_BLOCKING2.has(i.code))) {
5700
+ const inheritanceChildren = /* @__PURE__ */ new Map();
5701
+ for (const r2 of rels) {
5702
+ if (!INHERITANCE_KINDS.has(r2.kind)) continue;
5703
+ if (r2.sourceId === r2.targetId) continue;
5704
+ const arr = inheritanceChildren.get(r2.sourceId) ?? [];
5705
+ arr.push(r2.targetId);
5706
+ inheritanceChildren.set(r2.sourceId, arr);
5707
+ }
5708
+ for (const arr of inheritanceChildren.values()) arr.sort((a, b) => a - b);
5709
+ const color = /* @__PURE__ */ new Map();
5710
+ const seenCycles = /* @__PURE__ */ new Set();
5711
+ const dfs = (start) => {
5712
+ const stack = [{ id: start, nextChild: 0 }];
5713
+ color.set(start, 1);
5714
+ while (stack.length > 0) {
5715
+ const frame = stack[stack.length - 1];
5716
+ const children = inheritanceChildren.get(frame.id) ?? [];
5717
+ if (frame.nextChild >= children.length) {
5718
+ color.set(frame.id, 2);
5719
+ stack.pop();
5720
+ continue;
5721
+ }
5722
+ const child = children[frame.nextChild];
5723
+ frame.nextChild += 1;
5724
+ const c = color.get(child) ?? 0;
5725
+ if (c === 1) {
5726
+ const from = stack.findIndex((f) => f.id === child);
5727
+ const cycle = stack.slice(from).map((f) => f.id);
5728
+ const minIdx = cycle.indexOf(Math.min(...cycle));
5729
+ const rotated = [...cycle.slice(minIdx), ...cycle.slice(0, minIdx)];
5730
+ const key = rotated.join(">");
5731
+ if (!seenCycles.has(key)) {
5732
+ seenCycles.add(key);
5733
+ push(
5734
+ "generalization-cycle",
5735
+ `generalization cycle: ${[...rotated, rotated[0]].join(" \u2192 ")}`
5736
+ );
5737
+ }
5738
+ } else if (c === 0) {
5739
+ color.set(child, 1);
5740
+ stack.push({ id: child, nextChild: 0 });
5741
+ }
5742
+ }
5743
+ };
5744
+ for (const c of [...classById.values()].sort((a, b) => a.id - b.id)) {
5745
+ if ((color.get(c.id) ?? 0) === 0) dfs(c.id);
5746
+ }
5747
+ }
5748
+ return sortIssues6(issues);
5749
+ }
5750
+ function validateUml(input) {
5751
+ const issues = umlIssues(input);
5752
+ if (issues.length > 0) throw new UmlValidationError(issues);
5753
+ }
5754
+ function sideCapacity(boxH, rowGap, pitch, glyphHalf) {
5755
+ const clear = boxH / 2 + rowGap / 2 - glyphHalf;
5756
+ const k = Math.max(0, Math.floor(clear / pitch));
5757
+ return 2 * k + 1;
5758
+ }
5759
+ function sideCapacityIssue(classId, side, count, capacity) {
5760
+ return {
5761
+ code: "too-many-side-edges",
5762
+ message: `class ${classId} has ${count} edges on its ${side} side \u2014 at most ${capacity} fit without a port stub spilling through a neighbouring box`
5763
+ };
5764
+ }
5765
+
5766
+ // src/uml/labels.ts
5767
+ var UML_TITLE_LABELS_EN = {
5768
+ kinds: {
5769
+ association: "association",
5770
+ "directed-association": "directed association",
5771
+ aggregation: "aggregation",
5772
+ composition: "composition",
5773
+ generalization: "generalization",
5774
+ realization: "realization",
5775
+ dependency: "dependency"
5776
+ },
5777
+ visibilities: {
5778
+ public: "public",
5779
+ private: "private",
5780
+ protected: "protected",
5781
+ package: "package"
5782
+ }
5783
+ };
5784
+ var UML_SVG_LABELS_EN = {
5785
+ legend: {
5786
+ association: "Association",
5787
+ "directed-association": "Directed association",
5788
+ aggregation: "Aggregation",
5789
+ composition: "Composition",
5790
+ generalization: "Generalization",
5791
+ realization: "Realization",
5792
+ dependency: "Dependency"
5793
+ },
5794
+ ariaLabel: "UML class diagram"
5795
+ };
5796
+
5797
+ // src/uml/layout.ts
5798
+ var UML_PADDING = 32;
5799
+ var UML_COL_GAP = 64;
5800
+ var UML_ROW_GAP = 56;
5801
+ var UML_BOX_PAD_X = 10;
5802
+ var UML_BOX_PAD_Y = 8;
5803
+ var UML_LINE_H = 15;
5804
+ var UML_MIN_BOX_W = 120;
5805
+ var UML_NAME_FONT = 12;
5806
+ var UML_FEAT_FONT = 11;
5807
+ var UML_SEP_PAD = 4;
5808
+ var UML_PORT_PITCH = 12;
5809
+ var UML_SELF_LOOP_W = 28;
5810
+ var UML_LABEL_FONT = 10;
5811
+ var UML_ASSOC_ROUTE_BASE = 1e6;
5812
+ var UML_GLYPH_BASE = 2e6;
5813
+ var UML_SELF_LOOP_BASE = 3e6;
5814
+ var MIN_TERMINAL_STUB = 4;
5815
+ var round16 = (n) => Math.round(n * 100) / 100;
5816
+ function featureLine(f) {
5817
+ const prefix = f.visibility !== null ? UML_VIS_GLYPH[f.visibility] + " " : "";
5818
+ return prefix + f.text;
5819
+ }
5820
+ function measureClass(cls) {
5821
+ const nameLines = [];
5822
+ if (cls.stereotype !== null) nameLines.push(cls.stereotype);
5823
+ nameLines.push(cls.name);
5824
+ const attrLines = cls.attributes.map(featureLine);
5825
+ const opLines = cls.operations.map(featureLine);
5826
+ return measureCompartmentBox(
5827
+ [
5828
+ { lines: nameLines, font: UML_NAME_FONT },
5829
+ { lines: attrLines, font: UML_FEAT_FONT },
5830
+ { lines: opLines, font: UML_FEAT_FONT }
5831
+ ],
5832
+ {
5833
+ padX: UML_BOX_PAD_X,
5834
+ padY: UML_BOX_PAD_Y,
5835
+ lineH: UML_LINE_H,
5836
+ minW: UML_MIN_BOX_W}
5837
+ );
5838
+ }
5839
+ function boxEdges(node) {
5840
+ const left = round16(node.cx - node.boxW / 2);
5841
+ const right = round16(left + node.boxW);
5842
+ const top = round16(node.cy - node.boxH / 2);
5843
+ const bottom = round16(top + node.boxH);
5844
+ return { left, right, top, bottom };
5845
+ }
5846
+ function glyphInsetForKind(gk) {
5847
+ switch (gk) {
5848
+ case "none":
5849
+ return 0;
5850
+ case "open-arrow":
5851
+ return glyphInset("right", "arrow");
5852
+ case "hollow-triangle":
5853
+ return glyphInset("right", "triangle");
5854
+ case "hollow-diamond":
5855
+ case "solid-diamond":
5856
+ return glyphInset("right", "diamond");
5857
+ }
5858
+ }
5859
+ var MAX_GLYPH_HALF = GLYPH_TRI_HALF;
5860
+ var GLYPH_SLOT_PITCH = Math.max(UML_PORT_PITCH, MAX_GLYPH_HALF * 2 + 2);
5861
+ function portOffset(slotIndex, slotSize) {
5862
+ if (slotIndex === 0) return 0;
5863
+ const sign = slotIndex % 2 === 1 ? 1 : -1;
5864
+ const dist = Math.ceil(slotIndex / 2);
5865
+ return sign * dist * slotSize;
5866
+ }
5867
+ function sidePort(node, side, dy) {
5868
+ const { left, right } = boxEdges(node);
5869
+ const x = side === "right" ? right : left;
5870
+ return { x, y: round16(node.cy + dy) };
5871
+ }
5872
+ function vGutterLeft(colIdx, colX, grid) {
5873
+ return round16(colX[colIdx] + grid.colW[colIdx]);
5874
+ }
5875
+ var V_GUTTER_MARGIN = 6;
5876
+ function horizontalGutterY(rowIdx, rowY, grid) {
5877
+ return round16(rowY[rowIdx] + grid.rowH[rowIdx] + UML_ROW_GAP / 2);
5878
+ }
5879
+ function moveAlongDir(p, dir, dist) {
5880
+ switch (dir) {
5881
+ case "right":
5882
+ return { x: round16(p.x + dist), y: p.y };
5883
+ case "left":
5884
+ return { x: round16(p.x - dist), y: p.y };
5885
+ case "down":
5886
+ return { x: p.x, y: round16(p.y + dist) };
5887
+ case "up":
5888
+ return { x: p.x, y: round16(p.y - dist) };
5889
+ }
5890
+ }
5891
+ function simplifyWaypoints(pts) {
5892
+ const dedup = [pts[0]];
5893
+ for (let i = 1; i < pts.length; i++) {
5894
+ const prev = dedup[dedup.length - 1];
5895
+ const cur = pts[i];
5896
+ if (Math.abs(cur.x - prev.x) > 1e-6 || Math.abs(cur.y - prev.y) > 1e-6) dedup.push(cur);
5897
+ }
5898
+ if (dedup.length <= 2) return dedup;
5899
+ const out = [dedup[0]];
5900
+ for (let i = 1; i < dedup.length - 1; i++) {
5901
+ const a = out[out.length - 1];
5902
+ const b = dedup[i];
5903
+ const c = dedup[i + 1];
5904
+ const abH = Math.abs(a.y - b.y) <= 1e-6;
5905
+ const bcH = Math.abs(b.y - c.y) <= 1e-6;
5906
+ const abV = Math.abs(a.x - b.x) <= 1e-6;
5907
+ const bcV = Math.abs(b.x - c.x) <= 1e-6;
5908
+ if (abH && bcH || abV && bcV) continue;
5909
+ out.push(b);
5910
+ }
5911
+ out.push(dedup[dedup.length - 1]);
5912
+ return out;
5913
+ }
5914
+ function chooseSides(srcNode, tgtNode, maxCol) {
5915
+ const sc = srcNode.col;
5916
+ const tc = tgtNode.col;
5917
+ if (tc > sc) return { srcSide: "right", tgtSide: "left", srcVCol: sc, tgtVCol: tc - 1 };
5918
+ if (tc < sc) return { srcSide: "left", tgtSide: "right", srcVCol: sc - 1, tgtVCol: tc };
5919
+ if (sc < maxCol) return { srcSide: "right", tgtSide: "right", srcVCol: sc, tgtVCol: sc };
5920
+ if (sc > 0) return { srcSide: "left", tgtSide: "left", srcVCol: sc - 1, tgtVCol: sc - 1 };
5921
+ return { srcSide: "right", tgtSide: "right", srcVCol: sc, tgtVCol: sc };
5922
+ }
5923
+ function hBaseY(plan, rowY, grid) {
5924
+ if (plan.hKind === "pad-top") return round16(UML_PADDING / 2);
5925
+ return horizontalGutterY(plan.hRow, rowY, grid);
5926
+ }
5927
+ function hBandBounds(plan, rowY, grid) {
5928
+ if (plan.hKind === "pad-top") return { lo: 0, hi: round16(rowY[0] ?? UML_PADDING) };
5929
+ const lo = round16(rowY[plan.hRow] + grid.rowH[plan.hRow]);
5930
+ const hi = round16(rowY[plan.hRow + 1]);
5931
+ return { lo, hi };
5932
+ }
5933
+ var LANE_EPS = 0.5;
5934
+ var X_OVERLAP_EPS = 1e-4;
5935
+ function allocateHorizontal(runs, base, usableHalf) {
5936
+ const floating = runs.filter((r2) => r2.y === null);
5937
+ if (floating.length === 0) return;
5938
+ const pinned = runs.filter((r2) => r2.y !== null);
5939
+ const trackCount = floating.length + pinned.length + 1;
5940
+ const candidates = trackCount <= 1 || usableHalf <= 0 ? [base] : Array.from(
5941
+ { length: trackCount },
5942
+ (_, k) => round16(base - usableHalf + (k + 0.5) * (2 * usableHalf) / trackCount)
5943
+ );
5944
+ const placed = pinned.map((r2) => ({ lo: r2.lo, hi: r2.hi, y: r2.y }));
5945
+ const ordered = [...floating].sort((a, b) => a.lo - b.lo || a.hi - b.hi);
5946
+ for (const run of ordered) {
5947
+ let chosen = candidates[0];
5948
+ for (const cy of candidates) {
5949
+ const collides = placed.some(
5950
+ (q) => Math.abs(q.y - cy) <= LANE_EPS && Math.min(run.hi, q.hi) - Math.max(run.lo, q.lo) > X_OVERLAP_EPS
5951
+ );
5952
+ if (!collides) {
5953
+ chosen = cy;
5954
+ break;
5955
+ }
5956
+ }
5957
+ run.set(chosen);
5958
+ placed.push({ lo: run.lo, hi: run.hi, y: chosen });
5959
+ }
5960
+ }
5961
+ function applyGlyphInsets(pts, srcInset, tgtInset) {
5962
+ if (pts.length < 2) return pts;
5963
+ const out = pts.map((p) => ({ ...p }));
5964
+ if (srcInset > 0) {
5965
+ const p0 = out[0];
5966
+ const p1 = out[1];
5967
+ const dir = p1.x >= p0.x ? "right" : "left";
5968
+ out[0] = moveAlongDir(p0, dir, srcInset);
5969
+ }
5970
+ if (tgtInset > 0) {
5971
+ const pn = out[out.length - 1];
5972
+ const pm = out[out.length - 2];
5973
+ const dir = pm.x >= pn.x ? "right" : "left";
5974
+ out[out.length - 1] = moveAlongDir(pn, dir, tgtInset);
5975
+ }
5976
+ return out;
5977
+ }
5978
+ function reserveTextBox(relId, slot, text, anchorX, anchorY, anchor) {
5979
+ const w = round16(estimateTextWidth(text, UML_LABEL_FONT));
5980
+ const h = round16(UML_LABEL_FONT * 1.2);
5981
+ const x = anchor === "middle" ? round16(anchorX - w / 2) : round16(anchorX);
5982
+ const y = round16(anchorY);
5983
+ return { relId, slot, text, x, y, w, h, anchor };
5984
+ }
5985
+ function computeUmlLayout(input, labels = UML_TITLE_LABELS_EN) {
5986
+ validateUml(input);
5987
+ const relTitle = (rel) => rel.title ?? labels.kinds[rel.kind];
5988
+ if (input.classes.length === 0) {
5989
+ return {
5990
+ nodes: [],
5991
+ elements: [],
5992
+ textBoxes: [],
5993
+ canvasW: UML_PADDING * 2,
5994
+ canvasH: UML_PADDING * 2,
5995
+ grid: { cellX: [], cellY: [], colW: [], rowH: [] },
5996
+ colX: [],
5997
+ rowY: []
5998
+ };
5999
+ }
6000
+ const classes = [...input.classes].sort((a, b) => a.id - b.id);
6001
+ const rels = [...input.relationships].sort((a, b) => a.id - b.id);
6002
+ const metricsMap = /* @__PURE__ */ new Map();
6003
+ for (const cls of classes) metricsMap.set(cls.id, measureClass(cls));
6004
+ const gridCells = classes.map((cls) => ({
6005
+ col: cls.col,
6006
+ row: cls.row,
6007
+ w: metricsMap.get(cls.id).boxW,
6008
+ h: metricsMap.get(cls.id).boxH
6009
+ }));
6010
+ const grid = packGrid(gridCells, { colGap: UML_COL_GAP, rowGap: UML_ROW_GAP });
6011
+ const colX = [];
6012
+ {
6013
+ let x = UML_PADDING;
6014
+ for (let c = 0; c < grid.colW.length; c++) {
6015
+ colX.push(round16(x));
6016
+ x = round16(x + grid.colW[c] + UML_COL_GAP);
6017
+ }
6018
+ }
6019
+ const rowY = [];
6020
+ {
6021
+ let y = UML_PADDING;
6022
+ for (let r2 = 0; r2 < grid.rowH.length; r2++) {
6023
+ rowY.push(round16(y));
6024
+ y = round16(y + grid.rowH[r2] + UML_ROW_GAP);
6025
+ }
6026
+ }
6027
+ const nodeByClassId = /* @__PURE__ */ new Map();
6028
+ const nodes = [];
6029
+ for (const cls of classes) {
6030
+ const metrics = metricsMap.get(cls.id);
6031
+ const cx = round16(colX[cls.col] + grid.colW[cls.col] / 2);
6032
+ const cy = round16(rowY[cls.row] + grid.rowH[cls.row] / 2);
6033
+ const title = cls.title ?? (cls.stereotype !== null ? `\xAB${cls.stereotype}\xBB ${cls.name}` : cls.name);
6034
+ const node = {
6035
+ classId: cls.id,
6036
+ cx,
6037
+ cy,
6038
+ boxW: metrics.boxW,
6039
+ boxH: metrics.boxH,
6040
+ metrics,
6041
+ title,
6042
+ col: cls.col,
6043
+ row: cls.row
6044
+ };
6045
+ nodes.push(node);
6046
+ nodeByClassId.set(cls.id, node);
6047
+ }
6048
+ const maxCol = grid.colW.length - 1;
6049
+ const maxRow = grid.rowH.length - 1;
6050
+ const sideCount = /* @__PURE__ */ new Map();
6051
+ const bumpSide = (classId, side) => {
6052
+ const key = `${classId}:${side}`;
6053
+ sideCount.set(key, (sideCount.get(key) ?? 0) + 1);
6054
+ };
6055
+ for (const rel of rels) {
6056
+ const srcNode = nodeByClassId.get(rel.sourceId);
6057
+ const tgtNode = nodeByClassId.get(rel.targetId);
6058
+ if (!srcNode || !tgtNode) continue;
6059
+ if (rel.sourceId === rel.targetId) {
6060
+ bumpSide(srcNode.classId, "right");
6061
+ bumpSide(srcNode.classId, "right");
6062
+ continue;
6063
+ }
6064
+ const { srcSide, tgtSide } = chooseSides(srcNode, tgtNode, maxCol);
6065
+ bumpSide(srcNode.classId, srcSide);
6066
+ bumpSide(tgtNode.classId, tgtSide);
6067
+ }
6068
+ {
6069
+ const capIssues = [];
6070
+ for (const node of nodes) {
6071
+ const cap = sideCapacity(node.boxH, UML_ROW_GAP, GLYPH_SLOT_PITCH, MAX_GLYPH_HALF);
6072
+ for (const side of ["left", "right"]) {
6073
+ const count = sideCount.get(`${node.classId}:${side}`) ?? 0;
6074
+ if (count > cap) capIssues.push(sideCapacityIssue(node.classId, side, count, cap));
6075
+ }
6076
+ }
6077
+ if (capIssues.length > 0) {
6078
+ throw new UmlValidationError(capIssues);
6079
+ }
6080
+ }
6081
+ const sidePortSlot = /* @__PURE__ */ new Map();
6082
+ const nextSidePort = (classId, side) => {
6083
+ const key = `${classId}:${side}`;
6084
+ const idx = sidePortSlot.get(key) ?? 0;
6085
+ sidePortSlot.set(key, idx + 1);
6086
+ return idx;
6087
+ };
6088
+ const elements = [];
6089
+ const textBoxes = [];
6090
+ const plans = [];
6091
+ for (const rel of rels) {
6092
+ const srcNode = nodeByClassId.get(rel.sourceId);
6093
+ const tgtNode = nodeByClassId.get(rel.targetId);
6094
+ if (!srcNode || !tgtNode) continue;
6095
+ const glyphs = RELATION_GLYPHS[rel.kind];
6096
+ const srcInset = glyphInsetForKind(glyphs.sourceEnd);
6097
+ const tgtInset = glyphInsetForKind(glyphs.targetEnd);
6098
+ if (rel.sourceId === rel.targetId) {
6099
+ const vCol = srcNode.col;
6100
+ const armDy1 = portOffset(nextSidePort(srcNode.classId, "right"), GLYPH_SLOT_PITCH);
6101
+ const armDy2 = portOffset(nextSidePort(srcNode.classId, "right"), GLYPH_SLOT_PITCH);
6102
+ plans.push({
6103
+ rel,
6104
+ srcNode,
6105
+ tgtNode,
6106
+ srcSide: "right",
6107
+ tgtSide: "right",
6108
+ srcVCol: vCol,
6109
+ tgtVCol: vCol,
6110
+ srcInset,
6111
+ tgtInset,
6112
+ srcPortY: round16(srcNode.cy + armDy1),
6113
+ tgtPortY: round16(srcNode.cy + armDy2),
6114
+ hKind: "row",
6115
+ hRow: 0,
6116
+ // unused for self-loops (no H-leg)
6117
+ isSelfLoop: true,
6118
+ srcVx: 0,
6119
+ tgtVx: 0,
6120
+ hY: 0
6121
+ });
6122
+ continue;
6123
+ }
6124
+ const { srcSide, tgtSide, srcVCol, tgtVCol } = chooseSides(srcNode, tgtNode, maxCol);
6125
+ const srcPortDy = portOffset(nextSidePort(srcNode.classId, srcSide), GLYPH_SLOT_PITCH);
6126
+ const tgtPortDy = portOffset(nextSidePort(tgtNode.classId, tgtSide), GLYPH_SLOT_PITCH);
6127
+ const sr = srcNode.row;
6128
+ const tr = tgtNode.row;
6129
+ let hKind;
6130
+ let hRow;
6131
+ if (sr !== tr) {
6132
+ hKind = "row";
6133
+ hRow = Math.min(sr, tr);
6134
+ } else if (maxRow >= 1) {
6135
+ hKind = "row";
6136
+ hRow = sr < maxRow ? sr : sr - 1;
6137
+ } else {
6138
+ hKind = "pad-top";
6139
+ hRow = -1;
6140
+ }
6141
+ plans.push({
6142
+ rel,
6143
+ srcNode,
6144
+ tgtNode,
6145
+ srcSide,
6146
+ tgtSide,
6147
+ srcVCol,
6148
+ tgtVCol,
6149
+ srcInset,
6150
+ tgtInset,
6151
+ srcPortY: round16(srcNode.cy + srcPortDy),
6152
+ tgtPortY: round16(tgtNode.cy + tgtPortDy),
6153
+ hKind,
6154
+ hRow,
6155
+ isSelfLoop: false,
6156
+ srcVx: 0,
6157
+ tgtVx: 0,
6158
+ hY: 0
6159
+ });
6160
+ }
6161
+ const hHalfBand = (p) => p.hKind === "pad-top" ? round16(UML_PADDING / 2) : round16(UML_ROW_GAP / 2);
6162
+ const vByKey = /* @__PURE__ */ new Map();
6163
+ const pushV = (col, side, portY, hBase, half, inset, set) => {
6164
+ const lo = Math.min(portY, round16(hBase - half));
6165
+ const hi = Math.max(portY, round16(hBase + half));
6166
+ const key = `${col}:${side}`;
6167
+ const arr = vByKey.get(key) ?? [];
6168
+ arr.push({ lo, hi, inset, lane: 0, set });
6169
+ vByKey.set(key, arr);
6170
+ };
6171
+ for (const p of plans) {
6172
+ if (p.isSelfLoop) {
6173
+ const lo = Math.min(p.srcPortY, p.tgtPortY);
6174
+ const hi = Math.max(p.srcPortY, p.tgtPortY);
6175
+ const key = `${p.srcVCol}:right`;
6176
+ const arr = vByKey.get(key) ?? [];
6177
+ arr.push({ lo, hi, inset: Math.max(p.srcInset, p.tgtInset), lane: 0, set: (x) => {
6178
+ p.srcVx = x;
6179
+ p.tgtVx = x;
6180
+ } });
6181
+ vByKey.set(key, arr);
6182
+ continue;
6183
+ }
6184
+ if (p.srcVCol === p.tgtVCol && p.srcSide === p.tgtSide) {
6185
+ const lo = Math.min(p.srcPortY, p.tgtPortY);
6186
+ const hi = Math.max(p.srcPortY, p.tgtPortY);
6187
+ const key = `${p.srcVCol}:${p.srcSide}`;
6188
+ const arr = vByKey.get(key) ?? [];
6189
+ arr.push({ lo, hi, inset: Math.max(p.srcInset, p.tgtInset), lane: 0, set: (x) => {
6190
+ p.srcVx = x;
6191
+ p.tgtVx = x;
6192
+ } });
6193
+ vByKey.set(key, arr);
6194
+ continue;
6195
+ }
6196
+ const hBase = hBaseY(p, rowY, grid);
6197
+ const half = hHalfBand(p);
6198
+ pushV(p.srcVCol, p.srcSide, p.srcPortY, hBase, half, p.srcInset, (x) => {
6199
+ p.srcVx = x;
6200
+ });
6201
+ pushV(p.tgtVCol, p.tgtSide, p.tgtPortY, hBase, half, p.tgtInset, (x) => {
6202
+ p.tgtVx = x;
6203
+ });
6204
+ }
6205
+ for (const [key, items] of vByKey.entries()) {
6206
+ const side = key.endsWith(":right") ? "right" : "left";
6207
+ const col = Number(key.slice(0, key.lastIndexOf(":")));
6208
+ const laneCount = allocateLanes(
6209
+ items.map((it) => ({ lo: it.lo, hi: it.hi, set: (l) => {
6210
+ it.lane = l;
6211
+ } }))
6212
+ );
6213
+ const left = vGutterLeft(col, colX, grid);
6214
+ const right = round16(left + UML_COL_GAP);
6215
+ const center = round16((left + right) / 2);
6216
+ const maxBaseGap = Math.max(...items.map((it) => Math.max(V_GUTTER_MARGIN, it.inset + MIN_TERMINAL_STUB)));
6217
+ for (const it of items) {
6218
+ let x;
6219
+ if (side === "right") {
6220
+ const lo = left + maxBaseGap;
6221
+ const hi = center - 1;
6222
+ x = laneCount <= 1 ? lo : lo + it.lane * (hi - lo) / (laneCount - 1);
6223
+ } else {
6224
+ const hi = right - maxBaseGap;
6225
+ const lo = center + 1;
6226
+ x = laneCount <= 1 ? hi : hi - it.lane * (hi - lo) / (laneCount - 1);
6227
+ }
6228
+ it.set(round16(x));
6229
+ }
6230
+ }
6231
+ const usableHalfOf = (kind) => Math.max(0, (kind === "pad-top" ? round16(UML_PADDING / 2) : round16(UML_ROW_GAP / 2)) - 3);
6232
+ const gutterKey = (p) => p.hKind === "pad-top" ? "pad-top" : `row:${p.hRow}`;
6233
+ const gutterMeta = /* @__PURE__ */ new Map();
6234
+ for (const p of plans) {
6235
+ if (p.isSelfLoop) continue;
6236
+ const key = gutterKey(p);
6237
+ if (!gutterMeta.has(key)) gutterMeta.set(key, { base: hBaseY(p, rowY, grid), usableHalf: usableHalfOf(p.hKind) });
6238
+ }
6239
+ const hRunsByKey = /* @__PURE__ */ new Map();
6240
+ const pushRun = (key, run) => {
6241
+ const arr = hRunsByKey.get(key) ?? [];
6242
+ arr.push(run);
6243
+ hRunsByKey.set(key, arr);
6244
+ };
6245
+ const pinStub = (yy, a, b) => {
6246
+ for (const [gk, meta] of gutterMeta.entries()) {
6247
+ if (yy >= meta.base - meta.usableHalf - LANE_EPS && yy <= meta.base + meta.usableHalf + LANE_EPS) {
6248
+ pushRun(gk, { lo: Math.min(a, b), hi: Math.max(a, b), y: yy, set: () => {
6249
+ } });
6250
+ }
6251
+ }
6252
+ };
6253
+ for (const p of plans) {
6254
+ if (p.isSelfLoop) {
6255
+ const sideX = p.srcSide === "right" ? boxEdges(p.srcNode).right : boxEdges(p.srcNode).left;
6256
+ pinStub(p.srcPortY, sideX, p.srcVx);
6257
+ pinStub(p.tgtPortY, sideX, p.tgtVx);
6258
+ continue;
6259
+ }
6260
+ const key = gutterKey(p);
6261
+ if (Math.abs(p.srcVx - p.tgtVx) > LANE_EPS) {
6262
+ pushRun(key, { lo: Math.min(p.srcVx, p.tgtVx), hi: Math.max(p.srcVx, p.tgtVx), y: null, set: (y) => {
6263
+ p.hY = round16(y);
6264
+ } });
6265
+ } else {
6266
+ p.hY = p.srcPortY;
6267
+ }
6268
+ const srcPortX = p.srcSide === "right" ? boxEdges(p.srcNode).right : boxEdges(p.srcNode).left;
6269
+ const tgtPortX = p.tgtSide === "right" ? boxEdges(p.tgtNode).right : boxEdges(p.tgtNode).left;
6270
+ pinStub(p.srcPortY, srcPortX, p.srcVx);
6271
+ pinStub(p.tgtPortY, tgtPortX, p.tgtVx);
6272
+ }
6273
+ for (const [key, runs] of hRunsByKey.entries()) {
6274
+ const meta = gutterMeta.get(key);
6275
+ allocateHorizontal(runs, meta.base, meta.usableHalf);
6276
+ }
6277
+ for (const p of plans) {
6278
+ const srcPort = sidePort(p.srcNode, p.srcSide, p.srcPortY - p.srcNode.cy);
6279
+ const tgtPort = sidePort(p.tgtNode, p.tgtSide, p.tgtPortY - p.tgtNode.cy);
6280
+ if (p.isSelfLoop) {
6281
+ const raw2 = [
6282
+ srcPort,
6283
+ { x: p.srcVx, y: srcPort.y },
6284
+ { x: p.srcVx, y: tgtPort.y },
6285
+ tgtPort
6286
+ ];
6287
+ const portPts2 = simplifyWaypoints(raw2);
6288
+ const pts2 = applyGlyphInsets(portPts2, p.srcInset, p.tgtInset);
6289
+ const edgeId2 = UML_SELF_LOOP_BASE + p.rel.id;
6290
+ elements.push({ edgeId: edgeId2, relId: p.rel.id, points: pts2, kind: "self-loop", title: relTitle(p.rel) });
6291
+ reserveSelfLoopText(p.rel, p, textBoxes);
6292
+ continue;
6293
+ }
6294
+ const raw = [
6295
+ srcPort,
6296
+ { x: p.srcVx, y: srcPort.y },
6297
+ { x: p.srcVx, y: p.hY },
6298
+ { x: p.tgtVx, y: p.hY },
6299
+ { x: p.tgtVx, y: tgtPort.y },
6300
+ tgtPort
6301
+ ];
6302
+ const portPts = simplifyWaypoints(raw);
6303
+ const pts = applyGlyphInsets(portPts, p.srcInset, p.tgtInset);
6304
+ const edgeId = UML_ASSOC_ROUTE_BASE + p.rel.id;
6305
+ elements.push({ edgeId, relId: p.rel.id, points: pts, kind: "route", title: relTitle(p.rel) });
6306
+ reserveRouteText(p.rel, p, hBandBounds(p, rowY, grid), textBoxes);
6307
+ }
6308
+ let maxRight = 0;
6309
+ let maxBottom = 0;
6310
+ for (const node of nodes) {
6311
+ const { right, bottom } = boxEdges(node);
6312
+ if (right > maxRight) maxRight = right;
6313
+ if (bottom > maxBottom) maxBottom = bottom;
6314
+ }
6315
+ for (const el of elements) {
6316
+ for (const pt2 of el.points) {
6317
+ if (pt2.x > maxRight) maxRight = pt2.x;
6318
+ if (pt2.y > maxBottom) maxBottom = pt2.y;
6319
+ }
6320
+ }
6321
+ for (const tb of textBoxes) {
6322
+ if (tb.x + tb.w > maxRight) maxRight = tb.x + tb.w;
6323
+ if (tb.y + tb.h > maxBottom) maxBottom = tb.y + tb.h;
6324
+ }
6325
+ const canvasW = round16(maxRight + UML_PADDING);
6326
+ const canvasH = round16(maxBottom + UML_PADDING);
6327
+ return { nodes, elements, textBoxes, canvasW, canvasH, grid, colX, rowY };
6328
+ }
6329
+ function reserveRouteText(rel, p, band, out) {
6330
+ const textH = round16(UML_LABEL_FONT * 1.2);
6331
+ const clampY = (y) => round16(Math.max(band.lo + 1, Math.min(y, band.hi - 1 - textH)));
6332
+ const above = clampY(p.hY - textH - 1);
6333
+ const below = clampY(p.hY + 1);
6334
+ const srcX = round16(p.srcVx + 3);
6335
+ const tgtX = round16(p.tgtVx + 3);
6336
+ if (rel.sourceMultiplicity !== null) {
6337
+ out.push(reserveTextBox(rel.id, "src-mult", rel.sourceMultiplicity, srcX, above, "start"));
6338
+ }
6339
+ if (rel.sourceRole !== null) {
6340
+ out.push(reserveTextBox(rel.id, "src-role", rel.sourceRole, srcX, below, "start"));
6341
+ }
6342
+ if (rel.targetMultiplicity !== null) {
6343
+ out.push(reserveTextBox(rel.id, "tgt-mult", rel.targetMultiplicity, tgtX, above, "start"));
6344
+ }
6345
+ if (rel.targetRole !== null) {
6346
+ out.push(reserveTextBox(rel.id, "tgt-role", rel.targetRole, tgtX, below, "start"));
6347
+ }
6348
+ if (rel.label !== null) {
6349
+ const midX = round16((p.srcVx + p.tgtVx) / 2);
6350
+ out.push(reserveTextBox(rel.id, "label", rel.label, midX, above, "middle"));
6351
+ }
6352
+ }
6353
+ function reserveSelfLoopText(rel, p, out) {
6354
+ const x0 = round16(p.srcVx + 3);
6355
+ const srcY = p.srcPortY;
6356
+ const tgtY = p.tgtPortY;
6357
+ if (rel.sourceMultiplicity !== null) {
6358
+ out.push(reserveTextBox(rel.id, "src-mult", rel.sourceMultiplicity, x0, round16(srcY - UML_LABEL_FONT - 1), "start"));
6359
+ }
6360
+ if (rel.sourceRole !== null) {
6361
+ out.push(reserveTextBox(rel.id, "src-role", rel.sourceRole, x0, round16(srcY + 1), "start"));
6362
+ }
6363
+ if (rel.targetMultiplicity !== null) {
6364
+ out.push(reserveTextBox(rel.id, "tgt-mult", rel.targetMultiplicity, x0, round16(tgtY - UML_LABEL_FONT - 1), "start"));
6365
+ }
6366
+ if (rel.targetRole !== null) {
6367
+ out.push(reserveTextBox(rel.id, "tgt-role", rel.targetRole, x0, round16(tgtY + 1), "start"));
6368
+ }
6369
+ if (rel.label !== null) {
6370
+ out.push(reserveTextBox(rel.id, "label", rel.label, x0, round16((srcY + tgtY) / 2 - UML_LABEL_FONT / 2), "start"));
6371
+ }
6372
+ }
6373
+
6374
+ // src/uml/svg.ts
6375
+ var GLYPH_STROKE6 = "#52525b";
6376
+ var LABEL_FILL7 = "#3f3f46";
6377
+ var EDGE_INK8 = "#71717a";
6378
+ var round17 = (n) => Math.round(n * 100) / 100;
6379
+ function emitGlyph(gk, tip, dir) {
6380
+ switch (gk) {
6381
+ case "none":
6382
+ return "";
6383
+ case "open-arrow": {
6384
+ const pts = arrowOpenPoints(tip, dir);
6385
+ return `<polyline points="${pts}" fill="none" stroke="${GLYPH_STROKE6}" stroke-width="1.5"/>`;
6386
+ }
6387
+ case "hollow-triangle": {
6388
+ const pts = trianglePoints(tip, dir);
6389
+ return `<polygon points="${pts}" fill="#fff" stroke="${GLYPH_STROKE6}" stroke-width="1.5"/>`;
6390
+ }
6391
+ case "hollow-diamond": {
6392
+ const pts = diamondPoints(tip, dir);
6393
+ return `<polygon points="${pts}" fill="#fff" stroke="${GLYPH_STROKE6}" stroke-width="1.5"/>`;
6394
+ }
6395
+ case "solid-diamond": {
6396
+ const pts = diamondPoints(tip, dir);
6397
+ return `<polygon points="${pts}" fill="${GLYPH_STROKE6}" stroke="${GLYPH_STROKE6}" stroke-width="1.5"/>`;
6398
+ }
6399
+ }
6400
+ }
6401
+ function glyphDir(firstPt, secondPt) {
6402
+ const dx = secondPt.x - firstPt.x;
6403
+ const dy = secondPt.y - firstPt.y;
6404
+ if (Math.abs(dx) >= Math.abs(dy)) {
6405
+ return dx >= 0 ? "left" : "right";
6406
+ }
6407
+ return dy >= 0 ? "up" : "down";
6408
+ }
6409
+ function featureSvg(f, x, y, font) {
6410
+ const prefix = f.visibility !== null ? UML_VIS_GLYPH[f.visibility] + " " : "";
6411
+ const text = prefix + f.text;
6412
+ const baseline = round17(y + font * 0.8);
6413
+ let out = `<text x="${x}" y="${baseline}" font-family="${FONT_FAMILY}" font-size="${font}" fill="${LABEL_FILL7}">${xmlEscape(text)}</text>`;
6414
+ if (f.isStatic) {
6415
+ const lineY = round17(baseline + 1);
6416
+ const approxW = round17(text.length * font * 0.6);
6417
+ out += `<line x1="${x}" y1="${lineY}" x2="${round17(x + approxW)}" y2="${lineY}" stroke="${LABEL_FILL7}" stroke-width="0.8"/>`;
6418
+ }
6419
+ return out;
6420
+ }
6421
+ function boxSvg2(node, cls) {
6422
+ const left = round17(node.cx - node.boxW / 2);
6423
+ const top = round17(node.cy - node.boxH / 2);
6424
+ const metrics = node.metrics;
6425
+ const pieces = [];
6426
+ pieces.push(`<title>${xmlEscape(node.title)}</title>`);
6427
+ pieces.push(
6428
+ `<rect x="${left}" y="${top}" width="${node.boxW}" height="${node.boxH}" rx="2" fill="#fff" stroke="${GLYPH_STROKE6}" stroke-width="1.5"/>`
6429
+ );
6430
+ for (const dy of metrics.dividerYs) {
6431
+ const y = round17(top + dy);
6432
+ pieces.push(
6433
+ `<line x1="${left}" y1="${y}" x2="${round17(left + node.boxW)}" y2="${y}" stroke="${GLYPH_STROKE6}" stroke-width="0.75"/>`
6434
+ );
6435
+ }
6436
+ const compartment0 = metrics.rows[0];
6437
+ const textX = round17(left + node.boxW / 2);
6438
+ let lineIdx = 0;
6439
+ if (cls.stereotype !== null) {
6440
+ const stereoY = round17(top + compartment0.top + lineIdx * UML_LINE_H + UML_NAME_FONT * 0.8);
6441
+ pieces.push(
6442
+ `<text x="${textX}" y="${stereoY}" font-family="${FONT_FAMILY}" font-size="${UML_NAME_FONT}" fill="${LABEL_FILL7}" text-anchor="middle">${xmlEscape(cls.stereotype)}</text>`
6443
+ );
6444
+ lineIdx++;
6445
+ }
6446
+ const nameY = round17(top + compartment0.top + lineIdx * UML_LINE_H + UML_NAME_FONT * 0.8);
6447
+ const nameStyle = cls.isAbstract ? ` font-style="italic"` : "";
6448
+ const nameText = metrics.rows[0].lines[lineIdx] ?? "";
6449
+ pieces.push(
6450
+ `<text x="${textX}" y="${nameY}" font-family="${FONT_FAMILY}" font-size="${UML_NAME_FONT}" fill="${LABEL_FILL7}" text-anchor="middle"${nameStyle}>${xmlEscape(nameText)}</text>`
6451
+ );
6452
+ const attrCompartment = metrics.rows[1];
6453
+ const attrX = round17(left + UML_BOX_PAD_X);
6454
+ for (let i = 0; i < cls.attributes.length; i++) {
6455
+ const f = cls.attributes[i];
6456
+ const fy = round17(top + attrCompartment.top + i * UML_LINE_H);
6457
+ pieces.push(featureSvg(f, attrX, fy, UML_FEAT_FONT));
6458
+ }
6459
+ const opCompartment = metrics.rows[2];
6460
+ const opX = round17(left + UML_BOX_PAD_X);
6461
+ for (let i = 0; i < cls.operations.length; i++) {
6462
+ const f = cls.operations[i];
6463
+ const fy = round17(top + opCompartment.top + i * UML_LINE_H);
6464
+ pieces.push(featureSvg(f, opX, fy, UML_FEAT_FONT));
6465
+ }
6466
+ return `<g data-node-id="c${node.classId}">${pieces.join("")}</g>`;
6467
+ }
6468
+ function edgeSvg(el, rel, textBoxes) {
6469
+ const glyphs = RELATION_GLYPHS[rel.kind];
6470
+ const isDashed = glyphs.line === "dashed";
6471
+ const stroke = isDashed ? EDGE_STROKE.distant : { width: 1.5, opacity: 0.7 };
6472
+ const strokeAttrs = isDashed ? `stroke="${EDGE_INK8}" stroke-width="${stroke.width}" stroke-dasharray="4,4" opacity="${stroke.opacity}"` : `stroke="${EDGE_INK8}" stroke-width="${stroke.width}" opacity="${stroke.opacity}"`;
6473
+ const pts = el.points;
6474
+ const pieces = [`<title>${xmlEscape(el.title)}</title>`];
6475
+ if (pts.length >= 2) {
6476
+ pieces.push(`<polyline points="${pts.map((p) => `${p.x},${p.y}`).join(" ")}" fill="none" ${strokeAttrs}/>`);
6477
+ if (glyphs.sourceEnd !== "none") {
6478
+ const tip = pts[0];
6479
+ const next = pts[1];
6480
+ const dir = glyphDir(tip, next);
6481
+ pieces.push(emitGlyph(glyphs.sourceEnd, tip, dir));
6482
+ }
6483
+ if (glyphs.targetEnd !== "none") {
6484
+ const tip = pts[pts.length - 1];
6485
+ const prev = pts[pts.length - 2];
6486
+ const dir = glyphDir(tip, prev);
6487
+ pieces.push(emitGlyph(glyphs.targetEnd, tip, dir));
6488
+ }
6489
+ for (const tb of textBoxes) {
6490
+ if (tb.relId !== rel.id) continue;
6491
+ const italic = tb.slot === "src-role" || tb.slot === "tgt-role" ? ` font-style="italic"` : "";
6492
+ const anchor = tb.anchor === "middle" ? ` text-anchor="middle"` : "";
6493
+ const drawX = tb.anchor === "middle" ? round17(tb.x + tb.w / 2) : tb.x;
6494
+ const baseline = round17(tb.y + UML_LABEL_FONT * 0.8);
6495
+ pieces.push(
6496
+ `<text x="${drawX}" y="${baseline}" font-family="${FONT_FAMILY}" font-size="${UML_LABEL_FONT}" fill="${LABEL_FILL7}"${anchor}${italic}>${xmlEscape(tb.text)}</text>`
6497
+ );
6498
+ }
6499
+ }
6500
+ return `<g data-edge-id="${el.edgeId}">${pieces.join("")}</g>`;
6501
+ }
6502
+ function makeSwatchLine(isDashed) {
6503
+ return (x, y) => {
6504
+ const attrs = isDashed ? `stroke="${EDGE_INK8}" stroke-width="1.5" stroke-dasharray="4,4"` : `stroke="${EDGE_INK8}" stroke-width="1.5"`;
6505
+ return `<line x1="${x}" y1="${y}" x2="${x + LEGEND_SWATCH_W}" y2="${y}" ${attrs}/>`;
6506
+ };
6507
+ }
6508
+ function umlLayoutSvg(layout, input, opts = {}) {
6509
+ const labels = opts.labels ?? UML_SVG_LABELS_EN;
6510
+ const showLegend = opts.legend !== false;
6511
+ const classMap = new Map(input.classes.map((c) => [c.id, c]));
6512
+ const relMap = new Map(input.relationships.map((r2) => [r2.id, r2]));
6513
+ const parts = [];
6514
+ for (const node of layout.nodes) {
6515
+ const cls = classMap.get(node.classId);
6516
+ parts.push(boxSvg2(node, cls));
6517
+ }
6518
+ for (const el of layout.elements) {
6519
+ const rel = relMap.get(el.relId);
6520
+ if (!rel) continue;
6521
+ parts.push(edgeSvg(el, rel, layout.textBoxes));
6522
+ }
6523
+ let canvasW = layout.canvasW;
6524
+ let canvasH = layout.canvasH;
6525
+ if (showLegend) {
6526
+ const usedKinds = new Set(
6527
+ input.relationships.map((r2) => r2.kind)
6528
+ );
6529
+ const entries = [];
6530
+ for (const kind of UML_RELATIONSHIPS) {
6531
+ if (!usedKinds.has(kind)) continue;
6532
+ const isDashed = RELATION_GLYPHS[kind].line === "dashed";
6533
+ entries.push({
6534
+ swatch: makeSwatchLine(isDashed),
6535
+ label: labels.legend[kind]
6536
+ });
6537
+ }
6538
+ if (entries.length > 0) {
6539
+ const block = legendBlock(entries, canvasH);
6540
+ parts.push(block.svg);
6541
+ canvasH = block.height;
6542
+ canvasW = Math.max(canvasW, block.width);
6543
+ }
6544
+ }
6545
+ const roundedW = round17(canvasW);
6546
+ const roundedH = round17(canvasH);
6547
+ 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>`;
6548
+ }
6549
+
6550
+ // src/uml/render.ts
6551
+ function umlSvg(input, opts = {}) {
6552
+ const layout = computeUmlLayout(input, opts.titleLabels ?? UML_TITLE_LABELS_EN);
6553
+ const svg = umlLayoutSvg(layout, input, opts);
6554
+ return { svg, layout };
6555
+ }
6556
+
4744
6557
  exports.ANNOTATION_INK = ANNOTATION_INK;
4745
6558
  exports.CHAR_W = CHAR_W;
4746
6559
  exports.CODE_FONT = CODE_FONT;
@@ -4764,6 +6577,12 @@ exports.FishboneValidationError = FishboneValidationError;
4764
6577
  exports.GATE_TYPES = GATE_TYPES;
4765
6578
  exports.GENOGRAM_SVG_LABELS_EN = GENOGRAM_SVG_LABELS_EN;
4766
6579
  exports.GENOGRAM_TITLE_LABELS_EN = GENOGRAM_TITLE_LABELS_EN;
6580
+ exports.GLYPH_ARROW_HALF = GLYPH_ARROW_HALF;
6581
+ exports.GLYPH_ARROW_LEN = GLYPH_ARROW_LEN;
6582
+ exports.GLYPH_DIAMOND_HALF = GLYPH_DIAMOND_HALF;
6583
+ exports.GLYPH_DIAMOND_LEN = GLYPH_DIAMOND_LEN;
6584
+ exports.GLYPH_TRI_HALF = GLYPH_TRI_HALF;
6585
+ exports.GLYPH_TRI_LEN = GLYPH_TRI_LEN;
4767
6586
  exports.KINSHIP_EN = KINSHIP_EN;
4768
6587
  exports.LABEL_FONT = LABEL_FONT;
4769
6588
  exports.LABEL_GAP = LABEL_GAP;
@@ -4815,16 +6634,63 @@ exports.PHYLO_SCALEBAR_ID = PHYLO_SCALEBAR_ID;
4815
6634
  exports.PHYLO_SUPPORT_FONT = PHYLO_SUPPORT_FONT;
4816
6635
  exports.PHYLO_SVG_LABELS_EN = PHYLO_SVG_LABELS_EN;
4817
6636
  exports.PHYLO_TITLE_LABELS_EN = PHYLO_TITLE_LABELS_EN;
6637
+ exports.PRISMA_BAND_LABEL_W = PRISMA_BAND_LABEL_W;
6638
+ exports.PRISMA_BOX_KINDS = PRISMA_BOX_KINDS;
6639
+ exports.PRISMA_BOX_PAD_X = PRISMA_BOX_PAD_X;
6640
+ exports.PRISMA_BOX_PAD_Y = PRISMA_BOX_PAD_Y;
6641
+ exports.PRISMA_COLUMNS = PRISMA_COLUMNS;
6642
+ exports.PRISMA_COL_GAP = PRISMA_COL_GAP;
6643
+ exports.PRISMA_CORRIDOR = PRISMA_CORRIDOR;
6644
+ exports.PRISMA_COUNT_FONT = PRISMA_COUNT_FONT;
6645
+ exports.PRISMA_EXCL_ARROW_BASE = PRISMA_EXCL_ARROW_BASE;
6646
+ exports.PRISMA_EXCL_GAP = PRISMA_EXCL_GAP;
6647
+ exports.PRISMA_FLOW_ARROW_BASE = PRISMA_FLOW_ARROW_BASE;
6648
+ exports.PRISMA_HEADING_FONT = PRISMA_HEADING_FONT;
6649
+ exports.PRISMA_LINE_H = PRISMA_LINE_H;
6650
+ exports.PRISMA_MERGE_ARROW_BASE = PRISMA_MERGE_ARROW_BASE;
6651
+ exports.PRISMA_MIN_BOX_W = PRISMA_MIN_BOX_W;
6652
+ exports.PRISMA_PADDING = PRISMA_PADDING;
6653
+ exports.PRISMA_PHASES = PRISMA_PHASES;
6654
+ exports.PRISMA_SVG_LABELS_EN = PRISMA_SVG_LABELS_EN;
6655
+ exports.PRISMA_TITLE_LABELS_EN = PRISMA_TITLE_LABELS_EN;
4818
6656
  exports.PROMOTED_REL_ID_BASE = PROMOTED_REL_ID_BASE;
4819
6657
  exports.PedigreeValidationError = PedigreeValidationError;
4820
6658
  exports.PhyloValidationError = PhyloValidationError;
6659
+ exports.PrismaValidationError = PrismaValidationError;
4821
6660
  exports.QUALITY_LEXICON_EN = QUALITY_LEXICON_EN;
6661
+ exports.RELATION_GLYPHS = RELATION_GLYPHS;
6662
+ exports.UML_ASSOC_ROUTE_BASE = UML_ASSOC_ROUTE_BASE;
6663
+ exports.UML_BOX_PAD_X = UML_BOX_PAD_X;
6664
+ exports.UML_BOX_PAD_Y = UML_BOX_PAD_Y;
6665
+ exports.UML_COL_GAP = UML_COL_GAP;
6666
+ exports.UML_FEAT_FONT = UML_FEAT_FONT;
6667
+ exports.UML_GLYPH_BASE = UML_GLYPH_BASE;
6668
+ exports.UML_LABEL_FONT = UML_LABEL_FONT;
6669
+ exports.UML_LINE_H = UML_LINE_H;
6670
+ exports.UML_MIN_BOX_W = UML_MIN_BOX_W;
6671
+ exports.UML_NAME_FONT = UML_NAME_FONT;
6672
+ exports.UML_PADDING = UML_PADDING;
6673
+ exports.UML_PORT_PITCH = UML_PORT_PITCH;
6674
+ exports.UML_RELATIONSHIPS = UML_RELATIONSHIPS;
6675
+ exports.UML_ROW_GAP = UML_ROW_GAP;
6676
+ exports.UML_SELF_LOOP_BASE = UML_SELF_LOOP_BASE;
6677
+ exports.UML_SELF_LOOP_W = UML_SELF_LOOP_W;
6678
+ exports.UML_SEP_PAD = UML_SEP_PAD;
6679
+ exports.UML_SVG_LABELS_EN = UML_SVG_LABELS_EN;
6680
+ exports.UML_TITLE_LABELS_EN = UML_TITLE_LABELS_EN;
6681
+ exports.UML_VISIBILITIES = UML_VISIBILITIES;
6682
+ exports.UML_VIS_GLYPH = UML_VIS_GLYPH;
4822
6683
  exports.UNION_NOTATION = UNION_NOTATION;
4823
6684
  exports.UNION_REL_ID_BASE = UNION_REL_ID_BASE;
4824
6685
  exports.UNION_STATUSES = UNION_STATUSES;
6686
+ exports.UmlValidationError = UmlValidationError;
6687
+ exports.allocateLanes = allocateLanes;
4825
6688
  exports.annotationDot = annotationDot;
4826
6689
  exports.annotationSwatch = annotationSwatch;
4827
6690
  exports.annotationTick = annotationTick;
6691
+ exports.arrowFilledPoints = arrowFilledPoints;
6692
+ exports.arrowOpenPoints = arrowOpenPoints;
6693
+ exports.bandStack = bandStack;
4828
6694
  exports.clampLabel = clampLabel;
4829
6695
  exports.classifyRelationshipType = classifyRelationshipType;
4830
6696
  exports.computeFaultTreeLayout = computeFaultTreeLayout;
@@ -4832,6 +6698,9 @@ exports.computeGenogramLayout = computeGenogramLayout;
4832
6698
  exports.computeOrgChartLayout = computeOrgChartLayout;
4833
6699
  exports.computePedigreeLayout = computePedigreeLayout;
4834
6700
  exports.computePhyloLayout = computePhyloLayout;
6701
+ exports.computePrismaLayout = computePrismaLayout;
6702
+ exports.computeUmlLayout = computeUmlLayout;
6703
+ exports.diamondPoints = diamondPoints;
4835
6704
  exports.ecomapSvg = ecomapSvg;
4836
6705
  exports.estimateTextWidth = estimateTextWidth;
4837
6706
  exports.faultTreeIssues = faultTreeIssues;
@@ -4840,13 +6709,16 @@ exports.faultTreeSvg = faultTreeSvg;
4840
6709
  exports.fishboneSvg = fishboneSvg;
4841
6710
  exports.genogramLayoutSvg = genogramLayoutSvg;
4842
6711
  exports.genogramSvg = genogramSvg;
6712
+ exports.glyphInset = glyphInset;
4843
6713
  exports.latestUnionPerPair = latestUnionPerPair;
4844
6714
  exports.legendBlock = legendBlock;
6715
+ exports.measureCompartmentBox = measureCompartmentBox;
4845
6716
  exports.niceScaleStep = niceScaleStep;
4846
6717
  exports.normalizeText = normalizeText;
4847
6718
  exports.orgChartIssues = orgChartIssues;
4848
6719
  exports.orgChartLayoutSvg = orgChartLayoutSvg;
4849
6720
  exports.orgChartSvg = orgChartSvg;
6721
+ exports.packGrid = packGrid;
4850
6722
  exports.packSubtree = packSubtree;
4851
6723
  exports.pathData = pathData;
4852
6724
  exports.pedigreeIssues = pedigreeIssues;
@@ -4855,13 +6727,24 @@ exports.pedigreeSvg = pedigreeSvg;
4855
6727
  exports.phyloIssues = phyloIssues;
4856
6728
  exports.phyloLayoutSvg = phyloLayoutSvg;
4857
6729
  exports.phyloSvg = phyloSvg;
6730
+ exports.prismaIssues = prismaIssues;
6731
+ exports.prismaLayoutSvg = prismaLayoutSvg;
6732
+ exports.prismaSvg = prismaSvg;
4858
6733
  exports.qualityLineStyle = qualityLineStyle;
4859
6734
  exports.relationshipTypeTokens = relationshipTypeTokens;
4860
6735
  exports.romanNumeral = romanNumeral;
6736
+ exports.sideCapacity = sideCapacity;
6737
+ exports.sideCapacityIssue = sideCapacityIssue;
6738
+ exports.trianglePoints = trianglePoints;
6739
+ exports.umlIssues = umlIssues;
6740
+ exports.umlLayoutSvg = umlLayoutSvg;
6741
+ exports.umlSvg = umlSvg;
4861
6742
  exports.validateFaultTree = validateFaultTree;
4862
6743
  exports.validateOrgChart = validateOrgChart;
4863
6744
  exports.validatePedigree = validatePedigree;
4864
6745
  exports.validatePhylo = validatePhylo;
6746
+ exports.validatePrisma = validatePrisma;
6747
+ exports.validateUml = validateUml;
4865
6748
  exports.wrapLabel = wrapLabel;
4866
6749
  exports.wrapLabelBalanced = wrapLabelBalanced;
4867
6750
  exports.xmlEscape = xmlEscape;