@knowvah/dot-engine 2.6.11 → 2.7.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 (270) hide show
  1. package/README.md +1 -1
  2. package/dist/api.js +18 -4
  3. package/dist/api.js.map +3 -3
  4. package/dist/async/render-async.d.ts.map +1 -1
  5. package/dist/common/agerr.d.ts +37 -0
  6. package/dist/common/agerr.d.ts.map +1 -0
  7. package/dist/common/arrows-shapes-poly.d.ts.map +1 -1
  8. package/dist/common/color.d.ts.map +1 -1
  9. package/dist/common/crand.d.ts +0 -20
  10. package/dist/common/crand.d.ts.map +1 -1
  11. package/dist/common/emit-once.d.ts +6 -0
  12. package/dist/common/emit-once.d.ts.map +1 -0
  13. package/dist/common/graph-init.d.ts.map +1 -1
  14. package/dist/common/htmltable-attr-warn.d.ts.map +1 -1
  15. package/dist/common/htmltable-emit-fill.d.ts +29 -2
  16. package/dist/common/htmltable-emit-fill.d.ts.map +1 -1
  17. package/dist/common/htmltable-emit-rules.d.ts.map +1 -1
  18. package/dist/common/htmltable-emit.d.ts.map +1 -1
  19. package/dist/common/htmltable-lex-expat.d.ts +1 -6
  20. package/dist/common/htmltable-lex-expat.d.ts.map +1 -1
  21. package/dist/common/htmltable-pos-runs.d.ts +7 -4
  22. package/dist/common/htmltable-pos-runs.d.ts.map +1 -1
  23. package/dist/common/htmltable.d.ts +1 -7
  24. package/dist/common/htmltable.d.ts.map +1 -1
  25. package/dist/common/libm-trig.d.ts +5 -0
  26. package/dist/common/libm-trig.d.ts.map +1 -0
  27. package/dist/common/multicolor.d.ts +21 -13
  28. package/dist/common/multicolor.d.ts.map +1 -1
  29. package/dist/common/poly-gencode.d.ts +1 -2
  30. package/dist/common/poly-gencode.d.ts.map +1 -1
  31. package/dist/common/poly-init.d.ts.map +1 -1
  32. package/dist/common/postproc.d.ts.map +1 -1
  33. package/dist/common/random.d.ts.map +1 -1
  34. package/dist/common/render-state.d.ts +8 -0
  35. package/dist/common/render-state.d.ts.map +1 -0
  36. package/dist/common/splines-clip.d.ts +7 -0
  37. package/dist/common/splines-clip.d.ts.map +1 -1
  38. package/dist/common/splines-geom.d.ts +8 -0
  39. package/dist/common/splines-geom.d.ts.map +1 -1
  40. package/dist/common/splines-routespl.d.ts.map +1 -1
  41. package/dist/common/textmeasure-lookup.d.ts.map +1 -1
  42. package/dist/common/textmeasure.d.ts.map +1 -1
  43. package/dist/gvc/anchor.d.ts.map +1 -1
  44. package/dist/gvc/context.d.ts +6 -0
  45. package/dist/gvc/context.d.ts.map +1 -1
  46. package/dist/gvc/device-cluster.d.ts +15 -0
  47. package/dist/gvc/device-cluster.d.ts.map +1 -1
  48. package/dist/gvc/device-labels.d.ts +14 -0
  49. package/dist/gvc/device-labels.d.ts.map +1 -1
  50. package/dist/gvc/device.d.ts.map +1 -1
  51. package/dist/gvc/first-layer.d.ts +9 -0
  52. package/dist/gvc/first-layer.d.ts.map +1 -0
  53. package/dist/gvc/gvrender-set-style.d.ts +28 -0
  54. package/dist/gvc/gvrender-set-style.d.ts.map +1 -0
  55. package/dist/gvc/usershape.d.ts +18 -0
  56. package/dist/gvc/usershape.d.ts.map +1 -1
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/index.js +3169 -1296
  59. package/dist/index.js.map +4 -4
  60. package/dist/layout/dot/cluster.d.ts.map +1 -1
  61. package/dist/layout/dot/compound.d.ts.map +1 -1
  62. package/dist/layout/dot/conc.d.ts.map +1 -1
  63. package/dist/layout/dot/decomp.d.ts.map +1 -1
  64. package/dist/layout/dot/edge-route-clip.d.ts +32 -14
  65. package/dist/layout/dot/edge-route-clip.d.ts.map +1 -1
  66. package/dist/layout/dot/edge-route-routing.d.ts.map +1 -1
  67. package/dist/layout/dot/mincross-build.d.ts.map +1 -1
  68. package/dist/layout/dot/mincross-cross.d.ts.map +1 -1
  69. package/dist/layout/dot/rank.d.ts.map +1 -1
  70. package/dist/layout/dot/splines.d.ts.map +1 -1
  71. package/dist/layout/fdp/index.d.ts.map +1 -1
  72. package/dist/layout/fdp/tlayout-parms.d.ts.map +1 -1
  73. package/dist/layout/fdp/xlayout.d.ts.map +1 -1
  74. package/dist/layout/neato/__fixtures__/dijkstra-f.d.ts +11 -0
  75. package/dist/layout/neato/__fixtures__/dijkstra-f.d.ts.map +1 -0
  76. package/dist/layout/neato/__fixtures__/hier-imds.d.ts +36 -0
  77. package/dist/layout/neato/__fixtures__/hier-imds.d.ts.map +1 -0
  78. package/dist/layout/neato/__fixtures__/hier-majorization.d.ts +32 -0
  79. package/dist/layout/neato/__fixtures__/hier-majorization.d.ts.map +1 -0
  80. package/dist/layout/neato/cdt-surface.d.ts.map +1 -1
  81. package/dist/layout/neato/compute-hierarchy.d.ts +24 -0
  82. package/dist/layout/neato/compute-hierarchy.d.ts.map +1 -0
  83. package/dist/layout/neato/conjgrad-mkernel.d.ts +20 -0
  84. package/dist/layout/neato/conjgrad-mkernel.d.ts.map +1 -0
  85. package/dist/layout/neato/conjgrad.d.ts +19 -0
  86. package/dist/layout/neato/conjgrad.d.ts.map +1 -1
  87. package/dist/layout/neato/constrained-majorization.d.ts +26 -0
  88. package/dist/layout/neato/constrained-majorization.d.ts.map +1 -0
  89. package/dist/layout/neato/dijkstra.d.ts +33 -16
  90. package/dist/layout/neato/dijkstra.d.ts.map +1 -1
  91. package/dist/layout/neato/edge-len.d.ts.map +1 -1
  92. package/dist/layout/neato/fdp-adjust.d.ts.map +1 -1
  93. package/dist/layout/neato/gts-triangle.d.ts +14 -0
  94. package/dist/layout/neato/gts-triangle.d.ts.map +1 -0
  95. package/dist/layout/neato/init.d.ts +6 -22
  96. package/dist/layout/neato/init.d.ts.map +1 -1
  97. package/dist/layout/neato/kk.d.ts.map +1 -1
  98. package/dist/layout/neato/kkutils.d.ts +20 -0
  99. package/dist/layout/neato/kkutils.d.ts.map +1 -0
  100. package/dist/layout/neato/make-graph-data.d.ts +43 -0
  101. package/dist/layout/neato/make-graph-data.d.ts.map +1 -0
  102. package/dist/layout/neato/matrix-ops.d.ts +46 -0
  103. package/dist/layout/neato/matrix-ops.d.ts.map +1 -1
  104. package/dist/layout/neato/multispline-router.d.ts.map +1 -1
  105. package/dist/layout/neato/multispline.d.ts.map +1 -1
  106. package/dist/layout/neato/opt-arrangement.d.ts +20 -0
  107. package/dist/layout/neato/opt-arrangement.d.ts.map +1 -0
  108. package/dist/layout/neato/poly.d.ts.map +1 -1
  109. package/dist/layout/neato/quad-prog-solve.d.ts +44 -0
  110. package/dist/layout/neato/quad-prog-solve.d.ts.map +1 -0
  111. package/dist/layout/neato/sgd.d.ts.map +1 -1
  112. package/dist/layout/neato/smart-ini-x.d.ts +9 -0
  113. package/dist/layout/neato/smart-ini-x.d.ts.map +1 -0
  114. package/dist/layout/neato/splines.d.ts +1 -1
  115. package/dist/layout/neato/splines.d.ts.map +1 -1
  116. package/dist/layout/neato/start.d.ts +4 -1
  117. package/dist/layout/neato/start.d.ts.map +1 -1
  118. package/dist/layout/neato/stress.d.ts +8 -0
  119. package/dist/layout/neato/stress.d.ts.map +1 -1
  120. package/dist/model/edge.d.ts +1 -1
  121. package/dist/model/edge.d.ts.map +1 -1
  122. package/dist/model/lazy-attrs.d.ts +10 -0
  123. package/dist/model/lazy-attrs.d.ts.map +1 -0
  124. package/dist/model/node.d.ts +1 -1
  125. package/dist/model/node.d.ts.map +1 -1
  126. package/dist/ortho/index.d.ts.map +1 -1
  127. package/dist/parser/index.d.ts.map +1 -1
  128. package/dist/render/color-resolve.d.ts +49 -1
  129. package/dist/render/color-resolve.d.ts.map +1 -1
  130. package/dist/render/dot/edge-draw.d.ts +1 -1
  131. package/dist/render/dot/edge-draw.d.ts.map +1 -1
  132. package/dist/render/public.d.ts.map +1 -1
  133. package/dist/render/svg-edge-split.d.ts +2 -0
  134. package/dist/render/svg-edge-split.d.ts.map +1 -1
  135. package/dist/render/svg-multicolor.d.ts +2 -2
  136. package/dist/render/svg-multicolor.d.ts.map +1 -1
  137. package/dist/render/xdot-public.d.ts.map +1 -1
  138. package/dist/render.js +2799 -927
  139. package/dist/render.js.map +4 -4
  140. package/package.json +1 -1
  141. package/src/async/render-async.ts +6 -0
  142. package/src/common/agerr.ts +96 -0
  143. package/src/common/arrows-shapes-poly.ts +19 -5
  144. package/src/common/color.ts +38 -10
  145. package/src/common/crand.ts +8 -2
  146. package/src/common/emit-once.ts +27 -0
  147. package/src/common/graph-init.ts +2 -1
  148. package/src/common/htmltable-attr-warn.ts +3 -4
  149. package/src/common/htmltable-emit-fill.ts +104 -11
  150. package/src/common/htmltable-emit-rules.ts +2 -0
  151. package/src/common/htmltable-emit.ts +4 -3
  152. package/src/common/htmltable-lex-expat.ts +96 -23
  153. package/src/common/htmltable-pos-runs.ts +11 -6
  154. package/src/common/htmltable.ts +28 -117
  155. package/src/common/libm-trig.ts +43 -0
  156. package/src/common/multicolor.ts +88 -24
  157. package/src/common/poly-gencode.ts +30 -10
  158. package/src/common/poly-init.ts +20 -1
  159. package/src/common/postproc.ts +4 -1
  160. package/src/common/random.ts +11 -1
  161. package/src/common/render-state.ts +36 -0
  162. package/src/common/splines-clip.ts +15 -12
  163. package/src/common/splines-geom.ts +8 -4
  164. package/src/common/splines-routespl.ts +2 -1
  165. package/src/common/textmeasure-lookup.ts +9 -2
  166. package/src/common/textmeasure.ts +45 -2
  167. package/src/gvc/anchor.ts +1 -12
  168. package/src/gvc/context.ts +8 -0
  169. package/src/gvc/device-cluster.ts +93 -10
  170. package/src/gvc/device-labels.ts +27 -0
  171. package/src/gvc/device.ts +34 -8
  172. package/src/gvc/first-layer.ts +26 -0
  173. package/src/gvc/gvrender-set-style.ts +114 -0
  174. package/src/gvc/usershape.ts +26 -0
  175. package/src/index.ts +3 -0
  176. package/src/layout/dot/cluster.ts +2 -1
  177. package/src/layout/dot/compound.ts +3 -4
  178. package/src/layout/dot/conc.ts +6 -5
  179. package/src/layout/dot/decomp.ts +3 -0
  180. package/src/layout/dot/edge-route-clip.ts +46 -28
  181. package/src/layout/dot/edge-route-routing.ts +11 -3
  182. package/src/layout/dot/fastgr.ts +2 -2
  183. package/src/layout/dot/mincross-build.ts +2 -0
  184. package/src/layout/dot/mincross-cross.ts +2 -0
  185. package/src/layout/dot/rank.ts +2 -0
  186. package/src/layout/dot/splines.ts +2 -1
  187. package/src/layout/fdp/index.ts +2 -1
  188. package/src/layout/fdp/tlayout-parms.ts +9 -2
  189. package/src/layout/fdp/xlayout.ts +10 -2
  190. package/src/layout/neato/__fixtures__/dijkstra-f.ts +17 -0
  191. package/src/layout/neato/__fixtures__/hier/c1554.gv +33 -0
  192. package/src/layout/neato/__fixtures__/hier/c1554.plain +26 -0
  193. package/src/layout/neato/__fixtures__/hier/c1554.xdot +161 -0
  194. package/src/layout/neato/__fixtures__/hier/cases/cycle.gv +1 -0
  195. package/src/layout/neato/__fixtures__/hier/cases/cycle.plain +10 -0
  196. package/src/layout/neato/__fixtures__/hier/cases/cycle.stderr +0 -0
  197. package/src/layout/neato/__fixtures__/hier/cases/dirnone.gv +1 -0
  198. package/src/layout/neato/__fixtures__/hier/cases/dirnone.plain +11 -0
  199. package/src/layout/neato/__fixtures__/hier/cases/dirnone.stderr +0 -0
  200. package/src/layout/neato/__fixtures__/hier/cases/epsilon.gv +1 -0
  201. package/src/layout/neato/__fixtures__/hier/cases/epsilon.plain +13 -0
  202. package/src/layout/neato/__fixtures__/hier/cases/epsilon.stderr +0 -0
  203. package/src/layout/neato/__fixtures__/hier/cases/levelsgap.gv +1 -0
  204. package/src/layout/neato/__fixtures__/hier/cases/levelsgap.plain +8 -0
  205. package/src/layout/neato/__fixtures__/hier/cases/levelsgap.stderr +0 -0
  206. package/src/layout/neato/__fixtures__/hier/cases/mds.gv +1 -0
  207. package/src/layout/neato/__fixtures__/hier/cases/mds.plain +10 -0
  208. package/src/layout/neato/__fixtures__/hier/cases/mds.stderr +0 -0
  209. package/src/layout/neato/__fixtures__/hier/cases/multiedge.gv +1 -0
  210. package/src/layout/neato/__fixtures__/hier/cases/multiedge.plain +10 -0
  211. package/src/layout/neato/__fixtures__/hier/cases/multiedge.stderr +0 -0
  212. package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.gv +1 -0
  213. package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.plain +8 -0
  214. package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.stderr +0 -0
  215. package/src/layout/neato/__fixtures__/hier/cases/startrandom.gv +1 -0
  216. package/src/layout/neato/__fixtures__/hier/cases/startrandom.plain +10 -0
  217. package/src/layout/neato/__fixtures__/hier/cases/startrandom.stderr +0 -0
  218. package/src/layout/neato/__fixtures__/hier/cases/startregular.gv +1 -0
  219. package/src/layout/neato/__fixtures__/hier/cases/startregular.plain +10 -0
  220. package/src/layout/neato/__fixtures__/hier/cases/startregular.stderr +0 -0
  221. package/src/layout/neato/__fixtures__/hier/cases/tdist.gv +1 -0
  222. package/src/layout/neato/__fixtures__/hier/cases/tdist.plain +13 -0
  223. package/src/layout/neato/__fixtures__/hier/cases/tdist.stderr +0 -0
  224. package/src/layout/neato/__fixtures__/hier/cases/undirected.gv +1 -0
  225. package/src/layout/neato/__fixtures__/hier/cases/undirected.plain +10 -0
  226. package/src/layout/neato/__fixtures__/hier/cases/undirected.stderr +0 -0
  227. package/src/layout/neato/__fixtures__/hier/hier-probe.gv +1 -0
  228. package/src/layout/neato/__fixtures__/hier/hier-probe.plain +16 -0
  229. package/src/layout/neato/__fixtures__/hier/hier-probe.xdot +62 -0
  230. package/src/layout/neato/__fixtures__/hier-imds.ts +151 -0
  231. package/src/layout/neato/__fixtures__/hier-majorization.ts +145 -0
  232. package/src/layout/neato/cdt-surface.ts +12 -22
  233. package/src/layout/neato/compute-hierarchy.ts +90 -0
  234. package/src/layout/neato/conjgrad-mkernel.ts +141 -0
  235. package/src/layout/neato/conjgrad.ts +208 -0
  236. package/src/layout/neato/constrained-majorization.ts +479 -0
  237. package/src/layout/neato/dijkstra.ts +218 -92
  238. package/src/layout/neato/edge-len.ts +2 -1
  239. package/src/layout/neato/fdp-adjust.ts +3 -2
  240. package/src/layout/neato/gts-triangle.ts +42 -0
  241. package/src/layout/neato/init.ts +68 -68
  242. package/src/layout/neato/kk.ts +2 -1
  243. package/src/layout/neato/kkutils.ts +96 -0
  244. package/src/layout/neato/make-graph-data.ts +166 -0
  245. package/src/layout/neato/matrix-ops.ts +138 -0
  246. package/src/layout/neato/multispline-router.ts +3 -3
  247. package/src/layout/neato/multispline.ts +3 -2
  248. package/src/layout/neato/opt-arrangement.ts +86 -0
  249. package/src/layout/neato/poly.ts +3 -2
  250. package/src/layout/neato/quad-prog-solve.ts +489 -0
  251. package/src/layout/neato/sgd.ts +5 -2
  252. package/src/layout/neato/smart-ini-x.ts +503 -0
  253. package/src/layout/neato/splines.ts +4 -4
  254. package/src/layout/neato/start.ts +19 -14
  255. package/src/layout/neato/stress.ts +28 -0
  256. package/src/model/edge.ts +3 -2
  257. package/src/model/lazy-attrs.ts +36 -0
  258. package/src/model/node.ts +3 -2
  259. package/src/ortho/index.ts +2 -1
  260. package/src/parser/dot.js +4 -1
  261. package/src/parser/dot.pegjs +5 -1
  262. package/src/parser/index.ts +4 -3
  263. package/src/render/color-resolve.ts +193 -4
  264. package/src/render/dot/edge-draw.ts +6 -3
  265. package/src/render/dot.ts +1 -1
  266. package/src/render/public.ts +2 -0
  267. package/src/render/svg-edge-split.ts +6 -4
  268. package/src/render/svg-multicolor.ts +4 -3
  269. package/src/render/svg-parallel-edge.ts +2 -2
  270. package/src/render/xdot-public.ts +2 -0
@@ -22,6 +22,7 @@ import { FillType } from '../gvc/context.js';
22
22
  import { emitHtmlLabel } from './htmltable-emit.js';
23
23
  import { transformPoint } from '../gvc/device.js';
24
24
  import { nodeAttr } from './poly-init.js';
25
+ import { gvrenderSetStyle, checkStyleTokens } from '../gvc/gvrender-set-style.js';
25
26
  import { substObjAnchor, preprocessTooltip } from './subst.js';
26
27
  import type { ResolvedFill, PolyStyleFlags } from './style-resolve.js';
27
28
  import {
@@ -33,7 +34,8 @@ import {
33
34
  styleTokens,
34
35
  } from './style-resolve.js';
35
36
  import { stripedBox, wedgedEllipse } from '../render/svg-multicolor.js';
36
- import { resolveRenderColor } from '../render/color-resolve.js';
37
+ import { resolveRenderColor, reportColorNames } from '../render/color-resolve.js';
38
+ import { htmlLabelColors } from './htmltable-emit-fill.js';
37
39
  import { svgNodeId } from '../render/svg-id.js';
38
40
  import { drawRoundCorners, mcircleHack, underlineDraw } from './poly-shapes.js';
39
41
 
@@ -95,6 +97,7 @@ interface RingCtx {
95
97
  job: RenderJob;
96
98
  style: PolyStyleFlags;
97
99
  fillcolor: string;
100
+ nodeName: string; // agnameof(n): the `in node %s` AGPREV of a parseSegs rv > 1
98
101
  shape: number;
99
102
  diagonals: boolean;
100
103
  rounded: boolean;
@@ -113,7 +116,7 @@ function renderEllipseRing(
113
116
  const pf = ring.map(
114
117
  (v) => transformPoint({ x: v.x + coord.x, y: v.y + coord.y }, ctx.job),
115
118
  );
116
- wedgedEllipse(ctx.job, pf, ctx.fillcolor, ctx.renderer);
119
+ wedgedEllipse(ctx.job, pf, ctx.fillcolor, ctx.renderer, () => `in node ${ctx.nodeName}`);
117
120
  renderEllipse(ring, coord, ctx.renderer, ctx.job, false); // boundary unfilled
118
121
  } else {
119
122
  renderEllipse(ring, coord, ctx.renderer, ctx.job, filled);
@@ -123,14 +126,12 @@ function renderEllipseRing(
123
126
  }
124
127
 
125
128
  /**
126
- * Render one polygon periphery ring, dispatching to stripedBox when
127
- * style.striped + j==0.
129
+ * Striped j===0 ring: the multicolor box plus an unfilled boundary polygon.
128
130
  * @see lib/common/shapes.c:poly_gencode :3037-3043
129
131
  */
130
- /** Striped j===0 ring: the multicolor box plus an unfilled boundary polygon. */
131
132
  function renderStripedRing(ring: Point[], coord: Point, ctx: RingCtx): void {
132
133
  const af = ring.map((v) => transformPoint({ x: v.x + coord.x, y: v.y + coord.y }, ctx.job));
133
- stripedBox(ctx.job, af, ctx.fillcolor, true, ctx.renderer);
134
+ stripedBox(ctx.job, af, ctx.fillcolor, true, ctx.renderer, () => `in node ${ctx.nodeName}`);
134
135
  renderPolygon(ring, coord, ctx.renderer, ctx.job, false); // boundary unfilled
135
136
  }
136
137
 
@@ -191,6 +192,8 @@ export function renderLabel(
191
192
  job: RenderJob,
192
193
  ): void {
193
194
  if (label.u.kind !== 'txt' || label.u.nspans < 1) return;
195
+ // gvrender_set_pencolor(job, lp->fontcolor), once, before the spans.
196
+ reportColorNames([label.fontcolor]);
194
197
  label.pos = coord;
195
198
  label.set = true;
196
199
  // First-span baseline per valign (labelloc). @see lib/common/labels.c:240-251
@@ -227,6 +230,7 @@ function renderNodeLabel(
227
230
  // Route the HTML label's box/fill/text ops into the node LABEL emit-state
228
231
  // (→ _ldraw_ for xdot), not the node DRAW state. SVG ignores emit_state.
229
232
  const html = label.u.html as PlacedHtml;
233
+ reportColorNames(htmlLabelColors(html));
230
234
  withLabelEmitState(job, () => emitHtmlLabel(html, coord, renderer, job));
231
235
  } else {
232
236
  renderLabel(label, coord, renderer, job);
@@ -363,12 +367,27 @@ function applyPenState(obj: ObjState, styleAttr: string | undefined,
363
367
  export function applyNodeStyle(obj: ObjState, n: Node, job: RenderJob): boolean {
364
368
  const styleAttr = nodeAttr(n, n.root, 'style');
365
369
  const colorAttr = nodeAttr(n, n.root, 'color');
370
+ // stylenode: gvrender_set_style(job, checkStyle list), before any colour is
371
+ // resolved; point_gencode passes its own fixed list instead (never unknown).
372
+ // @see lib/common/shapes.c:536 stylenode · :3257 point_gencode
373
+ if (!isPointNode(n)) {
374
+ const shape = n.info.shape as ShapeDesc | undefined;
375
+ gvrenderSetStyle(checkStyleTokens(styleTokens(styleAttr), shape?.polygon ?? null));
376
+ }
366
377
  // The node's `colorscheme` window is NOT opened here: C opens it once around
367
378
  // the whole node emission (emit_begin_node..emit_end_node), which covers both
368
379
  // this color block and the label textspans. Both callers of this function are
369
380
  // codefns, so they already run inside that window. @see gvc/device.ts
381
+ const penwidth = nodeAttr(n, n.root, 'penwidth');
382
+ // record_gencode resolves the pen (penColor) before the fill; poly_gencode
383
+ // and point_gencode resolve the fill first. Only the order in which an
384
+ // unknown colour is reported differs. @see shapes.c:3890-3892 vs 2985-3004
385
+ if ((n.info.shape as ShapeDesc | undefined)?.kind === ShapeKind.SH_RECORD) {
386
+ applyPenState(obj, styleAttr, colorAttr, penwidth);
387
+ return applyFillState(obj, n, job);
388
+ }
370
389
  const filled = applyFillState(obj, n, job);
371
- applyPenState(obj, styleAttr, colorAttr, nodeAttr(n, n.root, 'penwidth'));
390
+ applyPenState(obj, styleAttr, colorAttr, penwidth);
372
391
  return filled;
373
392
  }
374
393
 
@@ -379,7 +398,8 @@ export function applyNodeStyle(obj: ObjState, n: Node, job: RenderJob): boolean
379
398
  */
380
399
  function prepareDrawPoly(poly: PolygonT, filled: boolean, obj: ObjState): PolygonT {
381
400
  if (poly.peripheries !== 0 || !filled) return poly;
382
- obj.penColor = { type: 'string', s: 'transparent' };
401
+ // gvrender_set_pencolor resolves (and may report) the name like any other.
402
+ obj.penColor = resolveRenderColor('transparent');
383
403
  return { ...poly, peripheries: 1 };
384
404
  }
385
405
 
@@ -434,6 +454,7 @@ function resolveNodeDrawCtx(job: RenderJob, n: Node): NodeDrawCtx | null {
434
454
  job,
435
455
  style: styleFlags,
436
456
  fillcolor: nodeAttr(n, n.root, 'fillcolor') ?? '',
457
+ nodeName: n.name,
437
458
  ...specialCornerFlags(drawPoly.option, styleFlags),
438
459
  };
439
460
  return { poly: drawPoly, coord, filled, ringCtx };
@@ -455,8 +476,7 @@ function specialCornerFlags(
455
476
  }
456
477
 
457
478
  /**
458
- * Render node shape (polygon/ellipse) and label.
459
- * Shape codefn — called from walkNodes.
479
+ * Render node shape (polygon/ellipse) and label; shape codefn, from walkNodes.
460
480
  * @see lib/common/shapes.c:poly_gencode
461
481
  */
462
482
  export function polyGencode(rawJob: unknown, rawNode: unknown): void {
@@ -24,6 +24,8 @@ import {
24
24
  DEFAULT_COLOR,
25
25
  } from './make-label.js';
26
26
  import { computeVertices } from './poly-vertices.js';
27
+ import { agwarningf } from './agerr.js';
28
+ import { imageIsSupplied, imageOpenWarning } from '../gvc/usershape.js';
27
29
 
28
30
  // Default half-dimensions in points (72 dpi, 0.75 in wide, 0.5 in tall)
29
31
  const DEFAULT_LW = 27;
@@ -110,6 +112,22 @@ export function buildNodeLabel(n: Node, g: Graph, measurer: TextMeasurer): void
110
112
  n.info.label = makeAnyLabel(content, isHtml, font, measurer, n);
111
113
  }
112
114
 
115
+ /**
116
+ * poly_init's `image` branch (a non-custom shape): when the caller supplies no
117
+ * image, gvusershape_size fails and C warns after gvusershape_open's own
118
+ * warning. The port never sizes node images.
119
+ * @see lib/common/shapes.c:2043-2046
120
+ */
121
+ function warnMissingImage(n: Node, g: Graph, shapefile: string | undefined): void {
122
+ if (shapefile !== undefined && shapefile !== '' && nodeAttr(n, g, 'shape') !== 'epsf') return;
123
+ const image = nodeAttr(n, g, 'image');
124
+ if (image === undefined || image === '') return;
125
+ const gvc = g.root.info.gvc;
126
+ if (imageIsSupplied(image, gvc?.imageSizer, gvc?.imageResolver)) return;
127
+ agwarningf(imageOpenWarning(image));
128
+ agwarningf(`No or improper image="${image}" for node "${n.name}"`);
129
+ }
130
+
113
131
  export function polyInit(n: Node, g: Graph, measurer: TextMeasurer): void {
114
132
  const shapeName = nodeAttr(n, g, 'shape') ?? 'ellipse';
115
133
  const shapefile = nodeAttr(n, g, 'shapefile');
@@ -117,12 +135,13 @@ export function polyInit(n: Node, g: Graph, measurer: TextMeasurer): void {
117
135
  // C warns when the custom shape's image cannot be sized (headless: always).
118
136
  // @see lib/common/shapes.c:2027-2033
119
137
  if (shapefile !== undefined && shapefile !== '' && shapeName !== 'epsf') {
120
- console.error(`Warning: No or improper shapefile="${shapefile}" for node "${n.name}"`);
138
+ agwarningf(`No or improper shapefile="${shapefile}" for node "${n.name}"`);
121
139
  }
122
140
  if ((n.info.shape as ShapeDesc).kind === ShapeKind.SH_RECORD) {
123
141
  if (n.info.shape_info === undefined) recordNodeInit(n, g, measurer);
124
142
  return;
125
143
  }
144
+ warnMissingImage(n, g, shapefile);
126
145
  buildNodeLabel(n, g, measurer);
127
146
 
128
147
  // Layout-time common_init_node already installed the attr-resolved
@@ -26,6 +26,8 @@ import {
26
26
  RANKDIR_TB, RANKDIR_LR, RANKDIR_BT, RANKDIR_RL,
27
27
  } from '../layout/dot/init.js';
28
28
  import { addXLabels } from './xlabels-place.js';
29
+ import { registerRenderState } from './render-state.js';
30
+ import { agerrorf } from './agerr.js';
29
31
 
30
32
  /** Concentrated multi-edge duplicate — never drawn. @see lib/common/const.h:IGNORED */
31
33
  const IGNORED_EDGE = 6;
@@ -38,6 +40,7 @@ const IGNORED_EDGE = 6;
38
40
 
39
41
  let Rankdir = 0;
40
42
  let Offset: Point = { x: 0, y: 0 };
43
+ registerRenderState(() => { Rankdir = 0; Offset = { x: 0, y: 0 }; });
41
44
 
42
45
  /** The bezier fields map_edge reads/writes. */
43
46
  type MappedBezier = Pick<Bezier, 'list' | 'sflag' | 'eflag' | 'sp' | 'ep' | 'sArrowOps' | 'eArrowOps'>;
@@ -226,7 +229,7 @@ function translateDrawing(g: Graph): void {
226
229
  // @see lib/common/postproc.c:map_edge ("lost %s %s edge")
227
230
  if (e.info.spl === undefined) {
228
231
  if (!(g.info.concentrate ?? false) && (e.info.edge_type ?? 0) !== IGNORED_EDGE) {
229
- console.warn(`lost ${e.tail.name} ${e.head.name} edge`);
232
+ agerrorf(`lost ${e.tail.name} ${e.head.name} edge`);
230
233
  }
231
234
  continue;
232
235
  }
@@ -14,12 +14,22 @@
14
14
  * @see lib/neatogen/neatoinit.c:checkStart (srand48 call)
15
15
  */
16
16
 
17
+ import { registerRenderState } from './render-state.js';
18
+
17
19
  const A = BigInt('0x5deece66d');
18
20
  const C = BigInt('0xb');
19
21
  const MASK48 = (1n << 48n) - 1n;
20
22
  const TWO48 = 2 ** 48;
23
+ /**
24
+ * Unseeded generator state: libc's X0 = 0x1234ABCD330E (rand48 seed words
25
+ * 0x1234, 0xABCD, 0x330E); verified on the reference machine — an unseeded
26
+ * drand48() returns 0.39646477376027534.
27
+ * @see POSIX drand48(3)
28
+ */
29
+ const UNSEEDED_STATE = 0x1234abcd330en;
21
30
 
22
- let state = 0x330en; // POSIX default: X0 = 0x1234ABCD330E... see srand48 note
31
+ let state = UNSEEDED_STATE;
32
+ registerRenderState(() => { state = UNSEEDED_STATE; });
23
33
 
24
34
  /**
25
35
  * Seed the generator: X = (seed << 16) | 0x330E (low 32 bits of seed).
@@ -0,0 +1,36 @@
1
+ // SPDX-License-Identifier: EPL-2.0
2
+
3
+ /**
4
+ * Per-render scope for C process-level state (AD-4).
5
+ *
6
+ * Native Graphviz renders one graph per `dot` process, so every file static —
7
+ * RNG streams, sequence counters, the emit_once warning set — starts each
8
+ * graph at its C initial value (and gvFreeContext's emit_once_reset clears the
9
+ * warning set). The port's module statics outlive a render, so each module
10
+ * owning such state registers a reset that restores its C initial value, and
11
+ * every public render entry calls {@link resetRenderState} before any other
12
+ * work. Render output is then a function of its input alone.
13
+ *
14
+ * Not registered here: caller-installed hooks (setTextMeasurer,
15
+ * setImageSizer, setImageResolver) and debug hooks — they are configuration,
16
+ * not C process state.
17
+ *
18
+ * @see lib/gvc/gvcontext.c:gvFreeContext (emit_once_reset)
19
+ * @see lib/common/emit.c:emit_once_reset
20
+ */
21
+
22
+ /** Registered resets, run in registration (module evaluation) order. */
23
+ const resets: Array<() => void> = [];
24
+
25
+ /**
26
+ * Register a reset restoring one module's C process state to its initial
27
+ * value. Called once, at module evaluation.
28
+ */
29
+ export function registerRenderState(reset: () => void): void {
30
+ resets.push(reset);
31
+ }
32
+
33
+ /** Restore every registered module's C process state; call first per render. */
34
+ export function resetRenderState(): void {
35
+ for (const reset of resets) reset();
36
+ }
@@ -16,7 +16,7 @@ import type { Edge } from '../model/edge.js';
16
16
  import type { Node } from '../model/node.js';
17
17
  import { approxEqPt, bezierClip, updateBbBz, InsideContext } from './splines-geom.js';
18
18
  import { MILLIPOINT, ARR_NONE } from './splines-constants.js';
19
- import { arrowClipLength } from '../layout/dot/edge-route-clip.js';
19
+ import { arrowClipLength, distFused } from '../layout/dot/edge-route-clip.js';
20
20
  import {
21
21
  arrowDrawOpsForEnd, edgeArrowName, edgeArrowsize,
22
22
  } from '../layout/dot/edge-route-arrow.js';
@@ -25,6 +25,14 @@ import { resolvePenWidth, parseStyleFlags } from './style-resolve.js';
25
25
  /** Normal (filled triangle) arrowhead. @see lib/common/const.h:ARR_TYPE_NORM */
26
26
  export const ARR_NORM = 1;
27
27
 
28
+ /**
29
+ * DIST(p, q) as the oracle compiles it: sqrt(fma(dx, dx, dy * dy)), not
30
+ * Math.hypot (libgvc 16.1 `_arrowEndClip`/`_arrowStartClip`, incl. the inlined
31
+ * sphere_ctx inside(), and `_arrowOrthoClip`).
32
+ * @see lib/common/geom.h:DIST
33
+ */
34
+ export const dist: (p: Point, q: Point) => number = distFused;
35
+
28
36
  // Internal shape accessor type for host objects that may carry a shape.
29
37
  type ShapeHost = { shape?: { fns?: { insidefn?: ((c: InsideContext, p: Point) => boolean) | null } | null } | null };
30
38
 
@@ -123,11 +131,6 @@ class SplineClipHelper {
123
131
  return [sflag, eflag];
124
132
  }
125
133
 
126
- /** DIST(p, q). */
127
- static dist(p: Point, q: Point): number {
128
- return Math.hypot(p.x - q.x, p.y - q.y);
129
- }
130
-
131
134
  /** Sphere inside-fn around `tip` with radius `r` for bezierClip. */
132
135
  static sphereCtx(tip: Point, r: number): {
133
136
  ctx: InsideContext;
@@ -135,7 +138,7 @@ class SplineClipHelper {
135
138
  } {
136
139
  return {
137
140
  ctx: { nodeCoord: { x: 0, y: 0 }, rw: 0, bp: null },
138
- fn: (_c: InsideContext, p: Point) => SplineClipHelper.dist(p, tip) <= r,
141
+ fn: (_c: InsideContext, p: Point) => dist(p, tip) <= r,
139
142
  };
140
143
  }
141
144
 
@@ -170,7 +173,7 @@ class SplineClipHelper {
170
173
  SplineClipHelper.clipArrowsize(e), SplineClipHelper.clipPenwidth(e));
171
174
  spl.eflag = eflag;
172
175
  spl.ep = ps[endp + 3]!;
173
- if (endp > startp && SplineClipHelper.dist(ps[endp]!, ps[endp + 3]!) < elen) {
176
+ if (endp > startp && dist(ps[endp]!, ps[endp + 3]!) < elen) {
174
177
  endp -= 3;
175
178
  }
176
179
  const sp: Point[] = [spl.ep, ps[endp + 2]!, ps[endp + 1]!, ps[endp]!];
@@ -198,7 +201,7 @@ class SplineClipHelper {
198
201
  SplineClipHelper.clipArrowsize(e), SplineClipHelper.clipPenwidth(e));
199
202
  spl.sflag = sflag;
200
203
  spl.sp = ps[startp]!;
201
- if (endp > startp && SplineClipHelper.dist(ps[startp]!, ps[startp + 3]!) < slen) {
204
+ if (endp > startp && dist(ps[startp]!, ps[startp + 3]!) < slen) {
202
205
  startp += 3;
203
206
  }
204
207
  const sp: Point[] = [ps[startp + 3]!, ps[startp + 2]!, ps[startp + 1]!, spl.sp];
@@ -293,7 +296,7 @@ class SplineClipHelper {
293
296
  if (sflag && eflag && endp === startp) {
294
297
  // two arrows on one segment
295
298
  const p = ps[endp]!; const q = ps[endp + 3]!;
296
- const d = SplineClipHelper.dist(p, q);
299
+ const d = dist(p, q);
297
300
  let hlen = hlen0; let tlen = tlen0;
298
301
  if (hlen + tlen >= d) { hlen = tlen = d / 3.0; }
299
302
  const s = { x: p.x, y: p.y }; const t = { x: q.x, y: q.y };
@@ -315,7 +318,7 @@ class SplineClipHelper {
315
318
  if (eflag) {
316
319
  let hlen = hlen0;
317
320
  const p = ps[endp]!; const q = ps[endp + 3]!;
318
- const maxd = 0.9 * SplineClipHelper.dist(p, q);
321
+ const maxd = 0.9 * dist(p, q);
319
322
  if (hlen >= maxd) hlen = maxd;
320
323
  const r = { x: p.x, y: p.y };
321
324
  if (p.y === q.y) { r.y = p.y; r.x = p.x < q.x ? q.x - hlen : q.x + hlen; }
@@ -326,7 +329,7 @@ class SplineClipHelper {
326
329
  if (sflag) {
327
330
  let tlen = tlen0;
328
331
  const p = ps[startp]!; const q = ps[startp + 3]!;
329
- const maxd = 0.9 * SplineClipHelper.dist(p, q);
332
+ const maxd = 0.9 * dist(p, q);
330
333
  if (tlen >= maxd) tlen = maxd;
331
334
  const r = { x: p.x, y: p.y };
332
335
  if (p.y === q.y) { r.y = p.y; r.x = p.x < q.x ? p.x + tlen : p.x - tlen; }
@@ -10,6 +10,7 @@
10
10
 
11
11
  import type { Point, Box } from '../model/geom.js';
12
12
  import { bezier as evalBezier } from './bezier.js';
13
+ import { fma } from './fma.js';
13
14
 
14
15
  // ---------------------------------------------------------------------------
15
16
  // Approximate equality
@@ -47,16 +48,19 @@ const HW = 2.0;
47
48
  const SMALL = 0.0000000001;
48
49
 
49
50
  /**
50
- * Distance from point p to line a-b, squared.
51
+ * Distance from point p to line a-b, squared. Native (arm64, clang
52
+ * -ffp-contract=on) fuses the numerator as fmadd(p.y-a.y, dx, -(dy*(p.x-a.x)))
53
+ * and the denominator as fmadd(dx, dx, dy*dy): libgvc _ptToLine2 at
54
+ * 0x21214-0x21244, identically in inlined _update_bb_bz and _approx_bezier.
51
55
  * @see lib/common/geom.c:ptToLine2
52
56
  */
53
- function ptToLine2(a: Point, b: Point, p: Point): number {
57
+ export function ptToLine2(a: Point, b: Point, p: Point): number {
54
58
  const dx = b.x - a.x;
55
59
  const dy = b.y - a.y;
56
- let a2 = (p.y - a.y) * dx - (p.x - a.x) * dy;
60
+ let a2 = fma(p.y - a.y, dx, -(dy * (p.x - a.x)));
57
61
  a2 *= a2; // square — ensures positive
58
62
  if (a2 < SMALL) return 0; // avoid 0/0 problems
59
- return a2 / (dx * dx + dy * dy);
63
+ return a2 / fma(dx, dx, dy * dy);
60
64
  }
61
65
 
62
66
  /**
@@ -15,6 +15,7 @@ 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
17
  import { fma } from './fma.js';
18
+ import { agerrorf } from './agerr.js';
18
19
 
19
20
  // ---------------------------------------------------------------------------
20
21
  // Stage-dump hook (diagnosis only; null in production — same pattern as
@@ -450,7 +451,7 @@ function routeSplinesInternal(pp: Path, polyline: boolean): Point[] | null {
450
451
  if (pl === null) {
451
452
  // C: agerrorf("in routesplines, Pshortestpath failed") — the caller then
452
453
  // loses the edge. @see lib/common/routespl.c (Pshortestpath < 0 branch)
453
- console.warn('in routesplines, Pshortestpath failed');
454
+ agerrorf('in routesplines, Pshortestpath failed');
454
455
  return null;
455
456
  }
456
457
  emitDump('S3_POLY', dumpNames, () => ({
@@ -8,6 +8,8 @@ import {
8
8
  ALL_FONT_METRICS,
9
9
  type FontFamilyData,
10
10
  } from "./textmeasure-lut-data.js";
11
+ import { registerRenderState } from "./render-state.js";
12
+ import { agwarningf } from './agerr.js';
11
13
 
12
14
  /** Times is always present; used as the fallback family. */
13
15
  export const TIMES_FALLBACK: FontFamilyData = ALL_FONT_METRICS[0]!;
@@ -30,8 +32,13 @@ export function normalizeFontName(name: string): string {
30
32
  return result;
31
33
  }
32
34
 
33
- /** Set of font names that have already triggered the fallback warning. */
35
+ /**
36
+ * Font names that have already triggered the fallback warning: C's emit_once
37
+ * set, emptied per render (a native run is one process; emit_once_reset).
38
+ * @see lib/common/emit.c:emit_once
39
+ */
34
40
  const warnedFontNames = new Set<string>();
41
+ registerRenderState(() => warnedFontNames.clear());
35
42
 
36
43
  /** @see lib/common/textspan_lut.c:get_metrics_for_font_family */
37
44
  export function getFamilyMetrics(fontName: string): FontFamilyData {
@@ -41,7 +48,7 @@ export function getFamilyMetrics(fontName: string): FontFamilyData {
41
48
  }
42
49
  if (!warnedFontNames.has(fontName)) {
43
50
  warnedFontNames.add(fontName);
44
- console.warn(
51
+ agwarningf(
45
52
  `Warning: no hard-coded metrics for '${fontName}'. ` +
46
53
  `Falling back to 'Times' metrics`,
47
54
  );
@@ -10,6 +10,8 @@
10
10
  import { type FontFamilyData } from "./textmeasure-lut-data.js";
11
11
  import { getFamilyMetrics, normalizeFontName } from "./textmeasure-lookup.js";
12
12
  import { canvasFont } from "./css-font.js";
13
+ import { agwarningf } from "./agerr.js";
14
+ import { registerRenderState } from "./render-state.js";
13
15
 
14
16
  /** Number of hard-coded font families in the LUT. */
15
17
  export const LUT_FAMILY_COUNT = 11;
@@ -72,12 +74,53 @@ function getVariantWidths(
72
74
  // ── Character width lookup ────────────────────────────────────────────────────
73
75
 
74
76
  const SPACE_CHAR_CODE = 32;
77
+ const FIRST_NON_ASCII = 128;
78
+
79
+ /**
80
+ * C's two `static bool warning_already_reported` flags, one per warning.
81
+ * Once per render (AD-4), as one native process renders one graph.
82
+ * @see lib/common/textspan_lut.c:estimate_character_width_canonical
83
+ */
84
+ const warned = new Set<string>();
85
+ const NON_ASCII_FLAG = "non-ascii";
86
+ const ASCII_FLAG = "ascii";
87
+
88
+ registerRenderState(() => warned.clear());
89
+
90
+ /**
91
+ * The C format strings already begin with `Warning: `, and agwarningf adds its
92
+ * own prefix, so native prints `Warning: Warning: ...`. The trailing newline
93
+ * of the C format is the line terminator `console.warn` supplies.
94
+ */
95
+ function warnNonAscii(character: number): void {
96
+ if (warned.has(NON_ASCII_FLAG)) return;
97
+ warned.add(NON_ASCII_FLAG);
98
+ agwarningf(
99
+ `Warning: no value for width of non-ASCII character ${character}. Falling back to width of space character`,
100
+ );
101
+ }
102
+
103
+ function warnAscii(character: number): void {
104
+ if (warned.has(ASCII_FLAG)) return;
105
+ warned.add(ASCII_FLAG);
106
+ agwarningf(
107
+ `Warning: no value for width of ASCII character ${character}. Falling back to 0`,
108
+ );
109
+ }
75
110
 
76
111
  /** @see lib/common/textspan_lut.c:estimate_character_width_canonical */
77
112
  function charWidthUnits(widths: readonly number[], charCode: number): number {
78
- const code = charCode >= 128 ? SPACE_CHAR_CODE : charCode;
113
+ let code = charCode;
114
+ if (code >= FIRST_NON_ASCII) {
115
+ warnNonAscii(code);
116
+ code = SPACE_CHAR_CODE;
117
+ }
79
118
  const w = widths[code] ?? -1;
80
- return w < 0 ? 0 : w;
119
+ if (w < 0) {
120
+ warnAscii(code);
121
+ return 0;
122
+ }
123
+ return w;
81
124
  }
82
125
 
83
126
  /**
package/src/gvc/anchor.ts CHANGED
@@ -21,6 +21,7 @@ import { substObjAnchor, preprocessTooltip } from '../common/subst.js';
21
21
  import { parseStyleFlags } from '../common/style-resolve.js';
22
22
  import { nodeAttr } from '../common/poly-init.js';
23
23
  import { bezier } from '../common/bezier.js';
24
+ import { ptToLine2 } from '../common/splines-geom.js';
24
25
  import type { RendererPlugin } from './context.js';
25
26
  import { MapShape, type ObjState, type RenderJob } from './job.js';
26
27
 
@@ -231,8 +232,6 @@ export function beginAnchorIf(renderer: RendererPlugin, job: RenderJob): boolean
231
232
  const DFLT_SAMPLE = 20;
232
233
  /** HW: maximum distance from line, in points. @see lib/common/emit.c:739 */
233
234
  const MAP_HW = 2.0;
234
- /** SMALL: 0/0 guard in ptToLine2. @see lib/common/geom.c:176 */
235
- const MAP_SMALL = 0.0000000001;
236
235
 
237
236
  /**
238
237
  * Coordinate-transform context for the imagemap device. `bb` is the unpadded
@@ -430,16 +429,6 @@ export function computeLabelRectMap(lab: TextlabelT, obj: ObjState, ctx: MapCtx)
430
429
 
431
430
  // --- Edge spline hot spots (map_output_bspline) --------------------------
432
431
 
433
- /** ptToLine2: squared distance from p to segment (a,b). @see lib/common/geom.c */
434
- function ptToLine2(a: Point, b: Point, p: Point): number {
435
- const dx = b.x - a.x;
436
- const dy = b.y - a.y;
437
- let a2 = (p.y - a.y) * dx - (p.x - a.x) * dy;
438
- a2 *= a2;
439
- if (a2 < MAP_SMALL) return 0;
440
- return a2 / (dx * dx + dy * dy);
441
- }
442
-
443
432
  /** check_control_points: both handles within HW of the chord. @see emit.c */
444
433
  function checkControlPoints(cp: Point[]): boolean {
445
434
  return ptToLine2(cp[0]!, cp[3]!, cp[1]!) < MAP_HW * MAP_HW
@@ -26,6 +26,7 @@ import type { DebugOptions } from '../debug.js';
26
26
  import type { ImageSizer } from '../common/htmltable-types.js';
27
27
  import type { ImageResolver } from './image-resolver.js';
28
28
  import type { RenderJob } from './job.js'; // scaffold in T25; full class in T26
29
+ import { resetRenderState } from '../common/render-state.js';
29
30
  import { RenderError, invalidArgType, invalidArgValue } from '../errors.js';
30
31
 
31
32
  // ---------------------------------------------------------------------------
@@ -285,6 +286,12 @@ export class GvcContext {
285
286
  * rendering happens in between and must see the layout state
286
287
  * (e.g. cluster arrays).
287
288
  *
289
+ * Contract (AD-4/AD-8): first resets the C process state (RNG streams,
290
+ * sequence counters, once-only warnings), so each `layout` call behaves
291
+ * as one native run — drive one graph per `layout`. Caller-installed hooks
292
+ * (setTextMeasurer, setImageSizer, setImageResolver, the per-context
293
+ * measurer/sizer/resolver) are configuration and are not reset.
294
+ *
288
295
  * @throws TypeError (ERR_INVALID_ARG_TYPE / ERR_INVALID_ARG_VALUE) for a
289
296
  * bad `g`, or an engine argument that is not registered
290
297
  * @throws RenderError (UNKNOWN_LAYOUT) if the `layout` attribute names no
@@ -295,6 +302,7 @@ export class GvcContext {
295
302
  * @see lib/gvc/gvlayout.c:gvLayoutJobs
296
303
  */
297
304
  layout(g: Graph, engineName: EngineName): void {
305
+ resetRenderState(); // AD-8: idempotent under the public entries' own reset
298
306
  const selected = this.checkLayoutArgs(g, engineName);
299
307
  // C gvLayoutJobs: the graph's `layout` ATTRIBUTE unconditionally
300
308
  // overrides the selected engine (-K / API choice); an unrecognized