@knowvah/dot-engine 1.3.0 → 1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knowvah/dot-engine",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "Faithful TypeScript port of Graphviz (dot, neato, fdp, sfdp, circo, twopi, osage, patchwork) — no C: no native binary, no WASM. Oracle-verified against the C implementation, runs in the browser.",
5
5
  "keywords": [
6
6
  "graphviz",
@@ -106,13 +106,37 @@ export interface NodeGeometry {
106
106
  * type) still holds calloc-zero coordinates, so it is reported as absent
107
107
  * rather than as a label at the origin.
108
108
  *
109
+ * `sp`/`ep` are the arrow attachment points. When an end carries an arrow the
110
+ * spline is shortened to leave room for it, and the arrow spans from the
111
+ * terminal control point out to this point — so a consumer drawing its own
112
+ * arrowheads reads the tip here instead of extrapolating one. Each is present
113
+ * only when that end actually has an arrow (C `sflag`/`eflag`); with no arrow
114
+ * the field holds the calloc-zero point, which is not geometry.
115
+ *
116
+ * These are the *attachment* points on the node boundary, verbatim from the
117
+ * bezier — with `arrowhead=none` the spline simply ends there. Graphviz's own
118
+ * renderer insets the arrow polygon it draws by a penwidth-dependent amount
119
+ * (measured: ~1.5pt at `penwidth=1`, ~6.2pt at `penwidth=5`), so `ep` is the
120
+ * point to draw an arrow *to*, not a copy of the rendered polygon's tip.
121
+ *
109
122
  * @see lib/common/types.h:ED_spl, ED_label, ED_tail_label, ED_head_label
123
+ * @see lib/common/types.h:bezier (sflag/eflag, sp/ep)
110
124
  */
111
125
  export interface EdgeGeometry {
112
126
  tail: string;
113
127
  head: string;
114
128
  /** Bezier control points for the edge spline, in points. */
115
129
  points: { x: number; y: number }[];
130
+ /**
131
+ * Arrow attachment point at the tail end, if that end carries an arrow.
132
+ * @see lib/common/types.h:bezier.sp
133
+ */
134
+ sp?: { x: number; y: number };
135
+ /**
136
+ * Arrow attachment point at the head end, if that end carries an arrow.
137
+ * @see lib/common/types.h:bezier.ep
138
+ */
139
+ ep?: { x: number; y: number };
116
140
  /** Centre edge label position, if present. @see lib/common/types.h:ED_label */
117
141
  label?: { x: number; y: number };
118
142
  /**
@@ -140,7 +164,15 @@ export interface EdgeGeometry {
140
164
  * `class="cluster"` polygon, so a consumer quantizing to SVG precision gets
141
165
  * byte-conformant geometry.
142
166
  *
167
+ * `label` is the cluster title's placed position and measured size, present
168
+ * only when the cluster declares a label. Unlike the edge port labels, it is
169
+ * *not* gated on the label's `set` flag: C draws a cluster label on existence
170
+ * alone (emit.c:3920 has no `->set` test, unlike emit_edge_label:2891), so an
171
+ * unplaced one still renders at whatever pos it holds. Gating here would hide
172
+ * geometry `render()` acts on.
173
+ *
143
174
  * @see lib/common/types.h:GD_bb (of a cluster subgraph)
175
+ * @see lib/common/types.h:GD_label (textlabel_t.pos / .dimen)
144
176
  */
145
177
  export interface ClusterGeometry {
146
178
  /** Cluster subgraph name (e.g. `cluster6`); encodes nesting. */
@@ -149,6 +181,13 @@ export interface ClusterGeometry {
149
181
  y: number;
150
182
  width: number;
151
183
  height: number;
184
+ /**
185
+ * Cluster label placement, if the cluster has one. `x`/`y` are the **centre**
186
+ * of the label space (matching `EdgeGeometry.label`, not the box corner
187
+ * `x`/`y` above); `width`/`height` are its measured size.
188
+ * @see lib/common/postproc.c:place_graph_label
189
+ */
190
+ label?: { x: number; y: number; width: number; height: number };
152
191
  }
153
192
 
154
193
  /**
@@ -237,10 +276,33 @@ function placedLabelPos(
237
276
  return { x: lbl.pos.x, y: flipY(lbl.pos.y) };
238
277
  }
239
278
 
279
+ /**
280
+ * Arrow attachment points, one per end that actually carries an arrow.
281
+ * C keeps `sp`/`ep` beside the control points and gates them on
282
+ * `sflag`/`eflag`; with no arrow at that end the flag is 0 and the point is
283
+ * still calloc-zero, so an ungated read would publish (0, 0) as geometry.
284
+ * Indexes the bezier array by `spl.size` (not `list.length`), matching C.
285
+ * @see lib/common/types.h:bezier
286
+ * @see lib/common/postproc.c:endPoints
287
+ */
288
+ function arrowAttachPoints(
289
+ edge: Edge,
290
+ flipY: (y: number) => number,
291
+ ): { sp?: { x: number; y: number }; ep?: { x: number; y: number } } {
292
+ const spl = edge.info.spl;
293
+ if (spl === undefined || spl.size === 0) return {};
294
+ const out: { sp?: { x: number; y: number }; ep?: { x: number; y: number } } = {};
295
+ const first = spl.list[0];
296
+ if (first.sflag !== 0) out.sp = { x: first.sp.x, y: flipY(first.sp.y) };
297
+ const last = spl.list[spl.size - 1];
298
+ if (last.eflag !== 0) out.ep = { x: last.ep.x, y: flipY(last.ep.y) };
299
+ return out;
300
+ }
301
+
240
302
  /**
241
303
  * Snapshot one edge's geometry.
242
304
  * @see lib/common/types.h:ED_spl, ED_label, ED_tail_label, ED_head_label
243
- * (textlabel_t.pos)
305
+ * (textlabel_t.pos), bezier.sp/ep
244
306
  */
245
307
  function snapshotEdge(edge: Edge, flipY: (y: number) => number): EdgeGeometry {
246
308
  const geom: EdgeGeometry = {
@@ -256,6 +318,9 @@ function snapshotEdge(edge: Edge, flipY: (y: number) => number): EdgeGeometry {
256
318
  if (tailLabel !== undefined) geom.tailLabel = tailLabel;
257
319
  const headLabel = placedLabelPos(edge.info.head_label, flipY);
258
320
  if (headLabel !== undefined) geom.headLabel = headLabel;
321
+ const { sp, ep } = arrowAttachPoints(edge, flipY);
322
+ if (sp !== undefined) geom.sp = sp;
323
+ if (ep !== undefined) geom.ep = ep;
259
324
  return geom;
260
325
  }
261
326
 
@@ -266,13 +331,18 @@ function snapshotEdge(edge: Edge, flipY: (y: number) => number): EdgeGeometry {
266
331
  * native lower-left corner (ll); `yAxis:'down'` returns the top-left corner
267
332
  * (ll.x, flipped ur.y). `width`/`height` are frame-independent (ur - ll).
268
333
  *
334
+ * The label rides in the same call because place_graph_label runs on the same
335
+ * layout pass that fills GD_bb; `pos` is the centre of the label space, so it
336
+ * flips like any other coordinate rather than like the box corner.
337
+ *
269
338
  * @see lib/common/types.h:GD_bb (cluster subgraph)
339
+ * @see lib/common/postproc.c:place_graph_label
270
340
  */
271
341
  function snapshotCluster(
272
342
  sg: Graph, yAxis: YAxis, flipY: (y: number) => number,
273
343
  ): ClusterGeometry {
274
344
  const bb = sg.info.bb;
275
- return {
345
+ const geom: ClusterGeometry = {
276
346
  name: sg.name,
277
347
  x: bb.ll.x,
278
348
  // 'up' keeps the lower-left y; 'down' flips the upper-right y to the
@@ -281,6 +351,16 @@ function snapshotCluster(
281
351
  width: bb.ur.x - bb.ll.x,
282
352
  height: bb.ur.y - bb.ll.y,
283
353
  };
354
+ const lab = sg.info.label as TextlabelT | undefined;
355
+ if (lab !== undefined) {
356
+ geom.label = {
357
+ x: lab.pos.x,
358
+ y: flipY(lab.pos.y),
359
+ width: lab.dimen.x,
360
+ height: lab.dimen.y,
361
+ };
362
+ }
363
+ return geom;
284
364
  }
285
365
 
286
366
  /**