@knowvah/dot-engine 2.3.0 → 2.4.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.
- package/dist/common/arith.d.ts +12 -0
- package/dist/common/arith.d.ts.map +1 -1
- package/dist/common/edge-in-box.d.ts +31 -0
- package/dist/common/edge-in-box.d.ts.map +1 -0
- package/dist/common/htmltable-emit.d.ts +1 -1
- package/dist/common/htmltable-emit.d.ts.map +1 -1
- package/dist/common/make-label.d.ts +3 -1
- package/dist/common/make-label.d.ts.map +1 -1
- package/dist/common/poly-gencode.d.ts +12 -1
- package/dist/common/poly-gencode.d.ts.map +1 -1
- package/dist/gvc/device.d.ts.map +1 -1
- package/dist/gvc/emit-gates.d.ts +35 -0
- package/dist/gvc/emit-gates.d.ts.map +1 -0
- package/dist/gvc/job.d.ts +4 -0
- package/dist/gvc/job.d.ts.map +1 -1
- package/dist/gvc/viewport.d.ts +13 -0
- package/dist/gvc/viewport.d.ts.map +1 -1
- package/dist/index.js +1030 -742
- package/dist/index.js.map +4 -4
- package/dist/layout/circo/blockpath.d.ts +1 -38
- package/dist/layout/circo/blockpath.d.ts.map +1 -1
- package/dist/layout/circo/crossings.d.ts +59 -0
- package/dist/layout/circo/crossings.d.ts.map +1 -0
- package/dist/layout/dot/edge-route-chain.d.ts +5 -3
- package/dist/layout/dot/edge-route-chain.d.ts.map +1 -1
- package/dist/layout/dot/edge-route.d.ts.map +1 -1
- package/dist/layout/dot/index.d.ts +10 -2
- package/dist/layout/dot/index.d.ts.map +1 -1
- package/dist/layout/dot/ortho-adapter.d.ts +7 -3
- package/dist/layout/dot/ortho-adapter.d.ts.map +1 -1
- package/dist/layout/dot/pack-components.d.ts +4 -1
- package/dist/layout/dot/pack-components.d.ts.map +1 -1
- package/dist/layout/dot/splines-flat-multi.d.ts +7 -5
- package/dist/layout/dot/splines-flat-multi.d.ts.map +1 -1
- package/dist/layout/dot/splines-flat.d.ts +2 -13
- package/dist/layout/dot/splines-flat.d.ts.map +1 -1
- package/dist/layout/dot/splines-route.d.ts.map +1 -1
- package/dist/layout/neato/constraint-adjust.d.ts +32 -0
- package/dist/layout/neato/constraint-adjust.d.ts.map +1 -1
- package/dist/layout/neato/multispline.d.ts.map +1 -1
- package/dist/layout/neato/splines.d.ts.map +1 -1
- package/dist/layout/pack/poly-place.d.ts.map +1 -1
- package/dist/layout/sfdp/__fixtures__/pcp-rotate-native.d.ts +23 -0
- package/dist/layout/sfdp/__fixtures__/pcp-rotate-native.d.ts.map +1 -0
- package/dist/layout/sfdp/spring-driver.d.ts +6 -0
- package/dist/layout/sfdp/spring-driver.d.ts.map +1 -1
- package/dist/ortho/index.d.ts +4 -1
- package/dist/ortho/index.d.ts.map +1 -1
- package/dist/ortho/maze.d.ts +5 -2
- package/dist/ortho/maze.d.ts.map +1 -1
- package/dist/ortho/partition.d.ts +8 -1
- package/dist/ortho/partition.d.ts.map +1 -1
- package/dist/ortho/trap-segment.d.ts +2 -1
- package/dist/ortho/trap-segment.d.ts.map +1 -1
- package/dist/ortho/trapezoid.d.ts.map +1 -1
- package/dist/parser/index.d.ts +0 -7
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/parser/number-scan.d.ts +31 -0
- package/dist/parser/number-scan.d.ts.map +1 -0
- package/dist/pathplan/route.d.ts.map +1 -1
- package/dist/pathplan/solvers.d.ts +15 -3
- package/dist/pathplan/solvers.d.ts.map +1 -1
- package/dist/render/dot/edge-draw.d.ts +12 -0
- package/dist/render/dot/edge-draw.d.ts.map +1 -1
- package/dist/render/svg-graph.d.ts +6 -5
- package/dist/render/svg-graph.d.ts.map +1 -1
- package/dist/render/svg.d.ts +2 -21
- package/dist/render/svg.d.ts.map +1 -1
- package/dist/render.js +1030 -742
- package/dist/render.js.map +4 -4
- package/dist/xdot/parse.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/common/arith.ts +25 -0
- package/src/common/edge-in-box.ts +54 -0
- package/src/common/graph-init.ts +3 -3
- package/src/common/htmltable-emit.ts +4 -2
- package/src/common/make-label.ts +3 -1
- package/src/common/poly-gencode.ts +18 -4
- package/src/gvc/device.ts +8 -41
- package/src/gvc/edge-labels.ts +30 -13
- package/src/gvc/emit-gates.ts +89 -0
- package/src/gvc/job.ts +4 -0
- package/src/gvc/viewport.ts +29 -0
- package/src/layout/circo/blockpath.ts +6 -125
- package/src/layout/circo/crossings.ts +171 -0
- package/src/layout/dot/__fixtures__/2784.dot +1 -0
- package/src/layout/dot/__fixtures__/2784.native-16.1.svg +48 -0
- package/src/layout/dot/edge-route-chain.ts +11 -5
- package/src/layout/dot/edge-route.ts +19 -8
- package/src/layout/dot/index.ts +14 -4
- package/src/layout/dot/ortho-adapter.ts +11 -6
- package/src/layout/dot/pack-components.ts +11 -4
- package/src/layout/dot/splines-flat-multi.ts +21 -8
- package/src/layout/dot/splines-flat.ts +8 -35
- package/src/layout/dot/splines-route.ts +10 -2
- package/src/layout/dot/splines.ts +6 -6
- package/src/layout/neato/constraint-adjust.ts +10 -4
- package/src/layout/neato/multispline.ts +26 -9
- package/src/layout/neato/splines.ts +7 -7
- package/src/layout/pack/poly-place.ts +9 -2
- package/src/layout/sfdp/__fixtures__/pcp-rotate-native.ts +94 -0
- package/src/layout/sfdp/spring-driver.ts +45 -13
- package/src/ortho/index.ts +22 -13
- package/src/ortho/maze.ts +8 -2
- package/src/ortho/partition.ts +17 -2
- package/src/ortho/trap-segment.ts +47 -24
- package/src/ortho/trapezoid.ts +24 -15
- package/src/parser/dot.js +345 -380
- package/src/parser/dot.pegjs +25 -10
- package/src/parser/index.ts +15 -0
- package/src/parser/number-scan.ts +118 -0
- package/src/pathplan/route.ts +79 -34
- package/src/pathplan/solvers.ts +78 -50
- package/src/render/dot/edge-draw.ts +25 -0
- package/src/render/svg-graph.ts +77 -47
- package/src/render/svg-helpers.ts +2 -2
- package/src/render/svg.ts +4 -41
- package/src/xdot/parse.ts +19 -5
package/src/render/svg-graph.ts
CHANGED
|
@@ -16,12 +16,12 @@ import type { Box, Point } from '../model/geom.js';
|
|
|
16
16
|
import type { RenderJob } from '../gvc/job.js';
|
|
17
17
|
import { createObjState, ObjType } from '../gvc/job.js';
|
|
18
18
|
import { FillType } from '../gvc/context.js';
|
|
19
|
-
import { escapeXml, escapeXmlTitle } from './svg-helpers.js';
|
|
19
|
+
import { escapeXml, escapeXmlTitle, svgComment } from './svg-helpers.js';
|
|
20
20
|
import { resolveRenderColor, colorPaint } from './color-resolve.js';
|
|
21
21
|
import { svgGraphId, svgGraphClass } from './svg-id.js';
|
|
22
22
|
import { parseGradientSpec } from '../common/htmltable-emit-fill.js';
|
|
23
23
|
import { findStopColor, parseStyleFlags } from '../common/style-resolve.js';
|
|
24
|
-
import {
|
|
24
|
+
import { croundNarrow32 } from '../common/arith.js';
|
|
25
25
|
import { FontnameKind } from '../model/layoutParams.js';
|
|
26
26
|
import {
|
|
27
27
|
emitLinearGradient,
|
|
@@ -66,7 +66,7 @@ function formatG(n: number): string {
|
|
|
66
66
|
* (svgBeginPage). ViewBox starts at (round(margin.x), round(margin.y)) --
|
|
67
67
|
* (0,0) only in the common no-margin case.
|
|
68
68
|
*/
|
|
69
|
-
export function emitSvgTag(job: RenderJob): void {
|
|
69
|
+
export function emitSvgTag(job: RenderJob, svgid: string | undefined = ''): void {
|
|
70
70
|
const bb = job.bb;
|
|
71
71
|
// Size-fitted, UNROUNDED page-oriented view extent (init_job_pagination's
|
|
72
72
|
// local `imageSize`/`pageSize`, emit.c:1198-1246): sz = bb extent plus pad,
|
|
@@ -87,8 +87,11 @@ export function emitSvgTag(job: RenderJob): void {
|
|
|
87
87
|
const pageX = rotated ? viewY : viewX;
|
|
88
88
|
const pageY = rotated ? viewX : viewY;
|
|
89
89
|
// job->width/job->height (emit.c:1249-1250, dpi=72 so *dpi/72 is a no-op).
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
// The native build vectorises these ROUNDs (fcvtzs.2d + xtn: low 32 bits of
|
|
91
|
+
// an int64), so an out-of-range page size wraps exactly as native prints it
|
|
92
|
+
// (2784: 2.16e18 -> 1255669760). @see croundNarrow32
|
|
93
|
+
const w = croundNarrow32(pageX + 2 * job.margin.x);
|
|
94
|
+
const h = croundNarrow32(pageY + 2 * job.margin.y);
|
|
92
95
|
job.write('<svg width="' + String(w) + 'pt" height="' + String(h) + 'pt"\n');
|
|
93
96
|
// job->pageBoundingBox (canvasBox.LL = margin+centering, canvasBox.UR = LL +
|
|
94
97
|
// imageSize, then ROUND -- emit.c:1282-1293). C's rotation-branch double
|
|
@@ -96,14 +99,20 @@ export function emitSvgTag(job: RenderJob): void {
|
|
|
96
99
|
// swap-back, emit.c:1275-1298) cancels for LL (always plain margin) but NOT
|
|
97
100
|
// for UR, which ends up paired with the page-oriented (rotation-swapped)
|
|
98
101
|
// view extent -- i.e. the same `pageX`/`pageY` used for width/height above.
|
|
99
|
-
const vbLLx =
|
|
100
|
-
const vbLLy =
|
|
101
|
-
const vbURx =
|
|
102
|
-
const vbURy =
|
|
102
|
+
const vbLLx = croundNarrow32(job.margin.x);
|
|
103
|
+
const vbLLy = croundNarrow32(job.margin.y);
|
|
104
|
+
const vbURx = croundNarrow32(job.margin.x + pageX);
|
|
105
|
+
const vbURy = croundNarrow32(job.margin.y + pageY);
|
|
103
106
|
job.write(' viewBox="' + String(vbLLx) + '.00 ' + String(vbLLy) + '.00 ' +
|
|
104
107
|
String(vbURx) + '.00 ' + String(vbURy) + '.00"\n');
|
|
105
108
|
job.write(' xmlns="http://www.w3.org/2000/svg"');
|
|
106
|
-
job.write(' xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
109
|
+
job.write(' xmlns:xlink="http://www.w3.org/1999/xlink"');
|
|
110
|
+
// graphviz 16.1: `svgid` graph attribute -> id on the root <svg> (gvputs_xml).
|
|
111
|
+
// @see plugin/core/gvrender_core_svg.c:svg_begin_graph
|
|
112
|
+
if (svgid !== undefined && svgid.length > 0) {
|
|
113
|
+
job.write(' id="' + escapeXmlTitle(svgid) + '"');
|
|
114
|
+
}
|
|
115
|
+
job.write('>\n');
|
|
107
116
|
}
|
|
108
117
|
|
|
109
118
|
/**
|
|
@@ -175,22 +184,23 @@ export function resolveGraphBgcolor(bgcolorAttr: string | undefined): string {
|
|
|
175
184
|
}
|
|
176
185
|
|
|
177
186
|
/**
|
|
178
|
-
* Emit background polygon covering the
|
|
187
|
+
* Emit background polygon covering the page clip box (C gvrender_box over
|
|
188
|
+
* job->clip — bb ± pad as setup_page evaluates it, see setupPageClip).
|
|
179
189
|
*
|
|
180
190
|
* Omits the polygon entirely when resolvedColor is the transparent sentinel.
|
|
181
191
|
* Coordinates are in the group's local frame (y-flipped via devscale.y=-1):
|
|
182
|
-
* x: [
|
|
183
|
-
* y (flipped): [-
|
|
192
|
+
* x: [clip.ll.x, clip.ur.x]
|
|
193
|
+
* y (flipped): [-clip.ur.y, -clip.ll.y]
|
|
184
194
|
*
|
|
185
195
|
* @see plugin/core/gvrender_core_svg.c:svg_begin_page
|
|
186
196
|
* @see lib/common/emit.c:emit_background:1476
|
|
187
197
|
*/
|
|
188
|
-
export function emitGraphBackground(
|
|
198
|
+
export function emitGraphBackground(clip: Box, resolvedColor: string, job: RenderJob): void {
|
|
189
199
|
if (resolvedColor === BGCOLOR_TRANSPARENT) return;
|
|
190
|
-
const left =
|
|
191
|
-
const right =
|
|
192
|
-
const top = -
|
|
193
|
-
const bottom = -
|
|
200
|
+
const left = clip.ll.x;
|
|
201
|
+
const right = clip.ur.x;
|
|
202
|
+
const top = -clip.ur.y;
|
|
203
|
+
const bottom = -clip.ll.y;
|
|
194
204
|
const corners: [number, number][] = [
|
|
195
205
|
[left, bottom], [left, top], [right, top], [right, bottom], [left, bottom],
|
|
196
206
|
];
|
|
@@ -209,11 +219,11 @@ export function emitGraphBackground(bb: Box, resolvedColor: string, job: RenderJ
|
|
|
209
219
|
* Passed to getGradientPoints inside emitLinearGradient.
|
|
210
220
|
* @see lib/common/emit.c:emit_background:1517 (gvrender_box with clip)
|
|
211
221
|
*/
|
|
212
|
-
function bgGradientPts(
|
|
213
|
-
const left =
|
|
214
|
-
const right =
|
|
215
|
-
const bottom =
|
|
216
|
-
const top =
|
|
222
|
+
function bgGradientPts(clip: Box): Point[] {
|
|
223
|
+
const left = clip.ll.x;
|
|
224
|
+
const right = clip.ur.x;
|
|
225
|
+
const bottom = clip.ll.y;
|
|
226
|
+
const top = clip.ur.y;
|
|
217
227
|
return [
|
|
218
228
|
{ x: left, y: bottom },
|
|
219
229
|
{ x: left, y: top },
|
|
@@ -227,11 +237,11 @@ function bgGradientPts(bb: Box, pad: Point): Point[] {
|
|
|
227
237
|
* Coords identical to emitGraphBackground; only fill changes.
|
|
228
238
|
* @see plugin/core/gvrender_core_svg.c:svg_polygon:686
|
|
229
239
|
*/
|
|
230
|
-
function writeGradientPolygon(
|
|
231
|
-
const left =
|
|
232
|
-
const right =
|
|
233
|
-
const top = -
|
|
234
|
-
const bottom = -
|
|
240
|
+
function writeGradientPolygon(clip: Box, id: string, job: RenderJob): void {
|
|
241
|
+
const left = clip.ll.x;
|
|
242
|
+
const right = clip.ur.x;
|
|
243
|
+
const top = -clip.ur.y;
|
|
244
|
+
const bottom = -clip.ll.y;
|
|
235
245
|
const corners: [number, number][] = [
|
|
236
246
|
[left, bottom], [left, top], [right, top], [right, bottom], [left, bottom],
|
|
237
247
|
];
|
|
@@ -256,7 +266,7 @@ function writeGradientPolygon(bb: Box, id: string, job: RenderJob): void {
|
|
|
256
266
|
* @see plugin/core/gvrender_core_svg.c:svg_gradstyle:572
|
|
257
267
|
* @see plugin/core/gvrender_core_svg.c:svg_rgradstyle:608
|
|
258
268
|
*/
|
|
259
|
-
function emitGradientBackground(
|
|
269
|
+
function emitGradientBackground(clip: Box, spec: BgGradientSpec, job: RenderJob): void {
|
|
260
270
|
const obj = createObjState(ObjType.RootGraph);
|
|
261
271
|
obj.id = 'graph0';
|
|
262
272
|
obj.fillColor = resolveRenderColor(spec.fillColor);
|
|
@@ -271,10 +281,10 @@ function emitGradientBackground(bb: Box, spec: BgGradientSpec, job: RenderJob):
|
|
|
271
281
|
emitRadialGradient(job, gid);
|
|
272
282
|
} else {
|
|
273
283
|
gid = gradientId('graph0', 'l', job.linearGradId++);
|
|
274
|
-
emitLinearGradient(job, bgGradientPts(
|
|
284
|
+
emitLinearGradient(job, bgGradientPts(clip), gid);
|
|
275
285
|
}
|
|
276
286
|
job.popObj();
|
|
277
|
-
writeGradientPolygon(
|
|
287
|
+
writeGradientPolygon(clip, gid, job);
|
|
278
288
|
}
|
|
279
289
|
|
|
280
290
|
/**
|
|
@@ -296,17 +306,17 @@ function resolveBgGradientSpec(g: Graph, bgcolorAttr: string): BgGradientSpec |
|
|
|
296
306
|
* Separated to keep svgBeginGraph under the 30-line body limit.
|
|
297
307
|
* @see lib/common/emit.c:emit_background:1476
|
|
298
308
|
*/
|
|
299
|
-
function emitBgcolorBackground(g: Graph,
|
|
309
|
+
function emitBgcolorBackground(g: Graph, clip: Box, bgcolorAttr: string | undefined, job: RenderJob): void {
|
|
300
310
|
if (!bgcolorAttr || bgcolorAttr.length === 0 || bgcolorAttr === 'transparent') {
|
|
301
|
-
emitGraphBackground(
|
|
311
|
+
emitGraphBackground(clip, resolveGraphBgcolor(bgcolorAttr), job);
|
|
302
312
|
return;
|
|
303
313
|
}
|
|
304
314
|
const spec = resolveBgGradientSpec(g, bgcolorAttr);
|
|
305
315
|
if (spec !== null) {
|
|
306
|
-
emitGradientBackground(
|
|
316
|
+
emitGradientBackground(clip, spec, job);
|
|
307
317
|
return;
|
|
308
318
|
}
|
|
309
|
-
emitGraphBackground(
|
|
319
|
+
emitGraphBackground(clip, resolveGraphBgcolor(bgcolorAttr), job);
|
|
310
320
|
}
|
|
311
321
|
|
|
312
322
|
/**
|
|
@@ -344,7 +354,25 @@ export function svgBeginGraph(g: Graph, job: RenderJob): void {
|
|
|
344
354
|
}
|
|
345
355
|
job.write(SVG_DOCTYPE);
|
|
346
356
|
job.write(SVG_GENERATOR_COMMENT);
|
|
347
|
-
|
|
357
|
+
// emit_graph: the root `comment` attribute, between svg_begin_job and
|
|
358
|
+
// emit_begin_graph. @see lib/common/emit.c:emit_graph
|
|
359
|
+
const comment = g.attrs.get('comment');
|
|
360
|
+
if (comment !== undefined && comment.length > 0) svgComment(comment, job);
|
|
361
|
+
emitTitlePagesComment(g, job);
|
|
362
|
+
emitSvgTag(job, g.attrs.get('svgid'));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* svg_begin_graph's header comment: ` Title: <name>` for a user-named graph,
|
|
367
|
+
* then the page count (always 1 — the port emits a single page).
|
|
368
|
+
* @see plugin/core/gvrender_core_svg.c:svg_begin_graph
|
|
369
|
+
*/
|
|
370
|
+
function emitTitlePagesComment(g: Graph, job: RenderJob): void {
|
|
371
|
+
job.write('<!--');
|
|
372
|
+
if (g.name.length > 0 && g.name[0] !== '%') {
|
|
373
|
+
job.write(' Title: ' + escapeXmlTitle(g.name));
|
|
374
|
+
}
|
|
375
|
+
job.write(' Pages: 1 -->\n');
|
|
348
376
|
}
|
|
349
377
|
|
|
350
378
|
/**
|
|
@@ -353,14 +381,16 @@ export function svgBeginGraph(g: Graph, job: RenderJob): void {
|
|
|
353
381
|
* @see plugin/core/gvrender_core_svg.c:svg_begin_page
|
|
354
382
|
*/
|
|
355
383
|
export function svgBeginPage(g: Graph, job: RenderJob): void {
|
|
356
|
-
const
|
|
357
|
-
//
|
|
384
|
+
const clip = job.clip;
|
|
385
|
+
// Portrait: translation.x = -clip.LL.x, with job->clip as setup_page builds
|
|
386
|
+
// it (setupPageClip: focus ∓ pageSize/2, i.e. bb ± pad up to rounding).
|
|
387
|
+
// gvPostprocess normally zeroes bb.ll, but a dot layout failure skips it
|
|
388
|
+
// (graphviz 16.1, corpus 2784: translate(31 ...)).
|
|
358
389
|
// Landscape (ADR-3): setup_page's rotation branch (SVG = GVRENDER_Y_GOES_DOWN)
|
|
359
|
-
// gives translation.x = -
|
|
360
|
-
//
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
// translate(-634 208.5). @see emit.c:setup_page; gvrender_core_svg.c:svg_begin_page
|
|
390
|
+
// gives translation.x = -clip.UR.x; translation.y = -clip.UR.y in both
|
|
391
|
+
// branches (printed negated). pageSize = imageSize/zoom is Z-independent
|
|
392
|
+
// (outer scale carries Z). Pinned to b68 native translate(-634 208.5).
|
|
393
|
+
// @see emit.c:setup_page; gvrender_core_svg.c:svg_begin_page
|
|
364
394
|
//
|
|
365
395
|
// margin (F6): setup_page adds `canvasBox.LL / job->zoom` on top of the
|
|
366
396
|
// pad-only formula above -- canvasBox.LL is the FINAL (post swap-cancel)
|
|
@@ -374,9 +404,9 @@ export function svgBeginPage(g: Graph, job: RenderJob): void {
|
|
|
374
404
|
// @see lib/common/emit.c:1566-1582 setup_page (CAUTION comment there)
|
|
375
405
|
const z = job.scale.x;
|
|
376
406
|
const tx = job.rotation !== 0
|
|
377
|
-
? -
|
|
378
|
-
:
|
|
379
|
-
const ty =
|
|
407
|
+
? -clip.ur.x - job.margin.x / z
|
|
408
|
+
: -clip.ll.x + job.margin.x / z;
|
|
409
|
+
const ty = clip.ur.y + job.margin.y / z;
|
|
380
410
|
emitGraphGroupOpen(
|
|
381
411
|
job.idLayerPrefix() + svgGraphId(g, job),
|
|
382
412
|
svgGraphClass(g),
|
|
@@ -393,7 +423,7 @@ export function svgBeginPage(g: Graph, job: RenderJob): void {
|
|
|
393
423
|
* @see lib/common/emit.c:emit_page (emit_background after begin_page)
|
|
394
424
|
*/
|
|
395
425
|
export function svgPageBackground(g: Graph, job: RenderJob): void {
|
|
396
|
-
emitBgcolorBackground(g, job.
|
|
426
|
+
emitBgcolorBackground(g, job.clip, g.attrs.get('bgcolor'), job);
|
|
397
427
|
}
|
|
398
428
|
|
|
399
429
|
/** Close the graph `<g>` group. @see svg_end_page */
|
|
@@ -421,8 +421,8 @@ export function svgPolyline(pts: Point[], job: RenderJob): void {
|
|
|
421
421
|
job.write('"/>\n');
|
|
422
422
|
}
|
|
423
423
|
|
|
424
|
-
export function svgComment(text: string, job: RenderJob): void {
|
|
425
|
-
job.write('<!-- ' +
|
|
424
|
+
export function svgComment(text: string, job: RenderJob): void { // svg_comment: gvputs_xml
|
|
425
|
+
job.write('<!-- ' + escapeXmlTitle(text) + ' -->\n');
|
|
426
426
|
}
|
|
427
427
|
|
|
428
428
|
// ---------------------------------------------------------------------------
|
package/src/render/svg.ts
CHANGED
|
@@ -20,13 +20,12 @@ import type { Graph } from '../model/graph.js';
|
|
|
20
20
|
import type { Node } from '../model/node.js';
|
|
21
21
|
import type { Edge } from '../model/edge.js';
|
|
22
22
|
import type { Point, Box } from '../model/geom.js';
|
|
23
|
-
import { boxOverlap } from '../model/geom.js';
|
|
24
|
-
import type { TextlabelT } from '../common/types.js';
|
|
25
23
|
import type { TextSpan } from '../common/emit-types.js';
|
|
26
24
|
import type { RendererPlugin, LabelType } from '../gvc/context.js';
|
|
27
25
|
import type { RenderJob } from '../gvc/job.js';
|
|
28
26
|
import { renderEdgeLabels } from '../gvc/edge-labels.js';
|
|
29
27
|
import { beginAnchorIf } from '../gvc/anchor.js';
|
|
28
|
+
import { edgeHasDrawableContent } from '../common/edge-in-box.js';
|
|
30
29
|
import {
|
|
31
30
|
svgBeginGraph,
|
|
32
31
|
svgEndGraph,
|
|
@@ -86,45 +85,9 @@ function emitMulticolorArrows(e: Edge, job: RenderJob, headColor: string, tailCo
|
|
|
86
85
|
// @see plugin/core/gvrender_core_svg.c:svg_engine
|
|
87
86
|
// ---------------------------------------------------------------------------
|
|
88
87
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const sx = lp.dimen.x / 2;
|
|
93
|
-
const sy = lp.dimen.y / 2;
|
|
94
|
-
const bb: Box = {
|
|
95
|
-
ll: { x: lp.pos.x - sx, y: lp.pos.y - sy },
|
|
96
|
-
ur: { x: lp.pos.x + sx, y: lp.pos.y + sy },
|
|
97
|
-
};
|
|
98
|
-
return boxOverlap(b, bb);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Faithful-to-outcome `edge_in_box`: true when the edge has something to draw.
|
|
103
|
-
* C's gvrender defers the `<g>` until a draw op, so an edge whose content falls
|
|
104
|
-
* entirely outside the clip emits no group (emit.c:edge_in_box tests each piece
|
|
105
|
-
* vs job->clip via boxf_overlap/overlap_label).
|
|
106
|
-
*
|
|
107
|
-
* The only piece that can legitimately fall OUTSIDE the clip on this corpus is a
|
|
108
|
-
* degenerate labeled-flat label: it is spline-less, so map_edge leaves it at its
|
|
109
|
-
* internal (un-normalized) x-frame position while the clip is the final frame.
|
|
110
|
-
* The `overlapLabel` test on the main label reproduces C's draw-iff-overlap for
|
|
111
|
-
* exactly that case — on-canvas (2368) → drawn; off-canvas (2368_1, negative
|
|
112
|
-
* un-normalized x) → suppressed. A present spline always lies within the clip
|
|
113
|
-
* (GD_bb contains it), and head/tail/xlabels are positioned at on-canvas
|
|
114
|
-
* endpoints, so those keep their existing unconditional triggers — adding an
|
|
115
|
-
* overlap test there only risks suppressing legitimately-drawn content (it
|
|
116
|
-
* regressed neato/circo edges whose spl.bb is computed differently). The `set`
|
|
117
|
-
* guard on the main label is kept: the port retains an unplaced (`pos=0,0`,
|
|
118
|
-
* `set=false`) label on merged back-edges where C has none.
|
|
119
|
-
* @see lib/common/emit.c:edge_in_box, lib/common/utils.c:overlap_label
|
|
120
|
-
*/
|
|
121
|
-
export function edgeHasDrawableContent(e: Edge, clip: Box): boolean {
|
|
122
|
-
const i = e.info;
|
|
123
|
-
return i.spl !== undefined
|
|
124
|
-
|| (i.label !== undefined && i.label.set && overlapLabel(i.label, clip))
|
|
125
|
-
|| i.xlabel !== undefined
|
|
126
|
-
|| i.head_label !== undefined || i.tail_label !== undefined;
|
|
127
|
-
}
|
|
88
|
+
// edge_in_box gate lives in common/edge-in-box.ts (shared with the device walk);
|
|
89
|
+
// re-exported for existing import sites.
|
|
90
|
+
export { edgeHasDrawableContent };
|
|
128
91
|
|
|
129
92
|
export class SvgRenderer implements RendererPlugin {
|
|
130
93
|
readonly type = 'svg';
|
package/src/xdot/parse.ts
CHANGED
|
@@ -13,6 +13,9 @@ import type {
|
|
|
13
13
|
} from "./types.js";
|
|
14
14
|
import { XDOT_PARSE_ERROR } from "./types.js";
|
|
15
15
|
|
|
16
|
+
/** ULONG_MAX on an LP64 target: strtoul saturation value. */
|
|
17
|
+
const ULONG_MAX = 0xffff_ffff_ffff_ffffn;
|
|
18
|
+
|
|
16
19
|
type PR<T> = { val: T; pos: number } | null;
|
|
17
20
|
type OpResult = { op: XdotOp; pos: number } | "error";
|
|
18
21
|
type OpMaybeResult = OpResult | null;
|
|
@@ -63,12 +66,23 @@ class XdotParser {
|
|
|
63
66
|
return { val: parseInt(s.slice(start, i), 10), pos: i };
|
|
64
67
|
}
|
|
65
68
|
|
|
69
|
+
/**
|
|
70
|
+
* strtoul(s, &endp, 10) cast to unsigned int: optional sign, a leading "-"
|
|
71
|
+
* negates modulo 2^64, magnitude overflow saturates to ULONG_MAX, and the
|
|
72
|
+
* result keeps the low 32 bits.
|
|
73
|
+
* @see lib/xdot/xdot.c:parseUInt
|
|
74
|
+
*/
|
|
66
75
|
static parseUInt_(s: string, pos: number): PR<number> {
|
|
67
76
|
const i = XdotParser.skipWs(s, pos);
|
|
68
|
-
|
|
69
|
-
const
|
|
77
|
+
let start = i;
|
|
78
|
+
const negative = i < s.length && s[i] === "-";
|
|
79
|
+
if (i < s.length && (s[i] === "-" || s[i] === "+")) start++;
|
|
80
|
+
const end = XdotParser.consumeDigits(s, start);
|
|
70
81
|
if (end === start) return null;
|
|
71
|
-
|
|
82
|
+
const mag = BigInt(s.slice(start, end));
|
|
83
|
+
const clamped = mag > ULONG_MAX ? ULONG_MAX : mag;
|
|
84
|
+
const wrapped = negative && mag <= ULONG_MAX ? BigInt.asUintN(64, -clamped) : clamped;
|
|
85
|
+
return { val: Number(BigInt.asUintN(32, wrapped)), pos: end };
|
|
72
86
|
}
|
|
73
87
|
|
|
74
88
|
static parseRect(s: string, pos: number): PR<XdotRect> {
|
|
@@ -163,7 +177,7 @@ class XdotParser {
|
|
|
163
177
|
const ry0 = XdotParser.parseReal(s, rx0.pos); if (!ry0) return null;
|
|
164
178
|
const rx1 = XdotParser.parseReal(s, ry0.pos); if (!rx1) return null;
|
|
165
179
|
const ry1 = XdotParser.parseReal(s, rx1.pos); if (!ry1) return null;
|
|
166
|
-
const rn = XdotParser.
|
|
180
|
+
const rn = XdotParser.parseUInt_(s, ry1.pos); if (!rn) return null;
|
|
167
181
|
const rs = XdotParser.parseStops(s, rn.pos, rn.val); if (!rs) return null;
|
|
168
182
|
return { x0: rx0.val, y0: ry0.val, x1: rx1.val, y1: ry1.val, stops: rs.stops };
|
|
169
183
|
}
|
|
@@ -175,7 +189,7 @@ class XdotParser {
|
|
|
175
189
|
const rx1 = XdotParser.parseReal(s, rr0.pos); if (!rx1) return null;
|
|
176
190
|
const ry1 = XdotParser.parseReal(s, rx1.pos); if (!ry1) return null;
|
|
177
191
|
const rr1 = XdotParser.parseReal(s, ry1.pos); if (!rr1) return null;
|
|
178
|
-
const rn = XdotParser.
|
|
192
|
+
const rn = XdotParser.parseUInt_(s, rr1.pos); if (!rn) return null;
|
|
179
193
|
const rs = XdotParser.parseStops(s, rn.pos, rn.val); if (!rs) return null;
|
|
180
194
|
return { x0: rx0.val, y0: ry0.val, r0: rr0.val, x1: rx1.val, y1: ry1.val, r1: rr1.val, stops: rs.stops };
|
|
181
195
|
}
|