@knowvah/dot-engine 2.6.11 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/api.js +18 -4
- package/dist/api.js.map +3 -3
- package/dist/async/render-async.d.ts.map +1 -1
- package/dist/common/agerr.d.ts +37 -0
- package/dist/common/agerr.d.ts.map +1 -0
- package/dist/common/arrows-shapes-poly.d.ts.map +1 -1
- package/dist/common/color.d.ts.map +1 -1
- package/dist/common/crand.d.ts +0 -20
- package/dist/common/crand.d.ts.map +1 -1
- package/dist/common/emit-once.d.ts +6 -0
- package/dist/common/emit-once.d.ts.map +1 -0
- package/dist/common/graph-init.d.ts.map +1 -1
- package/dist/common/htmltable-attr-warn.d.ts.map +1 -1
- package/dist/common/htmltable-emit-fill.d.ts +29 -2
- package/dist/common/htmltable-emit-fill.d.ts.map +1 -1
- package/dist/common/htmltable-emit-rules.d.ts.map +1 -1
- package/dist/common/htmltable-emit.d.ts.map +1 -1
- package/dist/common/htmltable-lex-expat.d.ts +1 -6
- package/dist/common/htmltable-lex-expat.d.ts.map +1 -1
- package/dist/common/htmltable-pos-runs.d.ts +7 -4
- package/dist/common/htmltable-pos-runs.d.ts.map +1 -1
- package/dist/common/htmltable.d.ts +1 -7
- package/dist/common/htmltable.d.ts.map +1 -1
- package/dist/common/libm-trig.d.ts +5 -0
- package/dist/common/libm-trig.d.ts.map +1 -0
- package/dist/common/multicolor.d.ts +21 -13
- package/dist/common/multicolor.d.ts.map +1 -1
- package/dist/common/poly-gencode.d.ts +1 -2
- package/dist/common/poly-gencode.d.ts.map +1 -1
- package/dist/common/poly-init.d.ts.map +1 -1
- package/dist/common/postproc.d.ts.map +1 -1
- package/dist/common/random.d.ts.map +1 -1
- package/dist/common/render-state.d.ts +8 -0
- package/dist/common/render-state.d.ts.map +1 -0
- package/dist/common/splines-clip.d.ts +7 -0
- package/dist/common/splines-clip.d.ts.map +1 -1
- package/dist/common/splines-geom.d.ts +8 -0
- package/dist/common/splines-geom.d.ts.map +1 -1
- package/dist/common/splines-routespl.d.ts.map +1 -1
- package/dist/common/textmeasure-lookup.d.ts.map +1 -1
- package/dist/common/textmeasure.d.ts.map +1 -1
- package/dist/gvc/anchor.d.ts.map +1 -1
- package/dist/gvc/context.d.ts +6 -0
- package/dist/gvc/context.d.ts.map +1 -1
- package/dist/gvc/device-cluster.d.ts +15 -0
- package/dist/gvc/device-cluster.d.ts.map +1 -1
- package/dist/gvc/device-labels.d.ts +14 -0
- package/dist/gvc/device-labels.d.ts.map +1 -1
- package/dist/gvc/device.d.ts.map +1 -1
- package/dist/gvc/first-layer.d.ts +9 -0
- package/dist/gvc/first-layer.d.ts.map +1 -0
- package/dist/gvc/gvrender-set-style.d.ts +28 -0
- package/dist/gvc/gvrender-set-style.d.ts.map +1 -0
- package/dist/gvc/usershape.d.ts +18 -0
- package/dist/gvc/usershape.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3169 -1296
- package/dist/index.js.map +4 -4
- package/dist/layout/dot/cluster.d.ts.map +1 -1
- package/dist/layout/dot/compound.d.ts.map +1 -1
- package/dist/layout/dot/conc.d.ts.map +1 -1
- package/dist/layout/dot/decomp.d.ts.map +1 -1
- package/dist/layout/dot/edge-route-clip.d.ts +32 -14
- package/dist/layout/dot/edge-route-clip.d.ts.map +1 -1
- package/dist/layout/dot/edge-route-routing.d.ts.map +1 -1
- package/dist/layout/dot/mincross-build.d.ts.map +1 -1
- package/dist/layout/dot/mincross-cross.d.ts.map +1 -1
- package/dist/layout/dot/rank.d.ts.map +1 -1
- package/dist/layout/dot/splines.d.ts.map +1 -1
- package/dist/layout/fdp/index.d.ts.map +1 -1
- package/dist/layout/fdp/tlayout-parms.d.ts.map +1 -1
- package/dist/layout/fdp/xlayout.d.ts.map +1 -1
- package/dist/layout/neato/__fixtures__/dijkstra-f.d.ts +11 -0
- package/dist/layout/neato/__fixtures__/dijkstra-f.d.ts.map +1 -0
- package/dist/layout/neato/__fixtures__/hier-imds.d.ts +36 -0
- package/dist/layout/neato/__fixtures__/hier-imds.d.ts.map +1 -0
- package/dist/layout/neato/__fixtures__/hier-majorization.d.ts +32 -0
- package/dist/layout/neato/__fixtures__/hier-majorization.d.ts.map +1 -0
- package/dist/layout/neato/cdt-surface.d.ts.map +1 -1
- package/dist/layout/neato/compute-hierarchy.d.ts +24 -0
- package/dist/layout/neato/compute-hierarchy.d.ts.map +1 -0
- package/dist/layout/neato/conjgrad-mkernel.d.ts +20 -0
- package/dist/layout/neato/conjgrad-mkernel.d.ts.map +1 -0
- package/dist/layout/neato/conjgrad.d.ts +19 -0
- package/dist/layout/neato/conjgrad.d.ts.map +1 -1
- package/dist/layout/neato/constrained-majorization.d.ts +26 -0
- package/dist/layout/neato/constrained-majorization.d.ts.map +1 -0
- package/dist/layout/neato/dijkstra.d.ts +33 -16
- package/dist/layout/neato/dijkstra.d.ts.map +1 -1
- package/dist/layout/neato/edge-len.d.ts.map +1 -1
- package/dist/layout/neato/fdp-adjust.d.ts.map +1 -1
- package/dist/layout/neato/gts-triangle.d.ts +14 -0
- package/dist/layout/neato/gts-triangle.d.ts.map +1 -0
- package/dist/layout/neato/init.d.ts +6 -22
- package/dist/layout/neato/init.d.ts.map +1 -1
- package/dist/layout/neato/kk.d.ts.map +1 -1
- package/dist/layout/neato/kkutils.d.ts +20 -0
- package/dist/layout/neato/kkutils.d.ts.map +1 -0
- package/dist/layout/neato/make-graph-data.d.ts +43 -0
- package/dist/layout/neato/make-graph-data.d.ts.map +1 -0
- package/dist/layout/neato/matrix-ops.d.ts +46 -0
- package/dist/layout/neato/matrix-ops.d.ts.map +1 -1
- package/dist/layout/neato/multispline-router.d.ts.map +1 -1
- package/dist/layout/neato/multispline.d.ts.map +1 -1
- package/dist/layout/neato/opt-arrangement.d.ts +20 -0
- package/dist/layout/neato/opt-arrangement.d.ts.map +1 -0
- package/dist/layout/neato/poly.d.ts.map +1 -1
- package/dist/layout/neato/quad-prog-solve.d.ts +44 -0
- package/dist/layout/neato/quad-prog-solve.d.ts.map +1 -0
- package/dist/layout/neato/sgd.d.ts.map +1 -1
- package/dist/layout/neato/smart-ini-x.d.ts +9 -0
- package/dist/layout/neato/smart-ini-x.d.ts.map +1 -0
- package/dist/layout/neato/splines.d.ts +1 -1
- package/dist/layout/neato/splines.d.ts.map +1 -1
- package/dist/layout/neato/start.d.ts +4 -1
- package/dist/layout/neato/start.d.ts.map +1 -1
- package/dist/layout/neato/stress.d.ts +8 -0
- package/dist/layout/neato/stress.d.ts.map +1 -1
- package/dist/model/edge.d.ts +1 -1
- package/dist/model/edge.d.ts.map +1 -1
- package/dist/model/lazy-attrs.d.ts +10 -0
- package/dist/model/lazy-attrs.d.ts.map +1 -0
- package/dist/model/node.d.ts +1 -1
- package/dist/model/node.d.ts.map +1 -1
- package/dist/ortho/index.d.ts.map +1 -1
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/render/color-resolve.d.ts +49 -1
- package/dist/render/color-resolve.d.ts.map +1 -1
- package/dist/render/dot/edge-draw.d.ts +1 -1
- package/dist/render/dot/edge-draw.d.ts.map +1 -1
- package/dist/render/public.d.ts.map +1 -1
- package/dist/render/svg-edge-split.d.ts +2 -0
- package/dist/render/svg-edge-split.d.ts.map +1 -1
- package/dist/render/svg-multicolor.d.ts +2 -2
- package/dist/render/svg-multicolor.d.ts.map +1 -1
- package/dist/render/xdot-public.d.ts.map +1 -1
- package/dist/render.js +2799 -927
- package/dist/render.js.map +4 -4
- package/package.json +1 -1
- package/src/async/render-async.ts +6 -0
- package/src/common/agerr.ts +96 -0
- package/src/common/arrows-shapes-poly.ts +19 -5
- package/src/common/color.ts +38 -10
- package/src/common/crand.ts +8 -2
- package/src/common/emit-once.ts +27 -0
- package/src/common/graph-init.ts +2 -1
- package/src/common/htmltable-attr-warn.ts +3 -4
- package/src/common/htmltable-emit-fill.ts +104 -11
- package/src/common/htmltable-emit-rules.ts +2 -0
- package/src/common/htmltable-emit.ts +4 -3
- package/src/common/htmltable-lex-expat.ts +96 -23
- package/src/common/htmltable-pos-runs.ts +11 -6
- package/src/common/htmltable.ts +28 -117
- package/src/common/libm-trig.ts +43 -0
- package/src/common/multicolor.ts +88 -24
- package/src/common/poly-gencode.ts +30 -10
- package/src/common/poly-init.ts +20 -1
- package/src/common/postproc.ts +4 -1
- package/src/common/random.ts +11 -1
- package/src/common/render-state.ts +36 -0
- package/src/common/splines-clip.ts +15 -12
- package/src/common/splines-geom.ts +8 -4
- package/src/common/splines-routespl.ts +2 -1
- package/src/common/textmeasure-lookup.ts +9 -2
- package/src/common/textmeasure.ts +45 -2
- package/src/gvc/anchor.ts +1 -12
- package/src/gvc/context.ts +8 -0
- package/src/gvc/device-cluster.ts +93 -10
- package/src/gvc/device-labels.ts +27 -0
- package/src/gvc/device.ts +34 -8
- package/src/gvc/first-layer.ts +26 -0
- package/src/gvc/gvrender-set-style.ts +114 -0
- package/src/gvc/usershape.ts +26 -0
- package/src/index.ts +3 -0
- package/src/layout/dot/cluster.ts +2 -1
- package/src/layout/dot/compound.ts +3 -4
- package/src/layout/dot/conc.ts +6 -5
- package/src/layout/dot/decomp.ts +3 -0
- package/src/layout/dot/edge-route-clip.ts +46 -28
- package/src/layout/dot/edge-route-routing.ts +11 -3
- package/src/layout/dot/fastgr.ts +2 -2
- package/src/layout/dot/mincross-build.ts +2 -0
- package/src/layout/dot/mincross-cross.ts +2 -0
- package/src/layout/dot/rank.ts +2 -0
- package/src/layout/dot/splines.ts +2 -1
- package/src/layout/fdp/index.ts +2 -1
- package/src/layout/fdp/tlayout-parms.ts +9 -2
- package/src/layout/fdp/xlayout.ts +10 -2
- package/src/layout/neato/__fixtures__/dijkstra-f.ts +17 -0
- package/src/layout/neato/__fixtures__/hier/c1554.gv +33 -0
- package/src/layout/neato/__fixtures__/hier/c1554.plain +26 -0
- package/src/layout/neato/__fixtures__/hier/c1554.xdot +161 -0
- package/src/layout/neato/__fixtures__/hier/cases/cycle.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/cycle.plain +10 -0
- package/src/layout/neato/__fixtures__/hier/cases/cycle.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/dirnone.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/dirnone.plain +11 -0
- package/src/layout/neato/__fixtures__/hier/cases/dirnone.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/epsilon.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/epsilon.plain +13 -0
- package/src/layout/neato/__fixtures__/hier/cases/epsilon.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/levelsgap.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/levelsgap.plain +8 -0
- package/src/layout/neato/__fixtures__/hier/cases/levelsgap.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/mds.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/mds.plain +10 -0
- package/src/layout/neato/__fixtures__/hier/cases/mds.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/multiedge.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/multiedge.plain +10 -0
- package/src/layout/neato/__fixtures__/hier/cases/multiedge.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.plain +8 -0
- package/src/layout/neato/__fixtures__/hier/cases/neglevelsgap.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/startrandom.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/startrandom.plain +10 -0
- package/src/layout/neato/__fixtures__/hier/cases/startrandom.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/startregular.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/startregular.plain +10 -0
- package/src/layout/neato/__fixtures__/hier/cases/startregular.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/tdist.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/tdist.plain +13 -0
- package/src/layout/neato/__fixtures__/hier/cases/tdist.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/cases/undirected.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/cases/undirected.plain +10 -0
- package/src/layout/neato/__fixtures__/hier/cases/undirected.stderr +0 -0
- package/src/layout/neato/__fixtures__/hier/hier-probe.gv +1 -0
- package/src/layout/neato/__fixtures__/hier/hier-probe.plain +16 -0
- package/src/layout/neato/__fixtures__/hier/hier-probe.xdot +62 -0
- package/src/layout/neato/__fixtures__/hier-imds.ts +151 -0
- package/src/layout/neato/__fixtures__/hier-majorization.ts +145 -0
- package/src/layout/neato/cdt-surface.ts +12 -22
- package/src/layout/neato/compute-hierarchy.ts +90 -0
- package/src/layout/neato/conjgrad-mkernel.ts +141 -0
- package/src/layout/neato/conjgrad.ts +208 -0
- package/src/layout/neato/constrained-majorization.ts +479 -0
- package/src/layout/neato/dijkstra.ts +218 -92
- package/src/layout/neato/edge-len.ts +2 -1
- package/src/layout/neato/fdp-adjust.ts +3 -2
- package/src/layout/neato/gts-triangle.ts +42 -0
- package/src/layout/neato/init.ts +68 -68
- package/src/layout/neato/kk.ts +2 -1
- package/src/layout/neato/kkutils.ts +96 -0
- package/src/layout/neato/make-graph-data.ts +166 -0
- package/src/layout/neato/matrix-ops.ts +138 -0
- package/src/layout/neato/multispline-router.ts +3 -3
- package/src/layout/neato/multispline.ts +3 -2
- package/src/layout/neato/opt-arrangement.ts +86 -0
- package/src/layout/neato/poly.ts +3 -2
- package/src/layout/neato/quad-prog-solve.ts +489 -0
- package/src/layout/neato/sgd.ts +5 -2
- package/src/layout/neato/smart-ini-x.ts +503 -0
- package/src/layout/neato/splines.ts +4 -4
- package/src/layout/neato/start.ts +19 -14
- package/src/layout/neato/stress.ts +28 -0
- package/src/model/edge.ts +3 -2
- package/src/model/lazy-attrs.ts +36 -0
- package/src/model/node.ts +3 -2
- package/src/ortho/index.ts +2 -1
- package/src/parser/dot.js +4 -1
- package/src/parser/dot.pegjs +5 -1
- package/src/parser/index.ts +4 -3
- package/src/render/color-resolve.ts +193 -4
- package/src/render/dot/edge-draw.ts +6 -3
- package/src/render/dot.ts +1 -1
- package/src/render/public.ts +2 -0
- package/src/render/svg-edge-split.ts +6 -4
- package/src/render/svg-multicolor.ts +4 -3
- package/src/render/svg-parallel-edge.ts +2 -2
- package/src/render/xdot-public.ts +2 -0
|
@@ -6,14 +6,19 @@
|
|
|
6
6
|
|
|
7
7
|
import type { Graph } from '../model/graph.js';
|
|
8
8
|
import type { ObjState } from './job.js';
|
|
9
|
+
import type { RendererPlugin } from './context.js';
|
|
10
|
+
import { transformPoint } from './device-transform.js';
|
|
11
|
+
import { stripedBox } from '../render/svg-multicolor.js';
|
|
9
12
|
import { RenderJob } from './job.js';
|
|
10
13
|
import { FillType } from './context.js';
|
|
11
14
|
import type { ResolvedFill, ClusterAttrs } from '../common/style-resolve.js';
|
|
12
15
|
import {
|
|
13
16
|
parseStyleFlags, resolvePenColor, resolvePenType, resolvePenWidth,
|
|
14
|
-
resolveClusterFillEx,
|
|
17
|
+
resolveClusterFillEx, styleTokens,
|
|
15
18
|
} from '../common/style-resolve.js';
|
|
16
|
-
import {
|
|
19
|
+
import { gvrenderSetStyle, checkClusterStyleTokens } from './gvrender-set-style.js';
|
|
20
|
+
import { resolveRenderColor, withColorScheme, reportColorNames } from '../render/color-resolve.js';
|
|
21
|
+
import { parseSegs } from '../common/multicolor.js';
|
|
17
22
|
|
|
18
23
|
/**
|
|
19
24
|
* Copy gradient fields from a linear/radial ResolvedFill onto obj.
|
|
@@ -99,16 +104,94 @@ export function applyClusterObjState(sg: Graph, job: RenderJob): boolean {
|
|
|
99
104
|
const fillRes = resolveClusterFillEx(clusterAttrsOf(sg));
|
|
100
105
|
if (job.obj === null) return false;
|
|
101
106
|
const obj = job.obj;
|
|
107
|
+
// emit_clusters: gvrender_set_style(job, checkClusterStyle list) precedes the
|
|
108
|
+
// colour resolution. @see lib/common/emit.c:3808
|
|
109
|
+
gvrenderSetStyle(checkClusterStyleTokens(styleTokens(clusterStyle(sg))));
|
|
102
110
|
// C wraps the cluster color block with setColorScheme. @see emit.c:3800/3943
|
|
103
111
|
return withColorScheme(clusterAttr(sg, 'colorscheme'), () => {
|
|
112
|
+
// C resolves the fill (set_fillcolor / gradient vals) before the pen,
|
|
113
|
+
// which is set just before gvrender_box. @see emit.c:3846-3868 vs 3907-3913
|
|
114
|
+
const filled = applyClusterFill(obj, fillRes);
|
|
104
115
|
applyClusterPenState(obj, sg);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
obj.fill = FillType.Solid;
|
|
108
|
-
obj.fillColor = resolveRenderColor(fillRes.color);
|
|
109
|
-
return true;
|
|
110
|
-
}
|
|
111
|
-
applyClusterGradient(obj, fillRes);
|
|
112
|
-
return true;
|
|
116
|
+
reportColorNames(stripedClusterColors(sg));
|
|
117
|
+
return filled;
|
|
113
118
|
});
|
|
114
119
|
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* True when emit_clusters takes the striped branch: `style=striped` and not
|
|
123
|
+
* `rounded` (the rounded branch is tested first).
|
|
124
|
+
* @see lib/common/emit.c:3877-3892 emit_clusters (rounded / striped / plain)
|
|
125
|
+
*/
|
|
126
|
+
export function isStripedCluster(sg: Graph): boolean {
|
|
127
|
+
const flags = parseStyleFlags(clusterStyle(sg));
|
|
128
|
+
return flags.striped && !flags.rounded;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The fillcolor string emit_clusters hands to stripedBox.
|
|
133
|
+
* @see lib/common/emit.c:3820-3850 emit_clusters (fillcolor selection)
|
|
134
|
+
*/
|
|
135
|
+
function stripedFillSpec(sg: Graph): string {
|
|
136
|
+
const flags = parseStyleFlags(clusterStyle(sg));
|
|
137
|
+
const color = clusterAttr(sg, 'color') ?? '';
|
|
138
|
+
let fill = clusterAttr(sg, 'fillcolor') || color;
|
|
139
|
+
const bg = clusterAttr(sg, 'bgcolor') ?? '';
|
|
140
|
+
if ((!flags.filled || fill === '') && bg !== '') fill = bg;
|
|
141
|
+
return fill === '' ? 'lightgrey' : fill;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The colours stripedBox sets for a `style=striped` cluster (not rounded): each
|
|
146
|
+
* segment of the fill spec with a positive width, after the pen.
|
|
147
|
+
* @see lib/common/emit.c:3896-3906 emit_clusters (striped branch)
|
|
148
|
+
* @see lib/common/emit.c:595 stripedBox
|
|
149
|
+
*/
|
|
150
|
+
function stripedClusterColors(sg: Graph): string[] {
|
|
151
|
+
if (!isStripedCluster(sg)) return [];
|
|
152
|
+
const { segs, error } = parseSegs(stripedFillSpec(sg), () => `in cluster ${sg.name}`);
|
|
153
|
+
if (error === 1 || error === 2) return [];
|
|
154
|
+
return segs.flatMap((seg) => (seg.color !== null && seg.t > 0 ? [seg.color] : []));
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Draw a `style=striped` cluster box: stripedBox over the bb corners, then the
|
|
159
|
+
* unfilled bb outline (pen already "transparent" for `peripheries=0`). The
|
|
160
|
+
* once-per-render Total-size warning is claimed by stripedClusterColors when
|
|
161
|
+
* the pen state is applied, so the parse here does not print twice.
|
|
162
|
+
* @see lib/common/emit.c:3892-3906 emit_clusters (striped branch)
|
|
163
|
+
*/
|
|
164
|
+
export function renderStripedClusterBox(
|
|
165
|
+
sg: Graph, renderer: RendererPlugin, job: RenderJob,
|
|
166
|
+
): void {
|
|
167
|
+
// stripedBox runs inside emit_clusters' setColorScheme window (3800/3943).
|
|
168
|
+
withColorScheme(clusterAttr(sg, 'colorscheme'), () => {
|
|
169
|
+
const { ll, ur } = sg.info.bb!;
|
|
170
|
+
const af = [
|
|
171
|
+
{ x: ll.x, y: ll.y }, { x: ur.x, y: ll.y }, { x: ur.x, y: ur.y }, { x: ll.x, y: ur.y },
|
|
172
|
+
].map((p) => transformPoint(p, job));
|
|
173
|
+
// stripedBox passes filled=1 (never GRADIENT) to gvrender_polygon; the port's
|
|
174
|
+
// renderers read the gradient from obj.fill, so show them a solid fill.
|
|
175
|
+
const obj = job.obj;
|
|
176
|
+
const savedFill = obj?.fill;
|
|
177
|
+
if (obj !== null && savedFill !== undefined) obj.fill = FillType.Solid;
|
|
178
|
+
stripedBox(job, af, stripedFillSpec(sg), false, renderer, () => `in cluster ${sg.name}`);
|
|
179
|
+
if (obj !== null && savedFill !== undefined) obj.fill = savedFill;
|
|
180
|
+
const rawPts = [
|
|
181
|
+
{ x: ll.x, y: ll.y }, { x: ll.x, y: ur.y }, { x: ur.x, y: ur.y }, { x: ur.x, y: ll.y },
|
|
182
|
+
];
|
|
183
|
+
renderer.polygon(rawPts.map((p) => transformPoint(p, job)), false, job);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** The fill half of {@link applyClusterObjState}; true when the box is filled. */
|
|
188
|
+
function applyClusterFill(obj: ObjState, fillRes: ResolvedFill): boolean {
|
|
189
|
+
if (fillRes.kind === 'none') { obj.fill = FillType.None; return false; }
|
|
190
|
+
if (fillRes.kind === 'solid') {
|
|
191
|
+
obj.fill = FillType.Solid;
|
|
192
|
+
obj.fillColor = resolveRenderColor(fillRes.color);
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
applyClusterGradient(obj, fillRes);
|
|
196
|
+
return true;
|
|
197
|
+
}
|
package/src/gvc/device-labels.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { Graph } from '../model/graph.js';
|
|
12
12
|
import type { Node } from '../model/node.js';
|
|
13
|
+
import type { Edge } from '../model/edge.js';
|
|
13
14
|
import type { RendererPlugin } from './context.js';
|
|
14
15
|
import type { RenderJob } from './job.js';
|
|
15
16
|
import type { TextlabelT } from '../common/types.js';
|
|
@@ -17,6 +18,8 @@ import type { TextSpan } from '../common/emit-types.js';
|
|
|
17
18
|
import type { PlacedHtml } from '../common/htmltable-pos.js';
|
|
18
19
|
import { gvrenderTextspan, withLabelEmitState } from './textspan-emit.js';
|
|
19
20
|
import { emitHtmlLabel } from '../common/htmltable-emit.js';
|
|
21
|
+
import { reportColorNames } from '../render/color-resolve.js';
|
|
22
|
+
import { htmlLabelColors } from '../common/htmltable-emit-fill.js';
|
|
20
23
|
|
|
21
24
|
/** valign codes stored on textlabel_t. @see lib/common/types.h:textlabel_t.valign */
|
|
22
25
|
const VALIGN_TOP = 't'.charCodeAt(0);
|
|
@@ -38,6 +41,29 @@ function labelSpanX(lp: TextlabelT, just: 'l' | 'n' | 'r'): number {
|
|
|
38
41
|
return lp.pos.x;
|
|
39
42
|
}
|
|
40
43
|
|
|
44
|
+
/**
|
|
45
|
+
* The names emit_label hands to gvrender_resolve_color: an HTML label's walk,
|
|
46
|
+
* or a plain label's fontcolor (set once, before the spans, when there is a
|
|
47
|
+
* span).
|
|
48
|
+
* @see lib/common/labels.c:emit_label
|
|
49
|
+
*/
|
|
50
|
+
export function labelColorNames(lp: TextlabelT): string[] {
|
|
51
|
+
if (lp.html) return lp.u.kind === 'html' ? htmlLabelColors(lp.u.html as PlacedHtml) : [];
|
|
52
|
+
return lp.u.kind === 'txt' && lp.u.nspans >= 1 ? [lp.fontcolor] : [];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Report the unknown colours of an edge's four labels in emit_end_edge order
|
|
57
|
+
* (label, xlabel, head, tail), each only when placed.
|
|
58
|
+
* @see lib/common/emit.c:emit_edge_label
|
|
59
|
+
*/
|
|
60
|
+
export function reportEdgeLabelColors(e: Edge): void {
|
|
61
|
+
const labels = [e.info.label, e.info.xlabel, e.info.head_label, e.info.tail_label];
|
|
62
|
+
for (const lp of labels as Array<TextlabelT | undefined>) {
|
|
63
|
+
if (lp?.set) reportColorNames(labelColorNames(lp));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
41
67
|
/**
|
|
42
68
|
* Emit one label's text spans if present and placed.
|
|
43
69
|
* Shared by edge-label, node-xlabel, and graph-label slots.
|
|
@@ -59,6 +85,7 @@ export function renderOneLabel(
|
|
|
59
85
|
// default pos. Callers mirroring the existence-only sites pass false.
|
|
60
86
|
if (!lp) return;
|
|
61
87
|
if (requireSet && !lp.set) return;
|
|
88
|
+
reportColorNames(labelColorNames(lp));
|
|
62
89
|
// HTML branch: @see lib/common/labels.c:emit_label (226-230)
|
|
63
90
|
// C routes to emit_html_label(job, lp->u.html, lp) using lp->pos as anchor.
|
|
64
91
|
if (lp.html) {
|
package/src/gvc/device.ts
CHANGED
|
@@ -26,6 +26,7 @@ import type { ShapeDesc, TextlabelT } from '../common/types.js';
|
|
|
26
26
|
import { type LayerInfo, parseLayers } from '../common/layers.js';
|
|
27
27
|
import { RenderJob, EMIT_CLUSTERS_LAST, createObjState, ObjType, EmitState } from './job.js';
|
|
28
28
|
import { walkNodesAndEdges } from './emit-walk.js';
|
|
29
|
+
import { warnLayersUnsupported } from './first-layer.js';
|
|
29
30
|
import { polyInit } from '../common/poly-init.js';
|
|
30
31
|
import {
|
|
31
32
|
setHtmlAnchorObj,
|
|
@@ -42,11 +43,15 @@ import {
|
|
|
42
43
|
styleTokens,
|
|
43
44
|
} from '../common/style-resolve.js';
|
|
44
45
|
import { isPointNode } from '../common/poly-gencode.js';
|
|
45
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
resolveRenderColor, withColorScheme, reportEdgeColors, reportColorNames, backgroundColors,
|
|
48
|
+
setRendererKnownColors,
|
|
49
|
+
} from '../render/color-resolve.js';
|
|
46
50
|
import { emitRoundedBezier } from '../common/poly-shapes.js';
|
|
47
|
-
import { applyClusterObjState, clusterStyle, clusterPeripheries } from './device-cluster.js';
|
|
51
|
+
import { applyClusterObjState, clusterStyle, clusterPeripheries, isStripedCluster, renderStripedClusterBox } from './device-cluster.js';
|
|
48
52
|
import { transformPoint } from './device-transform.js';
|
|
49
|
-
import {
|
|
53
|
+
import { gvrenderSetStyle, edgeStyleResets } from './gvrender-set-style.js';
|
|
54
|
+
import { renderOneLabel, renderNodeXLabel, renderClusterLabel, reportEdgeLabelColors } from './device-labels.js';
|
|
50
55
|
export { transformPoint, applyRotation, applyScale, buildPoint } from './device-transform.js';
|
|
51
56
|
export { renderOneLabel, renderNodeXLabel, renderClusterLabel } from './device-labels.js';
|
|
52
57
|
import { svgNodeId, svgEdgeId, svgClusterId, svgGraphId } from '../render/svg-id.js';
|
|
@@ -156,12 +161,15 @@ function setEdgePen(e: Edge, job: RenderJob): void {
|
|
|
156
161
|
const obj = job.obj;
|
|
157
162
|
const flags = parseStyleFlags(e.attrs.get('style'));
|
|
158
163
|
obj.penColor = withColorScheme(e.attrs.get('colorscheme'),
|
|
159
|
-
() => resolveRenderColor(resolvePenColor(e.attrs.get('color'))));
|
|
164
|
+
() => resolveRenderColor(resolvePenColor(e.attrs.get('color')), false));
|
|
160
165
|
obj.pen = resolvePenType(flags);
|
|
161
166
|
obj.penWidth = resolvePenWidth(flags, e.attrs.get('penwidth'));
|
|
162
167
|
// Raw style tokens for xdot's `S` ops (e.g. explicit style="solid"); SVG
|
|
163
168
|
// reads only obj.pen, so this is xdot-only. @see gvrender_core_dot.c xdot_style
|
|
164
169
|
obj.rawStyle = styleTokens(e.attrs.get('style'));
|
|
170
|
+
// emit_begin_edge: `if (styles && ED_spl(e)) gvrender_set_style(job, styles)`
|
|
171
|
+
// @see lib/common/emit.c:2726
|
|
172
|
+
if (e.info.spl !== undefined) gvrenderSetStyle(obj.rawStyle);
|
|
165
173
|
}
|
|
166
174
|
|
|
167
175
|
export function renderEdge(e: Edge, renderer: RendererPlugin, job: RenderJob): void {
|
|
@@ -189,6 +197,14 @@ export function renderEdge(e: Edge, renderer: RendererPlugin, job: RenderJob): v
|
|
|
189
197
|
// @see lib/common/emit.c:emit_edge (begin/end color context)
|
|
190
198
|
withColorScheme(e.attrs.get('colorscheme'), () => {
|
|
191
199
|
renderer.beginEdge(e, job);
|
|
200
|
+
// emit_edge_graphics, then emit_end_edge's labels: C resolves these names
|
|
201
|
+
// here whatever the renderer draws (the non-drawing ones included).
|
|
202
|
+
reportEdgeColors(e);
|
|
203
|
+
// arrow_gen resets the style; C sets the edge's styles again after each
|
|
204
|
+
// flagged bezier of a multi-bezier spline. @see emit.c:2047, :2677
|
|
205
|
+
const styles = styleTokens(e.attrs.get('style'));
|
|
206
|
+
for (let i = edgeStyleResets(e, styles); i > 0; i--) gvrenderSetStyle(styles);
|
|
207
|
+
reportEdgeLabelColors(e);
|
|
192
208
|
renderer.endEdge(e, job);
|
|
193
209
|
});
|
|
194
210
|
} finally {
|
|
@@ -223,6 +239,10 @@ function renderClusterBox(
|
|
|
223
239
|
sg: Graph, filled: boolean, renderer: RendererPlugin, job: RenderJob,
|
|
224
240
|
): void {
|
|
225
241
|
const { ll, ur } = sg.info.bb!;
|
|
242
|
+
if (isStripedCluster(sg)) {
|
|
243
|
+
renderStripedClusterBox(sg, renderer, job);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
226
246
|
if (parseStyleFlags(clusterStyle(sg)).rounded) {
|
|
227
247
|
const af = [{ x: ll.x, y: ll.y }, { x: ur.x, y: ll.y }, { x: ur.x, y: ur.y }, { x: ll.x, y: ur.y }];
|
|
228
248
|
emitRoundedBezier(af, { x: 0, y: 0 }, filled, { renderer, job });
|
|
@@ -273,7 +293,7 @@ function renderOneCluster(sg: Graph, renderer: RendererPlugin, job: RenderJob):
|
|
|
273
293
|
// style=invis sets PEN_NONE and gvrender_polygon skips the whole box
|
|
274
294
|
// (fill included); the group/title still emit.
|
|
275
295
|
// @see lib/common/emit.c:3907-3917, lib/gvc/gvrender.c:543
|
|
276
|
-
if ((clusterPeripheries(sg) !== 0 || filled) && job.obj?.pen !== PenType.None) {
|
|
296
|
+
if ((clusterPeripheries(sg) !== 0 || filled || isStripedCluster(sg)) && job.obj?.pen !== PenType.None) {
|
|
277
297
|
renderClusterBox(sg, filled, renderer, job);
|
|
278
298
|
}
|
|
279
299
|
renderClusterLabel(sg, renderer, job);
|
|
@@ -327,6 +347,7 @@ export function renderGraph(g: Graph, job: RenderJob, renderer: RendererPlugin):
|
|
|
327
347
|
job.numLayers = layered ? info.numLayers : 1;
|
|
328
348
|
job.layerIDs = info.layerIDs;
|
|
329
349
|
renderer.beginGraph(g, job);
|
|
350
|
+
warnLayersUnsupported(job.format, info.numLayers, renderer.beginLayer !== undefined);
|
|
330
351
|
for (let ln = 1; ln <= job.numLayers; ln++) {
|
|
331
352
|
job.layerNum = ln;
|
|
332
353
|
if (layered) renderer.beginLayer!(info.layerIDs[ln]!, job);
|
|
@@ -351,9 +372,13 @@ function renderPage(g: Graph, renderer: RendererPlugin, job: RenderJob, info: La
|
|
|
351
372
|
resolveObjAnchor(g, labelTextOf(g.info.label), svgGraphId(g, job), job.obj);
|
|
352
373
|
}
|
|
353
374
|
const anchored = beginAnchorIf(renderer, job);
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
375
|
+
// emit_page opens the graph's colorscheme around the background and label.
|
|
376
|
+
withColorScheme(g.attrs.get('colorscheme'), () => {
|
|
377
|
+
reportColorNames(backgroundColors(g.attrs.get('bgcolor'), renderer.type === 'svg'));
|
|
378
|
+
renderer.pageBackground?.(g, job);
|
|
379
|
+
// emit root-graph label before clusters/nodes @see lib/common/emit.c:emit_page
|
|
380
|
+
renderGraphLabel(g, renderer, job);
|
|
381
|
+
});
|
|
357
382
|
if (anchored) renderer.endAnchor?.(job);
|
|
358
383
|
// emit_view: clusters are drawn BEFORE nodes/edges normally, but the map
|
|
359
384
|
// device (EMIT_CLUSTERS_LAST) emits their <area> AFTER, so a node/edge area
|
|
@@ -459,6 +484,7 @@ export function render(ctx: GvcContext, g: Graph, format: string, inlineImages =
|
|
|
459
484
|
// @see lib/common/emit.c:3260 (gvc->rotation=90); :3390 (job->rotation)
|
|
460
485
|
job.rotation = parseLandscape(g) ? 90 : 0;
|
|
461
486
|
job.renderer = renderer;
|
|
487
|
+
setRendererKnownColors(renderer.type === 'svg'); // svg_knowncolors; others have none
|
|
462
488
|
resetHtmlAnchorIds(); // C: fresh anchorId per dot invocation
|
|
463
489
|
for (const n of g.nodes.values()) polyInit(n, g, job.measurer);
|
|
464
490
|
return renderGraph(g, job, renderer);
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EPL-2.0
|
|
2
|
+
/**
|
|
3
|
+
* The `GVDEVICE_DOES_LAYERS` check of `firstlayer`: a device without it prints
|
|
4
|
+
* one layer and warns. The one-layer effect is `renderGraph`'s `layered` flag
|
|
5
|
+
* (only svg has `beginLayer`; the other native layer-capable devices, ps/pov,
|
|
6
|
+
* are not ported); this file owns the warning.
|
|
7
|
+
*/
|
|
8
|
+
import { agwarningf } from '../common/agerr.js';
|
|
9
|
+
|
|
10
|
+
/** Port format ids that spell a native `-T` name differently (`output_langname`). */
|
|
11
|
+
const NATIVE_LANGNAME: ReadonlyMap<string, string> = new Map([
|
|
12
|
+
['cmapx-np', 'cmapx_np'],
|
|
13
|
+
['imap-np', 'imap_np'],
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Print `layers not supported in <output_langname> output` when the graph has
|
|
18
|
+
* more than one layer and the device cannot draw layers. Called after
|
|
19
|
+
* beginGraph, as emit_graph calls firstlayer after emit_begin_graph: for
|
|
20
|
+
* `-Tjson` the nested xdot render in json_begin_graph therefore warns first.
|
|
21
|
+
* @see lib/common/emit.c:firstlayer
|
|
22
|
+
*/
|
|
23
|
+
export function warnLayersUnsupported(format: string, numLayers: number, doesLayers: boolean): void {
|
|
24
|
+
if (numLayers <= 1 || doesLayers) return;
|
|
25
|
+
agwarningf(`layers not supported in ${NATIVE_LANGNAME.get(format) ?? format} output`);
|
|
26
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The warning half of `gvrender_set_style`, plus the style-list filters its
|
|
5
|
+
* callers apply first. The pen/width/fill mapping of the same C function is
|
|
6
|
+
* not duplicated here: the port resolves it through style-resolve.ts
|
|
7
|
+
* (parseStyleFlags, resolvePenType, resolvePenWidth), which already mirrors
|
|
8
|
+
* the token cases below. Every caller in the port runs inside a render with a
|
|
9
|
+
* render engine (`if (gvre)` in C), so no engine test is needed here.
|
|
10
|
+
*
|
|
11
|
+
* @see lib/gvc/gvrender.c:480 gvrender_set_style
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { agwarningf } from '../common/agerr.js';
|
|
15
|
+
import { parseSegsSilent } from '../common/multicolor.js';
|
|
16
|
+
import type { PolygonT } from '../common/types.js';
|
|
17
|
+
import type { Edge } from '../model/edge.js';
|
|
18
|
+
|
|
19
|
+
/** Tokens gvrender_set_style maps to pen, pen width or fill state. */
|
|
20
|
+
const SUPPORTED_STYLES: ReadonlySet<string> = new Set([
|
|
21
|
+
'solid', 'dashed', 'dotted', 'invis', 'invisible', 'bold', 'setlinewidth',
|
|
22
|
+
'filled', 'unfilled', 'tapered',
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
/** `fmod(orientation, 90)` bound of shapes.c:isBox. @see lib/common/shapes.c:isBox */
|
|
26
|
+
const BOX_ORIENTATION_TOLERANCE = 0.5;
|
|
27
|
+
const BOX_ORIENTATION_STEP = 90;
|
|
28
|
+
const BOX_SIDES = 4;
|
|
29
|
+
/** polygon_t.sides bound of shapes.c:isEllipse. @see lib/common/shapes.c:isEllipse */
|
|
30
|
+
const ELLIPSE_MAX_SIDES = 2;
|
|
31
|
+
|
|
32
|
+
/** The name C compares: parse_style stores a token's `(args)` after its NUL. */
|
|
33
|
+
function styleName(token: string): string {
|
|
34
|
+
const paren = token.indexOf('(');
|
|
35
|
+
return paren < 0 ? token : token.slice(0, paren);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Warn for every token the render engine cannot map, on every call.
|
|
40
|
+
* @see lib/gvc/gvrender.c:480 gvrender_set_style
|
|
41
|
+
*/
|
|
42
|
+
export function gvrenderSetStyle(styles: readonly string[] | null): void {
|
|
43
|
+
if (styles === null) return;
|
|
44
|
+
for (const token of styles) {
|
|
45
|
+
const name = styleName(token);
|
|
46
|
+
if (!SUPPORTED_STYLES.has(name)) {
|
|
47
|
+
agwarningf(`gvrender_set_style: unsupported style ${name} - ignoring`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** @see lib/common/shapes.c:isBox */
|
|
53
|
+
function isBox(p: PolygonT | null): boolean {
|
|
54
|
+
if (p === null) return false;
|
|
55
|
+
return p.sides === BOX_SIDES
|
|
56
|
+
&& Math.abs(p.orientation % BOX_ORIENTATION_STEP) < BOX_ORIENTATION_TOLERANCE
|
|
57
|
+
&& p.distortion === 0 && p.skew === 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** @see lib/common/shapes.c:isEllipse */
|
|
61
|
+
function isEllipse(p: PolygonT | null): boolean {
|
|
62
|
+
return p !== null && p.sides <= ELLIPSE_MAX_SIDES;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The node style list passed to the renderer: checkStyle removes the tokens
|
|
67
|
+
* it turns into polygon flags. `shape` is `ND_shape(n)->polygon` (null for
|
|
68
|
+
* records).
|
|
69
|
+
* @see lib/common/shapes.c:465 checkStyle
|
|
70
|
+
*/
|
|
71
|
+
export function checkStyleTokens(styles: readonly string[], shape: PolygonT | null): string[] {
|
|
72
|
+
return styles.filter((p) => {
|
|
73
|
+
if (p === 'rounded' || p === 'diagonals' || p === 'radial') return false;
|
|
74
|
+
if (p === 'striped') return !isBox(shape);
|
|
75
|
+
if (p === 'wedged') return !isEllipse(shape);
|
|
76
|
+
return true;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The cluster style list passed to the renderer.
|
|
82
|
+
* @see lib/common/emit.c:367 checkClusterStyle
|
|
83
|
+
*/
|
|
84
|
+
export function checkClusterStyleTokens(styles: readonly string[]): string[] {
|
|
85
|
+
return styles.filter((p) => p !== 'radial' && p !== 'striped' && p !== 'rounded');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Count of `ch` in `s`. */
|
|
89
|
+
function countChar(s: string, ch: string): number {
|
|
90
|
+
return [...s].filter((c) => c === ch).length;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** True when emit_edge_graphics' multicolor() accepts the colour list. */
|
|
94
|
+
function multicolorTaken(color: string): boolean {
|
|
95
|
+
if (countChar(color, ';') === 0 || countChar(color, ':') === 0) return false;
|
|
96
|
+
const { error } = parseSegsSilent(color);
|
|
97
|
+
return error !== 1 && error !== 2;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* How many extra `gvrender_set_style(job, styles)` calls emit_edge_graphics
|
|
102
|
+
* makes: one per bezier carrying an arrow, when the spline has several
|
|
103
|
+
* beziers, in the `;` multicolor branch (when its colour list parses) and in
|
|
104
|
+
* the plain branch. The tapered and parallel `:` branches make none.
|
|
105
|
+
* @see lib/common/emit.c:2047 multicolor · :2677 emit_edge_graphics
|
|
106
|
+
*/
|
|
107
|
+
export function edgeStyleResets(e: Edge, styles: readonly string[]): number {
|
|
108
|
+
const spl = e.info.spl;
|
|
109
|
+
if (spl === undefined || spl.size <= 1 || styles.length === 0) return 0;
|
|
110
|
+
const color = e.attrs.get('color') ?? '';
|
|
111
|
+
const otherBranch = styles.includes('tapered') || countChar(color, ':') > 0;
|
|
112
|
+
if (otherBranch && !multicolorTaken(color)) return 0;
|
|
113
|
+
return spl.list.filter((bz) => bz.sflag !== 0 || bz.eflag !== 0).length;
|
|
114
|
+
}
|
package/src/gvc/usershape.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
import { invalidArgType } from '../errors.js';
|
|
17
17
|
import type { ImageSizer } from '../common/htmltable-types.js';
|
|
18
|
+
import { findImageBytes, type ImageResolver } from './image-resolver.js';
|
|
18
19
|
|
|
19
20
|
export type { ImageSizer } from '../common/htmltable-types.js';
|
|
20
21
|
|
|
@@ -37,3 +38,28 @@ export function setImageSizer(sizer: ImageSizer | null): void {
|
|
|
37
38
|
export function findImageSize(src: string): { w: number; h: number } | null {
|
|
38
39
|
return activeSizer !== null ? activeSizer(src) : null;
|
|
39
40
|
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* `strerror(ENOENT)`: the port has no filesystem, so an image the caller
|
|
44
|
+
* cannot supply is C's missing file (AD-11).
|
|
45
|
+
*/
|
|
46
|
+
export const STRERROR_ENOENT = 'No such file or directory';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Whether the caller supplies `name` at all: a size from `sizer` (default: the
|
|
50
|
+
* global one) or resolver bytes (a per-context `resolver` replaces the global). Mirrors gvusershape_open
|
|
51
|
+
* finding the file.
|
|
52
|
+
*/
|
|
53
|
+
export function imageIsSupplied(name: string, sizer?: ImageSizer, resolver?: ImageResolver): boolean {
|
|
54
|
+
const size = sizer !== undefined ? sizer(name) : findImageSize(name);
|
|
55
|
+
return size !== null || findImageBytes(name, resolver) !== null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The `agwarningf` text `gvusershape_file_access` prints when it cannot open
|
|
60
|
+
* `name`. C does not cache a failed open, so every sizing request repeats it.
|
|
61
|
+
* @see lib/gvc/gvusershape.c:gvusershape_file_access
|
|
62
|
+
*/
|
|
63
|
+
export function imageOpenWarning(name: string): string {
|
|
64
|
+
return `${STRERROR_ENOENT} while opening ${name}`;
|
|
65
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ import type { GvError, RenderResult } from './errors.js';
|
|
|
22
22
|
import type { EngineName } from './gvc/context.js';
|
|
23
23
|
import { render as deviceRender } from './gvc/device.js';
|
|
24
24
|
import { createDefaultContext } from './gvc/default-context.js';
|
|
25
|
+
import { resetRenderState } from './common/render-state.js';
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
|
|
@@ -90,6 +91,7 @@ function classifyError(err: unknown): GvError {
|
|
|
90
91
|
*/
|
|
91
92
|
export function renderSvg(dotSource: string, engine: EngineName): string {
|
|
92
93
|
checkRenderArgs(dotSource, engine);
|
|
94
|
+
resetRenderState(); // AD-4: one render = one native process
|
|
93
95
|
const ctx = createDefaultContext();
|
|
94
96
|
try {
|
|
95
97
|
// parse() is inside the try so any non-ParseError throw (e.g. a raw
|
|
@@ -122,6 +124,7 @@ export function renderSvg(dotSource: string, engine: EngineName): string {
|
|
|
122
124
|
export function tryRenderSvg(dotSource: string, engine: EngineName): RenderResult {
|
|
123
125
|
checkRenderArgs(dotSource, engine);
|
|
124
126
|
try {
|
|
127
|
+
// renderSvg calls resetRenderState first (AD-4).
|
|
125
128
|
return { svg: renderSvg(dotSource, engine) };
|
|
126
129
|
} catch (err: unknown) {
|
|
127
130
|
if (isUsageError(err)) throw err;
|
|
@@ -27,6 +27,7 @@ import { CLUSTER } from './rank.js';
|
|
|
27
27
|
import { makeSlots, safeOtherEdge, makeInterclustChain } from './cluster-path.js';
|
|
28
28
|
// mergeChain lives in classify.ts (circular: classify ↔ cluster — safe for function refs)
|
|
29
29
|
import { mergeChain, portsEq } from './classify.js';
|
|
30
|
+
import { agwarningf } from '../../common/agerr.js';
|
|
30
31
|
|
|
31
32
|
export { CLUSTER_EDGE } from './cluster-path.js';
|
|
32
33
|
export { mapInterclustNode } from './cluster-path.js';
|
|
@@ -297,7 +298,7 @@ export function markClusterNode(clust: Graph, n: Node, g: Graph): void {
|
|
|
297
298
|
if ((n.info.ranktype ?? 0) !== 0) {
|
|
298
299
|
// C warns with the ENCLOSING graph's name, not the cluster's.
|
|
299
300
|
// @see lib/dotgen/cluster.c:317-320
|
|
300
|
-
|
|
301
|
+
agwarningf(`${n.name} was already in a rankset, deleted from cluster ${g.name}`);
|
|
301
302
|
agDeleteFromCluster(clust, n);
|
|
302
303
|
return;
|
|
303
304
|
}
|
|
@@ -12,10 +12,9 @@ import type { Edge } from '../../model/edge.js';
|
|
|
12
12
|
import {
|
|
13
13
|
inBoxf, midPointf, splineIntersectf, boxIntersectf,
|
|
14
14
|
} from './compound-clip.js';
|
|
15
|
-
import { arrowEndClip, tailArrowEndClip, arrowClipLength } from './edge-route-clip.js';
|
|
15
|
+
import { arrowEndClip, tailArrowEndClip, arrowClipLength, distFused } from './edge-route-clip.js';
|
|
16
16
|
import { arrowDrawOpsForEnd, edgeArrowName, edgeArrowsize } from './edge-route-arrow.js';
|
|
17
17
|
import { edgePenwidthAttr, edgeRenderPenwidth } from './edge-route-helpers.js';
|
|
18
|
-
import { dist } from '../../common/arrows-geometry.js';
|
|
19
18
|
|
|
20
19
|
// Re-export geometry helpers so existing callers (tests) import from one place.
|
|
21
20
|
export {
|
|
@@ -67,7 +66,7 @@ function arrowEndClipIdx(state: ClipState, endp: number): number {
|
|
|
67
66
|
const elen = arrowClipLength(edgeArrowName(e, 'head'), edgeArrowsize(e), edgePenwidthAttr(e));
|
|
68
67
|
bez.eflag = bez.eflag || 1;
|
|
69
68
|
bez.ep = { ...bez.list[endp + 3] };
|
|
70
|
-
if (endp > state.starti &&
|
|
69
|
+
if (endp > state.starti && distFused(bez.list[endp], bez.list[endp + 3]) < elen) endp -= 3;
|
|
71
70
|
const seg = [bez.list[endp], bez.list[endp + 1], bez.list[endp + 2], bez.list[endp + 3]];
|
|
72
71
|
const clipped = arrowEndClip(seg, bez.ep, elen);
|
|
73
72
|
for (let k = 0; k < 4; k++) bez.list[endp + k] = clipped[k];
|
|
@@ -83,7 +82,7 @@ function arrowStartClipIdx(state: ClipState, startp: number, endp: number): numb
|
|
|
83
82
|
const slen = arrowClipLength(edgeArrowName(e, 'tail'), edgeArrowsize(e), edgePenwidthAttr(e));
|
|
84
83
|
bez.sflag = bez.sflag || 1;
|
|
85
84
|
bez.sp = { ...bez.list[startp] };
|
|
86
|
-
if (endp > startp &&
|
|
85
|
+
if (endp > startp && distFused(bez.list[startp], bez.list[startp + 3]) < slen) startp += 3;
|
|
87
86
|
const seg = [bez.list[startp], bez.list[startp + 1], bez.list[startp + 2], bez.list[startp + 3]];
|
|
88
87
|
const clipped = tailArrowEndClip(seg, bez.sp, slen);
|
|
89
88
|
for (let k = 0; k < 4; k++) bez.list[startp + k] = clipped[k];
|
package/src/layout/dot/conc.ts
CHANGED
|
@@ -20,6 +20,7 @@ import { dotScanRanks } from './rank.js';
|
|
|
20
20
|
import { dotRoot, rankGet } from './mincross-utils.js';
|
|
21
21
|
import { nodesInSeq } from './decomp.js';
|
|
22
22
|
import { graphMinrank, graphMaxrank } from './position-aux.js';
|
|
23
|
+
import { agerr, agerrorf, agwarningf } from '../../common/agerr.js';
|
|
23
24
|
|
|
24
25
|
const UP = 0;
|
|
25
26
|
const DOWN = 1;
|
|
@@ -263,13 +264,13 @@ export function fillRankVlist(g: Graph, r: number, root: Graph): number {
|
|
|
263
264
|
const rankArr = g.info.rank!;
|
|
264
265
|
const lead = g.info.rankleader![r];
|
|
265
266
|
if (lead === undefined || lead === null) {
|
|
266
|
-
|
|
267
|
+
agerrorf(`rebuild_vlists: lead is null for rank ${r}`);
|
|
267
268
|
return -1;
|
|
268
269
|
}
|
|
269
270
|
const rootRank = root.info.rank![r];
|
|
270
271
|
if (rootRank.v[lead.info.order ?? 0] !== lead) {
|
|
271
|
-
|
|
272
|
-
`
|
|
272
|
+
agerrorf(
|
|
273
|
+
`rebuild_vlists: rank lead ${lead.name} not in order ` +
|
|
273
274
|
`${lead.info.order ?? 0} of rank ${r}`,
|
|
274
275
|
);
|
|
275
276
|
return -1;
|
|
@@ -292,7 +293,7 @@ export function fillRankVlist(g: Graph, r: number, root: Graph): number {
|
|
|
292
293
|
rankArr[r].v = rootRank.v;
|
|
293
294
|
rankArr[r].vStart = lead.info.order ?? 0;
|
|
294
295
|
const maxi = computeMaxi(g, r);
|
|
295
|
-
if (maxi === -1)
|
|
296
|
+
if (maxi === -1) agwarningf(`degenerate concentrated rank ${g.name},${r}`);
|
|
296
297
|
rankArr[r].n = maxi + 1;
|
|
297
298
|
return 0;
|
|
298
299
|
}
|
|
@@ -425,7 +426,7 @@ export function dotConcentrate(g: Graph): number {
|
|
|
425
426
|
const clust = g.info.clust ?? [];
|
|
426
427
|
for (let c = 0; c < nCluster; c++) {
|
|
427
428
|
if (rebuildVlists(clust[c]) !== 0) {
|
|
428
|
-
|
|
429
|
+
agerr('prev', 'concentrate=true may not work correctly.');
|
|
429
430
|
return -1;
|
|
430
431
|
}
|
|
431
432
|
}
|
package/src/layout/dot/decomp.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import type { Node } from '../../model/node.js';
|
|
4
4
|
import type { Graph } from '../../model/graph.js';
|
|
5
5
|
import type { EdgeList } from '../../model/nodeInfo.js';
|
|
6
|
+
import { registerRenderState } from '../../common/render-state.js';
|
|
6
7
|
|
|
7
8
|
// ---------------------------------------------------------------------------
|
|
8
9
|
// AGSEQ-order node iteration (agfstnode/agnxtnode mirror)
|
|
@@ -135,7 +136,9 @@ export function decompSearch(g: Graph, start: Node, cmark: number): void {
|
|
|
135
136
|
}
|
|
136
137
|
|
|
137
138
|
// Cmark as const-object property avoids module-level let that confuses Lizard.
|
|
139
|
+
/** @see lib/dotgen/decomp.c:Cmark (C static, 0 at process start) */
|
|
138
140
|
const _dc = { cmark: 0 };
|
|
141
|
+
registerRenderState(() => { _dc.cmark = 0; });
|
|
139
142
|
|
|
140
143
|
// ONE private function immediately before decompose — the acyclic.ts pattern.
|
|
141
144
|
function resolveNode(n: Node, pass: 0 | 1): Node | undefined {
|