@knowvah/dot-engine 1.3.0 → 1.4.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.4.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
  /**
@@ -237,10 +261,33 @@ function placedLabelPos(
237
261
  return { x: lbl.pos.x, y: flipY(lbl.pos.y) };
238
262
  }
239
263
 
264
+ /**
265
+ * Arrow attachment points, one per end that actually carries an arrow.
266
+ * C keeps `sp`/`ep` beside the control points and gates them on
267
+ * `sflag`/`eflag`; with no arrow at that end the flag is 0 and the point is
268
+ * still calloc-zero, so an ungated read would publish (0, 0) as geometry.
269
+ * Indexes the bezier array by `spl.size` (not `list.length`), matching C.
270
+ * @see lib/common/types.h:bezier
271
+ * @see lib/common/postproc.c:endPoints
272
+ */
273
+ function arrowAttachPoints(
274
+ edge: Edge,
275
+ flipY: (y: number) => number,
276
+ ): { sp?: { x: number; y: number }; ep?: { x: number; y: number } } {
277
+ const spl = edge.info.spl;
278
+ if (spl === undefined || spl.size === 0) return {};
279
+ const out: { sp?: { x: number; y: number }; ep?: { x: number; y: number } } = {};
280
+ const first = spl.list[0];
281
+ if (first.sflag !== 0) out.sp = { x: first.sp.x, y: flipY(first.sp.y) };
282
+ const last = spl.list[spl.size - 1];
283
+ if (last.eflag !== 0) out.ep = { x: last.ep.x, y: flipY(last.ep.y) };
284
+ return out;
285
+ }
286
+
240
287
  /**
241
288
  * Snapshot one edge's geometry.
242
289
  * @see lib/common/types.h:ED_spl, ED_label, ED_tail_label, ED_head_label
243
- * (textlabel_t.pos)
290
+ * (textlabel_t.pos), bezier.sp/ep
244
291
  */
245
292
  function snapshotEdge(edge: Edge, flipY: (y: number) => number): EdgeGeometry {
246
293
  const geom: EdgeGeometry = {
@@ -256,6 +303,9 @@ function snapshotEdge(edge: Edge, flipY: (y: number) => number): EdgeGeometry {
256
303
  if (tailLabel !== undefined) geom.tailLabel = tailLabel;
257
304
  const headLabel = placedLabelPos(edge.info.head_label, flipY);
258
305
  if (headLabel !== undefined) geom.headLabel = headLabel;
306
+ const { sp, ep } = arrowAttachPoints(edge, flipY);
307
+ if (sp !== undefined) geom.sp = sp;
308
+ if (ep !== undefined) geom.ep = ep;
259
309
  return geom;
260
310
  }
261
311