@knowvah/dot-engine 0.1.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 (986) hide show
  1. package/LICENSE +277 -0
  2. package/README.md +292 -0
  3. package/dist/api/builder.d.ts +78 -0
  4. package/dist/api/builder.d.ts.map +1 -0
  5. package/dist/api/edge-ops.d.ts +45 -0
  6. package/dist/api/edge-ops.d.ts.map +1 -0
  7. package/dist/api/geometry.d.ts +159 -0
  8. package/dist/api/geometry.d.ts.map +1 -0
  9. package/dist/api/index.d.ts +24 -0
  10. package/dist/api/index.d.ts.map +1 -0
  11. package/dist/api.js +719 -0
  12. package/dist/api.js.map +7 -0
  13. package/dist/bundle.js +101 -0
  14. package/dist/cdt/bag.d.ts +58 -0
  15. package/dist/cdt/bag.d.ts.map +1 -0
  16. package/dist/cdt/hash-core.d.ts +40 -0
  17. package/dist/cdt/hash-core.d.ts.map +1 -0
  18. package/dist/cdt/hash.d.ts +74 -0
  19. package/dist/cdt/hash.d.ts.map +1 -0
  20. package/dist/cdt/index.d.ts +16 -0
  21. package/dist/cdt/index.d.ts.map +1 -0
  22. package/dist/cdt/splay-core.d.ts +78 -0
  23. package/dist/cdt/splay-core.d.ts.map +1 -0
  24. package/dist/cdt/splay.d.ts +63 -0
  25. package/dist/cdt/splay.d.ts.map +1 -0
  26. package/dist/cdt/strhash.d.ts +26 -0
  27. package/dist/cdt/strhash.d.ts.map +1 -0
  28. package/dist/cdt/types.d.ts +11 -0
  29. package/dist/cdt/types.d.ts.map +1 -0
  30. package/dist/common/arith.d.ts +35 -0
  31. package/dist/common/arith.d.ts.map +1 -0
  32. package/dist/common/arm-pow-data.d.ts +17 -0
  33. package/dist/common/arm-pow-data.d.ts.map +1 -0
  34. package/dist/common/arm-pow.d.ts +12 -0
  35. package/dist/common/arm-pow.d.ts.map +1 -0
  36. package/dist/common/arrows-constants.d.ts +97 -0
  37. package/dist/common/arrows-constants.d.ts.map +1 -0
  38. package/dist/common/arrows-geometry.d.ts +24 -0
  39. package/dist/common/arrows-geometry.d.ts.map +1 -0
  40. package/dist/common/arrows-miter.d.ts +24 -0
  41. package/dist/common/arrows-miter.d.ts.map +1 -0
  42. package/dist/common/arrows-shapes-poly.d.ts +24 -0
  43. package/dist/common/arrows-shapes-poly.d.ts.map +1 -0
  44. package/dist/common/arrows-shapes-util.d.ts +77 -0
  45. package/dist/common/arrows-shapes-util.d.ts.map +1 -0
  46. package/dist/common/arrows-shapes.d.ts +48 -0
  47. package/dist/common/arrows-shapes.d.ts.map +1 -0
  48. package/dist/common/arrows-types.d.ts +59 -0
  49. package/dist/common/arrows-types.d.ts.map +1 -0
  50. package/dist/common/arrows.d.ts +69 -0
  51. package/dist/common/arrows.d.ts.map +1 -0
  52. package/dist/common/color.d.ts +66 -0
  53. package/dist/common/color.d.ts.map +1 -0
  54. package/dist/common/colorData.d.ts +10 -0
  55. package/dist/common/colorData.d.ts.map +1 -0
  56. package/dist/common/compass-port.d.ts +39 -0
  57. package/dist/common/compass-port.d.ts.map +1 -0
  58. package/dist/common/crand.d.ts +42 -0
  59. package/dist/common/crand.d.ts.map +1 -0
  60. package/dist/common/edge-label-init.d.ts +51 -0
  61. package/dist/common/edge-label-init.d.ts.map +1 -0
  62. package/dist/common/edge-offset.d.ts +66 -0
  63. package/dist/common/edge-offset.d.ts.map +1 -0
  64. package/dist/common/ellipse-wedge.d.ts +10 -0
  65. package/dist/common/ellipse-wedge.d.ts.map +1 -0
  66. package/dist/common/emit-types.d.ts +255 -0
  67. package/dist/common/emit-types.d.ts.map +1 -0
  68. package/dist/common/fma.d.ts +10 -0
  69. package/dist/common/fma.d.ts.map +1 -0
  70. package/dist/common/graph-init.d.ts +61 -0
  71. package/dist/common/graph-init.d.ts.map +1 -0
  72. package/dist/common/html-entities.d.ts +17 -0
  73. package/dist/common/html-entities.d.ts.map +1 -0
  74. package/dist/common/html-string.d.ts +14 -0
  75. package/dist/common/html-string.d.ts.map +1 -0
  76. package/dist/common/htmltable-emit-fill.d.ts +84 -0
  77. package/dist/common/htmltable-emit-fill.d.ts.map +1 -0
  78. package/dist/common/htmltable-emit-rules.d.ts +79 -0
  79. package/dist/common/htmltable-emit-rules.d.ts.map +1 -0
  80. package/dist/common/htmltable-emit.d.ts +63 -0
  81. package/dist/common/htmltable-emit.d.ts.map +1 -0
  82. package/dist/common/htmltable-lex.d.ts +16 -0
  83. package/dist/common/htmltable-lex.d.ts.map +1 -0
  84. package/dist/common/htmltable-parse.d.ts +37 -0
  85. package/dist/common/htmltable-parse.d.ts.map +1 -0
  86. package/dist/common/htmltable-port.d.ts +15 -0
  87. package/dist/common/htmltable-port.d.ts.map +1 -0
  88. package/dist/common/htmltable-pos-runs.d.ts +88 -0
  89. package/dist/common/htmltable-pos-runs.d.ts.map +1 -0
  90. package/dist/common/htmltable-pos.d.ts +192 -0
  91. package/dist/common/htmltable-pos.d.ts.map +1 -0
  92. package/dist/common/htmltable-types.d.ts +237 -0
  93. package/dist/common/htmltable-types.d.ts.map +1 -0
  94. package/dist/common/htmltable.d.ts +74 -0
  95. package/dist/common/htmltable.d.ts.map +1 -0
  96. package/dist/common/layers.d.ts +20 -0
  97. package/dist/common/layers.d.ts.map +1 -0
  98. package/dist/common/make-label.d.ts +60 -0
  99. package/dist/common/make-label.d.ts.map +1 -0
  100. package/dist/common/multicolor.d.ts +46 -0
  101. package/dist/common/multicolor.d.ts.map +1 -0
  102. package/dist/common/nodeinit.d.ts +45 -0
  103. package/dist/common/nodeinit.d.ts.map +1 -0
  104. package/dist/common/poly-gencode.d.ts +40 -0
  105. package/dist/common/poly-gencode.d.ts.map +1 -0
  106. package/dist/common/poly-init.d.ts +51 -0
  107. package/dist/common/poly-init.d.ts.map +1 -0
  108. package/dist/common/poly-inside.d.ts +37 -0
  109. package/dist/common/poly-inside.d.ts.map +1 -0
  110. package/dist/common/poly-shapes-cases.d.ts +31 -0
  111. package/dist/common/poly-shapes-cases.d.ts.map +1 -0
  112. package/dist/common/poly-shapes-cases2.d.ts +25 -0
  113. package/dist/common/poly-shapes-cases2.d.ts.map +1 -0
  114. package/dist/common/poly-shapes-util.d.ts +42 -0
  115. package/dist/common/poly-shapes-util.d.ts.map +1 -0
  116. package/dist/common/poly-shapes.d.ts +39 -0
  117. package/dist/common/poly-shapes.d.ts.map +1 -0
  118. package/dist/common/poly-sizing.d.ts +189 -0
  119. package/dist/common/poly-sizing.d.ts.map +1 -0
  120. package/dist/common/poly-vertices.d.ts +42 -0
  121. package/dist/common/poly-vertices.d.ts.map +1 -0
  122. package/dist/common/postproc.d.ts +49 -0
  123. package/dist/common/postproc.d.ts.map +1 -0
  124. package/dist/common/ps-fontalias.d.ts +36 -0
  125. package/dist/common/ps-fontalias.d.ts.map +1 -0
  126. package/dist/common/random.d.ts +8 -0
  127. package/dist/common/random.d.ts.map +1 -0
  128. package/dist/common/record-port.d.ts +34 -0
  129. package/dist/common/record-port.d.ts.map +1 -0
  130. package/dist/common/record.d.ts +105 -0
  131. package/dist/common/record.d.ts.map +1 -0
  132. package/dist/common/shapeData.d.ts +186 -0
  133. package/dist/common/shapeData.d.ts.map +1 -0
  134. package/dist/common/shapes.d.ts +36 -0
  135. package/dist/common/shapes.d.ts.map +1 -0
  136. package/dist/common/spline-midpoint.d.ts +31 -0
  137. package/dist/common/spline-midpoint.d.ts.map +1 -0
  138. package/dist/common/splines-clip.d.ts +21 -0
  139. package/dist/common/splines-clip.d.ts.map +1 -0
  140. package/dist/common/splines-constants.d.ts +42 -0
  141. package/dist/common/splines-constants.d.ts.map +1 -0
  142. package/dist/common/splines-geom.d.ts +51 -0
  143. package/dist/common/splines-geom.d.ts.map +1 -0
  144. package/dist/common/splines-path-begin.d.ts +17 -0
  145. package/dist/common/splines-path-begin.d.ts.map +1 -0
  146. package/dist/common/splines-path-end.d.ts +17 -0
  147. package/dist/common/splines-path-end.d.ts.map +1 -0
  148. package/dist/common/splines-path-shared.d.ts +53 -0
  149. package/dist/common/splines-path-shared.d.ts.map +1 -0
  150. package/dist/common/splines-routespl.d.ts +40 -0
  151. package/dist/common/splines-routespl.d.ts.map +1 -0
  152. package/dist/common/splines-selfedge.d.ts +21 -0
  153. package/dist/common/splines-selfedge.d.ts.map +1 -0
  154. package/dist/common/splines.d.ts +18 -0
  155. package/dist/common/splines.d.ts.map +1 -0
  156. package/dist/common/style-resolve.d.ts +197 -0
  157. package/dist/common/style-resolve.d.ts.map +1 -0
  158. package/dist/common/subst.d.ts +31 -0
  159. package/dist/common/subst.d.ts.map +1 -0
  160. package/dist/common/taper.d.ts +23 -0
  161. package/dist/common/taper.d.ts.map +1 -0
  162. package/dist/common/textmeasure-factory.d.ts +33 -0
  163. package/dist/common/textmeasure-factory.d.ts.map +1 -0
  164. package/dist/common/textmeasure-lookup.d.ts +17 -0
  165. package/dist/common/textmeasure-lookup.d.ts.map +1 -0
  166. package/dist/common/textmeasure-lut-data-a.d.ts +13 -0
  167. package/dist/common/textmeasure-lut-data-a.d.ts.map +1 -0
  168. package/dist/common/textmeasure-lut-data-b.d.ts +12 -0
  169. package/dist/common/textmeasure-lut-data-b.d.ts.map +1 -0
  170. package/dist/common/textmeasure-lut-data-c.d.ts +13 -0
  171. package/dist/common/textmeasure-lut-data-c.d.ts.map +1 -0
  172. package/dist/common/textmeasure-lut-data.d.ts +27 -0
  173. package/dist/common/textmeasure-lut-data.d.ts.map +1 -0
  174. package/dist/common/textmeasure.d.ts +120 -0
  175. package/dist/common/textmeasure.d.ts.map +1 -0
  176. package/dist/common/types.d.ts +316 -0
  177. package/dist/common/types.d.ts.map +1 -0
  178. package/dist/common/xlabels-place.d.ts +125 -0
  179. package/dist/common/xlabels-place.d.ts.map +1 -0
  180. package/dist/debug.d.ts +58 -0
  181. package/dist/debug.d.ts.map +1 -0
  182. package/dist/errors.d.ts +63 -0
  183. package/dist/errors.d.ts.map +1 -0
  184. package/dist/gvc/anchor.d.ts +86 -0
  185. package/dist/gvc/anchor.d.ts.map +1 -0
  186. package/dist/gvc/context.d.ts +177 -0
  187. package/dist/gvc/context.d.ts.map +1 -0
  188. package/dist/gvc/default-context.d.ts +20 -0
  189. package/dist/gvc/default-context.d.ts.map +1 -0
  190. package/dist/gvc/device-cluster.d.ts +25 -0
  191. package/dist/gvc/device-cluster.d.ts.map +1 -0
  192. package/dist/gvc/device.d.ts +99 -0
  193. package/dist/gvc/device.d.ts.map +1 -0
  194. package/dist/gvc/edge-labels.d.ts +10 -0
  195. package/dist/gvc/edge-labels.d.ts.map +1 -0
  196. package/dist/gvc/emit-walk.d.ts +22 -0
  197. package/dist/gvc/emit-walk.d.ts.map +1 -0
  198. package/dist/gvc/image-resolver.d.ts +44 -0
  199. package/dist/gvc/image-resolver.d.ts.map +1 -0
  200. package/dist/gvc/job.d.ts +234 -0
  201. package/dist/gvc/job.d.ts.map +1 -0
  202. package/dist/gvc/textlayout.d.ts +33 -0
  203. package/dist/gvc/textlayout.d.ts.map +1 -0
  204. package/dist/gvc/textspan-emit.d.ts +41 -0
  205. package/dist/gvc/textspan-emit.d.ts.map +1 -0
  206. package/dist/gvc/usershape.d.ts +25 -0
  207. package/dist/gvc/usershape.d.ts.map +1 -0
  208. package/dist/gvc/viewport.d.ts +71 -0
  209. package/dist/gvc/viewport.d.ts.map +1 -0
  210. package/dist/index.d.ts +54 -0
  211. package/dist/index.d.ts.map +1 -0
  212. package/dist/index.js +52862 -0
  213. package/dist/index.js.map +7 -0
  214. package/dist/label/index.d.ts +74 -0
  215. package/dist/label/index.d.ts.map +1 -0
  216. package/dist/label/node.d.ts +108 -0
  217. package/dist/label/node.d.ts.map +1 -0
  218. package/dist/label/rectangle.d.ts +60 -0
  219. package/dist/label/rectangle.d.ts.map +1 -0
  220. package/dist/label/split-q.d.ts +13 -0
  221. package/dist/label/split-q.d.ts.map +1 -0
  222. package/dist/label/xlabels-geom.d.ts +46 -0
  223. package/dist/label/xlabels-geom.d.ts.map +1 -0
  224. package/dist/label/xlabels-intersect.d.ts +79 -0
  225. package/dist/label/xlabels-intersect.d.ts.map +1 -0
  226. package/dist/label/xlabels.d.ts +67 -0
  227. package/dist/label/xlabels.d.ts.map +1 -0
  228. package/dist/layout/circo/blockpath.d.ts +120 -0
  229. package/dist/layout/circo/blockpath.d.ts.map +1 -0
  230. package/dist/layout/circo/blocks.d.ts +135 -0
  231. package/dist/layout/circo/blocks.d.ts.map +1 -0
  232. package/dist/layout/circo/blocktree.d.ts +51 -0
  233. package/dist/layout/circo/blocktree.d.ts.map +1 -0
  234. package/dist/layout/circo/circular.d.ts +59 -0
  235. package/dist/layout/circo/circular.d.ts.map +1 -0
  236. package/dist/layout/circo/index.d.ts +35 -0
  237. package/dist/layout/circo/index.d.ts.map +1 -0
  238. package/dist/layout/circo/init.d.ts +50 -0
  239. package/dist/layout/circo/init.d.ts.map +1 -0
  240. package/dist/layout/circo/position-helpers.d.ts +33 -0
  241. package/dist/layout/circo/position-helpers.d.ts.map +1 -0
  242. package/dist/layout/circo/position.d.ts +66 -0
  243. package/dist/layout/circo/position.d.ts.map +1 -0
  244. package/dist/layout/dot/acyclic.d.ts +8 -0
  245. package/dist/layout/dot/acyclic.d.ts.map +1 -0
  246. package/dist/layout/dot/aspect.d.ts +17 -0
  247. package/dist/layout/dot/aspect.d.ts.map +1 -0
  248. package/dist/layout/dot/classify.d.ts +52 -0
  249. package/dist/layout/dot/classify.d.ts.map +1 -0
  250. package/dist/layout/dot/cluster-path.d.ts +38 -0
  251. package/dist/layout/dot/cluster-path.d.ts.map +1 -0
  252. package/dist/layout/dot/cluster.d.ts +102 -0
  253. package/dist/layout/dot/cluster.d.ts.map +1 -0
  254. package/dist/layout/dot/compound-clip.d.ts +69 -0
  255. package/dist/layout/dot/compound-clip.d.ts.map +1 -0
  256. package/dist/layout/dot/compound-geom.d.ts +115 -0
  257. package/dist/layout/dot/compound-geom.d.ts.map +1 -0
  258. package/dist/layout/dot/compound.d.ts +56 -0
  259. package/dist/layout/dot/compound.d.ts.map +1 -0
  260. package/dist/layout/dot/conc.d.ts +88 -0
  261. package/dist/layout/dot/conc.d.ts.map +1 -0
  262. package/dist/layout/dot/decomp.d.ts +66 -0
  263. package/dist/layout/dot/decomp.d.ts.map +1 -0
  264. package/dist/layout/dot/edge-order.d.ts +27 -0
  265. package/dist/layout/dot/edge-order.d.ts.map +1 -0
  266. package/dist/layout/dot/edge-route-arrow.d.ts +46 -0
  267. package/dist/layout/dot/edge-route-arrow.d.ts.map +1 -0
  268. package/dist/layout/dot/edge-route-boxes.d.ts +75 -0
  269. package/dist/layout/dot/edge-route-boxes.d.ts.map +1 -0
  270. package/dist/layout/dot/edge-route-chain.d.ts +67 -0
  271. package/dist/layout/dot/edge-route-chain.d.ts.map +1 -0
  272. package/dist/layout/dot/edge-route-clip.d.ts +88 -0
  273. package/dist/layout/dot/edge-route-clip.d.ts.map +1 -0
  274. package/dist/layout/dot/edge-route-faithful.d.ts +94 -0
  275. package/dist/layout/dot/edge-route-faithful.d.ts.map +1 -0
  276. package/dist/layout/dot/edge-route-geom.d.ts +43 -0
  277. package/dist/layout/dot/edge-route-geom.d.ts.map +1 -0
  278. package/dist/layout/dot/edge-route-helpers.d.ts +50 -0
  279. package/dist/layout/dot/edge-route-helpers.d.ts.map +1 -0
  280. package/dist/layout/dot/edge-route-poly.d.ts +48 -0
  281. package/dist/layout/dot/edge-route-poly.d.ts.map +1 -0
  282. package/dist/layout/dot/edge-route-rank.d.ts +57 -0
  283. package/dist/layout/dot/edge-route-rank.d.ts.map +1 -0
  284. package/dist/layout/dot/edge-route-routing.d.ts +99 -0
  285. package/dist/layout/dot/edge-route-routing.d.ts.map +1 -0
  286. package/dist/layout/dot/edge-route.d.ts +57 -0
  287. package/dist/layout/dot/edge-route.d.ts.map +1 -0
  288. package/dist/layout/dot/fastgr.d.ts +147 -0
  289. package/dist/layout/dot/fastgr.d.ts.map +1 -0
  290. package/dist/layout/dot/flat-utils.d.ts +21 -0
  291. package/dist/layout/dot/flat-utils.d.ts.map +1 -0
  292. package/dist/layout/dot/flat.d.ts +152 -0
  293. package/dist/layout/dot/flat.d.ts.map +1 -0
  294. package/dist/layout/dot/graph-label.d.ts +18 -0
  295. package/dist/layout/dot/graph-label.d.ts.map +1 -0
  296. package/dist/layout/dot/index.d.ts +128 -0
  297. package/dist/layout/dot/index.d.ts.map +1 -0
  298. package/dist/layout/dot/init.d.ts +93 -0
  299. package/dist/layout/dot/init.d.ts.map +1 -0
  300. package/dist/layout/dot/label-order.d.ts +45 -0
  301. package/dist/layout/dot/label-order.d.ts.map +1 -0
  302. package/dist/layout/dot/mincross-build.d.ts +52 -0
  303. package/dist/layout/dot/mincross-build.d.ts.map +1 -0
  304. package/dist/layout/dot/mincross-cross.d.ts +80 -0
  305. package/dist/layout/dot/mincross-cross.d.ts.map +1 -0
  306. package/dist/layout/dot/mincross-flat.d.ts +27 -0
  307. package/dist/layout/dot/mincross-flat.d.ts.map +1 -0
  308. package/dist/layout/dot/mincross-order.d.ts +89 -0
  309. package/dist/layout/dot/mincross-order.d.ts.map +1 -0
  310. package/dist/layout/dot/mincross-utils.d.ts +60 -0
  311. package/dist/layout/dot/mincross-utils.d.ts.map +1 -0
  312. package/dist/layout/dot/mincross.d.ts +75 -0
  313. package/dist/layout/dot/mincross.d.ts.map +1 -0
  314. package/dist/layout/dot/ns-core.d.ts +37 -0
  315. package/dist/layout/dot/ns-core.d.ts.map +1 -0
  316. package/dist/layout/dot/ns-range.d.ts +17 -0
  317. package/dist/layout/dot/ns-range.d.ts.map +1 -0
  318. package/dist/layout/dot/ns-subtree.d.ts +78 -0
  319. package/dist/layout/dot/ns-subtree.d.ts.map +1 -0
  320. package/dist/layout/dot/ns.d.ts +86 -0
  321. package/dist/layout/dot/ns.d.ts.map +1 -0
  322. package/dist/layout/dot/ortho-adapter.d.ts +19 -0
  323. package/dist/layout/dot/ortho-adapter.d.ts.map +1 -0
  324. package/dist/layout/dot/pack-components.d.ts +100 -0
  325. package/dist/layout/dot/pack-components.d.ts.map +1 -0
  326. package/dist/layout/dot/position-aux.d.ts +110 -0
  327. package/dist/layout/dot/position-aux.d.ts.map +1 -0
  328. package/dist/layout/dot/position-bbox.d.ts +57 -0
  329. package/dist/layout/dot/position-bbox.d.ts.map +1 -0
  330. package/dist/layout/dot/position-cluster.d.ts +48 -0
  331. package/dist/layout/dot/position-cluster.d.ts.map +1 -0
  332. package/dist/layout/dot/position-ycoords.d.ts +42 -0
  333. package/dist/layout/dot/position-ycoords.d.ts.map +1 -0
  334. package/dist/layout/dot/position.d.ts +47 -0
  335. package/dist/layout/dot/position.d.ts.map +1 -0
  336. package/dist/layout/dot/rank-dot2.d.ts +114 -0
  337. package/dist/layout/dot/rank-dot2.d.ts.map +1 -0
  338. package/dist/layout/dot/rank.d.ts +119 -0
  339. package/dist/layout/dot/rank.d.ts.map +1 -0
  340. package/dist/layout/dot/sameport.d.ts +70 -0
  341. package/dist/layout/dot/sameport.d.ts.map +1 -0
  342. package/dist/layout/dot/self-loop.d.ts +54 -0
  343. package/dist/layout/dot/self-loop.d.ts.map +1 -0
  344. package/dist/layout/dot/splines-clone.d.ts +33 -0
  345. package/dist/layout/dot/splines-clone.d.ts.map +1 -0
  346. package/dist/layout/dot/splines-flat-labeled.d.ts +101 -0
  347. package/dist/layout/dot/splines-flat-labeled.d.ts.map +1 -0
  348. package/dist/layout/dot/splines-flat-multi.d.ts +35 -0
  349. package/dist/layout/dot/splines-flat-multi.d.ts.map +1 -0
  350. package/dist/layout/dot/splines-flat.d.ts +116 -0
  351. package/dist/layout/dot/splines-flat.d.ts.map +1 -0
  352. package/dist/layout/dot/splines-groups.d.ts +38 -0
  353. package/dist/layout/dot/splines-groups.d.ts.map +1 -0
  354. package/dist/layout/dot/splines-label.d.ts +105 -0
  355. package/dist/layout/dot/splines-label.d.ts.map +1 -0
  356. package/dist/layout/dot/splines-route-type.d.ts +28 -0
  357. package/dist/layout/dot/splines-route-type.d.ts.map +1 -0
  358. package/dist/layout/dot/splines-route.d.ts +99 -0
  359. package/dist/layout/dot/splines-route.d.ts.map +1 -0
  360. package/dist/layout/dot/splines.d.ts +157 -0
  361. package/dist/layout/dot/splines.d.ts.map +1 -0
  362. package/dist/layout/dot/straight-edges.d.ts +39 -0
  363. package/dist/layout/dot/straight-edges.d.ts.map +1 -0
  364. package/dist/layout/fdp/cluster-edges.d.ts +31 -0
  365. package/dist/layout/fdp/cluster-edges.d.ts.map +1 -0
  366. package/dist/layout/fdp/comp.d.ts +29 -0
  367. package/dist/layout/fdp/comp.d.ts.map +1 -0
  368. package/dist/layout/fdp/derive.d.ts +39 -0
  369. package/dist/layout/fdp/derive.d.ts.map +1 -0
  370. package/dist/layout/fdp/fdp-model.d.ts +101 -0
  371. package/dist/layout/fdp/fdp-model.d.ts.map +1 -0
  372. package/dist/layout/fdp/grid.d.ts +49 -0
  373. package/dist/layout/fdp/grid.d.ts.map +1 -0
  374. package/dist/layout/fdp/index.d.ts +36 -0
  375. package/dist/layout/fdp/index.d.ts.map +1 -0
  376. package/dist/layout/fdp/init.d.ts +26 -0
  377. package/dist/layout/fdp/init.d.ts.map +1 -0
  378. package/dist/layout/fdp/layout.d.ts +57 -0
  379. package/dist/layout/fdp/layout.d.ts.map +1 -0
  380. package/dist/layout/fdp/normalize.d.ts +18 -0
  381. package/dist/layout/fdp/normalize.d.ts.map +1 -0
  382. package/dist/layout/fdp/ports.d.ts +41 -0
  383. package/dist/layout/fdp/ports.d.ts.map +1 -0
  384. package/dist/layout/fdp/tlayout-init.d.ts +21 -0
  385. package/dist/layout/fdp/tlayout-init.d.ts.map +1 -0
  386. package/dist/layout/fdp/tlayout-parms.d.ts +98 -0
  387. package/dist/layout/fdp/tlayout-parms.d.ts.map +1 -0
  388. package/dist/layout/fdp/tlayout.d.ts +43 -0
  389. package/dist/layout/fdp/tlayout.d.ts.map +1 -0
  390. package/dist/layout/fdp/xlayout.d.ts +26 -0
  391. package/dist/layout/fdp/xlayout.d.ts.map +1 -0
  392. package/dist/layout/neato/bfs.d.ts +43 -0
  393. package/dist/layout/neato/bfs.d.ts.map +1 -0
  394. package/dist/layout/neato/cdt-surface.d.ts +16 -0
  395. package/dist/layout/neato/cdt-surface.d.ts.map +1 -0
  396. package/dist/layout/neato/conjgrad.d.ts +61 -0
  397. package/dist/layout/neato/conjgrad.d.ts.map +1 -0
  398. package/dist/layout/neato/delaunay.d.ts +28 -0
  399. package/dist/layout/neato/delaunay.d.ts.map +1 -0
  400. package/dist/layout/neato/dijkstra.d.ts +80 -0
  401. package/dist/layout/neato/dijkstra.d.ts.map +1 -0
  402. package/dist/layout/neato/fdp-adjust.d.ts +31 -0
  403. package/dist/layout/neato/fdp-adjust.d.ts.map +1 -0
  404. package/dist/layout/neato/index.d.ts +58 -0
  405. package/dist/layout/neato/index.d.ts.map +1 -0
  406. package/dist/layout/neato/init.d.ts +207 -0
  407. package/dist/layout/neato/init.d.ts.map +1 -0
  408. package/dist/layout/neato/legal.d.ts +7 -0
  409. package/dist/layout/neato/legal.d.ts.map +1 -0
  410. package/dist/layout/neato/matrix-ops.d.ts +28 -0
  411. package/dist/layout/neato/matrix-ops.d.ts.map +1 -0
  412. package/dist/layout/neato/multispline-router.d.ts +80 -0
  413. package/dist/layout/neato/multispline-router.d.ts.map +1 -0
  414. package/dist/layout/neato/multispline.d.ts +24 -0
  415. package/dist/layout/neato/multispline.d.ts.map +1 -0
  416. package/dist/layout/neato/overlap-prism.d.ts +26 -0
  417. package/dist/layout/neato/overlap-prism.d.ts.map +1 -0
  418. package/dist/layout/neato/overlap.d.ts +85 -0
  419. package/dist/layout/neato/overlap.d.ts.map +1 -0
  420. package/dist/layout/neato/sc-adjust.d.ts +33 -0
  421. package/dist/layout/neato/sc-adjust.d.ts.map +1 -0
  422. package/dist/layout/neato/sep-factor.d.ts +41 -0
  423. package/dist/layout/neato/sep-factor.d.ts.map +1 -0
  424. package/dist/layout/neato/set-aspect.d.ts +33 -0
  425. package/dist/layout/neato/set-aspect.d.ts.map +1 -0
  426. package/dist/layout/neato/sgd.d.ts +166 -0
  427. package/dist/layout/neato/sgd.d.ts.map +1 -0
  428. package/dist/layout/neato/splines.d.ts +153 -0
  429. package/dist/layout/neato/splines.d.ts.map +1 -0
  430. package/dist/layout/neato/stress-kernel.d.ts +53 -0
  431. package/dist/layout/neato/stress-kernel.d.ts.map +1 -0
  432. package/dist/layout/neato/stress.d.ts +158 -0
  433. package/dist/layout/neato/stress.d.ts.map +1 -0
  434. package/dist/layout/osage/index.d.ts +158 -0
  435. package/dist/layout/osage/index.d.ts.map +1 -0
  436. package/dist/layout/osage/layout.d.ts +49 -0
  437. package/dist/layout/osage/layout.d.ts.map +1 -0
  438. package/dist/layout/osage/types.d.ts +28 -0
  439. package/dist/layout/osage/types.d.ts.map +1 -0
  440. package/dist/layout/pack/array-pack.d.ts +114 -0
  441. package/dist/layout/pack/array-pack.d.ts.map +1 -0
  442. package/dist/layout/pack/index.d.ts +157 -0
  443. package/dist/layout/pack/index.d.ts.map +1 -0
  444. package/dist/layout/pack/poly-pack.d.ts +126 -0
  445. package/dist/layout/pack/poly-pack.d.ts.map +1 -0
  446. package/dist/layout/pack/poly-place.d.ts +27 -0
  447. package/dist/layout/pack/poly-place.d.ts.map +1 -0
  448. package/dist/layout/pack/spiral-search.d.ts +18 -0
  449. package/dist/layout/pack/spiral-search.d.ts.map +1 -0
  450. package/dist/layout/pack/test-helpers.d.ts +7 -0
  451. package/dist/layout/pack/test-helpers.d.ts.map +1 -0
  452. package/dist/layout/pack/types.d.ts +74 -0
  453. package/dist/layout/pack/types.d.ts.map +1 -0
  454. package/dist/layout/patchwork/index.d.ts +136 -0
  455. package/dist/layout/patchwork/index.d.ts.map +1 -0
  456. package/dist/layout/patchwork/tree-map.d.ts +32 -0
  457. package/dist/layout/patchwork/tree-map.d.ts.map +1 -0
  458. package/dist/layout/patchwork/tree-node.d.ts +46 -0
  459. package/dist/layout/patchwork/tree-node.d.ts.map +1 -0
  460. package/dist/layout/sfdp/index.d.ts +39 -0
  461. package/dist/layout/sfdp/index.d.ts.map +1 -0
  462. package/dist/layout/sfdp/init.d.ts +61 -0
  463. package/dist/layout/sfdp/init.d.ts.map +1 -0
  464. package/dist/layout/sfdp/multilevel.d.ts +23 -0
  465. package/dist/layout/sfdp/multilevel.d.ts.map +1 -0
  466. package/dist/layout/sfdp/quadtree.d.ts +45 -0
  467. package/dist/layout/sfdp/quadtree.d.ts.map +1 -0
  468. package/dist/layout/sfdp/sparse-matrix-multiply.d.ts +34 -0
  469. package/dist/layout/sfdp/sparse-matrix-multiply.d.ts.map +1 -0
  470. package/dist/layout/sfdp/sparse-matrix.d.ts +101 -0
  471. package/dist/layout/sfdp/sparse-matrix.d.ts.map +1 -0
  472. package/dist/layout/sfdp/sparse-solve.d.ts +14 -0
  473. package/dist/layout/sfdp/sparse-solve.d.ts.map +1 -0
  474. package/dist/layout/sfdp/spring-driver.d.ts +18 -0
  475. package/dist/layout/sfdp/spring-driver.d.ts.map +1 -0
  476. package/dist/layout/sfdp/spring-electrical.d.ts +61 -0
  477. package/dist/layout/sfdp/spring-electrical.d.ts.map +1 -0
  478. package/dist/layout/twopi/circle.d.ts +83 -0
  479. package/dist/layout/twopi/circle.d.ts.map +1 -0
  480. package/dist/layout/twopi/index.d.ts +24 -0
  481. package/dist/layout/twopi/index.d.ts.map +1 -0
  482. package/dist/layout/twopi/init.d.ts +85 -0
  483. package/dist/layout/twopi/init.d.ts.map +1 -0
  484. package/dist/layout/twopi/pipeline.d.ts +37 -0
  485. package/dist/layout/twopi/pipeline.d.ts.map +1 -0
  486. package/dist/layout/twopi/twopi-test-helpers.d.ts +12 -0
  487. package/dist/layout/twopi/twopi-test-helpers.d.ts.map +1 -0
  488. package/dist/model/algData.d.ts +175 -0
  489. package/dist/model/algData.d.ts.map +1 -0
  490. package/dist/model/cgraph-ops.d.ts +86 -0
  491. package/dist/model/cgraph-ops.d.ts.map +1 -0
  492. package/dist/model/edge.d.ts +80 -0
  493. package/dist/model/edge.d.ts.map +1 -0
  494. package/dist/model/edgeInfo.d.ts +299 -0
  495. package/dist/model/edgeInfo.d.ts.map +1 -0
  496. package/dist/model/geom.d.ts +149 -0
  497. package/dist/model/geom.d.ts.map +1 -0
  498. package/dist/model/graph.d.ts +135 -0
  499. package/dist/model/graph.d.ts.map +1 -0
  500. package/dist/model/graphInfo.d.ts +418 -0
  501. package/dist/model/graphInfo.d.ts.map +1 -0
  502. package/dist/model/index.d.ts +10 -0
  503. package/dist/model/index.d.ts.map +1 -0
  504. package/dist/model/layoutParams.d.ts +85 -0
  505. package/dist/model/layoutParams.d.ts.map +1 -0
  506. package/dist/model/node.d.ts +101 -0
  507. package/dist/model/node.d.ts.map +1 -0
  508. package/dist/model/nodeInfo.d.ts +278 -0
  509. package/dist/model/nodeInfo.d.ts.map +1 -0
  510. package/dist/model/rankEntry.d.ts +80 -0
  511. package/dist/model/rankEntry.d.ts.map +1 -0
  512. package/dist/ortho/chan-dict.d.ts +82 -0
  513. package/dist/ortho/chan-dict.d.ts.map +1 -0
  514. package/dist/ortho/fpq.d.ts +54 -0
  515. package/dist/ortho/fpq.d.ts.map +1 -0
  516. package/dist/ortho/index.d.ts +35 -0
  517. package/dist/ortho/index.d.ts.map +1 -0
  518. package/dist/ortho/maze-channels.d.ts +47 -0
  519. package/dist/ortho/maze-channels.d.ts.map +1 -0
  520. package/dist/ortho/maze.d.ts +27 -0
  521. package/dist/ortho/maze.d.ts.map +1 -0
  522. package/dist/ortho/ortho-parallel.d.ts +26 -0
  523. package/dist/ortho/ortho-parallel.d.ts.map +1 -0
  524. package/dist/ortho/ortho-route.d.ts +31 -0
  525. package/dist/ortho/ortho-route.d.ts.map +1 -0
  526. package/dist/ortho/partition.d.ts +34 -0
  527. package/dist/ortho/partition.d.ts.map +1 -0
  528. package/dist/ortho/rawgraph.d.ts +41 -0
  529. package/dist/ortho/rawgraph.d.ts.map +1 -0
  530. package/dist/ortho/sgraph.d.ts +74 -0
  531. package/dist/ortho/sgraph.d.ts.map +1 -0
  532. package/dist/ortho/trap-query.d.ts +32 -0
  533. package/dist/ortho/trap-query.d.ts.map +1 -0
  534. package/dist/ortho/trap-segment.d.ts +23 -0
  535. package/dist/ortho/trap-segment.d.ts.map +1 -0
  536. package/dist/ortho/trap-types.d.ts +91 -0
  537. package/dist/ortho/trap-types.d.ts.map +1 -0
  538. package/dist/ortho/trap-update.d.ts +12 -0
  539. package/dist/ortho/trap-update.d.ts.map +1 -0
  540. package/dist/ortho/trapezoid.d.ts +18 -0
  541. package/dist/ortho/trapezoid.d.ts.map +1 -0
  542. package/dist/ortho/types.d.ts +241 -0
  543. package/dist/ortho/types.d.ts.map +1 -0
  544. package/dist/parser/ast.d.ts +49 -0
  545. package/dist/parser/ast.d.ts.map +1 -0
  546. package/dist/parser/builder.d.ts +75 -0
  547. package/dist/parser/builder.d.ts.map +1 -0
  548. package/dist/parser/index.d.ts +97 -0
  549. package/dist/parser/index.d.ts.map +1 -0
  550. package/dist/pathplan/index.d.ts +8 -0
  551. package/dist/pathplan/index.d.ts.map +1 -0
  552. package/dist/pathplan/route.d.ts +9 -0
  553. package/dist/pathplan/route.d.ts.map +1 -0
  554. package/dist/pathplan/shortest.d.ts +5 -0
  555. package/dist/pathplan/shortest.d.ts.map +1 -0
  556. package/dist/pathplan/solvers.d.ts +5 -0
  557. package/dist/pathplan/solvers.d.ts.map +1 -0
  558. package/dist/pathplan/triang.d.ts +25 -0
  559. package/dist/pathplan/triang.d.ts.map +1 -0
  560. package/dist/pathplan/types.d.ts +22 -0
  561. package/dist/pathplan/types.d.ts.map +1 -0
  562. package/dist/pathplan/visibility.d.ts +13 -0
  563. package/dist/pathplan/visibility.d.ts.map +1 -0
  564. package/dist/pathplan/vispath.d.ts +9 -0
  565. package/dist/pathplan/vispath.d.ts.map +1 -0
  566. package/dist/rbtree/index.d.ts +127 -0
  567. package/dist/rbtree/index.d.ts.map +1 -0
  568. package/dist/render/color-resolve.d.ts +38 -0
  569. package/dist/render/color-resolve.d.ts.map +1 -0
  570. package/dist/render/dot.d.ts +379 -0
  571. package/dist/render/dot.d.ts.map +1 -0
  572. package/dist/render/index.d.ts +24 -0
  573. package/dist/render/index.d.ts.map +1 -0
  574. package/dist/render/json.d.ts +89 -0
  575. package/dist/render/json.d.ts.map +1 -0
  576. package/dist/render/map-test-helpers.d.ts +13 -0
  577. package/dist/render/map-test-helpers.d.ts.map +1 -0
  578. package/dist/render/map.d.ts +197 -0
  579. package/dist/render/map.d.ts.map +1 -0
  580. package/dist/render/public.d.ts +64 -0
  581. package/dist/render/public.d.ts.map +1 -0
  582. package/dist/render/svg-arrow-ops.d.ts +10 -0
  583. package/dist/render/svg-arrow-ops.d.ts.map +1 -0
  584. package/dist/render/svg-cluster.d.ts +9 -0
  585. package/dist/render/svg-cluster.d.ts.map +1 -0
  586. package/dist/render/svg-edge-ortho-radius.d.ts +31 -0
  587. package/dist/render/svg-edge-ortho-radius.d.ts.map +1 -0
  588. package/dist/render/svg-edge-split.d.ts +48 -0
  589. package/dist/render/svg-edge-split.d.ts.map +1 -0
  590. package/dist/render/svg-gradient.d.ts +46 -0
  591. package/dist/render/svg-gradient.d.ts.map +1 -0
  592. package/dist/render/svg-graph.d.ts +97 -0
  593. package/dist/render/svg-graph.d.ts.map +1 -0
  594. package/dist/render/svg-helpers.d.ts +122 -0
  595. package/dist/render/svg-helpers.d.ts.map +1 -0
  596. package/dist/render/svg-id.d.ts +28 -0
  597. package/dist/render/svg-id.d.ts.map +1 -0
  598. package/dist/render/svg-multicolor.d.ts +60 -0
  599. package/dist/render/svg-multicolor.d.ts.map +1 -0
  600. package/dist/render/svg-parallel-edge.d.ts +28 -0
  601. package/dist/render/svg-parallel-edge.d.ts.map +1 -0
  602. package/dist/render/svg-tapered-edge.d.ts +18 -0
  603. package/dist/render/svg-tapered-edge.d.ts.map +1 -0
  604. package/dist/render/svg.d.ts +104 -0
  605. package/dist/render/svg.d.ts.map +1 -0
  606. package/dist/render/xdot-public.d.ts +95 -0
  607. package/dist/render/xdot-public.d.ts.map +1 -0
  608. package/dist/render/xml-escape.d.ts +21 -0
  609. package/dist/render/xml-escape.d.ts.map +1 -0
  610. package/dist/render.js +52556 -0
  611. package/dist/render.js.map +7 -0
  612. package/dist/util/agxbuf.d.ts +72 -0
  613. package/dist/util/agxbuf.d.ts.map +1 -0
  614. package/dist/util/bsd-qsort.d.ts +22 -0
  615. package/dist/util/bsd-qsort.d.ts.map +1 -0
  616. package/dist/util/index.d.ts +11 -0
  617. package/dist/util/index.d.ts.map +1 -0
  618. package/dist/util/list.d.ts +47 -0
  619. package/dist/util/list.d.ts.map +1 -0
  620. package/dist/util/math.d.ts +54 -0
  621. package/dist/util/math.d.ts.map +1 -0
  622. package/dist/util/mt19937.d.ts +40 -0
  623. package/dist/util/mt19937.d.ts.map +1 -0
  624. package/dist/util/printf-round.d.ts +45 -0
  625. package/dist/util/printf-round.d.ts.map +1 -0
  626. package/dist/util/xml.d.ts +24 -0
  627. package/dist/util/xml.d.ts.map +1 -0
  628. package/dist/vpsc/Block.d.ts +116 -0
  629. package/dist/vpsc/Block.d.ts.map +1 -0
  630. package/dist/vpsc/Blocks.d.ts +66 -0
  631. package/dist/vpsc/Blocks.d.ts.map +1 -0
  632. package/dist/vpsc/Constraint.d.ts +51 -0
  633. package/dist/vpsc/Constraint.d.ts.map +1 -0
  634. package/dist/vpsc/HeapUtils.d.ts +37 -0
  635. package/dist/vpsc/HeapUtils.d.ts.map +1 -0
  636. package/dist/vpsc/Solver.d.ts +107 -0
  637. package/dist/vpsc/Solver.d.ts.map +1 -0
  638. package/dist/vpsc/SweepLine.d.ts +38 -0
  639. package/dist/vpsc/SweepLine.d.ts.map +1 -0
  640. package/dist/vpsc/Variable.d.ts +38 -0
  641. package/dist/vpsc/Variable.d.ts.map +1 -0
  642. package/dist/vpsc/index.d.ts +54 -0
  643. package/dist/vpsc/index.d.ts.map +1 -0
  644. package/dist/vpsc/sweepTypes.d.ts +29 -0
  645. package/dist/vpsc/sweepTypes.d.ts.map +1 -0
  646. package/dist/xdot/index.d.ts +6 -0
  647. package/dist/xdot/index.d.ts.map +1 -0
  648. package/dist/xdot/lex.d.ts +22 -0
  649. package/dist/xdot/lex.d.ts.map +1 -0
  650. package/dist/xdot/misc.d.ts +13 -0
  651. package/dist/xdot/misc.d.ts.map +1 -0
  652. package/dist/xdot/parse.d.ts +21 -0
  653. package/dist/xdot/parse.d.ts.map +1 -0
  654. package/dist/xdot/types.d.ts +152 -0
  655. package/dist/xdot/types.d.ts.map +1 -0
  656. package/package.json +113 -0
  657. package/src/api/builder.ts +277 -0
  658. package/src/api/edge-ops.ts +113 -0
  659. package/src/api/geometry.ts +307 -0
  660. package/src/api/index.ts +46 -0
  661. package/src/cdt/bag.ts +196 -0
  662. package/src/cdt/hash-core.ts +84 -0
  663. package/src/cdt/hash.ts +207 -0
  664. package/src/cdt/index.ts +17 -0
  665. package/src/cdt/splay-core.ts +301 -0
  666. package/src/cdt/splay.ts +191 -0
  667. package/src/cdt/strhash.ts +52 -0
  668. package/src/cdt/types.ts +13 -0
  669. package/src/common/arith.ts +39 -0
  670. package/src/common/arm-pow-data.ts +201 -0
  671. package/src/common/arm-pow.ts +296 -0
  672. package/src/common/arrows-constants.ts +175 -0
  673. package/src/common/arrows-geometry.ts +62 -0
  674. package/src/common/arrows-miter.ts +84 -0
  675. package/src/common/arrows-shapes-poly.ts +241 -0
  676. package/src/common/arrows-shapes-util.ts +162 -0
  677. package/src/common/arrows-shapes.ts +360 -0
  678. package/src/common/arrows-types.ts +59 -0
  679. package/src/common/arrows.ts +209 -0
  680. package/src/common/color.ts +340 -0
  681. package/src/common/colorData.ts +264 -0
  682. package/src/common/compass-port.ts +384 -0
  683. package/src/common/crand.ts +79 -0
  684. package/src/common/edge-label-init.ts +310 -0
  685. package/src/common/edge-offset.ts +140 -0
  686. package/src/common/ellipse-wedge.ts +189 -0
  687. package/src/common/emit-types.ts +332 -0
  688. package/src/common/fma.ts +52 -0
  689. package/src/common/graph-init.ts +359 -0
  690. package/src/common/html-entities.ts +299 -0
  691. package/src/common/html-string.ts +22 -0
  692. package/src/common/htmltable-emit-fill.ts +367 -0
  693. package/src/common/htmltable-emit-rules.ts +293 -0
  694. package/src/common/htmltable-emit.ts +345 -0
  695. package/src/common/htmltable-lex.ts +138 -0
  696. package/src/common/htmltable-parse.ts +539 -0
  697. package/src/common/htmltable-port.ts +32 -0
  698. package/src/common/htmltable-pos-runs.ts +313 -0
  699. package/src/common/htmltable-pos.ts +601 -0
  700. package/src/common/htmltable-types.ts +262 -0
  701. package/src/common/htmltable.ts +477 -0
  702. package/src/common/layers.ts +113 -0
  703. package/src/common/make-label.ts +192 -0
  704. package/src/common/multicolor.ts +167 -0
  705. package/src/common/nodeinit.ts +339 -0
  706. package/src/common/poly-gencode.ts +475 -0
  707. package/src/common/poly-init.ts +133 -0
  708. package/src/common/poly-inside.ts +202 -0
  709. package/src/common/poly-shapes-cases.ts +253 -0
  710. package/src/common/poly-shapes-cases2.ts +170 -0
  711. package/src/common/poly-shapes-util.ts +124 -0
  712. package/src/common/poly-shapes.ts +181 -0
  713. package/src/common/poly-sizing.ts +639 -0
  714. package/src/common/poly-vertices.ts +237 -0
  715. package/src/common/postproc.ts +436 -0
  716. package/src/common/ps-fontalias.ts +112 -0
  717. package/src/common/random.ts +37 -0
  718. package/src/common/record-port.ts +89 -0
  719. package/src/common/record.ts +523 -0
  720. package/src/common/shapeData.ts +502 -0
  721. package/src/common/shapes.ts +187 -0
  722. package/src/common/spline-midpoint.ts +105 -0
  723. package/src/common/splines-clip.ts +449 -0
  724. package/src/common/splines-constants.ts +53 -0
  725. package/src/common/splines-geom.ts +194 -0
  726. package/src/common/splines-path-begin.ts +243 -0
  727. package/src/common/splines-path-end.ts +231 -0
  728. package/src/common/splines-path-shared.ts +258 -0
  729. package/src/common/splines-routespl.ts +444 -0
  730. package/src/common/splines-selfedge.ts +403 -0
  731. package/src/common/splines.ts +33 -0
  732. package/src/common/style-resolve.ts +481 -0
  733. package/src/common/subst.ts +126 -0
  734. package/src/common/taper.ts +219 -0
  735. package/src/common/textmeasure-factory.ts +86 -0
  736. package/src/common/textmeasure-lookup.ts +50 -0
  737. package/src/common/textmeasure-lut-data-a.ts +182 -0
  738. package/src/common/textmeasure-lut-data-b.ts +140 -0
  739. package/src/common/textmeasure-lut-data-c.ts +221 -0
  740. package/src/common/textmeasure-lut-data.ts +78 -0
  741. package/src/common/textmeasure.ts +321 -0
  742. package/src/common/types.ts +425 -0
  743. package/src/common/xlabels-place.ts +475 -0
  744. package/src/debug.ts +38 -0
  745. package/src/errors.ts +95 -0
  746. package/src/gvc/anchor.ts +530 -0
  747. package/src/gvc/context.ts +260 -0
  748. package/src/gvc/default-context.ts +70 -0
  749. package/src/gvc/device-cluster.ts +114 -0
  750. package/src/gvc/device.ts +598 -0
  751. package/src/gvc/edge-labels.ts +116 -0
  752. package/src/gvc/emit-walk.ts +98 -0
  753. package/src/gvc/image-resolver.ts +137 -0
  754. package/src/gvc/job.ts +401 -0
  755. package/src/gvc/textlayout.ts +44 -0
  756. package/src/gvc/textspan-emit.ts +100 -0
  757. package/src/gvc/usershape.ts +33 -0
  758. package/src/gvc/viewport.ts +203 -0
  759. package/src/index.ts +165 -0
  760. package/src/label/index.ts +302 -0
  761. package/src/label/node.ts +278 -0
  762. package/src/label/rectangle.ts +123 -0
  763. package/src/label/split-q.ts +237 -0
  764. package/src/label/xlabels-geom.ts +132 -0
  765. package/src/label/xlabels-intersect.ts +421 -0
  766. package/src/label/xlabels.ts +212 -0
  767. package/src/layout/circo/blockpath.ts +560 -0
  768. package/src/layout/circo/blocks.ts +234 -0
  769. package/src/layout/circo/blocktree.ts +246 -0
  770. package/src/layout/circo/circular.ts +227 -0
  771. package/src/layout/circo/index.ts +90 -0
  772. package/src/layout/circo/init.ts +84 -0
  773. package/src/layout/circo/position-helpers.ts +104 -0
  774. package/src/layout/circo/position.ts +196 -0
  775. package/src/layout/dot/__fixtures__/xcoord-pivot-divergence.gv +79 -0
  776. package/src/layout/dot/acyclic.ts +60 -0
  777. package/src/layout/dot/aspect.ts +22 -0
  778. package/src/layout/dot/classify.ts +462 -0
  779. package/src/layout/dot/cluster-path.ts +200 -0
  780. package/src/layout/dot/cluster.ts +480 -0
  781. package/src/layout/dot/compound-clip.ts +224 -0
  782. package/src/layout/dot/compound-geom.ts +282 -0
  783. package/src/layout/dot/compound.ts +294 -0
  784. package/src/layout/dot/conc.ts +433 -0
  785. package/src/layout/dot/decomp.ts +165 -0
  786. package/src/layout/dot/edge-order.ts +62 -0
  787. package/src/layout/dot/edge-route-arrow.ts +87 -0
  788. package/src/layout/dot/edge-route-boxes.ts +99 -0
  789. package/src/layout/dot/edge-route-chain.ts +420 -0
  790. package/src/layout/dot/edge-route-clip.ts +253 -0
  791. package/src/layout/dot/edge-route-faithful.ts +404 -0
  792. package/src/layout/dot/edge-route-geom.ts +90 -0
  793. package/src/layout/dot/edge-route-helpers.ts +170 -0
  794. package/src/layout/dot/edge-route-poly.ts +174 -0
  795. package/src/layout/dot/edge-route-rank.ts +136 -0
  796. package/src/layout/dot/edge-route-routing.ts +219 -0
  797. package/src/layout/dot/edge-route.ts +490 -0
  798. package/src/layout/dot/fastgr.ts +377 -0
  799. package/src/layout/dot/flat-utils.ts +52 -0
  800. package/src/layout/dot/flat.ts +518 -0
  801. package/src/layout/dot/graph-label.ts +114 -0
  802. package/src/layout/dot/index.ts +302 -0
  803. package/src/layout/dot/init.ts +300 -0
  804. package/src/layout/dot/label-order.ts +161 -0
  805. package/src/layout/dot/mincross-build.ts +373 -0
  806. package/src/layout/dot/mincross-cross.ts +409 -0
  807. package/src/layout/dot/mincross-flat.ts +213 -0
  808. package/src/layout/dot/mincross-order.ts +401 -0
  809. package/src/layout/dot/mincross-utils.ts +170 -0
  810. package/src/layout/dot/mincross.ts +386 -0
  811. package/src/layout/dot/ns-core.ts +122 -0
  812. package/src/layout/dot/ns-range.ts +228 -0
  813. package/src/layout/dot/ns-subtree.ts +347 -0
  814. package/src/layout/dot/ns.ts +506 -0
  815. package/src/layout/dot/ortho-adapter.ts +133 -0
  816. package/src/layout/dot/pack-components.ts +433 -0
  817. package/src/layout/dot/position-aux.ts +382 -0
  818. package/src/layout/dot/position-bbox.ts +221 -0
  819. package/src/layout/dot/position-cluster.ts +286 -0
  820. package/src/layout/dot/position-ycoords.ts +287 -0
  821. package/src/layout/dot/position.ts +194 -0
  822. package/src/layout/dot/rank-dot2.ts +528 -0
  823. package/src/layout/dot/rank.ts +547 -0
  824. package/src/layout/dot/sameport.ts +278 -0
  825. package/src/layout/dot/self-loop.ts +147 -0
  826. package/src/layout/dot/splines-clone.ts +121 -0
  827. package/src/layout/dot/splines-flat-labeled.ts +408 -0
  828. package/src/layout/dot/splines-flat-multi.ts +188 -0
  829. package/src/layout/dot/splines-flat.ts +598 -0
  830. package/src/layout/dot/splines-groups.ts +190 -0
  831. package/src/layout/dot/splines-label.ts +432 -0
  832. package/src/layout/dot/splines-route-type.ts +44 -0
  833. package/src/layout/dot/splines-route.ts +478 -0
  834. package/src/layout/dot/splines.ts +538 -0
  835. package/src/layout/dot/straight-edges.ts +332 -0
  836. package/src/layout/fdp/cluster-edges.ts +0 -0
  837. package/src/layout/fdp/comp.ts +160 -0
  838. package/src/layout/fdp/derive.ts +272 -0
  839. package/src/layout/fdp/fdp-model.ts +238 -0
  840. package/src/layout/fdp/grid.ts +77 -0
  841. package/src/layout/fdp/index.ts +113 -0
  842. package/src/layout/fdp/init.ts +116 -0
  843. package/src/layout/fdp/layout.ts +485 -0
  844. package/src/layout/fdp/normalize.ts +100 -0
  845. package/src/layout/fdp/ports.ts +161 -0
  846. package/src/layout/fdp/tlayout-init.ts +230 -0
  847. package/src/layout/fdp/tlayout-parms.ts +234 -0
  848. package/src/layout/fdp/tlayout.ts +317 -0
  849. package/src/layout/fdp/xlayout.ts +350 -0
  850. package/src/layout/neato/bfs.ts +131 -0
  851. package/src/layout/neato/cdt-surface.ts +693 -0
  852. package/src/layout/neato/conjgrad.ts +159 -0
  853. package/src/layout/neato/delaunay.ts +259 -0
  854. package/src/layout/neato/dijkstra.ts +296 -0
  855. package/src/layout/neato/fdp-adjust.ts +124 -0
  856. package/src/layout/neato/index.ts +310 -0
  857. package/src/layout/neato/init.ts +505 -0
  858. package/src/layout/neato/legal.ts +316 -0
  859. package/src/layout/neato/matrix-ops.ts +112 -0
  860. package/src/layout/neato/multispline-router.ts +417 -0
  861. package/src/layout/neato/multispline.ts +427 -0
  862. package/src/layout/neato/overlap-prism.ts +503 -0
  863. package/src/layout/neato/overlap.ts +158 -0
  864. package/src/layout/neato/sc-adjust.ts +206 -0
  865. package/src/layout/neato/sep-factor.ts +118 -0
  866. package/src/layout/neato/set-aspect.ts +178 -0
  867. package/src/layout/neato/sgd.ts +461 -0
  868. package/src/layout/neato/splines.ts +1014 -0
  869. package/src/layout/neato/stress-kernel.ts +362 -0
  870. package/src/layout/neato/stress.ts +420 -0
  871. package/src/layout/osage/index.ts +548 -0
  872. package/src/layout/osage/layout.ts +254 -0
  873. package/src/layout/osage/types.ts +31 -0
  874. package/src/layout/pack/array-pack.ts +254 -0
  875. package/src/layout/pack/index.ts +570 -0
  876. package/src/layout/pack/poly-pack.ts +266 -0
  877. package/src/layout/pack/poly-place.ts +426 -0
  878. package/src/layout/pack/spiral-search.ts +106 -0
  879. package/src/layout/pack/test-helpers.ts +46 -0
  880. package/src/layout/pack/types.ts +85 -0
  881. package/src/layout/patchwork/index.ts +441 -0
  882. package/src/layout/patchwork/tree-map.ts +183 -0
  883. package/src/layout/patchwork/tree-node.ts +120 -0
  884. package/src/layout/sfdp/index.ts +231 -0
  885. package/src/layout/sfdp/init.ts +224 -0
  886. package/src/layout/sfdp/multilevel.ts +262 -0
  887. package/src/layout/sfdp/quadtree.ts +267 -0
  888. package/src/layout/sfdp/sparse-matrix-multiply.ts +160 -0
  889. package/src/layout/sfdp/sparse-matrix.ts +367 -0
  890. package/src/layout/sfdp/sparse-solve.ts +122 -0
  891. package/src/layout/sfdp/spring-driver.ts +209 -0
  892. package/src/layout/sfdp/spring-electrical.ts +431 -0
  893. package/src/layout/twopi/circle.ts +339 -0
  894. package/src/layout/twopi/index.ts +78 -0
  895. package/src/layout/twopi/init.ts +174 -0
  896. package/src/layout/twopi/pipeline.ts +98 -0
  897. package/src/layout/twopi/twopi-test-helpers.ts +44 -0
  898. package/src/model/algData.ts +194 -0
  899. package/src/model/cgraph-ops.ts +175 -0
  900. package/src/model/edge.ts +104 -0
  901. package/src/model/edgeInfo.ts +431 -0
  902. package/src/model/geom.ts +204 -0
  903. package/src/model/graph.ts +172 -0
  904. package/src/model/graphInfo.ts +528 -0
  905. package/src/model/index.ts +10 -0
  906. package/src/model/layoutParams.ts +127 -0
  907. package/src/model/node.ts +138 -0
  908. package/src/model/nodeInfo.ts +424 -0
  909. package/src/model/rankEntry.ts +95 -0
  910. package/src/ortho/chan-dict.ts +236 -0
  911. package/src/ortho/fpq.ts +149 -0
  912. package/src/ortho/index.ts +206 -0
  913. package/src/ortho/maze-channels.ts +196 -0
  914. package/src/ortho/maze.ts +311 -0
  915. package/src/ortho/ortho-parallel.ts +254 -0
  916. package/src/ortho/ortho-route.ts +349 -0
  917. package/src/ortho/partition.ts +303 -0
  918. package/src/ortho/rawgraph.ts +119 -0
  919. package/src/ortho/sgraph.ts +283 -0
  920. package/src/ortho/trap-query.ts +174 -0
  921. package/src/ortho/trap-segment.ts +303 -0
  922. package/src/ortho/trap-types.ts +171 -0
  923. package/src/ortho/trap-update.ts +89 -0
  924. package/src/ortho/trapezoid.ts +120 -0
  925. package/src/ortho/types.ts +286 -0
  926. package/src/parser/__fixtures__/directed.dot +10 -0
  927. package/src/parser/__fixtures__/simple.dot +7 -0
  928. package/src/parser/__fixtures__/subgraph.dot +17 -0
  929. package/src/parser/ast.ts +64 -0
  930. package/src/parser/builder.ts +401 -0
  931. package/src/parser/dot.d.ts +208 -0
  932. package/src/parser/dot.js +3321 -0
  933. package/src/parser/dot.pegjs +263 -0
  934. package/src/parser/index.ts +353 -0
  935. package/src/pathplan/index.ts +9 -0
  936. package/src/pathplan/route.ts +284 -0
  937. package/src/pathplan/shortest.ts +268 -0
  938. package/src/pathplan/solvers.ts +65 -0
  939. package/src/pathplan/triang.ts +221 -0
  940. package/src/pathplan/types.ts +16 -0
  941. package/src/pathplan/visibility.ts +170 -0
  942. package/src/pathplan/vispath.ts +118 -0
  943. package/src/rbtree/index.ts +418 -0
  944. package/src/render/color-resolve.ts +171 -0
  945. package/src/render/dot.ts +1514 -0
  946. package/src/render/index.ts +29 -0
  947. package/src/render/json.ts +734 -0
  948. package/src/render/map-test-helpers.ts +61 -0
  949. package/src/render/map.ts +528 -0
  950. package/src/render/public.ts +131 -0
  951. package/src/render/svg-arrow-ops.ts +113 -0
  952. package/src/render/svg-cluster.ts +24 -0
  953. package/src/render/svg-edge-ortho-radius.ts +228 -0
  954. package/src/render/svg-edge-split.ts +180 -0
  955. package/src/render/svg-gradient.ts +185 -0
  956. package/src/render/svg-graph.ts +415 -0
  957. package/src/render/svg-helpers.ts +623 -0
  958. package/src/render/svg-id.ts +76 -0
  959. package/src/render/svg-multicolor.ts +313 -0
  960. package/src/render/svg-parallel-edge.ts +123 -0
  961. package/src/render/svg-tapered-edge.ts +56 -0
  962. package/src/render/svg.ts +307 -0
  963. package/src/render/xdot-public.ts +164 -0
  964. package/src/render/xml-escape.ts +56 -0
  965. package/src/util/agxbuf.ts +138 -0
  966. package/src/util/bsd-qsort.ts +251 -0
  967. package/src/util/index.ts +25 -0
  968. package/src/util/list.ts +122 -0
  969. package/src/util/math.ts +104 -0
  970. package/src/util/mt19937.ts +143 -0
  971. package/src/util/printf-round.ts +163 -0
  972. package/src/util/xml.ts +184 -0
  973. package/src/vpsc/Block.ts +375 -0
  974. package/src/vpsc/Blocks.ts +164 -0
  975. package/src/vpsc/Constraint.ts +94 -0
  976. package/src/vpsc/HeapUtils.ts +88 -0
  977. package/src/vpsc/Solver.ts +274 -0
  978. package/src/vpsc/SweepLine.ts +214 -0
  979. package/src/vpsc/Variable.ts +54 -0
  980. package/src/vpsc/index.ts +122 -0
  981. package/src/vpsc/sweepTypes.ts +31 -0
  982. package/src/xdot/index.ts +7 -0
  983. package/src/xdot/lex.ts +151 -0
  984. package/src/xdot/misc.ts +226 -0
  985. package/src/xdot/parse.ts +349 -0
  986. package/src/xdot/types.ts +158 -0
@@ -0,0 +1,1514 @@
1
+ // SPDX-License-Identifier: EPL-2.0
2
+
3
+ /**
4
+ * DOT and XDOT renderer plugins.
5
+ *
6
+ * Ports gvrender_core_dot.c — FORMAT_DOT and FORMAT_XDOT branches.
7
+ * FORMAT_CANON, FORMAT_XDOT12, FORMAT_XDOT14 are out of scope (AD-12).
8
+ *
9
+ * @see plugin/core/gvrender_core_dot.c
10
+ * @see plugin/core/gvplugin_core.c
11
+ */
12
+
13
+ import type { Graph } from '../model/graph.js';
14
+ import type { Node } from '../model/node.js';
15
+ import type { Edge } from '../model/edge.js';
16
+ import type { Point } from '../model/geom.js';
17
+ import type { TextSpan } from '../common/emit-types.js';
18
+ import type { ArrowDrawOp } from '../common/arrows-types.js';
19
+ import type { GVColor } from '../common/color.js';
20
+ import { colorxlate } from '../common/color.js';
21
+ import { resolveRenderColor } from './color-resolve.js';
22
+ import { getGradientPoints } from './svg-gradient.js';
23
+ import { edgeIsTapered } from './svg-tapered-edge.js';
24
+ import { taper, taperfun } from '../common/taper.js';
25
+ import { orthoRoundedRadius } from './svg-helpers.js';
26
+ import { orthoRoundedPolylines } from './svg-edge-ortho-radius.js';
27
+ import { findStopColor, parseStyleFlags } from '../common/style-resolve.js';
28
+ import { parseGraphPad } from '../gvc/viewport.js';
29
+ import { parseSegs } from '../common/multicolor.js';
30
+ import { splitSplineByColor } from './svg-edge-split.js';
31
+ import { buildOffsetLists, advanceTmpList } from '../common/edge-offset.js';
32
+ import { IGNORED } from '../layout/dot/rank.js';
33
+ import { edgeHasDrawableContent } from './svg.js';
34
+ import type { Bezier } from '../model/geom.js';
35
+ import type { RendererPlugin } from '../gvc/context.js';
36
+ import { PenType, FillType } from '../gvc/context.js';
37
+ import type { RenderJob, ObjState } from '../gvc/job.js';
38
+ import { EmitState, toFixed2HalfEven } from '../gvc/job.js';
39
+ import { renderEdgeLabels } from '../gvc/edge-labels.js';
40
+ import { printfSig, printfFixed } from '../util/printf-round.js';
41
+ import { POINTS_PER_INCH } from '../model/geom.js';
42
+ import type { TextlabelT, FieldT, ShapeDesc } from '../common/types.js';
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Constants
46
+ // ---------------------------------------------------------------------------
47
+
48
+ const XDOT_VERSION = '1.7';
49
+
50
+ /** Max magnitude cap — matches maxnegnum in C source. */
51
+ const MAX_NEGNUM = 999999999999999.99;
52
+
53
+ /** Near-zero suppression threshold (same as gvprintdouble). */
54
+ const NEAR_ZERO = 0.005;
55
+
56
+ /** Indices 8/9 alias 1; 10/11 alias 5. NUM_XBUFS covers 0–7. */
57
+ const NUM_XBUFS = 8;
58
+
59
+ /**
60
+ * Style tokens that never become an xdot `S` op: `filled`/`bold`/`setlinewidth`
61
+ * are filtered by xdot_style itself; the polygon/fill styles are consumed by the
62
+ * shape/fill code before gvrender_set_style, so they never reach the render
63
+ * style. Only line styles (solid/dashed/dotted + unknown) emit an `S` op.
64
+ * @see plugin/core/gvrender_core_dot.c:184 xdot_style · lib/common/shapes.c
65
+ */
66
+ const NON_LINE_STYLES: ReadonlySet<string> = new Set([
67
+ 'filled', 'bold', 'rounded', 'diagonals', 'striped', 'wedged',
68
+ 'invis', 'invisible', 'radial',
69
+ ]);
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // printNum — @see lib/gvc/gvdevice.c:gvprintnum
73
+ // ---------------------------------------------------------------------------
74
+
75
+ /**
76
+ * Strip trailing zeros (and the decimal point) from a toFixed(3) string,
77
+ * then collapse a leading "0." or "-0." prefix.
78
+ */
79
+ function trimAndStrip(s: string): string {
80
+ const dot = s.indexOf('.');
81
+ if (dot < 0) return s;
82
+ let end = s.length;
83
+ while (end > dot + 1 && s[end - 1] === '0') end--;
84
+ if (s[end - 1] === '.') end--;
85
+ const t = s.slice(0, end);
86
+ if (t.startsWith('0.')) return t.slice(1);
87
+ if (t.startsWith('-0.')) return '-' + t.slice(2);
88
+ return t;
89
+ }
90
+
91
+ /**
92
+ * Convert a double to compact string form for DOT/XDOT output.
93
+ *
94
+ * Rules (porting gvprintnum from lib/gvc/gvdevice.c):
95
+ * - |n| > MAX_NEGNUM → clamped to ±MAX_NEGNUM
96
+ * - |n| < NEAR_ZERO → "0" (suppresses -0)
97
+ * - 3 decimal places, trailing zeros and point stripped
98
+ * - Leading "0." collapsed to "." (e.g. 0.5 → ".5")
99
+ *
100
+ * Exported for json.ts (T30) and map.ts (T31).
101
+ *
102
+ * @see lib/gvc/gvdevice.c:gvprintnum
103
+ */
104
+ export function printNum(n: number): string {
105
+ if (Math.abs(n) > MAX_NEGNUM) {
106
+ return n < 0 ? String(-MAX_NEGNUM) : String(MAX_NEGNUM);
107
+ }
108
+ if (Math.abs(n) < NEAR_ZERO) return '0';
109
+ return trimAndStrip(n.toFixed(3));
110
+ }
111
+
112
+ // ---------------------------------------------------------------------------
113
+ // XDot buffer management
114
+ // ---------------------------------------------------------------------------
115
+
116
+ /**
117
+ * Create a 12-slot xbufs array with the canonical aliasing.
118
+ *
119
+ * Indices 8–9 (NDraw, EDraw) alias index 1 (CDraw).
120
+ * Indices 10–11 (NLabel, ELabel) alias index 5 (CLabel).
121
+ *
122
+ * @see plugin/core/gvrender_core_dot.c:xbufs
123
+ */
124
+ export function makeXbufs(): string[][] {
125
+ const bufs: string[][] = Array.from({ length: NUM_XBUFS + 4 }, () => []);
126
+ bufs[EmitState.NDraw] = bufs[EmitState.CDraw]!;
127
+ bufs[EmitState.EDraw] = bufs[EmitState.CDraw]!;
128
+ bufs[EmitState.NLabel] = bufs[EmitState.CLabel]!;
129
+ bufs[EmitState.ELabel] = bufs[EmitState.CLabel]!;
130
+ return bufs;
131
+ }
132
+
133
+ // ---------------------------------------------------------------------------
134
+ // XDOT op helpers — @see plugin/core/gvrender_core_dot.c
135
+ // ---------------------------------------------------------------------------
136
+
137
+ /**
138
+ * Format one xdot draw-op number: 2 decimals, trailing zeros and point trimmed
139
+ * — mirroring xdot_fmt_num ("%.02f" + agxbuf_trim_zeros). Distinct from
140
+ * `printNum` (used for the DOT `pos`/`bb`/`width`/`height` attributes), which
141
+ * keeps more precision; xdot's DRAW ops are emitted at 2 dp by the C engine.
142
+ * @see plugin/core/gvrender_core_dot.c:126 xdot_fmt_num
143
+ */
144
+ export function xdotNum(v: number): string {
145
+ // Round half-to-even like C's printf %.02f (FE_TONEAREST); JS toFixed rounds
146
+ // half-away-from-zero, which diverges at exact .xx5 ties (2323.125 → native
147
+ // 2323.12, not 2323.13). @see lib/gvc/gvdevice.c gvprintdouble
148
+ let s = toFixed2HalfEven(v);
149
+ if (s.indexOf('.') >= 0) {
150
+ let end = s.length;
151
+ while (end > 0 && s[end - 1] === '0') end--;
152
+ if (s[end - 1] === '.') end--;
153
+ s = s.slice(0, end);
154
+ }
155
+ return s === '-0' ? '0' : s;
156
+ }
157
+
158
+ /**
159
+ * Format a single xdot point "x y ". xdot is y-up: `Y_invert` defaults false, so
160
+ * `yDir(y, yOff)` returns `y` unchanged for xdot (only `-Ty` plain/dot invert).
161
+ * The layout coordinate passes through with NO inversion — unlike the SVG path.
162
+ * @see lib/common/output.c:36 yDir · plugin/core/gvrender_core_dot.c:132 xdot_point
163
+ */
164
+ export function xdotPoint(p: Point): string {
165
+ return xdotNum(p.x) + ' ' + xdotNum(p.y) + ' ';
166
+ }
167
+
168
+ /** Format N points preceded by opcode and count: "<c> <n> x0 y0 x1 y1 …". */
169
+ export function xdotPoints(c: string, pts: Point[]): string {
170
+ let s = c + ' ' + String(pts.length) + ' ';
171
+ for (const p of pts) s += xdotPoint(p);
172
+ return s;
173
+ }
174
+
175
+ /**
176
+ * UTF-8 byte length of a string — the value C's `xdot_str` writes as the length
177
+ * prefix (`strlen(s)` over the UTF-8 bytes), NOT the JS UTF-16 code-unit count.
178
+ * A label like `ÿ` (U+00FF) is 2 UTF-8 bytes, so its `T`/`F` op prefix is 2.
179
+ * @see plugin/core/gvrender_core_dot.c:83 xdot_str_xbuf (`%zu`, strlen)
180
+ */
181
+ export function utf8Len(s: string): number {
182
+ let n = 0;
183
+ for (let i = 0; i < s.length; i++) {
184
+ const c = s.charCodeAt(i);
185
+ if (c < 0x80) n += 1;
186
+ else if (c < 0x800) n += 2;
187
+ else if (c >= 0xd800 && c <= 0xdbff) { n += 4; i++; } // surrogate pair → 4 bytes
188
+ else n += 3;
189
+ }
190
+ return n;
191
+ }
192
+
193
+ /** Clamp a normalized [0,1] float channel to a 0-255 byte (round-to-nearest). */
194
+ function chanByte(v: number): number {
195
+ return Math.round(Math.max(0, Math.min(1, v)) * 255);
196
+ }
197
+
198
+ /**
199
+ * Resolve a GVColor to RGBA bytes — the value the C xdot callbacks read from
200
+ * `job->obj->pencolor.u.rgba` (already resolved by gvrender_set_pencolor). A
201
+ * plain `rgba` passes through; named/hex/HSV specs run through colorxlate; a
202
+ * `none` color is fully transparent black (callers gate on PEN_NONE).
203
+ */
204
+ export function gvColorRgba(c: GVColor): [number, number, number, number] {
205
+ if (c.type === 'rgba') return [chanByte(c.r), chanByte(c.g), chanByte(c.b), chanByte(c.a)];
206
+ // A `none`/transparent paint emits graphviz's "transparent" bytes (ff ff fe 00),
207
+ // matching native's `#fffffe00` — not fully-zero black. @see colxlate.c transparent
208
+ if (c.type === 'none') return [0xff, 0xff, 0xfe, 0x00];
209
+ const out: GVColor = { type: 'rgba', r: 0, g: 0, b: 0, a: 0 };
210
+ colorxlate(c.type === 'string' ? c.s : '', out, 'rgba');
211
+ return out.type === 'rgba'
212
+ ? [chanByte(out.r), chanByte(out.g), chanByte(out.b), chanByte(out.a)]
213
+ : [0, 0, 0, 255];
214
+ }
215
+
216
+ /**
217
+ * Bare xdot color body (no `c `/`C ` prefix): the CONSTANT length prefix
218
+ * (7 for `#rrggbb`, 9 for `#rrggbbaa`) plus `-#hex`, the alpha byte present only
219
+ * when not fully opaque. Used both by the color ops and by gradient color stops.
220
+ * @see plugin/core/gvrender_core_dot.c:99 xdot_str_color_xbuf
221
+ */
222
+ export function xdotColorBody(rgba: [number, number, number, number]): string {
223
+ const hx = (n: number): string => n.toString(16).padStart(2, '0');
224
+ const body = '#' + hx(rgba[0]) + hx(rgba[1]) + hx(rgba[2]);
225
+ return rgba[3] === 0xff ? '7 -' + body : '9 -' + body + hx(rgba[3]);
226
+ }
227
+
228
+ /**
229
+ * Format an xdot color op ("c "/"C ") from RGBA bytes.
230
+ * @see plugin/core/gvrender_core_dot.c:99 xdot_str_color_xbuf
231
+ */
232
+ export function xdotColorOp(prefix: 'c ' | 'C ', rgba: [number, number, number, number]): string {
233
+ return prefix + xdotColorBody(rgba) + ' ';
234
+ }
235
+
236
+ /**
237
+ * Build the linear-gradient `C len -[x0 y0 x1 y1 2 <stops>]` fill op. Endpoints
238
+ * come from getGradientPoints (the same geometry the SVG gradient uses); stops
239
+ * are (frac,fill)/(frac,stop) when frac>0, else (0,fill)/(1,stop). Shared by
240
+ * node/cluster gradient fills and the graph-background gradient.
241
+ * @see plugin/core/gvrender_core_dot.c:544-598 xdot_gradient_fillcolor
242
+ */
243
+ export function linearGradientOp(
244
+ pts: Point[],
245
+ fillColor: GVColor,
246
+ stopColor: GVColor,
247
+ frac: number,
248
+ angleDeg: number,
249
+ ): string {
250
+ // isRHS=true: native y-up coords, matching C's get_gradient_points(A,G,n,angle,2)
251
+ // for the xdot device path (the SVG path uses isRHS=false + a container flip).
252
+ const { g0, g1 } = getGradientPoints(pts, (angleDeg * Math.PI) / 180, false, true);
253
+ const inner =
254
+ '[' + xdotNum(g0.x) + ' ' + xdotNum(g0.y) + ' ' + xdotNum(g1.x) + ' ' + xdotNum(g1.y) +
255
+ ' 2 ' + gradientStops(fillColor, stopColor, frac) + ']';
256
+ return 'C ' + String(utf8Len(inner)) + ' -' + inner + ' ';
257
+ }
258
+
259
+ /** The `<frac> <colorbody>` stop pairs for a gradient (frac>0 vs the 0/1 form). */
260
+ function gradientStops(fillColor: GVColor, stopColor: GVColor, frac: number): string {
261
+ const fill = gvColorRgba(fillColor);
262
+ const stop = gvColorRgba(stopColor);
263
+ const stops: Array<[number, [number, number, number, number]]> =
264
+ frac > 0 ? [[frac, fill], [frac, stop]] : [[0, fill], [1, stop]];
265
+ return stops.map(([f, c]) => trimFixed3(f) + ' ' + xdotColorBody(c)).join(' ');
266
+ }
267
+
268
+ /**
269
+ * Build the radial-gradient `C len -(c1x c1y r1 c2x c2y r2 2 <stops>)` fill op.
270
+ * Reuses getGradientPoints (radial) for the center/radii, un-negating its SVG
271
+ * y. r1 = outerR/4, r2 = outerR; c2 is the center, c1 the center offset by r1
272
+ * along the gradient angle (== center when angle 0).
273
+ * @see plugin/core/gvrender_core_dot.c:562-585 xdot_gradient_fillcolor (radial)
274
+ */
275
+ export function radialGradientOp(
276
+ pts: Point[],
277
+ fillColor: GVColor,
278
+ stopColor: GVColor,
279
+ frac: number,
280
+ angleDeg: number,
281
+ ): string {
282
+ const rad = (angleDeg * Math.PI) / 180;
283
+ // isRHS=true: native y-up coords, matching C's get_gradient_points(A,G,n,0,3).
284
+ const gp = getGradientPoints(pts, rad, true, true);
285
+ const cx = gp.g0.x;
286
+ const cy = gp.g0.y;
287
+ const r1 = gp.g1.x;
288
+ const r2 = gp.g1.y;
289
+ const c1x = angleDeg === 0 ? cx : cx + r1 * Math.cos(rad);
290
+ const c1y = angleDeg === 0 ? cy : cy + r1 * Math.sin(rad);
291
+ const inner =
292
+ '(' + xdotNum(c1x) + ' ' + xdotNum(c1y) + ' ' + xdotNum(r1) + ' ' +
293
+ xdotNum(cx) + ' ' + xdotNum(cy) + ' ' + xdotNum(r2) + ' 2 ' +
294
+ gradientStops(fillColor, stopColor, frac) + ')';
295
+ return 'C ' + String(utf8Len(inner)) + ' -' + inner + ' ';
296
+ }
297
+
298
+ /** Pen ("c ") color op from a resolved GVColor. */
299
+ export function xdotPenColor(c: GVColor): string {
300
+ return xdotColorOp('c ', gvColorRgba(c));
301
+ }
302
+
303
+ /** Fill ("C ") color op from a resolved GVColor. */
304
+ export function xdotFillColor(c: GVColor): string {
305
+ return xdotColorOp('C ', gvColorRgba(c));
306
+ }
307
+
308
+ /**
309
+ * Build the xdot "F size len -name " font op. Mirrors xdot_textspan's `F` +
310
+ * `xdot_str(job, "", font->name)` — the length prefix is the byte length of the
311
+ * face name. @see plugin/core/gvrender_core_dot.c:498 xdot_textspan
312
+ */
313
+ export function xdotFont(size: number, name: string): string {
314
+ return 'F ' + xdotNum(size > 0 ? size : 0) + ' ' + String(utf8Len(name)) + ' -' + name + ' ';
315
+ }
316
+
317
+ /**
318
+ * Build an xdot length-prefixed string op ("S "/"" prefix): "<pfx><len> -<s> ".
319
+ * @see plugin/core/gvrender_core_dot.c:83 xdot_str_xbuf
320
+ */
321
+ function xdotStrOp(prefix: string, s: string): string {
322
+ return prefix + String(utf8Len(s)) + ' -' + s + ' ';
323
+ }
324
+
325
+ /**
326
+ * Quote a DOT identifier unless it is a bare id or numeral, mirroring agwrite's
327
+ * agcanonStr so the serialized graph reparses (the comparator reparses both
328
+ * sides). Only `"` is escaped (→ `\"`); a `\` is left as-is — it is already the
329
+ * start of a stored escape like `\n`/`\l` that agcanonStr keeps verbatim, so
330
+ * doubling it (`\\n`) would change the name (`a\n(b\n"c")` must stay `a\n…`, not
331
+ * `a\\n…`). Over-quoting a value native leaves bare is harmless: both parse to
332
+ * the same name. @see lib/cgraph/write.c:_agstrcanon (escapes '"', keeps '\')
333
+ */
334
+ export function xdotId(s: string): string {
335
+ if (/^[A-Za-z_][A-Za-z_0-9]*$/.test(s)) return s;
336
+ if (/^-?(\.[0-9]+|[0-9]+(\.[0-9]*)?)$/.test(s)) return s;
337
+ return '"' + s.replace(/"/g, '\\"') + '"';
338
+ }
339
+
340
+ /**
341
+ * Format a number as C's `%.5g` — 5 significant figures, trailing zeros and
342
+ * point trimmed, switching to `e±NN` (min 2 exponent digits) when the exponent
343
+ * is < -4 or ≥ 5. Native writes the DOT `pos`/`bb`/`width`/`height` attributes
344
+ * with `%.5g` (output.c:71/294/302), so large coordinates round to 5 sig figs
345
+ * (`2219962` → `2.2201e+06`); `printNum`'s fixed 3 dp keeps too much precision.
346
+ *
347
+ * Uses {@link printfSig} rather than `Number.prototype.toPrecision(5)` so
348
+ * exact-halfway values (e.g. 1399.25) round half-to-even like C's snprintf,
349
+ * not half-away-from-zero like `toPrecision`. @see docs proven case
350
+ * graphs-b786 (circo): pos coordinate 1399.25 → native "1399.2", `toPrecision`
351
+ * gave "1399.3".
352
+ * @see lib/common/output.c:71 (agxbprint "%.5g")
353
+ */
354
+ export function gfmt5(v: number): string {
355
+ if (!Number.isFinite(v)) return String(v);
356
+ if (v === 0) return '0';
357
+ let s = printfSig(v, 5);
358
+ const e = s.indexOf('e');
359
+ if (e >= 0) {
360
+ let mant = s.slice(0, e);
361
+ if (mant.indexOf('.') >= 0) mant = mant.replace(/0+$/, '').replace(/\.$/, '');
362
+ const ei = parseInt(s.slice(e + 1), 10);
363
+ return mant + 'e' + (ei < 0 ? '-' : '+') + String(Math.abs(ei)).padStart(2, '0');
364
+ }
365
+ if (s.indexOf('.') >= 0) s = s.replace(/0+$/, '').replace(/\.$/, '');
366
+ return s;
367
+ }
368
+
369
+ /**
370
+ * Format a number as C's `%.2f` — fixed 2 decimals, half-to-even on exact ties.
371
+ * `attach_attrs` writes the graph-label size attributes `lwidth`/`lheight` in
372
+ * INCHES with `%.2f`, unlike every other computed attribute (which uses `%.5g`).
373
+ * @see lib/common/output.c:244-247 (agxbprint "%.2f", PS2INCH)
374
+ */
375
+ function gfmt2(v: number): string {
376
+ return printfFixed(v, 2);
377
+ }
378
+
379
+ /**
380
+ * Format a label position as the `x,y` pair every computed `*_lp` attribute
381
+ * uses: both coordinates at `%.5g`. C passes y through `yDir()`, which is the
382
+ * identity unless `Y_invert` — and `Y_invert` is only set by `-Ty` (plain/dot),
383
+ * never for xdot — so no inversion is applied here, exactly as the existing
384
+ * `pos`/`bb` emission does.
385
+ * @see lib/common/output.c:35 yDir · lib/common/output.c:241 (agxbprint "%.5g,%.5g")
386
+ */
387
+ function lpStr(p: Point): string {
388
+ return gfmt5(p.x) + ',' + gfmt5(p.y);
389
+ }
390
+
391
+ /**
392
+ * C's `attach_attrs` edge loop skips IGNORED edges and edges with no spline
393
+ * (`ED_spl(e) == NULL`) via `continue`, so such an edge is attached NEITHER
394
+ * `pos` NOR any of `lp`/`xlp`/`head_lp`/`tail_lp` — even when it carries a
395
+ * label. All five attributes therefore share this one gate.
396
+ * @see lib/common/output.c:349-353
397
+ */
398
+ function edgeAttrsAttached(e: Edge): boolean {
399
+ return e.info.edge_type !== IGNORED && e.info.spl != null;
400
+ }
401
+
402
+ /**
403
+ * Append the leaf-field rectangles of a record node, mirroring the recursion in
404
+ * `set_record_rects`: a field with sub-fields contributes nothing itself, while
405
+ * a LEAF field (`n_flds == 0`) contributes its box translated by the node centre
406
+ * as `llx,lly,urx,ury` at `%.5g`. C emits each field followed by a space and
407
+ * then pops the trailing one — joining with a single space is equivalent.
408
+ * @see lib/common/output.c:215 set_record_rects
409
+ */
410
+ function appendRecordRects(n: Node, f: FieldT, out: string[]): void {
411
+ const c = n.info.coord;
412
+ if (f.n_flds === 0) {
413
+ out.push(
414
+ gfmt5(f.b.ll.x + c.x) + ',' + gfmt5(f.b.ll.y + c.y) + ',' +
415
+ gfmt5(f.b.ur.x + c.x) + ',' + gfmt5(f.b.ur.y + c.y),
416
+ );
417
+ }
418
+ for (let i = 0; i < f.n_flds; i++) appendRecordRects(n, f.fld![i], out);
419
+ }
420
+
421
+ /**
422
+ * Echo an attribute that `attach_attrs_and_arrows` did NOT overwrite.
423
+ *
424
+ * Every computed attribute is `agset` behind a gate (`rects` only for a
425
+ * `record` shape; an edge's `lp` only for a routed edge that HAS a label;
426
+ * a graph's `lp` only when `GD_label(g)` exists...). When the gate FAILS, C
427
+ * simply does not write — so the slot keeps whatever the INPUT file parsed
428
+ * into it, and `agwrite` (which serializes the whole attribute table, not
429
+ * just the fields layout computed) prints that stale value verbatim. This is
430
+ * highly visible under patchwork, which forces every shape to `box` and routes
431
+ * no edges: on a re-fed dot output (most of the corpus) native echoes back the
432
+ * `rects` / `lp` a *previous* dot run wrote, in that run's coordinate space.
433
+ *
434
+ * cgraph interns attribute strings, so a value equal to the declared (empty)
435
+ * default is the SAME pointer as the default and write.c's
436
+ * `data->str[sym->id] != sym->defval` test drops it — hence an empty value is
437
+ * never printed.
438
+ * @see lib/cgraph/write.c:427 write_nondefault_attrs · lib/common/output.c:270
439
+ */
440
+ function echoAttr(attrs: Map<string, string>, key: string): string[] {
441
+ const v = attrs.get(key);
442
+ if (v === undefined || v.length === 0) return [];
443
+ return [key + '="' + agcanonEscape(v) + '"'];
444
+ }
445
+
446
+ /** `echoAttr`'s result as a space-prefixed suffix for the string-built node block. */
447
+ function pfx(parts: string[]): string {
448
+ return parts.length === 0 ? '' : ' ' + parts.join(' ');
449
+ }
450
+
451
+ /**
452
+ * The `lp`/`lwidth`/`lheight` triple for a graph or cluster, in C's order.
453
+ * `rec_attach_bb` attaches them to the root graph AND recursively to every
454
+ * cluster, but ONLY when the graph carries a label with non-empty text
455
+ * (`GD_label(g) && GD_label(g)->text[0]`) — an absent or empty-text label emits
456
+ * none of the three. `lp` is the label centre in points (`%.5g`); `lwidth` and
457
+ * `lheight` are the label's `dimen` converted to inches and written `%.2f`.
458
+ * When the gate fails, the input's own values survive and are echoed: patchwork
459
+ * and osage never build a cluster label object, so a re-fed dot file's cluster
460
+ * `lp` comes straight back out.
461
+ * @see lib/common/output.c:239-248 rec_attach_bb
462
+ */
463
+ function graphLabelAttrs(g: Graph): string[] {
464
+ const label = g.info.label as TextlabelT | undefined;
465
+ if (!label || label.text.length === 0) {
466
+ return [
467
+ ...echoAttr(g.attrs, 'lp'),
468
+ ...echoAttr(g.attrs, 'lwidth'),
469
+ ...echoAttr(g.attrs, 'lheight'),
470
+ ];
471
+ }
472
+ return [
473
+ 'lp="' + lpStr(label.pos) + '"',
474
+ 'lwidth=' + gfmt2(label.dimen.x / POINTS_PER_INCH),
475
+ 'lheight=' + gfmt2(label.dimen.y / POINTS_PER_INCH),
476
+ ];
477
+ }
478
+
479
+ // ---------------------------------------------------------------------------
480
+ // DOT attribute helpers
481
+ // ---------------------------------------------------------------------------
482
+
483
+ /** Format node attributes for DOT output. */
484
+ export function formatNodeAttrs(n: Node): string {
485
+ const info = n.info;
486
+ const x = printNum(info.coord.x);
487
+ const y = printNum(info.coord.y);
488
+ const w = printNum(info.width);
489
+ const h = printNum(info.height);
490
+ const parts: string[] = [`pos="${x},${y}"`, `width=${w}`, `height=${h}`];
491
+ n.attrs.forEach((v, k) => {
492
+ if (k !== 'pos' && k !== 'width' && k !== 'height') {
493
+ parts.push(`${k}="${v}"`);
494
+ }
495
+ });
496
+ return parts.join(' ');
497
+ }
498
+
499
+ /**
500
+ * Format edge spline points for the DOT `pos` attribute. Per bezier: the start
501
+ * endpoint `s,sp` when `sflag` set, then the end endpoint `e,ep` when `eflag`
502
+ * set, then `bez.size` control points — all at `%.5g`, exactly as native's
503
+ * spline serialization. @see lib/common/output.c:357-372
504
+ */
505
+ export function formatEdgePos(e: Edge): string {
506
+ const spl = e.info.spl;
507
+ if (!spl || spl.list.length === 0) return '';
508
+ // Native's pos loop skips IGNORED edges (output.c:350) — concentrate merges
509
+ // an edge into its opposite and marks the absorbed one IGNORED; it is still
510
+ // drawn (has _draw_) but carries no `pos`. @see lib/common/output.c:349-353
511
+ if (e.info.edge_type === IGNORED) return '';
512
+ const parts: string[] = [];
513
+ for (const bez of spl.list) {
514
+ if (bez.sflag) parts.push('s,' + gfmt5(bez.sp.x) + ',' + gfmt5(bez.sp.y));
515
+ if (bez.eflag) parts.push('e,' + gfmt5(bez.ep.x) + ',' + gfmt5(bez.ep.y));
516
+ const pts = bez.list.slice(0, bez.size);
517
+ for (const p of pts) parts.push(gfmt5(p.x) + ',' + gfmt5(p.y));
518
+ }
519
+ return 'pos="' + parts.join(' ') + '"';
520
+ }
521
+
522
+ /** Return the edge connector token. */
523
+ export function edgeConnector(directed: boolean): string {
524
+ return directed ? '->' : '--';
525
+ }
526
+
527
+ /** Return true if the graph is directed or strict-directed. */
528
+ export function isDirected(g: Graph): boolean {
529
+ return g.kind === 'directed' || g.kind === 'strict-directed';
530
+ }
531
+
532
+ // ---------------------------------------------------------------------------
533
+ // Shared graph header helper
534
+ // ---------------------------------------------------------------------------
535
+
536
+ /** Emit the opening digraph/graph line and bb attribute. */
537
+ export function dotBeginGraphHeader(g: Graph, job: RenderJob): void {
538
+ const kw = isDirected(g) ? 'digraph' : 'graph';
539
+ job.write(kw + ' ' + g.name + ' {\n');
540
+ const bb = g.info.bb;
541
+ const bbStr = printNum(bb.ll.x) + ',' + printNum(bb.ll.y) + ','
542
+ + printNum(bb.ur.x) + ',' + printNum(bb.ur.y);
543
+ job.write('\tgraph [bb="' + bbStr + '"];\n');
544
+ }
545
+
546
+ // ---------------------------------------------------------------------------
547
+ // DotRenderer
548
+ // ---------------------------------------------------------------------------
549
+
550
+ /**
551
+ * DOT format renderer — writes graphviz DOT language with layout attributes.
552
+ *
553
+ * @see plugin/core/gvrender_core_dot.c:dot_engine
554
+ */
555
+ export class DotRenderer implements RendererPlugin {
556
+ readonly type = 'dot';
557
+ /** @see plugin/core/gvplugin_core.c registration table */
558
+ readonly quality = 0;
559
+
560
+ beginGraph(g: Graph, job: RenderJob): void {
561
+ dotBeginGraphHeader(g, job);
562
+ }
563
+
564
+ endGraph(_g: Graph, job: RenderJob): void {
565
+ job.write('}\n');
566
+ }
567
+
568
+ beginNode(_n: Node, _job: RenderJob): void { /* no-op */ }
569
+
570
+ endNode(n: Node, job: RenderJob): void {
571
+ const attrs = formatNodeAttrs(n);
572
+ job.write('\t' + n.name + ' [' + attrs + '];\n');
573
+ }
574
+
575
+ beginEdge(_e: Edge, _job: RenderJob): void { /* no-op */ }
576
+
577
+ endEdge(e: Edge, job: RenderJob): void {
578
+ const directed = isDirected(e.tail.root);
579
+ const conn = edgeConnector(directed);
580
+ const posAttr = formatEdgePos(e);
581
+ const attrList = posAttr ? ' [' + posAttr + ']' : '';
582
+ job.write('\t' + e.tail.name + ' ' + conn + ' ' + e.head.name + attrList + ';\n');
583
+ }
584
+
585
+ textspan(_pos: Point, _span: TextSpan, _job: RenderJob): void { /* no-op */ }
586
+ ellipse(_c: Point, _rx: number, _ry: number, _f: boolean, _j: RenderJob): void { /* no-op */ }
587
+ polygon(_pts: Point[], _filled: boolean, _job: RenderJob): void { /* no-op */ }
588
+ bezier(_pts: Point[], _filled: boolean, _job: RenderJob): void { /* no-op */ }
589
+ polyline(_pts: Point[], _job: RenderJob): void { /* no-op */ }
590
+ }
591
+
592
+ // ---------------------------------------------------------------------------
593
+ // XdotRenderer
594
+ // ---------------------------------------------------------------------------
595
+
596
+ /** Accumulated xdot draw strings for one model object (agset side-table). */
597
+ export interface XdotDraws {
598
+ draw?: string;
599
+ ldraw?: string;
600
+ hdraw?: string;
601
+ tdraw?: string;
602
+ hldraw?: string;
603
+ tldraw?: string;
604
+ }
605
+
606
+ /** Mutable state threaded through the recursive agwrite serializer. */
607
+ interface SerCtx {
608
+ out: string[];
609
+ /** subgraph → preorder number (write.c:subgdfs). */
610
+ preorder: Map<Graph, number>;
611
+ /** node → preorder of the subgraph it was last written in (node_last_written). */
612
+ nodeLW: Map<Node, number>;
613
+ /** edge → preorder of the subgraph it was last written in (edge_last_written). */
614
+ edgeLW: Map<Edge, number>;
615
+ /** objects whose attributes have already been emitted (AGATTRWF/attrs_written). */
616
+ attrsWritten: Set<Node | Edge>;
617
+ /** current indentation depth. */
618
+ level: number;
619
+ }
620
+
621
+ /**
622
+ * Escape backslashes in a LABEL draw string — C's put_escaping_backslashes,
623
+ * applied to the `_ldraw_`/`_hldraw_`/`_tldraw_` buffers (not the shape draws)
624
+ * before agset. A literal `\` in a label's text (e.g. `WXYZ\nabc`) becomes `\\`
625
+ * so it survives the DOT string round-trip; the T-op byte-length prefix stays on
626
+ * the UNescaped text. @see plugin/core/gvrender_core_dot.c:218 put_escaping_backslashes
627
+ */
628
+ function escBackslash(s: string): string {
629
+ return s.replace(/\\/g, '\\\\');
630
+ }
631
+
632
+ /** True if s[i..] starts an escape sequence agcanonStr keeps verbatim: a `\`
633
+ * followed by one of E G H L N T l n r \ ". @see lib/cgraph/write.c:is_escape */
634
+ function isEscapeSeq(s: string, i: number): boolean {
635
+ if (s[i] !== '\\') return false;
636
+ const n = s[i + 1];
637
+ return n === 'E' || n === 'G' || n === 'H' || n === 'L' || n === 'N' || n === 'T'
638
+ || n === 'l' || n === 'n' || n === 'r' || n === '\\' || n === '"';
639
+ }
640
+
641
+ /**
642
+ * Escape a value for a DOT attribute exactly as agwrite's agcanonStr does: a `"`
643
+ * becomes `\"` ONLY when it is not already part of an escape sequence, so an
644
+ * existing `\"` or `\\` in the value is passed through verbatim rather than
645
+ * double-escaped. For an already-backslash-doubled value (node/edge/graph labels,
646
+ * post put_escaping_backslashes) this is identical to a naive `"`→`\"` — every
647
+ * `"` follows a doubled `\\`, so none are ever part_of_escape. For raw cluster
648
+ * labels (agset, no put_escaping) it preserves the source `\"`/`\\` unchanged.
649
+ * @see lib/cgraph/write.c:_agstrcanon (135-167)
650
+ */
651
+ function agcanonEscape(s: string): string {
652
+ let out = '';
653
+ let partOfEscape = false;
654
+ for (let i = 0; i < s.length; i++) {
655
+ const c = s[i]!;
656
+ if (c === '"' && !partOfEscape) {
657
+ out += '\\';
658
+ } else if (!partOfEscape && isEscapeSeq(s, i)) {
659
+ partOfEscape = true;
660
+ } else {
661
+ partOfEscape = false;
662
+ }
663
+ out += c;
664
+ }
665
+ return out;
666
+ }
667
+
668
+ /** Trim a "%.3f" fixed string like C's agxbuf_trim_zeros (trailing 0s + dot). */
669
+ function trimFixed3(v: number): string {
670
+ let s = v.toFixed(3);
671
+ if (s.indexOf('.') < 0) return s;
672
+ let end = s.length;
673
+ while (end > 0 && s[end - 1] === '0') end--;
674
+ if (s[end - 1] === '.') end--;
675
+ return s.slice(0, end);
676
+ }
677
+
678
+ /**
679
+ * XDOT format renderer — mirrors plugin/core/gvrender_core_dot.c's xdot engine.
680
+ *
681
+ * Draw ops accumulate in per-emit-state xbufs during the shared emit pass; at
682
+ * the end of each object they are attached as `_draw_`/`_ldraw_`/`_hdraw_`/
683
+ * `_tdraw_` strings (a side-table here, `agset` in C), and the whole graph is
684
+ * serialized at `endGraph` — the model-attribute + agwrite-at-end model that
685
+ * lets the graph-level `_draw_` (canvas background, cluster boxes) precede `bb`
686
+ * even though it is only known after the body has been drawn.
687
+ *
688
+ * xdot is y-up (no coordinate inversion); colors come from the resolved
689
+ * graphics state (`job.obj.penColor`/`fillColor`), not hardcoded black.
690
+ *
691
+ * @see plugin/core/gvrender_core_dot.c xdot_begin_graph / xdot_end_graph
692
+ */
693
+ export class XdotRenderer implements RendererPlugin {
694
+ readonly type = 'xdot';
695
+ /** @see plugin/core/gvplugin_core.c registration table */
696
+ readonly quality = 0;
697
+
698
+ /** Per-render draw buffers. Indices 8/9 alias 1; 10/11 alias 5. */
699
+ private bufs: string[][] = makeXbufs();
700
+ /** setlinewidth state per emit_state. @see gvrender_core_dot.c penwidth[] */
701
+ private penwidth: number[] = new Array(12).fill(1);
702
+ /** text-flag state per emit_state. @see gvrender_core_dot.c textflags[] */
703
+ private textflags: number[] = new Array(12).fill(0);
704
+ /** Accumulated draw strings keyed by model object (C: agset on the object). */
705
+ private draws = new Map<Node | Edge | Graph, XdotDraws>();
706
+ /** Clusters in render order — the graphs `endCluster` was actually called for
707
+ * (from `renderClusters`/`GD_clust`), which is NOT always reachable via
708
+ * `g.subgraphs`, so it is tracked here rather than re-walked. */
709
+ private clusters: Graph[] = [];
710
+ /** edge_in_box gate for the current edge: emit_edge draws nothing (spline OR
711
+ * label) for an edge whose content is outside job->clip. Mirrors svg.ts's
712
+ * edgeGroupOpen. @see lib/common/emit.c:emit_edge (3039) */
713
+ private edgeDrawable = true;
714
+
715
+ beginGraph(g: Graph, _job: RenderJob): void {
716
+ this.bufs = makeXbufs();
717
+ this.penwidth = new Array(12).fill(1);
718
+ this.textflags = new Array(12).fill(0);
719
+ this.draws = new Map();
720
+ this.clusters = [];
721
+ }
722
+
723
+ /** The per-object draw strings accumulated during the last render, keyed by
724
+ * the ORIGINAL model object. These are the pre-serialization values C's
725
+ * `-Tjson` reads directly (via agxget) — feeding them to parseXDot avoids the
726
+ * DOT-text round-trip, which cannot represent draw text containing `"` and so
727
+ * drops a preceding backslash (a cluster label's `\"` → `"`, id 2239). */
728
+ drawStringsByObject(): ReadonlyMap<Node | Edge | Graph, XdotDraws> {
729
+ return this.draws;
730
+ }
731
+
732
+ endGraph(g: Graph, job: RenderJob): void {
733
+ // Flush the graph-level GDRAW/GLABEL buffers (canvas background + graph
734
+ // label) onto the root graph, then serialize the whole graph.
735
+ // @see gvrender_core_dot.c:427 xdot_end_graph
736
+ const gd = this.flush(EmitState.GDraw);
737
+ const gl = this.flush(EmitState.GLabel);
738
+ if (gd || gl) {
739
+ const set = this.drawsFor(g);
740
+ if (gd) set.draw = gd;
741
+ if (gl) set.ldraw = escBackslash(gl);
742
+ }
743
+ job.write(this.serialize(g));
744
+ }
745
+
746
+ /** Emit the canvas background polygon into the GDRAW buffer.
747
+ * @see lib/common/emit.c:1476 emit_background */
748
+ pageBackground(g: Graph, job: RenderJob): void {
749
+ const bg = g.attrs.get('bgcolor');
750
+ let fillSpec = bg !== undefined && bg !== '' ? bg : 'white';
751
+ // The xdot device is not GVDEVICE_DOES_TRUECOLOR, so emit_background maps an
752
+ // explicit `bgcolor=transparent` to white (a filled white canvas), unlike a
753
+ // truecolor device that paints nothing. @see lib/common/emit.c:1490
754
+ if (fillSpec === 'transparent') fillSpec = 'white';
755
+ // The canvas fill covers job->clip = gvc->bb ± job->pad. The dot/xdot device
756
+ // default pad is 0 (SVG's is 4), so only an explicit `pad` attr expands the
757
+ // background box (e.g. pad=2.0 → ±144). @see gvrender_core_dot.c:739
758
+ // (render_features_dot default_pad 0) · emit.c:3367 (job->bb = bb ± pad)
759
+ const padAttr = g.attrs.get('pad');
760
+ const pad = padAttr !== undefined && padAttr !== '' ? parseGraphPad(padAttr) : { x: 0, y: 0 };
761
+ const bb = job.bb;
762
+ const corners: Point[] = [
763
+ { x: bb.ll.x - pad.x, y: bb.ll.y - pad.y },
764
+ { x: bb.ll.x - pad.x, y: bb.ur.y + pad.y },
765
+ { x: bb.ur.x + pad.x, y: bb.ur.y + pad.y },
766
+ { x: bb.ur.x + pad.x, y: bb.ll.y - pad.y },
767
+ ];
768
+ const buf = this.bufs[EmitState.GDraw]!;
769
+ buf.push(xdotPenColor(resolveRenderColor('transparent')));
770
+ // A two-color bgcolor is a gradient (emit_background → findStopColor); a
771
+ // linear one emits the bracketed fill, a radial one is deferred to the base.
772
+ const stop = fillSpec.includes(':') ? findStopColor(fillSpec) : null;
773
+ if (stop !== null) {
774
+ const angle = Number(g.attrs.get('gradientangle') ?? 0) || 0;
775
+ const radial = parseStyleFlags(g.attrs.get('style')).radial;
776
+ const op = radial ? radialGradientOp : linearGradientOp;
777
+ buf.push(
778
+ op(corners, resolveRenderColor(stop.fillColor), resolveRenderColor(stop.stopColor), stop.frac, angle),
779
+ );
780
+ } else {
781
+ buf.push(xdotFillColor(resolveRenderColor(fillSpec)));
782
+ }
783
+ buf.push(xdotPoints('P', corners));
784
+ }
785
+
786
+ beginNode(_n: Node, _job: RenderJob): void { /* no-op */ }
787
+
788
+ endNode(n: Node, _job: RenderJob): void {
789
+ const draw = this.flush(EmitState.NDraw);
790
+ const ldraw = this.flush(EmitState.NLabel);
791
+ if (draw || ldraw) {
792
+ const set = this.drawsFor(n);
793
+ if (draw) set.draw = draw;
794
+ if (ldraw) set.ldraw = escBackslash(ldraw);
795
+ }
796
+ this.resetState(EmitState.NDraw, EmitState.NLabel);
797
+ }
798
+
799
+ /**
800
+ * Emit the edge spline beziers (EDRAW) and arrowhead ops (TDRAW/HDRAW),
801
+ * reading the already-routed geometry from `e.info`. Mirrors
802
+ * emit_edge_graphics: each bezier under the edge pen, then tail/head arrows
803
+ * under the default solid line style. The port draws SVG edges directly in
804
+ * svg.ts (not via shared bezier/polygon callbacks), so the xdot edge draw is
805
+ * self-contained here — the same per-renderer split the port already uses.
806
+ * @see lib/common/emit.c:emit_edge_graphics
807
+ */
808
+ beginEdge(e: Edge, job: RenderJob): void {
809
+ // emit_edge gates ALL edge drawing (spline + labels) on edge_in_box: an edge
810
+ // whose only content is a label placed outside job->clip draws nothing. The
811
+ // SVG renderer applies the same gate via edgeGroupOpen; the xdot renderer
812
+ // must too, else concentrate-merged edges (no spline, stale off-box label
813
+ // position) emit a spurious _ldraw_. @see lib/common/emit.c:emit_edge (3039)
814
+ this.edgeDrawable = edgeHasDrawableContent(e, job.bb);
815
+ const spl = e.info.spl;
816
+ if (!this.edgeDrawable || spl === undefined) return;
817
+ const edraw = this.bufs[EmitState.EDraw]!;
818
+ const colorAttr = e.attrs.get('color') ?? '';
819
+ const numc = (colorAttr.match(/:/g) ?? []).length;
820
+ const numsemi = (colorAttr.match(/;/g) ?? []).length;
821
+ // Tapered edge (style=tapered) → the first bezier as a filled taper polygon
822
+ // with transparent pen + edge-color fill; else plain `:` multicolor → N
823
+ // parallel offset beziers; else the single-color spline. @see emit.c:2422/2443
824
+ // Split-multicolor arrow colors: tail = first color, head = end color
825
+ // (inverse of the parallel branch). Undefined for all other edge kinds.
826
+ // @see lib/common/emit.c:2400 (multicolor arrow rule)
827
+ let tailArrowColor: string | undefined;
828
+ let headArrowColor: string | undefined;
829
+ if (numsemi > 0 && numc > 0) {
830
+ // Split-along-length `;` multicolor (e.g. `red;0.5:blue`): one bezier per
831
+ // color segment, split along the spline's arc length. Takes precedence
832
+ // over tapered / `:` parallel, matching C's `if (numsemi && numc)` order.
833
+ // @see lib/common/emit.c:2389 multicolor
834
+ const c = this.emitSplitSpline(spl.list as (Bezier | undefined)[], colorAttr, edraw, job);
835
+ tailArrowColor = c.firstColor;
836
+ headArrowColor = c.endColor;
837
+ } else if (edgeIsTapered(e)) {
838
+ this.emitTaperedSpline(e, spl.list[0], edraw, job);
839
+ } else if (numc > 0) {
840
+ this.emitParallelSpline(spl.list as (Bezier | undefined)[], colorAttr, numc, edraw, job);
841
+ } else {
842
+ // splines=ortho + radius/style=rounded → straight segments + corner arcs
843
+ // as polylines (L), else the single bezier. @see emit.c:2583 / svg-helpers
844
+ const radius = orthoRoundedRadius(e, job);
845
+ const obj = job.obj;
846
+ const origStyle = obj !== null ? [...obj.rawStyle] : [];
847
+ const multi = spl.list.length > 1;
848
+ for (const bez of spl.list) {
849
+ const pts = bez.list.slice(0, bez.size);
850
+ const polys = radius !== null && bez.size >= 4 ? orthoRoundedPolylines(pts, radius) : [];
851
+ if (polys.length > 0) {
852
+ for (const poly of polys) edraw.push(this.styleOp(job), this.penOp(job), xdotPoints('L', poly));
853
+ } else {
854
+ edraw.push(this.styleOp(job), this.penOp(job), xdotPoints('B', pts));
855
+ }
856
+ // arrow_gen (drawn to TDRAW/HDRAW below) resets the job style to
857
+ // defaultlinestyle ("solid") as a side effect. For a multi-bezier spline
858
+ // the NEXT segment's xdot_style re-emits the current rawstyle every call,
859
+ // so a solid edge picks up a bare `S 5 -solid`; C restores the edge's own
860
+ // styles afterward only when it has explicit ones. @see emit.c:2668-2677
861
+ if (obj !== null && multi && (bez.sflag || bez.eflag)) {
862
+ obj.rawStyle = origStyle.length > 0 ? origStyle : ['solid'];
863
+ }
864
+ }
865
+ if (obj !== null) obj.rawStyle = origStyle;
866
+ }
867
+ // Arrows: y-up ops already computed for the shared render path. C sets the
868
+ // default line style ("solid") + penwidth before each arrow primitive.
869
+ this.emitArrows(this.bufs[EmitState.TDraw]!, e.info.tailArrowOps, job, EmitState.TDraw, tailArrowColor);
870
+ this.emitArrows(this.bufs[EmitState.HDraw]!, e.info.headArrowOps, job, EmitState.HDraw, headArrowColor);
871
+ }
872
+
873
+ /**
874
+ * Emit a tapered edge's first bezier as a filled taper polygon: `S <n>
875
+ * -tapered` (the style), a transparent pen, the edge-color fill, then the
876
+ * polygon vertices from `taper()` (y-up). @see lib/common/emit.c:2422
877
+ */
878
+ private emitTaperedSpline(e: Edge, bz: Bezier | undefined, edraw: string[], job: RenderJob): void {
879
+ if (bz === undefined) return;
880
+ const radfunc = taperfun(e.attrs.get('dir'), isDirected(e.tail.root));
881
+ const verts = taper(bz, radfunc, job.obj?.penWidth ?? 1);
882
+ const edgeColor = job.obj?.penColor ?? { type: 'string', s: 'black' };
883
+ edraw.push(this.styleOp(job));
884
+ edraw.push(xdotPenColor(resolveRenderColor('transparent')));
885
+ edraw.push(xdotFillColor(edgeColor));
886
+ edraw.push(xdotPoints('P', verts));
887
+ }
888
+
889
+ /**
890
+ * Emit a split-along-length `;` multicolor edge spline: split each routed
891
+ * bezier along its arc length into one sub-curve per color segment, drawn
892
+ * under that segment's pen. Reuses the same split geometry
893
+ * (splitSplineByColor) as the SVG path. @see lib/common/emit.c:1975 multicolor
894
+ */
895
+ private emitSplitSpline(
896
+ bzList: (Bezier | undefined)[],
897
+ colorAttr: string,
898
+ edraw: string[],
899
+ job: RenderJob,
900
+ ): { firstColor: string; endColor: string } {
901
+ const segs = parseSegs(colorAttr).segs;
902
+ const firstColor = segs[0]?.color ?? 'black';
903
+ let endColor = firstColor;
904
+ for (const bz of bzList) {
905
+ if (bz === undefined || bz.size < 4) continue;
906
+ const split = splitSplineByColor(bz.list.slice(0, bz.size), segs);
907
+ endColor = split.endColor;
908
+ for (const c of split.curves) {
909
+ edraw.push(this.styleOp(job), xdotPenColor(resolveRenderColor(c.color)), xdotPoints('B', c.points));
910
+ }
911
+ }
912
+ return { firstColor, endColor };
913
+ }
914
+
915
+ /**
916
+ * Emit the parallel-multicolor edge spline: one offset Bézier per color,
917
+ * offset SEP=2.0 perpendicular per pass — reusing the same offset geometry
918
+ * (buildOffsetLists/advanceTmpList) as the SVG parallel-edge path.
919
+ * @see lib/common/emit.c:2443 (parallel multicolor) / svg-parallel-edge.ts
920
+ */
921
+ private emitParallelSpline(
922
+ bzList: (Bezier | undefined)[],
923
+ colorAttr: string,
924
+ numc: number,
925
+ edraw: string[],
926
+ job: RenderJob,
927
+ ): void {
928
+ const segData = bzList.map((bz) =>
929
+ bz !== undefined && bz.size >= 4
930
+ ? buildOffsetLists(bz.list, (2 + numc) / 2)
931
+ : { offlist: [] as Point[], tmplist: [] as Point[] },
932
+ );
933
+ const colors = parseSegs(colorAttr).segs.map((s) => s.color ?? 'black');
934
+ for (const color of colors) {
935
+ const pen = xdotPenColor(resolveRenderColor(color));
936
+ for (const sd of segData) {
937
+ if (sd.offlist.length === 0) continue;
938
+ advanceTmpList(sd.tmplist, sd.offlist);
939
+ edraw.push(this.styleOp(job), pen, xdotPoints('B', sd.tmplist));
940
+ }
941
+ }
942
+ }
943
+
944
+ /** Emit one arrow's primitive ops into `buf` (pen/fill from the edge color).
945
+ * arrow_gen sets the default line style ("solid") and the edge penwidth
946
+ * before each primitive, so a non-default penwidth emits `S setlinewidth(N)`
947
+ * once (tracked per HDRAW/TDRAW state). @see arrows.c:arrow_gen */
948
+ private emitArrows(
949
+ buf: string[], ops: ArrowDrawOp[] | undefined, job: RenderJob, state: EmitState,
950
+ penOverride?: string,
951
+ ): void {
952
+ if (ops === undefined) return;
953
+ const pen = penOverride !== undefined
954
+ ? resolveRenderColor(penOverride)
955
+ : job.obj?.penColor ?? { type: 'string', s: 'black' };
956
+ const pw = job.obj?.penWidth ?? 1;
957
+ for (const op of ops) {
958
+ if (Math.abs(pw - this.penwidth[state]!) >= 0.0005) {
959
+ this.penwidth[state] = pw;
960
+ buf.push(xdotStrOp('S ', 'setlinewidth(' + trimFixed3(pw) + ')'));
961
+ }
962
+ buf.push(xdotStrOp('S ', 'solid'));
963
+ buf.push(xdotPenColor(pen));
964
+ switch (op.kind) {
965
+ case 'polygon':
966
+ if (op.filled) buf.push(xdotFillColor(pen));
967
+ buf.push(xdotPoints(op.filled ? 'P' : 'p', op.points));
968
+ break;
969
+ case 'ellipse':
970
+ if (op.filled) buf.push(xdotFillColor(pen));
971
+ buf.push(
972
+ (op.filled ? 'E ' : 'e ') + xdotPoint(op.center) +
973
+ xdotNum(op.rx) + ' ' + xdotNum(op.ry) + ' ',
974
+ );
975
+ break;
976
+ case 'polyline':
977
+ buf.push(xdotPoints('L', op.points));
978
+ break;
979
+ case 'bezier':
980
+ buf.push(xdotPoints('B', op.points));
981
+ break;
982
+ }
983
+ }
984
+ }
985
+
986
+ endEdge(e: Edge, job: RenderJob): void {
987
+ // edge_in_box gate (set in beginEdge): a content-less / off-box edge draws
988
+ // nothing, matching native's emit_edge early return. @see emit.c:emit_edge
989
+ if (!this.edgeDrawable) {
990
+ this.resetState(EmitState.EDraw, EmitState.ELabel);
991
+ this.resetState(EmitState.HDraw, EmitState.TDraw);
992
+ this.penwidth[EmitState.HLabel] = 1;
993
+ this.penwidth[EmitState.TLabel] = 1;
994
+ return;
995
+ }
996
+ // Emit the edge's labels (center/xlabel/head/tail) — the port draws these
997
+ // in svg.ts endEdge, not the shared path, so the xdot renderer runs them
998
+ // itself, mirroring emit_edge's emit_edge_label. gvrenderTextspan routes
999
+ // each span to the edge's ELABEL buffer → _ldraw_. @see emit.c:3010
1000
+ renderEdgeLabels(e, this, job);
1001
+ const draw = this.flush(EmitState.EDraw);
1002
+ const hdraw = this.flush(EmitState.HDraw);
1003
+ const tdraw = this.flush(EmitState.TDraw);
1004
+ const ldraw = this.flush(EmitState.ELabel);
1005
+ const hldraw = this.flush(EmitState.HLabel);
1006
+ const tldraw = this.flush(EmitState.TLabel);
1007
+ if (draw || hdraw || tdraw || ldraw || hldraw || tldraw) {
1008
+ const set = this.drawsFor(e);
1009
+ if (draw) set.draw = draw;
1010
+ if (hdraw) set.hdraw = hdraw;
1011
+ if (tdraw) set.tdraw = tdraw;
1012
+ if (ldraw) set.ldraw = escBackslash(ldraw);
1013
+ if (hldraw) set.hldraw = escBackslash(hldraw);
1014
+ if (tldraw) set.tldraw = escBackslash(tldraw);
1015
+ }
1016
+ this.resetState(EmitState.EDraw, EmitState.ELabel);
1017
+ this.resetState(EmitState.HDraw, EmitState.TDraw);
1018
+ this.penwidth[EmitState.HLabel] = 1;
1019
+ this.penwidth[EmitState.TLabel] = 1;
1020
+ this.textflags[EmitState.HLabel] = 0;
1021
+ this.textflags[EmitState.TLabel] = 0;
1022
+ }
1023
+
1024
+ beginCluster(_sg: Graph, _job: RenderJob): void { /* no-op */ }
1025
+
1026
+ endCluster(sg: Graph, _job: RenderJob): void {
1027
+ const draw = this.flush(EmitState.CDraw);
1028
+ const ldraw = this.flush(EmitState.CLabel);
1029
+ if (draw || ldraw) {
1030
+ const set = this.drawsFor(sg);
1031
+ if (draw) set.draw = draw;
1032
+ // Cluster labels are stored via plain agxset (xdot_end_cluster:286), NOT
1033
+ // put_escaping_backslashes like node/edge/graph labels — so NO escBackslash.
1034
+ // The source `\"`/`\\` in the label survive as-is through agcanonEscape on
1035
+ // serialize (drawAttr). @see gvrender_core_dot.c:286
1036
+ if (ldraw) set.ldraw = ldraw;
1037
+ }
1038
+ this.clusters.push(sg);
1039
+ this.resetState(EmitState.CDraw, EmitState.CLabel);
1040
+ }
1041
+
1042
+ textspan(pos: Point, span: TextSpan, job: RenderJob): void {
1043
+ const buf = this.getBuf(job);
1044
+ const st = job.obj !== null ? job.obj.emitState : EmitState.GDraw;
1045
+ const j = span.just === 'l' ? -1 : span.just === 'r' ? 1 : 0;
1046
+ const p = { x: pos.x, y: pos.y + span.yoffset_centerline };
1047
+ buf.push(xdotFont(span.fontSize, span.fontName ?? ''));
1048
+ buf.push(xdotPenColor(resolveRenderColor(span.fontColor ?? 'black')));
1049
+ // Text flags (xdot version >= 15): emit `t <bits>` only when they change.
1050
+ // @see gvrender_core_dot.c:520 xdot_textspan
1051
+ const bits = (span.fontFlags ?? 0) & 0x7f;
1052
+ if (this.textflags[st] !== bits) {
1053
+ buf.push('t ' + String(bits) + ' ');
1054
+ this.textflags[st] = bits;
1055
+ }
1056
+ buf.push(
1057
+ 'T ' + xdotPoint(p) + String(j) + ' ' + xdotNum(span.size.x) + ' ' +
1058
+ String(utf8Len(span.str)) + ' -' + span.str + ' ',
1059
+ );
1060
+ }
1061
+
1062
+ ellipse(center: Point, rx: number, ry: number, filled: boolean, job: RenderJob): void {
1063
+ const buf = this.getBuf(job);
1064
+ buf.push(this.styleOp(job), this.penOp(job));
1065
+ // C passes A=[center, corner] to the gradient; corner = center + (rx,ry).
1066
+ if (filled) buf.push(this.fillOp(job, [center, { x: center.x + rx, y: center.y + ry }]));
1067
+ buf.push(filled ? 'E ' : 'e ', xdotPoint(center), xdotNum(rx) + ' ' + xdotNum(ry) + ' ');
1068
+ }
1069
+
1070
+ polygon(pts: Point[], filled: boolean, job: RenderJob): void {
1071
+ const buf = this.getBuf(job);
1072
+ buf.push(this.styleOp(job), this.penOp(job));
1073
+ if (filled) buf.push(this.fillOp(job, pts));
1074
+ buf.push(xdotPoints(filled ? 'P' : 'p', pts));
1075
+ }
1076
+
1077
+ bezier(pts: Point[], filled: boolean, job: RenderJob): void {
1078
+ const buf = this.getBuf(job);
1079
+ buf.push(this.styleOp(job), this.penOp(job));
1080
+ if (filled) buf.push(this.fillOp(job, pts));
1081
+ // NB 'b'/'B' are reversed vs the other ops. @see gvrender_core_dot.c:632
1082
+ buf.push(xdotPoints(filled ? 'b' : 'B', pts));
1083
+ }
1084
+
1085
+ polyline(pts: Point[], job: RenderJob): void {
1086
+ const buf = this.getBuf(job);
1087
+ buf.push(this.styleOp(job), this.penOp(job), xdotPoints('L', pts));
1088
+ }
1089
+
1090
+ // --- graphics-state ops ------------------------------------------------
1091
+
1092
+ /** Pen color op from the resolved graphics state (default black). */
1093
+ private penOp(job: RenderJob): string {
1094
+ return xdotPenColor(job.obj?.penColor ?? { type: 'string', s: 'black' });
1095
+ }
1096
+
1097
+ /**
1098
+ * Fill op from the resolved graphics state. A linear gradient emits the
1099
+ * bracketed `C len -[G0 G1 2 <stops>]` form (xdot_gradient_fillcolor); a plain
1100
+ * fill emits `C len -#hex`. `pts` are the shape points the gradient endpoints
1101
+ * derive from. Radial gradients are deferred (emit the base fill).
1102
+ * @see plugin/core/gvrender_core_dot.c:544 xdot_gradient_fillcolor
1103
+ */
1104
+ private fillOp(job: RenderJob, pts: Point[]): string {
1105
+ const obj = job.obj;
1106
+ if (obj && obj.fill === FillType.Linear) {
1107
+ return linearGradientOp(pts, obj.fillColor, obj.stopColor, obj.gradientFrac, obj.gradientAngle);
1108
+ }
1109
+ if (obj && obj.fill === FillType.Radial) {
1110
+ return radialGradientOp(pts, obj.fillColor, obj.stopColor, obj.gradientFrac, obj.gradientAngle);
1111
+ }
1112
+ return xdotFillColor(obj?.fillColor ?? { type: 'string', s: 'black' });
1113
+ }
1114
+
1115
+ /** Style ops (`S`): setlinewidth on a penwidth change, plus dash/dot pen.
1116
+ * @see gvrender_core_dot.c:161 xdot_style */
1117
+ private styleOp(job: RenderJob): string {
1118
+ const obj = job.obj;
1119
+ if (obj === null) return '';
1120
+ let s = '';
1121
+ const st = obj.emitState;
1122
+ if (Math.abs(obj.penWidth - this.penwidth[st]!) >= 0.0005) {
1123
+ this.penwidth[st] = obj.penWidth;
1124
+ s += xdotStrOp('S ', 'setlinewidth(' + trimFixed3(obj.penWidth) + ')');
1125
+ }
1126
+ // Named styles carried in obj.rawStyle (the parsed style tokens, e.g. an
1127
+ // explicit `style="solid"`/`"dashed"`) emit an `S` op — but only the LINE
1128
+ // styles. xdot_style filters filled/bold/setlinewidth; the polygon/fill
1129
+ // styles (rounded/diagonals/striped/wedged/invis/radial/tapered) are
1130
+ // consumed by the shape/fill code before gvrender_set_style, so native
1131
+ // never emits them as `S` ops either. @see gvrender_core_dot.c:184
1132
+ if (obj.rawStyle.length > 0) {
1133
+ for (const p of obj.rawStyle) {
1134
+ if (NON_LINE_STYLES.has(p) || p.startsWith('setlinewidth')) continue;
1135
+ s += xdotStrOp('S ', p);
1136
+ }
1137
+ } else {
1138
+ // Fallback for paths that set only a PenType (no rawStyle): reconstruct
1139
+ // the dash/dot token C would emit from the pen.
1140
+ if (obj.pen === PenType.Dashed) s += xdotStrOp('S ', 'dashed');
1141
+ else if (obj.pen === PenType.Dotted) s += xdotStrOp('S ', 'dotted');
1142
+ }
1143
+ return s;
1144
+ }
1145
+
1146
+ // --- buffer + side-table plumbing --------------------------------------
1147
+
1148
+ /** Active xdot buffer for the current emit state. */
1149
+ private getBuf(job: RenderJob): string[] {
1150
+ const obj = job.obj;
1151
+ return this.bufs[obj !== null ? obj.emitState : EmitState.GDraw]!;
1152
+ }
1153
+
1154
+ /** Join and clear the buffer for `state`; returns '' when empty. */
1155
+ private flush(state: EmitState): string {
1156
+ const buf = this.bufs[state]!;
1157
+ const s = buf.join('');
1158
+ buf.length = 0;
1159
+ return s;
1160
+ }
1161
+
1162
+ /** Reset per-emit-state penwidth/textflags after an object (C's reset). */
1163
+ private resetState(draw: EmitState, label: EmitState): void {
1164
+ this.penwidth[draw] = 1;
1165
+ this.penwidth[label] = 1;
1166
+ this.textflags[draw] = 0;
1167
+ this.textflags[label] = 0;
1168
+ }
1169
+
1170
+ private drawsFor(o: Node | Edge | Graph): XdotDraws {
1171
+ let d = this.draws.get(o);
1172
+ if (d === undefined) {
1173
+ d = {};
1174
+ this.draws.set(o, d);
1175
+ }
1176
+ return d;
1177
+ }
1178
+
1179
+ // --- serialization (agwrite-at-end) ------------------------------------
1180
+
1181
+ /**
1182
+ * Emit `key="value"`, escaping the value the way agwrite's agcanonStr does: a
1183
+ * `"` becomes `\"` unless it is already part of an escape sequence (see
1184
+ * agcanonEscape). A draw string carries label text that may contain a bare `"`
1185
+ * (would close the attribute early) or a source `\"`/`\\` (must not be
1186
+ * double-escaped). The byte-length prefix stays on the UNescaped text (the
1187
+ * parser un-escapes before parseXDot re-reads it), matching native exactly.
1188
+ * @see lib/cgraph/write.c:_agstrcanon (135-167)
1189
+ */
1190
+ private drawAttr(key: string, value: string): string {
1191
+ return key + '="' + agcanonEscape(value) + '"';
1192
+ }
1193
+
1194
+ /** `llx,lly,urx,ury` from a graph's layout bb. */
1195
+ private bbStr(g: Graph): string {
1196
+ const bb = g.info.bb;
1197
+ return gfmt5(bb.ll.x) + ',' + gfmt5(bb.ll.y) + ',' +
1198
+ gfmt5(bb.ur.x) + ',' + gfmt5(bb.ur.y);
1199
+ }
1200
+
1201
+ /**
1202
+ * Serialize the whole laid-out graph to xdot DOT text — a faithful port of
1203
+ * cgraph's agwrite (lib/cgraph/write.c). Recurses the subgraph tree
1204
+ * (write_subgs/write_body), scoping each node/edge to the subgraph(s) it
1205
+ * belongs to via preorder numbers (write_node_test/write_edge_test), and
1206
+ * re-emits an object bare (no attrs) on any scope after the first
1207
+ * (attrs_written). This reproduces native's per-subgraph edge re-declarations
1208
+ * — e.g. an edge in a rank=same subgraph is drawn once, then re-declared bare.
1209
+ * @see lib/cgraph/write.c:agwrite
1210
+ */
1211
+ private serialize(g: Graph): string {
1212
+ const ctx: SerCtx = {
1213
+ out: [],
1214
+ preorder: new Map<Graph, number>(),
1215
+ nodeLW: new Map<Node, number>(),
1216
+ edgeLW: new Map<Edge, number>(),
1217
+ attrsWritten: new Set<Node | Edge>(),
1218
+ level: 0,
1219
+ };
1220
+ this.subgdfs(g, 1, ctx.preorder);
1221
+ this.writeHdr(g, true, ctx);
1222
+ this.writeBody(g, ctx);
1223
+ this.writeTrl(ctx);
1224
+ return ctx.out.join('');
1225
+ }
1226
+
1227
+ /** Preorder-number the subgraph tree. @see write.c:subgdfs */
1228
+ private subgdfs(g: Graph, ix: number, preorder: Map<Graph, number>): number {
1229
+ let ix0 = ix;
1230
+ preorder.set(g, ix0);
1231
+ for (const sub of g.subgraphs.values()) ix0 = this.subgdfs(sub, ix0, preorder);
1232
+ return ix0 + 1;
1233
+ }
1234
+
1235
+ /** A subgraph is anonymous when its name is empty or a `%N` local name. */
1236
+ private isAnonymous(g: Graph): boolean {
1237
+ return g.name.length === 0 || g.name.charCodeAt(0) === 0x25 /* % */;
1238
+ }
1239
+
1240
+ /** A graph carries a `bb` attribute when it is the root or has a layout bb
1241
+ * (clusters, and any subgraph output.c computed a box for). Native seeds `bb`
1242
+ * on every graph via safe_dcl, so the value differs (set vs empty "") between
1243
+ * a boxed graph and an unboxed child — the driver that makes an anon subgraph
1244
+ * under the root or a cluster "relevant". @see lib/common/output.c:safe_dcl */
1245
+ private hasBb(g: Graph): boolean {
1246
+ return g === g.root || this.clusters.includes(g);
1247
+ }
1248
+
1249
+ /** Anonymous subgraph with no own node/edge defaults and no graph attrs
1250
+ * differing from its parent → inlined into the parent. Native compares every
1251
+ * graph attr over the root attr dict; the load-bearing ones are `bb` (set on
1252
+ * the parent, empty on the child) and `rank`. @see write.c:irrelevant_subgraph */
1253
+ private irrelevantSubgraph(g: Graph): boolean {
1254
+ if (!this.isAnonymous(g)) return false;
1255
+ if (this.clusters.includes(g)) return false;
1256
+ if (g.nodeDefaults.size > 0 || g.edgeDefaults.size > 0) return false;
1257
+ if (g.parent) {
1258
+ if (this.hasBb(g) !== this.hasBb(g.parent)) return false;
1259
+ for (const [k, v] of g.attrs) {
1260
+ if (g.parent.attrs.get(k) !== v) return false;
1261
+ }
1262
+ } else if (g.attrs.size > 0) {
1263
+ return false;
1264
+ }
1265
+ return true;
1266
+ }
1267
+
1268
+ /** Non-draw graph attrs a subgraph emits (rank for rank=same; clusters use
1269
+ * clusterAttrs). Only comparator-relevant fields need be exact.
1270
+ *
1271
+ * `rec_attach_bb` walks the ROOT and then `GD_clust` recursively, so a
1272
+ * subgraph the layout did not box is attached NEITHER `bb` NOR the label
1273
+ * triple — both keep the INPUT's values, which agwrite echoes. circo and
1274
+ * twopi lay out no clusters at all (`GD_clust` empty — mirrored here by
1275
+ * `this.clusters`, filled from endCluster), so on a re-fed dot output their
1276
+ * `cluster0` comes back carrying the *previous* run's `bb` and `lp`.
1277
+ * @see lib/common/output.c:249 rec_attach_bb */
1278
+ private subgGraphAttrs(sg: Graph): string {
1279
+ if (this.clusters.includes(sg)) return this.clusterAttrs(sg);
1280
+ const parts: string[] = [];
1281
+ const rank = sg.attrs.get('rank');
1282
+ if (rank !== undefined) parts.push('rank=' + xdotId(rank));
1283
+ parts.push(...echoAttr(sg.attrs, 'bb'));
1284
+ parts.push(...graphLabelAttrs(sg));
1285
+ return parts.join(' ');
1286
+ }
1287
+
1288
+ private indent(ctx: SerCtx): string {
1289
+ return '\t'.repeat(ctx.level);
1290
+ }
1291
+
1292
+ /** @see write.c:write_hdr */
1293
+ private writeHdr(g: Graph, top: boolean, ctx: SerCtx): void {
1294
+ if (top) {
1295
+ const strict = g.kind === 'strict-directed' || g.kind === 'strict-undirected' ? 'strict ' : '';
1296
+ const kw = isDirected(g) ? 'digraph' : 'graph';
1297
+ const nm = g.name.length > 0 && !this.isAnonymous(g) ? xdotId(g.name) + ' ' : '';
1298
+ ctx.out.push(strict + kw + ' ' + nm + '{\n');
1299
+ ctx.level++;
1300
+ ctx.out.push(this.indent(ctx) + 'graph [' + this.graphAttrs(g) + '];\n');
1301
+ ctx.out.push(this.indent(ctx) + 'node [label="\\N"];\n');
1302
+ } else {
1303
+ const nm = this.isAnonymous(g) ? '' : 'subgraph ' + xdotId(g.name) + ' ';
1304
+ ctx.out.push(this.indent(ctx) + nm + '{\n');
1305
+ ctx.level++;
1306
+ const ga = this.subgGraphAttrs(g);
1307
+ if (ga) ctx.out.push(this.indent(ctx) + 'graph [' + ga + '];\n');
1308
+ }
1309
+ }
1310
+
1311
+ /** @see write.c:write_trl */
1312
+ private writeTrl(ctx: SerCtx): void {
1313
+ ctx.level--;
1314
+ ctx.out.push(this.indent(ctx) + '}\n');
1315
+ }
1316
+
1317
+ /** @see write.c:write_subgs */
1318
+ private writeSubgs(g: Graph, ctx: SerCtx): void {
1319
+ for (const sub of g.subgraphs.values()) {
1320
+ if (this.irrelevantSubgraph(sub)) {
1321
+ this.writeSubgs(sub, ctx);
1322
+ } else {
1323
+ this.writeHdr(sub, false, ctx);
1324
+ this.writeBody(sub, ctx);
1325
+ this.writeTrl(ctx);
1326
+ }
1327
+ }
1328
+ }
1329
+
1330
+ /** @see write.c:write_body — subgraphs, then this scope's nodes and edges. */
1331
+ private writeBody(g: Graph, ctx: SerCtx): void {
1332
+ this.writeSubgs(g, ctx);
1333
+ for (const n of g.nodes.values()) {
1334
+ if (this.writeNodeTest(g, n, ctx)) this.writeNode(g, n, ctx);
1335
+ let prev: Node = n;
1336
+ for (const e of n.outEdges(g)) {
1337
+ if (prev !== e.head && this.writeNodeTest(g, e.head, ctx)) {
1338
+ this.writeNode(g, e.head, ctx);
1339
+ prev = e.head;
1340
+ }
1341
+ if (this.writeEdgeTest(g, e, ctx)) this.writeEdge(g, e, ctx);
1342
+ }
1343
+ }
1344
+ }
1345
+
1346
+ /** @see write.c:write_node_test — every xdot node carries pos/size, so it is
1347
+ * never "default"; write it in the first scope that has not yet emitted it.
1348
+ * Cluster nodes (fdp compound proxies, ND_clustnode) are never declared —
1349
+ * C's write_plain/writenodeandport suppress them. @see lib/common/output.c:146 */
1350
+ private writeNodeTest(g: Graph, n: Node, ctx: SerCtx): boolean {
1351
+ if (n.info.clustnode) return false;
1352
+ return (ctx.nodeLW.get(n) ?? 0) < ctx.preorder.get(g)!;
1353
+ }
1354
+
1355
+ /** Emitted node name: a cluster node's synthetic `__i:<cluster>` id is written
1356
+ * as the cluster name it stands for. @see lib/common/output.c:114 */
1357
+ private emitNodeName(n: Node): string {
1358
+ if (n.info.clustnode) {
1359
+ const i = n.name.indexOf(':');
1360
+ if (i >= 0) return n.name.slice(i + 1);
1361
+ }
1362
+ return n.name;
1363
+ }
1364
+
1365
+ /** @see write.c:write_edge_test */
1366
+ private writeEdgeTest(g: Graph, e: Edge, ctx: SerCtx): boolean {
1367
+ return (ctx.edgeLW.get(e) ?? 0) < ctx.preorder.get(g)!;
1368
+ }
1369
+
1370
+ /** @see write.c:write_node */
1371
+ private writeNode(g: Graph, n: Node, ctx: SerCtx): void {
1372
+ let s = this.indent(ctx) + xdotId(this.emitNodeName(n));
1373
+ if (!ctx.attrsWritten.has(n)) {
1374
+ s += ' [' + this.nodeAttrs(n) + ']';
1375
+ ctx.attrsWritten.add(n);
1376
+ }
1377
+ ctx.out.push(s + ';\n');
1378
+ ctx.nodeLW.set(n, ctx.preorder.get(g)!);
1379
+ }
1380
+
1381
+ /** @see write.c:write_edge — attrs only on first emission, bare thereafter. */
1382
+ private writeEdge(g: Graph, e: Edge, ctx: SerCtx): void {
1383
+ const conn = edgeConnector(isDirected(g));
1384
+ let s = this.indent(ctx) + xdotId(this.emitNodeName(e.tail)) + ' ' + conn + ' ' +
1385
+ xdotId(this.emitNodeName(e.head));
1386
+ if (!ctx.attrsWritten.has(e)) {
1387
+ const attrs = this.edgeAttrStr(e);
1388
+ if (attrs) s += ' [' + attrs + ']';
1389
+ ctx.attrsWritten.add(e);
1390
+ }
1391
+ ctx.out.push(s + ';\n');
1392
+ ctx.edgeLW.set(e, ctx.preorder.get(g)!);
1393
+ }
1394
+
1395
+ /** Root-graph attribute block: `_draw_`, `_ldraw_`, `bb`, `xdotversion`. */
1396
+ private graphAttrs(g: Graph): string {
1397
+ const d = this.draws.get(g);
1398
+ const parts: string[] = [];
1399
+ if (d?.draw) parts.push(this.drawAttr('_draw_', d.draw));
1400
+ if (d?.ldraw) parts.push(this.drawAttr('_ldraw_', d.ldraw));
1401
+ parts.push('bb="' + this.bbStr(g) + '"');
1402
+ parts.push(...graphLabelAttrs(g));
1403
+ parts.push('xdotversion="' + XDOT_VERSION + '"');
1404
+ return parts.join(' ');
1405
+ }
1406
+
1407
+ /** Cluster attribute block. C's xdot_end_cluster ALWAYS agsets `_draw_` when
1408
+ * the graph has clusters (even empty, e.g. peripheries=0), so emit it
1409
+ * unconditionally; `_ldraw_` only when the cluster has a label.
1410
+ * @see plugin/core/gvrender_core_dot.c:284 xdot_end_cluster */
1411
+ private clusterAttrs(sg: Graph): string {
1412
+ const d = this.draws.get(sg);
1413
+ const parts: string[] = [this.drawAttr('_draw_', d?.draw ?? '')];
1414
+ if (d?.ldraw) parts.push(this.drawAttr('_ldraw_', d.ldraw));
1415
+ if (sg.info.bb) parts.push('bb="' + this.bbStr(sg) + '"');
1416
+ // rec_attach_bb recurses into GD_clust, so a labelled cluster carries the
1417
+ // same lp/lwidth/lheight triple as the root. @see lib/common/output.c:249
1418
+ parts.push(...graphLabelAttrs(sg));
1419
+ return parts.join(' ');
1420
+ }
1421
+
1422
+ /** Node attribute block: pos/width/height plus `_draw_`/`_ldraw_`. */
1423
+ private nodeAttrs(n: Node): string {
1424
+ const info = n.info;
1425
+ // attach_attrs derives the emitted size from ND_lw+ND_rw / ND_ht, NOT
1426
+ // ND_width/ND_height (output.c:307-308). They coincide except where an
1427
+ // engine leaves them divergent — patchwork's finishNode lets poly_init
1428
+ // clobber ND_width/height while the tile survives in lw/rw/ht.
1429
+ let s = 'pos="' + gfmt5(info.coord.x) + ',' + gfmt5(info.coord.y) + '"' +
1430
+ ' width=' + gfmt5((info.lw + info.rw) / 72) + ' height=' + gfmt5(info.ht / 72);
1431
+ // xlp — only when the node HAS an xlabel AND the xlabel placer actually set
1432
+ // its position (`ND_xlabel(n)->set`). An xlabel that was never placed is
1433
+ // omitted entirely — so an input `xlp` survives and is echoed.
1434
+ // @see lib/common/output.c:309-313
1435
+ const xlabel = info.xlabel as TextlabelT | undefined;
1436
+ if (xlabel && xlabel.set) s += ' xlp="' + lpStr(xlabel.pos) + '"';
1437
+ else s += pfx(echoAttr(n.attrs, 'xlp'));
1438
+ // rects — record field boxes. C gates on the SHAPE NAME being exactly
1439
+ // "record" (`strcmp(ND_shape(n)->name, "record") == 0`), so `Mrecord` and
1440
+ // HTML-table nodes get NO rects; confirmed against the native oracle. When
1441
+ // the shape is not a record the input's `rects` is never overwritten and is
1442
+ // echoed — patchwork forces every node to `box`, so a re-fed dot output
1443
+ // returns its old record rects untouched. @see lib/common/output.c:314-317
1444
+ const shape = info.shape as ShapeDesc | undefined;
1445
+ if (shape?.name === 'record') {
1446
+ const rects: string[] = [];
1447
+ appendRecordRects(n, info.shape_info as FieldT, rects);
1448
+ s += ' rects="' + rects.join(' ') + '"';
1449
+ } else {
1450
+ s += pfx(echoAttr(n.attrs, 'rects'));
1451
+ }
1452
+ const d = this.draws.get(n);
1453
+ if (d?.draw) s += ' ' + this.drawAttr('_draw_', d.draw);
1454
+ if (d?.ldraw) s += ' ' + this.drawAttr('_ldraw_', d.ldraw);
1455
+ return s;
1456
+ }
1457
+
1458
+ /** The `[...]` attribute body for an edge (draw ops + spline `pos`), or ''. */
1459
+ private edgeAttrStr(e: Edge): string {
1460
+ const d = this.draws.get(e);
1461
+ const parts: string[] = [];
1462
+ if (d?.draw) parts.push(this.drawAttr('_draw_', d.draw));
1463
+ if (d?.ldraw) parts.push(this.drawAttr('_ldraw_', d.ldraw));
1464
+ if (d?.hdraw) parts.push(this.drawAttr('_hdraw_', d.hdraw));
1465
+ if (d?.tdraw) parts.push(this.drawAttr('_tdraw_', d.tdraw));
1466
+ if (d?.hldraw) parts.push(this.drawAttr('_hldraw_', d.hldraw));
1467
+ if (d?.tldraw) parts.push(this.drawAttr('_tldraw_', d.tldraw));
1468
+ const pos = formatEdgePos(e);
1469
+ if (pos) {
1470
+ parts.push(pos);
1471
+ } else {
1472
+ // C's attach_attrs only agsets `pos` when the edge HAS a spline
1473
+ // (output.c:348); an engine that never routes (patchwork) leaves the
1474
+ // INPUT's own pos attribute intact and write.c emits it verbatim.
1475
+ parts.push(...echoAttr(e.attrs, 'pos'));
1476
+ }
1477
+ // The label-position attributes live inside the SAME loop that writes `pos`
1478
+ // (output.c:377-396), so they are attached only for a routed, non-IGNORED
1479
+ // edge. Note the asymmetry C encodes: `lp`/`head_lp`/`tail_lp` are emitted
1480
+ // whenever the label EXISTS, but `xlp` additionally requires `->set` — an
1481
+ // unplaced xlabel is omitted. Order mirrors C: lp, xlp, head_lp, tail_lp.
1482
+ // Each gate is independent, and each FAILED gate leaves the input's own
1483
+ // value in the slot for write.c to echo: an unrouted edge (patchwork routes
1484
+ // none) returns the `lp` a previous dot run wrote.
1485
+ const attached = edgeAttrsAttached(e);
1486
+ const info = e.info;
1487
+ if (attached && info.label) parts.push('lp="' + lpStr(info.label.pos) + '"');
1488
+ else parts.push(...echoAttr(e.attrs, 'lp'));
1489
+ if (attached && info.xlabel && info.xlabel.set) {
1490
+ parts.push('xlp="' + lpStr(info.xlabel.pos) + '"');
1491
+ } else parts.push(...echoAttr(e.attrs, 'xlp'));
1492
+ if (attached && info.head_label) {
1493
+ parts.push('head_lp="' + lpStr(info.head_label.pos) + '"');
1494
+ } else parts.push(...echoAttr(e.attrs, 'head_lp'));
1495
+ if (attached && info.tail_label) {
1496
+ parts.push('tail_lp="' + lpStr(info.tail_label.pos) + '"');
1497
+ } else parts.push(...echoAttr(e.attrs, 'tail_lp'));
1498
+ return parts.join(' ');
1499
+ }
1500
+ }
1501
+
1502
+ // ---------------------------------------------------------------------------
1503
+ // Factory functions
1504
+ // ---------------------------------------------------------------------------
1505
+
1506
+ /** @see plugin/core/gvrender_core_dot.c FORMAT_DOT */
1507
+ export function createDotRenderer(): RendererPlugin {
1508
+ return new DotRenderer();
1509
+ }
1510
+
1511
+ /** @see plugin/core/gvrender_core_dot.c FORMAT_XDOT */
1512
+ export function createXdotRenderer(): RendererPlugin {
1513
+ return new XdotRenderer();
1514
+ }