@knowvah/dot-engine 2.6.11 → 2.6.12

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 (125) hide show
  1. package/dist/async/render-async.d.ts.map +1 -1
  2. package/dist/common/agerr.d.ts +37 -0
  3. package/dist/common/agerr.d.ts.map +1 -0
  4. package/dist/common/arrows-shapes-poly.d.ts.map +1 -1
  5. package/dist/common/color.d.ts.map +1 -1
  6. package/dist/common/crand.d.ts +0 -20
  7. package/dist/common/crand.d.ts.map +1 -1
  8. package/dist/common/emit-once.d.ts +6 -0
  9. package/dist/common/emit-once.d.ts.map +1 -0
  10. package/dist/common/graph-init.d.ts.map +1 -1
  11. package/dist/common/htmltable-attr-warn.d.ts.map +1 -1
  12. package/dist/common/htmltable-emit-fill.d.ts +9 -0
  13. package/dist/common/htmltable-emit-fill.d.ts.map +1 -1
  14. package/dist/common/htmltable-emit-rules.d.ts.map +1 -1
  15. package/dist/common/htmltable-lex-expat.d.ts +1 -6
  16. package/dist/common/htmltable-lex-expat.d.ts.map +1 -1
  17. package/dist/common/htmltable.d.ts +1 -7
  18. package/dist/common/htmltable.d.ts.map +1 -1
  19. package/dist/common/poly-gencode.d.ts.map +1 -1
  20. package/dist/common/poly-init.d.ts.map +1 -1
  21. package/dist/common/postproc.d.ts.map +1 -1
  22. package/dist/common/random.d.ts.map +1 -1
  23. package/dist/common/render-state.d.ts +8 -0
  24. package/dist/common/render-state.d.ts.map +1 -0
  25. package/dist/common/splines-geom.d.ts +8 -0
  26. package/dist/common/splines-geom.d.ts.map +1 -1
  27. package/dist/common/splines-routespl.d.ts.map +1 -1
  28. package/dist/common/textmeasure-lookup.d.ts.map +1 -1
  29. package/dist/common/textmeasure.d.ts.map +1 -1
  30. package/dist/gvc/anchor.d.ts.map +1 -1
  31. package/dist/gvc/device-cluster.d.ts.map +1 -1
  32. package/dist/gvc/device-labels.d.ts +14 -0
  33. package/dist/gvc/device-labels.d.ts.map +1 -1
  34. package/dist/gvc/device.d.ts.map +1 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +765 -451
  37. package/dist/index.js.map +4 -4
  38. package/dist/layout/dot/cluster.d.ts.map +1 -1
  39. package/dist/layout/dot/conc.d.ts.map +1 -1
  40. package/dist/layout/dot/decomp.d.ts.map +1 -1
  41. package/dist/layout/dot/edge-route-clip.d.ts +18 -10
  42. package/dist/layout/dot/edge-route-clip.d.ts.map +1 -1
  43. package/dist/layout/dot/mincross-build.d.ts.map +1 -1
  44. package/dist/layout/dot/mincross-cross.d.ts.map +1 -1
  45. package/dist/layout/dot/rank.d.ts.map +1 -1
  46. package/dist/layout/dot/splines.d.ts.map +1 -1
  47. package/dist/layout/fdp/index.d.ts.map +1 -1
  48. package/dist/layout/fdp/tlayout-parms.d.ts.map +1 -1
  49. package/dist/layout/fdp/xlayout.d.ts.map +1 -1
  50. package/dist/layout/neato/edge-len.d.ts.map +1 -1
  51. package/dist/layout/neato/fdp-adjust.d.ts.map +1 -1
  52. package/dist/layout/neato/init.d.ts +4 -10
  53. package/dist/layout/neato/init.d.ts.map +1 -1
  54. package/dist/layout/neato/kk.d.ts.map +1 -1
  55. package/dist/layout/neato/multispline-router.d.ts.map +1 -1
  56. package/dist/layout/neato/multispline.d.ts.map +1 -1
  57. package/dist/layout/neato/poly.d.ts.map +1 -1
  58. package/dist/layout/neato/sgd.d.ts.map +1 -1
  59. package/dist/layout/neato/start.d.ts.map +1 -1
  60. package/dist/ortho/index.d.ts.map +1 -1
  61. package/dist/parser/index.d.ts.map +1 -1
  62. package/dist/render/color-resolve.d.ts +49 -1
  63. package/dist/render/color-resolve.d.ts.map +1 -1
  64. package/dist/render/public.d.ts.map +1 -1
  65. package/dist/render/svg-edge-split.d.ts +2 -0
  66. package/dist/render/svg-edge-split.d.ts.map +1 -1
  67. package/dist/render/xdot-public.d.ts.map +1 -1
  68. package/dist/render.js +513 -200
  69. package/dist/render.js.map +4 -4
  70. package/package.json +1 -1
  71. package/src/async/render-async.ts +6 -0
  72. package/src/common/agerr.ts +96 -0
  73. package/src/common/arrows-shapes-poly.ts +19 -5
  74. package/src/common/color.ts +38 -10
  75. package/src/common/crand.ts +8 -2
  76. package/src/common/emit-once.ts +27 -0
  77. package/src/common/graph-init.ts +2 -1
  78. package/src/common/htmltable-attr-warn.ts +3 -4
  79. package/src/common/htmltable-emit-fill.ts +48 -0
  80. package/src/common/htmltable-emit-rules.ts +2 -0
  81. package/src/common/htmltable-lex-expat.ts +78 -23
  82. package/src/common/htmltable.ts +15 -16
  83. package/src/common/poly-gencode.ts +14 -2
  84. package/src/common/poly-init.ts +2 -1
  85. package/src/common/postproc.ts +4 -1
  86. package/src/common/random.ts +11 -1
  87. package/src/common/render-state.ts +36 -0
  88. package/src/common/splines-geom.ts +8 -4
  89. package/src/common/splines-routespl.ts +2 -1
  90. package/src/common/textmeasure-lookup.ts +10 -3
  91. package/src/common/textmeasure.ts +45 -2
  92. package/src/gvc/anchor.ts +1 -12
  93. package/src/gvc/device-cluster.ts +37 -9
  94. package/src/gvc/device-labels.ts +27 -0
  95. package/src/gvc/device.ts +18 -6
  96. package/src/index.ts +3 -0
  97. package/src/layout/dot/cluster.ts +2 -1
  98. package/src/layout/dot/conc.ts +6 -5
  99. package/src/layout/dot/decomp.ts +3 -0
  100. package/src/layout/dot/edge-route-clip.ts +27 -21
  101. package/src/layout/dot/mincross-build.ts +2 -0
  102. package/src/layout/dot/mincross-cross.ts +2 -0
  103. package/src/layout/dot/rank.ts +2 -0
  104. package/src/layout/dot/splines.ts +2 -1
  105. package/src/layout/fdp/index.ts +2 -1
  106. package/src/layout/fdp/tlayout-parms.ts +9 -2
  107. package/src/layout/fdp/xlayout.ts +10 -2
  108. package/src/layout/neato/cdt-surface.ts +6 -6
  109. package/src/layout/neato/edge-len.ts +2 -1
  110. package/src/layout/neato/fdp-adjust.ts +3 -2
  111. package/src/layout/neato/init.ts +11 -11
  112. package/src/layout/neato/kk.ts +2 -1
  113. package/src/layout/neato/multispline-router.ts +3 -3
  114. package/src/layout/neato/multispline.ts +3 -2
  115. package/src/layout/neato/poly.ts +3 -2
  116. package/src/layout/neato/sgd.ts +5 -2
  117. package/src/layout/neato/start.ts +14 -10
  118. package/src/ortho/index.ts +3 -1
  119. package/src/parser/dot.js +4 -1
  120. package/src/parser/dot.pegjs +5 -1
  121. package/src/parser/index.ts +4 -3
  122. package/src/render/color-resolve.ts +193 -4
  123. package/src/render/public.ts +2 -0
  124. package/src/render/svg-edge-split.ts +4 -2
  125. package/src/render/xdot-public.ts +2 -0
@@ -40,6 +40,7 @@ export {
40
40
  constrainingFlatEdge, countConstraining, postorder,
41
41
  flatReorderBuildTemprank, flatReorderFixEdges, flatReorderRank, flatReorder,
42
42
  } from './mincross-flat.js';
43
+ import { registerRenderState } from '../../common/render-state.js';
43
44
 
44
45
  // allocate_ranks — @see lib/dotgen/mincross.c:allocate_ranks
45
46
  export function makeEmptyRank(): RankEntry {
@@ -99,6 +100,7 @@ export function allocateRanks(g: Graph): void {
99
100
  * distinct names rather than colliding/deduping by name).
100
101
  */
101
102
  let fillSeq = 0;
103
+ registerRenderState(() => { fillSeq = 0; });
102
104
 
103
105
  /**
104
106
  * Create a placeholder fill node in the `_new_rank` subgraph at `rank`.
@@ -18,12 +18,14 @@ import { CLUSTER } from './rank.js';
18
18
  import {
19
19
  MincrossContext, matrixGet, rankGet,
20
20
  } from './mincross-utils.js';
21
+ import { registerRenderState } from '../../common/render-state.js';
21
22
 
22
23
  /**
23
24
  * Mirror of C's file-global `ReMincross`. Set true only during the remincross
24
25
  * pass so left2right tightens the cluster constraint. @see mincross.c
25
26
  */
26
27
  let reMincross = false;
28
+ registerRenderState(() => { reMincross = false; });
27
29
  export function setReMincross(b: boolean): void { reMincross = b; }
28
30
 
29
31
  // ---------------------------------------------------------------------------
@@ -12,6 +12,7 @@ import { rank } from './ns.js';
12
12
  import { dot2Rank } from './rank-dot2.js';
13
13
  import { class1 } from './classify.js';
14
14
  import { pruneForeignClusterNodes } from './cluster.js';
15
+ import { registerRenderState } from '../../common/render-state.js';
15
16
 
16
17
  // ---------------------------------------------------------------------------
17
18
  // Constants @see lib/common/const.h
@@ -54,6 +55,7 @@ export const IGNORED = 6;
54
55
  /** Global cluster layout type. @see lib/common/globals.h:CL_type */
55
56
  export let clType = LOCAL;
56
57
  export function setClType(t: number): void { clType = t; }
58
+ registerRenderState(() => { clType = LOCAL; });
57
59
 
58
60
  // ---------------------------------------------------------------------------
59
61
  // Utility helpers
@@ -26,6 +26,7 @@ import { dispatchOrthoEdges } from './ortho-adapter.js';
26
26
  import { routeEdgeGroups } from './splines-groups.js';
27
27
  import { placePortLabels, placeRegularEdgeLabels, setEdgeLabelPos } from './splines-label.js';
28
28
  import { nodesInSeq } from './decomp.js';
29
+ import { agerrLineOn } from '../../common/agerr.js';
29
30
 
30
31
  // ---------------------------------------------------------------------------
31
32
  // Edge-type flag constants
@@ -473,7 +474,7 @@ function orthoDispatch(g: Graph): number {
473
474
  function curvedTop(g: Graph): void {
474
475
  resetRW(g);
475
476
  if (((g.root.info.has_labels ?? 0) & EDGE_LABEL) !== 0) {
476
- console.warn('edge labels with splines=curved not supported in dot - use xlabels\n');
477
+ agerrLineOn('warn', 'edge labels with splines=curved not supported in dot - use xlabels\n');
477
478
  }
478
479
  }
479
480
 
@@ -27,6 +27,7 @@ import { fdpLayout, mkClusters } from './layout.js';
27
27
  import { gdata } from './fdp-model.js';
28
28
  import { graphInit } from '../../common/graph-init.js';
29
29
  import { csrand } from '../../common/crand.js';
30
+ import { agerrLineOn } from '../../common/agerr.js';
30
31
 
31
32
  export { fdpLayout, mkClusters, evalPositions, setBB } from './layout.js';
32
33
  export { fdpTLayout } from './tlayout.js';
@@ -93,7 +94,7 @@ function fdpSplines(g: Graph): void {
93
94
  );
94
95
  }
95
96
  if (g.info.n_cluster_edges) { // HAS_CLUST_EDGE(g)
96
- console.warn(CLUSTER_EDGE_WARNING);
97
+ agerrLineOn('warn', CLUSTER_EDGE_WARNING);
97
98
  et = EDGETYPE_LINE;
98
99
  } else {
99
100
  splineEdges1(g, et);
@@ -15,6 +15,7 @@
15
15
  import type { Graph } from '../../model/graph.js';
16
16
  import { lateDouble, lateInt } from '../../common/nodeinit.js';
17
17
  import { type XParams, aggetGraph } from './fdp-model.js';
18
+ import { registerRenderState } from '../../common/render-state.js';
18
19
 
19
20
  // ---------------------------------------------------------------------------
20
21
  // Constants — @see lib/fdpgen/tlayout.c:96-101, lib/neatogen/neato.h:27-30
@@ -90,13 +91,19 @@ export interface TParms {
90
91
  loopcnt: number;
91
92
  }
92
93
 
93
- /** Module-level mutable state, mirroring the C static. @see tlayout.c:parms */
94
- export const parms: TParms = {
94
+ /** C's zero-initialized static parms_t. */
95
+ const PARMS_INIT: Readonly<TParms> = {
95
96
  useGrid: 0, useNew: 0, seed: 0, numIters: 0, maxIters: 0, unscaled: 0,
96
97
  C: 0, Tfact: 0, K: 0, T0: 0, smode: 0, Cell: 0, Wd: 0, Ht: 0,
97
98
  pass1: 0, loopcnt: 0,
98
99
  };
99
100
 
101
+ /** Module-level mutable state, mirroring the C static. @see tlayout.c:parms */
102
+ export const parms: TParms = { ...PARMS_INIT };
103
+ // fdpParms is not reset: it is the -L command-line configuration (args.c),
104
+ // and the one field a render writes (K) is set by fdpInitParams before any read.
105
+ registerRenderState(() => { Object.assign(parms, PARMS_INIT); });
106
+
100
107
  /** Linear cooling schedule. @see lib/fdpgen/tlayout.c:cool */
101
108
  export function cool(t: number): number {
102
109
  return parms.T0 * (parms.maxIters - t) / parms.maxIters;
@@ -36,6 +36,7 @@ import { libmHypot } from '../../common/libm-hypot.js';
36
36
  import { fma, fms } from '../../common/fma.js';
37
37
  import { normalizeG } from './normalize.js';
38
38
  import { simpleScale } from '../neato/simple-scale.js';
39
+ import { registerRenderState } from '../../common/render-state.js';
39
40
 
40
41
  /** @see lib/fdpgen/xlayout.c:DFLT_overlap */
41
42
  const DFLT_OVERLAP = '9:prism';
@@ -47,8 +48,8 @@ const PS2INCH = (x: number): number => x / 72;
47
48
  // xParams / X_marg — static state (C statics, persist across calls)
48
49
  // ---------------------------------------------------------------------------
49
50
 
50
- /** @see lib/fdpgen/xlayout.c:xParams */
51
- const xParams: XParams = {
51
+ /** C's static initializer of xParams. @see lib/fdpgen/xlayout.c:xParams */
52
+ const XPARAMS_INIT: Readonly<XParams> = {
52
53
  numIters: 60,
53
54
  T0: 0.0,
54
55
  K: 0.3,
@@ -56,8 +57,15 @@ const xParams: XParams = {
56
57
  loopcnt: 0,
57
58
  };
58
59
 
60
+ /** @see lib/fdpgen/xlayout.c:xParams */
61
+ const xParams: XParams = { ...XPARAMS_INIT };
62
+
59
63
  /** @see lib/fdpgen/xlayout.c:X_marg */
60
64
  let xMarg: ExpandT = { x: 0, y: 0, doAdd: false };
65
+ registerRenderState(() => {
66
+ Object.assign(xParams, XPARAMS_INIT);
67
+ xMarg = { x: 0, y: 0, doAdd: false };
68
+ });
61
69
 
62
70
  /** Overlap/non-overlap repulsion strengths for one try. */
63
71
  interface RepStrength {
@@ -354,14 +354,14 @@ class Cdt {
354
354
 
355
355
  if (this.inCircle(v1, v2, v3, v4) > 0) {
356
356
  const en = this.edgeOrNew(v3, v4);
357
- // f3 ring: (v4, v2, v3) with edges (e5, e2, en); e5 = v4–v2
358
- // f4 ring: (v1, v4, v3) with edges (e4, en, e3); e4 = v1–v4
359
- // (rings derived from C's gts_face_new(en,e5,e2)/(en,e3,e4) cycles,
360
- // matching the vertex triples C passes to the recursive calls)
357
+ // C: f3 = gts_face_new(en, e5, e2), f4 = gts_face_new(en, e3, e4).
358
+ // The stored ring starts at C's t->e1 (en) because gts_triangle_vertices
359
+ // reads (e1-only, e1∩e2, e2-only): f3 → (v3, v4, v2), f4 → (v4, v3, v1).
360
+ // That is the vertex order mkSurface emits and triCenter sums in.
361
361
  this.removeFace(f1);
362
362
  this.removeFace(f2);
363
- const f3 = this.addFace([v4, v2, v3], [e5, e2, en]);
364
- const f4 = this.addFace([v1, v4, v3], [e4, en, e3]);
363
+ const f3 = this.addFace([v3, v4, v2], [en, e5, e2]);
364
+ const f4 = this.addFace([v4, v3, v1], [en, e3, e4]);
365
365
 
366
366
  this.swapIfInCircle(f3, v4, v2, v3, e5, e2, en);
367
367
  this.swapIfInCircle(f4, v1, v4, v3, e4, en, e3);
@@ -9,6 +9,7 @@
9
9
 
10
10
  import type { Graph } from '../../model/graph.js';
11
11
  import type { Edge } from '../../model/edge.js';
12
+ import { agerrLineOn } from '../../common/agerr.js';
12
13
 
13
14
  /** `dfltlen` passed by scan_graph_mode. @see lib/neatogen/stuff.c:scan_graph_mode */
14
15
  export const DEFAULT_EDGE_LEN = 1.0;
@@ -24,7 +25,7 @@ export function lenattr(ep: Edge, g: Graph): number {
24
25
  const val = /^\s*[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?/.exec(s);
25
26
  const len = val === null ? Number.NaN : parseFloat(val[0]);
26
27
  if (Number.isNaN(len) || len < 0 || len === 0) {
27
- console.warn(`bad edge len "${s}" in ${g.name} - setting to ${DEFAULT_EDGE_LEN.toFixed(2)}`);
28
+ agerrLineOn('warn', `bad edge len "${s}" in ${g.name} - setting to ${DEFAULT_EDGE_LEN.toFixed(2)}`);
28
29
  return DEFAULT_EDGE_LEN;
29
30
  }
30
31
  return len;
@@ -26,6 +26,7 @@ import { vpscAdjust } from './vpsc-adjust.js';
26
26
  import { sepFactor } from './sep-factor.js';
27
27
  import { lateDouble } from '../../common/nodeinit.js';
28
28
  import { RenderError } from '../../errors.js';
29
+ import { agerrLineOn } from '../../common/agerr.js';
29
30
 
30
31
  /** @see lib/neatogen/adjust.c:DFLT_MARGIN (points) */
31
32
  const DFLT_MARGIN = 4;
@@ -161,7 +162,7 @@ export function warnUnrecognizedOverlap(flag: string | undefined): void {
161
162
  const s = flag.toLowerCase();
162
163
  if (s.startsWith('prism') || NAMED_MODES.has(s)) return;
163
164
  if (mapBoolDflt(flag, false) === mapBoolDflt(flag, true)) return;
164
- console.warn(`Unrecognized overlap value "${flag}" - using false`);
165
+ agerrLineOn('warn', `Unrecognized overlap value "${flag}" - using false`);
165
166
  }
166
167
 
167
168
  /**
@@ -203,7 +204,7 @@ function adjustLate(g: Graph, am: AdjustData): number {
203
204
  case AM_IPSEP: return 0;
204
205
  case AM_VPSC: return vpscAdjust(g);
205
206
  default:
206
- console.warn(`Unhandled adjust option ${am.print}`);
207
+ agerrLineOn('warn', `Unhandled adjust option ${am.print}`);
207
208
  return 0;
208
209
  }
209
210
  }
@@ -50,6 +50,7 @@ export const MODE_HIER = 2;
50
50
  export const MODE_IPSEP = 3;
51
51
  /** Stochastic gradient descent. @see lib/neatogen/neatoprocs.h */
52
52
  export const MODE_SGD = 4;
53
+ /** `MaxIter` under MODE_SGD. @see neatoinit.c:1285 */ const SGD_ITERATIONS = 30;
53
54
 
54
55
  // ---------------------------------------------------------------------------
55
56
  // Model constants — @see lib/neatogen/defs.h
@@ -321,6 +322,13 @@ export function mapModelToStress(model: number): number {
321
322
  // solveModel helpers
322
323
  // ---------------------------------------------------------------------------
323
324
 
325
+ /** `MaxIter` per mode; `maxiter` goes through C `atoi` (no digits -> 0). @see neatoinit.c:1280-1287 */
326
+ export function neatoMaxIter(maxiter: string | undefined, mode: number, n: number): number {
327
+ if (maxiter !== undefined) return parseInt(maxiter, 10) || 0;
328
+ if (mode === MODE_MAJOR) return DFLT_ITERATIONS;
329
+ return mode === MODE_SGD ? SGD_ITERATIONS : 100 * n;
330
+ }
331
+
324
332
  /** Run stress majorization and write positions back. @see lib/neatogen/neatoinit.c:majorization */
325
333
  export function runMajorization(g: Graph, mode: number, model: number): void {
326
334
  void model; // only MODEL_SHORTPATH distances are exercised by the suite
@@ -329,7 +337,7 @@ export function runMajorization(g: Graph, mode: number, model: number): void {
329
337
  const haveLen = graphHasLen(g);
330
338
  const vtx = makeGraphDataC(g, nodeList, haveLen, mode === MODE_HIER || mode === MODE_IPSEP);
331
339
  checkStart(g); // C: srand48(seed) immediately before initLayout draws
332
- const maxi = lateDouble(g.attrs.get('maxiter'), DFLT_ITERATIONS, 0);
340
+ const maxi = neatoMaxIter(g.attrs.get('maxiter'), mode, n);
333
341
  const epsilon = lateDouble(g.attrs.get('epsilon'), 1e-4, 0); // DFLT_TOLERANCE
334
342
  // C ipsep (stress_majorization_cola, constrained_majorization_ipsep.c:150) always uses
335
343
  // compute_apsp_packed; kD_mkernel uses the weighted APSP iff graph->ewgts (stress.c:855).
@@ -389,11 +397,7 @@ export function makeGraphDataC(
389
397
  return vtx;
390
398
  }
391
399
 
392
- /**
393
- * Dispatch to the appropriate layout solver for the given mode.
394
- *
395
- * @see lib/neatogen/neatoinit.c:neatoLayout
396
- */
400
+ /** Dispatch to the layout solver for the mode. @see lib/neatogen/neatoinit.c:neatoLayout */
397
401
  export function solveModel(g: Graph, mode: number, model: number): void {
398
402
  if (g.nodes.size < 2) return;
399
403
  assertSupported(g, mode, model);
@@ -411,11 +415,7 @@ export function solveModel(g: Graph, mode: number, model: number): void {
411
415
  // neatoTranslate helpers
412
416
  // ---------------------------------------------------------------------------
413
417
 
414
- /**
415
- * Find minimum X and Y across all node positions.
416
- *
417
- * @see lib/neatogen/neatoinit.c (translate logic)
418
- */
418
+ /** Minimum X and Y across all node positions. @see lib/neatogen/neatoinit.c (translate logic) */
419
419
  export function findMinPos(g: Graph): { minX: number; minY: number } {
420
420
  let minX = Infinity;
421
421
  let minY = Infinity;
@@ -27,6 +27,7 @@ import { MODEL_MDS } from './init.js';
27
27
  import { solve } from './kk-solve.js';
28
28
  import { lenattr } from './edge-len.js';
29
29
  import { newPathState, shortestPath, mdsModel, type Neighbour } from './kk-paths.js';
30
+ import { agerrLineOn } from '../../common/agerr.js';
30
31
 
31
32
  /** `Ndim`: layout dimension (this port lays out in 2-D only). */
32
33
  const NDIM = 2;
@@ -289,7 +290,7 @@ function moveNode(st: KkState, m: number): void {
289
290
  function solveModel(g: Graph, st: KkState): void {
290
291
  for (let m = chooseNode(st); m >= 0; m = chooseNode(st)) moveNode(st, m);
291
292
  if (st.move === st.maxIter) {
292
- console.warn(`Max. iterations (${st.maxIter}) reached on graph ${g.name}`);
293
+ agerrLineOn('warn', `Max. iterations (${st.maxIter}) reached on graph ${g.name}`);
293
294
  }
294
295
  }
295
296
 
@@ -19,6 +19,7 @@
19
19
  import type { Point, Poly } from '../../pathplan/types.js';
20
20
  import { wind } from '../../pathplan/visibility.js';
21
21
  import { mkSurface } from './cdt-surface.js';
22
+ import { dist2 } from '../../common/spline-midpoint.js';
22
23
  import { InternalError } from '../../errors.js';
23
24
 
24
25
  /** index pair. @see multispline.c:ipair */
@@ -87,9 +88,8 @@ function sharedEdge(faces: number[], p3: number, q3: number): Ipair {
87
88
 
88
89
  /** @see multispline.c:addTriEdge */
89
90
  export function addTriEdge(g: Tgraph, t: number, h: number, seg: Ipair): void {
90
- const dist = Math.hypot(
91
- g.nodes[t]!.ctr.x - g.nodes[h]!.ctr.x,
92
- g.nodes[t]!.ctr.y - g.nodes[h]!.ctr.y);
91
+ /* DIST = sqrt(fused DIST2): libgvplugin_neato_layout _addTriEdge fmul/fmadd/fsqrt */
92
+ const dist = Math.sqrt(dist2(g.nodes[t]!.ctr, g.nodes[h]!.ctr));
93
93
  const idx = g.edges.length;
94
94
  g.edges.push({ t, h, seg, dist });
95
95
  g.nodes[t]!.edges.push(idx);
@@ -25,6 +25,7 @@ import { clipAndInstall } from '../../common/splines-clip.js';
25
25
  import { makeStraightEdges, addEdgeLabels } from '../dot/straight-edges.js';
26
26
  import type { Router, Ipair } from './multispline-router.js';
27
27
  import { addEndpoint, edgeToSeg, triPath } from './multispline-router.js';
28
+ import { agwarningf } from '../../common/agerr.js';
28
29
 
29
30
  export type { Router } from './multispline-router.js';
30
31
  export { mkRouter } from './multispline-router.js';
@@ -339,8 +340,8 @@ function chainOf(e: Edge): Edge[] {
339
340
  * @see multispline.c:genroute
340
341
  */
341
342
  function warnNoControlPoints(e: Edge): number {
342
- console.warn(
343
- `Warning: Could not create control points for multiple spline for edge (${e.tail.name},${e.head.name})`,
343
+ agwarningf(
344
+ `Could not create control points for multiple spline for edge (${e.tail.name},${e.head.name})`,
344
345
  );
345
346
  return 1;
346
347
  }
@@ -19,6 +19,7 @@ import type { Node } from '../../model/node.js';
19
19
  import type { Box, Point } from '../../model/geom.js';
20
20
  import { ShapeKind } from '../../common/types.js';
21
21
  import type { PolygonT } from '../../common/types.js';
22
+ import { agerrorf } from '../../common/agerr.js';
22
23
 
23
24
  /** @see lib/neatogen/poly.c:BOX */
24
25
  export const POLY_BOX = 1;
@@ -258,7 +259,7 @@ export function makeAddPoly(n: Node, xmargin: number, ymargin: number): Poly | n
258
259
  case ShapeKind.SH_POINT:
259
260
  return finishPoly(genRound(n, xmargin, ymargin), POLY_CIRCLE);
260
261
  default:
261
- console.error(`Error: makeAddPoly: unknown shape type ${shapeName(n)}\n`);
262
+ agerrorf(`makeAddPoly: unknown shape type ${shapeName(n)}\n`);
262
263
  return null;
263
264
  }
264
265
  }
@@ -296,7 +297,7 @@ function baseShape(n: Node): { verts: Point[]; kind: number } | null {
296
297
  case ShapeKind.SH_POINT:
297
298
  return { verts: genRound(n, 0, 0), kind: POLY_CIRCLE };
298
299
  default:
299
- console.error(`Error: makePoly: unknown shape type ${shapeName(n)}\n`);
300
+ agerrorf(`makePoly: unknown shape type ${shapeName(n)}\n`);
300
301
  return null;
301
302
  }
302
303
  }
@@ -17,6 +17,7 @@ import type { RkState } from '../../util/mt19937.js';
17
17
  import { drand48 } from '../../common/random.js';
18
18
  import { lateDouble } from '../../common/nodeinit.js';
19
19
  import { checkStart, parseStart, INIT_RANDOM, INIT_REGULAR } from './start.js';
20
+ import { agerrLineOn } from '../../common/agerr.js';
20
21
 
21
22
  // ---------------------------------------------------------------------------
22
23
  // Model constants — @see lib/neatogen/defs.h
@@ -407,13 +408,15 @@ export function buildUnfixed(g: Graph, n: number): boolean[] {
407
408
  */
408
409
  export function resolveModel(model: number): number {
409
410
  if (model === MODEL_CIRCUIT) {
410
- console.warn(
411
+ agerrLineOn(
412
+ 'warn',
411
413
  'circuit model not yet supported in Gmode=sgd, reverting to shortpath model',
412
414
  );
413
415
  return MODEL_SHORTPATH;
414
416
  }
415
417
  if (model === MODEL_MDS) {
416
- console.warn(
418
+ agerrLineOn(
419
+ 'warn',
417
420
  'mds model not yet supported in Gmode=sgd, reverting to shortpath model',
418
421
  );
419
422
  return MODEL_SHORTPATH;
@@ -13,12 +13,14 @@
13
13
  import { RenderError } from '../../errors.js';
14
14
  import type { Graph } from '../../model/graph.js';
15
15
  import { srand48 } from '../../common/random.js';
16
+ import { registerRenderState } from '../../common/render-state.js';
16
17
  import { mapbool, isACluster } from '../dot/rank.js';
17
18
  import { getPack } from '../pack/index.js';
18
19
  import {
19
20
  MODE_KK, MODE_MAJOR, MODE_HIER, MODE_IPSEP, MODE_SGD,
20
21
  MODEL_CIRCUIT, MODEL_SUBSET,
21
22
  } from './init.js';
23
+ import { agerrLineOn } from '../../common/agerr.js';
22
24
 
23
25
  /** @see lib/neatogen/neato.h:INIT_SELF */
24
26
  export const INIT_SELF = 0;
@@ -41,11 +43,12 @@ const WARN_POS_IGNORED = 'node positions are ignored unless start=random';
41
43
  const WARN_SELF_IGNORED = 'start=0 not supported with mode=self - ignored';
42
44
 
43
45
  /**
44
- * Roots already warned about start=self under KK/SGD. C warns once per process
45
- * (stuff.c:330 static atomic_flag); one render is one C process here, so the
46
- * flag is per root graph rather than module state (multi-diagram safety).
46
+ * C's `static atomic_flag once` for the start=self warning under KK/SGD: once
47
+ * per process, so once per render here (AD-4 resets it at render entry).
48
+ * @see lib/neatogen/stuff.c:330
47
49
  */
48
- const selfWarned = new WeakSet<object>();
50
+ const selfOnce = { warned: false };
51
+ registerRenderState(() => { selfOnce.warned = false; });
49
52
  /** Roots already warned about the KK circuit fallback (one warning per graph). */
50
53
  const circuitWarned = new WeakSet<object>();
51
54
 
@@ -96,7 +99,7 @@ export function checkStart(
96
99
  ): number {
97
100
  const { init, seed } = parseStart(g.root.attrs.get('start'), dflt);
98
101
  if (init !== INIT_RANDOM && [...g.nodes.values()].some((n) => n.attrs.has('pos'))) {
99
- console.warn(WARN_POS_IGNORED);
102
+ agerrLineOn('warn', WARN_POS_IGNORED);
100
103
  }
101
104
  if (init === INIT_REGULAR) initRegular(g, nG);
102
105
  srand48(seed);
@@ -141,7 +144,7 @@ export function neatoMode(g: Graph): number {
141
144
  if (str === undefined || str === '') return MODE_MAJOR;
142
145
  const mode = modeByName().get(str);
143
146
  if (mode !== undefined) return mode;
144
- console.warn(`Illegal value ${str} for attribute "mode" in graph ${g.root.name} - ignored`);
147
+ agerrLineOn('warn', `Illegal value ${str} for attribute "mode" in graph ${g.root.name} - ignored`);
145
148
  return MODE_MAJOR;
146
149
  }
147
150
 
@@ -161,9 +164,9 @@ function checkStartAttr(g: Graph, mode: number): void {
161
164
  // @see lib/neatogen/neatoinit.c:1095, lib/neatogen/stress.c:884 (smart_ini)
162
165
  if (mode === MODE_MAJOR || mode === MODE_IPSEP) unsupported('start', value ?? '', 'smart initialisation');
163
166
  // @see lib/neatogen/stuff.c:330 (KK and SGD warn once and ignore it)
164
- if ((mode === MODE_KK || mode === MODE_SGD) && !selfWarned.has(g.root)) {
165
- selfWarned.add(g.root);
166
- console.warn(WARN_SELF_IGNORED);
167
+ if ((mode === MODE_KK || mode === MODE_SGD) && !selfOnce.warned) {
168
+ selfOnce.warned = true;
169
+ agerrLineOn('warn', WARN_SELF_IGNORED);
167
170
  }
168
171
  }
169
172
 
@@ -189,7 +192,8 @@ function kkCircuitFallsBack(g: Graph): boolean {
189
192
  function warnCircuitDisconnected(g: Graph): void {
190
193
  if (circuitWarned.has(g.root)) return;
191
194
  circuitWarned.add(g.root);
192
- console.warn(
195
+ agerrLineOn(
196
+ 'warn',
193
197
  `graph ${g.root.name} is disconnected. Hence, the circuit model\n`
194
198
  + 'is undefined. Reverting to the shortest path model.\n'
195
199
  + 'Alternatively, consider running neato using -Gpack=true or decomposing\n'
@@ -20,6 +20,7 @@ import {
20
20
  } from "./sgraph.js";
21
21
  import { convertSPtoRoute, assignSegs, assignTracks, vtrack, htrack } from "./ortho-route.js";
22
22
  import { gvQsort } from "../util/bsd-qsort.js";
23
+ import { agerrLineOn } from '../common/agerr.js';
23
24
 
24
25
  export type { OrthoGraph, OrthoEdge, OrthoNode, OrthoPoint, ClipAndInstallFn };
25
26
  export { SEED } from "./partition.js";
@@ -144,7 +145,8 @@ export function orthoEdges(
144
145
  clipAndInstall?: ClipAndInstallFn,
145
146
  ): number {
146
147
  if (useLbls) {
147
- console.warn(
148
+ agerrLineOn(
149
+ 'warn',
148
150
  "Orthogonal edges do not currently handle edge labels. Try using xlabels.\n",
149
151
  );
150
152
  useLbls = false; // eslint-disable-line no-param-reassign
package/src/parser/dot.js CHANGED
@@ -3,6 +3,9 @@
3
3
  // https://peggyjs.org/
4
4
 
5
5
 
6
+
7
+ import { agwarningf } from "../common/agerr.js";
8
+
6
9
  class peg$SyntaxError extends SyntaxError {
7
10
  constructor(message, expected, found, location) {
8
11
  super(message);
@@ -286,7 +289,7 @@ function peg$parse(input, options) {
286
289
  function peg$f13(t, m, attrs) {
287
290
  // grammar.y:attrstmt calls nomacros() when a macro name is present;
288
291
  // the attributes are still applied unchanged.
289
- if (m) console.error("Warning: attribute macros not implemented");
292
+ if (m) agwarningf("attribute macros not implemented", "error");
290
293
  return { type: "attr", target: t, attrs: attrs };
291
294
  }
292
295
  function peg$f14(a) { return { type: "assign", key: a.key, value: a.value }; }
@@ -8,6 +8,10 @@
8
8
  // Architecture decision AD-11: this file is the canonical faithfulness
9
9
  // artifact; every production cites its Bison rule or Flex token.
10
10
 
11
+ {{
12
+ import { agwarningf } from "../common/agerr.js";
13
+ }}
14
+
11
15
  // ── Top-level rule ───────────────────────────────────────────────────────────
12
16
  // grammar.y: graph : hdr body | error | /* empty */
13
17
  // `end` is the offset just past the graph's closing brace: the last input the
@@ -71,7 +75,7 @@ AttrStmt
71
75
  {
72
76
  // grammar.y:attrstmt calls nomacros() when a macro name is present;
73
77
  // the attributes are still applied unchanged.
74
- if (m) console.error("Warning: attribute macros not implemented");
78
+ if (m) agwarningf("attribute macros not implemented", "error");
75
79
  return { type: "attr", target: t, attrs: attrs };
76
80
  }
77
81
  / a:AttrAssignment
@@ -18,6 +18,7 @@ import { syntaxErrorMessage } from './scan-token.js';
18
18
  import type { ParsedGraph } from './ast.js';
19
19
  import type { GvErrorCode, GvExpectation } from '../errors.js';
20
20
  import { DotEngineError, friendlyMessageFor, invalidArgType } from '../errors.js';
21
+ import { agerrFinished } from '../common/agerr.js';
21
22
 
22
23
  // ── ParseError ────────────────────────────────────────────────────────────────
23
24
 
@@ -233,7 +234,7 @@ export function isPeggyError(err: unknown): err is {
233
234
  function warnNumberAmbiguities(src: string, upTo: number): void {
234
235
  for (const a of scanNumberAmbiguities(src, Stripper.strip(src))) {
235
236
  if (a.offset > upTo) break;
236
- console.error(numberAmbiguityMessage(a));
237
+ agerrFinished(numberAmbiguityMessage(a), 'error');
237
238
  }
238
239
  }
239
240
 
@@ -250,7 +251,7 @@ export function parse(src: string): Graph {
250
251
  if (isPeggyError(err)) {
251
252
  const start = err.location.start;
252
253
  warnNumberAmbiguities(src, start.offset);
253
- console.error(syntaxErrorMessage(src, start.offset));
254
+ agerrFinished(syntaxErrorMessage(src, start.offset));
254
255
  const code: GvErrorCode =
255
256
  err.found === null ? 'SYNTAX_UNEXPECTED_EOF' : 'SYNTAX_ERROR';
256
257
  throw new ParseError(
@@ -282,7 +283,7 @@ export function parse(src: string): Graph {
282
283
  const offset = err instanceof ParseError ? err.location.offset : undefined;
283
284
  if (offset !== undefined) {
284
285
  warnNumberAmbiguities(src, offset);
285
- console.error(syntaxErrorMessage(src, offset));
286
+ agerrFinished(syntaxErrorMessage(src, offset));
286
287
  }
287
288
  throw err;
288
289
  }