@knowvah/dot-engine 1.2.0 → 1.2.2
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/nodeinit.d.ts.map +1 -1
- package/dist/common/poly-inside.d.ts +6 -0
- package/dist/common/poly-inside.d.ts.map +1 -1
- package/dist/common/poly-sizing.d.ts +8 -0
- package/dist/common/poly-sizing.d.ts.map +1 -1
- package/dist/common/poly-vertices.d.ts.map +1 -1
- package/dist/index.js +61 -15
- package/dist/index.js.map +3 -3
- package/dist/layout/dot/edge-route-helpers.d.ts.map +1 -1
- package/dist/render.js +61 -15
- package/dist/render.js.map +3 -3
- package/package.json +1 -1
- package/src/common/nodeinit.ts +6 -2
- package/src/common/poly-inside.ts +21 -8
- package/src/common/poly-sizing.ts +10 -0
- package/src/common/poly-vertices.ts +37 -4
- package/src/layout/dot/edge-route-helpers.ts +19 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knowvah/dot-engine",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Faithful TypeScript port of Graphviz (dot, neato, fdp, sfdp, circo, twopi, osage, patchwork) — no C: no native binary, no WASM. Oracle-verified against the C implementation, runs in the browser.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"graphviz",
|
package/src/common/nodeinit.ts
CHANGED
|
@@ -201,6 +201,7 @@ function pointSizeResult(
|
|
|
201
201
|
return {
|
|
202
202
|
...gvNodesize(sz, sz, false),
|
|
203
203
|
outlineW: szOutline, outlineH: szOutline, baseW: sz0, baseH: sz0,
|
|
204
|
+
fixedshape: false, // C point_init never sets option.fixedshape
|
|
204
205
|
};
|
|
205
206
|
}
|
|
206
207
|
|
|
@@ -262,17 +263,20 @@ function initNodeFromLabel(n: Node, g: Graph, measurer: TextMeasurer): boolean {
|
|
|
262
263
|
// C poly_init installs the ATTR-RESOLVED polygon into ND_shape_info
|
|
263
264
|
// (sides/orientation/skew/distortion/peripheries/regular); edge
|
|
264
265
|
// clipping and rendering read it.
|
|
265
|
-
assignShapeInfo(n, effectivePolygon(poly, params));
|
|
266
|
+
assignShapeInfo(n, effectivePolygon(poly, params, sz.fixedshape));
|
|
266
267
|
return true;
|
|
267
268
|
}
|
|
268
269
|
|
|
269
270
|
/** The attr-resolved polygon poly_init stores. @see shapes.c:poly_init (poly->... assignments) */
|
|
270
|
-
function effectivePolygon(poly: PolygonT, p: PolySizeParams): PolygonT {
|
|
271
|
+
function effectivePolygon(poly: PolygonT, p: PolySizeParams, fixedshape: boolean): PolygonT {
|
|
271
272
|
let sides = p.sides;
|
|
272
273
|
// ellipses with distortion/skew become 120-gons
|
|
273
274
|
if (sides <= 2 && (p.distortion !== 0 || p.skew !== 0)) sides = 120;
|
|
274
275
|
return {
|
|
275
276
|
...poly,
|
|
277
|
+
// C: poly->option.fixedshape = true inside the fixedsize="shape" branch;
|
|
278
|
+
// poly-inside and computeVertices key the shape-box/width split on it.
|
|
279
|
+
option: { ...poly.option, fixedshape },
|
|
276
280
|
regular: p.regular,
|
|
277
281
|
peripheries: p.peripheries,
|
|
278
282
|
sides,
|
|
@@ -32,7 +32,7 @@ export function sameSide(p0: Point, p1: Point, L0: Point, L1: Point): boolean {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/** Vertex-extent box of a fixedshape polygon. @see lib/common/shapes.c:polyBB */
|
|
35
|
-
function fixedShapeSize(poly: PolygonT): { w: number; h: number } {
|
|
35
|
+
export function fixedShapeSize(poly: PolygonT): { w: number; h: number } {
|
|
36
36
|
let xmax = 0;
|
|
37
37
|
let ymax = 0;
|
|
38
38
|
for (const v of poly.vertices ?? []) {
|
|
@@ -50,13 +50,10 @@ interface InsideScale {
|
|
|
50
50
|
boxURy: number;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
/** Node size + outline extents (points) for the inside scale
|
|
54
|
-
|
|
53
|
+
/** Node size + outline extents (points) for the inside scale (non-fixedshape;
|
|
54
|
+
* the fixedshape branch short-circuits in insideScale). */
|
|
55
|
+
function insideDims(n: Node, xsize: number, ysize: number):
|
|
55
56
|
{ w: number; h: number; ow: number; oh: number } {
|
|
56
|
-
if (poly.option.fixedshape) {
|
|
57
|
-
const { w, h } = fixedShapeSize(poly);
|
|
58
|
-
return { w, h, ow: w, oh: h };
|
|
59
|
-
}
|
|
60
57
|
const w = 72 * (n.info.width || xsize / 72);
|
|
61
58
|
const h = 72 * (n.info.height || ysize / 72);
|
|
62
59
|
// Nodes that skipped poly_init (no measurer) have no outline dims.
|
|
@@ -67,10 +64,26 @@ function insideDims(n: Node, poly: PolygonT, xsize: number, ysize: number):
|
|
|
67
64
|
function insideScale(n: Node, poly: PolygonT): InsideScale {
|
|
68
65
|
// C swaps the node extents for flipped (LR/RL) layouts. @see poly_inside (GD_flip)
|
|
69
66
|
const flip = n.root.info.flip === true;
|
|
67
|
+
// C's fixedshape branch derives xsize/ysize from the polyBB shape box
|
|
68
|
+
// itself (NOT ND_lw/rw/ht, which are label-grown there), so the scale
|
|
69
|
+
// collapses to 1 (swapped under flip) and the boundary is the pure vertex
|
|
70
|
+
// extent with no penwidth outline growth.
|
|
71
|
+
// @see shapes.c:poly_inside (option.fixedshape branch)
|
|
72
|
+
if (poly.option.fixedshape) {
|
|
73
|
+
const { w, h } = fixedShapeSize(poly);
|
|
74
|
+
const xsize = flip ? h : w;
|
|
75
|
+
const ysize = flip ? w : h;
|
|
76
|
+
return {
|
|
77
|
+
scalex: xsize !== 0 ? w / xsize : w,
|
|
78
|
+
scaley: ysize !== 0 ? h / ysize : h,
|
|
79
|
+
boxURx: w / 2,
|
|
80
|
+
boxURy: h / 2,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
70
83
|
const lwrw = n.info.lw + n.info.rw;
|
|
71
84
|
const xsize = flip ? n.info.ht : lwrw;
|
|
72
85
|
const ysize = flip ? lwrw : n.info.ht;
|
|
73
|
-
const { w, h, ow, oh } = insideDims(n,
|
|
86
|
+
const { w, h, ow, oh } = insideDims(n, xsize, ysize);
|
|
74
87
|
return {
|
|
75
88
|
scalex: xsize !== 0 ? w / xsize : w,
|
|
76
89
|
scaley: ysize !== 0 ? h / ysize : h,
|
|
@@ -110,6 +110,14 @@ export interface PolySizeResult extends NodeSize {
|
|
|
110
110
|
*/
|
|
111
111
|
baseW: number;
|
|
112
112
|
baseH: number;
|
|
113
|
+
/**
|
|
114
|
+
* fixedsize="shape": the drawn shape stays at the attr box while
|
|
115
|
+
* width/height grow to max(label, shape). Installed on the polygon
|
|
116
|
+
* descriptor as `option.fixedshape`; vertex generation and insideness
|
|
117
|
+
* must read the base box, not the label-grown width.
|
|
118
|
+
* @see lib/common/shapes.c:poly_init (poly->option.fixedshape)
|
|
119
|
+
*/
|
|
120
|
+
fixedshape: boolean;
|
|
113
121
|
/**
|
|
114
122
|
* Justification borders — the space available for the label within the node
|
|
115
123
|
* box. `emit_label` reads this (not `dimen`) for `labelloc` top/bottom and
|
|
@@ -628,6 +636,7 @@ function assembleResult(
|
|
|
628
636
|
outlineW: Math.max(dimen.x, grown.outline.x),
|
|
629
637
|
outlineH: Math.max(dimen.y, grown.outline.y),
|
|
630
638
|
baseW: grown.base.x, baseH: grown.base.y,
|
|
639
|
+
fixedshape,
|
|
631
640
|
};
|
|
632
641
|
}
|
|
633
642
|
return {
|
|
@@ -635,5 +644,6 @@ function assembleResult(
|
|
|
635
644
|
outlineW: grown.outline.x,
|
|
636
645
|
outlineH: grown.outline.y,
|
|
637
646
|
baseW: grown.base.x, baseH: grown.base.y,
|
|
647
|
+
fixedshape,
|
|
638
648
|
};
|
|
639
649
|
}
|
|
@@ -100,11 +100,32 @@ export function generalPolyVertices(poly: PolygonT, w: number, h: number): Point
|
|
|
100
100
|
* which grows bb to the outermost ring after placing rings outward.
|
|
101
101
|
* @see lib/common/shapes.c:poly_init (ellipse peripheries, :2173-2184)
|
|
102
102
|
*/
|
|
103
|
-
function ellipseRings(
|
|
103
|
+
function ellipseRings(
|
|
104
|
+
w: number,
|
|
105
|
+
h: number,
|
|
106
|
+
peripheries: number,
|
|
107
|
+
base?: { w: number; h: number },
|
|
108
|
+
): Point[] {
|
|
109
|
+
if (base === undefined) {
|
|
110
|
+
const out: Point[] = [];
|
|
111
|
+
for (let j = 0; j < peripheries; j++) {
|
|
112
|
+
const inset = (peripheries - 1 - j) * GAP;
|
|
113
|
+
out.push(...ellipseVertices(w - 2 * inset, h - 2 * inset));
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
// C's forward order: the base ring at the shape box, each further ring
|
|
118
|
+
// stepping the half-extent OUTWARD by GAP. Needed under fixedshape, where
|
|
119
|
+
// the stored width is label-grown and the inset-from-width form above
|
|
120
|
+
// cannot recover the shape box. @see lib/common/shapes.c:poly_init
|
|
121
|
+
// (sides < 3: vertices[0/1] = ±bb/2, then P.x += GAP per periphery)
|
|
104
122
|
const out: Point[] = [];
|
|
123
|
+
let px = base.w / 2;
|
|
124
|
+
let py = base.h / 2;
|
|
105
125
|
for (let j = 0; j < peripheries; j++) {
|
|
106
|
-
|
|
107
|
-
|
|
126
|
+
out.push({ x: -px, y: -py }, { x: px, y: py });
|
|
127
|
+
px += GAP;
|
|
128
|
+
py += GAP;
|
|
108
129
|
}
|
|
109
130
|
return out;
|
|
110
131
|
}
|
|
@@ -221,13 +242,25 @@ export function computeVertices(
|
|
|
221
242
|
base?: { w: number; h: number },
|
|
222
243
|
): Point[] {
|
|
223
244
|
const sides = poly.sides;
|
|
245
|
+
// C builds vertices from the constrained shape box (bb) BEFORE the
|
|
246
|
+
// fixedshape branch sets ND_width/ND_height to max(label, bb)
|
|
247
|
+
// (shapes.c:poly_init tail), so under fixedsize="shape" the stored width
|
|
248
|
+
// is label-grown while the drawn shape stays at the attr box. Substitute
|
|
249
|
+
// the stored base box (= that bb) as the vertex source; every other case
|
|
250
|
+
// has width == shape box and is untouched.
|
|
251
|
+
if (poly.option.fixedshape && base !== undefined) {
|
|
252
|
+
w = base.w;
|
|
253
|
+
h = base.h;
|
|
254
|
+
}
|
|
224
255
|
// Cylinder is a 19-point bezier control polygon, not a regular 19-gon.
|
|
225
256
|
if (poly.option.shape === CYLINDER) return cylinderVertices(w, h);
|
|
226
257
|
// C generates one ring even for peripheries=0 (outp >= 1); the draw
|
|
227
258
|
// loop is what skips it. @see lib/common/shapes.c:poly_init (outp)
|
|
228
259
|
const peripheries = Math.max(poly.peripheries, 1);
|
|
229
260
|
if (poly.option.shape === STAR) return starRings({ w, h, base }, peripheries);
|
|
230
|
-
if (sides <= 2)
|
|
261
|
+
if (sides <= 2) {
|
|
262
|
+
return ellipseRings(w, h, peripheries, poly.option.fixedshape ? base : undefined);
|
|
263
|
+
}
|
|
231
264
|
// C's isBox test: right angles only (diamond = orientation 45).
|
|
232
265
|
if (sides === 4 && Math.abs(poly.orientation % 90) < 0.5
|
|
233
266
|
&& poly.distortion === 0 && poly.skew === 0) {
|
|
@@ -15,6 +15,8 @@ import type { Graph } from '../../model/graph.js';
|
|
|
15
15
|
import type { Node } from '../../model/node.js';
|
|
16
16
|
import type { Edge as GraphEdge } from '../../model/edge.js';
|
|
17
17
|
import { nodeAttr } from '../../common/poly-init.js';
|
|
18
|
+
import { fixedShapeSize } from '../../common/poly-inside.js';
|
|
19
|
+
import type { PolygonT } from '../../common/types.js';
|
|
18
20
|
import type { Point, Bezier, Spline } from '../../model/geom.js';
|
|
19
21
|
import { routeSpline } from '../../pathplan/route.js';
|
|
20
22
|
import type { Edge as BarrierEdge } from '../../pathplan/types.js';
|
|
@@ -69,6 +71,23 @@ export function nodeBoxOf(n: Node, g: Graph): NodeBox {
|
|
|
69
71
|
const rb = recordClipBox(n);
|
|
70
72
|
if (rb) return { center: n.info.coord, ...rb, isEllipse: false, penwidth };
|
|
71
73
|
}
|
|
74
|
+
// C poly_inside sizes the boundary from polyBB (the vertex extent) under
|
|
75
|
+
// fixedshape — ND_lw/rw/ht are label-grown there and must not clip the
|
|
76
|
+
// edge, and the boundary carries NO penwidth outline growth.
|
|
77
|
+
// @see lib/common/shapes.c:poly_inside (option.fixedshape branch)
|
|
78
|
+
const si = n.info.shape_info as PolygonT | undefined;
|
|
79
|
+
if (si?.option?.fixedshape === true && si.vertices) {
|
|
80
|
+
const ext = fixedShapeSize(si);
|
|
81
|
+
const flip = g.root.info.flip === true;
|
|
82
|
+
const w = flip ? ext.h : ext.w;
|
|
83
|
+
const h = flip ? ext.w : ext.h;
|
|
84
|
+
return {
|
|
85
|
+
center: n.info.coord,
|
|
86
|
+
lw: w / 2, rw: w / 2, ht: h,
|
|
87
|
+
isEllipse: ELLIPSE_SHAPES.has(shapeName),
|
|
88
|
+
penwidth: 0,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
72
91
|
return {
|
|
73
92
|
center: n.info.coord,
|
|
74
93
|
lw: n.info.lw > 0 ? n.info.lw : 27,
|