@knowvah/dot-engine 1.9.0 → 2.0.1

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 (184) hide show
  1. package/README.md +232 -30
  2. package/dist/api/builder.d.ts +3 -0
  3. package/dist/api/builder.d.ts.map +1 -1
  4. package/dist/api/edge-ops.d.ts +7 -0
  5. package/dist/api/edge-ops.d.ts.map +1 -1
  6. package/dist/api/geometry.d.ts +5 -2
  7. package/dist/api/geometry.d.ts.map +1 -1
  8. package/dist/api.js +159 -31
  9. package/dist/api.js.map +3 -3
  10. package/dist/async/collect.d.ts +49 -0
  11. package/dist/async/collect.d.ts.map +1 -0
  12. package/dist/async/fonts.d.ts +20 -0
  13. package/dist/async/fonts.d.ts.map +1 -0
  14. package/dist/async/render-async.d.ts +91 -0
  15. package/dist/async/render-async.d.ts.map +1 -0
  16. package/dist/async/render-into.d.ts +38 -0
  17. package/dist/async/render-into.d.ts.map +1 -0
  18. package/dist/async/sanitize.d.ts +28 -0
  19. package/dist/async/sanitize.d.ts.map +1 -0
  20. package/dist/common/htmltable-types.d.ts +3 -3
  21. package/dist/common/htmltable-types.d.ts.map +1 -1
  22. package/dist/common/make-label.d.ts.map +1 -1
  23. package/dist/common/poly-shapes.d.ts.map +1 -1
  24. package/dist/common/textmeasure-factory.d.ts +2 -0
  25. package/dist/common/textmeasure-factory.d.ts.map +1 -1
  26. package/dist/common/utils-inputscale.d.ts +19 -0
  27. package/dist/common/utils-inputscale.d.ts.map +1 -0
  28. package/dist/errors.d.ts +61 -5
  29. package/dist/errors.d.ts.map +1 -1
  30. package/dist/gvc/context.d.ts +36 -4
  31. package/dist/gvc/context.d.ts.map +1 -1
  32. package/dist/gvc/device.d.ts +5 -2
  33. package/dist/gvc/device.d.ts.map +1 -1
  34. package/dist/gvc/image-resolver.d.ts +16 -16
  35. package/dist/gvc/image-resolver.d.ts.map +1 -1
  36. package/dist/gvc/job.d.ts +1 -9
  37. package/dist/gvc/job.d.ts.map +1 -1
  38. package/dist/gvc/usershape.d.ts +2 -12
  39. package/dist/gvc/usershape.d.ts.map +1 -1
  40. package/dist/index.d.ts +27 -8
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +7682 -5535
  43. package/dist/index.js.map +4 -4
  44. package/dist/label/index.d.ts.map +1 -1
  45. package/dist/label/node.d.ts +0 -6
  46. package/dist/label/node.d.ts.map +1 -1
  47. package/dist/label/rectangle.d.ts +1 -7
  48. package/dist/label/rectangle.d.ts.map +1 -1
  49. package/dist/layout/circo/circular.d.ts +8 -5
  50. package/dist/layout/circo/circular.d.ts.map +1 -1
  51. package/dist/layout/dot/pack-components.d.ts +0 -19
  52. package/dist/layout/dot/pack-components.d.ts.map +1 -1
  53. package/dist/layout/dot/position.d.ts +7 -2
  54. package/dist/layout/dot/position.d.ts.map +1 -1
  55. package/dist/layout/fdp/derive.d.ts.map +1 -1
  56. package/dist/layout/fdp/index.d.ts.map +1 -1
  57. package/dist/layout/fdp/init.d.ts.map +1 -1
  58. package/dist/layout/fdp/layout.d.ts.map +1 -1
  59. package/dist/layout/fdp/normalize.d.ts +2 -1
  60. package/dist/layout/fdp/normalize.d.ts.map +1 -1
  61. package/dist/layout/fdp/ports.d.ts +0 -10
  62. package/dist/layout/fdp/ports.d.ts.map +1 -1
  63. package/dist/layout/fdp/xlayout.d.ts +0 -16
  64. package/dist/layout/fdp/xlayout.d.ts.map +1 -1
  65. package/dist/layout/neato/adjust-info.d.ts +117 -0
  66. package/dist/layout/neato/adjust-info.d.ts.map +1 -0
  67. package/dist/layout/neato/cdt-surface.d.ts.map +1 -1
  68. package/dist/layout/neato/constraint-adjust.d.ts +40 -0
  69. package/dist/layout/neato/constraint-adjust.d.ts.map +1 -0
  70. package/dist/layout/neato/edge-len.d.ts +20 -0
  71. package/dist/layout/neato/edge-len.d.ts.map +1 -0
  72. package/dist/layout/neato/fdp-adjust.d.ts +32 -4
  73. package/dist/layout/neato/fdp-adjust.d.ts.map +1 -1
  74. package/dist/layout/neato/index.d.ts +9 -6
  75. package/dist/layout/neato/index.d.ts.map +1 -1
  76. package/dist/layout/neato/init.d.ts +4 -11
  77. package/dist/layout/neato/init.d.ts.map +1 -1
  78. package/dist/layout/neato/kk-paths.d.ts +50 -0
  79. package/dist/layout/neato/kk-paths.d.ts.map +1 -0
  80. package/dist/layout/neato/kk-solve.d.ts +14 -0
  81. package/dist/layout/neato/kk-solve.d.ts.map +1 -0
  82. package/dist/layout/neato/kk.d.ts +44 -0
  83. package/dist/layout/neato/kk.d.ts.map +1 -0
  84. package/dist/layout/neato/multispline-router.d.ts.map +1 -1
  85. package/dist/layout/neato/poly.d.ts +50 -0
  86. package/dist/layout/neato/poly.d.ts.map +1 -0
  87. package/dist/layout/neato/sc-adjust.d.ts +2 -9
  88. package/dist/layout/neato/sc-adjust.d.ts.map +1 -1
  89. package/dist/layout/neato/sgd-dijkstra.d.ts +20 -0
  90. package/dist/layout/neato/sgd-dijkstra.d.ts.map +1 -0
  91. package/dist/layout/neato/sgd.d.ts +14 -9
  92. package/dist/layout/neato/sgd.d.ts.map +1 -1
  93. package/dist/layout/neato/simple-scale.d.ts +15 -0
  94. package/dist/layout/neato/simple-scale.d.ts.map +1 -0
  95. package/dist/layout/neato/start.d.ts +51 -0
  96. package/dist/layout/neato/start.d.ts.map +1 -0
  97. package/dist/layout/neato/vpsc-adjust.d.ts +21 -0
  98. package/dist/layout/neato/vpsc-adjust.d.ts.map +1 -0
  99. package/dist/layout/sfdp/index.d.ts.map +1 -1
  100. package/dist/layout/sfdp/init.d.ts +0 -9
  101. package/dist/layout/sfdp/init.d.ts.map +1 -1
  102. package/dist/layout/sfdp/spring-driver.d.ts.map +1 -1
  103. package/dist/layout/twopi/circle.d.ts +0 -7
  104. package/dist/layout/twopi/circle.d.ts.map +1 -1
  105. package/dist/ortho/ortho-parallel.d.ts.map +1 -1
  106. package/dist/ortho/trap-query.d.ts.map +1 -1
  107. package/dist/parser/index.d.ts +9 -5
  108. package/dist/parser/index.d.ts.map +1 -1
  109. package/dist/render/index.d.ts +2 -0
  110. package/dist/render/index.d.ts.map +1 -1
  111. package/dist/render/public.d.ts +9 -3
  112. package/dist/render/public.d.ts.map +1 -1
  113. package/dist/render/xdot-public.d.ts +7 -2
  114. package/dist/render/xdot-public.d.ts.map +1 -1
  115. package/dist/render.js +7468 -5556
  116. package/dist/render.js.map +4 -4
  117. package/dist/util/xml.d.ts.map +1 -1
  118. package/dist/vpsc/Solver.d.ts +1 -0
  119. package/dist/vpsc/Solver.d.ts.map +1 -1
  120. package/package.json +1 -1
  121. package/src/api/builder.ts +73 -5
  122. package/src/api/edge-ops.ts +19 -0
  123. package/src/api/geometry.ts +22 -5
  124. package/src/async/collect.ts +204 -0
  125. package/src/async/fonts.ts +61 -0
  126. package/src/async/render-async.ts +205 -0
  127. package/src/async/render-into.ts +115 -0
  128. package/src/async/sanitize.ts +150 -0
  129. package/src/common/htmltable-types.ts +4 -5
  130. package/src/common/make-label.ts +10 -1
  131. package/src/common/poly-shapes.ts +4 -1
  132. package/src/common/textmeasure-factory.ts +11 -0
  133. package/src/common/utils-inputscale.ts +31 -0
  134. package/src/errors.ts +188 -5
  135. package/src/gvc/context.ts +96 -17
  136. package/src/gvc/device.ts +11 -6
  137. package/src/gvc/image-resolver.ts +25 -2
  138. package/src/gvc/job.ts +3 -1
  139. package/src/gvc/usershape.ts +6 -0
  140. package/src/index.ts +59 -43
  141. package/src/label/index.ts +6 -2
  142. package/src/label/node.ts +2 -1
  143. package/src/label/rectangle.ts +4 -2
  144. package/src/layout/circo/circular.ts +9 -6
  145. package/src/layout/dot/pack-components.ts +6 -2
  146. package/src/layout/dot/position.ts +21 -3
  147. package/src/layout/fdp/derive.ts +7 -6
  148. package/src/layout/fdp/index.ts +45 -5
  149. package/src/layout/fdp/init.ts +7 -6
  150. package/src/layout/fdp/layout.ts +2 -1
  151. package/src/layout/fdp/normalize.ts +12 -8
  152. package/src/layout/fdp/ports.ts +3 -2
  153. package/src/layout/fdp/xlayout.ts +8 -5
  154. package/src/layout/neato/adjust-info.ts +332 -0
  155. package/src/layout/neato/cdt-surface.ts +50 -30
  156. package/src/layout/neato/constraint-adjust.ts +466 -0
  157. package/src/layout/neato/edge-len.ts +36 -0
  158. package/src/layout/neato/fdp-adjust.ts +120 -12
  159. package/src/layout/neato/index.ts +36 -54
  160. package/src/layout/neato/init.ts +21 -36
  161. package/src/layout/neato/kk-paths.ts +146 -0
  162. package/src/layout/neato/kk-solve.ts +64 -0
  163. package/src/layout/neato/kk.ts +322 -0
  164. package/src/layout/neato/multispline-router.ts +4 -3
  165. package/src/layout/neato/poly.ts +493 -0
  166. package/src/layout/neato/sc-adjust.ts +2 -16
  167. package/src/layout/neato/sgd-dijkstra.ts +125 -0
  168. package/src/layout/neato/sgd.ts +63 -40
  169. package/src/layout/neato/simple-scale.ts +54 -0
  170. package/src/layout/neato/start.ts +222 -0
  171. package/src/layout/neato/vpsc-adjust.ts +93 -0
  172. package/src/layout/sfdp/index.ts +4 -5
  173. package/src/layout/sfdp/init.ts +40 -4
  174. package/src/layout/sfdp/spring-driver.ts +30 -1
  175. package/src/layout/twopi/circle.ts +2 -1
  176. package/src/ortho/ortho-parallel.ts +2 -1
  177. package/src/ortho/trap-query.ts +2 -1
  178. package/src/parser/index.ts +11 -11
  179. package/src/render/index.ts +5 -0
  180. package/src/render/public.ts +24 -24
  181. package/src/render/svg.ts +1 -1
  182. package/src/render/xdot-public.ts +19 -18
  183. package/src/util/xml.ts +24 -30
  184. package/src/vpsc/Solver.ts +5 -3
@@ -0,0 +1,332 @@
1
+ // SPDX-License-Identifier: EPL-2.0
2
+
3
+ /**
4
+ * The Info_t machinery of neatogen's adjust.c shared by the Voronoi and
5
+ * oscale (`AM_SCALE`, "old scaling") adjusters: per-node polygons and site
6
+ * coordinates, the clipping bounding box, site sorting, coincident-node
7
+ * separation, the exact overlap count, and `sAdjust`. Voronoi-only functions
8
+ * (`vAdjust`, `newPos`, `addCorners`, `increaseBoundBox`, `nextOne`) are not
9
+ * ported.
10
+ *
11
+ * C keeps `nodeInfo`, `nsites`, `pxmin..pymax`, `xmin..deltax` as file
12
+ * statics and `state_t` for the rest; here they are one `InfoState` created
13
+ * per call (`newInfoState`). Units are inches, as in C. `Verbose` stderr
14
+ * output ("overlap [i] : n", "Number of iterations") is not reproduced: the
15
+ * port has no verbosity switch. `qsort` is unstable in C; `Array.sort` is
16
+ * stable, which only changes the order among sites with identical
17
+ * coordinates (all of which `rmEquality` treats as one group).
18
+ *
19
+ * @see lib/neatogen/adjust.c
20
+ */
21
+
22
+ import type { Graph } from '../../model/graph.js';
23
+ import type { Node } from '../../model/node.js';
24
+ import type { Point } from '../../model/geom.js';
25
+ import { aggetGraph } from '../fdp/fdp-model.js';
26
+ import { makeAddPoly, makePoly, polyOverlap } from './poly.js';
27
+ import type { Poly } from './poly.js';
28
+ import { sepFactor } from './sep-factor.js';
29
+
30
+ /** Default `voro_margin`. @see lib/neatogen/adjust.c:chkBoundBox */
31
+ const DEFAULT_VORO_MARGIN = 0.05;
32
+ /** Growth per oscale iteration. @see lib/neatogen/adjust.c:incr */
33
+ const INCR = 0.05;
34
+ /** @see lib/common/geom.h:POINTS_PER_INCH */
35
+ const POINTS_PER_INCH = 72;
36
+
37
+ /** @see lib/neatogen/site.h:Site */
38
+ export interface Site {
39
+ coord: Point;
40
+ sitenbr: number;
41
+ }
42
+
43
+ /** @see lib/neatogen/info.h:Info_t (Voronoi `verts` fields omitted) */
44
+ export interface InfoT {
45
+ site: Site;
46
+ overlaps: boolean;
47
+ poly: Poly;
48
+ node: Node;
49
+ }
50
+
51
+ /**
52
+ * `state_t` plus the adjust.c file statics (`nodeInfo`, `nsites`, `pxmin`…,
53
+ * `xmin`…). `nsites` is `nodeInfo.length`.
54
+ * @see lib/neatogen/adjust.c:state_t
55
+ */
56
+ export interface InfoState {
57
+ nodeInfo: InfoT[];
58
+ sites: Site[];
59
+ nw: Point;
60
+ ne: Point;
61
+ sw: Point;
62
+ se: Point;
63
+ pxmin: number;
64
+ pxmax: number;
65
+ pymin: number;
66
+ pymax: number;
67
+ xmin: number;
68
+ xmax: number;
69
+ ymin: number;
70
+ ymax: number;
71
+ deltax: number;
72
+ }
73
+
74
+ /** Fresh, empty state (`state_t st = {0}` and zeroed statics). */
75
+ export function newInfoState(): InfoState {
76
+ return {
77
+ nodeInfo: [], sites: [],
78
+ nw: { x: 0, y: 0 }, ne: { x: 0, y: 0 }, sw: { x: 0, y: 0 }, se: { x: 0, y: 0 },
79
+ pxmin: 0, pxmax: 0, pymin: 0, pymax: 0,
80
+ xmin: 0, xmax: 0, ymin: 0, ymax: 0, deltax: 0,
81
+ };
82
+ }
83
+
84
+ /** @see lib/neatogen/adjust.c:setBoundBox */
85
+ function setBoundBox(st: InfoState, ll: Point, ur: Point): void {
86
+ st.pxmin = ll.x;
87
+ st.pxmax = ur.x;
88
+ st.pymin = ll.y;
89
+ st.pymax = ur.y;
90
+ st.nw = { x: st.pxmin, y: st.pymax };
91
+ st.sw = { x: st.pxmin, y: st.pymin };
92
+ st.ne = { x: st.pxmax, y: st.pymax };
93
+ st.se = { x: st.pxmax, y: st.pymin };
94
+ }
95
+
96
+ /** C `atof` on the leading numeric prefix (0 when there is none). */
97
+ function atof(s: string): number {
98
+ const m = /^\s*[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?/.exec(s);
99
+ return m === null ? 0 : parseFloat(m[0]);
100
+ }
101
+
102
+ /**
103
+ * Extremes of the polygons, then the bounding box as a `voro_margin`
104
+ * (default 0.05) expansion of them. Requires at least one site.
105
+ * @see lib/neatogen/adjust.c:chkBoundBox
106
+ */
107
+ export function chkBoundBox(st: InfoState, graph: Graph): void {
108
+ let xMin = Number.MAX_VALUE;
109
+ let yMin = Number.MAX_VALUE;
110
+ let xMax = -Number.MAX_VALUE;
111
+ let yMax = -Number.MAX_VALUE;
112
+ for (const ip of st.nodeInfo) {
113
+ const pp = ip.poly;
114
+ xMin = Math.min(xMin, pp.origin.x + ip.site.coord.x);
115
+ yMin = Math.min(yMin, pp.origin.y + ip.site.coord.y);
116
+ xMax = Math.max(xMax, pp.corner.x + ip.site.coord.x);
117
+ yMax = Math.max(yMax, pp.corner.y + ip.site.coord.y);
118
+ }
119
+ const marg = aggetGraph(graph, 'voro_margin');
120
+ const margin = marg !== undefined && marg !== '' ? atof(marg) : DEFAULT_VORO_MARGIN;
121
+ const ydelta = margin * (yMax - yMin);
122
+ const xdelta = margin * (xMax - xMin);
123
+ setBoundBox(st, { x: xMin - xdelta, y: yMin - ydelta }, { x: xMax + xdelta, y: yMax + ydelta });
124
+ }
125
+
126
+ /**
127
+ * Create the Info_t array for every node of `graph` (polygons per
128
+ * `sepFactor`: additive margins use `makeAddPoly` in inches, scale margins
129
+ * use `makePoly`). Returns false (C: 1, `nodeInfo` freed) when a polygon
130
+ * cannot be made.
131
+ * @see lib/neatogen/adjust.c:makeInfo
132
+ */
133
+ export function makeInfo(st: InfoState, graph: Graph): boolean {
134
+ const pmargin = { ...sepFactor(graph) };
135
+ if (pmargin.doAdd) {
136
+ pmargin.x /= POINTS_PER_INCH;
137
+ pmargin.y /= POINTS_PER_INCH;
138
+ }
139
+ const polyf = pmargin.doAdd ? makeAddPoly : makePoly;
140
+ const info: InfoT[] = [];
141
+ for (const node of graph.nodes.values()) {
142
+ const poly = polyf(node, pmargin.x, pmargin.y);
143
+ if (poly === null) {
144
+ st.nodeInfo = [];
145
+ return false;
146
+ }
147
+ const pos = node.info.pos ?? [0, 0];
148
+ info.push({
149
+ site: { coord: { x: pos[0], y: pos[1] }, sitenbr: info.length },
150
+ overlaps: false, poly, node,
151
+ });
152
+ }
153
+ st.nodeInfo = info;
154
+ return true;
155
+ }
156
+
157
+ /** Sort sites on y, then x. @see lib/neatogen/adjust.c:scomp */
158
+ function scomp(s1: Site, s2: Site): number {
159
+ if (s1.coord.y < s2.coord.y) return -1;
160
+ if (s1.coord.y > s2.coord.y) return 1;
161
+ if (s1.coord.x < s2.coord.x) return -1;
162
+ if (s1.coord.x > s2.coord.x) return 1;
163
+ return 0;
164
+ }
165
+
166
+ /**
167
+ * Fill the site array from `nodeInfo` and sort it with `scomp`.
168
+ * @see lib/neatogen/adjust.c:sortSites
169
+ */
170
+ export function sortSites(st: InfoState): void {
171
+ st.sites = st.nodeInfo.map((ip) => ip.site);
172
+ st.sites.sort(scomp);
173
+ }
174
+
175
+ /** @see lib/neatogen/adjust.c:geomUpdate */
176
+ export function geomUpdate(st: InfoState, doSort: boolean): void {
177
+ if (doSort) sortSites(st);
178
+ st.xmin = Number.MAX_VALUE;
179
+ st.xmax = -Number.MAX_VALUE;
180
+ for (const s of st.sites) {
181
+ st.xmin = Math.min(st.xmin, s.coord.x);
182
+ st.xmax = Math.max(st.xmax, s.coord.x);
183
+ }
184
+ st.ymin = st.sites[0]!.coord.y;
185
+ st.ymax = st.sites[st.sites.length - 1]!.coord.y;
186
+ st.deltax = st.xmax - st.xmin;
187
+ }
188
+
189
+ /** Whether sites `a` and `b` share both coordinates. */
190
+ function sameCoord(a: Site, b: Site): boolean {
191
+ return a.coord.x === b.coord.x && a.coord.y === b.coord.y;
192
+ }
193
+
194
+ /** Spread a coincident run `[ip+1, kp)` evenly toward the site `kp` on its row. */
195
+ function spreadToRight(sites: Site[], ip: number, kp: number, cnt: number): void {
196
+ const xdel = (sites[kp]!.coord.x - sites[ip]!.coord.x) / cnt;
197
+ let i = 1;
198
+ for (let jp = ip + 1; jp < kp; jp++) {
199
+ sites[jp]!.coord.x += i * xdel;
200
+ i++;
201
+ }
202
+ }
203
+
204
+ /** Chain a coincident run `[ip+1, kp)` rightward by polygon widths. */
205
+ function chainByWidth(st: InfoState, ip: number, kp: number): void {
206
+ let a = ip;
207
+ for (let jp = ip + 1; jp < kp; a++, jp++) {
208
+ let info = st.nodeInfo[st.sites[a]!.sitenbr]!;
209
+ let xdel = info.poly.corner.x - info.poly.origin.x;
210
+ info = st.nodeInfo[st.sites[jp]!.sitenbr]!;
211
+ xdel += info.poly.corner.x - info.poly.origin.x;
212
+ st.sites[jp]!.coord.x = st.sites[a]!.coord.x + xdel / 2;
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Check for nodes with identical positions and tweak the positions.
218
+ * @see lib/neatogen/adjust.c:rmEquality
219
+ */
220
+ export function rmEquality(st: InfoState): void {
221
+ sortSites(st);
222
+ const sites = st.sites;
223
+ const end = sites.length;
224
+ let ip = 0;
225
+ while (ip < end) {
226
+ let jp = ip + 1;
227
+ if (jp >= end || !sameCoord(sites[jp]!, sites[ip]!)) {
228
+ ip = jp;
229
+ continue;
230
+ }
231
+ /* Find first node kp with position different from ip */
232
+ let cnt = 2;
233
+ let kp = jp + 1;
234
+ while (kp < end && sameCoord(sites[kp]!, sites[ip]!)) {
235
+ cnt++;
236
+ jp = kp;
237
+ kp = jp + 1;
238
+ }
239
+ /* If next node exists and is on the same line */
240
+ if (kp < end && sites[kp]!.coord.y === sites[ip]!.coord.y) {
241
+ spreadToRight(sites, ip, kp, cnt);
242
+ } else {
243
+ chainByWidth(st, ip, kp);
244
+ }
245
+ ip = kp;
246
+ }
247
+ }
248
+
249
+ /**
250
+ * Count number of node-node overlaps at iteration `iter` (exact polygon
251
+ * test), flagging the nodes involved.
252
+ * @see lib/neatogen/adjust.c:countOverlap
253
+ */
254
+ export function countOverlap(st: InfoState, _iter: number): number {
255
+ const info = st.nodeInfo;
256
+ let count = 0;
257
+ for (const ip of info) ip.overlaps = false;
258
+ for (let i = 0; i < info.length - 1; i++) {
259
+ const ip = info[i]!;
260
+ for (let j = i + 1; j < info.length; j++) {
261
+ const jp = info[j]!;
262
+ if (polyOverlap(ip.site.coord, ip.poly, jp.site.coord, jp.poly)) {
263
+ count++;
264
+ ip.overlaps = true;
265
+ jp.overlaps = true;
266
+ }
267
+ }
268
+ }
269
+ return count;
270
+ }
271
+
272
+ /** @see lib/neatogen/adjust.c:rePos */
273
+ export function rePos(st: InfoState): void {
274
+ const f = 1.0 + INCR;
275
+ for (const ip of st.nodeInfo) {
276
+ ip.site.coord.x *= f;
277
+ ip.site.coord.y *= f;
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Scale positions up by 5% until no polygons overlap. Returns 1 when
283
+ * positions changed, 0 when there was nothing to do.
284
+ * @see lib/neatogen/adjust.c:sAdjust
285
+ */
286
+ export function sAdjust(st: InfoState): number {
287
+ let iterCnt = 0;
288
+ if (countOverlap(st, iterCnt) === 0) return 0;
289
+ rmEquality(st);
290
+ for (;;) {
291
+ rePos(st);
292
+ iterCnt++;
293
+ if (countOverlap(st, iterCnt) === 0) break;
294
+ }
295
+ return 1;
296
+ }
297
+
298
+ /**
299
+ * Enter new node positions into the graph.
300
+ * @see lib/neatogen/adjust.c:updateGraph
301
+ */
302
+ export function updateGraph(st: InfoState): void {
303
+ for (const ip of st.nodeInfo) {
304
+ ip.node.info.pos = [ip.site.coord.x, ip.site.coord.y];
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Exact overlap count of the graph's nodes (`makeInfo` + `countOverlap(0)`);
310
+ * 0 when `makeInfo` fails.
311
+ * @see lib/neatogen/adjust.c:countOverlap
312
+ */
313
+ export function countOverlapIn(g: Graph): number {
314
+ const st = newInfoState();
315
+ if (!makeInfo(st, g)) return 0;
316
+ return countOverlap(st, 0);
317
+ }
318
+
319
+ /**
320
+ * The AM_SCALE branch of `removeOverlapWith`: `makeInfo`, `chkBoundBox`,
321
+ * `sAdjust`, and `updateGraph` when sAdjust moved nodes. Returns sAdjust's
322
+ * result (0 when `makeInfo` fails). Requires at least one node.
323
+ * @see lib/neatogen/adjust.c:removeOverlapWith
324
+ */
325
+ export function sAdjustGraph(g: Graph): number {
326
+ const st = newInfoState();
327
+ if (!makeInfo(st, g)) return 0;
328
+ chkBoundBox(st, g);
329
+ const ret = sAdjust(st);
330
+ if (ret !== 0) updateGraph(st);
331
+ return ret;
332
+ }
@@ -35,6 +35,7 @@
35
35
  */
36
36
 
37
37
  import { incircle, orient2d } from './delaunay.js';
38
+ import { InternalError } from '../../errors.js';
38
39
 
39
40
  /** Triangulated surface. @see lib/neatogen/delaunay.h:surface_t */
40
41
  export interface Surface {
@@ -164,7 +165,7 @@ class Cdt {
164
165
  let i = 0;
165
166
  if (e !== null) {
166
167
  i = f.e.indexOf(e);
167
- if (i < 0) throw new Error('cdt: edge not in face');
168
+ if (i < 0) throw new InternalError('cdt: edge not in face');
168
169
  }
169
170
  const j = (i + 1) % 3;
170
171
  const k = (i + 2) % 3;
@@ -395,10 +396,12 @@ class Cdt {
395
396
  }
396
397
 
397
398
  /** gts_delaunay_add_vertex. Returns null on success, the duplicate
398
- * vertex id for coincident points, or throws if outside the hull. */
399
+ * vertex id for coincident points, or `v` itself when `v` lies outside
400
+ * the hull: C does not add it and the caller (tri) carries on.
401
+ * @see gts-0.7.6/src/cdt.c:668-680 gts_delaunay_add_vertex */
399
402
  addVertex(v: number): number | null {
400
403
  const f = this.pointLocate(this.px[v]!, this.py[v]!);
401
- if (f === null) throw new Error('cdt: vertex outside triangulation hull');
404
+ if (f === null) return v;
402
405
  return this.addVertexToFace(v, f);
403
406
  }
404
407
 
@@ -476,48 +479,59 @@ class Cdt {
476
479
  }
477
480
  }
478
481
 
479
- /** @see cdt.c:remove_intersected_edge (incl. the NEXT_CUT macro) */
482
+ /**
483
+ * Returns the constraints the walk crossed and removed (C's returned
484
+ * GSList): a crossed constraint edge is collected, kept even when it loses
485
+ * all its triangles, and the walk continues through it.
486
+ * @see cdt.c:remove_intersected_edge (incl. the NEXT_CUT macro)
487
+ */
480
488
  private removeIntersectedEdge(
481
489
  sv1: number, sv2: number, e: CEdge, f: CFace,
482
490
  left: CEdge[], right: CEdge[],
483
- ): void {
484
- if (e.constraint) throw new Error('cdt: constraint edges cross');
491
+ ): CEdge[] {
492
+ const constraint: CEdge[] = e.constraint ? [e] : [];
485
493
 
486
494
  const r = this.ringFrom(f, e);
487
495
  const v1 = r.v1; const v2 = r.v2; const v3 = r.v3;
488
496
  const e1 = r.e2; const e2 = r.e3; // C shadows: e←r.e1, e1←r.e2, e2←r.e3
489
- const cut = (edge: CEdge, edge1: CEdge, list: CEdge[]): void => {
497
+ const cut = (edge: CEdge, edge1: CEdge, list: CEdge[]): CEdge[] => {
490
498
  const next = this.neighbor(f, edge);
491
499
  this.removeTriangles(e);
492
- if (e.tris.length === 0) this.destroyEdge(e);
493
- if (next === null) throw new Error('cdt: constraint walk left the surface');
500
+ if (constraint.length === 0 && e.tris.length === 0) this.destroyEdge(e);
501
+ if (next === null) throw new InternalError('cdt: constraint walk left the surface');
494
502
  list.push(edge1);
495
- this.removeIntersectedEdge(sv1, sv2, edge, next, left, right);
503
+ return constraint.concat(
504
+ this.removeIntersectedEdge(sv1, sv2, edge, next, left, right));
496
505
  };
497
506
 
498
507
  const o1 = this.orient(v2, v3, sv2);
499
508
  const o2 = this.orient(v3, v1, sv2);
500
509
  if (o1 === 0) {
501
510
  // terminal: s.v2 is v3
511
+ if (o2 !== 0) throw new InternalError('cdt: remove_intersected_edge: o2 != 0 at terminal'); // cdt.c:887 g_assert
502
512
  this.removeTriangles(e);
503
- if (e.tris.length === 0) this.destroyEdge(e);
513
+ if (constraint.length === 0 && e.tris.length === 0) this.destroyEdge(e);
504
514
  left.push(e2);
505
515
  right.push(e1);
506
- } else if (o1 > 0) {
507
- cut(e2, e1, right);
508
- } else if (o2 >= 0) {
509
- cut(e1, e2, left);
510
- } else {
511
- const o3 = this.orient(sv1, sv2, v3);
512
- if (o3 > 0) cut(e1, e2, left);
513
- else cut(e2, e1, right);
516
+ return constraint;
517
+ }
518
+ if (o1 > 0) {
519
+ if (o2 > 0) throw new InternalError('cdt: remove_intersected_edge: o2 > 0 with o1 > 0'); // cdt.c:895 g_assert
520
+ return cut(e2, e1, right);
514
521
  }
522
+ if (o2 >= 0) return cut(e1, e2, left);
523
+ const o3 = this.orient(sv1, sv2, v3);
524
+ return o3 > 0 ? cut(e1, e2, left) : cut(e2, e1, right);
515
525
  }
516
526
 
517
- /** @see cdt.c:remove_intersected_vertex */
527
+ /**
528
+ * Returns the reference face (C's `*ref` out-param) and the constraints the
529
+ * walk crossed and removed (C's returned GSList).
530
+ * @see cdt.c:remove_intersected_vertex
531
+ */
518
532
  private removeIntersectedVertex(
519
533
  sv1: number, sv2: number, left: CEdge[], right: CEdge[],
520
- ): CFace {
534
+ ): { ref: CFace; constraints: CEdge[] } {
521
535
  // triangles around sv1 in the surface
522
536
  for (const t of this.surface) {
523
537
  if (t.v[0] !== sv1 && t.v[1] !== sv1 && t.v[2] !== sv1) continue;
@@ -538,25 +552,30 @@ class Cdt {
538
552
  const e2 = rr.e2; // v3–sv1 side
539
553
  const e1 = rr.e3; // sv1–v2 side
540
554
 
541
- if (o3 >= 0) return t; // s.v2 inside (or on far edge of) t — nothing to remove
555
+ // s.v2 inside (or on far edge of) t — nothing to remove
556
+ if (o3 >= 0) return { ref: t, constraints: [] };
542
557
 
543
558
  // remove t but keep it floating as the attribute reference
544
559
  this.removeFace(t, true);
545
560
  left.push(e2);
546
561
  right.push(e1);
547
- if (next === null) throw new Error('cdt: constraint walk left the surface');
548
- this.removeIntersectedEdge(sv1, sv2, e, next, left, right);
549
- return t;
562
+ if (next === null) throw new InternalError('cdt: constraint walk left the surface');
563
+ const constraints = this.removeIntersectedEdge(sv1, sv2, e, next, left, right);
564
+ return { ref: t, constraints };
550
565
  }
551
- throw new Error('cdt: no wedge triangle found at constraint endpoint');
566
+ throw new InternalError('cdt: no wedge triangle found at constraint endpoint');
552
567
  }
553
568
 
554
- /** @see cdt.c:gts_delaunay_add_constraint */
555
- addConstraint(c: CEdge): void {
569
+ /**
570
+ * Returns the constraints crossing `c` that were removed (C's return
571
+ * value; tri() ignores it).
572
+ * @see cdt.c:gts_delaunay_add_constraint
573
+ */
574
+ addConstraint(c: CEdge): CEdge[] {
556
575
  const left: CEdge[] = [];
557
576
  const right: CEdge[] = [];
558
- const ref = this.removeIntersectedVertex(c.v1, c.v2, left, right);
559
- if (ref.inSurface) return; // constraint already realized (o3 >= 0 path)
577
+ const { ref, constraints } = this.removeIntersectedVertex(c.v1, c.v2, left, right);
578
+ if (ref.inSurface) return constraints; // already realized (o3 >= 0 path)
560
579
 
561
580
  // C: triangulate_polygon(prepend(reverse(right), c)) then
562
581
  // triangulate_polygon(prepend(left, c)).
@@ -576,6 +595,7 @@ class Cdt {
576
595
  this.triangulatePolygon([c, ...[...left].reverse()]);
577
596
  // ref face was kept floating; destroy it now (detach from edges)
578
597
  this.detachFace(ref);
598
+ return constraints;
579
599
  }
580
600
 
581
601
  // -------------------------------------------------------------------