@knowvah/dot-engine 2.6.4 → 2.6.5

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 (88) hide show
  1. package/dist/common/arrows-shapes-poly.d.ts.map +1 -1
  2. package/dist/common/bezier.d.ts +12 -0
  3. package/dist/common/bezier.d.ts.map +1 -0
  4. package/dist/common/htmltable-attr-warn.d.ts +41 -0
  5. package/dist/common/htmltable-attr-warn.d.ts.map +1 -0
  6. package/dist/common/htmltable-fontinfo.d.ts +42 -0
  7. package/dist/common/htmltable-fontinfo.d.ts.map +1 -0
  8. package/dist/common/htmltable-lex-expat.d.ts +63 -0
  9. package/dist/common/htmltable-lex-expat.d.ts.map +1 -0
  10. package/dist/common/htmltable-lex-units.d.ts +32 -0
  11. package/dist/common/htmltable-lex-units.d.ts.map +1 -0
  12. package/dist/common/htmltable-lex.d.ts +10 -2
  13. package/dist/common/htmltable-lex.d.ts.map +1 -1
  14. package/dist/common/htmltable-parse-attrs.d.ts +7 -5
  15. package/dist/common/htmltable-parse-attrs.d.ts.map +1 -1
  16. package/dist/common/htmltable-parse-font.d.ts +33 -0
  17. package/dist/common/htmltable-parse-font.d.ts.map +1 -0
  18. package/dist/common/htmltable-parse-state.d.ts +39 -0
  19. package/dist/common/htmltable-parse-state.d.ts.map +1 -0
  20. package/dist/common/htmltable-parse.d.ts +18 -23
  21. package/dist/common/htmltable-parse.d.ts.map +1 -1
  22. package/dist/common/htmltable-pos.d.ts.map +1 -1
  23. package/dist/common/htmltable-types.d.ts +33 -3
  24. package/dist/common/htmltable-types.d.ts.map +1 -1
  25. package/dist/common/htmltable.d.ts +3 -2
  26. package/dist/common/htmltable.d.ts.map +1 -1
  27. package/dist/common/spline-midpoint.d.ts.map +1 -1
  28. package/dist/common/splines-geom.d.ts +5 -6
  29. package/dist/common/splines-geom.d.ts.map +1 -1
  30. package/dist/common/splines-routespl.d.ts.map +1 -1
  31. package/dist/common/taper.d.ts.map +1 -1
  32. package/dist/gvc/anchor.d.ts.map +1 -1
  33. package/dist/index.js +1362 -647
  34. package/dist/index.js.map +4 -4
  35. package/dist/layout/dot/compound-clip.d.ts +5 -5
  36. package/dist/layout/dot/compound-clip.d.ts.map +1 -1
  37. package/dist/layout/dot/compound-geom.d.ts +2 -7
  38. package/dist/layout/dot/compound-geom.d.ts.map +1 -1
  39. package/dist/layout/dot/compound.d.ts +1 -1
  40. package/dist/layout/dot/compound.d.ts.map +1 -1
  41. package/dist/layout/dot/edge-route-clip.d.ts +1 -1
  42. package/dist/layout/dot/edge-route-clip.d.ts.map +1 -1
  43. package/dist/layout/dot/ortho-adapter.d.ts +13 -4
  44. package/dist/layout/dot/ortho-adapter.d.ts.map +1 -1
  45. package/dist/layout/dot/splines-label.d.ts.map +1 -1
  46. package/dist/layout/neato/bfs.d.ts +10 -3
  47. package/dist/layout/neato/bfs.d.ts.map +1 -1
  48. package/dist/layout/neato/init.d.ts +6 -11
  49. package/dist/layout/neato/init.d.ts.map +1 -1
  50. package/dist/layout/neato/splines.d.ts.map +1 -1
  51. package/dist/parser/index.d.ts.map +1 -1
  52. package/dist/parser/scan-token.d.ts +40 -0
  53. package/dist/parser/scan-token.d.ts.map +1 -0
  54. package/dist/render/svg-edge-split.d.ts.map +1 -1
  55. package/dist/render.js +1384 -669
  56. package/dist/render.js.map +4 -4
  57. package/package.json +1 -1
  58. package/src/common/arrows-shapes-poly.ts +9 -10
  59. package/src/common/bezier.ts +33 -0
  60. package/src/common/htmltable-attr-warn.ts +270 -0
  61. package/src/common/htmltable-fontinfo.ts +83 -0
  62. package/src/common/htmltable-lex-expat.ts +357 -0
  63. package/src/common/htmltable-lex-units.ts +75 -0
  64. package/src/common/htmltable-lex.ts +221 -160
  65. package/src/common/htmltable-parse-attrs.ts +10 -14
  66. package/src/common/htmltable-parse-font.ts +92 -0
  67. package/src/common/htmltable-parse-state.ts +64 -0
  68. package/src/common/htmltable-parse.ts +138 -98
  69. package/src/common/htmltable-pos.ts +9 -8
  70. package/src/common/htmltable-types.ts +35 -3
  71. package/src/common/htmltable.ts +14 -7
  72. package/src/common/spline-midpoint.ts +2 -22
  73. package/src/common/splines-geom.ts +5 -25
  74. package/src/common/splines-routespl.ts +21 -13
  75. package/src/common/taper.ts +2 -14
  76. package/src/gvc/anchor.ts +5 -14
  77. package/src/layout/dot/compound-clip.ts +9 -19
  78. package/src/layout/dot/compound-geom.ts +6 -23
  79. package/src/layout/dot/compound.ts +1 -1
  80. package/src/layout/dot/edge-route-clip.ts +6 -29
  81. package/src/layout/dot/ortho-adapter.ts +5 -5
  82. package/src/layout/dot/splines-label.ts +3 -29
  83. package/src/layout/neato/bfs.ts +13 -4
  84. package/src/layout/neato/init.ts +15 -15
  85. package/src/layout/neato/splines.ts +4 -8
  86. package/src/parser/index.ts +13 -1
  87. package/src/parser/scan-token.ts +289 -0
  88. package/src/render/svg-edge-split.ts +5 -13
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { Point, Box } from '../model/geom.js';
12
+ import { bezier as evalBezier } from './bezier.js';
12
13
 
13
14
  // ---------------------------------------------------------------------------
14
15
  // Approximate equality
@@ -29,33 +30,12 @@ export function approxEqPt(p: Point, q: Point, tol: number): boolean {
29
30
  // ---------------------------------------------------------------------------
30
31
 
31
32
  /**
32
- * Evaluates cubic Bezier sp[] at parameter t.
33
- * When left !== null, fills left[0..3] with the left subdivision.
34
- * When right !== null, fills right[0..3] with the right subdivision.
35
- * Returns the point on the curve.
33
+ * Evaluates cubic Bezier sp[] at parameter t (left/right fill as in C): the
34
+ * shared fused bezier(), re-exported under the name this module's callers use.
36
35
  *
37
- * @see lib/common/geomprocs.h:Bezier
36
+ * @see lib/common/utils.c:Bezier
38
37
  */
39
- export function evalBezier(
40
- sp: Point[],
41
- t: number,
42
- left: Point[] | null,
43
- right: Point[] | null,
44
- ): Point {
45
- const ab: Point = { x: sp[0].x + t * (sp[1].x - sp[0].x), y: sp[0].y + t * (sp[1].y - sp[0].y) };
46
- const bc: Point = { x: sp[1].x + t * (sp[2].x - sp[1].x), y: sp[1].y + t * (sp[2].y - sp[1].y) };
47
- const cd: Point = { x: sp[2].x + t * (sp[3].x - sp[2].x), y: sp[2].y + t * (sp[3].y - sp[2].y) };
48
- const abbc: Point = { x: ab.x + t * (bc.x - ab.x), y: ab.y + t * (bc.y - ab.y) };
49
- const bccd: Point = { x: bc.x + t * (cd.x - bc.x), y: bc.y + t * (cd.y - bc.y) };
50
- const pt: Point = { x: abbc.x + t * (bccd.x - abbc.x), y: abbc.y + t * (bccd.y - abbc.y) };
51
- if (left !== null) {
52
- left[0] = sp[0]; left[1] = ab; left[2] = abbc; left[3] = pt;
53
- }
54
- if (right !== null) {
55
- right[0] = pt; right[1] = bccd; right[2] = cd; right[3] = sp[3];
56
- }
57
- return pt;
58
- }
38
+ export { evalBezier };
59
39
 
60
40
  // ---------------------------------------------------------------------------
61
41
  // Bounding box update
@@ -14,6 +14,7 @@ import type { Path } from './types.js';
14
14
  import { shortestPath, routeSpline, makePolyline } from '../pathplan/index.js';
15
15
  import type { Poly } from '../pathplan/types.js';
16
16
  import { INIT_DELTA, LOOP_TRIES, ROUTESPL_FUDGE } from './splines-constants.js';
17
+ import { fma } from './fma.js';
17
18
 
18
19
  // ---------------------------------------------------------------------------
19
20
  // Stage-dump hook (diagnosis only; null in production — same pattern as
@@ -193,21 +194,28 @@ export function checkPath(boxn: number, boxes: Box[], pp: Path): boolean {
193
194
  // limitBoxes helpers
194
195
  // ---------------------------------------------------------------------------
195
196
 
196
- /** Evaluate de Casteljau subdivision to get a point on a cubic Bezier. */
197
+ /** C's inlined de Casteljau: `sp[k].x += t * (sp[k+1].x - sp[k].x)`, which clang
198
+ * contracts to fma(t, b - a, a) (oracle `_limitBoxes`: fsub + fmadd, x12). Not
199
+ * utils.c:Bezier's form, so it does not use the shared bezier().
200
+ * @see lib/common/routespl.c:limitBoxes (lines 251-262)
201
+ */
197
202
  function deCasteljauPoint(
198
203
  p0: Point, p1: Point, p2: Point, p3: Point, t: number,
199
204
  ): Point {
200
- const ax = p0.x + t * (p1.x - p0.x);
201
- const ay = p0.y + t * (p1.y - p0.y);
202
- const bx = p1.x + t * (p2.x - p1.x);
203
- const by = p1.y + t * (p2.y - p1.y);
204
- const cx = p2.x + t * (p3.x - p2.x);
205
- const cy = p2.y + t * (p3.y - p2.y);
206
- const dx = ax + t * (bx - ax);
207
- const dy = ay + t * (by - ay);
208
- const ex = bx + t * (cx - bx);
209
- const ey = by + t * (cy - by);
210
- return { x: dx + t * (ex - dx), y: dy + t * (ey - dy) };
205
+ let s0x = p0.x, s0y = p0.y, s1x = p1.x, s1y = p1.y, s2x = p2.x, s2y = p2.y;
206
+ s0x = fma(t, s1x - s0x, s0x);
207
+ s0y = fma(t, s1y - s0y, s0y);
208
+ s1x = fma(t, s2x - s1x, s1x);
209
+ s1y = fma(t, s2y - s1y, s1y);
210
+ s2x = fma(t, p3.x - s2x, s2x);
211
+ s2y = fma(t, p3.y - s2y, s2y);
212
+ s0x = fma(t, s1x - s0x, s0x);
213
+ s0y = fma(t, s1y - s0y, s0y);
214
+ s1x = fma(t, s2x - s1x, s1x);
215
+ s1y = fma(t, s2y - s1y, s1y);
216
+ s0x = fma(t, s1x - s0x, s0x);
217
+ s0y = fma(t, s1y - s0y, s0y);
218
+ return { x: s0x, y: s0y };
211
219
  }
212
220
 
213
221
  /** Update box x-extents for a sample point if it lies in the y-band. */
@@ -253,7 +261,7 @@ export function limitBoxes(
253
261
  pn: number,
254
262
  delta: number,
255
263
  ): void {
256
- const numDiv = Math.round(delta * boxn);
264
+ const numDiv = delta * boxn; // C: const double num_div (routespl.c:242)
257
265
  for (let splinepi = 0; splinepi + 3 < pn; splinepi += 3) {
258
266
  sampleBezierSegment(boxes, boxn, pps, splinepi, numDiv);
259
267
  }
@@ -9,6 +9,7 @@
9
9
  // @see lib/common/taper.c:taper
10
10
 
11
11
  import type { Point, Bezier } from '../model/geom.js';
12
+ import { bezier } from './bezier.js';
12
13
 
13
14
  /** Sample points per Bézier segment. @see taper.c:BEZIERSUBDIVISION */
14
15
  const BEZIER_SUBDIVISION = 20;
@@ -65,19 +66,6 @@ function l2dist(p0: Point, p1: Point): number {
65
66
  return Math.hypot(p0.x - p1.x, p0.y - p1.y);
66
67
  }
67
68
 
68
- /** Evaluate a cubic Bézier at t via de Casteljau. @see lib/common/utils.c:Bezier */
69
- function bezierPoint(v: readonly Point[], t: number): Point {
70
- const vx = [v[0]!.x, v[1]!.x, v[2]!.x, v[3]!.x];
71
- const vy = [v[0]!.y, v[1]!.y, v[2]!.y, v[3]!.y];
72
- for (let i = 1; i <= 3; i++) {
73
- for (let j = 0; j <= 3 - i; j++) {
74
- vx[j] = (1.0 - t) * vx[j]! + t * vx[j + 1]!;
75
- vy[j] = (1.0 - t) * vy[j]! + t * vy[j + 1]!;
76
- }
77
- }
78
- return { x: vx[0]!, y: vy[0]! };
79
- }
80
-
81
69
  interface PathPoint {
82
70
  x: number;
83
71
  y: number;
@@ -107,7 +95,7 @@ function pathtolines(bez: Bezier): PathPoint[] {
107
95
  for (let j = 1; j <= 3; j++) V[j] = A[i + j]!;
108
96
  let p0 = V[0]!;
109
97
  for (let step = 1; step <= BEZIER_SUBDIVISION; step++) {
110
- const p1 = bezierPoint(V, step / BEZIER_SUBDIVISION);
98
+ const p1 = bezier(V, step / BEZIER_SUBDIVISION, null, null);
111
99
  linelen += l2dist(p0, p1);
112
100
  push(p1, linelen);
113
101
  p0 = p1;
package/src/gvc/anchor.ts CHANGED
@@ -20,6 +20,7 @@ import type { GraphObj } from '../common/subst.js';
20
20
  import { substObjAnchor, interpretCRNL } from '../common/subst.js';
21
21
  import { parseStyleFlags } from '../common/style-resolve.js';
22
22
  import { nodeAttr } from '../common/poly-init.js';
23
+ import { bezier } from '../common/bezier.js';
23
24
  import type { RendererPlugin } from './context.js';
24
25
  import { MapShape, type ObjState, type RenderJob } from './job.js';
25
26
 
@@ -447,20 +448,10 @@ function checkControlPoints(cp: Point[]): boolean {
447
448
  /** de Casteljau split of a cubic at t=0.5, returning left & right control sets.
448
449
  * @see lib/common/utils.c:Bezier */
449
450
  function bezierHalf(v: Point[]): { left: Point[]; right: Point[] } {
450
- const t = 0.5;
451
- const vt: Point[][] = [v.slice(0, 4)];
452
- for (let i = 1; i <= 3; i++) {
453
- vt[i] = [];
454
- for (let j = 0; j <= 3 - i; j++) {
455
- const a = vt[i - 1]![j]!;
456
- const b = vt[i - 1]![j + 1]!;
457
- vt[i]![j] = { x: (1 - t) * a.x + t * b.x, y: (1 - t) * a.y + t * b.y };
458
- }
459
- }
460
- return {
461
- left: [vt[0]![0]!, vt[1]![0]!, vt[2]![0]!, vt[3]![0]!],
462
- right: [vt[3]![0]!, vt[2]![1]!, vt[1]![2]!, vt[0]![3]!],
463
- };
451
+ const left: Point[] = [];
452
+ const right: Point[] = [];
453
+ bezier(v, 0.5, left, right);
454
+ return { left, right };
464
455
  }
465
456
 
466
457
  /** approx_bezier: flatten a cubic to a polyline. @see lib/common/emit.c */
@@ -9,6 +9,7 @@
9
9
 
10
10
  import type { Point, Box } from '../../model/geom.js';
11
11
  import { cround } from '../../common/arith.js';
12
+ import { bezier } from '../../common/bezier.js';
12
13
 
13
14
  /** Sign comparison matching C fcmp(). @see lib/util/gv_math.h:fcmp */
14
15
  export function fcmp(a: number, b: number): number {
@@ -31,26 +32,15 @@ export function midPointf(a: Point, b: Point): Point {
31
32
  // De Casteljau subdivision
32
33
  // ---------------------------------------------------------------------------
33
34
 
34
- type CasteljauRow = [Point, Point, Point, Point];
35
-
36
- /** One De Casteljau subdivision step. */
37
- export function casteljauStep(prev: CasteljauRow, t: number): CasteljauRow {
38
- const u = 1 - t;
39
- return [
40
- { x: u * prev[0].x + t * prev[1].x, y: u * prev[0].y + t * prev[1].y },
41
- { x: u * prev[1].x + t * prev[2].x, y: u * prev[1].y + t * prev[2].y },
42
- { x: u * prev[2].x + t * prev[3].x, y: u * prev[2].y + t * prev[3].y },
43
- { x: 0, y: 0 },
44
- ];
45
- }
46
-
47
- /** Subdivide cubic Bezier at t; return left or right half. @see lib/common/utils.c:Bezier */
35
+ /**
36
+ * Subdivide cubic Bezier at t; return left or right half, through the shared
37
+ * fused bezier().
38
+ * @see lib/common/utils.c:Bezier
39
+ */
48
40
  export function subdivideBezier(v: readonly Point[], t: number, side: 'left' | 'right'): Point[] {
49
- const r0: CasteljauRow = [{ ...v[0] }, { ...v[1] }, { ...v[2] }, { ...v[3] }];
50
- const r1 = casteljauStep(r0, t);
51
- const r2 = casteljauStep(r1, t);
52
- const r3 = casteljauStep(r2, t);
53
- return side === 'left' ? [r0[0], r1[0], r2[0], r3[0]] : [r3[0], r2[1], r1[2], r0[3]];
41
+ const half: Point[] = [];
42
+ bezier([...v], t, side === 'left' ? half : null, side === 'right' ? half : null);
43
+ return half;
54
44
  }
55
45
 
56
46
  // ---------------------------------------------------------------------------
@@ -10,6 +10,7 @@
10
10
 
11
11
  import type { Point, Box } from '../../model/geom.js';
12
12
  import { cround } from '../../common/arith.js';
13
+ import { bezier } from '../../common/bezier.js';
13
14
 
14
15
  // ---------------------------------------------------------------------------
15
16
  // Primitive helpers
@@ -49,24 +50,10 @@ export function midPointf(a: Point, b: Point): Point {
49
50
  // Bezier subdivision
50
51
  // ---------------------------------------------------------------------------
51
52
 
52
- /** One triangle row for De Casteljau; avoids nested array allocation. */
53
- type CasteljauRow = [Point, Point, Point, Point];
54
-
55
- /** Compute one row of the De Casteljau triangle at parameter t. */
56
- export function casteljauStep(prev: CasteljauRow, t: number): CasteljauRow {
57
- const u = 1 - t;
58
- return [
59
- { x: u * prev[0].x + t * prev[1].x, y: u * prev[0].y + t * prev[1].y },
60
- { x: u * prev[1].x + t * prev[2].x, y: u * prev[1].y + t * prev[2].y },
61
- { x: u * prev[2].x + t * prev[3].x, y: u * prev[2].y + t * prev[3].y },
62
- { x: 0, y: 0 }, // unused slot; keeps tuple type uniform
63
- ];
64
- }
65
-
66
53
  /**
67
54
  * De Casteljau subdivision of a cubic Bezier at parameter t.
68
- * Returns left-half or right-half control points.
69
- * Matches C Bezier() triangle computation.
55
+ * Returns left-half or right-half control points through the shared fused
56
+ * bezier().
70
57
  *
71
58
  * @see lib/common/utils.c:Bezier
72
59
  */
@@ -75,13 +62,9 @@ export function subdivideBezier(
75
62
  t: number,
76
63
  side: 'left' | 'right',
77
64
  ): Point[] {
78
- const r0: CasteljauRow = [{ ...v[0] }, { ...v[1] }, { ...v[2] }, { ...v[3] }];
79
- const r1 = casteljauStep(r0, t);
80
- const r2 = casteljauStep(r1, t);
81
- const r3 = casteljauStep(r2, t);
82
- return side === 'left'
83
- ? [r0[0], r1[0], r2[0], r3[0]]
84
- : [r3[0], r2[1], r1[2], r0[3]];
65
+ const half: Point[] = [];
66
+ bezier([...v], t, side === 'left' ? half : null, side === 'right' ? half : null);
67
+ return half;
85
68
  }
86
69
 
87
70
  // ---------------------------------------------------------------------------
@@ -19,7 +19,7 @@ import { dist } from '../../common/arrows-geometry.js';
19
19
 
20
20
  // Re-export geometry helpers so existing callers (tests) import from one place.
21
21
  export {
22
- fcmp, inBoxf, midPointf, casteljauStep, subdivideBezier,
22
+ fcmp, inBoxf, midPointf, subdivideBezier,
23
23
  countVertCross, countHorzCross, tryUpdateIntersect,
24
24
  splineIntersectf,
25
25
  tryLeftSide, tryRightSide, tryBottomSide, tryTopSide, boxIntersectf,
@@ -11,49 +11,26 @@
11
11
  import type { Point } from '../../model/geom.js';
12
12
  import { parseArrow, resolveArrowType } from '../../common/arrows.js';
13
13
  import { arrowLength } from '../../common/arrows-shapes.js';
14
+ import { bezier } from '../../common/bezier.js';
14
15
 
15
16
  // ---------------------------------------------------------------------------
16
17
  // De Casteljau subdivision
17
18
  // @see lib/common/utils.c:Bezier
18
19
  // ---------------------------------------------------------------------------
19
20
 
20
- /**
21
- * Build the De Casteljau triangle for a cubic Bezier at parameter t.
22
- * v[i][j] = (1-t)*v[i-1][j] + t*v[i-1][j+1].
23
- * @see lib/common/utils.c:Bezier (triangle computation section)
24
- */
25
- function deCasteljauTriangle(sp: Point[], t: number): Point[][] {
26
- const u = 1 - t;
27
- const v: Point[][] = [
28
- [sp[0], sp[1], sp[2], sp[3]],
29
- [{ x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }],
30
- [{ x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }],
31
- [{ x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }, { x: 0, y: 0 }],
32
- ];
33
- for (let i = 1; i <= 3; i++) {
34
- for (let j = 0; j <= 3 - i; j++) {
35
- v[i][j] = {
36
- x: u * v[i - 1][j].x + t * v[i - 1][j + 1].x,
37
- y: u * v[i - 1][j].y + t * v[i - 1][j + 1].y,
38
- };
39
- }
40
- }
41
- return v;
42
- }
43
-
44
21
  /**
45
22
  * De Casteljau subdivision of a cubic Bezier at parameter t.
46
- * Returns [left4, right4, midpoint].
23
+ * Returns [left4, right4, midpoint], computed by the shared fused bezier().
47
24
  * @see lib/common/utils.c:Bezier
48
25
  */
49
26
  export function bezierSubdivide(
50
27
  sp: Point[],
51
28
  t: number,
52
29
  ): [Point[], Point[], Point] {
53
- const v = deCasteljauTriangle(sp, t);
54
- const left: Point[] = [v[0][0], v[1][0], v[2][0], v[3][0]];
55
- const right: Point[] = [v[3][0], v[2][1], v[1][2], v[0][3]];
56
- return [left, right, v[3][0]];
30
+ const left: Point[] = [];
31
+ const right: Point[] = [];
32
+ const mid = bezier(sp, t, left, right);
33
+ return [left, right, mid];
57
34
  }
58
35
 
59
36
  // ---------------------------------------------------------------------------
@@ -1,10 +1,10 @@
1
1
  // SPDX-License-Identifier: EPL-2.0
2
2
  /**
3
3
  * Dot-local adapter between the dot `Graph` model and the `orthoEdges`
4
- * pipeline (`src/ortho`). Mirrors neato's private `OrthoHelper`
5
- * (`neato/splines.ts:247`) rather than extracting a shared helper, so the
6
- * working neato path is untouched (ADR-1) — this matches C, where dotgen and
7
- * neatogen each have their own call site into `ortho.c:orthoEdges`.
4
+ * pipeline (`src/ortho`). Mirrors neato's `OrthoHelper` (`neato/splines.ts`);
5
+ * the two share only the concentrate dedup (`isConcentrateDup`), as C has one
6
+ * gather loop while dotgen and neatogen each have their own call site into
7
+ * `ortho.c:orthoEdges`.
8
8
  *
9
9
  * @see lib/dotgen/dotsplines.c:dot_splines_ (EDGETYPE_ORTHO branch)
10
10
  * @see src/layout/neato/splines.ts:OrthoHelper
@@ -56,7 +56,7 @@ function buildNodes(nodeArr: Node[]): OrthoNodeArr {
56
56
  * (min, max) and makes one isInPS/addPS pair on it.
57
57
  * @see lib/ortho/ortho.c:1221-1229 (orthoEdges Concentrate PS)
58
58
  */
59
- function isConcentrateDup(ps: PointSet, e: Edge): boolean {
59
+ export function isConcentrateDup(ps: PointSet, e: Edge): boolean {
60
60
  const ti = e.tail.id;
61
61
  const hi = e.head.id;
62
62
  const lo = ti <= hi ? ti : hi;
@@ -17,6 +17,7 @@ import { IGNORED } from './rank.js';
17
17
  import { lateDouble } from '../../common/nodeinit.js';
18
18
  import { nodesInSeq } from './decomp.js';
19
19
  import { RADIANS } from '../../common/arith.js';
20
+ import { bezier } from '../../common/bezier.js';
20
21
 
21
22
  // ---------------------------------------------------------------------------
22
23
  // TextLabel interface (minimal — full type deferred to Batch 5b)
@@ -125,33 +126,6 @@ export const PORT_LABEL_DISTANCE = 10;
125
126
  /** Default port-label angle in degrees (CW negative). @see lib/common/const.h:PORT_LABEL_ANGLE */
126
127
  export const PORT_LABEL_ANGLE = -25;
127
128
 
128
- // ---------------------------------------------------------------------------
129
- // cubicBezierAt — de Casteljau at parameter t (Bezier() in utils.c)
130
- // @see lib/common/utils.c:175-203
131
- // ---------------------------------------------------------------------------
132
-
133
- /**
134
- * Evaluate a cubic Bezier curve at parameter t using the de Casteljau
135
- * triangle computation. Requires pts[0..3]. No Left/Right subdivision
136
- * output needed here, so they are omitted.
137
- *
138
- * @see lib/common/utils.c:Bezier
139
- */
140
- function cubicBezierAt(pts: readonly { x: number; y: number }[], t: number): { x: number; y: number } {
141
- // Triangle computation (degree = 3)
142
- const p0 = pts[0], p1 = pts[1], p2 = pts[2], p3 = pts[3];
143
- const mt = 1.0 - t;
144
- // Level 1
145
- const q0 = { x: mt * p0.x + t * p1.x, y: mt * p0.y + t * p1.y };
146
- const q1 = { x: mt * p1.x + t * p2.x, y: mt * p1.y + t * p2.y };
147
- const q2 = { x: mt * p2.x + t * p3.x, y: mt * p2.y + t * p3.y };
148
- // Level 2
149
- const r0 = { x: mt * q0.x + t * q1.x, y: mt * q0.y + t * q1.y };
150
- const r1 = { x: mt * q1.x + t * q2.x, y: mt * q1.y + t * q2.y };
151
- // Level 3
152
- return { x: mt * r0.x + t * r1.x, y: mt * r0.y + t * r1.y };
153
- }
154
-
155
129
  // ---------------------------------------------------------------------------
156
130
  // Bezier endpoint extraction helpers
157
131
  // @see lib/common/splines.c:place_portlabel:1331-1351
@@ -171,7 +145,7 @@ function tailEndpoints(bez: BezierSeg): { pe: { x: number; y: number }; pf: { x:
171
145
  return { pe: bez.sp, pf: bez.list[0] };
172
146
  }
173
147
  const pe = bez.list[0];
174
- const pf = cubicBezierAt(bez.list, 0.1);
148
+ const pf = bezier(bez.list, 0.1, null, null);
175
149
  return { pe, pf };
176
150
  }
177
151
 
@@ -192,7 +166,7 @@ function headEndpoints(bez: BezierSeg): { pe: { x: number; y: number }; pf: { x:
192
166
  }
193
167
  const pe = bez.list[bez.size - 1];
194
168
  const last4 = bez.list.slice(bez.size - 4, bez.size);
195
- const pf = cubicBezierAt(last4, 0.9);
169
+ const pf = bezier(last4, 0.9, null, null);
196
170
  return { pe, pf };
197
171
  }
198
172
 
@@ -33,7 +33,13 @@ export function bfsFixDisconnected(
33
33
  }
34
34
  }
35
35
 
36
- /** Process one BFS vertex: enqueue unvisited neighbours. @internal */
36
+ /**
37
+ * Process one BFS vertex: enqueue unvisited neighbours. The bump is 1 when
38
+ * graph[0] has no ewgts (C NULL, the port's empty array), else the float
39
+ * ewgts truncated to DistType (int).
40
+ * @see lib/neatogen/bfs.c:bfs (bfs.c:41-43)
41
+ * @internal
42
+ */
37
43
  export function bfsVisitNeighbors(
38
44
  vertex: number,
39
45
  graph: VtxData[],
@@ -42,10 +48,12 @@ export function bfsVisitNeighbors(
42
48
  ): number {
43
49
  const vd = graph[vertex];
44
50
  const d = dist[vertex];
51
+ const unit = graph[0].ewgts.length === 0;
45
52
  for (let i = 1; i < vd.nedges; i++) {
46
53
  const nb = vd.edges[i];
47
54
  if (dist[nb] < 0) {
48
- dist[nb] = d + 1;
55
+ // C ewgts is float*: round to float32, then (DistType) truncates
56
+ dist[nb] = d + (unit ? 1 : Math.trunc(Math.fround(vd.ewgts[i])));
49
57
  queue.push(nb);
50
58
  }
51
59
  }
@@ -57,8 +65,9 @@ export function bfsVisitNeighbors(
57
65
  // ---------------------------------------------------------------------------
58
66
 
59
67
  /**
60
- * Single-source BFS (unweighted) from `src`.
61
- * Populates `dist` with hop-distances; unreachable nodes get `BFS_UNREACHABLE`.
68
+ * Single-source BFS from `src`: first visit wins, each hop bumps by 1 or by
69
+ * the int-truncated ewgts (see bfsVisitNeighbors). Unreached nodes get
70
+ * `closestDist + 10`.
62
71
  *
63
72
  * @see lib/neatogen/bfs.c:bfs
64
73
  */
@@ -321,21 +321,20 @@ export function mapModelToStress(model: number): number {
321
321
  // solveModel helpers
322
322
  // ---------------------------------------------------------------------------
323
323
 
324
- /**
325
- * Run stress majorization on the graph and write results back to node positions.
326
- *
327
- * @see lib/neatogen/neatoinit.c:majorization
328
- */
324
+ /** Run stress majorization and write positions back. @see lib/neatogen/neatoinit.c:majorization */
329
325
  export function runMajorization(g: Graph, mode: number, model: number): void {
330
326
  void model; // only MODEL_SHORTPATH distances are exercised by the suite
331
327
  const nodeList = assignNodeIds(g);
332
328
  const n = nodeList.length;
333
329
  const haveLen = graphHasLen(g);
334
- const vtx = makeGraphDataC(g, nodeList, haveLen);
330
+ const vtx = makeGraphDataC(g, nodeList, haveLen, mode === MODE_HIER || mode === MODE_IPSEP);
335
331
  checkStart(g); // C: srand48(seed) immediately before initLayout draws
336
332
  const maxi = lateDouble(g.attrs.get('maxiter'), DFLT_ITERATIONS, 0);
337
333
  const epsilon = lateDouble(g.attrs.get('epsilon'), 1e-4, 0); // DFLT_TOLERANCE
338
- const Dij = haveLen ? computeWeightedApspPacked(vtx, n) : computeApspPacked(vtx, n);
334
+ // C ipsep (stress_majorization_cola, constrained_majorization_ipsep.c:150) always uses
335
+ // compute_apsp_packed; kD_mkernel uses the weighted APSP iff graph->ewgts (stress.c:855).
336
+ const weighted = haveLen && mode !== MODE_IPSEP;
337
+ const Dij = weighted ? computeWeightedApspPacked(vtx, n) : computeApspPacked(vtx, n);
339
338
  const dCoords = [new Float64Array(n), new Float64Array(n)];
340
339
  stressMajorizationKDMkernel(Dij, n, dCoords, nodeList, {
341
340
  dim: DFLT_DIM, opts: OPT_EXP_FLAG, maxi, epsilon,
@@ -354,17 +353,18 @@ export function graphHasLen(g: Graph): boolean {
354
353
  }
355
354
 
356
355
  /**
357
- * Build the vtx_data adjacency in C's makeGraphData shape: per node,
358
- * out-edges then in-edges (agfstedge order), self entry at index 0,
359
- * duplicate neighbours merged (len keeps the max). ewgts are edge
360
- * lens (default 1.0) when any len attr exists, else absent so the
361
- * APSP uses BFS hop counts.
356
+ * C makeGraphData's vtx_data: per node out-edges then in-edges (agfstedge order), self at
357
+ * index 0, duplicate neighbours merged (len keeps the max). ewgts: lens (default 1.0) if any
358
+ * len attr, else 1.0 under haveDir (hier/ipsep), else absent (C NULL). eweights (haveWt)
359
+ * and edists are not built: nothing on a supported path reads them.
362
360
  * @see lib/neatogen/neatoinit.c:makeGraphData
363
361
  * @see lib/neatogen/stuff.c:setEdgeLen
364
362
  */
365
- export function makeGraphDataC(g: Graph, nodeList: Node[], haveLen: boolean): VtxData[] {
363
+ export function makeGraphDataC(
364
+ g: Graph, nodeList: Node[], haveLen: boolean, haveDir: boolean,
365
+ ): VtxData[] {
366
366
  const vtx: VtxData[] = nodeList.map((_, i) => ({
367
- nedges: 1, edges: [i], ewgts: haveLen ? [0] : [],
367
+ nedges: 1, edges: [i], ewgts: haveLen || haveDir ? [0] : [],
368
368
  }));
369
369
  for (const np of nodeList) {
370
370
  const i = np.info.id!;
@@ -382,7 +382,7 @@ export function makeGraphDataC(g: Graph, nodeList: Node[], haveLen: boolean): Vt
382
382
  }
383
383
  seen.set(vid, entry.nedges);
384
384
  entry.edges.push(vid);
385
- if (haveLen) entry.ewgts.push(dist);
385
+ if (haveLen || haveDir) entry.ewgts.push(dist); // C neatoinit.c:827: haveDir 1.0
386
386
  entry.nedges++;
387
387
  }
388
388
  }
@@ -32,6 +32,8 @@ import {
32
32
  import { makeSelfEdge } from '../../common/splines.js';
33
33
  import { newSpline, clipAndInstall } from '../../common/splines-clip.js';
34
34
  import { orthoEdges } from '../../ortho/index.js';
35
+ import { PointSet } from '../../common/pointset.js';
36
+ import { isConcentrateDup } from '../dot/ortho-adapter.js';
35
37
  import type { OrthoGraph, OrthoEdge, OrthoPoint } from '../../ortho/index.js';
36
38
  import {
37
39
  EDGETYPE_NONE, EDGETYPE_LINE, EDGETYPE_ORTHO,
@@ -595,17 +597,11 @@ class OrthoHelper {
595
597
  // pair is routed (ortho.c:1223-1233). Self-loops are NOT filtered — the
596
598
  // maze routes them via addLoop.
597
599
  const concentrate = g.root.info.concentrate ?? false;
598
- const seen = new Set<string>();
600
+ const ps = concentrate ? new PointSet() : null;
599
601
  const edges: TaggedOrthoEdge[] = [];
600
602
  for (const n of nodesInSeq(g)) {
601
603
  for (const e of n.outEdges(g)) {
602
- if (concentrate) {
603
- const ti = e.tail.id;
604
- const hi = e.head.id;
605
- const key = ti <= hi ? `${ti}|${hi}` : `${hi}|${ti}`;
606
- if (seen.has(key)) continue;
607
- seen.add(key);
608
- }
604
+ if (ps && isConcentrateDup(ps, e)) continue;
609
605
  // C attachOrthoEdges endpoints: p1 = ND_coord(tail) + ED_tail_port.p,
610
606
  // q1 = ND_coord(head) + ED_head_port.p (ortho.c:1075-1076). Plumb the
611
607
  // port offset so compass/record ports (tailport=s, headport=n, …) exit
@@ -14,6 +14,7 @@ import type { Edge } from '../model/edge.js';
14
14
  import { isHtmlValue, htmlValueContent } from '../common/html-string.js';
15
15
  import { buildFromAst } from './builder.js';
16
16
  import { scanNumberAmbiguities, numberAmbiguityMessage } from './number-scan.js';
17
+ import { syntaxErrorMessage } from './scan-token.js';
17
18
  import type { ParsedGraph } from './ast.js';
18
19
  import type { GvErrorCode, GvExpectation } from '../errors.js';
19
20
  import { DotEngineError, friendlyMessageFor, invalidArgType } from '../errors.js';
@@ -249,6 +250,7 @@ export function parse(src: string): Graph {
249
250
  if (isPeggyError(err)) {
250
251
  const start = err.location.start;
251
252
  warnNumberAmbiguities(src, start.offset);
253
+ console.error(syntaxErrorMessage(src, start.offset));
252
254
  const code: GvErrorCode =
253
255
  err.found === null ? 'SYNTAX_UNEXPECTED_EOF' : 'SYNTAX_ERROR';
254
256
  throw new ParseError(
@@ -270,8 +272,18 @@ export function parse(src: string): Graph {
270
272
  }
271
273
  throw err;
272
274
  }
275
+ try {
276
+ validateEdgeOperators(src, ast.directed);
277
+ } catch (err: unknown) {
278
+ // C's lexer hands the wrong operator to bison as '-': a syntax error there.
279
+ const offset = err instanceof ParseError ? err.location.offset : undefined;
280
+ if (offset !== undefined) {
281
+ warnNumberAmbiguities(src, offset);
282
+ console.error(syntaxErrorMessage(src, offset));
283
+ }
284
+ throw err;
285
+ }
273
286
  warnNumberAmbiguities(src, src.length);
274
- validateEdgeOperators(src, ast.directed);
275
287
  return buildFromAst(ast);
276
288
  }
277
289