@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
@@ -0,0 +1,36 @@
1
+ // SPDX-License-Identifier: EPL-2.0
2
+
3
+ /**
4
+ * Shared empty attribute map for dot virtual nodes/edges (AD-12).
5
+ *
6
+ * C virtual nodes/edges carry no attribute record; allocating an empty `Map`
7
+ * per object cost ~372 B per virtual pair. Virtual objects start with this
8
+ * single shared map instead; reads see an empty map, and the first write goes
9
+ * through {@link ownAttrs}, which materialises a private `Map`. A direct
10
+ * mutation of the shared map throws rather than leaking into every virtual.
11
+ * @see lib/dotgen/fastgr.c:virtual_node
12
+ */
13
+
14
+ class SharedEmptyAttrs extends Map<string, string> {
15
+ override set(key: string, _value: string): never {
16
+ throw new Error(`attrs.set("${key}") on a shared empty map; use ownAttrs()`);
17
+ }
18
+ override delete(key: string): never {
19
+ throw new Error(`attrs.delete("${key}") on a shared empty map; use ownAttrs()`);
20
+ }
21
+ override clear(): never {
22
+ throw new Error('attrs.clear() on a shared empty map; use ownAttrs()');
23
+ }
24
+ }
25
+
26
+ /** The one shared empty attribute map (read-only by construction). */
27
+ export const EMPTY_ATTRS: Map<string, string> = new SharedEmptyAttrs();
28
+
29
+ /**
30
+ * Return the object's private attribute map, materialising it on first write
31
+ * when the object still holds {@link EMPTY_ATTRS}.
32
+ */
33
+ export function ownAttrs(o: { attrs: Map<string, string> }): Map<string, string> {
34
+ if (o.attrs === EMPTY_ATTRS) o.attrs = new Map();
35
+ return o.attrs;
36
+ }
package/src/model/node.ts CHANGED
@@ -8,6 +8,7 @@
8
8
 
9
9
  import type { Edge } from './edge.js';
10
10
  import type { Graph } from './graph.js';
11
+ import { EMPTY_ATTRS } from './lazy-attrs.js';
11
12
  import { type NodeInfo, makeNodeInfo } from './nodeInfo.js';
12
13
 
13
14
  /**
@@ -64,10 +65,10 @@ export class Node {
64
65
  nodeDefaultsSnapshot?: Map<string, string>;
65
66
 
66
67
  /** @see lib/cgraph/node.c:agnode */
67
- constructor(id: number, name: string, root: Graph) {
68
+ constructor(id: number, name: string, root: Graph, lazyAttrs = false) {
68
69
  this.id = id;
69
70
  this.name = name;
70
- this.attrs = new Map();
71
+ this.attrs = lazyAttrs ? EMPTY_ATTRS : new Map();
71
72
  this.info = makeNodeInfo();
72
73
  this.root = root;
73
74
  }
@@ -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 { agwarningf } 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,7 @@ export function orthoEdges(
144
145
  clipAndInstall?: ClipAndInstallFn,
145
146
  ): number {
146
147
  if (useLbls) {
147
- console.warn(
148
+ agwarningf(
148
149
  "Orthogonal edges do not currently handle edge labels. Try using xlabels.\n",
149
150
  );
150
151
  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");
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");
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));
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
  }
@@ -18,10 +18,17 @@
18
18
 
19
19
  import {
20
20
  colorxlate,
21
+ ColorxlateResult,
21
22
  getColorScheme,
22
23
  setColorScheme,
23
24
  type GVColor,
24
25
  } from '../common/color.js';
26
+ import type { Edge } from '../model/edge.js';
27
+ import { registerRenderState } from '../common/render-state.js';
28
+ import { agerrorf, agwarningf } from '../common/agerr.js';
29
+ import { parseSegsSilent } from '../common/multicolor.js';
30
+ import { findStopColor, styleTokens } from '../common/style-resolve.js';
31
+ import { emitOnce } from '../common/emit-once.js';
25
32
 
26
33
  /**
27
34
  * The SVG renderer's known color names, ported verbatim from
@@ -78,19 +85,70 @@ const SVG_KNOWN_COLORS: readonly string[] = [
78
85
  /** Lower-cased lookup set for the SVG known colors (case-insensitive, as C). */
79
86
  export const KNOWN_COLORS: ReadonlySet<string> = new Set(SVG_KNOWN_COLORS);
80
87
 
88
+ /**
89
+ * Whether the active renderer has the SVG known-colors list (svg). Only then
90
+ * does a known name skip colorxlate; every other renderer runs colorxlate on
91
+ * every name, which under a colorscheme can reject a name the list accepts.
92
+ * Per render (AD-4); a render that sets nothing is svg.
93
+ * @see lib/gvc/gvrender.c:195-199 gvrender_resolve_color (knowncolors)
94
+ */
95
+ const rendererColors = { hasKnownList: true };
96
+ registerRenderState(() => {
97
+ rendererColors.hasKnownList = true;
98
+ });
99
+
100
+ /** Select the active renderer's known-colors behaviour; svg is the only list. */
101
+ export function setRendererKnownColors(hasList: boolean): void {
102
+ rendererColors.hasKnownList = hasList;
103
+ }
104
+
105
+ /**
106
+ * The first color of a `c1:c2` list: gvrender_set_pencolor cuts the name at the
107
+ * first ':' before resolving it.
108
+ * @see lib/gvc/gvrender.c:gvrender_set_pencolor
109
+ */
110
+ export function firstColor(name: string): string {
111
+ const i = name.indexOf(':');
112
+ return i < 0 ? name : name.slice(0, i);
113
+ }
114
+
81
115
  /**
82
116
  * Resolve a raw color string to a canonical {@link GVColor} for rendering.
83
117
  *
118
+ * `report` false resolves without the unknown-color warning, for a caller that
119
+ * resolves earlier than C does (the warning belongs to C's own call order).
120
+ *
84
121
  * @see lib/gvc/gvrender.c:188-213 gvrender_resolve_color
85
122
  */
86
- export function resolveRenderColor(raw: string): GVColor {
123
+ export function resolveRenderColor(raw: string, report = true): GVColor {
87
124
  if (raw === '' || raw === 'none') return { type: 'none' };
88
125
  // C keeps a known name verbatim (case preserved); only the compare is
89
126
  // case-insensitive (bsearch over a lowercase table).
90
- if (KNOWN_COLORS.has(raw.toLowerCase())) return { type: 'string', s: raw };
127
+ const known = KNOWN_COLORS.has(raw.toLowerCase());
128
+ if (known && (rendererColors.hasKnownList || !report)) return { type: 'string', s: raw };
91
129
  const color: GVColor = { type: 'rgba', r: 0, g: 0, b: 0, a: 0 };
92
- colorxlate(raw, color, 'rgba');
93
- return color;
130
+ const rc = colorxlate(raw, color, 'rgba');
131
+ if (report) reportColorxlate(raw, rc);
132
+ return known ? { type: 'string', s: raw } : color;
133
+ }
134
+
135
+ /**
136
+ * The non-OK branch of gvrender_resolve_color: an unknown name warns once per
137
+ * render (emit_once "color <name>"), any other failure is an error.
138
+ *
139
+ * The result is renderer independent: every renderer's known-colors list is
140
+ * either absent (dot, xdot, json, cmapx, ...) or the SVG list, whose names all
141
+ * colorxlate to COLOR_OK, so only the xlate result decides whether C warns.
142
+ *
143
+ * @see lib/gvc/gvrender.c:201-209 gvrender_resolve_color
144
+ */
145
+ function reportColorxlate(name: string, rc: ColorxlateResult): void {
146
+ if (rc === ColorxlateResult.ColorOk) return;
147
+ if (rc === ColorxlateResult.ColorUnknown) {
148
+ if (emitOnce(`color ${name}`)) agwarningf(`${name} is not a known color.`);
149
+ } else {
150
+ agerrorf('error in colorxlate()');
151
+ }
94
152
  }
95
153
 
96
154
  /**
@@ -169,3 +227,134 @@ export function textFillAttrs(fontColor: string | null): string {
169
227
  if (byte < 255) out += ' fill-opacity="' + (byte / 255).toFixed(6) + '"';
170
228
  return out;
171
229
  }
230
+
231
+ // ---------------------------------------------------------------------------
232
+ // Unknown-color reporting in C's call order
233
+ //
234
+ // Which names C hands to gvrender_resolve_color, and in what order, is fixed by
235
+ // the emit code (emit_edge_graphics, emit_background, emit_label, ...), not by
236
+ // the renderer. The port's renderers resolve in their own order and not at all
237
+ // for the non-drawing ones (plain, imap, cmapx), so each pass below lists the
238
+ // names in C's order and resolves them once, before any renderer code runs;
239
+ // emit_once then makes every later resolve of the same name silent.
240
+ // ---------------------------------------------------------------------------
241
+
242
+ /** @see lib/common/const.h DEFAULT_COLOR */
243
+ const DEFAULT_COLOR = 'black';
244
+ /** @see lib/common/emit.c:452 AEQ0 (EPS) */
245
+ const SEG_EPS = 1e-5;
246
+
247
+ /**
248
+ * Resolve each name as gvrender_set_pencolor/_fillcolor/_gradient_vals would
249
+ * (cut at the first ':'), reporting unknown colors.
250
+ */
251
+ export function reportColorNames(names: readonly string[]): void {
252
+ for (const name of names) resolveRenderColor(firstColor(name));
253
+ }
254
+
255
+ /** The per-bezier arrow flags emit_edge_graphics consults. */
256
+ export interface EdgeArrowFlags {
257
+ sflag: boolean;
258
+ eflag: boolean;
259
+ }
260
+
261
+ /** The inputs of emit_edge_graphics' colour decisions. */
262
+ export interface EdgeColorSpec {
263
+ /** late_string(e, E_color, ""). */
264
+ color: string;
265
+ /** E_fillcolor when set and non-empty, else "". */
266
+ fillcolor: string;
267
+ tapered: boolean;
268
+ arrows: readonly EdgeArrowFlags[];
269
+ }
270
+
271
+ /** Count occurrences of one character. */
272
+ function countChar(s: string, ch: string): number {
273
+ return s.split(ch).length - 1;
274
+ }
275
+
276
+ /**
277
+ * The colours C's multicolor() sets, or null when the colour spec is rejected
278
+ * (parseSegs rv 1 or 2) and emit_edge_graphics falls back to DEFAULT_COLOR.
279
+ * @see lib/common/emit.c:1975 multicolor
280
+ */
281
+ function multicolorNames(
282
+ color: string,
283
+ arrows: readonly EdgeArrowFlags[],
284
+ ): string[] | null {
285
+ const { segs, error } = parseSegsSilent(color);
286
+ if (error === 1 || error === 2) return null;
287
+ const out: string[] = [];
288
+ const front = segs[0]?.color ?? null;
289
+ for (const bz of arrows) {
290
+ let endcolor: string | null = null;
291
+ for (const seg of segs) {
292
+ if (seg.color === null) break;
293
+ if (Math.abs(seg.t) < SEG_EPS) continue;
294
+ out.push(seg.color);
295
+ endcolor = seg.color;
296
+ }
297
+ if (bz.sflag && front !== null) out.push(front);
298
+ if (bz.eflag && endcolor !== null) out.push(endcolor);
299
+ }
300
+ return out;
301
+ }
302
+
303
+ /** The tapered branch: transparent pen, colour as fill, then pen and fill. */
304
+ function taperedNames(color: string, fillcolor: string, hasFill: boolean): string[] {
305
+ const pen = color === '' ? DEFAULT_COLOR : color;
306
+ return hasFill ? [pen, fillcolor === '' ? DEFAULT_COLOR : fillcolor] : [pen];
307
+ }
308
+
309
+ /**
310
+ * The names emit_edge_graphics resolves for an edge with a spline, in order.
311
+ * @see lib/common/emit.c:2350 emit_edge_graphics
312
+ */
313
+ export function edgeGraphicsColors(spec: EdgeColorSpec): string[] {
314
+ const numc = countChar(spec.color, ':');
315
+ let color = spec.color;
316
+ if (countChar(color, ';') > 0 && numc > 0) {
317
+ const multi = multicolorNames(color, spec.arrows);
318
+ if (multi !== null) return multi;
319
+ color = DEFAULT_COLOR;
320
+ }
321
+ const hasFill = spec.fillcolor !== '';
322
+ const early = hasFill ? [spec.fillcolor] : [];
323
+ if (spec.tapered) return [...early, ...taperedNames(color, spec.fillcolor, hasFill)];
324
+ if (numc > 0) return [...early, ...color.split(':').filter((c) => c !== '')];
325
+ const fill = hasFill ? spec.fillcolor : color;
326
+ if (color !== '') return [...early, color, fill];
327
+ return [...early, DEFAULT_COLOR, fill !== '' ? fill : DEFAULT_COLOR];
328
+ }
329
+
330
+ /**
331
+ * Report the unknown colours of an edge's graphics in C's order.
332
+ * @see lib/common/emit.c:2350 emit_edge_graphics
333
+ */
334
+ export function reportEdgeColors(e: Edge): void {
335
+ const spl = e.info.spl;
336
+ if (!spl) return;
337
+ const color = e.attrs.get('color') ?? '';
338
+ reportColorNames(edgeGraphicsColors({
339
+ color,
340
+ fillcolor: e.attrs.get('fillcolor') ?? '',
341
+ tapered: styleTokens(e.attrs.get('style')).includes('tapered'),
342
+ arrows: spl.list.map((bz) => ({ sflag: bz.sflag !== 0, eflag: bz.eflag !== 0 })),
343
+ }));
344
+ }
345
+
346
+ /**
347
+ * The names emit_background resolves: the fill (a gradient's first colour, the
348
+ * transparent pen, then the stop). Only a truecolor device (svg) keeps a
349
+ * transparent background unpainted; elsewhere it becomes white.
350
+ * @see lib/common/emit.c:1476 emit_background
351
+ */
352
+ export function backgroundColors(bgcolor: string | undefined, truecolor: boolean): string[] {
353
+ let str = bgcolor !== undefined && bgcolor !== '' ? bgcolor : 'white';
354
+ if (str === 'transparent') {
355
+ if (truecolor) return [];
356
+ str = 'white';
357
+ }
358
+ const stop = findStopColor(str);
359
+ return stop === null ? [str, 'transparent'] : [stop.fillColor, 'transparent', stop.stopColor];
360
+ }
@@ -23,11 +23,12 @@ import { resolveRenderColor } from '../color-resolve.js';
23
23
  import { taper, taperfun } from '../../common/taper.js';
24
24
  import { orthoRoundedRadius } from '../svg-helpers.js';
25
25
  import { orthoRoundedPolylines } from '../svg-edge-ortho-radius.js';
26
- import { parseSegs } from '../../common/multicolor.js';
26
+ import { edgeSegsContext, parseSegs, parseSegsSilent } from '../../common/multicolor.js';
27
27
  import { splitSplineByColor } from '../svg-edge-split.js';
28
28
  import { buildOffsetLists, advanceTmpList } from '../../common/edge-offset.js';
29
29
  import { xdotFillColor, xdotNum, xdotPenColor, xdotPoint, xdotPoints, xdotStrOp, trimFixed3 } from './xdot-ops.js';
30
30
  import { DotWriterBase } from './agwrite.js';
31
+ import { gvrenderSetStyle } from '../../gvc/gvrender-set-style.js';
31
32
 
32
33
  /** Pen colour when a colour-list entry is empty. @see lib/common/emit.c:2485 */
33
34
  const DEFAULT_COLOR = 'black';
@@ -81,6 +82,7 @@ export abstract class EdgeDrawBase extends DotWriterBase {
81
82
  */
82
83
  attachmentPolyline(pts: Point[], pencolor: string, job: RenderJob): void {
83
84
  const obj = job.obj;
85
+ gvrenderSetStyle(DEFAULT_LINE_STYLE);
84
86
  if (obj !== null) {
85
87
  obj.rawStyle = [...DEFAULT_LINE_STYLE];
86
88
  obj.pen = PenType.Solid;
@@ -147,11 +149,12 @@ export abstract class EdgeDrawBase extends DotWriterBase {
147
149
  */
148
150
  protected emitSplitSpline(
149
151
  bzList: (Bezier | undefined)[],
152
+ e: Edge,
150
153
  colorAttr: string,
151
154
  edraw: string[],
152
155
  job: RenderJob,
153
156
  ): { firstColor: string; endColor: string } {
154
- const segs = parseSegs(colorAttr).segs;
157
+ const segs = parseSegs(colorAttr, edgeSegsContext(e)).segs;
155
158
  const firstColor = segs[0]?.color ?? DEFAULT_COLOR;
156
159
  let endColor = firstColor;
157
160
  for (const bz of bzList) {
@@ -183,7 +186,7 @@ export abstract class EdgeDrawBase extends DotWriterBase {
183
186
  ? buildOffsetLists(bz.list, (2 + numc) / 2)
184
187
  : { offlist: [] as Point[], tmplist: [] as Point[] },
185
188
  );
186
- const colors = parseSegs(colorAttr).segs.map((s) => s.color ?? DEFAULT_COLOR);
189
+ const colors = parseSegsSilent(colorAttr).segs.map((s) => s.color ?? DEFAULT_COLOR);
187
190
  for (const color of colors) {
188
191
  const pen = xdotPenColor(resolveRenderColor(color));
189
192
  for (const sd of segData) {
package/src/render/dot.ts CHANGED
@@ -215,7 +215,7 @@ export class XdotRenderer extends EdgeDrawBase implements RendererPlugin {
215
215
  // color segment, split along the spline's arc length. Takes precedence
216
216
  // over tapered / `:` parallel, matching C's `if (numsemi && numc)` order.
217
217
  // @see lib/common/emit.c:2389 multicolor
218
- const c = this.emitSplitSpline(spl.list as (Bezier | undefined)[], colorAttr, edraw, job);
218
+ const c = this.emitSplitSpline(spl.list as (Bezier | undefined)[], e, colorAttr, edraw, job);
219
219
  tailArrowColor = c.firstColor;
220
220
  headArrowColor = c.endColor;
221
221
  } else if (edgeIsTapered(e)) {
@@ -14,6 +14,7 @@ import type { Graph } from '../model/graph.js';
14
14
  import { createDefaultContext } from '../gvc/default-context.js';
15
15
  import { render as deviceRender } from '../gvc/device.js';
16
16
  import { invalidArgType, rethrowAtBoundary } from '../errors.js';
17
+ import { resetRenderState } from '../common/render-state.js';
17
18
  import type { EngineName } from '../gvc/context.js';
18
19
 
19
20
  // ---------------------------------------------------------------------------
@@ -118,6 +119,7 @@ export function render(
118
119
  opts?: RenderOptions,
119
120
  ): string {
120
121
  checkRenderArgs(g, format, opts);
122
+ resetRenderState(); // AD-4: one render = one native process
121
123
  const engine: EngineName = opts?.engine ?? 'dot';
122
124
  const inlineImages = opts?.inlineImages ?? false;
123
125
  const ctx = createDefaultContext();
@@ -20,7 +20,8 @@ import type { Edge } from '../model/edge.js';
20
20
  import type { RenderJob } from '../gvc/job.js';
21
21
  import type { Point, Bezier } from '../model/geom.js';
22
22
  import { bezier } from '../common/bezier.js';
23
- import { parseSegs } from '../common/multicolor.js';
23
+ import { dist2 } from '../common/spline-midpoint.js';
24
+ import { edgeSegsContext, parseSegs } from '../common/multicolor.js';
24
25
  import { transformPoint } from '../gvc/device.js';
25
26
  import { emitBezierPath, emitDash, emitPenWidth } from './svg-helpers.js';
26
27
  import { resolveRenderColor, colorPaint } from './color-resolve.js';
@@ -31,12 +32,13 @@ const PENWIDTH_THRESHOLD = 0.005;
31
32
  /** AEQ0 tolerance for fraction comparisons. */
32
33
  const EPS = 1e-5;
33
34
 
35
+ /** DIST: sqrt of the fused DIST2 (libgvc splitBSpline: fmla + fsqrt). @see lib/common/geom.h:DIST */
34
36
  function dist(a: Point, b: Point): number {
35
- return Math.hypot(a.x - b.x, a.y - b.y);
37
+ return Math.sqrt(dist2(a, b));
36
38
  }
37
39
 
38
40
  /** Chord length of one cubic (control points at off..off+3). @see emit.c:1908 */
39
- function approxLen(list: Point[], off: number): number {
41
+ export function approxLen(list: Point[], off: number): number {
40
42
  return dist(list[off]!, list[off + 1]!) +
41
43
  dist(list[off + 1]!, list[off + 2]!) +
42
44
  dist(list[off + 2]!, list[off + 3]!);
@@ -158,7 +160,7 @@ export function emitSplitEdgePaths(
158
160
  job: RenderJob,
159
161
  colorList: string,
160
162
  ): { firstColor: string; endColor: string } {
161
- const segs = parseSegs(colorList).segs;
163
+ const segs = parseSegs(colorList, edgeSegsContext(e)).segs;
162
164
  const firstColor = segs[0]?.color ?? DEFAULT_COLOR;
163
165
  const spl = e.info.spl;
164
166
  let endColor = firstColor;
@@ -231,8 +231,9 @@ function drawWedge(seg: WedgeSeg, e: EllipseParams, ctx: RenderCtx): number {
231
231
  */
232
232
  export function wedgedEllipse(
233
233
  job: RenderJob, pf: Point[], clrs: string, renderer: RendererPlugin,
234
+ context?: () => string,
234
235
  ): number {
235
- const { segs, error } = parseSegs(clrs);
236
+ const { segs, error } = parseSegs(clrs, context);
236
237
  if (error === 1 || error === 2) return error;
237
238
  const ctrX = (pf[0]!.x + pf[1]!.x) / 2;
238
239
  const ctrY = (pf[0]!.y + pf[1]!.y) / 2;
@@ -286,9 +287,9 @@ function drawBand(p: Point[], seg: BandSeg, ctx: RenderCtx): void {
286
287
  */
287
288
  export function stripedBox(
288
289
  job: RenderJob, pts: Point[], clrs: string,
289
- rotate: boolean, renderer: RendererPlugin,
290
+ rotate: boolean, renderer: RendererPlugin, context?: () => string,
290
291
  ): number {
291
- const { segs, error } = parseSegs(clrs);
292
+ const { segs, error } = parseSegs(clrs, context);
292
293
  if (error === 1 || error === 2) return error;
293
294
  // @see lib/common/emit.c:605-615 rotate swap
294
295
  const p: Point[] = rotate
@@ -27,7 +27,7 @@ import { buildOffsetLists, advanceTmpList } from '../common/edge-offset.js';
27
27
  import { transformPoint } from '../gvc/device.js';
28
28
  import { emitBezierPath, emitDash, emitPenWidth } from './svg-helpers.js';
29
29
  import { resolveRenderColor, colorPaint, colorOpacity } from './color-resolve.js';
30
- import { parseSegs } from '../common/multicolor.js';
30
+ import { parseSegsSilent } from '../common/multicolor.js';
31
31
 
32
32
  const PENWIDTH_NORMAL = 1.0;
33
33
  const PENWIDTH_THRESHOLD = 0.005;
@@ -114,7 +114,7 @@ export function emitParallelEdgePaths(
114
114
  // parseSegs strips any ';weight', so the semicolon syntax (which C routes
115
115
  // to split-along-length multicolor — a follow-up) at least yields valid
116
116
  // parallel colors instead of an invalid "red;0.5" stroke.
117
- const colors = parseSegs(colorList).segs.map((s) => s.color ?? DEFAULT_COLOR);
117
+ const colors = parseSegsSilent(colorList).segs.map((s) => s.color ?? DEFAULT_COLOR);
118
118
  emitColorPasses(segData, colors, job);
119
119
  return {
120
120
  headColor: colors[0] ?? DEFAULT_COLOR,
@@ -35,6 +35,7 @@ import { createDefaultContext } from '../gvc/default-context.js';
35
35
  import { parseXDot } from '../xdot/index.js';
36
36
  import type { EngineName } from '../gvc/context.js';
37
37
  import { invalidArgType, rethrowAtBoundary } from '../errors.js';
38
+ import { resetRenderState } from '../common/render-state.js';
38
39
 
39
40
  /**
40
41
  * `Xdot` — the parsed result of one xdot attribute stream: `ops` (the
@@ -160,6 +161,7 @@ function layoutAndRenderXdot(g: Graph, engine: EngineName): string {
160
161
  */
161
162
  export function getDrawOps(g: Graph, opts?: DrawOpsOptions): XdotOp[] {
162
163
  checkDrawOpsArgs(g, opts);
164
+ resetRenderState(); // AD-4: one render = one native process
163
165
  const engine = opts?.engine ?? DEFAULT_DRAW_ENGINE;
164
166
  return collectGraphOps(parse(layoutAndRenderXdot(g, engine)));
165
167
  }