@unpunnyfuns/conduit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/LICENSE +25 -0
  2. package/README.md +202 -0
  3. package/dist/assert.d.ts +6 -0
  4. package/dist/assert.d.ts.map +1 -0
  5. package/dist/assert.js +8 -0
  6. package/dist/assert.js.map +1 -0
  7. package/dist/cn.d.ts +3 -0
  8. package/dist/cn.d.ts.map +1 -0
  9. package/dist/cn.js +3 -0
  10. package/dist/cn.js.map +1 -0
  11. package/dist/components/Badge.d.ts +6 -0
  12. package/dist/components/Badge.d.ts.map +1 -0
  13. package/dist/components/Badge.js +11 -0
  14. package/dist/components/Badge.js.map +1 -0
  15. package/dist/components/Card.d.ts +20 -0
  16. package/dist/components/Card.d.ts.map +1 -0
  17. package/dist/components/Card.js +24 -0
  18. package/dist/components/Card.js.map +1 -0
  19. package/dist/components/Diagram.d.ts +37 -0
  20. package/dist/components/Diagram.d.ts.map +1 -0
  21. package/dist/components/Diagram.js +92 -0
  22. package/dist/components/Diagram.js.map +1 -0
  23. package/dist/components/EdgeLayer.d.ts +23 -0
  24. package/dist/components/EdgeLayer.d.ts.map +1 -0
  25. package/dist/components/EdgeLayer.js +85 -0
  26. package/dist/components/EdgeLayer.js.map +1 -0
  27. package/dist/components/Lane.d.ts +11 -0
  28. package/dist/components/Lane.d.ts.map +1 -0
  29. package/dist/components/Lane.js +6 -0
  30. package/dist/components/Lane.js.map +1 -0
  31. package/dist/components/icons.d.ts +7 -0
  32. package/dist/components/icons.d.ts.map +1 -0
  33. package/dist/components/icons.js +47 -0
  34. package/dist/components/icons.js.map +1 -0
  35. package/dist/components/pulse.d.ts +15 -0
  36. package/dist/components/pulse.d.ts.map +1 -0
  37. package/dist/components/pulse.js +10 -0
  38. package/dist/components/pulse.js.map +1 -0
  39. package/dist/index.d.ts +14 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +12 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/layout/architecture.d.ts +60 -0
  44. package/dist/layout/architecture.d.ts.map +1 -0
  45. package/dist/layout/architecture.js +88 -0
  46. package/dist/layout/architecture.js.map +1 -0
  47. package/dist/layout/bounds.d.ts +25 -0
  48. package/dist/layout/bounds.d.ts.map +1 -0
  49. package/dist/layout/bounds.js +32 -0
  50. package/dist/layout/bounds.js.map +1 -0
  51. package/dist/layout/congestion.d.ts +18 -0
  52. package/dist/layout/congestion.d.ts.map +1 -0
  53. package/dist/layout/congestion.js +49 -0
  54. package/dist/layout/congestion.js.map +1 -0
  55. package/dist/layout/design.d.ts +98 -0
  56. package/dist/layout/design.d.ts.map +1 -0
  57. package/dist/layout/design.js +92 -0
  58. package/dist/layout/design.js.map +1 -0
  59. package/dist/layout/edges.d.ts +53 -0
  60. package/dist/layout/edges.d.ts.map +1 -0
  61. package/dist/layout/edges.js +848 -0
  62. package/dist/layout/edges.js.map +1 -0
  63. package/dist/layout/errors.d.ts +6 -0
  64. package/dist/layout/errors.d.ts.map +1 -0
  65. package/dist/layout/errors.js +9 -0
  66. package/dist/layout/errors.js.map +1 -0
  67. package/dist/layout/frame.d.ts +35 -0
  68. package/dist/layout/frame.d.ts.map +1 -0
  69. package/dist/layout/frame.js +63 -0
  70. package/dist/layout/frame.js.map +1 -0
  71. package/dist/layout/geometry.d.ts +23 -0
  72. package/dist/layout/geometry.d.ts.map +1 -0
  73. package/dist/layout/geometry.js +22 -0
  74. package/dist/layout/geometry.js.map +1 -0
  75. package/dist/layout/labels.d.ts +12 -0
  76. package/dist/layout/labels.d.ts.map +1 -0
  77. package/dist/layout/labels.js +153 -0
  78. package/dist/layout/labels.js.map +1 -0
  79. package/dist/layout/lanes.d.ts +5 -0
  80. package/dist/layout/lanes.d.ts.map +1 -0
  81. package/dist/layout/lanes.js +13 -0
  82. package/dist/layout/lanes.js.map +1 -0
  83. package/dist/layout/layout.d.ts +42 -0
  84. package/dist/layout/layout.d.ts.map +1 -0
  85. package/dist/layout/layout.js +94 -0
  86. package/dist/layout/layout.js.map +1 -0
  87. package/dist/layout/rank.d.ts +14 -0
  88. package/dist/layout/rank.d.ts.map +1 -0
  89. package/dist/layout/rank.js +78 -0
  90. package/dist/layout/rank.js.map +1 -0
  91. package/dist/layout/scope.d.ts +20 -0
  92. package/dist/layout/scope.d.ts.map +1 -0
  93. package/dist/layout/scope.js +52 -0
  94. package/dist/layout/scope.js.map +1 -0
  95. package/dist/layout/seating.d.ts +27 -0
  96. package/dist/layout/seating.d.ts.map +1 -0
  97. package/dist/layout/seating.js +135 -0
  98. package/dist/layout/seating.js.map +1 -0
  99. package/dist/layout/text.d.ts +12 -0
  100. package/dist/layout/text.d.ts.map +1 -0
  101. package/dist/layout/text.js +148 -0
  102. package/dist/layout/text.js.map +1 -0
  103. package/dist/layout/trace.d.ts +8 -0
  104. package/dist/layout/trace.d.ts.map +1 -0
  105. package/dist/layout/trace.js +51 -0
  106. package/dist/layout/trace.js.map +1 -0
  107. package/dist/layout.d.ts +9 -0
  108. package/dist/layout.d.ts.map +1 -0
  109. package/dist/layout.js +6 -0
  110. package/dist/layout.js.map +1 -0
  111. package/dist/schema/document.d.ts +239 -0
  112. package/dist/schema/document.d.ts.map +1 -0
  113. package/dist/schema/document.js +83 -0
  114. package/dist/schema/document.js.map +1 -0
  115. package/dist/schema/errors.d.ts +20 -0
  116. package/dist/schema/errors.d.ts.map +1 -0
  117. package/dist/schema/errors.js +12 -0
  118. package/dist/schema/errors.js.map +1 -0
  119. package/dist/schema/integrity.d.ts +8 -0
  120. package/dist/schema/integrity.d.ts.map +1 -0
  121. package/dist/schema/integrity.js +63 -0
  122. package/dist/schema/integrity.js.map +1 -0
  123. package/dist/schema/parse.d.ts +6 -0
  124. package/dist/schema/parse.d.ts.map +1 -0
  125. package/dist/schema/parse.js +53 -0
  126. package/dist/schema/parse.js.map +1 -0
  127. package/dist/schema/primitives.d.ts +58 -0
  128. package/dist/schema/primitives.d.ts.map +1 -0
  129. package/dist/schema/primitives.js +44 -0
  130. package/dist/schema/primitives.js.map +1 -0
  131. package/dist/theme.css +63 -0
  132. package/package.json +83 -0
@@ -0,0 +1,94 @@
1
+ import { canvasFor, union } from "./bounds.js";
2
+ import { relieveCongestion } from "./congestion.js";
3
+ import { BADGE_HEIGHT, BADGE_RISE, DEFAULT_CARD_HEIGHTS, DIAGRAM_MARGIN, } from "./design.js";
4
+ import { curveBounds, pathOf, shiftCurve, transposeCurve } from "./edges.js";
5
+ import { ConduitLayoutError } from "./errors.js";
6
+ import { frameFor } from "./frame.js";
7
+ import { roundCoord, transposeBox } from "./geometry.js";
8
+ import { placeLabelPills } from "./labels.js";
9
+ import { findView, resolveScope } from "./scope.js";
10
+ const WHOLE_DOCUMENT = { kind: "all" };
11
+ const requireView = (views, id) => {
12
+ const view = findView(views, id);
13
+ if (view === undefined)
14
+ throw new ConduitLayoutError("UNKNOWN_VIEW", `this document has no view '${id}'`);
15
+ return view;
16
+ };
17
+ const shiftBox = (box, canvas) => ({
18
+ x: roundCoord(box.x + canvas.shiftX),
19
+ y: roundCoord(box.y + canvas.shiftY),
20
+ width: roundCoord(box.width),
21
+ height: roundCoord(box.height),
22
+ });
23
+ const badgeStrip = (placed) => placed.node.badges.length === 0
24
+ ? undefined
25
+ : {
26
+ x: placed.box.x,
27
+ y: placed.box.y - BADGE_RISE,
28
+ width: placed.box.width,
29
+ height: BADGE_HEIGHT,
30
+ };
31
+ /**
32
+ * A document in, every box out. Nothing here reads a clock, a font or a
33
+ * random number: the same document and options produce the same geometry,
34
+ * so a diagram never rearranges itself between two renders that barely
35
+ * changed anything.
36
+ */
37
+ export const layout = (doc, options = {}) => {
38
+ const heights = { ...DEFAULT_CARD_HEIGHTS, ...options.cardHeights };
39
+ const view = options.view === undefined ? undefined : requireView(doc.views, options.view);
40
+ const graph = resolveScope(doc, view?.scope ?? WHOLE_DOCUMENT);
41
+ if (graph.nodes.length === 0)
42
+ throw new ConduitLayoutError("NOTHING_TO_RENDER", "no nodes are in scope for this view");
43
+ const frame = frameFor(doc.direction, graph, heights);
44
+ const { layout: placed, routed } = relieveCongestion(graph, frame);
45
+ const swapped = frame.direction === "down";
46
+ const pills = placeLabelPills(routed, swapped);
47
+ const orient = (box) => (swapped ? transposeBox(box) : box);
48
+ const orientCurve = (curve) => (swapped ? transposeCurve(curve) : curve);
49
+ const lanesOnScreen = placed.lanes.map((lane) => ({ ...lane, box: orient(lane.box) }));
50
+ const nodesOnScreen = placed.nodes.map((node) => ({ ...node, box: orient(node.box) }));
51
+ const curves = new Map(routed.map(({ edge, curve }) => [edge.id, orientCurve(curve)]));
52
+ const pillBoxes = new Map([...pills].map(([id, box]) => [id, orient(box)]));
53
+ const extent = swapped ? { width: placed.height, height: placed.width } : placed;
54
+ const drawn = [
55
+ ...lanesOnScreen.map(({ box }) => box),
56
+ ...nodesOnScreen.flatMap((node) => {
57
+ const strip = badgeStrip(node);
58
+ return strip === undefined ? [node.box] : [node.box, strip];
59
+ }),
60
+ ...pillBoxes.values(),
61
+ ...[...curves.values()].map(curveBounds),
62
+ ];
63
+ const canvas = canvasFor(extent, union(drawn), DIAGRAM_MARGIN);
64
+ const lanes = lanesOnScreen.map((lane) => ({ ...lane, box: shiftBox(lane.box, canvas) }));
65
+ const nodes = nodesOnScreen.map((node) => ({ ...node, box: shiftBox(node.box, canvas) }));
66
+ const edgeBoxes = {};
67
+ const edges = routed.map(({ edge }) => {
68
+ const curve = curves.get(edge.id) ?? { from: { x: 0, y: 0 }, segments: [] };
69
+ edgeBoxes[edge.id] = shiftBox(curveBounds(curve), canvas);
70
+ const pill = pillBoxes.get(edge.id);
71
+ return {
72
+ edge,
73
+ path: pathOf(shiftCurve(curve, canvas.shiftX, canvas.shiftY)),
74
+ tone: edge.status,
75
+ ...(pill === undefined || edge.label === undefined
76
+ ? {}
77
+ : { label: { text: edge.label, box: shiftBox(pill, canvas) } }),
78
+ };
79
+ });
80
+ return {
81
+ width: canvas.width,
82
+ height: canvas.height,
83
+ direction: doc.direction,
84
+ lanes,
85
+ nodes,
86
+ edges,
87
+ atlas: {
88
+ lanes: Object.fromEntries(lanes.map(({ lane, box }) => [lane.id, box])),
89
+ nodes: Object.fromEntries(nodes.map(({ node, box }) => [node.id, box])),
90
+ edges: edgeBoxes,
91
+ },
92
+ };
93
+ };
94
+ //# sourceMappingURL=layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/layout/layout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { Edge, ConduitNode } from "../schema/document.js";
2
+ /**
3
+ * Layer index per node: how far down the diagram it sits.
4
+ *
5
+ * Longest path from a source, so an arrow always points at a node below the
6
+ * one it left. Real dependency graphs contain cycles, and a cycle has no
7
+ * such ordering, so the edges that close one are dropped first — the arrow
8
+ * still gets drawn, it just runs back up the page.
9
+ *
10
+ * `hints` are explicit rows, applied as a floor rather than an answer: a hint
11
+ * can push a node further down, never above something that feeds it.
12
+ */
13
+ export declare const rankNodes: (nodes: readonly ConduitNode[], edges: readonly Edge[], hints: Readonly<Record<string, number>>) => Map<string, number>;
14
+ //# sourceMappingURL=rank.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rank.d.ts","sourceRoot":"","sources":["../../src/layout/rank.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAE/D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,SAAS,UACb,SAAS,WAAW,EAAE,SACtB,SAAS,IAAI,EAAE,SACf,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KACtC,GAAG,CAAC,MAAM,EAAE,MAAM,CA6BpB,CAAC"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Layer index per node: how far down the diagram it sits.
3
+ *
4
+ * Longest path from a source, so an arrow always points at a node below the
5
+ * one it left. Real dependency graphs contain cycles, and a cycle has no
6
+ * such ordering, so the edges that close one are dropped first — the arrow
7
+ * still gets drawn, it just runs back up the page.
8
+ *
9
+ * `hints` are explicit rows, applied as a floor rather than an answer: a hint
10
+ * can push a node further down, never above something that feeds it.
11
+ */
12
+ export const rankNodes = (nodes, edges, hints) => {
13
+ const forward = new Map();
14
+ const backward = new Map();
15
+ for (const node of nodes) {
16
+ forward.set(node.id, []);
17
+ backward.set(node.id, []);
18
+ }
19
+ const withinGraph = edges.filter((edge) => edge.from !== edge.to && forward.has(edge.from) && forward.has(edge.to));
20
+ for (const edge of withinGraph)
21
+ forward.get(edge.from)?.push(edge.to);
22
+ for (const [from, to] of forwardEdgesWithoutCycles(nodes, forward))
23
+ backward.get(to)?.push(from);
24
+ const ranks = new Map();
25
+ const settle = (id) => {
26
+ const known = ranks.get(id);
27
+ if (known !== undefined)
28
+ return known;
29
+ let rank = hints[id] ?? 0;
30
+ for (const predecessor of backward.get(id) ?? [])
31
+ rank = Math.max(rank, settle(predecessor) + 1);
32
+ ranks.set(id, rank);
33
+ return rank;
34
+ };
35
+ for (const node of nodes)
36
+ settle(node.id);
37
+ return ranks;
38
+ };
39
+ /**
40
+ * Depth-first from every node in document order, dropping any edge that
41
+ * reaches a node still open on the stack. Which edge closes a cycle depends
42
+ * on where the walk started, so the walk order is fixed by the document
43
+ * rather than by iteration order of a map.
44
+ */
45
+ const forwardEdgesWithoutCycles = (nodes, forward) => {
46
+ const kept = [];
47
+ const open = new Set();
48
+ const closed = new Set();
49
+ for (const root of nodes) {
50
+ if (closed.has(root.id))
51
+ continue;
52
+ const stack = [{ id: root.id, next: 0 }];
53
+ open.add(root.id);
54
+ while (stack.length > 0) {
55
+ const frame = stack[stack.length - 1];
56
+ if (frame === undefined)
57
+ break;
58
+ const successors = forward.get(frame.id) ?? [];
59
+ if (frame.next >= successors.length) {
60
+ open.delete(frame.id);
61
+ closed.add(frame.id);
62
+ stack.pop();
63
+ continue;
64
+ }
65
+ const successor = successors[frame.next];
66
+ frame.next += 1;
67
+ if (successor === undefined || open.has(successor))
68
+ continue;
69
+ kept.push([frame.id, successor]);
70
+ if (closed.has(successor))
71
+ continue;
72
+ open.add(successor);
73
+ stack.push({ id: successor, next: 0 });
74
+ }
75
+ }
76
+ return kept;
77
+ };
78
+ //# sourceMappingURL=rank.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rank.js","sourceRoot":"","sources":["../../src/layout/rank.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { Edge, Lane, ConduitDocument, ConduitNode, View, ViewScope } from "../schema/document.js";
2
+ /** The slice of a document one SVG draws. */
3
+ export type ScopedGraph = {
4
+ lanes: Lane[];
5
+ nodes: ConduitNode[];
6
+ edges: Edge[];
7
+ };
8
+ export declare const findView: (views: readonly View[], id: string) => View | undefined;
9
+ export declare const flattenViews: (views: readonly View[]) => View[];
10
+ /**
11
+ * Turns a view's scope into the elements to draw.
12
+ *
13
+ * A selection pulls in what its members need to make sense: the lanes its
14
+ * nodes sit in, and both ends of every edge it names, since half an arrow is
15
+ * worse than none. Edges are the one place the author keeps full control —
16
+ * name any and exactly those are drawn, name none and the connections between
17
+ * the selected nodes come along.
18
+ */
19
+ export declare const resolveScope: (doc: ConduitDocument, scope: ViewScope) => ScopedGraph;
20
+ //# sourceMappingURL=scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../src/layout/scope.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,IAAI,EACJ,IAAI,EACJ,eAAe,EACf,WAAW,EACX,IAAI,EACJ,SAAS,EACV,MAAM,uBAAuB,CAAC;AAG/B,6CAA6C;AAC7C,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,KAAK,EAAE,IAAI,EAAE,CAAC;CACf,CAAC;AAEF,eAAO,MAAM,QAAQ,UAAW,SAAS,IAAI,EAAE,MAAM,MAAM,KAAG,IAAI,GAAG,SAOpE,CAAC;AAEF,eAAO,MAAM,YAAY,UAAW,SAAS,IAAI,EAAE,KAAG,IAAI,EACO,CAAC;AAElE;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,QAAS,eAAe,SAAS,SAAS,KAAG,WA8BrE,CAAC"}
@@ -0,0 +1,52 @@
1
+ import { assertNever } from "../assert.js";
2
+ export const findView = (views, id) => {
3
+ for (const view of views) {
4
+ if (view.id === id)
5
+ return view;
6
+ const nested = findView(view.children, id);
7
+ if (nested)
8
+ return nested;
9
+ }
10
+ return undefined;
11
+ };
12
+ export const flattenViews = (views) => views.flatMap((view) => [view, ...flattenViews(view.children)]);
13
+ /**
14
+ * Turns a view's scope into the elements to draw.
15
+ *
16
+ * A selection pulls in what its members need to make sense: the lanes its
17
+ * nodes sit in, and both ends of every edge it names, since half an arrow is
18
+ * worse than none. Edges are the one place the author keeps full control —
19
+ * name any and exactly those are drawn, name none and the connections between
20
+ * the selected nodes come along.
21
+ */
22
+ export const resolveScope = (doc, scope) => {
23
+ switch (scope.kind) {
24
+ case "all":
25
+ return { lanes: [...doc.lanes], nodes: [...doc.nodes], edges: [...doc.edges] };
26
+ case "selection": {
27
+ const selectedLanes = new Set(scope.lanes);
28
+ const selectedEdges = new Set(scope.edges);
29
+ const nodeIds = new Set(scope.nodes);
30
+ for (const node of doc.nodes)
31
+ if (selectedLanes.has(node.lane))
32
+ nodeIds.add(node.id);
33
+ for (const edge of doc.edges) {
34
+ if (!selectedEdges.has(edge.id))
35
+ continue;
36
+ nodeIds.add(edge.from);
37
+ nodeIds.add(edge.to);
38
+ }
39
+ const nodes = doc.nodes.filter((node) => nodeIds.has(node.id));
40
+ const edges = scope.edges.length > 0
41
+ ? doc.edges.filter((edge) => selectedEdges.has(edge.id))
42
+ : doc.edges.filter((edge) => nodeIds.has(edge.from) && nodeIds.has(edge.to));
43
+ const laneIds = new Set(scope.lanes);
44
+ for (const node of nodes)
45
+ laneIds.add(node.lane);
46
+ return { lanes: doc.lanes.filter((lane) => laneIds.has(lane.id)), nodes, edges };
47
+ }
48
+ default:
49
+ return assertNever(scope, "Unhandled view scope");
50
+ }
51
+ };
52
+ //# sourceMappingURL=scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.js","sourceRoot":"","sources":["../../src/layout/scope.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAS3C,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAsB,EAAE,EAAU,EAAoB,EAAE;IAC/E,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,EAAE,KAAK,EAAE;YAAE,OAAO,IAAI,CAAC;QAChC,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAC3C,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;IAC5B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,KAAsB,EAAU,EAAE,CAC7D,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AAElE;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAAoB,EAAE,KAAgB,EAAe,EAAE;IAClF,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,KAAK;YACR,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACjF,KAAK,WAAW,EAAE,CAAC;YACjB,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAC3C,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YAE3C,MAAM,OAAO,GAAG,IAAI,GAAG,CAAS,KAAK,CAAC,KAAK,CAAC,CAAC;YAC7C,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK;gBAAE,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;oBAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACrF,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;gBAC7B,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;oBAAE,SAAS;gBAC1C,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACvB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACvB,CAAC;YAED,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YAC/D,MAAM,KAAK,GACT,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;gBACpB,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACxD,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;YAEjF,MAAM,OAAO,GAAG,IAAI,GAAG,CAAS,KAAK,CAAC,KAAK,CAAC,CAAC;YAC7C,KAAK,MAAM,IAAI,IAAI,KAAK;gBAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAEjD,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;QACnF,CAAC;QACD;YACE,OAAO,WAAW,CAAC,KAAK,EAAE,sBAAsB,CAAC,CAAC;IACtD,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { Edge, Lane, ConduitNode } from "../schema/document.js";
2
+ /** One occupied row of one lane: a single card, or two side by side. */
3
+ export type SeatedRow = {
4
+ grid: number;
5
+ nodes: ConduitNode[];
6
+ };
7
+ export type Seating = {
8
+ rowsByLane: Map<string, SeatedRow[]>;
9
+ rowCount: number;
10
+ };
11
+ /**
12
+ * Where every card sits: a row grid shared across all lanes.
13
+ *
14
+ * A node with an explicit `row` claims that grid row outright. Everything
15
+ * else is ranked by its connections — longest path from a source — so an
16
+ * arrow always points at a node below the one it left, and then falls to
17
+ * the next row its lane has not already claimed. Seating reads connections,
18
+ * explicit rows and document order, never a label, so a rename moves nothing.
19
+ *
20
+ * When no row is explicit, the ranks in use are compressed onto contiguous
21
+ * rows, so a lane that enters the story late does not leave a hole above it.
22
+ * Once any row is explicit the grid is absolute — an automatic node sits at
23
+ * its rank — because a compressed row could land above the pinned card that
24
+ * feeds it, and an arrow running back up the page is worse than a gap.
25
+ */
26
+ export declare const seatNodes: (orderedLanes: readonly Lane[], nodes: readonly ConduitNode[], edges: readonly Edge[]) => Seating;
27
+ //# sourceMappingURL=seating.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seating.d.ts","sourceRoot":"","sources":["../../src/layout/seating.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAIrE,wEAAwE;AACxE,MAAM,MAAM,SAAS,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,EAAE,CAAA;CAAE,CAAC;AAE/D,MAAM,MAAM,OAAO,GAAG;IACpB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IACrC,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,SAAS,iBACN,SAAS,IAAI,EAAE,SACtB,SAAS,WAAW,EAAE,SACtB,SAAS,IAAI,EAAE,KACrB,OAoCF,CAAC"}
@@ -0,0 +1,135 @@
1
+ import { ConduitLayoutError } from "./errors.js";
2
+ import { rankNodes } from "./rank.js";
3
+ /**
4
+ * Where every card sits: a row grid shared across all lanes.
5
+ *
6
+ * A node with an explicit `row` claims that grid row outright. Everything
7
+ * else is ranked by its connections — longest path from a source — so an
8
+ * arrow always points at a node below the one it left, and then falls to
9
+ * the next row its lane has not already claimed. Seating reads connections,
10
+ * explicit rows and document order, never a label, so a rename moves nothing.
11
+ *
12
+ * When no row is explicit, the ranks in use are compressed onto contiguous
13
+ * rows, so a lane that enters the story late does not leave a hole above it.
14
+ * Once any row is explicit the grid is absolute — an automatic node sits at
15
+ * its rank — because a compressed row could land above the pinned card that
16
+ * feeds it, and an arrow running back up the page is worse than a gap.
17
+ */
18
+ export const seatNodes = (orderedLanes, nodes, edges) => {
19
+ const laneIndex = new Map(orderedLanes.map((lane, index) => [lane.id, index]));
20
+ const docIndex = new Map(nodes.map((node, index) => [node.id, index]));
21
+ const pinnedRows = {};
22
+ for (const node of nodes)
23
+ if (node.row !== undefined)
24
+ pinnedRows[node.id] = node.row;
25
+ const anyPinned = Object.keys(pinnedRows).length > 0;
26
+ const ranks = rankNodes(nodes, edges, pinnedRows);
27
+ const rowOfRank = anyPinned ? identityRows(ranks) : compressRanks(nodes, ranks);
28
+ const keys = barycenters(nodes, edges, ranks, laneIndex);
29
+ const rowsByLane = new Map();
30
+ let lastRow = -1;
31
+ for (const lane of orderedLanes) {
32
+ const members = nodes.filter((node) => node.lane === lane.id);
33
+ if (members.length === 0)
34
+ continue;
35
+ const pinned = pinRows(members.filter((node) => node.row !== undefined));
36
+ const claimed = new Set(pinned.map((row) => row.grid));
37
+ const automatic = seatLane(members.filter((node) => node.row === undefined), ranks, rowOfRank, keys, docIndex, claimed);
38
+ const rows = [...pinned, ...automatic].sort((a, b) => a.grid - b.grid);
39
+ rowsByLane.set(lane.id, rows);
40
+ lastRow = Math.max(lastRow, rows[rows.length - 1]?.grid ?? -1);
41
+ }
42
+ return { rowsByLane, rowCount: lastRow + 1 };
43
+ };
44
+ /** Rank values in use, in order, mapped onto contiguous rows. */
45
+ const compressRanks = (nodes, ranks) => {
46
+ const used = [...new Set(nodes.map((node) => ranks.get(node.id) ?? 0))].sort((a, b) => a - b);
47
+ return new Map(used.map((rank, index) => [rank, index]));
48
+ };
49
+ const identityRows = (ranks) => new Map([...new Set(ranks.values())].map((rank) => [rank, rank]));
50
+ /** Explicit rows, grouped: a row holds at most two cards, in array order. */
51
+ const pinRows = (pinned) => {
52
+ const byRow = new Map();
53
+ for (const node of pinned) {
54
+ const grid = node.row ?? 0;
55
+ const list = byRow.get(grid) ?? [];
56
+ list.push(node);
57
+ byRow.set(grid, list);
58
+ }
59
+ return [...byRow.entries()]
60
+ .sort(([a], [b]) => a - b)
61
+ .map(([grid, members]) => {
62
+ if (members.length > 2)
63
+ throw new ConduitLayoutError("ROW_OVERFULL", `lane '${members[0]?.lane}' row ${grid} holds ${members.length} nodes; a row holds at most two`);
64
+ return { grid, nodes: members };
65
+ });
66
+ };
67
+ const barycenters = (nodes, edges, ranks, laneIndex) => {
68
+ const laneOf = new Map(nodes.map((node) => [node.id, laneIndex.get(node.lane) ?? 0]));
69
+ const partners = new Map(nodes.map((node) => [node.id, []]));
70
+ for (const edge of edges) {
71
+ if (edge.from === edge.to || !laneOf.has(edge.from) || !laneOf.has(edge.to))
72
+ continue;
73
+ partners.get(edge.from)?.push(edge.to);
74
+ partners.get(edge.to)?.push(edge.from);
75
+ }
76
+ const fall = new Map();
77
+ const side = new Map();
78
+ for (const node of nodes) {
79
+ const linked = partners.get(node.id) ?? [];
80
+ fall.set(node.id, mean(linked.map((id) => ranks.get(id) ?? 0)) ?? ranks.get(node.id) ?? 0);
81
+ side.set(node.id, mean(linked.map((id) => laneOf.get(id) ?? 0)) ?? laneOf.get(node.id) ?? 0);
82
+ }
83
+ return { fall, side };
84
+ };
85
+ const mean = (values) => values.length === 0 ? undefined : values.reduce((sum, value) => sum + value, 0) / values.length;
86
+ /**
87
+ * One lane's automatic members onto the shared grid.
88
+ *
89
+ * A card sits at its rank's row unless an earlier card of the same lane
90
+ * already claimed it — by rank or by an explicit row — in which case it falls
91
+ * to the next free one. Two cards share a row only at the same rank in the
92
+ * same sub-group. Who falls, and who sits left in a pair, is decided by where
93
+ * each card's partners are, with document order as the stable tiebreak.
94
+ */
95
+ const seatLane = (members, ranks, rowOfRank, keys, docIndex, claimed) => {
96
+ const doc = (node) => docIndex.get(node.id) ?? 0;
97
+ const sorted = [...members].sort((a, b) => {
98
+ const row = (rowOfRank.get(ranks.get(a.id) ?? 0) ?? 0) - (rowOfRank.get(ranks.get(b.id) ?? 0) ?? 0);
99
+ if (row !== 0)
100
+ return row;
101
+ const fall = (keys.fall.get(a.id) ?? 0) - (keys.fall.get(b.id) ?? 0);
102
+ if (fall !== 0)
103
+ return fall;
104
+ return doc(a) - doc(b);
105
+ });
106
+ const rows = [];
107
+ let previous = -1;
108
+ for (const node of sorted) {
109
+ const rank = ranks.get(node.id) ?? 0;
110
+ const open = rows[rows.length - 1];
111
+ if (open !== undefined &&
112
+ open.nodes.length === 1 &&
113
+ open.rank === rank &&
114
+ (open.nodes[0]?.group ?? "") === (node.group ?? "")) {
115
+ const first = open.nodes[0];
116
+ if (first !== undefined && leansLeft(node, first, keys, doc))
117
+ open.nodes.unshift(node);
118
+ else
119
+ open.nodes.push(node);
120
+ continue;
121
+ }
122
+ previous = Math.max(previous + 1, rowOfRank.get(rank) ?? 0);
123
+ while (claimed.has(previous))
124
+ previous += 1;
125
+ rows.push({ grid: previous, rank, nodes: [node] });
126
+ }
127
+ return rows.map(({ grid, nodes }) => ({ grid, nodes }));
128
+ };
129
+ const leansLeft = (node, other, keys, doc) => {
130
+ const side = (keys.side.get(node.id) ?? 0) - (keys.side.get(other.id) ?? 0);
131
+ if (side !== 0)
132
+ return side < 0;
133
+ return doc(node) < doc(other);
134
+ };
135
+ //# sourceMappingURL=seating.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seating.js","sourceRoot":"","sources":["../../src/layout/seating.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAUtC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,YAA6B,EAC7B,KAA6B,EAC7B,KAAsB,EACb,EAAE;IACX,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC/E,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAEvE,MAAM,UAAU,GAA2B,EAAE,CAAC;IAC9C,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS;YAAE,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC;IACrF,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;IAErD,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAChF,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;IAEzD,MAAM,UAAU,GAAG,IAAI,GAAG,EAAuB,CAAC;IAClD,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC;IAEjB,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;QAC9D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QAEnC,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC;QACzE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC;QACvD,MAAM,SAAS,GAAG,QAAQ,CACxB,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,KAAK,SAAS,CAAC,EAChD,KAAK,EACL,SAAS,EACT,IAAI,EACJ,QAAQ,EACR,OAAO,CACR,CAAC;QAEF,MAAM,IAAI,GAAG,CAAC,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC;QACvE,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;QAC9B,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;IACjE,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,GAAG,CAAC,EAAE,CAAC;AAC/C,CAAC,CAAC;AAEF,iEAAiE;AACjE,MAAM,aAAa,GAAG,CACpB,KAA6B,EAC7B,KAAkC,EACb,EAAE;IACvB,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9F,OAAO,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;AAC3D,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,CAAC,KAAkC,EAAuB,EAAE,CAC/E,IAAI,GAAG,CAAC,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;AAEpE,6EAA6E;AAC7E,MAAM,OAAO,GAAG,CAAC,MAA8B,EAAe,EAAE;IAC9D,MAAM,KAAK,GAAG,IAAI,GAAG,EAAyB,CAAC;IAC/C,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QAC3B,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACnC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChB,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACxB,CAAC;IAED,OAAO,CAAC,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;SACxB,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;SACzB,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,EAAE;QACvB,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YACpB,MAAM,IAAI,kBAAkB,CAC1B,cAAc,EACd,SAAS,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,SAAS,IAAI,UAAU,OAAO,CAAC,MAAM,iCAAiC,CAChG,CAAC;QACJ,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IAClC,CAAC,CAAC,CAAC;AACP,CAAC,CAAC;AASF,MAAM,WAAW,GAAG,CAClB,KAA6B,EAC7B,KAAsB,EACtB,KAAkC,EAClC,SAAsC,EACzB,EAAE;IACf,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACtF,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAmB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/E,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAAE,SAAS;QACtF,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACvC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;QAC3C,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;QAC3F,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/F,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACxB,CAAC,CAAC;AAEF,MAAM,IAAI,GAAG,CAAC,MAAyB,EAAsB,EAAE,CAC7D,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,KAAK,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;AAElG;;;;;;;;GAQG;AACH,MAAM,QAAQ,GAAG,CACf,OAA+B,EAC/B,KAAkC,EAClC,SAAsC,EACtC,IAAiB,EACjB,QAAqC,EACrC,OAA4B,EACf,EAAE;IACf,MAAM,GAAG,GAAG,CAAC,IAAiB,EAAU,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;IACtE,MAAM,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACxC,MAAM,GAAG,GACP,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;QAC1F,IAAI,GAAG,KAAK,CAAC;YAAE,OAAO,GAAG,CAAC;QAC1B,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;QACrE,IAAI,IAAI,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAC5B,OAAO,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;IAEH,MAAM,IAAI,GAA2D,EAAE,CAAC;IACxE,IAAI,QAAQ,GAAG,CAAC,CAAC,CAAC;IAElB,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAEnC,IACE,IAAI,KAAK,SAAS;YAClB,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;YACvB,IAAI,CAAC,IAAI,KAAK,IAAI;YAClB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,EACnD,CAAC;YACD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC5B,IAAI,KAAK,KAAK,SAAS,IAAI,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC;gBAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;;gBAClF,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC3B,SAAS;QACX,CAAC;QAED,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5D,OAAO,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,QAAQ,IAAI,CAAC,CAAC;QAC5C,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AAC1D,CAAC,CAAC;AAEF,MAAM,SAAS,GAAG,CAChB,IAAiB,EACjB,KAAkB,EAClB,IAAiB,EACjB,GAAkC,EACzB,EAAE;IACX,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5E,IAAI,IAAI,KAAK,CAAC;QAAE,OAAO,IAAI,GAAG,CAAC,CAAC;IAChC,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC;AAChC,CAAC,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The renderer never asks a font engine how wide a string is: it must produce
3
+ * the same geometry on a CI runner with no fonts installed as on a laptop
4
+ * with all of them. Widths come from a table instead, and every box is sized
5
+ * with enough slack that the small error against a real face stays invisible.
6
+ */
7
+ export type Face = "sans" | "sans-bold" | "mono";
8
+ export declare const SANS_STACK = "-apple-system, BlinkMacSystemFont, \"Segoe UI\", \"Noto Sans\", Helvetica, Arial, sans-serif";
9
+ export declare const MONO_STACK = "ui-monospace, SFMono-Regular, \"SF Mono\", Menlo, Consolas, monospace";
10
+ /** Width of `text` in user units at `fontSize`, to a hundredth of a unit. */
11
+ export declare const measure: (text: string, face: Face, fontSize: number) => number;
12
+ //# sourceMappingURL=text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/layout/text.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;AAEjD,eAAO,MAAM,UAAU,iGACqE,CAAC;AAC7F,eAAO,MAAM,UAAU,0EAAwE,CAAC;AAsJhG,6EAA6E;AAC7E,eAAO,MAAM,OAAO,SAAU,MAAM,QAAQ,IAAI,YAAY,MAAM,KAAG,MAIpE,CAAC"}
@@ -0,0 +1,148 @@
1
+ import { assertNever } from "../assert.js";
2
+ export const SANS_STACK = '-apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif';
3
+ export const MONO_STACK = 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace';
4
+ /** Advance widths per 1000 units of em, for the regular sans face. */
5
+ const SANS_ADVANCE = {
6
+ " ": 278,
7
+ "!": 278,
8
+ '"': 355,
9
+ "#": 556,
10
+ $: 556,
11
+ "%": 889,
12
+ "&": 667,
13
+ "'": 191,
14
+ "(": 333,
15
+ ")": 333,
16
+ "*": 389,
17
+ "+": 584,
18
+ ",": 278,
19
+ "-": 333,
20
+ ".": 278,
21
+ "/": 278,
22
+ "0": 556,
23
+ "1": 556,
24
+ "2": 556,
25
+ "3": 556,
26
+ "4": 556,
27
+ "5": 556,
28
+ "6": 556,
29
+ "7": 556,
30
+ "8": 556,
31
+ "9": 556,
32
+ ":": 278,
33
+ ";": 278,
34
+ "<": 584,
35
+ "=": 584,
36
+ ">": 584,
37
+ "?": 556,
38
+ "@": 1015,
39
+ A: 667,
40
+ B: 667,
41
+ C: 722,
42
+ D: 722,
43
+ E: 667,
44
+ F: 611,
45
+ G: 778,
46
+ H: 722,
47
+ I: 278,
48
+ J: 500,
49
+ K: 667,
50
+ L: 556,
51
+ M: 833,
52
+ N: 722,
53
+ O: 778,
54
+ P: 667,
55
+ Q: 778,
56
+ R: 722,
57
+ S: 667,
58
+ T: 611,
59
+ U: 722,
60
+ V: 667,
61
+ W: 944,
62
+ X: 667,
63
+ Y: 667,
64
+ Z: 611,
65
+ "[": 278,
66
+ "\\": 278,
67
+ "]": 278,
68
+ "^": 469,
69
+ _: 556,
70
+ "`": 333,
71
+ a: 556,
72
+ b: 556,
73
+ c: 500,
74
+ d: 556,
75
+ e: 556,
76
+ f: 278,
77
+ g: 556,
78
+ h: 556,
79
+ i: 222,
80
+ j: 222,
81
+ k: 500,
82
+ l: 222,
83
+ m: 833,
84
+ n: 556,
85
+ o: 556,
86
+ p: 556,
87
+ q: 556,
88
+ r: 333,
89
+ s: 500,
90
+ t: 278,
91
+ u: 556,
92
+ v: 500,
93
+ w: 722,
94
+ x: 500,
95
+ y: 500,
96
+ z: 500,
97
+ "{": 334,
98
+ "|": 260,
99
+ "}": 334,
100
+ "~": 584,
101
+ };
102
+ /** What a narrow character outside the table is assumed to cost, in the same units. */
103
+ const FALLBACK_ADVANCE = 600;
104
+ /**
105
+ * East Asian Wide and Fullwidth glyphs occupy a full em. Measuring them at
106
+ * the narrow fallback puts every CJK label about 38% short of its real width,
107
+ * which is enough for an edge label's plate to stop covering its own text.
108
+ */
109
+ const isWide = (character) => {
110
+ const point = character.codePointAt(0) ?? 0;
111
+ return ((point >= 0x1100 && point <= 0x115f) ||
112
+ (point >= 0x2e80 && point <= 0xa4cf) ||
113
+ (point >= 0xac00 && point <= 0xd7a3) ||
114
+ (point >= 0xf900 && point <= 0xfaff) ||
115
+ (point >= 0xfe30 && point <= 0xfe6f) ||
116
+ (point >= 0xff00 && point <= 0xff60) ||
117
+ (point >= 0xffe0 && point <= 0xffe6) ||
118
+ (point >= 0x20000 && point <= 0x3fffd));
119
+ };
120
+ const FULL_WIDTH_ADVANCE = 1000;
121
+ const fallbackAdvance = (character) => isWide(character) ? FULL_WIDTH_ADVANCE : FALLBACK_ADVANCE;
122
+ /**
123
+ * The bold face of the same family runs a little wider at every weight step.
124
+ * One factor over the regular table is close enough for boxes that already
125
+ * carry padding, and it keeps a second table from drifting out of sync.
126
+ */
127
+ const BOLD_WIDENING = 1.06;
128
+ const MONO_ADVANCE = 600;
129
+ const advanceFor = (face, character) => {
130
+ switch (face) {
131
+ case "sans":
132
+ return SANS_ADVANCE[character] ?? fallbackAdvance(character);
133
+ case "sans-bold":
134
+ return (SANS_ADVANCE[character] ?? fallbackAdvance(character)) * BOLD_WIDENING;
135
+ case "mono":
136
+ return MONO_ADVANCE;
137
+ default:
138
+ return assertNever(face, "Unhandled font face");
139
+ }
140
+ };
141
+ /** Width of `text` in user units at `fontSize`, to a hundredth of a unit. */
142
+ export const measure = (text, face, fontSize) => {
143
+ let thousandths = 0;
144
+ for (const character of text)
145
+ thousandths += advanceFor(face, character);
146
+ return Math.round((thousandths / 1000) * fontSize * 100) / 100;
147
+ };
148
+ //# sourceMappingURL=text.js.map