@knowvah/dot-engine 1.1.0 → 1.2.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 +48 -17
- package/dist/api.js +24 -0
- package/dist/api.js.map +2 -2
- package/dist/common/arrows.d.ts.map +1 -1
- package/dist/common/edge-label-init.d.ts.map +1 -1
- package/dist/common/htmltable-emit.d.ts +0 -8
- package/dist/common/htmltable-emit.d.ts.map +1 -1
- package/dist/common/ps-fontalias.d.ts +22 -9
- package/dist/common/ps-fontalias.d.ts.map +1 -1
- package/dist/common/splines-clip.d.ts.map +1 -1
- package/dist/gvc/device.d.ts.map +1 -1
- package/dist/gvc/job.d.ts +4 -0
- package/dist/gvc/job.d.ts.map +1 -1
- package/dist/index.js +1981 -1533
- package/dist/index.js.map +4 -4
- package/dist/layout/dot/index.d.ts +1 -1
- package/dist/layout/dot/index.d.ts.map +1 -1
- package/dist/layout/sfdp/init.d.ts.map +1 -1
- package/dist/model/graph.d.ts +24 -0
- package/dist/model/graph.d.ts.map +1 -1
- package/dist/parser/builder.d.ts +32 -0
- package/dist/parser/builder.d.ts.map +1 -1
- package/dist/render/dot/agwrite.d.ts +186 -0
- package/dist/render/dot/agwrite.d.ts.map +1 -0
- package/dist/render/dot/attrs.d.ts +220 -0
- package/dist/render/dot/attrs.d.ts.map +1 -0
- package/dist/render/dot/edge-draw.d.ts +60 -0
- package/dist/render/dot/edge-draw.d.ts.map +1 -0
- package/dist/render/dot/types.d.ts +28 -0
- package/dist/render/dot/types.d.ts.map +1 -0
- package/dist/render/dot/xdot-ops.d.ts +206 -0
- package/dist/render/dot/xdot-ops.d.ts.map +1 -0
- package/dist/render/dot.d.ts +58 -277
- package/dist/render/dot.d.ts.map +1 -1
- package/dist/render/map.d.ts +14 -1
- package/dist/render/map.d.ts.map +1 -1
- package/dist/render/svg-graph.d.ts.map +1 -1
- package/dist/render.js +1942 -1494
- package/dist/render.js.map +4 -4
- package/package.json +3 -1
- package/src/common/arrows.ts +17 -4
- package/src/common/edge-label-init.ts +9 -2
- package/src/common/htmltable-emit.ts +36 -8
- package/src/common/poly-gencode.ts +8 -8
- package/src/common/ps-fontalias.ts +80 -49
- package/src/common/splines-clip.ts +36 -10
- package/src/gvc/device.ts +18 -6
- package/src/gvc/job.ts +4 -0
- package/src/layout/dot/index.ts +37 -5
- package/src/layout/sfdp/init.ts +11 -0
- package/src/model/graph.ts +26 -0
- package/src/parser/builder.ts +90 -5
- package/src/render/dot/agwrite.ts +506 -0
- package/src/render/dot/attrs.ts +437 -0
- package/src/render/dot/edge-draw.ts +203 -0
- package/src/render/dot/types.ts +32 -0
- package/src/render/dot/xdot-ops.ts +432 -0
- package/src/render/dot.ts +114 -1131
- package/src/render/map.ts +18 -1
- package/src/render/svg-graph.ts +4 -0
- package/src/render/svg-helpers.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -3572,6 +3572,30 @@ var Graph = class {
|
|
|
3572
3572
|
* @see lib/cgraph/graph.c:agsubg (attr defval copy)
|
|
3573
3573
|
*/
|
|
3574
3574
|
graphDefaultsSnapshot;
|
|
3575
|
+
/**
|
|
3576
|
+
* Keys in `attrs` the BUILDER seeded from `graphDefaultsSnapshot` rather than
|
|
3577
|
+
* the input declaring them in this scope (see builder.ts GRAPH_LABEL_INHERIT_
|
|
3578
|
+
* KEYS). cgraph distinguishes these structurally — a seeded value is inherited
|
|
3579
|
+
* through the dict view and has no local symbol, while a local declaration
|
|
3580
|
+
* creates one even when its value equals the inherited value — so the
|
|
3581
|
+
* serializer needs the provenance to decide what write_dict would print.
|
|
3582
|
+
* Undefined on the root graph and on any scope that seeded nothing.
|
|
3583
|
+
* @see lib/cgraph/write.c:262 write_dict
|
|
3584
|
+
*/
|
|
3585
|
+
seededAttrs;
|
|
3586
|
+
/**
|
|
3587
|
+
* Graph-attribute keys whose declaration in THIS scope was the first anywhere
|
|
3588
|
+
* in the graph. cgraph's `setattr` splits on exactly this: a key with no
|
|
3589
|
+
* existing symbol takes the global-declaration branch, which ends up seeding
|
|
3590
|
+
* every direct subgraph that already exists with a local empty value (see
|
|
3591
|
+
* `eagerEmptyParts`); a key some earlier scope already declared takes the
|
|
3592
|
+
* "new local definition" branch and touches no subgraph at all. So an
|
|
3593
|
+
* identical `graph [fill=x]` statement makes a child print `fill=""` or print
|
|
3594
|
+
* nothing purely according to whether a SIBLING declared `fill` first.
|
|
3595
|
+
* Undefined on any scope that declared nothing new.
|
|
3596
|
+
* @see lib/cgraph/attr.c:257 setattr (lsym / psym / global branches)
|
|
3597
|
+
*/
|
|
3598
|
+
firstGraphDecl;
|
|
3575
3599
|
/**
|
|
3576
3600
|
* Layout-engine info; replaces GD_* macros via agbindrec (AD-1).
|
|
3577
3601
|
* @see lib/cgraph/cgraph.h:agbindrec
|
|
@@ -4054,7 +4078,7 @@ var NameCollector = class _NameCollector {
|
|
|
4054
4078
|
}
|
|
4055
4079
|
}
|
|
4056
4080
|
};
|
|
4057
|
-
var StmtProcessor = class {
|
|
4081
|
+
var StmtProcessor = class _StmtProcessor {
|
|
4058
4082
|
constructor(registry) {
|
|
4059
4083
|
this.registry = registry;
|
|
4060
4084
|
}
|
|
@@ -4073,6 +4097,41 @@ var StmtProcessor = class {
|
|
|
4073
4097
|
* @see lib/cgraph/id.c:agnameof (`'%' + id`)
|
|
4074
4098
|
*/
|
|
4075
4099
|
anonCounter = 0;
|
|
4100
|
+
/**
|
|
4101
|
+
* Strict-graph edge index: canonical endpoint-pair key → the edge already
|
|
4102
|
+
* created for it. `agedge` on a strict graph probes for a pre-existing edge
|
|
4103
|
+
* and returns it instead of creating a second one, so `strict digraph
|
|
4104
|
+
* { a->b; a->b }` holds ONE edge. A Map (rather than scanning root.edges)
|
|
4105
|
+
* keeps that probe O(1); it stays empty for non-strict graphs.
|
|
4106
|
+
* @see lib/cgraph/edge.c:agedge (agfindedge_by_key probe, :262-287)
|
|
4107
|
+
*/
|
|
4108
|
+
strictEdges = /* @__PURE__ */ new Map();
|
|
4109
|
+
/**
|
|
4110
|
+
* Canonical key for a strict-graph endpoint pair. C probes (t,h) and then,
|
|
4111
|
+
* for an UNDIRECTED graph, (h,t) — so an undirected pair matches either way
|
|
4112
|
+
* round and is canonicalized here by node id; a directed pair is not.
|
|
4113
|
+
* @see lib/cgraph/edge.c:agedge:274-275 (`if (e == NULL && agisundirected(g))`)
|
|
4114
|
+
*/
|
|
4115
|
+
static strictKey(tail, head, undirected) {
|
|
4116
|
+
const swap2 = undirected && head.id < tail.id;
|
|
4117
|
+
return swap2 ? `${head.id}\0${tail.id}` : `${tail.id}\0${head.id}`;
|
|
4118
|
+
}
|
|
4119
|
+
/**
|
|
4120
|
+
* Apply the DOT-syntax `:port:compass` endpoints to an edge's attrs.
|
|
4121
|
+
*
|
|
4122
|
+
* C's precedence is explicit attr > `:port` syntax > edge default (verified
|
|
4123
|
+
* against the oracle: with `edge [headport=n]` declared, `a:e -> b:sw` yields
|
|
4124
|
+
* headport `sw`, while `c:e -> d:sw [headport=w]` yields `w`). The guard is
|
|
4125
|
+
* therefore against THIS statement's own attribute list — not the edge's
|
|
4126
|
+
* accumulated map, which also holds inherited defaults and, for a strict
|
|
4127
|
+
* duplicate, the earlier statement's values.
|
|
4128
|
+
* @see lib/cgraph/grammar.y:396 (mkport)
|
|
4129
|
+
*/
|
|
4130
|
+
static applySyntaxPorts(target, stmtAttrs, tailPort, headPort) {
|
|
4131
|
+
const setByStmt = (k) => stmtAttrs.some((a) => a.key === k);
|
|
4132
|
+
if (tailPort && !setByStmt("tailport")) target.set("tailport", tailPort);
|
|
4133
|
+
if (headPort && !setByStmt("headport")) target.set("headport", headPort);
|
|
4134
|
+
}
|
|
4076
4135
|
/** Consume one anonymous id, returning cgraph's `2*counter+1`. */
|
|
4077
4136
|
nextAnonId() {
|
|
4078
4137
|
return this.anonCounter++ * 2 + 1;
|
|
@@ -4088,12 +4147,21 @@ var StmtProcessor = class {
|
|
|
4088
4147
|
}
|
|
4089
4148
|
processAssign(stmt, graph) {
|
|
4090
4149
|
graph.attrs.set(stmt.key, normaliseAttrValue(stmt.value));
|
|
4091
|
-
|
|
4150
|
+
this.declareGraphAttr(graph, stmt.key);
|
|
4151
|
+
}
|
|
4152
|
+
/** Record a graph-attribute declaration, noting whether THIS scope was the
|
|
4153
|
+
* first to declare the key anywhere — the branch selector in cgraph's
|
|
4154
|
+
* setattr. @see lib/cgraph/attr.c:257 · model/graph.ts firstGraphDecl */
|
|
4155
|
+
declareGraphAttr(graph, key) {
|
|
4156
|
+
if (!graph.root.declaredGraphAttrs.has(key)) {
|
|
4157
|
+
(graph.firstGraphDecl ??= /* @__PURE__ */ new Set()).add(key);
|
|
4158
|
+
graph.root.declaredGraphAttrs.add(key);
|
|
4159
|
+
}
|
|
4092
4160
|
}
|
|
4093
4161
|
processAttr(stmt, graph) {
|
|
4094
4162
|
if (stmt.target === "graph") {
|
|
4095
4163
|
applyAttrs(stmt.attrs, graph.attrs);
|
|
4096
|
-
for (const a of stmt.attrs)
|
|
4164
|
+
for (const a of stmt.attrs) this.declareGraphAttr(graph, a.key);
|
|
4097
4165
|
} else if (stmt.target === "node") {
|
|
4098
4166
|
applyAttrs(stmt.attrs, graph.nodeDefaults);
|
|
4099
4167
|
} else {
|
|
@@ -4128,7 +4196,10 @@ var StmtProcessor = class {
|
|
|
4128
4196
|
buildGraph(stmt.stmts, sg, root, directed, this);
|
|
4129
4197
|
for (const key of GRAPH_LABEL_INHERIT_KEYS) {
|
|
4130
4198
|
const v = sg.graphDefaultsSnapshot.get(key);
|
|
4131
|
-
if (v !== void 0 && !sg.attrs.has(key))
|
|
4199
|
+
if (v !== void 0 && !sg.attrs.has(key)) {
|
|
4200
|
+
sg.attrs.set(key, v);
|
|
4201
|
+
(sg.seededAttrs ??= /* @__PURE__ */ new Set()).add(key);
|
|
4202
|
+
}
|
|
4132
4203
|
}
|
|
4133
4204
|
graph.subgraphs.set(sgName, sg);
|
|
4134
4205
|
return sg;
|
|
@@ -4178,14 +4249,28 @@ var StmtProcessor = class {
|
|
|
4178
4249
|
connectEndpoints(tailEnd, headEnd, attrs, graph, root) {
|
|
4179
4250
|
const tailPort = tailEnd.port;
|
|
4180
4251
|
const headPort = headEnd.port;
|
|
4252
|
+
const strict = root.kind === "strict-directed" || root.kind === "strict-undirected";
|
|
4253
|
+
const undirected = root.kind === "strict-undirected";
|
|
4181
4254
|
for (const tail of tailEnd.nodes) {
|
|
4182
4255
|
for (const head of headEnd.nodes) {
|
|
4256
|
+
const key = strict ? _StmtProcessor.strictKey(tail, head, undirected) : "";
|
|
4257
|
+
const existing = strict ? this.strictEdges.get(key) : void 0;
|
|
4258
|
+
if (existing !== void 0) {
|
|
4259
|
+
applyAttrs(attrs, existing.attrs);
|
|
4260
|
+
_StmtProcessor.applySyntaxPorts(existing.attrs, attrs, tailPort, headPort);
|
|
4261
|
+
for (let g = graph; g !== null && g !== root; g = g.parent) {
|
|
4262
|
+
g.nodes.set(tail.name, tail);
|
|
4263
|
+
g.nodes.set(head.name, head);
|
|
4264
|
+
if (!g.edges.includes(existing)) g.edges.push(existing);
|
|
4265
|
+
}
|
|
4266
|
+
continue;
|
|
4267
|
+
}
|
|
4183
4268
|
const edge = new Edge(tail, head, "");
|
|
4184
4269
|
this.advanceAnonId();
|
|
4185
4270
|
applyAttrs(attrs, edge.attrs);
|
|
4271
|
+
_StmtProcessor.applySyntaxPorts(edge.attrs, attrs, tailPort, headPort);
|
|
4186
4272
|
this.snapshotEdgeDefaults(edge, graph);
|
|
4187
|
-
if (
|
|
4188
|
-
if (headPort && !edge.attrs.has("headport")) edge.attrs.set("headport", headPort);
|
|
4273
|
+
if (strict) this.strictEdges.set(key, edge);
|
|
4189
4274
|
root.edges.push(edge);
|
|
4190
4275
|
edge.graphSeq = root.edges.length;
|
|
4191
4276
|
for (let g = graph; g !== null && g !== root; g = g.parent) {
|
|
@@ -5365,6 +5450,9 @@ var RenderJob = class {
|
|
|
5365
5450
|
radialGradId = 0;
|
|
5366
5451
|
/** Whether the graph being rendered is directed (edge titles use -> vs --). */
|
|
5367
5452
|
directed = true;
|
|
5453
|
+
/** GD_fontnames of the root graph — selects which PostScript-alias fields the
|
|
5454
|
+
* SVG emitter uses. @see plugin/core/gvrender_core_svg.c:464 */
|
|
5455
|
+
fontnames = FontnameKind.NativeFonts;
|
|
5368
5456
|
/** Root graph's `id` attribute (GD_drawing(root)->id), '' if unset. Used as
|
|
5369
5457
|
* the `<gid>_` prefix for non-root object ids. @see lib/common/emit.c:getObjId */
|
|
5370
5458
|
drawingId = "";
|
|
@@ -9599,6 +9687,13 @@ function emitHtmlImg(img, pos, renderer, job) {
|
|
|
9599
9687
|
}
|
|
9600
9688
|
renderer.usershape?.(img.src, b, job);
|
|
9601
9689
|
}
|
|
9690
|
+
function htmlStyleInvisible(style) {
|
|
9691
|
+
if (style === void 0) return false;
|
|
9692
|
+
return style.split(/[ ,]+/).some((tok) => {
|
|
9693
|
+
const t = tok.toUpperCase();
|
|
9694
|
+
return t === "INVIS" || t === "INVISIBLE";
|
|
9695
|
+
});
|
|
9696
|
+
}
|
|
9602
9697
|
function emitHtmlCell(cell2, pos, renderer, job) {
|
|
9603
9698
|
const inAnchor = initHtmlAnchor(
|
|
9604
9699
|
{ href: cell2.href, title: cell2.title, target: cell2.target, id: cell2.id },
|
|
@@ -9606,6 +9701,10 @@ function emitHtmlCell(cell2, pos, renderer, job) {
|
|
|
9606
9701
|
renderer,
|
|
9607
9702
|
job
|
|
9608
9703
|
);
|
|
9704
|
+
if (htmlStyleInvisible(cell2.style)) {
|
|
9705
|
+
if (inAnchor) endHtmlAnchor(renderer, job);
|
|
9706
|
+
return;
|
|
9707
|
+
}
|
|
9609
9708
|
emitCellDecoration(cell2, pos, renderer, job);
|
|
9610
9709
|
if (cell2.nested !== void 0) {
|
|
9611
9710
|
emitHtmlLabel(cell2.nested, pos, renderer, job);
|
|
@@ -9638,22 +9737,25 @@ function emitHtmlLabel(placed, pos, renderer, job) {
|
|
|
9638
9737
|
const tblBox = absBox(placed.box, pos);
|
|
9639
9738
|
const clustersLast = (job.flags & EMIT_CLUSTERS_LAST) !== 0;
|
|
9640
9739
|
const inAnchor = clustersLast ? false : initHtmlAnchor(anchorData, tblBox, renderer, job);
|
|
9641
|
-
|
|
9642
|
-
|
|
9643
|
-
|
|
9644
|
-
|
|
9645
|
-
|
|
9646
|
-
|
|
9647
|
-
|
|
9648
|
-
|
|
9649
|
-
|
|
9650
|
-
|
|
9651
|
-
|
|
9652
|
-
|
|
9653
|
-
|
|
9654
|
-
|
|
9655
|
-
|
|
9656
|
-
|
|
9740
|
+
const tblInvisible = htmlStyleInvisible(placed.style);
|
|
9741
|
+
if (!tblInvisible) {
|
|
9742
|
+
if (placed.bgcolor !== void 0) {
|
|
9743
|
+
emitBgFill({
|
|
9744
|
+
bgcolor: placed.bgcolor,
|
|
9745
|
+
box: placed.box,
|
|
9746
|
+
pos,
|
|
9747
|
+
border: placed.border,
|
|
9748
|
+
renderer,
|
|
9749
|
+
job,
|
|
9750
|
+
gradientangle: placed.gradientangle,
|
|
9751
|
+
style: placed.style
|
|
9752
|
+
});
|
|
9753
|
+
}
|
|
9754
|
+
for (const cell2 of placed.cells) emitHtmlCell(cell2, pos, renderer, job);
|
|
9755
|
+
emitTableRules(placed, pos, renderer, job);
|
|
9756
|
+
if (placed.border > 0) {
|
|
9757
|
+
emitBorder({ box: placed.box, pos, border: placed.border, color: placed.color, sides: placed.sides, style: placed.style }, renderer, job);
|
|
9758
|
+
}
|
|
9657
9759
|
}
|
|
9658
9760
|
if (inAnchor) endHtmlAnchor(renderer, job);
|
|
9659
9761
|
if (clustersLast && initHtmlAnchor(anchorData, tblBox, renderer, job)) {
|
|
@@ -17625,11 +17727,9 @@ function applyPenState(obj, styleAttr, colorAttr, penwidthAttr) {
|
|
|
17625
17727
|
function applyNodeStyle(obj, n, job) {
|
|
17626
17728
|
const styleAttr = nodeAttr(n, n.root, "style");
|
|
17627
17729
|
const colorAttr = nodeAttr(n, n.root, "color");
|
|
17628
|
-
|
|
17629
|
-
|
|
17630
|
-
|
|
17631
|
-
return filled;
|
|
17632
|
-
});
|
|
17730
|
+
const filled = applyFillState(obj, n, job);
|
|
17731
|
+
applyPenState(obj, styleAttr, colorAttr, nodeAttr(n, n.root, "penwidth"));
|
|
17732
|
+
return filled;
|
|
17633
17733
|
}
|
|
17634
17734
|
function prepareDrawPoly(poly, filled, obj) {
|
|
17635
17735
|
if (poly.peripheries !== 0 || !filled) return poly;
|
|
@@ -18298,6 +18398,7 @@ function bezierClip(insideCtx, insideFn, sp, leftInside) {
|
|
|
18298
18398
|
|
|
18299
18399
|
// src/common/arrows-constants.ts
|
|
18300
18400
|
var ARROW_LENGTH = 10;
|
|
18401
|
+
var NUMB_OF_ARROW_HEADS = 4;
|
|
18301
18402
|
var BITS_PER_ARROW_TYPE = 4;
|
|
18302
18403
|
var ARR_TYPE_NONE = 0;
|
|
18303
18404
|
var ARR_TYPE_NORM = 1;
|
|
@@ -18411,13 +18512,18 @@ var parseOne = (s) => {
|
|
|
18411
18512
|
function parseArrow(str) {
|
|
18412
18513
|
const result = [];
|
|
18413
18514
|
let s = str;
|
|
18414
|
-
|
|
18515
|
+
let parsedAny = false;
|
|
18516
|
+
while (s.length > 0 && result.length < NUMB_OF_ARROW_HEADS) {
|
|
18415
18517
|
const m = parseOne(s);
|
|
18416
18518
|
if (!m) break;
|
|
18417
|
-
|
|
18519
|
+
parsedAny = true;
|
|
18418
18520
|
s = m.rest;
|
|
18521
|
+
const isGap = m.comp.name === "none";
|
|
18522
|
+
if (isGap && result.length === NUMB_OF_ARROW_HEADS - 1) continue;
|
|
18523
|
+
if (isGap && result.length === 0 && s.length === 0) continue;
|
|
18524
|
+
result.push(m.comp);
|
|
18419
18525
|
}
|
|
18420
|
-
if (
|
|
18526
|
+
if (!parsedAny) result.push({ name: "normal", open: false, left: false, right: false });
|
|
18421
18527
|
return result;
|
|
18422
18528
|
}
|
|
18423
18529
|
var NAME_TO_FLAG = /* @__PURE__ */ new Map([
|
|
@@ -19145,6 +19251,21 @@ var SplineClipHelper = class _SplineClipHelper {
|
|
|
19145
19251
|
}
|
|
19146
19252
|
return [sflag, eflag];
|
|
19147
19253
|
}
|
|
19254
|
+
/**
|
|
19255
|
+
* The ORIGINAL edge's arrow end whose TYPE applies at a geometric spline end.
|
|
19256
|
+
*
|
|
19257
|
+
* C resolves both arrow types once from the original edge (arrow_flags) and
|
|
19258
|
+
* encodes each type in its flag, so swapping the two flags when swapEnds is
|
|
19259
|
+
* true carries the type along with it. This port keeps only the presence bit
|
|
19260
|
+
* in the flag and re-reads the type from the `arrowhead`/`arrowtail`
|
|
19261
|
+
* attribute, so the same swap has to be applied to the NAME lookup — without
|
|
19262
|
+
* it a reversed back edge (`C->A [arrowhead=tee]`, head ranked above tail)
|
|
19263
|
+
* draws its `arrowtail` type at the end where C draws the arrowhead.
|
|
19264
|
+
* @see lib/common/splines.c:arrow_clip (if (j) SWAP(sflag, eflag))
|
|
19265
|
+
*/
|
|
19266
|
+
static arrowAttrEnd(isTail, j) {
|
|
19267
|
+
return isTail !== j ? "tail" : "head";
|
|
19268
|
+
}
|
|
19148
19269
|
static resolveArrowFlags(info, hn, fe, orig, j) {
|
|
19149
19270
|
let [sflag, eflag] = _SplineClipHelper.arrowFlags(orig);
|
|
19150
19271
|
if (info.splineMerge != null) {
|
|
@@ -19185,10 +19306,10 @@ var SplineClipHelper = class _SplineClipHelper {
|
|
|
19185
19306
|
* Clip the spline end to the head arrowhead base; records the tip in
|
|
19186
19307
|
* spl.ep. @see lib/common/arrows.c:arrowEndClip
|
|
19187
19308
|
*/
|
|
19188
|
-
static arrowEndClip(e, ps, startp, endpIn, spl, eflag) {
|
|
19309
|
+
static arrowEndClip(e, ps, startp, endpIn, spl, eflag, attrEnd = "head") {
|
|
19189
19310
|
let endp = endpIn;
|
|
19190
19311
|
const elen = arrowClipLength(
|
|
19191
|
-
edgeArrowName(e,
|
|
19312
|
+
edgeArrowName(e, attrEnd),
|
|
19192
19313
|
_SplineClipHelper.clipArrowsize(e),
|
|
19193
19314
|
_SplineClipHelper.clipPenwidth(e)
|
|
19194
19315
|
);
|
|
@@ -19212,10 +19333,10 @@ var SplineClipHelper = class _SplineClipHelper {
|
|
|
19212
19333
|
* Clip the spline start to the tail arrowhead base; records the tip
|
|
19213
19334
|
* in spl.sp. @see lib/common/arrows.c:arrowStartClip
|
|
19214
19335
|
*/
|
|
19215
|
-
static arrowStartClip(e, ps, startpIn, endp, spl, sflag) {
|
|
19336
|
+
static arrowStartClip(e, ps, startpIn, endp, spl, sflag, attrEnd = "tail") {
|
|
19216
19337
|
let startp = startpIn;
|
|
19217
19338
|
const slen = arrowClipLength(
|
|
19218
|
-
edgeArrowName(e,
|
|
19339
|
+
edgeArrowName(e, attrEnd),
|
|
19219
19340
|
_SplineClipHelper.clipArrowsize(e),
|
|
19220
19341
|
_SplineClipHelper.clipPenwidth(e)
|
|
19221
19342
|
);
|
|
@@ -19264,6 +19385,8 @@ var SplineClipHelper = class _SplineClipHelper {
|
|
|
19264
19385
|
const [sflag, eflag] = _SplineClipHelper.resolveArrowFlags(info, hn, fe, orig, j);
|
|
19265
19386
|
newspl.sflag = sflag;
|
|
19266
19387
|
newspl.eflag = eflag;
|
|
19388
|
+
const startEnd = _SplineClipHelper.arrowAttrEnd(true, j);
|
|
19389
|
+
const endEnd = _SplineClipHelper.arrowAttrEnd(false, j);
|
|
19267
19390
|
if (info.isOrtho) {
|
|
19268
19391
|
if (sflag || eflag) {
|
|
19269
19392
|
_SplineClipHelper.arrowOrthoClip(
|
|
@@ -19275,8 +19398,8 @@ var SplineClipHelper = class _SplineClipHelper {
|
|
|
19275
19398
|
sflag,
|
|
19276
19399
|
eflag
|
|
19277
19400
|
);
|
|
19278
|
-
if (sflag) _SplineClipHelper.stashArrow(orig, newspl.sp, ps[bounds.start], true);
|
|
19279
|
-
if (eflag) _SplineClipHelper.stashArrow(orig, newspl.ep, ps[bounds.end + 3], false);
|
|
19401
|
+
if (sflag) _SplineClipHelper.stashArrow(orig, newspl.sp, ps[bounds.start], true, startEnd);
|
|
19402
|
+
if (eflag) _SplineClipHelper.stashArrow(orig, newspl.ep, ps[bounds.end + 3], false, endEnd);
|
|
19280
19403
|
}
|
|
19281
19404
|
return;
|
|
19282
19405
|
}
|
|
@@ -19287,9 +19410,10 @@ var SplineClipHelper = class _SplineClipHelper {
|
|
|
19287
19410
|
bounds.start,
|
|
19288
19411
|
bounds.end,
|
|
19289
19412
|
newspl,
|
|
19290
|
-
sflag
|
|
19413
|
+
sflag,
|
|
19414
|
+
startEnd
|
|
19291
19415
|
);
|
|
19292
|
-
_SplineClipHelper.stashArrow(orig, newspl.sp, ps[bounds.start], true);
|
|
19416
|
+
_SplineClipHelper.stashArrow(orig, newspl.sp, ps[bounds.start], true, startEnd);
|
|
19293
19417
|
}
|
|
19294
19418
|
if (eflag) {
|
|
19295
19419
|
bounds.end = _SplineClipHelper.arrowEndClip(
|
|
@@ -19298,9 +19422,10 @@ var SplineClipHelper = class _SplineClipHelper {
|
|
|
19298
19422
|
bounds.start,
|
|
19299
19423
|
bounds.end,
|
|
19300
19424
|
newspl,
|
|
19301
|
-
eflag
|
|
19425
|
+
eflag,
|
|
19426
|
+
endEnd
|
|
19302
19427
|
);
|
|
19303
|
-
_SplineClipHelper.stashArrow(orig, newspl.ep, ps[bounds.end + 3], false);
|
|
19428
|
+
_SplineClipHelper.stashArrow(orig, newspl.ep, ps[bounds.end + 3], false, endEnd);
|
|
19304
19429
|
}
|
|
19305
19430
|
}
|
|
19306
19431
|
/**
|
|
@@ -19399,9 +19524,10 @@ var SplineClipHelper = class _SplineClipHelper {
|
|
|
19399
19524
|
}
|
|
19400
19525
|
}
|
|
19401
19526
|
/** Record an arrow polygon on the edge for svgArrowPolygons. */
|
|
19402
|
-
static stashArrow(e, tip2, base, isTail) {
|
|
19527
|
+
static stashArrow(e, tip2, base, isTail, attrEnd) {
|
|
19403
19528
|
const dir = { x: base.x - tip2.x, y: base.y - tip2.y };
|
|
19404
|
-
const
|
|
19529
|
+
const end = attrEnd ?? (isTail ? "tail" : "head");
|
|
19530
|
+
const ops = arrowDrawOpsForEnd(e, end, tip2, dir, _SplineClipHelper.renderPenwidth(e));
|
|
19405
19531
|
if (isTail) e.info.tailArrowOps = ops;
|
|
19406
19532
|
else e.info.headArrowOps = ops;
|
|
19407
19533
|
}
|
|
@@ -23456,7 +23582,7 @@ function applyLabel(e, g, fi, measurer) {
|
|
|
23456
23582
|
const content = isHtml ? htmlValueContent(str) : str;
|
|
23457
23583
|
e.info.label = makeAnyLabel(content, isHtml, fi, measurer, e);
|
|
23458
23584
|
g.root.info.has_labels |= EDGE_LABEL;
|
|
23459
|
-
e.info.label_ontop = mapbool3(e.attrs.get("
|
|
23585
|
+
e.info.label_ontop = mapbool3(e.attrs.get("labelfloat")) ? 1 : 0;
|
|
23460
23586
|
}
|
|
23461
23587
|
function applyXLabel(e, g, fi, measurer) {
|
|
23462
23588
|
const str = e.attrs.get("xlabel");
|
|
@@ -24408,60 +24534,89 @@ function commonInitNodeEdge(g) {
|
|
|
24408
24534
|
|
|
24409
24535
|
// src/common/ps-fontalias.ts
|
|
24410
24536
|
var ALIAS_ROWS = [
|
|
24411
|
-
["AvantGarde-Book", "URW Gothic L", "book", null, null, "sans-Serif"],
|
|
24412
|
-
["AvantGarde-BookOblique", "URW Gothic L", "book", null, "oblique", "sans-Serif"],
|
|
24413
|
-
["AvantGarde-Demi", "URW Gothic L", "demi", null, null, "sans-Serif"],
|
|
24414
|
-
["AvantGarde-DemiOblique", "URW Gothic L", "demi", null, "oblique", "sans-Serif"],
|
|
24415
|
-
["Bookman-Demi", "URW Bookman L", "demi", null, null, "serif"],
|
|
24416
|
-
["Bookman-DemiItalic", "URW Bookman L", "demi", null, "italic", "serif"],
|
|
24417
|
-
["Bookman-Light", "URW Bookman L", "light", null, null, "serif"],
|
|
24418
|
-
["Bookman-LightItalic", "URW Bookman L", "light", null, "italic", "serif"],
|
|
24419
|
-
["Courier", "Courier", null, null, null, "monospace"],
|
|
24420
|
-
["Courier-Bold", "Courier", "bold", null, null, "monospace"],
|
|
24421
|
-
["Courier-BoldOblique", "Courier", "bold", null, "oblique", "monospace"],
|
|
24422
|
-
["Courier-Oblique", "Courier", null, null, "oblique", "monospace"],
|
|
24423
|
-
["Helvetica", "Helvetica", null, null, null, "sans-Serif"],
|
|
24424
|
-
["Helvetica-Bold", "Helvetica", "bold", null, null, "sans-Serif"],
|
|
24425
|
-
["Helvetica-BoldOblique", "Helvetica", "bold", null, "oblique", "sans-Serif"],
|
|
24426
|
-
["Helvetica-Narrow", "Helvetica", null, "condensed", null, "sans-Serif"],
|
|
24427
|
-
["Helvetica-Narrow-Bold", "Helvetica", "bold", "condensed", null, "sans-Serif"],
|
|
24428
|
-
["Helvetica-Narrow-BoldOblique", "Helvetica", "bold", "condensed", "oblique", "sans-Serif"],
|
|
24429
|
-
["Helvetica-Narrow-Oblique", "Helvetica", null, "condensed", "oblique", "sans-Serif"],
|
|
24430
|
-
["Helvetica-Oblique", "Helvetica", null, null, "oblique", "sans-Serif"],
|
|
24431
|
-
["NewCenturySchlbk-Bold", "Century Schoolbook L", "bold", null, null, "serif"],
|
|
24432
|
-
["NewCenturySchlbk-BoldItalic", "Century Schoolbook L", "bold", null, "italic", "serif"],
|
|
24433
|
-
["NewCenturySchlbk-Italic", "Century Schoolbook L", null, null, "italic", "serif"],
|
|
24434
|
-
["NewCenturySchlbk-Roman", "Century Schoolbook L", "roman", null, null, "serif"],
|
|
24435
|
-
["Palatino-Bold", "Palatino Linotype", "bold", null, null, "serif"],
|
|
24436
|
-
["Palatino-BoldItalic", "Palatino Linotype", "bold", null, "italic", "serif"],
|
|
24437
|
-
["Palatino-Italic", "Palatino Linotype", null, null, "italic", "serif"],
|
|
24438
|
-
["Palatino-Roman", "Palatino Linotype", "roman", null, null, "serif"],
|
|
24439
|
-
["Symbol", "Symbol", null, null, null, "fantasy"],
|
|
24440
|
-
["Times-Bold", "Times", "bold", null, null, "serif"],
|
|
24441
|
-
["Times-BoldItalic", "Times", "bold", null, "italic", "serif"],
|
|
24442
|
-
["Times-Italic", "Times", null, null, "italic", "serif"],
|
|
24443
|
-
["Times-Roman", "Times", null, null, null, "serif"],
|
|
24444
|
-
["ZapfChancery-MediumItalic", "URW Chancery L", "medium", null, "italic", "serif"],
|
|
24445
|
-
["ZapfDingbats", "Dingbats", null, null, null, "fantasy"]
|
|
24537
|
+
["AvantGarde-Book", "URW Gothic L", "book", null, null, "sans-Serif", null, null],
|
|
24538
|
+
["AvantGarde-BookOblique", "URW Gothic L", "book", null, "oblique", "sans-Serif", null, "italic"],
|
|
24539
|
+
["AvantGarde-Demi", "URW Gothic L", "demi", null, null, "sans-Serif", "bold", null],
|
|
24540
|
+
["AvantGarde-DemiOblique", "URW Gothic L", "demi", null, "oblique", "sans-Serif", "bold", "italic"],
|
|
24541
|
+
["Bookman-Demi", "URW Bookman L", "demi", null, null, "serif", "bold", null],
|
|
24542
|
+
["Bookman-DemiItalic", "URW Bookman L", "demi", null, "italic", "serif", "bold", "italic"],
|
|
24543
|
+
["Bookman-Light", "URW Bookman L", "light", null, null, "serif", null, null],
|
|
24544
|
+
["Bookman-LightItalic", "URW Bookman L", "light", null, "italic", "serif", null, "italic"],
|
|
24545
|
+
["Courier", "Courier", null, null, null, "monospace", null, null],
|
|
24546
|
+
["Courier-Bold", "Courier", "bold", null, null, "monospace", "bold", null],
|
|
24547
|
+
["Courier-BoldOblique", "Courier", "bold", null, "oblique", "monospace", "bold", "italic"],
|
|
24548
|
+
["Courier-Oblique", "Courier", null, null, "oblique", "monospace", null, "italic"],
|
|
24549
|
+
["Helvetica", "Helvetica", null, null, null, "sans-Serif", null, null],
|
|
24550
|
+
["Helvetica-Bold", "Helvetica", "bold", null, null, "sans-Serif", "bold", null],
|
|
24551
|
+
["Helvetica-BoldOblique", "Helvetica", "bold", null, "oblique", "sans-Serif", "bold", "italic"],
|
|
24552
|
+
["Helvetica-Narrow", "Helvetica", null, "condensed", null, "sans-Serif", null, null],
|
|
24553
|
+
["Helvetica-Narrow-Bold", "Helvetica", "bold", "condensed", null, "sans-Serif", "bold", null],
|
|
24554
|
+
["Helvetica-Narrow-BoldOblique", "Helvetica", "bold", "condensed", "oblique", "sans-Serif", "bold", "italic"],
|
|
24555
|
+
["Helvetica-Narrow-Oblique", "Helvetica", null, "condensed", "oblique", "sans-Serif", null, "italic"],
|
|
24556
|
+
["Helvetica-Oblique", "Helvetica", null, null, "oblique", "sans-Serif", null, "italic"],
|
|
24557
|
+
["NewCenturySchlbk-Bold", "Century Schoolbook L", "bold", null, null, "serif", "bold", null],
|
|
24558
|
+
["NewCenturySchlbk-BoldItalic", "Century Schoolbook L", "bold", null, "italic", "serif", "bold", "italic"],
|
|
24559
|
+
["NewCenturySchlbk-Italic", "Century Schoolbook L", null, null, "italic", "serif", null, "italic"],
|
|
24560
|
+
["NewCenturySchlbk-Roman", "Century Schoolbook L", "roman", null, null, "serif", null, null],
|
|
24561
|
+
["Palatino-Bold", "Palatino Linotype", "bold", null, null, "serif", "bold", null],
|
|
24562
|
+
["Palatino-BoldItalic", "Palatino Linotype", "bold", null, "italic", "serif", "bold", "italic"],
|
|
24563
|
+
["Palatino-Italic", "Palatino Linotype", null, null, "italic", "serif", null, "italic"],
|
|
24564
|
+
["Palatino-Roman", "Palatino Linotype", "roman", null, null, "serif", null, null],
|
|
24565
|
+
["Symbol", "Symbol", null, null, null, "fantasy", null, null],
|
|
24566
|
+
["Times-Bold", "Times", "bold", null, null, "serif", "bold", null],
|
|
24567
|
+
["Times-BoldItalic", "Times", "bold", null, "italic", "serif", "bold", "italic"],
|
|
24568
|
+
["Times-Italic", "Times", null, null, "italic", "serif", null, "italic"],
|
|
24569
|
+
["Times-Roman", "Times", null, null, null, "serif", null, null],
|
|
24570
|
+
["ZapfChancery-MediumItalic", "URW Chancery L", "medium", null, "italic", "serif", null, "italic"],
|
|
24571
|
+
["ZapfDingbats", "Dingbats", null, null, null, "fantasy", null, null]
|
|
24446
24572
|
];
|
|
24447
24573
|
var ALIAS_BY_NAME = new Map(
|
|
24448
24574
|
ALIAS_ROWS.map((r) => [
|
|
24449
24575
|
r[0].toLowerCase(),
|
|
24450
|
-
{
|
|
24576
|
+
{
|
|
24577
|
+
name: r[0],
|
|
24578
|
+
family: r[1],
|
|
24579
|
+
weight: r[2],
|
|
24580
|
+
stretch: r[3],
|
|
24581
|
+
style: r[4],
|
|
24582
|
+
svgFontFamily: r[5],
|
|
24583
|
+
svgFontWeight: r[6],
|
|
24584
|
+
svgFontStyle: r[7]
|
|
24585
|
+
}
|
|
24451
24586
|
])
|
|
24452
24587
|
);
|
|
24453
24588
|
function translatePostscriptFontname(name) {
|
|
24454
24589
|
return ALIAS_BY_NAME.get(name.toLowerCase()) ?? null;
|
|
24455
24590
|
}
|
|
24456
|
-
function fontFamilyAttrs(fontName) {
|
|
24591
|
+
function fontFamilyAttrs(fontName, mode = FontnameKind.NativeFonts) {
|
|
24457
24592
|
const a = fontName !== null ? translatePostscriptFontname(fontName) : null;
|
|
24458
24593
|
if (a === null) return null;
|
|
24459
|
-
|
|
24594
|
+
let family;
|
|
24595
|
+
let weight;
|
|
24596
|
+
let style;
|
|
24597
|
+
switch (mode) {
|
|
24598
|
+
case FontnameKind.PsFonts:
|
|
24599
|
+
family = a.name;
|
|
24600
|
+
weight = a.weight;
|
|
24601
|
+
style = a.style;
|
|
24602
|
+
break;
|
|
24603
|
+
case FontnameKind.SvgFonts:
|
|
24604
|
+
family = a.svgFontFamily;
|
|
24605
|
+
weight = a.svgFontWeight;
|
|
24606
|
+
style = a.svgFontStyle;
|
|
24607
|
+
break;
|
|
24608
|
+
default:
|
|
24609
|
+
family = a.family;
|
|
24610
|
+
weight = a.weight;
|
|
24611
|
+
style = a.style;
|
|
24612
|
+
break;
|
|
24613
|
+
}
|
|
24614
|
+
const fam = a.svgFontFamily !== family ? family + "," + a.svgFontFamily : family;
|
|
24460
24615
|
let attrs = ' font-family="' + fam + '"';
|
|
24461
|
-
if (
|
|
24616
|
+
if (weight !== null) attrs += ' font-weight="' + weight + '"';
|
|
24462
24617
|
if (a.stretch !== null) attrs += ' font-stretch="' + a.stretch + '"';
|
|
24463
|
-
if (
|
|
24464
|
-
return { attrs, weight:
|
|
24618
|
+
if (style !== null) attrs += ' font-style="' + style + '"';
|
|
24619
|
+
return { attrs, weight: weight !== null, style: style !== null };
|
|
24465
24620
|
}
|
|
24466
24621
|
|
|
24467
24622
|
// src/render/svg-gradient.ts
|
|
@@ -25106,6 +25261,7 @@ function emitBgcolorBackground(g, bb, bgcolorAttr, job) {
|
|
|
25106
25261
|
function svgBeginGraph(g, job) {
|
|
25107
25262
|
job.devscale = { x: 1, y: -1 };
|
|
25108
25263
|
job.directed = g.kind === "directed" || g.kind === "strict-directed";
|
|
25264
|
+
job.fontnames = g.info.fontnames ?? FontnameKind.NativeFonts;
|
|
25109
25265
|
job.drawingId = g.attrs.get("id") ?? "";
|
|
25110
25266
|
job.write(SVG_XML_DECL);
|
|
25111
25267
|
const stylesheet = g.attrs.get("stylesheet");
|
|
@@ -25395,7 +25551,7 @@ function svgTextspan(pos, span, job) {
|
|
|
25395
25551
|
job.write('" y="');
|
|
25396
25552
|
job.printDouble(y);
|
|
25397
25553
|
job.write('"');
|
|
25398
|
-
const ff = fontFamilyAttrs(span.fontName);
|
|
25554
|
+
const ff = fontFamilyAttrs(span.fontName, job.fontnames);
|
|
25399
25555
|
if (ff !== null) job.write(ff.attrs);
|
|
25400
25556
|
else job.write(' font-family="' + escapeXml(span.fontName ?? "Times,serif") + '"');
|
|
25401
25557
|
emitFontAttrs(span.fontFlags, job, ff ?? { weight: false, style: false });
|
|
@@ -26145,17 +26301,19 @@ function nodeInBox(n, job) {
|
|
|
26145
26301
|
function emitNodeBody(n, renderer, job) {
|
|
26146
26302
|
setHtmlAnchorObj(svgNodeId(n, job), labelTextOf2(n.info.label), job.obj ?? void 0);
|
|
26147
26303
|
setHtmlObjImgscale(nodeAttr(n, n.root, "imagescale"));
|
|
26148
|
-
|
|
26149
|
-
|
|
26150
|
-
|
|
26151
|
-
|
|
26152
|
-
|
|
26304
|
+
withColorScheme(nodeAttr(n, n.root, "colorscheme"), () => {
|
|
26305
|
+
renderer.beginNode(n, job);
|
|
26306
|
+
const shape = n.info.shape;
|
|
26307
|
+
if (shape?.fns?.codefn) shape.fns.codefn(job, n);
|
|
26308
|
+
renderNodeXLabel(n, renderer, job);
|
|
26309
|
+
renderer.endNode(n, job);
|
|
26310
|
+
});
|
|
26153
26311
|
}
|
|
26154
26312
|
function renderNode(n, renderer, job, done) {
|
|
26155
26313
|
if (done.has(n)) return;
|
|
26156
26314
|
done.add(n);
|
|
26157
26315
|
if (!nodeInBox(n, job)) return;
|
|
26158
|
-
const nodeStyle = n.
|
|
26316
|
+
const nodeStyle = nodeAttr(n, n.root, "style");
|
|
26159
26317
|
if (parseStyleFlags(nodeStyle).invis) return;
|
|
26160
26318
|
if (!isPointNode(n) && styleHasInvisibleAlias(nodeStyle)) return;
|
|
26161
26319
|
const obj = createObjState(2 /* Node */);
|
|
@@ -37745,18 +37903,18 @@ function buildSubgraph(root, nodes, name) {
|
|
|
37745
37903
|
sg.edges = root.edges.filter((e) => ns.has(e.tail) && ns.has(e.head));
|
|
37746
37904
|
return sg;
|
|
37747
37905
|
}
|
|
37748
|
-
function ccomps(g,
|
|
37906
|
+
function ccomps(g, pfx) {
|
|
37749
37907
|
const visited = /* @__PURE__ */ new Set();
|
|
37750
37908
|
const result = [];
|
|
37751
37909
|
let idx = 0;
|
|
37752
37910
|
for (const n of g.nodes.values()) {
|
|
37753
37911
|
if (visited.has(n.name)) continue;
|
|
37754
37912
|
const comp = dfsCollect(g, n, visited);
|
|
37755
|
-
result.push(buildSubgraph(g, comp, `${
|
|
37913
|
+
result.push(buildSubgraph(g, comp, `${pfx}_${idx++}`));
|
|
37756
37914
|
}
|
|
37757
37915
|
return result;
|
|
37758
37916
|
}
|
|
37759
|
-
function pccomps(g,
|
|
37917
|
+
function pccomps(g, pfx) {
|
|
37760
37918
|
const visited = /* @__PURE__ */ new Set();
|
|
37761
37919
|
const result = [];
|
|
37762
37920
|
let idx = 0;
|
|
@@ -37768,12 +37926,12 @@ function pccomps(g, pfx2) {
|
|
|
37768
37926
|
pinned = true;
|
|
37769
37927
|
}
|
|
37770
37928
|
if (pinnedNodes.length > 0) {
|
|
37771
|
-
result.push(buildSubgraph(g, pinnedNodes, `${
|
|
37929
|
+
result.push(buildSubgraph(g, pinnedNodes, `${pfx}_${idx++}`));
|
|
37772
37930
|
}
|
|
37773
37931
|
for (const n of g.nodes.values()) {
|
|
37774
37932
|
if (visited.has(n.name)) continue;
|
|
37775
37933
|
const comp = dfsCollect(g, n, visited);
|
|
37776
|
-
result.push(buildSubgraph(g, comp, `${
|
|
37934
|
+
result.push(buildSubgraph(g, comp, `${pfx}_${idx++}`));
|
|
37777
37935
|
}
|
|
37778
37936
|
return { graphs: result, pinned };
|
|
37779
37937
|
}
|
|
@@ -38047,12 +38205,6 @@ function setEdgeTypeFromAttr(g, defaultValue) {
|
|
|
38047
38205
|
else et = edgeTypeFromString(s, defaultValue);
|
|
38048
38206
|
setEdgeType(g, et);
|
|
38049
38207
|
}
|
|
38050
|
-
function getAttrInt(g, key, defaultVal) {
|
|
38051
|
-
const v = g.attrs.get(key);
|
|
38052
|
-
if (v === void 0 || v === "") return defaultVal;
|
|
38053
|
-
const n = parseInt(v, 10);
|
|
38054
|
-
return isNaN(n) ? defaultVal : n;
|
|
38055
|
-
}
|
|
38056
38208
|
function dotPhaseInit(g) {
|
|
38057
38209
|
dotGraphInit(g);
|
|
38058
38210
|
setEdgeTypeFromAttr(g, EDGETYPE_SPLINE);
|
|
@@ -38070,17 +38222,33 @@ function dotPhasePost(g) {
|
|
|
38070
38222
|
dotPhasePostNoFinish(g);
|
|
38071
38223
|
gvPostprocess(g);
|
|
38072
38224
|
}
|
|
38225
|
+
function graphPhase(g) {
|
|
38226
|
+
const v = g.attrs.get("phase");
|
|
38227
|
+
if (v === void 0 || v === "") return -1;
|
|
38228
|
+
const n = parseInt(v, 10);
|
|
38229
|
+
if (isNaN(n) || n > 2147483647) return -1;
|
|
38230
|
+
return n < 1 ? 1 : n;
|
|
38231
|
+
}
|
|
38073
38232
|
function dotLayoutPipeline(g) {
|
|
38074
|
-
const maxphase =
|
|
38233
|
+
const maxphase = graphPhase(g);
|
|
38075
38234
|
dotPhaseInit(g);
|
|
38076
38235
|
dotRank(g);
|
|
38077
|
-
if (maxphase === 1)
|
|
38236
|
+
if (maxphase === 1) {
|
|
38237
|
+
gvPostprocess(g);
|
|
38238
|
+
return;
|
|
38239
|
+
}
|
|
38078
38240
|
const rc = dotMincross(g);
|
|
38079
38241
|
if (rc !== 0) return;
|
|
38080
|
-
if (maxphase === 2)
|
|
38242
|
+
if (maxphase === 2) {
|
|
38243
|
+
gvPostprocess(g);
|
|
38244
|
+
return;
|
|
38245
|
+
}
|
|
38081
38246
|
const posRc = dotPosition(g);
|
|
38082
38247
|
if (posRc !== 0) return;
|
|
38083
|
-
if (maxphase === 3)
|
|
38248
|
+
if (maxphase === 3) {
|
|
38249
|
+
gvPostprocess(g);
|
|
38250
|
+
return;
|
|
38251
|
+
}
|
|
38084
38252
|
dotPhasePost(g);
|
|
38085
38253
|
}
|
|
38086
38254
|
var CL_OFFSET2 = 8;
|
|
@@ -44668,6 +44836,11 @@ function tuneControl(g, ctrl) {
|
|
|
44668
44836
|
ctrl.beautifyLeaves = mapBool(aggetGraph(g, "beautify"), false);
|
|
44669
44837
|
ctrl.doShrinking = mapBool(aggetGraph(g, "overlap_shrink"), true);
|
|
44670
44838
|
ctrl.rotation = lateDouble(aggetGraph(g, "rotation"), 0, -Number.MAX_VALUE);
|
|
44839
|
+
if (ctrl.rotation !== 0) {
|
|
44840
|
+
throw new Error(
|
|
44841
|
+
`sfdp rotation="${ctrl.rotation}": rotate() is not ported (unreachable at sfdp defaults); see plans/port-catalog/README.md`
|
|
44842
|
+
);
|
|
44843
|
+
}
|
|
44671
44844
|
ctrl.edgeLabelingScheme = lateInt(aggetGraph(g, "label_scheme"), 0, 0);
|
|
44672
44845
|
if (ctrl.edgeLabelingScheme > 4) ctrl.edgeLabelingScheme = 0;
|
|
44673
44846
|
}
|
|
@@ -50103,321 +50276,1480 @@ function taper(bez, radfunc, initwid) {
|
|
|
50103
50276
|
return emitStroke(pp);
|
|
50104
50277
|
}
|
|
50105
50278
|
|
|
50106
|
-
// src/render/
|
|
50107
|
-
var
|
|
50108
|
-
|
|
50109
|
-
|
|
50110
|
-
|
|
50111
|
-
|
|
50112
|
-
|
|
50113
|
-
|
|
50114
|
-
|
|
50115
|
-
|
|
50116
|
-
|
|
50117
|
-
|
|
50118
|
-
|
|
50119
|
-
|
|
50120
|
-
|
|
50121
|
-
|
|
50122
|
-
|
|
50123
|
-
const dot = s.indexOf(".");
|
|
50124
|
-
if (dot < 0) return s;
|
|
50125
|
-
let end = s.length;
|
|
50126
|
-
while (end > dot + 1 && s[end - 1] === "0") end--;
|
|
50127
|
-
if (s[end - 1] === ".") end--;
|
|
50128
|
-
const t = s.slice(0, end);
|
|
50129
|
-
if (t.startsWith("0.")) return t.slice(1);
|
|
50130
|
-
if (t.startsWith("-0.")) return "-" + t.slice(2);
|
|
50131
|
-
return t;
|
|
50132
|
-
}
|
|
50133
|
-
function printNum(n) {
|
|
50134
|
-
if (Math.abs(n) > MAX_NEGNUM) {
|
|
50135
|
-
return n < 0 ? String(-MAX_NEGNUM) : String(MAX_NEGNUM);
|
|
50279
|
+
// src/render/map.ts
|
|
50280
|
+
var DQ = '"';
|
|
50281
|
+
function exactDecimalDigits2(v) {
|
|
50282
|
+
const dv = new DataView(new ArrayBuffer(8));
|
|
50283
|
+
dv.setFloat64(0, Math.abs(v));
|
|
50284
|
+
const bits = dv.getBigUint64(0);
|
|
50285
|
+
const expBits = Number(bits >> 52n & 0x7ffn);
|
|
50286
|
+
const mant = bits & 0xfffffffffffffn;
|
|
50287
|
+
const m = expBits === 0 ? mant : mant | 1n << 52n;
|
|
50288
|
+
const e = (expBits === 0 ? 1 : expBits) - 1075;
|
|
50289
|
+
let intDigits;
|
|
50290
|
+
let fracLen = 0;
|
|
50291
|
+
if (e >= 0) {
|
|
50292
|
+
intDigits = (m << BigInt(e)).toString();
|
|
50293
|
+
} else {
|
|
50294
|
+
intDigits = (m * 5n ** BigInt(-e)).toString();
|
|
50295
|
+
fracLen = -e;
|
|
50136
50296
|
}
|
|
50137
|
-
if (
|
|
50138
|
-
|
|
50139
|
-
}
|
|
50140
|
-
function makeXbufs() {
|
|
50141
|
-
const bufs = Array.from({ length: NUM_XBUFS + 4 }, () => []);
|
|
50142
|
-
bufs[8 /* NDraw */] = bufs[1 /* CDraw */];
|
|
50143
|
-
bufs[9 /* EDraw */] = bufs[1 /* CDraw */];
|
|
50144
|
-
bufs[10 /* NLabel */] = bufs[5 /* CLabel */];
|
|
50145
|
-
bufs[11 /* ELabel */] = bufs[5 /* CLabel */];
|
|
50146
|
-
return bufs;
|
|
50147
|
-
}
|
|
50148
|
-
function xdotNum(v) {
|
|
50149
|
-
let s = toFixed2HalfEven(v);
|
|
50150
|
-
if (s.indexOf(".") >= 0) {
|
|
50151
|
-
let end = s.length;
|
|
50152
|
-
while (end > 0 && s[end - 1] === "0") end--;
|
|
50153
|
-
if (s[end - 1] === ".") end--;
|
|
50154
|
-
s = s.slice(0, end);
|
|
50297
|
+
if (intDigits.length <= fracLen) {
|
|
50298
|
+
intDigits = "0".repeat(fracLen - intDigits.length + 1) + intDigits;
|
|
50155
50299
|
}
|
|
50156
|
-
|
|
50157
|
-
|
|
50158
|
-
|
|
50159
|
-
return xdotNum(p.x) + " " + xdotNum(p.y) + " ";
|
|
50160
|
-
}
|
|
50161
|
-
function xdotPoints(c, pts) {
|
|
50162
|
-
let s = c + " " + String(pts.length) + " ";
|
|
50163
|
-
for (const p of pts) s += xdotPoint(p);
|
|
50164
|
-
return s;
|
|
50300
|
+
const pointPos = intDigits.length - fracLen;
|
|
50301
|
+
const firstSig = intDigits.search(/[1-9]/);
|
|
50302
|
+
return { digits: intDigits.slice(firstSig), exp10: pointPos - firstSig - 1 };
|
|
50165
50303
|
}
|
|
50166
|
-
function
|
|
50167
|
-
|
|
50168
|
-
|
|
50169
|
-
|
|
50170
|
-
|
|
50171
|
-
|
|
50172
|
-
|
|
50173
|
-
|
|
50174
|
-
|
|
50175
|
-
|
|
50304
|
+
function printG5(v) {
|
|
50305
|
+
if (v === 0 || !Number.isFinite(v)) return String(v);
|
|
50306
|
+
const P = 5;
|
|
50307
|
+
const { digits, exp10 } = exactDecimalDigits2(v);
|
|
50308
|
+
let sig = digits.slice(0, P);
|
|
50309
|
+
if (sig.length < P) sig += "0".repeat(P - sig.length);
|
|
50310
|
+
const rest = digits.slice(P);
|
|
50311
|
+
const restNonzeroAfterFirst = /[1-9]/.test(rest.slice(1));
|
|
50312
|
+
const roundUp = rest.length > 0 && (rest[0] > "5" || rest[0] === "5" && restNonzeroAfterFirst || rest[0] === "5" && !restNonzeroAfterFirst && (sig.charCodeAt(P - 1) - 48) % 2 === 1);
|
|
50313
|
+
let exp = exp10;
|
|
50314
|
+
if (roundUp) {
|
|
50315
|
+
const n = String(BigInt(sig) + 1n);
|
|
50316
|
+
sig = n.length > P ? (exp++, n.slice(0, P)) : n;
|
|
50176
50317
|
}
|
|
50177
|
-
|
|
50318
|
+
const sign2 = v < 0 ? "-" : "";
|
|
50319
|
+
if (exp < -4 || exp >= P) {
|
|
50320
|
+
const mant = (sig[0] + "." + sig.slice(1)).replace(/\.?0+$/, "");
|
|
50321
|
+
const as = Math.abs(exp);
|
|
50322
|
+
return sign2 + mant + "e" + (exp < 0 ? "-" : "+") + (as < 10 ? "0" : "") + String(as);
|
|
50323
|
+
}
|
|
50324
|
+
let s;
|
|
50325
|
+
if (exp >= sig.length - 1) {
|
|
50326
|
+
s = sig + "0".repeat(exp - sig.length + 1);
|
|
50327
|
+
} else if (exp >= 0) {
|
|
50328
|
+
s = sig.slice(0, exp + 1) + "." + sig.slice(exp + 1);
|
|
50329
|
+
} else {
|
|
50330
|
+
s = "0." + "0".repeat(-exp - 1) + sig;
|
|
50331
|
+
}
|
|
50332
|
+
if (s.includes(".")) s = s.replace(/\.?0+$/, "");
|
|
50333
|
+
return sign2 + s;
|
|
50178
50334
|
}
|
|
50179
|
-
function
|
|
50180
|
-
return
|
|
50335
|
+
function plainCoord(v) {
|
|
50336
|
+
return printG5(v / 72);
|
|
50181
50337
|
}
|
|
50182
|
-
|
|
50183
|
-
|
|
50184
|
-
|
|
50185
|
-
|
|
50186
|
-
colorxlate(c.type === "string" ? c.s : "", out, "rgba");
|
|
50187
|
-
return out.type === "rgba" ? [chanByte(out.r), chanByte(out.g), chanByte(out.b), chanByte(out.a)] : [0, 0, 0, 255];
|
|
50338
|
+
var CANON_KEYWORDS = ["node", "edge", "strict", "graph", "digraph", "subgraph"];
|
|
50339
|
+
var MAX_OUTPUTLINE = 128;
|
|
50340
|
+
function isDigitByte(b) {
|
|
50341
|
+
return b >= 48 && b <= 57;
|
|
50188
50342
|
}
|
|
50189
|
-
function
|
|
50190
|
-
|
|
50191
|
-
const body = "#" + hx(rgba[0]) + hx(rgba[1]) + hx(rgba[2]);
|
|
50192
|
-
return rgba[3] === 255 ? "7 -" + body : "9 -" + body + hx(rgba[3]);
|
|
50343
|
+
function isAlnumByte(b) {
|
|
50344
|
+
return isDigitByte(b) || b >= 65 && b <= 90 || b >= 97 && b <= 122;
|
|
50193
50345
|
}
|
|
50194
|
-
function
|
|
50195
|
-
return
|
|
50346
|
+
function isIdCharByte(b) {
|
|
50347
|
+
return isAlnumByte(b) || b === 46 || b === 45 || b >= 128;
|
|
50196
50348
|
}
|
|
50197
|
-
function
|
|
50198
|
-
|
|
50199
|
-
const
|
|
50200
|
-
return
|
|
50349
|
+
function isEscapeAt(bytes, i) {
|
|
50350
|
+
if (bytes[i] !== 92) return false;
|
|
50351
|
+
const c = bytes[i + 1];
|
|
50352
|
+
return c !== void 0 && 'EGHLNTlnr\\"'.includes(String.fromCharCode(c));
|
|
50201
50353
|
}
|
|
50202
|
-
function
|
|
50203
|
-
|
|
50204
|
-
|
|
50205
|
-
const stops = frac > 0 ? [[frac, fill], [frac, stop]] : [[0, fill], [1, stop]];
|
|
50206
|
-
return stops.map(([f, c]) => trimFixed3(f) + " " + xdotColorBody(c)).join(" ");
|
|
50354
|
+
function agstrcanon(arg) {
|
|
50355
|
+
if (isHtmlValue(arg)) return "<" + htmlValueContent(arg) + ">";
|
|
50356
|
+
return agstrcanonText(arg);
|
|
50207
50357
|
}
|
|
50208
|
-
function
|
|
50209
|
-
|
|
50210
|
-
const
|
|
50211
|
-
const
|
|
50212
|
-
|
|
50213
|
-
|
|
50214
|
-
|
|
50215
|
-
|
|
50216
|
-
|
|
50217
|
-
|
|
50218
|
-
|
|
50358
|
+
function agstrcanonText(arg) {
|
|
50359
|
+
if (arg.length === 0) return '""';
|
|
50360
|
+
const bytes = new TextEncoder().encode(arg);
|
|
50361
|
+
const out = [34];
|
|
50362
|
+
let needsQuotes = false;
|
|
50363
|
+
let partOfEscape = false;
|
|
50364
|
+
let backslashPending = false;
|
|
50365
|
+
let cnt = 0;
|
|
50366
|
+
let dotcnt = 0;
|
|
50367
|
+
let maybeNum = isDigitByte(bytes[0]) || bytes[0] === 46 || bytes[0] === 45;
|
|
50368
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
50369
|
+
const uc = bytes[i];
|
|
50370
|
+
if (uc === 34 && !partOfEscape) {
|
|
50371
|
+
out.push(92);
|
|
50372
|
+
needsQuotes = true;
|
|
50373
|
+
} else if (!partOfEscape && isEscapeAt(bytes, i)) {
|
|
50374
|
+
needsQuotes = true;
|
|
50375
|
+
partOfEscape = true;
|
|
50376
|
+
} else if (maybeNum) {
|
|
50377
|
+
if (uc === 45) {
|
|
50378
|
+
if (cnt) {
|
|
50379
|
+
maybeNum = false;
|
|
50380
|
+
needsQuotes = true;
|
|
50381
|
+
}
|
|
50382
|
+
} else if (uc === 46) {
|
|
50383
|
+
if (dotcnt++) {
|
|
50384
|
+
maybeNum = false;
|
|
50385
|
+
needsQuotes = true;
|
|
50386
|
+
}
|
|
50387
|
+
} else if (!isDigitByte(uc)) {
|
|
50388
|
+
maybeNum = false;
|
|
50389
|
+
needsQuotes = true;
|
|
50390
|
+
}
|
|
50391
|
+
partOfEscape = false;
|
|
50392
|
+
} else if (!(isAlnumByte(uc) || uc === 95 || uc >= 128)) {
|
|
50393
|
+
needsQuotes = true;
|
|
50394
|
+
partOfEscape = false;
|
|
50395
|
+
} else {
|
|
50396
|
+
partOfEscape = false;
|
|
50397
|
+
}
|
|
50398
|
+
out.push(uc);
|
|
50399
|
+
cnt++;
|
|
50400
|
+
const next = bytes[i + 1];
|
|
50401
|
+
if (next !== void 0) {
|
|
50402
|
+
const last = out[out.length - 1];
|
|
50403
|
+
const canBreak = !(isIdCharByte(last) || last === 92) && isIdCharByte(next);
|
|
50404
|
+
if (backslashPending && canBreak) {
|
|
50405
|
+
out.push(92, 10);
|
|
50406
|
+
needsQuotes = true;
|
|
50407
|
+
backslashPending = false;
|
|
50408
|
+
cnt = 0;
|
|
50409
|
+
} else if (cnt >= MAX_OUTPUTLINE) {
|
|
50410
|
+
if (canBreak) {
|
|
50411
|
+
out.push(92, 10);
|
|
50412
|
+
needsQuotes = true;
|
|
50413
|
+
cnt = 0;
|
|
50414
|
+
} else {
|
|
50415
|
+
backslashPending = true;
|
|
50416
|
+
}
|
|
50417
|
+
}
|
|
50418
|
+
}
|
|
50419
|
+
}
|
|
50420
|
+
out.push(34);
|
|
50421
|
+
const first = bytes[0];
|
|
50422
|
+
if (needsQuotes || cnt === 1 && (first === 46 || first === 45)) {
|
|
50423
|
+
return new TextDecoder().decode(new Uint8Array(out));
|
|
50424
|
+
}
|
|
50425
|
+
const lower = arg.toLowerCase();
|
|
50426
|
+
for (const tok of CANON_KEYWORDS) {
|
|
50427
|
+
if (tok === lower) return new TextDecoder().decode(new Uint8Array(out));
|
|
50428
|
+
}
|
|
50429
|
+
return arg;
|
|
50219
50430
|
}
|
|
50220
|
-
function
|
|
50221
|
-
return
|
|
50431
|
+
function lateNN(v, def) {
|
|
50432
|
+
return v !== void 0 && v !== "" ? v : def;
|
|
50222
50433
|
}
|
|
50223
|
-
function
|
|
50224
|
-
|
|
50434
|
+
function plainNodeFill(n, g) {
|
|
50435
|
+
const fillcolor = lateNN(nodeAttr(n, g, "fillcolor"), "");
|
|
50436
|
+
if (fillcolor !== "") return fillcolor;
|
|
50437
|
+
return lateNN(nodeAttr(n, g, "color"), "lightgrey");
|
|
50225
50438
|
}
|
|
50226
|
-
function
|
|
50227
|
-
|
|
50439
|
+
function plainNodeLabel(n, g) {
|
|
50440
|
+
const lbl = n.info.label;
|
|
50441
|
+
const attr2 = nodeAttr(n, g, "label");
|
|
50442
|
+
if (lbl !== void 0 && lbl.u.kind === "html") {
|
|
50443
|
+
const a = attr2 ?? "";
|
|
50444
|
+
return "<" + (isHtmlValue(a) ? htmlValueContent(a) : a) + ">";
|
|
50445
|
+
}
|
|
50446
|
+
if (attr2 !== void 0 && isHtmlValue(attr2) && lbl !== void 0 && lbl.u.kind === "txt") {
|
|
50447
|
+
return agstrcanonText(htmlValueContent(attr2));
|
|
50448
|
+
}
|
|
50449
|
+
const shape = n.info.shape;
|
|
50450
|
+
if (shape !== void 0 && shape.kind === 2 /* SH_RECORD */) {
|
|
50451
|
+
return agstrcanonText(nodeAttr(n, g, "label") ?? "\\N");
|
|
50452
|
+
}
|
|
50453
|
+
const text = lbl !== void 0 ? lbl.text : n.attrs.get("label") ?? n.name;
|
|
50454
|
+
return agstrcanonText(text);
|
|
50228
50455
|
}
|
|
50229
|
-
function
|
|
50230
|
-
|
|
50456
|
+
function plainNodeAttrs(n, g) {
|
|
50457
|
+
const shapeAttr = lateNN(nodeAttr(n, g, "shape"), "ellipse");
|
|
50458
|
+
const shapefile = nodeAttr(n, g, "shapefile");
|
|
50459
|
+
const shapeName = shapefile !== void 0 && shapefile !== "" && shapeAttr !== "epsf" ? "custom" : shapeAttr;
|
|
50460
|
+
return {
|
|
50461
|
+
label: plainNodeLabel(n, g),
|
|
50462
|
+
style: lateNN(nodeAttr(n, g, "style"), "solid"),
|
|
50463
|
+
shape: shapeName,
|
|
50464
|
+
color: lateNN(nodeAttr(n, g, "color"), "black"),
|
|
50465
|
+
fill: plainNodeFill(n, g)
|
|
50466
|
+
};
|
|
50231
50467
|
}
|
|
50232
|
-
function
|
|
50233
|
-
|
|
50234
|
-
|
|
50235
|
-
|
|
50468
|
+
function writePlainNode(n, g, out) {
|
|
50469
|
+
const x = plainCoord(n.info.coord.x);
|
|
50470
|
+
const y = plainCoord(n.info.coord.y);
|
|
50471
|
+
const w = printG5(n.info.width);
|
|
50472
|
+
const h = printG5(n.info.height);
|
|
50473
|
+
const a = plainNodeAttrs(n, g);
|
|
50474
|
+
out.push("node " + agstrcanonText(n.name) + " " + x + " " + y + " " + w + " " + h + " " + a.label + " " + a.style + " " + a.shape + " " + a.color + " " + a.fill + "\n");
|
|
50236
50475
|
}
|
|
50237
|
-
function
|
|
50238
|
-
if (!
|
|
50239
|
-
|
|
50240
|
-
|
|
50241
|
-
|
|
50242
|
-
if (e >= 0) {
|
|
50243
|
-
let mant = s.slice(0, e);
|
|
50244
|
-
if (mant.indexOf(".") >= 0) mant = mant.replace(/0+$/, "").replace(/\.$/, "");
|
|
50245
|
-
const ei = parseInt(s.slice(e + 1), 10);
|
|
50246
|
-
return mant + "e" + (ei < 0 ? "-" : "+") + String(Math.abs(ei)).padStart(2, "0");
|
|
50476
|
+
function collectSplinePts(e) {
|
|
50477
|
+
if (!e.info.spl) return [];
|
|
50478
|
+
const pts = [];
|
|
50479
|
+
for (const bz of e.info.spl.list) {
|
|
50480
|
+
for (let i = 0; i < bz.size; i++) pts.push(bz.list[i]);
|
|
50247
50481
|
}
|
|
50248
|
-
|
|
50249
|
-
return s;
|
|
50482
|
+
return pts;
|
|
50250
50483
|
}
|
|
50251
|
-
function
|
|
50252
|
-
|
|
50484
|
+
function writeNodeAndPort(n, portname, out) {
|
|
50485
|
+
let name = n.name;
|
|
50486
|
+
if (n.info.clustnode) {
|
|
50487
|
+
const i = name.indexOf(":");
|
|
50488
|
+
if (i >= 0) name = name.slice(i + 1);
|
|
50489
|
+
}
|
|
50490
|
+
out.push(" " + agstrcanonText(name));
|
|
50491
|
+
if (portname !== "") out.push(":" + agstrcanonText(portname));
|
|
50253
50492
|
}
|
|
50254
|
-
function
|
|
50255
|
-
|
|
50493
|
+
function writePlainEdgeHead(e, tport, hport, pts, out) {
|
|
50494
|
+
out.push("edge");
|
|
50495
|
+
writeNodeAndPort(e.tail, tport, out);
|
|
50496
|
+
writeNodeAndPort(e.head, hport, out);
|
|
50497
|
+
out.push(" " + String(pts.length));
|
|
50498
|
+
for (const pt of pts) {
|
|
50499
|
+
out.push(" " + plainCoord(pt.x) + " " + plainCoord(pt.y));
|
|
50500
|
+
}
|
|
50256
50501
|
}
|
|
50257
|
-
function
|
|
50258
|
-
|
|
50502
|
+
function writePlainEdge(e, extend, out) {
|
|
50503
|
+
const tport = extend ? e.attrs.get("tailport") ?? "" : "";
|
|
50504
|
+
const hport = extend ? e.attrs.get("headport") ?? "" : "";
|
|
50505
|
+
const pts = collectSplinePts(e);
|
|
50506
|
+
if (pts.length > 0) writePlainEdgeHead(e, tport, hport, pts, out);
|
|
50507
|
+
const lbl = e.info.label;
|
|
50508
|
+
if (lbl !== void 0) {
|
|
50509
|
+
out.push(" " + agstrcanonText(lbl.text) + " " + plainCoord(lbl.pos.x) + " " + plainCoord(lbl.pos.y));
|
|
50510
|
+
}
|
|
50511
|
+
const style = lateNN(e.attrs.get("style"), "solid");
|
|
50512
|
+
const color = lateNN(e.attrs.get("color"), "black");
|
|
50513
|
+
out.push(" " + style + " " + color + "\n");
|
|
50259
50514
|
}
|
|
50260
|
-
function
|
|
50261
|
-
const
|
|
50262
|
-
|
|
50263
|
-
|
|
50264
|
-
|
|
50265
|
-
)
|
|
50515
|
+
function writePlain(g, job, extend) {
|
|
50516
|
+
const w = plainCoord(g.info.bb.ur.x);
|
|
50517
|
+
const h = plainCoord(g.info.bb.ur.y);
|
|
50518
|
+
const zoom = initJobViewportZoom(
|
|
50519
|
+
job.bb,
|
|
50520
|
+
parseDrawingSize(g.attrs.get("size")),
|
|
50521
|
+
{ x: 0, y: 0 }
|
|
50522
|
+
);
|
|
50523
|
+
job.write("graph " + printG5(zoom) + " " + w + " " + h + "\n");
|
|
50524
|
+
const nodes = nodesInSeq(g);
|
|
50525
|
+
for (const n of nodes) {
|
|
50526
|
+
if (n.info.clustnode) continue;
|
|
50527
|
+
const buf = [];
|
|
50528
|
+
writePlainNode(n, g, buf);
|
|
50529
|
+
job.write(buf.join(""));
|
|
50266
50530
|
}
|
|
50267
|
-
for (
|
|
50531
|
+
for (const n of nodes) {
|
|
50532
|
+
for (const e of n.outEdges(g)) {
|
|
50533
|
+
const buf = [];
|
|
50534
|
+
writePlainEdge(e, extend, buf);
|
|
50535
|
+
job.write(buf.join(""));
|
|
50536
|
+
}
|
|
50537
|
+
}
|
|
50538
|
+
job.write("stop\n");
|
|
50268
50539
|
}
|
|
50269
|
-
function
|
|
50270
|
-
|
|
50271
|
-
|
|
50272
|
-
|
|
50540
|
+
function cmapxObjAnchor(obj) {
|
|
50541
|
+
return {
|
|
50542
|
+
url: obj.url ?? "",
|
|
50543
|
+
tooltip: obj.tooltip ?? "",
|
|
50544
|
+
target: obj.target ?? "",
|
|
50545
|
+
id: obj.id ?? ""
|
|
50546
|
+
};
|
|
50273
50547
|
}
|
|
50274
|
-
function
|
|
50275
|
-
|
|
50548
|
+
function writeCmapxGraphShape(job, out) {
|
|
50549
|
+
const obj = job.obj;
|
|
50550
|
+
if (!obj || !obj.urlMapPts.length) return;
|
|
50551
|
+
mapOutputCmapx(obj.urlMapShape, obj.urlMapPts, cmapxObjAnchor(obj), true, out);
|
|
50276
50552
|
}
|
|
50277
|
-
function
|
|
50278
|
-
|
|
50279
|
-
if (
|
|
50280
|
-
|
|
50281
|
-
...echoAttr(g.attrs, "lp"),
|
|
50282
|
-
...echoAttr(g.attrs, "lwidth"),
|
|
50283
|
-
...echoAttr(g.attrs, "lheight")
|
|
50284
|
-
];
|
|
50285
|
-
}
|
|
50286
|
-
return [
|
|
50287
|
-
'lp="' + lpStr(label.pos) + '"',
|
|
50288
|
-
"lwidth=" + gfmt2(label.dimen.x / POINTS_PER_INCH),
|
|
50289
|
-
"lheight=" + gfmt2(label.dimen.y / POINTS_PER_INCH)
|
|
50290
|
-
];
|
|
50553
|
+
function cmapxShape(shape) {
|
|
50554
|
+
if (shape === 1 /* Circle */) return "circle";
|
|
50555
|
+
if (shape === 0 /* Rectangle */) return "rect";
|
|
50556
|
+
return "poly";
|
|
50291
50557
|
}
|
|
50292
|
-
function
|
|
50293
|
-
|
|
50294
|
-
const x = printNum(info.coord.x);
|
|
50295
|
-
const y = printNum(info.coord.y);
|
|
50296
|
-
const w = printNum(info.width);
|
|
50297
|
-
const h = printNum(info.height);
|
|
50298
|
-
const parts = [`pos="${x},${y}"`, `width=${w}`, `height=${h}`];
|
|
50299
|
-
n.attrs.forEach((v, k) => {
|
|
50300
|
-
if (k !== "pos" && k !== "width" && k !== "height") {
|
|
50301
|
-
parts.push(`${k}="${v}"`);
|
|
50302
|
-
}
|
|
50303
|
-
});
|
|
50304
|
-
return parts.join(" ");
|
|
50558
|
+
function cmapxCoordsRect(pts) {
|
|
50559
|
+
return String(Math.round(pts[0].x)) + "," + String(Math.round(pts[1].y)) + "," + String(Math.round(pts[1].x)) + "," + String(Math.round(pts[0].y));
|
|
50305
50560
|
}
|
|
50306
|
-
function
|
|
50307
|
-
const
|
|
50308
|
-
|
|
50309
|
-
|
|
50310
|
-
|
|
50311
|
-
for (const bez of spl.list) {
|
|
50312
|
-
if (bez.sflag) parts.push("s," + gfmt5(bez.sp.x) + "," + gfmt5(bez.sp.y));
|
|
50313
|
-
if (bez.eflag) parts.push("e," + gfmt5(bez.ep.x) + "," + gfmt5(bez.ep.y));
|
|
50314
|
-
const pts = bez.list.slice(0, bez.size);
|
|
50315
|
-
for (const p of pts) parts.push(gfmt5(p.x) + "," + gfmt5(p.y));
|
|
50316
|
-
}
|
|
50317
|
-
return 'pos="' + parts.join(" ") + '"';
|
|
50561
|
+
function cmapxCoordsCircle(pts) {
|
|
50562
|
+
const cx = Math.round(pts[0].x);
|
|
50563
|
+
const cy = Math.round(pts[0].y);
|
|
50564
|
+
const r = Math.round(pts[1].x - pts[0].x);
|
|
50565
|
+
return String(cx) + "," + String(cy) + "," + String(r);
|
|
50318
50566
|
}
|
|
50319
|
-
function
|
|
50320
|
-
return
|
|
50567
|
+
function cmapxCoordsPoly(pts) {
|
|
50568
|
+
return pts.map((p) => String(Math.round(p.x)) + "," + String(Math.round(p.y))).join(",");
|
|
50321
50569
|
}
|
|
50322
|
-
function
|
|
50323
|
-
|
|
50570
|
+
function cmapxCoords(shape, pts) {
|
|
50571
|
+
if (shape === 1 /* Circle */) return cmapxCoordsCircle(pts);
|
|
50572
|
+
if (shape === 0 /* Rectangle */) return cmapxCoordsRect(pts);
|
|
50573
|
+
return cmapxCoordsPoly(pts);
|
|
50324
50574
|
}
|
|
50325
|
-
function
|
|
50326
|
-
|
|
50327
|
-
|
|
50328
|
-
|
|
50329
|
-
|
|
50330
|
-
job.write(' graph [bb="' + bbStr + '"];\n');
|
|
50575
|
+
function mapCmapxAttrs(a, out) {
|
|
50576
|
+
if (a.id) out.push(" id=" + DQ + escapeXml(a.id) + DQ);
|
|
50577
|
+
if (a.url) out.push(" href=" + DQ + escapeXml(a.url) + DQ);
|
|
50578
|
+
if (a.target) out.push(" target=" + DQ + escapeXmlTitle(a.target) + DQ);
|
|
50579
|
+
if (a.tooltip) out.push(" title=" + DQ + escapeXmlTitle(a.tooltip) + DQ);
|
|
50331
50580
|
}
|
|
50332
|
-
|
|
50333
|
-
|
|
50334
|
-
|
|
50335
|
-
|
|
50336
|
-
|
|
50337
|
-
|
|
50338
|
-
|
|
50339
|
-
|
|
50340
|
-
|
|
50341
|
-
|
|
50342
|
-
|
|
50343
|
-
}
|
|
50344
|
-
|
|
50345
|
-
|
|
50346
|
-
|
|
50347
|
-
|
|
50348
|
-
|
|
50349
|
-
}
|
|
50350
|
-
|
|
50351
|
-
|
|
50352
|
-
|
|
50353
|
-
|
|
50354
|
-
|
|
50355
|
-
|
|
50356
|
-
|
|
50357
|
-
|
|
50358
|
-
}
|
|
50581
|
+
function mapOutputCmapx(shape, pts, a, isXml, out) {
|
|
50582
|
+
out.push("<area shape=" + DQ + cmapxShape(shape) + DQ);
|
|
50583
|
+
mapCmapxAttrs(a, out);
|
|
50584
|
+
out.push(" alt=" + DQ + DQ);
|
|
50585
|
+
out.push(" coords=" + DQ + cmapxCoords(shape, pts) + DQ);
|
|
50586
|
+
out.push(isXml ? "/>\n" : ">\n");
|
|
50587
|
+
}
|
|
50588
|
+
function mapOutputImap(shape, pts, url, out) {
|
|
50589
|
+
if (!url) return;
|
|
50590
|
+
if (shape === 0 /* Rectangle */) {
|
|
50591
|
+
out.push("rect " + url + " " + Math.round(pts[0].x) + "," + Math.round(pts[1].y) + " " + Math.round(pts[1].x) + "," + Math.round(pts[0].y) + "\n");
|
|
50592
|
+
} else if (shape === 1 /* Circle */) {
|
|
50593
|
+
const r = Math.round(pts[1].x - pts[0].x);
|
|
50594
|
+
out.push("circle " + url + " " + Math.round(pts[0].x) + "," + Math.round(pts[0].y) + "," + String(r) + "\n");
|
|
50595
|
+
} else {
|
|
50596
|
+
const pairs = pts.map((p) => Math.round(p.x) + "," + Math.round(p.y)).join(" ");
|
|
50597
|
+
out.push("poly " + url + " " + pairs + "\n");
|
|
50598
|
+
}
|
|
50599
|
+
}
|
|
50600
|
+
var PlainRenderer = class {
|
|
50601
|
+
type = "plain";
|
|
50602
|
+
quality = 0;
|
|
50603
|
+
beginGraph(_g, _job) {
|
|
50604
|
+
}
|
|
50605
|
+
endGraph(g, job) {
|
|
50606
|
+
writePlain(g, job, false);
|
|
50607
|
+
}
|
|
50608
|
+
beginNode(_n, _job) {
|
|
50609
|
+
}
|
|
50610
|
+
endNode(_n, _job) {
|
|
50611
|
+
}
|
|
50612
|
+
beginEdge(_e, _job) {
|
|
50613
|
+
}
|
|
50614
|
+
endEdge(_e, _job) {
|
|
50615
|
+
}
|
|
50616
|
+
textspan(_pos, _span, _job) {
|
|
50617
|
+
}
|
|
50618
|
+
ellipse(_c, _rx, _ry, _f, _j) {
|
|
50619
|
+
}
|
|
50620
|
+
polygon(_pts, _filled, _job) {
|
|
50621
|
+
}
|
|
50622
|
+
bezier(_pts, _filled, _job) {
|
|
50623
|
+
}
|
|
50624
|
+
polyline(_pts, _job) {
|
|
50625
|
+
}
|
|
50626
|
+
};
|
|
50627
|
+
var PlainExtRenderer = class {
|
|
50628
|
+
type = "plain-ext";
|
|
50629
|
+
quality = 0;
|
|
50630
|
+
beginGraph(_g, _job) {
|
|
50631
|
+
}
|
|
50632
|
+
endGraph(g, job) {
|
|
50633
|
+
writePlain(g, job, true);
|
|
50634
|
+
}
|
|
50635
|
+
beginNode(_n, _job) {
|
|
50636
|
+
}
|
|
50637
|
+
endNode(_n, _job) {
|
|
50638
|
+
}
|
|
50639
|
+
beginEdge(_e, _job) {
|
|
50640
|
+
}
|
|
50641
|
+
endEdge(_e, _job) {
|
|
50642
|
+
}
|
|
50643
|
+
textspan(_pos, _span, _job) {
|
|
50644
|
+
}
|
|
50645
|
+
ellipse(_c, _rx, _ry, _f, _j) {
|
|
50646
|
+
}
|
|
50647
|
+
polygon(_pts, _filled, _job) {
|
|
50648
|
+
}
|
|
50649
|
+
bezier(_pts, _filled, _job) {
|
|
50650
|
+
}
|
|
50651
|
+
polyline(_pts, _job) {
|
|
50652
|
+
}
|
|
50653
|
+
};
|
|
50654
|
+
function mapGraphName(g) {
|
|
50655
|
+
return g.anonymous ? "%1" : g.name;
|
|
50656
|
+
}
|
|
50657
|
+
function graphAttr2(g, key) {
|
|
50658
|
+
for (let s = g; s !== null; s = s.parent) {
|
|
50659
|
+
const v = s.attrs.get(key);
|
|
50660
|
+
if (v !== void 0 && v !== "") return v;
|
|
50661
|
+
}
|
|
50662
|
+
return void 0;
|
|
50663
|
+
}
|
|
50664
|
+
function graphMapUrl(g) {
|
|
50665
|
+
const raw = graphAttr2(g, "href") ?? graphAttr2(g, "URL");
|
|
50666
|
+
return raw === void 0 ? null : substObjAnchor(raw, g);
|
|
50667
|
+
}
|
|
50668
|
+
function buildMapCtx(g, job, mapPolygon) {
|
|
50669
|
+
const drawingDpi = lateDouble(g.attrs.get("dpi"), lateDouble(g.attrs.get("resolution"), 0, 0), 0);
|
|
50670
|
+
const mapDpi = drawingDpi > 0 ? drawingDpi : 96;
|
|
50671
|
+
const devscale = mapDpi / POINTS_PER_INCH;
|
|
50672
|
+
const z = initJobViewportZoom(job.bb, parseDrawingSize(g.attrs.get("size")), job.pad);
|
|
50673
|
+
const marginOff = { x: job.margin.x * devscale, y: job.margin.y * devscale };
|
|
50674
|
+
return { bb: job.bb, pad: job.pad, scale: z * devscale, marginOff, mapPolygon };
|
|
50675
|
+
}
|
|
50676
|
+
function anchorOf(url, tooltip, target, id) {
|
|
50677
|
+
return { url: url ?? "", tooltip: tooltip ?? "", target: target ?? "", id: id ?? "" };
|
|
50678
|
+
}
|
|
50679
|
+
var MapRendererBase = class {
|
|
50680
|
+
quality = 0;
|
|
50681
|
+
mapCtx = null;
|
|
50682
|
+
beginGraph(g, job) {
|
|
50683
|
+
job.flags |= EMIT_CLUSTERS_LAST;
|
|
50684
|
+
this.mapCtx = buildMapCtx(g, job, this.mapPolygon);
|
|
50685
|
+
if (this.isCmapx) {
|
|
50686
|
+
const name = escapeXml(mapGraphName(g));
|
|
50687
|
+
job.write("<map id=" + DQ + name + DQ + " name=" + DQ + name + DQ + ">\n");
|
|
50688
|
+
return;
|
|
50689
|
+
}
|
|
50690
|
+
job.write("base referer\n");
|
|
50691
|
+
const url = graphMapUrl(g);
|
|
50692
|
+
if (url) job.write("default " + url + "\n");
|
|
50693
|
+
}
|
|
50694
|
+
endGraph(_g, job) {
|
|
50695
|
+
if (!this.isCmapx) return;
|
|
50696
|
+
const obj = job.obj;
|
|
50697
|
+
if (obj !== null && this.mapCtx !== null && (obj.url !== null || obj.explicitTooltip)) {
|
|
50698
|
+
computeGraphUrlMap(obj, this.mapCtx);
|
|
50699
|
+
}
|
|
50700
|
+
const buf = [];
|
|
50701
|
+
writeCmapxGraphShape(job, buf);
|
|
50702
|
+
job.write(buf.join(""));
|
|
50703
|
+
job.write("</map>\n");
|
|
50704
|
+
}
|
|
50705
|
+
beginNode(n, job) {
|
|
50706
|
+
if (this.mapCtx !== null && job.obj !== null) computeNodeUrlMap(n, job.obj, this.mapCtx);
|
|
50707
|
+
}
|
|
50708
|
+
beginCluster(sg, job) {
|
|
50709
|
+
const bb = sg.info.bb;
|
|
50710
|
+
if (this.mapCtx !== null && job.obj !== null && bb !== void 0) {
|
|
50711
|
+
computeClusterUrlMap(bb, job.obj, this.mapCtx);
|
|
50712
|
+
}
|
|
50713
|
+
}
|
|
50714
|
+
endEdge(e, job) {
|
|
50715
|
+
if (this.mapCtx !== null && job.obj !== null) this.emitEdge(e, job.obj, job);
|
|
50716
|
+
}
|
|
50717
|
+
/** C emit_map_rect: record the HTML table/cell box as the pending hot spot,
|
|
50718
|
+
* so the following beginAnchor emits its <area>. @see emit.c:640 */
|
|
50719
|
+
emitMapRect(box, job) {
|
|
50720
|
+
if (this.mapCtx !== null && job.obj !== null) {
|
|
50721
|
+
computeClusterUrlMap(box, job.obj, this.mapCtx);
|
|
50722
|
+
}
|
|
50723
|
+
}
|
|
50724
|
+
beginAnchor(url, tip2, target, id, job) {
|
|
50725
|
+
const obj = job.obj;
|
|
50726
|
+
if (obj === null || obj.urlMapPts.length === 0) return;
|
|
50727
|
+
this.emitShape(obj.urlMapShape, obj.urlMapPts, { url, tooltip: tip2, target, id }, job);
|
|
50728
|
+
}
|
|
50729
|
+
endNode(_n, _job) {
|
|
50730
|
+
}
|
|
50731
|
+
beginEdge(_e, _job) {
|
|
50732
|
+
}
|
|
50733
|
+
endCluster(_sg, _job) {
|
|
50734
|
+
}
|
|
50735
|
+
textspan(_pos, _span, _job) {
|
|
50736
|
+
}
|
|
50359
50737
|
ellipse(_c, _rx, _ry, _f, _j) {
|
|
50360
50738
|
}
|
|
50361
|
-
polygon(_pts, _filled, _job) {
|
|
50739
|
+
polygon(_pts, _filled, _job) {
|
|
50740
|
+
}
|
|
50741
|
+
bezier(_pts, _filled, _job) {
|
|
50742
|
+
}
|
|
50743
|
+
polyline(_pts, _job) {
|
|
50744
|
+
}
|
|
50745
|
+
/** Emit one hot spot: `<area>` (cmapx) or a plain `keyword url coords` line. */
|
|
50746
|
+
emitShape(shape, pts, a, job) {
|
|
50747
|
+
const buf = [];
|
|
50748
|
+
if (this.isCmapx) mapOutputCmapx(shape, pts, a, true, buf);
|
|
50749
|
+
else mapOutputImap(shape, pts, a.url, buf);
|
|
50750
|
+
job.write(buf.join(""));
|
|
50751
|
+
}
|
|
50752
|
+
/** Whole-edge spline outline(s) then center/head/tail label hot spots.
|
|
50753
|
+
* @see lib/common/emit.c:emit_begin_edge (2851-2872) / emit_end_edge */
|
|
50754
|
+
emitEdge(e, obj, job) {
|
|
50755
|
+
const spl = e.info.spl;
|
|
50756
|
+
const wholeEdge = obj.url !== null || obj.explicitTooltip;
|
|
50757
|
+
if (spl !== void 0 && this.mapPolygon && wholeEdge) {
|
|
50758
|
+
const w2 = Math.max(obj.penWidth / 2, 2);
|
|
50759
|
+
const anchor = anchorOf(obj.url, obj.tooltip, obj.target, obj.id);
|
|
50760
|
+
for (const poly of computeEdgeSplineMaps(spl, w2, this.mapCtx)) {
|
|
50761
|
+
this.emitShape(2 /* Polygon */, poly, anchor, job);
|
|
50762
|
+
}
|
|
50763
|
+
}
|
|
50764
|
+
const centerA = {
|
|
50765
|
+
url: obj.labelUrl,
|
|
50766
|
+
tooltip: obj.labelTooltip,
|
|
50767
|
+
target: obj.labelTarget,
|
|
50768
|
+
explicit: obj.explicitLabelTooltip
|
|
50769
|
+
};
|
|
50770
|
+
this.emitEdgeLabel(e.info.label, centerA, obj, job);
|
|
50771
|
+
this.emitEdgeLabel(e.info.xlabel, centerA, obj, job);
|
|
50772
|
+
this.emitEdgeLabel(
|
|
50773
|
+
e.info.head_label,
|
|
50774
|
+
{ url: obj.headUrl, tooltip: obj.headTooltip, target: obj.headTarget, explicit: obj.explicitHeadTooltip },
|
|
50775
|
+
obj,
|
|
50776
|
+
job
|
|
50777
|
+
);
|
|
50778
|
+
this.emitEdgeLabel(
|
|
50779
|
+
e.info.tail_label,
|
|
50780
|
+
{ url: obj.tailUrl, tooltip: obj.tailTooltip, target: obj.tailTarget, explicit: obj.explicitTailTooltip },
|
|
50781
|
+
obj,
|
|
50782
|
+
job
|
|
50783
|
+
);
|
|
50784
|
+
}
|
|
50785
|
+
/** One edge label hot spot (map_label rect), if placed and url/explicit-tip.
|
|
50786
|
+
* @see lib/common/emit.c:emit_edge_label */
|
|
50787
|
+
emitEdgeLabel(lab, la, obj, job) {
|
|
50788
|
+
const placed = lab !== void 0 && lab.set && this.mapCtx !== null;
|
|
50789
|
+
if (!placed || la.url === null && !la.explicit) return;
|
|
50790
|
+
computeLabelRectMap(lab, obj, this.mapCtx);
|
|
50791
|
+
this.emitShape(0 /* Rectangle */, obj.urlMapPts, anchorOf(la.url, la.tooltip, la.target, obj.id), job);
|
|
50792
|
+
}
|
|
50793
|
+
};
|
|
50794
|
+
var ImapRenderer = class extends MapRendererBase {
|
|
50795
|
+
type = "imap";
|
|
50796
|
+
isCmapx = false;
|
|
50797
|
+
mapPolygon = true;
|
|
50798
|
+
};
|
|
50799
|
+
var ImapNpRenderer = class extends MapRendererBase {
|
|
50800
|
+
type = "imap-np";
|
|
50801
|
+
isCmapx = false;
|
|
50802
|
+
mapPolygon = false;
|
|
50803
|
+
};
|
|
50804
|
+
var CmapxRenderer = class extends MapRendererBase {
|
|
50805
|
+
type = "cmapx";
|
|
50806
|
+
isCmapx = true;
|
|
50807
|
+
mapPolygon = true;
|
|
50808
|
+
};
|
|
50809
|
+
var CmapxNpRenderer = class extends MapRendererBase {
|
|
50810
|
+
type = "cmapx-np";
|
|
50811
|
+
isCmapx = true;
|
|
50812
|
+
mapPolygon = false;
|
|
50813
|
+
};
|
|
50814
|
+
function createPlainRenderer() {
|
|
50815
|
+
return new PlainRenderer();
|
|
50816
|
+
}
|
|
50817
|
+
function createPlainExtRenderer() {
|
|
50818
|
+
return new PlainExtRenderer();
|
|
50819
|
+
}
|
|
50820
|
+
function createImapRenderer() {
|
|
50821
|
+
return new ImapRenderer();
|
|
50822
|
+
}
|
|
50823
|
+
function createImapNpRenderer() {
|
|
50824
|
+
return new ImapNpRenderer();
|
|
50825
|
+
}
|
|
50826
|
+
function createCmapxRenderer() {
|
|
50827
|
+
return new CmapxRenderer();
|
|
50828
|
+
}
|
|
50829
|
+
function createCmapxNpRenderer() {
|
|
50830
|
+
return new CmapxNpRenderer();
|
|
50831
|
+
}
|
|
50832
|
+
|
|
50833
|
+
// src/render/dot/xdot-ops.ts
|
|
50834
|
+
var XDOT_VERSION = "1.7";
|
|
50835
|
+
var MAX_NEGNUM = 1e15;
|
|
50836
|
+
var NEAR_ZERO = 5e-3;
|
|
50837
|
+
var NUM_XBUFS = 8;
|
|
50838
|
+
var NON_LINE_STYLES = /* @__PURE__ */ new Set([
|
|
50839
|
+
"filled",
|
|
50840
|
+
"bold",
|
|
50841
|
+
"rounded",
|
|
50842
|
+
"diagonals",
|
|
50843
|
+
"striped",
|
|
50844
|
+
"wedged",
|
|
50845
|
+
"invis",
|
|
50846
|
+
"invisible",
|
|
50847
|
+
"radial"
|
|
50848
|
+
]);
|
|
50849
|
+
function trimAndStrip(s) {
|
|
50850
|
+
const dot = s.indexOf(".");
|
|
50851
|
+
if (dot < 0) return s;
|
|
50852
|
+
let end = s.length;
|
|
50853
|
+
while (end > dot + 1 && s[end - 1] === "0") end--;
|
|
50854
|
+
if (s[end - 1] === ".") end--;
|
|
50855
|
+
const t = s.slice(0, end);
|
|
50856
|
+
if (t.startsWith("0.")) return t.slice(1);
|
|
50857
|
+
if (t.startsWith("-0.")) return "-" + t.slice(2);
|
|
50858
|
+
return t;
|
|
50859
|
+
}
|
|
50860
|
+
function printNum(n) {
|
|
50861
|
+
if (Math.abs(n) > MAX_NEGNUM) {
|
|
50862
|
+
return n < 0 ? String(-MAX_NEGNUM) : String(MAX_NEGNUM);
|
|
50863
|
+
}
|
|
50864
|
+
if (Math.abs(n) < NEAR_ZERO) return "0";
|
|
50865
|
+
return trimAndStrip(n.toFixed(3));
|
|
50866
|
+
}
|
|
50867
|
+
function makeXbufs() {
|
|
50868
|
+
const bufs = Array.from({ length: NUM_XBUFS + 4 }, () => []);
|
|
50869
|
+
bufs[8 /* NDraw */] = bufs[1 /* CDraw */];
|
|
50870
|
+
bufs[9 /* EDraw */] = bufs[1 /* CDraw */];
|
|
50871
|
+
bufs[10 /* NLabel */] = bufs[5 /* CLabel */];
|
|
50872
|
+
bufs[11 /* ELabel */] = bufs[5 /* CLabel */];
|
|
50873
|
+
return bufs;
|
|
50874
|
+
}
|
|
50875
|
+
function xdotNum(v) {
|
|
50876
|
+
let s = toFixed2HalfEven(v);
|
|
50877
|
+
if (s.indexOf(".") >= 0) {
|
|
50878
|
+
let end = s.length;
|
|
50879
|
+
while (end > 0 && s[end - 1] === "0") end--;
|
|
50880
|
+
if (s[end - 1] === ".") end--;
|
|
50881
|
+
s = s.slice(0, end);
|
|
50882
|
+
}
|
|
50883
|
+
return s === "-0" ? "0" : s;
|
|
50884
|
+
}
|
|
50885
|
+
function xdotPoint(p) {
|
|
50886
|
+
return xdotNum(p.x) + " " + xdotNum(p.y) + " ";
|
|
50887
|
+
}
|
|
50888
|
+
function xdotPoints(c, pts) {
|
|
50889
|
+
let s = c + " " + String(pts.length) + " ";
|
|
50890
|
+
for (const p of pts) s += xdotPoint(p);
|
|
50891
|
+
return s;
|
|
50892
|
+
}
|
|
50893
|
+
function utf8Len(s) {
|
|
50894
|
+
let n = 0;
|
|
50895
|
+
for (let i = 0; i < s.length; i++) {
|
|
50896
|
+
const c = s.charCodeAt(i);
|
|
50897
|
+
if (c < 128) n += 1;
|
|
50898
|
+
else if (c < 2048) n += 2;
|
|
50899
|
+
else if (c >= 55296 && c <= 56319) {
|
|
50900
|
+
n += 4;
|
|
50901
|
+
i++;
|
|
50902
|
+
} else n += 3;
|
|
50903
|
+
}
|
|
50904
|
+
return n;
|
|
50905
|
+
}
|
|
50906
|
+
function chanByte(v) {
|
|
50907
|
+
return Math.round(Math.max(0, Math.min(1, v)) * 255);
|
|
50908
|
+
}
|
|
50909
|
+
function gvColorRgba(c) {
|
|
50910
|
+
if (c.type === "rgba") return [chanByte(c.r), chanByte(c.g), chanByte(c.b), chanByte(c.a)];
|
|
50911
|
+
if (c.type === "none") return [255, 255, 254, 0];
|
|
50912
|
+
const out = { type: "rgba", r: 0, g: 0, b: 0, a: 0 };
|
|
50913
|
+
colorxlate(c.type === "string" ? c.s : "", out, "rgba");
|
|
50914
|
+
return out.type === "rgba" ? [chanByte(out.r), chanByte(out.g), chanByte(out.b), chanByte(out.a)] : [0, 0, 0, 255];
|
|
50915
|
+
}
|
|
50916
|
+
function xdotColorBody(rgba) {
|
|
50917
|
+
const hx = (n) => n.toString(16).padStart(2, "0");
|
|
50918
|
+
const body = "#" + hx(rgba[0]) + hx(rgba[1]) + hx(rgba[2]);
|
|
50919
|
+
return rgba[3] === 255 ? "7 -" + body : "9 -" + body + hx(rgba[3]);
|
|
50920
|
+
}
|
|
50921
|
+
function xdotColorOp(prefix, rgba) {
|
|
50922
|
+
return prefix + xdotColorBody(rgba) + " ";
|
|
50923
|
+
}
|
|
50924
|
+
function linearGradientOp(pts, fillColor, stopColor, frac, angleDeg) {
|
|
50925
|
+
const { g0, g1 } = getGradientPoints(pts, angleDeg * Math.PI / 180, false, true);
|
|
50926
|
+
const inner = "[" + xdotNum(g0.x) + " " + xdotNum(g0.y) + " " + xdotNum(g1.x) + " " + xdotNum(g1.y) + " 2 " + gradientStops(fillColor, stopColor, frac) + "]";
|
|
50927
|
+
return "C " + String(utf8Len(inner)) + " -" + inner + " ";
|
|
50928
|
+
}
|
|
50929
|
+
function gradientStops(fillColor, stopColor, frac) {
|
|
50930
|
+
const fill = gvColorRgba(fillColor);
|
|
50931
|
+
const stop = gvColorRgba(stopColor);
|
|
50932
|
+
const stops = frac > 0 ? [[frac, fill], [frac, stop]] : [[0, fill], [1, stop]];
|
|
50933
|
+
return stops.map(([f, c]) => trimFixed3(f) + " " + xdotColorBody(c)).join(" ");
|
|
50934
|
+
}
|
|
50935
|
+
function radialGradientOp(pts, fillColor, stopColor, frac, angleDeg) {
|
|
50936
|
+
const rad2 = angleDeg * Math.PI / 180;
|
|
50937
|
+
const gp = getGradientPoints(pts, rad2, true, true);
|
|
50938
|
+
const cx = gp.g0.x;
|
|
50939
|
+
const cy = gp.g0.y;
|
|
50940
|
+
const r1 = gp.g1.x;
|
|
50941
|
+
const r2 = gp.g1.y;
|
|
50942
|
+
const c1x = angleDeg === 0 ? cx : cx + r1 * Math.cos(rad2);
|
|
50943
|
+
const c1y = angleDeg === 0 ? cy : cy + r1 * Math.sin(rad2);
|
|
50944
|
+
const inner = "(" + xdotNum(c1x) + " " + xdotNum(c1y) + " " + xdotNum(r1) + " " + xdotNum(cx) + " " + xdotNum(cy) + " " + xdotNum(r2) + " 2 " + gradientStops(fillColor, stopColor, frac) + ")";
|
|
50945
|
+
return "C " + String(utf8Len(inner)) + " -" + inner + " ";
|
|
50946
|
+
}
|
|
50947
|
+
function xdotPenColor(c) {
|
|
50948
|
+
return xdotColorOp("c ", gvColorRgba(c));
|
|
50949
|
+
}
|
|
50950
|
+
function xdotFillColor(c) {
|
|
50951
|
+
return xdotColorOp("C ", gvColorRgba(c));
|
|
50952
|
+
}
|
|
50953
|
+
function xdotFont(size, name) {
|
|
50954
|
+
return "F " + xdotNum(size > 0 ? size : 0) + " " + String(utf8Len(name)) + " -" + name + " ";
|
|
50955
|
+
}
|
|
50956
|
+
function xdotStrOp(prefix, s) {
|
|
50957
|
+
return prefix + String(utf8Len(s)) + " -" + s + " ";
|
|
50958
|
+
}
|
|
50959
|
+
function xdotId(s) {
|
|
50960
|
+
if (/^[A-Za-z_][A-Za-z_0-9]*$/.test(s)) return s;
|
|
50961
|
+
if (/^-?(\.[0-9]+|[0-9]+(\.[0-9]*)?)$/.test(s)) return s;
|
|
50962
|
+
const body = s.replace(/"/g, '\\"');
|
|
50963
|
+
const trailingBackslashes = /\\*$/.exec(body)[0].length;
|
|
50964
|
+
return '"' + body + (trailingBackslashes % 2 === 1 ? "\\" : "") + '"';
|
|
50965
|
+
}
|
|
50966
|
+
function gfmt5(v) {
|
|
50967
|
+
if (!Number.isFinite(v)) return String(v);
|
|
50968
|
+
if (v === 0) return "0";
|
|
50969
|
+
let s = printfSig(v, 5);
|
|
50970
|
+
const e = s.indexOf("e");
|
|
50971
|
+
if (e >= 0) {
|
|
50972
|
+
let mant = s.slice(0, e);
|
|
50973
|
+
if (mant.indexOf(".") >= 0) mant = mant.replace(/0+$/, "").replace(/\.$/, "");
|
|
50974
|
+
const ei = parseInt(s.slice(e + 1), 10);
|
|
50975
|
+
return mant + "e" + (ei < 0 ? "-" : "+") + String(Math.abs(ei)).padStart(2, "0");
|
|
50976
|
+
}
|
|
50977
|
+
if (s.indexOf(".") >= 0) s = s.replace(/0+$/, "").replace(/\.$/, "");
|
|
50978
|
+
return s;
|
|
50979
|
+
}
|
|
50980
|
+
function gfmt2(v) {
|
|
50981
|
+
return printfFixed(v, 2);
|
|
50982
|
+
}
|
|
50983
|
+
function lpStr(p) {
|
|
50984
|
+
return gfmt5(p.x) + "," + gfmt5(p.y);
|
|
50985
|
+
}
|
|
50986
|
+
function escBackslash(s) {
|
|
50987
|
+
return s.replace(/\\/g, "\\\\");
|
|
50988
|
+
}
|
|
50989
|
+
function isEscapeSeq(s, i) {
|
|
50990
|
+
if (s[i] !== "\\") return false;
|
|
50991
|
+
const n = s[i + 1];
|
|
50992
|
+
return n === "E" || n === "G" || n === "H" || n === "L" || n === "N" || n === "T" || n === "l" || n === "n" || n === "r" || n === "\\" || n === '"';
|
|
50993
|
+
}
|
|
50994
|
+
function agcanonEscape(s) {
|
|
50995
|
+
let out = "";
|
|
50996
|
+
let partOfEscape = false;
|
|
50997
|
+
for (let i = 0; i < s.length; i++) {
|
|
50998
|
+
const c = s[i];
|
|
50999
|
+
if (c === '"' && !partOfEscape) {
|
|
51000
|
+
out += "\\";
|
|
51001
|
+
} else if (!partOfEscape && isEscapeSeq(s, i)) {
|
|
51002
|
+
partOfEscape = true;
|
|
51003
|
+
} else {
|
|
51004
|
+
partOfEscape = false;
|
|
51005
|
+
}
|
|
51006
|
+
out += c;
|
|
51007
|
+
}
|
|
51008
|
+
return out;
|
|
51009
|
+
}
|
|
51010
|
+
function trimFixed3(v) {
|
|
51011
|
+
let s = v.toFixed(3);
|
|
51012
|
+
if (s.indexOf(".") < 0) return s;
|
|
51013
|
+
let end = s.length;
|
|
51014
|
+
while (end > 0 && s[end - 1] === "0") end--;
|
|
51015
|
+
if (s[end - 1] === ".") end--;
|
|
51016
|
+
return s.slice(0, end);
|
|
51017
|
+
}
|
|
51018
|
+
|
|
51019
|
+
// src/render/dot/attrs.ts
|
|
51020
|
+
function edgeAttrsAttached(e) {
|
|
51021
|
+
return e.info.edge_type !== IGNORED && e.info.spl != null;
|
|
51022
|
+
}
|
|
51023
|
+
function appendRecordRects(n, f, out) {
|
|
51024
|
+
const c = n.info.coord;
|
|
51025
|
+
if (f.n_flds === 0) {
|
|
51026
|
+
out.push(
|
|
51027
|
+
gfmt5(f.b.ll.x + c.x) + "," + gfmt5(f.b.ll.y + c.y) + "," + gfmt5(f.b.ur.x + c.x) + "," + gfmt5(f.b.ur.y + c.y)
|
|
51028
|
+
);
|
|
51029
|
+
}
|
|
51030
|
+
for (let i = 0; i < f.n_flds; i++) appendRecordRects(n, f.fld[i], out);
|
|
51031
|
+
}
|
|
51032
|
+
var COMPUTED_NODE_ATTRS = /* @__PURE__ */ new Set(["pos", "width", "height", "xlp", "rects", "_draw_", "_ldraw_"]);
|
|
51033
|
+
var COMPUTED_EDGE_ATTRS = /* @__PURE__ */ new Set([
|
|
51034
|
+
"pos",
|
|
51035
|
+
"lp",
|
|
51036
|
+
"xlp",
|
|
51037
|
+
"head_lp",
|
|
51038
|
+
"tail_lp",
|
|
51039
|
+
"tailport",
|
|
51040
|
+
"headport",
|
|
51041
|
+
"_draw_",
|
|
51042
|
+
"_ldraw_",
|
|
51043
|
+
"_hdraw_",
|
|
51044
|
+
"_tdraw_",
|
|
51045
|
+
"_hldraw_",
|
|
51046
|
+
"_tldraw_"
|
|
51047
|
+
]);
|
|
51048
|
+
var NODENAME_ESC = "\\N";
|
|
51049
|
+
function effectiveEdgeDefaults(scope) {
|
|
51050
|
+
const eff = /* @__PURE__ */ new Map();
|
|
51051
|
+
for (let g = scope; g !== null; g = g.parent) {
|
|
51052
|
+
for (const [k, v] of g.edgeDefaults) if (!eff.has(k)) eff.set(k, v);
|
|
51053
|
+
}
|
|
51054
|
+
return eff;
|
|
51055
|
+
}
|
|
51056
|
+
function effectiveNodeDefaults2(scope) {
|
|
51057
|
+
const eff = /* @__PURE__ */ new Map();
|
|
51058
|
+
for (let g = scope; g !== null; g = g.parent) {
|
|
51059
|
+
for (const [k, v] of g.nodeDefaults) if (!eff.has(k)) eff.set(k, v);
|
|
51060
|
+
}
|
|
51061
|
+
if (!eff.has("label")) eff.set("label", NODENAME_ESC);
|
|
51062
|
+
return eff;
|
|
51063
|
+
}
|
|
51064
|
+
function nodeRecord(n) {
|
|
51065
|
+
const rec = new Map(n.nodeDefaultsSnapshot ?? []);
|
|
51066
|
+
if (!rec.has("label")) rec.set("label", NODENAME_ESC);
|
|
51067
|
+
for (const [k, v] of n.attrs) rec.set(k, v);
|
|
51068
|
+
return rec;
|
|
51069
|
+
}
|
|
51070
|
+
function objInputParts(record, defaults, computed) {
|
|
51071
|
+
const parts = [];
|
|
51072
|
+
for (const k of /* @__PURE__ */ new Set([...record.keys(), ...defaults.keys()])) {
|
|
51073
|
+
if (computed.has(k)) continue;
|
|
51074
|
+
const v = record.get(k) ?? "";
|
|
51075
|
+
if (v === (defaults.get(k) ?? "")) continue;
|
|
51076
|
+
parts.push(k + "=" + agstrcanon(v));
|
|
51077
|
+
}
|
|
51078
|
+
return parts;
|
|
51079
|
+
}
|
|
51080
|
+
function computedPart(key, raw, defaults) {
|
|
51081
|
+
if (defaults !== void 0 && defaults.get(key) === raw) return [];
|
|
51082
|
+
return [key + "=" + agstrcanon(raw)];
|
|
51083
|
+
}
|
|
51084
|
+
var COMPUTED_GRAPH_ATTRS = /* @__PURE__ */ new Set([
|
|
51085
|
+
"_draw_",
|
|
51086
|
+
"_ldraw_",
|
|
51087
|
+
"bb",
|
|
51088
|
+
"lp",
|
|
51089
|
+
"lwidth",
|
|
51090
|
+
"lheight"
|
|
51091
|
+
]);
|
|
51092
|
+
var COMPUTED_GRAPH_ATTRS_XDOT = /* @__PURE__ */ new Set([...COMPUTED_GRAPH_ATTRS, "xdotversion"]);
|
|
51093
|
+
function computedGraphAttrs(emitDraws) {
|
|
51094
|
+
return emitDraws ? COMPUTED_GRAPH_ATTRS_XDOT : COMPUTED_GRAPH_ATTRS;
|
|
51095
|
+
}
|
|
51096
|
+
function graphInputParts(g, top, emitDraws) {
|
|
51097
|
+
const computed = computedGraphAttrs(emitDraws);
|
|
51098
|
+
const parts = [];
|
|
51099
|
+
const snap = g.graphDefaultsSnapshot;
|
|
51100
|
+
const seeded = g.seededAttrs;
|
|
51101
|
+
for (const [k, v] of g.attrs) {
|
|
51102
|
+
if (computed.has(k)) continue;
|
|
51103
|
+
if (!top && seeded !== void 0 && seeded.has(k)) continue;
|
|
51104
|
+
if (writeDictSkips(v, snap?.get(k))) continue;
|
|
51105
|
+
parts.push(k + "=" + agstrcanon(v));
|
|
51106
|
+
}
|
|
51107
|
+
if (!top && snap !== void 0) parts.push(...eagerEmptyParts(g, snap, computed));
|
|
51108
|
+
return parts;
|
|
51109
|
+
}
|
|
51110
|
+
function eagerEmptyParts(g, snap, computed) {
|
|
51111
|
+
const parts = [];
|
|
51112
|
+
const p = g.parent;
|
|
51113
|
+
if (p === null) return parts;
|
|
51114
|
+
const keys = p.parent === null ? p.attrs.keys() : p.firstGraphDecl ?? [];
|
|
51115
|
+
for (const k of keys) {
|
|
51116
|
+
if (computed.has(k)) continue;
|
|
51117
|
+
if (snap.has(k) || g.attrs.has(k)) continue;
|
|
51118
|
+
parts.push(k + "=" + agstrcanon(""));
|
|
51119
|
+
}
|
|
51120
|
+
return parts;
|
|
51121
|
+
}
|
|
51122
|
+
function dictParts(defaults) {
|
|
51123
|
+
const parts = [];
|
|
51124
|
+
for (const [k, v] of defaults) parts.push(k + "=" + agstrcanon(v));
|
|
51125
|
+
return parts;
|
|
51126
|
+
}
|
|
51127
|
+
function nodeDictParts(g, top) {
|
|
51128
|
+
const defs = new Map(g.nodeDefaults);
|
|
51129
|
+
if (top && !defs.has("label")) defs.set("label", NODENAME_ESC);
|
|
51130
|
+
return dictParts(defs);
|
|
51131
|
+
}
|
|
51132
|
+
function echoAttr(attrs, key) {
|
|
51133
|
+
const v = attrs.get(key);
|
|
51134
|
+
if (v === void 0 || v.length === 0) return [];
|
|
51135
|
+
return [key + '="' + agcanonEscape(v) + '"'];
|
|
51136
|
+
}
|
|
51137
|
+
function writeDictSkips(v, inherited) {
|
|
51138
|
+
return v.length === 0 && inherited === "";
|
|
51139
|
+
}
|
|
51140
|
+
function echoGraphAttr(g, key) {
|
|
51141
|
+
const v = g.attrs.get(key);
|
|
51142
|
+
if (v === void 0) return [];
|
|
51143
|
+
if (writeDictSkips(v, g.graphDefaultsSnapshot?.get(key))) return [];
|
|
51144
|
+
return [key + '="' + agcanonEscape(v) + '"'];
|
|
51145
|
+
}
|
|
51146
|
+
function graphLabelAttrs(g) {
|
|
51147
|
+
const label = g.info.label;
|
|
51148
|
+
if (!label || label.text.length === 0) {
|
|
51149
|
+
return [
|
|
51150
|
+
...echoGraphAttr(g, "lp"),
|
|
51151
|
+
...echoGraphAttr(g, "lwidth"),
|
|
51152
|
+
...echoGraphAttr(g, "lheight")
|
|
51153
|
+
];
|
|
51154
|
+
}
|
|
51155
|
+
return [
|
|
51156
|
+
'lp="' + lpStr(label.pos) + '"',
|
|
51157
|
+
"lwidth=" + gfmt2(label.dimen.x / POINTS_PER_INCH),
|
|
51158
|
+
"lheight=" + gfmt2(label.dimen.y / POINTS_PER_INCH)
|
|
51159
|
+
];
|
|
51160
|
+
}
|
|
51161
|
+
function edgePosRaw(e) {
|
|
51162
|
+
const spl = e.info.spl;
|
|
51163
|
+
if (!spl || spl.list.length === 0) return null;
|
|
51164
|
+
if (e.info.edge_type === IGNORED) return null;
|
|
51165
|
+
let out = "";
|
|
51166
|
+
for (let i = 0; i < spl.size; i++) {
|
|
51167
|
+
const bez = spl.list[i];
|
|
51168
|
+
if (i > 0) out += ";";
|
|
51169
|
+
if (bez.sflag) out += "s," + gfmt5(bez.sp.x) + "," + gfmt5(bez.sp.y) + " ";
|
|
51170
|
+
if (bez.eflag) out += "e," + gfmt5(bez.ep.x) + "," + gfmt5(bez.ep.y) + " ";
|
|
51171
|
+
for (let j = 0; j < bez.size; j++) {
|
|
51172
|
+
if (j > 0) out += " ";
|
|
51173
|
+
const p = bez.list[j];
|
|
51174
|
+
out += gfmt5(p.x) + "," + gfmt5(p.y);
|
|
51175
|
+
}
|
|
51176
|
+
}
|
|
51177
|
+
return out;
|
|
51178
|
+
}
|
|
51179
|
+
function edgeConnector(directed) {
|
|
51180
|
+
return directed ? "->" : "--";
|
|
51181
|
+
}
|
|
51182
|
+
function isDirected(g) {
|
|
51183
|
+
return g.kind === "directed" || g.kind === "strict-directed";
|
|
51184
|
+
}
|
|
51185
|
+
|
|
51186
|
+
// src/render/dot/agwrite.ts
|
|
51187
|
+
var DotWriterBase = class {
|
|
51188
|
+
/** Clusters in render order (GD_clust); filled by the renderer's endCluster. */
|
|
51189
|
+
clusters = [];
|
|
51190
|
+
/**
|
|
51191
|
+
* Emit `key="value"`, escaping the value the way agwrite's agcanonStr does: a
|
|
51192
|
+
* `"` becomes `\"` unless it is already part of an escape sequence (see
|
|
51193
|
+
* agcanonEscape). A draw string carries label text that may contain a bare `"`
|
|
51194
|
+
* (would close the attribute early) or a source `\"`/`\\` (must not be
|
|
51195
|
+
* double-escaped). The byte-length prefix stays on the UNescaped text (the
|
|
51196
|
+
* parser un-escapes before parseXDot re-reads it), matching native exactly.
|
|
51197
|
+
* @see lib/cgraph/write.c:_agstrcanon (135-167)
|
|
51198
|
+
*/
|
|
51199
|
+
drawAttr(key, value) {
|
|
51200
|
+
return key + '="' + agcanonEscape(value) + '"';
|
|
51201
|
+
}
|
|
51202
|
+
/** `llx,lly,urx,ury` from a graph's layout bb. */
|
|
51203
|
+
bbStr(g) {
|
|
51204
|
+
const bb = g.info.bb;
|
|
51205
|
+
return gfmt5(bb.ll.x) + "," + gfmt5(bb.ll.y) + "," + gfmt5(bb.ur.x) + "," + gfmt5(bb.ur.y);
|
|
51206
|
+
}
|
|
51207
|
+
/**
|
|
51208
|
+
* Serialize the whole laid-out graph to xdot DOT text — a faithful port of
|
|
51209
|
+
* cgraph's agwrite (lib/cgraph/write.c). Recurses the subgraph tree
|
|
51210
|
+
* (write_subgs/write_body), scoping each node/edge to the subgraph(s) it
|
|
51211
|
+
* belongs to via preorder numbers (write_node_test/write_edge_test), and
|
|
51212
|
+
* re-emits an object bare (no attrs) on any scope after the first
|
|
51213
|
+
* (attrs_written). This reproduces native's per-subgraph edge re-declarations
|
|
51214
|
+
* — e.g. an edge in a rank=same subgraph is drawn once, then re-declared bare.
|
|
51215
|
+
* @see lib/cgraph/write.c:agwrite
|
|
51216
|
+
*/
|
|
51217
|
+
serialize(g) {
|
|
51218
|
+
const ctx = {
|
|
51219
|
+
out: [],
|
|
51220
|
+
preorder: /* @__PURE__ */ new Map(),
|
|
51221
|
+
nodeLW: /* @__PURE__ */ new Map(),
|
|
51222
|
+
edgeLW: /* @__PURE__ */ new Map(),
|
|
51223
|
+
attrsWritten: /* @__PURE__ */ new Set(),
|
|
51224
|
+
level: 0
|
|
51225
|
+
};
|
|
51226
|
+
this.subgdfs(g, 1, ctx.preorder);
|
|
51227
|
+
this.writeHdr(g, true, ctx);
|
|
51228
|
+
this.writeBody(g, ctx);
|
|
51229
|
+
this.writeTrl(ctx);
|
|
51230
|
+
return ctx.out.join("");
|
|
51231
|
+
}
|
|
51232
|
+
/** Preorder-number the subgraph tree. @see write.c:subgdfs */
|
|
51233
|
+
subgdfs(g, ix, preorder) {
|
|
51234
|
+
let ix0 = ix;
|
|
51235
|
+
preorder.set(g, ix0);
|
|
51236
|
+
for (const sub of g.subgraphs.values()) ix0 = this.subgdfs(sub, ix0, preorder);
|
|
51237
|
+
return ix0 + 1;
|
|
51238
|
+
}
|
|
51239
|
+
/** A subgraph is anonymous when its name is empty or a `%N` local name. */
|
|
51240
|
+
isAnonymous(g) {
|
|
51241
|
+
return g.name.length === 0 || g.name.charCodeAt(0) === 37;
|
|
51242
|
+
}
|
|
51243
|
+
/** A graph carries a `bb` attribute when it is the root or has a layout bb
|
|
51244
|
+
* (clusters, and any subgraph output.c computed a box for). Native seeds `bb`
|
|
51245
|
+
* on every graph via safe_dcl, so the value differs (set vs empty "") between
|
|
51246
|
+
* a boxed graph and an unboxed child — the driver that makes an anon subgraph
|
|
51247
|
+
* under the root or a cluster "relevant". @see lib/common/output.c:safe_dcl */
|
|
51248
|
+
hasBb(g) {
|
|
51249
|
+
return g === g.root || this.clusters.includes(g);
|
|
51250
|
+
}
|
|
51251
|
+
/** Anonymous subgraph with no own node/edge defaults and no graph attrs
|
|
51252
|
+
* differing from its parent → inlined into the parent. Native compares every
|
|
51253
|
+
* graph attr over the root attr dict; the load-bearing ones are `bb` (set on
|
|
51254
|
+
* the parent, empty on the child) and `rank`. @see write.c:irrelevant_subgraph */
|
|
51255
|
+
irrelevantSubgraph(g) {
|
|
51256
|
+
if (!this.isAnonymous(g)) return false;
|
|
51257
|
+
if (this.clusters.includes(g)) return false;
|
|
51258
|
+
if (g.nodeDefaults.size > 0 || g.edgeDefaults.size > 0) return false;
|
|
51259
|
+
if (g.parent) {
|
|
51260
|
+
if (this.hasBb(g) !== this.hasBb(g.parent)) return false;
|
|
51261
|
+
for (const [k, v] of g.attrs) {
|
|
51262
|
+
if (g.parent.attrs.get(k) !== v) return false;
|
|
51263
|
+
}
|
|
51264
|
+
} else if (g.attrs.size > 0) {
|
|
51265
|
+
return false;
|
|
51266
|
+
}
|
|
51267
|
+
return true;
|
|
51268
|
+
}
|
|
51269
|
+
/** Non-draw graph attrs a subgraph emits (rank for rank=same; clusters use
|
|
51270
|
+
* clusterAttrs). Only comparator-relevant fields need be exact.
|
|
51271
|
+
*
|
|
51272
|
+
* `rec_attach_bb` walks the ROOT and then `GD_clust` recursively, so a
|
|
51273
|
+
* subgraph the layout did not box is attached NEITHER `bb` NOR the label
|
|
51274
|
+
* triple — both keep the INPUT's values, which agwrite echoes. circo and
|
|
51275
|
+
* twopi lay out no clusters at all (`GD_clust` empty — mirrored here by
|
|
51276
|
+
* `this.clusters`, filled from endCluster), so on a re-fed dot output their
|
|
51277
|
+
* `cluster0` comes back carrying the *previous* run's `bb` and `lp`.
|
|
51278
|
+
* @see lib/common/output.c:249 rec_attach_bb */
|
|
51279
|
+
subgGraphAttrs(sg) {
|
|
51280
|
+
const parts = [];
|
|
51281
|
+
parts.push(...echoGraphAttr(sg, "bb"));
|
|
51282
|
+
parts.push(...graphLabelAttrs(sg));
|
|
51283
|
+
parts.push(...graphInputParts(sg, false, this.emitDraws));
|
|
51284
|
+
return parts;
|
|
51285
|
+
}
|
|
51286
|
+
indent(ctx) {
|
|
51287
|
+
return " ".repeat(ctx.level);
|
|
51288
|
+
}
|
|
51289
|
+
/** @see write.c:write_hdr */
|
|
51290
|
+
writeHdr(g, top, ctx) {
|
|
51291
|
+
if (top) {
|
|
51292
|
+
const strict = g.kind === "strict-directed" || g.kind === "strict-undirected" ? "strict " : "";
|
|
51293
|
+
const kw = isDirected(g) ? "digraph" : "graph";
|
|
51294
|
+
const nm = g.name.length > 0 && !this.isAnonymous(g) ? xdotId(g.name) + " " : "";
|
|
51295
|
+
ctx.out.push(strict + kw + " " + nm + "{\n");
|
|
51296
|
+
ctx.level++;
|
|
51297
|
+
} else {
|
|
51298
|
+
const nm = this.isAnonymous(g) ? "" : "subgraph " + xdotId(g.name) + " ";
|
|
51299
|
+
ctx.out.push(this.indent(ctx) + nm + "{\n");
|
|
51300
|
+
ctx.level++;
|
|
51301
|
+
}
|
|
51302
|
+
this.writeDicts(g, top, ctx);
|
|
51303
|
+
}
|
|
51304
|
+
/**
|
|
51305
|
+
* Emit this scope's `graph` / `node` / `edge` default statements, in that
|
|
51306
|
+
* order. @see lib/cgraph/write.c:307 write_dicts
|
|
51307
|
+
*/
|
|
51308
|
+
writeDicts(g, top, ctx) {
|
|
51309
|
+
const graphParts = top ? this.graphAttrs(g) : this.clusters.includes(g) ? this.clusterAttrs(g) : this.subgGraphAttrs(g);
|
|
51310
|
+
this.writeDict("graph", graphParts, ctx);
|
|
51311
|
+
this.writeDict("node", nodeDictParts(g, top), ctx);
|
|
51312
|
+
this.writeDict("edge", dictParts(g.edgeDefaults), ctx);
|
|
51313
|
+
}
|
|
51314
|
+
/**
|
|
51315
|
+
* One `<name> [...]` default statement. Entries are emitted in **strcmp order
|
|
51316
|
+
* of attribute name**: cgraph's attribute dicts are `Dttree`s keyed on
|
|
51317
|
+
* `Agsym_t.name` with a NULL comparf (attr.c:34), so `dtfirst`/`dtnext` walk
|
|
51318
|
+
* them sorted — the oracle's `graph [bb, rankdir]` and `[height, pos, width]`.
|
|
51319
|
+
*
|
|
51320
|
+
* Layout mirrors write_dict exactly: a single entry stays on one line, and two
|
|
51321
|
+
* or more break after each `,` with the body indented one level deeper and the
|
|
51322
|
+
* closing `];` back at the statement's own level.
|
|
51323
|
+
*
|
|
51324
|
+
* Not ported: the `EMPTY(defval) && !sym->print` skip (write.c:271-280). Every
|
|
51325
|
+
* entry here was explicitly declared or computed, i.e. `print` is set, so that
|
|
51326
|
+
* branch is unreachable until the eager-propagation artifact lands — an
|
|
51327
|
+
* `agapply`-installed empty default is the only `print == false` producer.
|
|
51328
|
+
*
|
|
51329
|
+
* @see lib/cgraph/write.c:262 write_dict
|
|
51330
|
+
*/
|
|
51331
|
+
writeDict(name, parts, ctx) {
|
|
51332
|
+
if (parts.length === 0) return;
|
|
51333
|
+
const sorted = [...parts].sort((a, b) => {
|
|
51334
|
+
const ka = a.slice(0, a.indexOf("="));
|
|
51335
|
+
const kb = b.slice(0, b.indexOf("="));
|
|
51336
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
51337
|
+
});
|
|
51338
|
+
ctx.out.push(this.indent(ctx) + name + " [");
|
|
51339
|
+
ctx.level++;
|
|
51340
|
+
ctx.out.push(sorted.join(",\n" + this.indent(ctx)));
|
|
51341
|
+
ctx.level--;
|
|
51342
|
+
if (sorted.length > 1) ctx.out.push("\n" + this.indent(ctx));
|
|
51343
|
+
ctx.out.push("];\n");
|
|
51344
|
+
}
|
|
51345
|
+
/**
|
|
51346
|
+
* One object's `[...]` attribute block, or '' when it has no attributes.
|
|
51347
|
+
* Entries sort strcmp like every dict (attr.c:34). Layout is
|
|
51348
|
+
* write_nondefault_attrs, which differs from write_dict in two ways: the block
|
|
51349
|
+
* opens with a literal TAB before `[`, and the closing `]` follows the last
|
|
51350
|
+
* value directly — there is no newline+indent before it.
|
|
51351
|
+
* @see lib/cgraph/write.c:471 write_nondefault_attrs
|
|
51352
|
+
*/
|
|
51353
|
+
objAttrBlock(parts, ctx) {
|
|
51354
|
+
if (parts.length === 0) return "";
|
|
51355
|
+
const sorted = [...parts].sort((a, b) => {
|
|
51356
|
+
const ka = a.slice(0, a.indexOf("="));
|
|
51357
|
+
const kb = b.slice(0, b.indexOf("="));
|
|
51358
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
51359
|
+
});
|
|
51360
|
+
ctx.level++;
|
|
51361
|
+
const body = sorted.join(",\n" + this.indent(ctx));
|
|
51362
|
+
ctx.level--;
|
|
51363
|
+
return " [" + body + "]";
|
|
51364
|
+
}
|
|
51365
|
+
/** @see write.c:write_trl */
|
|
51366
|
+
writeTrl(ctx) {
|
|
51367
|
+
ctx.level--;
|
|
51368
|
+
ctx.out.push(this.indent(ctx) + "}\n");
|
|
51369
|
+
}
|
|
51370
|
+
/** @see write.c:write_subgs */
|
|
51371
|
+
writeSubgs(g, ctx) {
|
|
51372
|
+
for (const sub of g.subgraphs.values()) {
|
|
51373
|
+
if (this.irrelevantSubgraph(sub)) {
|
|
51374
|
+
this.writeSubgs(sub, ctx);
|
|
51375
|
+
} else {
|
|
51376
|
+
this.writeHdr(sub, false, ctx);
|
|
51377
|
+
this.writeBody(sub, ctx);
|
|
51378
|
+
this.writeTrl(ctx);
|
|
51379
|
+
}
|
|
51380
|
+
}
|
|
51381
|
+
}
|
|
51382
|
+
/** @see write.c:write_body — subgraphs, then this scope's nodes and edges. */
|
|
51383
|
+
writeBody(g, ctx) {
|
|
51384
|
+
this.writeSubgs(g, ctx);
|
|
51385
|
+
for (const n of nodesInSeq(g)) {
|
|
51386
|
+
if (this.writeNodeTest(g, n, ctx)) this.writeNode(g, n, ctx);
|
|
51387
|
+
let prev = n;
|
|
51388
|
+
for (const e of n.outEdges(g)) {
|
|
51389
|
+
if (prev !== e.head && this.writeNodeTest(g, e.head, ctx)) {
|
|
51390
|
+
this.writeNode(g, e.head, ctx);
|
|
51391
|
+
prev = e.head;
|
|
51392
|
+
}
|
|
51393
|
+
if (this.writeEdgeTest(g, e, ctx)) this.writeEdge(g, e, ctx);
|
|
51394
|
+
}
|
|
51395
|
+
}
|
|
51396
|
+
}
|
|
51397
|
+
/** @see write.c:write_node_test — every xdot node carries pos/size, so it is
|
|
51398
|
+
* never "default"; write it in the first scope that has not yet emitted it.
|
|
51399
|
+
* Cluster nodes (fdp compound proxies, ND_clustnode) are never declared —
|
|
51400
|
+
* C's write_plain/writenodeandport suppress them. @see lib/common/output.c:146 */
|
|
51401
|
+
writeNodeTest(g, n, ctx) {
|
|
51402
|
+
if (n.info.clustnode) return false;
|
|
51403
|
+
return (ctx.nodeLW.get(n) ?? 0) < ctx.preorder.get(g);
|
|
50362
51404
|
}
|
|
50363
|
-
|
|
51405
|
+
/** Emitted node name: a cluster node's synthetic `__i:<cluster>` id is written
|
|
51406
|
+
* as the cluster name it stands for. @see lib/common/output.c:114 */
|
|
51407
|
+
emitNodeName(n) {
|
|
51408
|
+
if (n.info.clustnode) {
|
|
51409
|
+
const i = n.name.indexOf(":");
|
|
51410
|
+
if (i >= 0) return n.name.slice(i + 1);
|
|
51411
|
+
}
|
|
51412
|
+
return n.name;
|
|
50364
51413
|
}
|
|
50365
|
-
|
|
51414
|
+
/** @see write.c:write_edge_test */
|
|
51415
|
+
writeEdgeTest(g, e, ctx) {
|
|
51416
|
+
return (ctx.edgeLW.get(e) ?? 0) < ctx.preorder.get(g);
|
|
50366
51417
|
}
|
|
50367
|
-
|
|
50368
|
-
|
|
50369
|
-
|
|
50370
|
-
|
|
50371
|
-
|
|
50372
|
-
|
|
50373
|
-
|
|
50374
|
-
|
|
50375
|
-
|
|
50376
|
-
|
|
50377
|
-
|
|
50378
|
-
|
|
50379
|
-
|
|
50380
|
-
|
|
50381
|
-
|
|
50382
|
-
|
|
50383
|
-
|
|
50384
|
-
|
|
51418
|
+
/** @see write.c:write_node */
|
|
51419
|
+
writeNode(g, n, ctx) {
|
|
51420
|
+
let s = this.indent(ctx) + xdotId(this.emitNodeName(n));
|
|
51421
|
+
if (!ctx.attrsWritten.has(n)) {
|
|
51422
|
+
s += this.objAttrBlock(this.nodeAttrs(n, g), ctx);
|
|
51423
|
+
ctx.attrsWritten.add(n);
|
|
51424
|
+
}
|
|
51425
|
+
ctx.out.push(s + ";\n");
|
|
51426
|
+
ctx.nodeLW.set(n, ctx.preorder.get(g));
|
|
51427
|
+
}
|
|
51428
|
+
/** @see write.c:write_edge — attrs only on first emission, bare thereafter. */
|
|
51429
|
+
/**
|
|
51430
|
+
* The `:port` / `:port:compass` suffix C writes after an endpoint name.
|
|
51431
|
+
*
|
|
51432
|
+
* These are NOT ordinary attributes on the way out: write_nondefault_attrs
|
|
51433
|
+
* skips the tailport/headport symbols (write.c:487-492) precisely because
|
|
51434
|
+
* this function re-emits them as endpoint syntax, so an edge parsed from
|
|
51435
|
+
* `A:f0:ne -> B` round-trips through the port syntax rather than through a
|
|
51436
|
+
* `tailport="f0:ne"` attribute.
|
|
51437
|
+
*
|
|
51438
|
+
* C reads the value with agxget, which falls back to the edge dict default,
|
|
51439
|
+
* and gates on the SYMBOL existing at all — both are subsumed here by the
|
|
51440
|
+
* attr-then-default lookup coming back undefined. An html-like value is
|
|
51441
|
+
* canonicalized whole; a plain one splits on its FIRST `:` (strchr) so the
|
|
51442
|
+
* port and the compass point are canonicalized separately.
|
|
51443
|
+
* @see lib/cgraph/write.c:565 write_port
|
|
51444
|
+
*/
|
|
51445
|
+
portSuffix(e, g, key) {
|
|
51446
|
+
const val2 = e.attrs.get(key) ?? effectiveEdgeDefaults(g).get(key);
|
|
51447
|
+
if (val2 === void 0 || val2.length === 0) return "";
|
|
51448
|
+
if (isHtmlValue(val2)) return ":" + agstrcanon(val2);
|
|
51449
|
+
const i = val2.indexOf(":");
|
|
51450
|
+
if (i < 0) return ":" + agstrcanonText(val2);
|
|
51451
|
+
return ":" + agstrcanonText(val2.slice(0, i)) + ":" + agstrcanonText(val2.slice(i + 1));
|
|
51452
|
+
}
|
|
51453
|
+
writeEdge(g, e, ctx) {
|
|
51454
|
+
const conn = edgeConnector(isDirected(g));
|
|
51455
|
+
let s = this.indent(ctx) + xdotId(this.emitNodeName(e.tail)) + this.portSuffix(e, g, "tailport") + " " + conn + " " + xdotId(this.emitNodeName(e.head)) + this.portSuffix(e, g, "headport");
|
|
51456
|
+
if (!ctx.attrsWritten.has(e)) {
|
|
51457
|
+
s += this.objAttrBlock(this.edgeAttrStr(e, g), ctx);
|
|
51458
|
+
ctx.attrsWritten.add(e);
|
|
51459
|
+
}
|
|
51460
|
+
ctx.out.push(s + ";\n");
|
|
51461
|
+
ctx.edgeLW.set(e, ctx.preorder.get(g));
|
|
51462
|
+
}
|
|
51463
|
+
/** Root-graph attribute block: `_draw_`, `_ldraw_`, `bb`, `xdotversion`. */
|
|
51464
|
+
graphAttrs(g) {
|
|
51465
|
+
const d = this.drawsOf(g);
|
|
51466
|
+
const parts = [];
|
|
51467
|
+
if (d?.draw) parts.push(this.drawAttr("_draw_", d.draw));
|
|
51468
|
+
if (d?.ldraw) parts.push(this.drawAttr("_ldraw_", d.ldraw));
|
|
51469
|
+
parts.push('bb="' + this.bbStr(g) + '"');
|
|
51470
|
+
parts.push(...graphLabelAttrs(g));
|
|
51471
|
+
if (this.emitDraws) parts.push("xdotversion=" + agstrcanon(XDOT_VERSION));
|
|
51472
|
+
parts.push(...graphInputParts(g, true, this.emitDraws));
|
|
51473
|
+
return parts;
|
|
51474
|
+
}
|
|
51475
|
+
/** Cluster attribute block. C's xdot_end_cluster ALWAYS agsets `_draw_` when
|
|
51476
|
+
* the graph has clusters (even empty, e.g. peripheries=0), so emit it
|
|
51477
|
+
* unconditionally; `_ldraw_` only when the cluster has a label.
|
|
51478
|
+
* @see plugin/core/gvrender_core_dot.c:284 xdot_end_cluster */
|
|
51479
|
+
clusterAttrs(sg) {
|
|
51480
|
+
const d = this.drawsOf(sg);
|
|
51481
|
+
const parts = this.emitDraws ? [this.drawAttr("_draw_", d?.draw ?? "")] : [];
|
|
51482
|
+
if (d?.ldraw) parts.push(this.drawAttr("_ldraw_", d.ldraw));
|
|
51483
|
+
if (sg.info.bb) parts.push('bb="' + this.bbStr(sg) + '"');
|
|
51484
|
+
parts.push(...graphLabelAttrs(sg));
|
|
51485
|
+
parts.push(...graphInputParts(sg, false, this.emitDraws));
|
|
51486
|
+
return parts;
|
|
51487
|
+
}
|
|
51488
|
+
/** Node attribute block: pos/width/height plus `_draw_`/`_ldraw_`. `scope` is
|
|
51489
|
+
* the subgraph the node is being WRITTEN in, whose node dict every value is
|
|
51490
|
+
* compared against. @see lib/cgraph/write.c:537-545 write_node */
|
|
51491
|
+
nodeAttrs(n, scope) {
|
|
51492
|
+
const info = n.info;
|
|
51493
|
+
const defs = effectiveNodeDefaults2(scope);
|
|
51494
|
+
const posRaw = gfmt5(info.coord.x) + "," + gfmt5(info.coord.y);
|
|
51495
|
+
const widthRaw = gfmt5((info.lw + info.rw) / 72);
|
|
51496
|
+
const heightRaw = gfmt5(info.ht / 72);
|
|
51497
|
+
const parts = [
|
|
51498
|
+
...computedPart("pos", posRaw, defs),
|
|
51499
|
+
...computedPart("width", widthRaw, defs),
|
|
51500
|
+
...computedPart("height", heightRaw, defs)
|
|
51501
|
+
];
|
|
51502
|
+
parts.push(...this.nodeXlpPart(n, defs));
|
|
51503
|
+
parts.push(...this.nodeRectsPart(n, defs));
|
|
51504
|
+
const d = this.drawsOf(n);
|
|
51505
|
+
if (d?.draw) parts.push(this.drawAttr("_draw_", d.draw));
|
|
51506
|
+
if (d?.ldraw) parts.push(this.drawAttr("_ldraw_", d.ldraw));
|
|
51507
|
+
parts.push(...objInputParts(nodeRecord(n), defs, COMPUTED_NODE_ATTRS));
|
|
51508
|
+
return parts;
|
|
51509
|
+
}
|
|
51510
|
+
/** The `[...]` attribute body for an edge (draw ops + spline `pos`), or ''. */
|
|
51511
|
+
edgeAttrStr(e, g) {
|
|
51512
|
+
const defs = effectiveEdgeDefaults(g);
|
|
51513
|
+
const parts = this.edgeDrawParts(e);
|
|
51514
|
+
const posRaw = edgePosRaw(e);
|
|
51515
|
+
if (posRaw !== null) {
|
|
51516
|
+
parts.push(...computedPart("pos", posRaw, defs));
|
|
50385
51517
|
} else {
|
|
50386
|
-
|
|
51518
|
+
parts.push(...echoAttr(e.attrs, "pos"));
|
|
50387
51519
|
}
|
|
50388
|
-
|
|
51520
|
+
parts.push(...this.edgeLabelPosParts(e, defs));
|
|
51521
|
+
parts.push(...objInputParts(e.attrs, defs, COMPUTED_EDGE_ATTRS));
|
|
51522
|
+
return parts;
|
|
51523
|
+
}
|
|
51524
|
+
/** `xlp` — only when the node HAS an xlabel AND the xlabel placer actually set
|
|
51525
|
+
* its position (`ND_xlabel(n)->set`). An xlabel that was never placed is
|
|
51526
|
+
* omitted entirely, so an input `xlp` survives and is echoed.
|
|
51527
|
+
* @see lib/common/output.c:309-313 */
|
|
51528
|
+
nodeXlpPart(n, defs) {
|
|
51529
|
+
const xlabel = n.info.xlabel;
|
|
51530
|
+
if (!xlabel || !xlabel.set) return echoAttr(n.attrs, "xlp");
|
|
51531
|
+
const raw = lpStr(xlabel.pos);
|
|
51532
|
+
return computedPart("xlp", raw, defs);
|
|
51533
|
+
}
|
|
51534
|
+
/** `rects` — record field boxes. C gates on the SHAPE NAME being exactly
|
|
51535
|
+
* "record" (`strcmp(ND_shape(n)->name, "record") == 0`), so `Mrecord` and
|
|
51536
|
+
* HTML-table nodes get NO rects; confirmed against the native oracle. When
|
|
51537
|
+
* the shape is not a record the input's `rects` is never overwritten and is
|
|
51538
|
+
* echoed — patchwork forces every node to `box`, so a re-fed dot output
|
|
51539
|
+
* returns its old record rects untouched. @see lib/common/output.c:314-317 */
|
|
51540
|
+
nodeRectsPart(n, defs) {
|
|
51541
|
+
const shape = n.info.shape;
|
|
51542
|
+
if (shape?.name !== "record") return echoAttr(n.attrs, "rects");
|
|
51543
|
+
const rects = [];
|
|
51544
|
+
appendRecordRects(n, n.info.shape_info, rects);
|
|
51545
|
+
const raw = rects.join(" ");
|
|
51546
|
+
return computedPart("rects", raw, defs);
|
|
51547
|
+
}
|
|
51548
|
+
/** The six `_draw_`-family attributes of an edge, in C's order. */
|
|
51549
|
+
edgeDrawParts(e) {
|
|
51550
|
+
const d = this.drawsOf(e);
|
|
51551
|
+
const parts = [];
|
|
51552
|
+
if (d?.draw) parts.push(this.drawAttr("_draw_", d.draw));
|
|
51553
|
+
if (d?.ldraw) parts.push(this.drawAttr("_ldraw_", d.ldraw));
|
|
51554
|
+
if (d?.hdraw) parts.push(this.drawAttr("_hdraw_", d.hdraw));
|
|
51555
|
+
if (d?.tdraw) parts.push(this.drawAttr("_tdraw_", d.tdraw));
|
|
51556
|
+
if (d?.hldraw) parts.push(this.drawAttr("_hldraw_", d.hldraw));
|
|
51557
|
+
if (d?.tldraw) parts.push(this.drawAttr("_tldraw_", d.tldraw));
|
|
51558
|
+
return parts;
|
|
51559
|
+
}
|
|
51560
|
+
/**
|
|
51561
|
+
* `lp`/`xlp`/`head_lp`/`tail_lp` for one edge, in C's order.
|
|
51562
|
+
*
|
|
51563
|
+
* Note the asymmetry C encodes: `lp`/`head_lp`/`tail_lp` are emitted whenever
|
|
51564
|
+
* the label EXISTS, but `xlp` additionally requires `->set` — an unplaced
|
|
51565
|
+
* xlabel is omitted. Each gate is independent, and each FAILED gate leaves the
|
|
51566
|
+
* input's own value in the slot for write.c to echo: an unrouted edge
|
|
51567
|
+
* (patchwork routes none) returns the `lp` a previous dot run wrote.
|
|
51568
|
+
* @see lib/common/output.c:377-396
|
|
51569
|
+
*/
|
|
51570
|
+
edgeLabelPosParts(e, defs) {
|
|
51571
|
+
const attached = edgeAttrsAttached(e);
|
|
51572
|
+
const info = e.info;
|
|
51573
|
+
const parts = [];
|
|
51574
|
+
const lpPart = (key, label) => {
|
|
51575
|
+
const raw = lpStr(label.pos);
|
|
51576
|
+
return computedPart(key, raw, defs);
|
|
51577
|
+
};
|
|
51578
|
+
if (attached && info.label) parts.push(...lpPart("lp", info.label));
|
|
51579
|
+
else parts.push(...echoAttr(e.attrs, "lp"));
|
|
51580
|
+
if (attached && info.xlabel && info.xlabel.set) {
|
|
51581
|
+
parts.push(...lpPart("xlp", info.xlabel));
|
|
51582
|
+
} else parts.push(...echoAttr(e.attrs, "xlp"));
|
|
51583
|
+
if (attached && info.head_label) {
|
|
51584
|
+
parts.push(...lpPart("head_lp", info.head_label));
|
|
51585
|
+
} else parts.push(...echoAttr(e.attrs, "head_lp"));
|
|
51586
|
+
if (attached && info.tail_label) {
|
|
51587
|
+
parts.push(...lpPart("tail_lp", info.tail_label));
|
|
51588
|
+
} else parts.push(...echoAttr(e.attrs, "tail_lp"));
|
|
51589
|
+
return parts;
|
|
51590
|
+
}
|
|
51591
|
+
};
|
|
51592
|
+
|
|
51593
|
+
// src/render/dot/edge-draw.ts
|
|
51594
|
+
var DEFAULT_COLOR5 = "black";
|
|
51595
|
+
function emitArrowOp(buf, op, pen) {
|
|
51596
|
+
switch (op.kind) {
|
|
51597
|
+
case "polygon":
|
|
51598
|
+
if (op.filled) buf.push(xdotFillColor(pen));
|
|
51599
|
+
buf.push(xdotPoints(op.filled ? "P" : "p", op.points));
|
|
51600
|
+
break;
|
|
51601
|
+
case "ellipse":
|
|
51602
|
+
if (op.filled) buf.push(xdotFillColor(pen));
|
|
51603
|
+
buf.push(
|
|
51604
|
+
(op.filled ? "E " : "e ") + xdotPoint(op.center) + xdotNum(op.rx) + " " + xdotNum(op.ry) + " "
|
|
51605
|
+
);
|
|
51606
|
+
break;
|
|
51607
|
+
case "polyline":
|
|
51608
|
+
buf.push(xdotPoints("L", op.points));
|
|
51609
|
+
break;
|
|
51610
|
+
case "bezier":
|
|
51611
|
+
buf.push(xdotPoints("B", op.points));
|
|
51612
|
+
break;
|
|
50389
51613
|
}
|
|
50390
|
-
return out;
|
|
50391
|
-
}
|
|
50392
|
-
function trimFixed3(v) {
|
|
50393
|
-
let s = v.toFixed(3);
|
|
50394
|
-
if (s.indexOf(".") < 0) return s;
|
|
50395
|
-
let end = s.length;
|
|
50396
|
-
while (end > 0 && s[end - 1] === "0") end--;
|
|
50397
|
-
if (s[end - 1] === ".") end--;
|
|
50398
|
-
return s.slice(0, end);
|
|
50399
51614
|
}
|
|
50400
|
-
var
|
|
51615
|
+
var EdgeDrawBase = class extends DotWriterBase {
|
|
51616
|
+
/** setlinewidth state per emit_state. @see gvrender_core_dot.c penwidth[] */
|
|
51617
|
+
penwidth = new Array(12).fill(1);
|
|
51618
|
+
/**
|
|
51619
|
+
* The plain single-colour spline branch of beginEdge — lifted verbatim so the
|
|
51620
|
+
* four-way spline-kind chain above stays readable; no branch was reordered or
|
|
51621
|
+
* merged. splines=ortho + radius/style=rounded emits straight segments plus
|
|
51622
|
+
* corner arcs as polylines (L), else the bezier itself.
|
|
51623
|
+
* @see lib/common/emit.c:2583
|
|
51624
|
+
*/
|
|
51625
|
+
emitPlainSpline(e, list, edraw, job) {
|
|
51626
|
+
const radius = orthoRoundedRadius(e, job);
|
|
51627
|
+
const obj = job.obj;
|
|
51628
|
+
const origStyle = obj !== null ? [...obj.rawStyle] : [];
|
|
51629
|
+
const multi = list.length > 1;
|
|
51630
|
+
for (const bez of list) {
|
|
51631
|
+
this.emitSplineBezier(bez, radius, edraw, job);
|
|
51632
|
+
if (obj !== null && multi && (bez.sflag || bez.eflag)) {
|
|
51633
|
+
obj.rawStyle = origStyle.length > 0 ? origStyle : ["solid"];
|
|
51634
|
+
}
|
|
51635
|
+
}
|
|
51636
|
+
if (obj !== null) obj.rawStyle = origStyle;
|
|
51637
|
+
}
|
|
51638
|
+
/** One bezier of a plain spline: rounded-ortho corner polylines when a radius
|
|
51639
|
+
* applies and the segment has enough control points, else the bezier. */
|
|
51640
|
+
emitSplineBezier(bez, radius, edraw, job) {
|
|
51641
|
+
const pts = bez.list.slice(0, bez.size);
|
|
51642
|
+
const polys = radius !== null && bez.size >= 4 ? orthoRoundedPolylines(pts, radius) : [];
|
|
51643
|
+
if (polys.length === 0) {
|
|
51644
|
+
edraw.push(this.styleOp(job), this.penOp(job), xdotPoints("B", pts));
|
|
51645
|
+
return;
|
|
51646
|
+
}
|
|
51647
|
+
for (const poly of polys) edraw.push(this.styleOp(job), this.penOp(job), xdotPoints("L", poly));
|
|
51648
|
+
}
|
|
51649
|
+
emitTaperedSpline(e, bz, edraw, job) {
|
|
51650
|
+
if (bz === void 0) return;
|
|
51651
|
+
const radfunc = taperfun(e.attrs.get("dir"), isDirected(e.tail.root));
|
|
51652
|
+
const verts = taper(bz, radfunc, job.obj?.penWidth ?? 1);
|
|
51653
|
+
const edgeColor = job.obj?.penColor ?? { type: "string", s: "black" };
|
|
51654
|
+
edraw.push(this.styleOp(job));
|
|
51655
|
+
edraw.push(xdotPenColor(resolveRenderColor("transparent")));
|
|
51656
|
+
edraw.push(xdotFillColor(edgeColor));
|
|
51657
|
+
edraw.push(xdotPoints("P", verts));
|
|
51658
|
+
}
|
|
51659
|
+
/**
|
|
51660
|
+
* Emit a split-along-length `;` multicolor edge spline: split each routed
|
|
51661
|
+
* bezier along its arc length into one sub-curve per color segment, drawn
|
|
51662
|
+
* under that segment's pen. Reuses the same split geometry
|
|
51663
|
+
* (splitSplineByColor) as the SVG path. @see lib/common/emit.c:1975 multicolor
|
|
51664
|
+
*/
|
|
51665
|
+
emitSplitSpline(bzList, colorAttr, edraw, job) {
|
|
51666
|
+
const segs = parseSegs(colorAttr).segs;
|
|
51667
|
+
const firstColor = segs[0]?.color ?? DEFAULT_COLOR5;
|
|
51668
|
+
let endColor = firstColor;
|
|
51669
|
+
for (const bz of bzList) {
|
|
51670
|
+
if (bz === void 0 || bz.size < 4) continue;
|
|
51671
|
+
const split = splitSplineByColor(bz.list.slice(0, bz.size), segs);
|
|
51672
|
+
endColor = split.endColor;
|
|
51673
|
+
for (const c of split.curves) {
|
|
51674
|
+
edraw.push(this.styleOp(job), xdotPenColor(resolveRenderColor(c.color)), xdotPoints("B", c.points));
|
|
51675
|
+
}
|
|
51676
|
+
}
|
|
51677
|
+
return { firstColor, endColor };
|
|
51678
|
+
}
|
|
51679
|
+
/**
|
|
51680
|
+
* Emit the parallel-multicolor edge spline: one offset Bézier per color,
|
|
51681
|
+
* offset SEP=2.0 perpendicular per pass — reusing the same offset geometry
|
|
51682
|
+
* (buildOffsetLists/advanceTmpList) as the SVG parallel-edge path.
|
|
51683
|
+
* @see lib/common/emit.c:2443 (parallel multicolor) / svg-parallel-edge.ts
|
|
51684
|
+
*/
|
|
51685
|
+
emitParallelSpline(bzList, colorAttr, numc, edraw, job) {
|
|
51686
|
+
const segData = bzList.map(
|
|
51687
|
+
(bz) => bz !== void 0 && bz.size >= 4 ? buildOffsetLists(bz.list, (2 + numc) / 2) : { offlist: [], tmplist: [] }
|
|
51688
|
+
);
|
|
51689
|
+
const colors = parseSegs(colorAttr).segs.map((s) => s.color ?? DEFAULT_COLOR5);
|
|
51690
|
+
for (const color of colors) {
|
|
51691
|
+
const pen = xdotPenColor(resolveRenderColor(color));
|
|
51692
|
+
for (const sd of segData) {
|
|
51693
|
+
if (sd.offlist.length === 0) continue;
|
|
51694
|
+
advanceTmpList(sd.tmplist, sd.offlist);
|
|
51695
|
+
edraw.push(this.styleOp(job), pen, xdotPoints("B", sd.tmplist));
|
|
51696
|
+
}
|
|
51697
|
+
}
|
|
51698
|
+
return {
|
|
51699
|
+
headColor: colors[0] ?? DEFAULT_COLOR5,
|
|
51700
|
+
tailColor: colors[1] ?? colors[0] ?? DEFAULT_COLOR5
|
|
51701
|
+
};
|
|
51702
|
+
}
|
|
51703
|
+
/** Emit one arrow's primitive ops into `buf` (pen/fill from the edge color).
|
|
51704
|
+
* arrow_gen sets the default line style ("solid") and the edge penwidth
|
|
51705
|
+
* before each primitive, so a non-default penwidth emits `S setlinewidth(N)`
|
|
51706
|
+
* once (tracked per HDRAW/TDRAW state). @see arrows.c:arrow_gen */
|
|
51707
|
+
emitArrows(buf, ops, job, state3, penOverride) {
|
|
51708
|
+
if (ops === void 0) return;
|
|
51709
|
+
const pen = penOverride !== void 0 ? resolveRenderColor(penOverride) : job.obj?.penColor ?? { type: "string", s: "black" };
|
|
51710
|
+
const pw = job.obj?.penWidth ?? 1;
|
|
51711
|
+
for (const op of ops) {
|
|
51712
|
+
if (Math.abs(pw - this.penwidth[state3]) >= 5e-4) {
|
|
51713
|
+
this.penwidth[state3] = pw;
|
|
51714
|
+
buf.push(xdotStrOp("S ", "setlinewidth(" + trimFixed3(pw) + ")"));
|
|
51715
|
+
}
|
|
51716
|
+
buf.push(xdotStrOp("S ", "solid"));
|
|
51717
|
+
buf.push(xdotPenColor(pen));
|
|
51718
|
+
emitArrowOp(buf, op, pen);
|
|
51719
|
+
}
|
|
51720
|
+
}
|
|
51721
|
+
};
|
|
51722
|
+
|
|
51723
|
+
// src/render/dot.ts
|
|
51724
|
+
var XdotRenderer = class extends EdgeDrawBase {
|
|
50401
51725
|
type = "xdot";
|
|
50402
51726
|
/** @see plugin/core/gvplugin_core.c registration table */
|
|
50403
51727
|
quality = 0;
|
|
51728
|
+
/**
|
|
51729
|
+
* Whether this format attaches the xdot draw attributes before agwrite.
|
|
51730
|
+
* Mirrors C's `job->render.id` switch: FORMAT_DOT runs `attach_attrs(g)` then
|
|
51731
|
+
* `agwrite`, while FORMAT_XDOT runs `attach_attrs_and_arrows` +
|
|
51732
|
+
* `xdot_begin_graph`/`xdot_end_graph` and *then* the same `agwrite`. The
|
|
51733
|
+
* serializer below is that shared `agwrite`; this flag is the only difference.
|
|
51734
|
+
* @see plugin/core/gvrender_core_dot.c:404,418,475,482
|
|
51735
|
+
*/
|
|
51736
|
+
emitDraws = true;
|
|
51737
|
+
/** Draw strings for `obj`, or undefined in a format that emits none (`-Tdot`).
|
|
51738
|
+
* Single gate so the xdot path cannot drift from the dot path. */
|
|
51739
|
+
drawsOf(obj) {
|
|
51740
|
+
return this.emitDraws ? this.draws.get(obj) : void 0;
|
|
51741
|
+
}
|
|
50404
51742
|
/** Per-render draw buffers. Indices 8/9 alias 1; 10/11 alias 5. */
|
|
50405
51743
|
bufs = makeXbufs();
|
|
50406
|
-
/** setlinewidth state per emit_state. @see gvrender_core_dot.c penwidth[] */
|
|
50407
|
-
penwidth = new Array(12).fill(1);
|
|
50408
51744
|
/** text-flag state per emit_state. @see gvrender_core_dot.c textflags[] */
|
|
50409
51745
|
textflags = new Array(12).fill(0);
|
|
50410
51746
|
/** Accumulated draw strings keyed by model object (C: agset on the object). */
|
|
50411
51747
|
draws = /* @__PURE__ */ new Map();
|
|
50412
|
-
/** Clusters in render order — the graphs `endCluster` was actually called for
|
|
50413
|
-
* (from `renderClusters`/`GD_clust`), which is NOT always reachable via
|
|
50414
|
-
* `g.subgraphs`, so it is tracked here rather than re-walked. */
|
|
50415
|
-
clusters = [];
|
|
50416
51748
|
/** edge_in_box gate for the current edge: emit_edge draws nothing (spline OR
|
|
50417
51749
|
* label) for an edge whose content is outside job->clip. Mirrors svg.ts's
|
|
50418
51750
|
* edgeGroupOpen. @see lib/common/emit.c:emit_edge (3039) */
|
|
50419
51751
|
edgeDrawable = true;
|
|
50420
|
-
beginGraph(
|
|
51752
|
+
beginGraph(_g, _job) {
|
|
50421
51753
|
this.bufs = makeXbufs();
|
|
50422
51754
|
this.penwidth = new Array(12).fill(1);
|
|
50423
51755
|
this.textflags = new Array(12).fill(0);
|
|
@@ -50435,7 +51767,7 @@ var XdotRenderer = class {
|
|
|
50435
51767
|
endGraph(g, job) {
|
|
50436
51768
|
const gd = this.flush(0 /* GDraw */);
|
|
50437
51769
|
const gl = this.flush(4 /* GLabel */);
|
|
50438
|
-
if (gd || gl) {
|
|
51770
|
+
if (this.emitDraws && (gd || gl)) {
|
|
50439
51771
|
const set = this.drawsFor(g);
|
|
50440
51772
|
if (gd) set.draw = gd;
|
|
50441
51773
|
if (gl) set.ldraw = escBackslash(gl);
|
|
@@ -50459,19 +51791,25 @@ var XdotRenderer = class {
|
|
|
50459
51791
|
];
|
|
50460
51792
|
const buf = this.bufs[0 /* GDraw */];
|
|
50461
51793
|
buf.push(xdotPenColor(resolveRenderColor("transparent")));
|
|
50462
|
-
|
|
50463
|
-
if (stop !== null) {
|
|
50464
|
-
const angle = Number(g.attrs.get("gradientangle") ?? 0) || 0;
|
|
50465
|
-
const radial = parseStyleFlags(g.attrs.get("style")).radial;
|
|
50466
|
-
const op = radial ? radialGradientOp : linearGradientOp;
|
|
50467
|
-
buf.push(
|
|
50468
|
-
op(corners, resolveRenderColor(stop.fillColor), resolveRenderColor(stop.stopColor), stop.frac, angle)
|
|
50469
|
-
);
|
|
50470
|
-
} else {
|
|
50471
|
-
buf.push(xdotFillColor(resolveRenderColor(fillSpec)));
|
|
50472
|
-
}
|
|
51794
|
+
buf.push(this.backgroundFillOp(g, fillSpec, corners));
|
|
50473
51795
|
buf.push(xdotPoints("P", corners));
|
|
50474
51796
|
}
|
|
51797
|
+
/** The canvas fill op: a two-colour `bgcolor` is a gradient (emit_background →
|
|
51798
|
+
* findStopColor), linear or radial per style; anything else is a flat fill.
|
|
51799
|
+
* @see lib/common/emit.c:1476 emit_background */
|
|
51800
|
+
backgroundFillOp(g, fillSpec, corners) {
|
|
51801
|
+
const stop = fillSpec.includes(":") ? findStopColor(fillSpec) : null;
|
|
51802
|
+
if (stop === null) return xdotFillColor(resolveRenderColor(fillSpec));
|
|
51803
|
+
const angle = Number(g.attrs.get("gradientangle") ?? 0) || 0;
|
|
51804
|
+
const op = parseStyleFlags(g.attrs.get("style")).radial ? radialGradientOp : linearGradientOp;
|
|
51805
|
+
return op(
|
|
51806
|
+
corners,
|
|
51807
|
+
resolveRenderColor(stop.fillColor),
|
|
51808
|
+
resolveRenderColor(stop.stopColor),
|
|
51809
|
+
stop.frac,
|
|
51810
|
+
angle
|
|
51811
|
+
);
|
|
51812
|
+
}
|
|
50475
51813
|
beginNode(_n, _job) {
|
|
50476
51814
|
}
|
|
50477
51815
|
endNode(n, _job) {
|
|
@@ -50510,25 +51848,17 @@ var XdotRenderer = class {
|
|
|
50510
51848
|
} else if (edgeIsTapered(e)) {
|
|
50511
51849
|
this.emitTaperedSpline(e, spl.list[0], edraw, job);
|
|
50512
51850
|
} else if (numc > 0) {
|
|
50513
|
-
this.emitParallelSpline(
|
|
51851
|
+
const c = this.emitParallelSpline(
|
|
51852
|
+
spl.list,
|
|
51853
|
+
colorAttr,
|
|
51854
|
+
numc,
|
|
51855
|
+
edraw,
|
|
51856
|
+
job
|
|
51857
|
+
);
|
|
51858
|
+
tailArrowColor = c.tailColor;
|
|
51859
|
+
headArrowColor = c.headColor;
|
|
50514
51860
|
} else {
|
|
50515
|
-
|
|
50516
|
-
const obj = job.obj;
|
|
50517
|
-
const origStyle = obj !== null ? [...obj.rawStyle] : [];
|
|
50518
|
-
const multi = spl.list.length > 1;
|
|
50519
|
-
for (const bez of spl.list) {
|
|
50520
|
-
const pts = bez.list.slice(0, bez.size);
|
|
50521
|
-
const polys = radius !== null && bez.size >= 4 ? orthoRoundedPolylines(pts, radius) : [];
|
|
50522
|
-
if (polys.length > 0) {
|
|
50523
|
-
for (const poly of polys) edraw.push(this.styleOp(job), this.penOp(job), xdotPoints("L", poly));
|
|
50524
|
-
} else {
|
|
50525
|
-
edraw.push(this.styleOp(job), this.penOp(job), xdotPoints("B", pts));
|
|
50526
|
-
}
|
|
50527
|
-
if (obj !== null && multi && (bez.sflag || bez.eflag)) {
|
|
50528
|
-
obj.rawStyle = origStyle.length > 0 ? origStyle : ["solid"];
|
|
50529
|
-
}
|
|
50530
|
-
}
|
|
50531
|
-
if (obj !== null) obj.rawStyle = origStyle;
|
|
51861
|
+
this.emitPlainSpline(e, spl.list, edraw, job);
|
|
50532
51862
|
}
|
|
50533
51863
|
this.emitArrows(this.bufs[2 /* TDraw */], e.info.tailArrowOps, job, 2 /* TDraw */, tailArrowColor);
|
|
50534
51864
|
this.emitArrows(this.bufs[3 /* HDraw */], e.info.headArrowOps, job, 3 /* HDraw */, headArrowColor);
|
|
@@ -50538,121 +51868,43 @@ var XdotRenderer = class {
|
|
|
50538
51868
|
* -tapered` (the style), a transparent pen, the edge-color fill, then the
|
|
50539
51869
|
* polygon vertices from `taper()` (y-up). @see lib/common/emit.c:2422
|
|
50540
51870
|
*/
|
|
50541
|
-
emitTaperedSpline(e, bz, edraw, job) {
|
|
50542
|
-
if (bz === void 0) return;
|
|
50543
|
-
const radfunc = taperfun(e.attrs.get("dir"), isDirected(e.tail.root));
|
|
50544
|
-
const verts = taper(bz, radfunc, job.obj?.penWidth ?? 1);
|
|
50545
|
-
const edgeColor = job.obj?.penColor ?? { type: "string", s: "black" };
|
|
50546
|
-
edraw.push(this.styleOp(job));
|
|
50547
|
-
edraw.push(xdotPenColor(resolveRenderColor("transparent")));
|
|
50548
|
-
edraw.push(xdotFillColor(edgeColor));
|
|
50549
|
-
edraw.push(xdotPoints("P", verts));
|
|
50550
|
-
}
|
|
50551
|
-
/**
|
|
50552
|
-
* Emit a split-along-length `;` multicolor edge spline: split each routed
|
|
50553
|
-
* bezier along its arc length into one sub-curve per color segment, drawn
|
|
50554
|
-
* under that segment's pen. Reuses the same split geometry
|
|
50555
|
-
* (splitSplineByColor) as the SVG path. @see lib/common/emit.c:1975 multicolor
|
|
50556
|
-
*/
|
|
50557
|
-
emitSplitSpline(bzList, colorAttr, edraw, job) {
|
|
50558
|
-
const segs = parseSegs(colorAttr).segs;
|
|
50559
|
-
const firstColor = segs[0]?.color ?? "black";
|
|
50560
|
-
let endColor = firstColor;
|
|
50561
|
-
for (const bz of bzList) {
|
|
50562
|
-
if (bz === void 0 || bz.size < 4) continue;
|
|
50563
|
-
const split = splitSplineByColor(bz.list.slice(0, bz.size), segs);
|
|
50564
|
-
endColor = split.endColor;
|
|
50565
|
-
for (const c of split.curves) {
|
|
50566
|
-
edraw.push(this.styleOp(job), xdotPenColor(resolveRenderColor(c.color)), xdotPoints("B", c.points));
|
|
50567
|
-
}
|
|
50568
|
-
}
|
|
50569
|
-
return { firstColor, endColor };
|
|
50570
|
-
}
|
|
50571
|
-
/**
|
|
50572
|
-
* Emit the parallel-multicolor edge spline: one offset Bézier per color,
|
|
50573
|
-
* offset SEP=2.0 perpendicular per pass — reusing the same offset geometry
|
|
50574
|
-
* (buildOffsetLists/advanceTmpList) as the SVG parallel-edge path.
|
|
50575
|
-
* @see lib/common/emit.c:2443 (parallel multicolor) / svg-parallel-edge.ts
|
|
50576
|
-
*/
|
|
50577
|
-
emitParallelSpline(bzList, colorAttr, numc, edraw, job) {
|
|
50578
|
-
const segData = bzList.map(
|
|
50579
|
-
(bz) => bz !== void 0 && bz.size >= 4 ? buildOffsetLists(bz.list, (2 + numc) / 2) : { offlist: [], tmplist: [] }
|
|
50580
|
-
);
|
|
50581
|
-
const colors = parseSegs(colorAttr).segs.map((s) => s.color ?? "black");
|
|
50582
|
-
for (const color of colors) {
|
|
50583
|
-
const pen = xdotPenColor(resolveRenderColor(color));
|
|
50584
|
-
for (const sd of segData) {
|
|
50585
|
-
if (sd.offlist.length === 0) continue;
|
|
50586
|
-
advanceTmpList(sd.tmplist, sd.offlist);
|
|
50587
|
-
edraw.push(this.styleOp(job), pen, xdotPoints("B", sd.tmplist));
|
|
50588
|
-
}
|
|
50589
|
-
}
|
|
50590
|
-
}
|
|
50591
|
-
/** Emit one arrow's primitive ops into `buf` (pen/fill from the edge color).
|
|
50592
|
-
* arrow_gen sets the default line style ("solid") and the edge penwidth
|
|
50593
|
-
* before each primitive, so a non-default penwidth emits `S setlinewidth(N)`
|
|
50594
|
-
* once (tracked per HDRAW/TDRAW state). @see arrows.c:arrow_gen */
|
|
50595
|
-
emitArrows(buf, ops, job, state3, penOverride) {
|
|
50596
|
-
if (ops === void 0) return;
|
|
50597
|
-
const pen = penOverride !== void 0 ? resolveRenderColor(penOverride) : job.obj?.penColor ?? { type: "string", s: "black" };
|
|
50598
|
-
const pw = job.obj?.penWidth ?? 1;
|
|
50599
|
-
for (const op of ops) {
|
|
50600
|
-
if (Math.abs(pw - this.penwidth[state3]) >= 5e-4) {
|
|
50601
|
-
this.penwidth[state3] = pw;
|
|
50602
|
-
buf.push(xdotStrOp("S ", "setlinewidth(" + trimFixed3(pw) + ")"));
|
|
50603
|
-
}
|
|
50604
|
-
buf.push(xdotStrOp("S ", "solid"));
|
|
50605
|
-
buf.push(xdotPenColor(pen));
|
|
50606
|
-
switch (op.kind) {
|
|
50607
|
-
case "polygon":
|
|
50608
|
-
if (op.filled) buf.push(xdotFillColor(pen));
|
|
50609
|
-
buf.push(xdotPoints(op.filled ? "P" : "p", op.points));
|
|
50610
|
-
break;
|
|
50611
|
-
case "ellipse":
|
|
50612
|
-
if (op.filled) buf.push(xdotFillColor(pen));
|
|
50613
|
-
buf.push(
|
|
50614
|
-
(op.filled ? "E " : "e ") + xdotPoint(op.center) + xdotNum(op.rx) + " " + xdotNum(op.ry) + " "
|
|
50615
|
-
);
|
|
50616
|
-
break;
|
|
50617
|
-
case "polyline":
|
|
50618
|
-
buf.push(xdotPoints("L", op.points));
|
|
50619
|
-
break;
|
|
50620
|
-
case "bezier":
|
|
50621
|
-
buf.push(xdotPoints("B", op.points));
|
|
50622
|
-
break;
|
|
50623
|
-
}
|
|
50624
|
-
}
|
|
50625
|
-
}
|
|
50626
51871
|
endEdge(e, job) {
|
|
50627
51872
|
if (!this.edgeDrawable) {
|
|
50628
|
-
this.
|
|
50629
|
-
this.resetState(3 /* HDraw */, 2 /* TDraw */);
|
|
50630
|
-
this.penwidth[7 /* HLabel */] = 1;
|
|
50631
|
-
this.penwidth[6 /* TLabel */] = 1;
|
|
51873
|
+
this.resetEdgeState();
|
|
50632
51874
|
return;
|
|
50633
51875
|
}
|
|
50634
51876
|
renderEdgeLabels(e, this, job);
|
|
50635
|
-
|
|
50636
|
-
|
|
50637
|
-
|
|
50638
|
-
|
|
50639
|
-
|
|
50640
|
-
|
|
50641
|
-
|
|
50642
|
-
|
|
50643
|
-
|
|
50644
|
-
|
|
50645
|
-
|
|
50646
|
-
|
|
50647
|
-
|
|
50648
|
-
|
|
51877
|
+
this.attachEdgeDraws(e);
|
|
51878
|
+
this.resetEdgeState();
|
|
51879
|
+
this.textflags[7 /* HLabel */] = 0;
|
|
51880
|
+
this.textflags[6 /* TLabel */] = 0;
|
|
51881
|
+
}
|
|
51882
|
+
/** Flush the six edge buffers and attach the non-empty ones. Label buffers get
|
|
51883
|
+
* put_escaping_backslashes; shape buffers do not.
|
|
51884
|
+
* @see plugin/core/gvrender_core_dot.c:218 */
|
|
51885
|
+
attachEdgeDraws(e) {
|
|
51886
|
+
const spec = [
|
|
51887
|
+
[9 /* EDraw */, "draw", false],
|
|
51888
|
+
[3 /* HDraw */, "hdraw", false],
|
|
51889
|
+
[2 /* TDraw */, "tdraw", false],
|
|
51890
|
+
[11 /* ELabel */, "ldraw", true],
|
|
51891
|
+
[7 /* HLabel */, "hldraw", true],
|
|
51892
|
+
[6 /* TLabel */, "tldraw", true]
|
|
51893
|
+
];
|
|
51894
|
+
const flushed = spec.map(([state3, field, isLabel]) => [field, this.flush(state3), isLabel]);
|
|
51895
|
+
if (!flushed.some(([, s]) => s)) return;
|
|
51896
|
+
const set = this.drawsFor(e);
|
|
51897
|
+
for (const [field, s, isLabel] of flushed) {
|
|
51898
|
+
if (s) set[field] = isLabel ? escBackslash(s) : s;
|
|
50649
51899
|
}
|
|
51900
|
+
}
|
|
51901
|
+
/** Per-edge emit-state reset, run on both the drawable and the early-return
|
|
51902
|
+
* path — identical in C, so it is one helper here rather than two copies. */
|
|
51903
|
+
resetEdgeState() {
|
|
50650
51904
|
this.resetState(9 /* EDraw */, 11 /* ELabel */);
|
|
50651
51905
|
this.resetState(3 /* HDraw */, 2 /* TDraw */);
|
|
50652
51906
|
this.penwidth[7 /* HLabel */] = 1;
|
|
50653
51907
|
this.penwidth[6 /* TLabel */] = 1;
|
|
50654
|
-
this.textflags[7 /* HLabel */] = 0;
|
|
50655
|
-
this.textflags[6 /* TLabel */] = 0;
|
|
50656
51908
|
}
|
|
50657
51909
|
beginCluster(_sg, _job) {
|
|
50658
51910
|
}
|
|
@@ -50749,302 +52001,48 @@ var XdotRenderer = class {
|
|
|
50749
52001
|
}
|
|
50750
52002
|
return s;
|
|
50751
52003
|
}
|
|
50752
|
-
// --- buffer + side-table plumbing --------------------------------------
|
|
50753
|
-
/** Active xdot buffer for the current emit state. */
|
|
50754
|
-
getBuf(job) {
|
|
50755
|
-
const obj = job.obj;
|
|
50756
|
-
return this.bufs[obj !== null ? obj.emitState : 0 /* GDraw */];
|
|
50757
|
-
}
|
|
50758
|
-
/** Join and clear the buffer for `state`; returns '' when empty. */
|
|
50759
|
-
flush(state3) {
|
|
50760
|
-
const buf = this.bufs[state3];
|
|
50761
|
-
const s = buf.join("");
|
|
50762
|
-
buf.length = 0;
|
|
50763
|
-
return s;
|
|
50764
|
-
}
|
|
50765
|
-
/** Reset per-emit-state penwidth/textflags after an object (C's reset). */
|
|
50766
|
-
resetState(draw, label) {
|
|
50767
|
-
this.penwidth[draw] = 1;
|
|
50768
|
-
this.penwidth[label] = 1;
|
|
50769
|
-
this.textflags[draw] = 0;
|
|
50770
|
-
this.textflags[label] = 0;
|
|
50771
|
-
}
|
|
50772
|
-
drawsFor(o) {
|
|
50773
|
-
let d = this.draws.get(o);
|
|
50774
|
-
if (d === void 0) {
|
|
50775
|
-
d = {};
|
|
50776
|
-
this.draws.set(o, d);
|
|
50777
|
-
}
|
|
50778
|
-
return d;
|
|
50779
|
-
}
|
|
50780
|
-
// --- serialization (agwrite-at-end) ------------------------------------
|
|
50781
|
-
/**
|
|
50782
|
-
* Emit `key="value"`, escaping the value the way agwrite's agcanonStr does: a
|
|
50783
|
-
* `"` becomes `\"` unless it is already part of an escape sequence (see
|
|
50784
|
-
* agcanonEscape). A draw string carries label text that may contain a bare `"`
|
|
50785
|
-
* (would close the attribute early) or a source `\"`/`\\` (must not be
|
|
50786
|
-
* double-escaped). The byte-length prefix stays on the UNescaped text (the
|
|
50787
|
-
* parser un-escapes before parseXDot re-reads it), matching native exactly.
|
|
50788
|
-
* @see lib/cgraph/write.c:_agstrcanon (135-167)
|
|
50789
|
-
*/
|
|
50790
|
-
drawAttr(key, value) {
|
|
50791
|
-
return key + '="' + agcanonEscape(value) + '"';
|
|
50792
|
-
}
|
|
50793
|
-
/** `llx,lly,urx,ury` from a graph's layout bb. */
|
|
50794
|
-
bbStr(g) {
|
|
50795
|
-
const bb = g.info.bb;
|
|
50796
|
-
return gfmt5(bb.ll.x) + "," + gfmt5(bb.ll.y) + "," + gfmt5(bb.ur.x) + "," + gfmt5(bb.ur.y);
|
|
50797
|
-
}
|
|
50798
|
-
/**
|
|
50799
|
-
* Serialize the whole laid-out graph to xdot DOT text — a faithful port of
|
|
50800
|
-
* cgraph's agwrite (lib/cgraph/write.c). Recurses the subgraph tree
|
|
50801
|
-
* (write_subgs/write_body), scoping each node/edge to the subgraph(s) it
|
|
50802
|
-
* belongs to via preorder numbers (write_node_test/write_edge_test), and
|
|
50803
|
-
* re-emits an object bare (no attrs) on any scope after the first
|
|
50804
|
-
* (attrs_written). This reproduces native's per-subgraph edge re-declarations
|
|
50805
|
-
* — e.g. an edge in a rank=same subgraph is drawn once, then re-declared bare.
|
|
50806
|
-
* @see lib/cgraph/write.c:agwrite
|
|
50807
|
-
*/
|
|
50808
|
-
serialize(g) {
|
|
50809
|
-
const ctx = {
|
|
50810
|
-
out: [],
|
|
50811
|
-
preorder: /* @__PURE__ */ new Map(),
|
|
50812
|
-
nodeLW: /* @__PURE__ */ new Map(),
|
|
50813
|
-
edgeLW: /* @__PURE__ */ new Map(),
|
|
50814
|
-
attrsWritten: /* @__PURE__ */ new Set(),
|
|
50815
|
-
level: 0
|
|
50816
|
-
};
|
|
50817
|
-
this.subgdfs(g, 1, ctx.preorder);
|
|
50818
|
-
this.writeHdr(g, true, ctx);
|
|
50819
|
-
this.writeBody(g, ctx);
|
|
50820
|
-
this.writeTrl(ctx);
|
|
50821
|
-
return ctx.out.join("");
|
|
50822
|
-
}
|
|
50823
|
-
/** Preorder-number the subgraph tree. @see write.c:subgdfs */
|
|
50824
|
-
subgdfs(g, ix, preorder) {
|
|
50825
|
-
let ix0 = ix;
|
|
50826
|
-
preorder.set(g, ix0);
|
|
50827
|
-
for (const sub of g.subgraphs.values()) ix0 = this.subgdfs(sub, ix0, preorder);
|
|
50828
|
-
return ix0 + 1;
|
|
50829
|
-
}
|
|
50830
|
-
/** A subgraph is anonymous when its name is empty or a `%N` local name. */
|
|
50831
|
-
isAnonymous(g) {
|
|
50832
|
-
return g.name.length === 0 || g.name.charCodeAt(0) === 37;
|
|
50833
|
-
}
|
|
50834
|
-
/** A graph carries a `bb` attribute when it is the root or has a layout bb
|
|
50835
|
-
* (clusters, and any subgraph output.c computed a box for). Native seeds `bb`
|
|
50836
|
-
* on every graph via safe_dcl, so the value differs (set vs empty "") between
|
|
50837
|
-
* a boxed graph and an unboxed child — the driver that makes an anon subgraph
|
|
50838
|
-
* under the root or a cluster "relevant". @see lib/common/output.c:safe_dcl */
|
|
50839
|
-
hasBb(g) {
|
|
50840
|
-
return g === g.root || this.clusters.includes(g);
|
|
50841
|
-
}
|
|
50842
|
-
/** Anonymous subgraph with no own node/edge defaults and no graph attrs
|
|
50843
|
-
* differing from its parent → inlined into the parent. Native compares every
|
|
50844
|
-
* graph attr over the root attr dict; the load-bearing ones are `bb` (set on
|
|
50845
|
-
* the parent, empty on the child) and `rank`. @see write.c:irrelevant_subgraph */
|
|
50846
|
-
irrelevantSubgraph(g) {
|
|
50847
|
-
if (!this.isAnonymous(g)) return false;
|
|
50848
|
-
if (this.clusters.includes(g)) return false;
|
|
50849
|
-
if (g.nodeDefaults.size > 0 || g.edgeDefaults.size > 0) return false;
|
|
50850
|
-
if (g.parent) {
|
|
50851
|
-
if (this.hasBb(g) !== this.hasBb(g.parent)) return false;
|
|
50852
|
-
for (const [k, v] of g.attrs) {
|
|
50853
|
-
if (g.parent.attrs.get(k) !== v) return false;
|
|
50854
|
-
}
|
|
50855
|
-
} else if (g.attrs.size > 0) {
|
|
50856
|
-
return false;
|
|
50857
|
-
}
|
|
50858
|
-
return true;
|
|
50859
|
-
}
|
|
50860
|
-
/** Non-draw graph attrs a subgraph emits (rank for rank=same; clusters use
|
|
50861
|
-
* clusterAttrs). Only comparator-relevant fields need be exact.
|
|
50862
|
-
*
|
|
50863
|
-
* `rec_attach_bb` walks the ROOT and then `GD_clust` recursively, so a
|
|
50864
|
-
* subgraph the layout did not box is attached NEITHER `bb` NOR the label
|
|
50865
|
-
* triple — both keep the INPUT's values, which agwrite echoes. circo and
|
|
50866
|
-
* twopi lay out no clusters at all (`GD_clust` empty — mirrored here by
|
|
50867
|
-
* `this.clusters`, filled from endCluster), so on a re-fed dot output their
|
|
50868
|
-
* `cluster0` comes back carrying the *previous* run's `bb` and `lp`.
|
|
50869
|
-
* @see lib/common/output.c:249 rec_attach_bb */
|
|
50870
|
-
subgGraphAttrs(sg) {
|
|
50871
|
-
if (this.clusters.includes(sg)) return this.clusterAttrs(sg);
|
|
50872
|
-
const parts = [];
|
|
50873
|
-
const rank3 = sg.attrs.get("rank");
|
|
50874
|
-
if (rank3 !== void 0) parts.push("rank=" + xdotId(rank3));
|
|
50875
|
-
parts.push(...echoAttr(sg.attrs, "bb"));
|
|
50876
|
-
parts.push(...graphLabelAttrs(sg));
|
|
50877
|
-
return parts.join(" ");
|
|
50878
|
-
}
|
|
50879
|
-
indent(ctx) {
|
|
50880
|
-
return " ".repeat(ctx.level);
|
|
50881
|
-
}
|
|
50882
|
-
/** @see write.c:write_hdr */
|
|
50883
|
-
writeHdr(g, top, ctx) {
|
|
50884
|
-
if (top) {
|
|
50885
|
-
const strict = g.kind === "strict-directed" || g.kind === "strict-undirected" ? "strict " : "";
|
|
50886
|
-
const kw = isDirected(g) ? "digraph" : "graph";
|
|
50887
|
-
const nm = g.name.length > 0 && !this.isAnonymous(g) ? xdotId(g.name) + " " : "";
|
|
50888
|
-
ctx.out.push(strict + kw + " " + nm + "{\n");
|
|
50889
|
-
ctx.level++;
|
|
50890
|
-
ctx.out.push(this.indent(ctx) + "graph [" + this.graphAttrs(g) + "];\n");
|
|
50891
|
-
ctx.out.push(this.indent(ctx) + 'node [label="\\N"];\n');
|
|
50892
|
-
} else {
|
|
50893
|
-
const nm = this.isAnonymous(g) ? "" : "subgraph " + xdotId(g.name) + " ";
|
|
50894
|
-
ctx.out.push(this.indent(ctx) + nm + "{\n");
|
|
50895
|
-
ctx.level++;
|
|
50896
|
-
const ga = this.subgGraphAttrs(g);
|
|
50897
|
-
if (ga) ctx.out.push(this.indent(ctx) + "graph [" + ga + "];\n");
|
|
50898
|
-
}
|
|
50899
|
-
}
|
|
50900
|
-
/** @see write.c:write_trl */
|
|
50901
|
-
writeTrl(ctx) {
|
|
50902
|
-
ctx.level--;
|
|
50903
|
-
ctx.out.push(this.indent(ctx) + "}\n");
|
|
50904
|
-
}
|
|
50905
|
-
/** @see write.c:write_subgs */
|
|
50906
|
-
writeSubgs(g, ctx) {
|
|
50907
|
-
for (const sub of g.subgraphs.values()) {
|
|
50908
|
-
if (this.irrelevantSubgraph(sub)) {
|
|
50909
|
-
this.writeSubgs(sub, ctx);
|
|
50910
|
-
} else {
|
|
50911
|
-
this.writeHdr(sub, false, ctx);
|
|
50912
|
-
this.writeBody(sub, ctx);
|
|
50913
|
-
this.writeTrl(ctx);
|
|
50914
|
-
}
|
|
50915
|
-
}
|
|
50916
|
-
}
|
|
50917
|
-
/** @see write.c:write_body — subgraphs, then this scope's nodes and edges. */
|
|
50918
|
-
writeBody(g, ctx) {
|
|
50919
|
-
this.writeSubgs(g, ctx);
|
|
50920
|
-
for (const n of g.nodes.values()) {
|
|
50921
|
-
if (this.writeNodeTest(g, n, ctx)) this.writeNode(g, n, ctx);
|
|
50922
|
-
let prev = n;
|
|
50923
|
-
for (const e of n.outEdges(g)) {
|
|
50924
|
-
if (prev !== e.head && this.writeNodeTest(g, e.head, ctx)) {
|
|
50925
|
-
this.writeNode(g, e.head, ctx);
|
|
50926
|
-
prev = e.head;
|
|
50927
|
-
}
|
|
50928
|
-
if (this.writeEdgeTest(g, e, ctx)) this.writeEdge(g, e, ctx);
|
|
50929
|
-
}
|
|
50930
|
-
}
|
|
50931
|
-
}
|
|
50932
|
-
/** @see write.c:write_node_test — every xdot node carries pos/size, so it is
|
|
50933
|
-
* never "default"; write it in the first scope that has not yet emitted it.
|
|
50934
|
-
* Cluster nodes (fdp compound proxies, ND_clustnode) are never declared —
|
|
50935
|
-
* C's write_plain/writenodeandport suppress them. @see lib/common/output.c:146 */
|
|
50936
|
-
writeNodeTest(g, n, ctx) {
|
|
50937
|
-
if (n.info.clustnode) return false;
|
|
50938
|
-
return (ctx.nodeLW.get(n) ?? 0) < ctx.preorder.get(g);
|
|
50939
|
-
}
|
|
50940
|
-
/** Emitted node name: a cluster node's synthetic `__i:<cluster>` id is written
|
|
50941
|
-
* as the cluster name it stands for. @see lib/common/output.c:114 */
|
|
50942
|
-
emitNodeName(n) {
|
|
50943
|
-
if (n.info.clustnode) {
|
|
50944
|
-
const i = n.name.indexOf(":");
|
|
50945
|
-
if (i >= 0) return n.name.slice(i + 1);
|
|
50946
|
-
}
|
|
50947
|
-
return n.name;
|
|
50948
|
-
}
|
|
50949
|
-
/** @see write.c:write_edge_test */
|
|
50950
|
-
writeEdgeTest(g, e, ctx) {
|
|
50951
|
-
return (ctx.edgeLW.get(e) ?? 0) < ctx.preorder.get(g);
|
|
52004
|
+
// --- buffer + side-table plumbing --------------------------------------
|
|
52005
|
+
/** Active xdot buffer for the current emit state. */
|
|
52006
|
+
getBuf(job) {
|
|
52007
|
+
const obj = job.obj;
|
|
52008
|
+
return this.bufs[obj !== null ? obj.emitState : 0 /* GDraw */];
|
|
50952
52009
|
}
|
|
50953
|
-
/**
|
|
50954
|
-
|
|
50955
|
-
|
|
50956
|
-
|
|
50957
|
-
|
|
50958
|
-
|
|
50959
|
-
}
|
|
50960
|
-
ctx.out.push(s + ";\n");
|
|
50961
|
-
ctx.nodeLW.set(n, ctx.preorder.get(g));
|
|
52010
|
+
/** Join and clear the buffer for `state`; returns '' when empty. */
|
|
52011
|
+
flush(state3) {
|
|
52012
|
+
const buf = this.bufs[state3];
|
|
52013
|
+
const s = buf.join("");
|
|
52014
|
+
buf.length = 0;
|
|
52015
|
+
return s;
|
|
50962
52016
|
}
|
|
50963
|
-
/**
|
|
50964
|
-
|
|
50965
|
-
|
|
50966
|
-
|
|
50967
|
-
|
|
50968
|
-
|
|
50969
|
-
|
|
50970
|
-
|
|
52017
|
+
/** Reset per-emit-state penwidth/textflags after an object (C's reset). */
|
|
52018
|
+
resetState(draw, label) {
|
|
52019
|
+
this.penwidth[draw] = 1;
|
|
52020
|
+
this.penwidth[label] = 1;
|
|
52021
|
+
this.textflags[draw] = 0;
|
|
52022
|
+
this.textflags[label] = 0;
|
|
52023
|
+
}
|
|
52024
|
+
drawsFor(o) {
|
|
52025
|
+
let d = this.draws.get(o);
|
|
52026
|
+
if (d === void 0) {
|
|
52027
|
+
d = {};
|
|
52028
|
+
this.draws.set(o, d);
|
|
50971
52029
|
}
|
|
50972
|
-
|
|
50973
|
-
ctx.edgeLW.set(e, ctx.preorder.get(g));
|
|
52030
|
+
return d;
|
|
50974
52031
|
}
|
|
50975
|
-
|
|
50976
|
-
|
|
50977
|
-
|
|
50978
|
-
|
|
50979
|
-
|
|
50980
|
-
|
|
50981
|
-
parts.push('bb="' + this.bbStr(g) + '"');
|
|
50982
|
-
parts.push(...graphLabelAttrs(g));
|
|
50983
|
-
parts.push('xdotversion="' + XDOT_VERSION + '"');
|
|
50984
|
-
return parts.join(" ");
|
|
52032
|
+
// --- serialization (agwrite-at-end) ------------------------------------
|
|
52033
|
+
};
|
|
52034
|
+
var DotRenderer = class extends XdotRenderer {
|
|
52035
|
+
type = "dot";
|
|
52036
|
+
emitDraws = false;
|
|
52037
|
+
textspan(_pos, _span, _job) {
|
|
50985
52038
|
}
|
|
50986
|
-
|
|
50987
|
-
* the graph has clusters (even empty, e.g. peripheries=0), so emit it
|
|
50988
|
-
* unconditionally; `_ldraw_` only when the cluster has a label.
|
|
50989
|
-
* @see plugin/core/gvrender_core_dot.c:284 xdot_end_cluster */
|
|
50990
|
-
clusterAttrs(sg) {
|
|
50991
|
-
const d = this.draws.get(sg);
|
|
50992
|
-
const parts = [this.drawAttr("_draw_", d?.draw ?? "")];
|
|
50993
|
-
if (d?.ldraw) parts.push(this.drawAttr("_ldraw_", d.ldraw));
|
|
50994
|
-
if (sg.info.bb) parts.push('bb="' + this.bbStr(sg) + '"');
|
|
50995
|
-
parts.push(...graphLabelAttrs(sg));
|
|
50996
|
-
return parts.join(" ");
|
|
52039
|
+
ellipse(_c, _rx, _ry, _f, _j) {
|
|
50997
52040
|
}
|
|
50998
|
-
|
|
50999
|
-
nodeAttrs(n) {
|
|
51000
|
-
const info = n.info;
|
|
51001
|
-
let s = 'pos="' + gfmt5(info.coord.x) + "," + gfmt5(info.coord.y) + '" width=' + gfmt5((info.lw + info.rw) / 72) + " height=" + gfmt5(info.ht / 72);
|
|
51002
|
-
const xlabel = info.xlabel;
|
|
51003
|
-
if (xlabel && xlabel.set) s += ' xlp="' + lpStr(xlabel.pos) + '"';
|
|
51004
|
-
else s += pfx(echoAttr(n.attrs, "xlp"));
|
|
51005
|
-
const shape = info.shape;
|
|
51006
|
-
if (shape?.name === "record") {
|
|
51007
|
-
const rects = [];
|
|
51008
|
-
appendRecordRects(n, info.shape_info, rects);
|
|
51009
|
-
s += ' rects="' + rects.join(" ") + '"';
|
|
51010
|
-
} else {
|
|
51011
|
-
s += pfx(echoAttr(n.attrs, "rects"));
|
|
51012
|
-
}
|
|
51013
|
-
const d = this.draws.get(n);
|
|
51014
|
-
if (d?.draw) s += " " + this.drawAttr("_draw_", d.draw);
|
|
51015
|
-
if (d?.ldraw) s += " " + this.drawAttr("_ldraw_", d.ldraw);
|
|
51016
|
-
return s;
|
|
52041
|
+
polygon(_pts, _filled, _job) {
|
|
51017
52042
|
}
|
|
51018
|
-
|
|
51019
|
-
|
|
51020
|
-
|
|
51021
|
-
const parts = [];
|
|
51022
|
-
if (d?.draw) parts.push(this.drawAttr("_draw_", d.draw));
|
|
51023
|
-
if (d?.ldraw) parts.push(this.drawAttr("_ldraw_", d.ldraw));
|
|
51024
|
-
if (d?.hdraw) parts.push(this.drawAttr("_hdraw_", d.hdraw));
|
|
51025
|
-
if (d?.tdraw) parts.push(this.drawAttr("_tdraw_", d.tdraw));
|
|
51026
|
-
if (d?.hldraw) parts.push(this.drawAttr("_hldraw_", d.hldraw));
|
|
51027
|
-
if (d?.tldraw) parts.push(this.drawAttr("_tldraw_", d.tldraw));
|
|
51028
|
-
const pos = formatEdgePos(e);
|
|
51029
|
-
if (pos) {
|
|
51030
|
-
parts.push(pos);
|
|
51031
|
-
} else {
|
|
51032
|
-
parts.push(...echoAttr(e.attrs, "pos"));
|
|
51033
|
-
}
|
|
51034
|
-
const attached = edgeAttrsAttached(e);
|
|
51035
|
-
const info = e.info;
|
|
51036
|
-
if (attached && info.label) parts.push('lp="' + lpStr(info.label.pos) + '"');
|
|
51037
|
-
else parts.push(...echoAttr(e.attrs, "lp"));
|
|
51038
|
-
if (attached && info.xlabel && info.xlabel.set) {
|
|
51039
|
-
parts.push('xlp="' + lpStr(info.xlabel.pos) + '"');
|
|
51040
|
-
} else parts.push(...echoAttr(e.attrs, "xlp"));
|
|
51041
|
-
if (attached && info.head_label) {
|
|
51042
|
-
parts.push('head_lp="' + lpStr(info.head_label.pos) + '"');
|
|
51043
|
-
} else parts.push(...echoAttr(e.attrs, "head_lp"));
|
|
51044
|
-
if (attached && info.tail_label) {
|
|
51045
|
-
parts.push('tail_lp="' + lpStr(info.tail_label.pos) + '"');
|
|
51046
|
-
} else parts.push(...echoAttr(e.attrs, "tail_lp"));
|
|
51047
|
-
return parts.join(" ");
|
|
52043
|
+
bezier(_pts, _filled, _job) {
|
|
52044
|
+
}
|
|
52045
|
+
polyline(_pts, _job) {
|
|
51048
52046
|
}
|
|
51049
52047
|
};
|
|
51050
52048
|
function createDotRenderer() {
|
|
@@ -51835,596 +52833,217 @@ function graphEffectiveAttrs(g, top) {
|
|
|
51835
52833
|
for (const [k, v] of g.attrs) m.set(k, v);
|
|
51836
52834
|
return m;
|
|
51837
52835
|
}
|
|
51838
|
-
function isEdgeLabelDeclared(g) {
|
|
51839
|
-
for (const e of g.edges) if (e.attrs.has("label")) return true;
|
|
51840
|
-
const walk = (sg) => {
|
|
51841
|
-
if (sg.edgeDefaults.has("label")) return true;
|
|
51842
|
-
for (const [, sub] of sg.subgraphs) if (walk(sub)) return true;
|
|
51843
|
-
return false;
|
|
51844
|
-
};
|
|
51845
|
-
return walk(g);
|
|
51846
|
-
}
|
|
51847
|
-
function dfsSubgraphs(g, out) {
|
|
51848
|
-
for (const [, sg] of g.subgraphs) {
|
|
51849
|
-
out.push(sg);
|
|
51850
|
-
dfsSubgraphs(sg, out);
|
|
51851
|
-
}
|
|
51852
|
-
}
|
|
51853
|
-
function assignIds(g, doXDot, draws) {
|
|
51854
|
-
const dfsSubs = [];
|
|
51855
|
-
dfsSubgraphs(g, dfsSubs);
|
|
51856
|
-
const subgGid = /* @__PURE__ */ new Map();
|
|
51857
|
-
dfsSubs.forEach((sg, i) => subgGid.set(sg, i));
|
|
51858
|
-
const sgcnt = dfsSubs.length;
|
|
51859
|
-
const nodeGid = /* @__PURE__ */ new Map();
|
|
51860
|
-
let ncnt = 0;
|
|
51861
|
-
for (const [, n] of g.nodes) nodeGid.set(n, sgcnt + ncnt++);
|
|
51862
|
-
const edgeGid = /* @__PURE__ */ new Map();
|
|
51863
|
-
let ecnt = 0;
|
|
51864
|
-
for (const { e } of canonicalRootEdges(g)) edgeGid.set(e, ecnt++);
|
|
51865
|
-
return {
|
|
51866
|
-
doXDot,
|
|
51867
|
-
draws,
|
|
51868
|
-
sgcnt,
|
|
51869
|
-
subgGid,
|
|
51870
|
-
nodeGid,
|
|
51871
|
-
edgeGid,
|
|
51872
|
-
dfsSubs,
|
|
51873
|
-
edgeLabelDeclared: isEdgeLabelDeclared(g),
|
|
51874
|
-
graphLabelDeclared: isGraphLabelDeclared(g),
|
|
51875
|
-
isLatin: g.info.charset === CHAR_LATIN1
|
|
51876
|
-
};
|
|
51877
|
-
}
|
|
51878
|
-
function emitAttrs(o, base, overlay, ctx) {
|
|
51879
|
-
const merged = new Map(base);
|
|
51880
|
-
if (overlay !== void 0) for (const [k, v] of overlay) merged.set(k, v);
|
|
51881
|
-
for (const [k, rawV] of merged) {
|
|
51882
|
-
const v = isHtmlValue(rawV) ? htmlValueContent(rawV) : rawV;
|
|
51883
|
-
if (v === "" && k !== "label") continue;
|
|
51884
|
-
if (ctx.doXDot && isXDot(k)) {
|
|
51885
|
-
o[k] = drawStringToOps(v, ctx.isLatin);
|
|
51886
|
-
} else if (!isXDot(k)) {
|
|
51887
|
-
o[k] = v;
|
|
51888
|
-
}
|
|
51889
|
-
}
|
|
51890
|
-
}
|
|
51891
|
-
function graphLabel(info) {
|
|
51892
|
-
const l = info.label;
|
|
51893
|
-
if (l !== void 0 && l !== null && typeof l === "object" && "pos" in l) {
|
|
51894
|
-
return l;
|
|
51895
|
-
}
|
|
51896
|
-
return void 0;
|
|
51897
|
-
}
|
|
51898
|
-
function labelPos(l) {
|
|
51899
|
-
return gfmt5(l.pos.x) + "," + gfmt5(l.pos.y);
|
|
51900
|
-
}
|
|
51901
|
-
function attachEdgeLabelPositions(o, e) {
|
|
51902
|
-
if (!e.info.spl) return;
|
|
51903
|
-
if (e.info.label !== void 0) o.lp = labelPos(e.info.label);
|
|
51904
|
-
if (e.info.head_label !== void 0) o.head_lp = labelPos(e.info.head_label);
|
|
51905
|
-
if (e.info.tail_label !== void 0) o.tail_lp = labelPos(e.info.tail_label);
|
|
51906
|
-
const xl = e.info.xlabel;
|
|
51907
|
-
if (xl !== void 0 && xl.set) o.xlp = labelPos(xl);
|
|
51908
|
-
}
|
|
51909
|
-
function attachNodeXlp(o, n) {
|
|
51910
|
-
const xl = n.info.xlabel;
|
|
51911
|
-
if (xl !== void 0 && xl.set) o.xlp = labelPos(xl);
|
|
51912
|
-
}
|
|
51913
|
-
function attachGraphLabelGeom(o, g) {
|
|
51914
|
-
const lbl = graphLabel(g.info);
|
|
51915
|
-
if (lbl !== void 0 && lbl.text !== "") {
|
|
51916
|
-
o.lp = gfmt5(lbl.pos.x) + "," + gfmt5(lbl.pos.y);
|
|
51917
|
-
o.lwidth = (lbl.dimen.x / 72).toFixed(2);
|
|
51918
|
-
o.lheight = (lbl.dimen.y / 72).toFixed(2);
|
|
51919
|
-
}
|
|
51920
|
-
}
|
|
51921
|
-
function setRecordRects(f, coord, out) {
|
|
51922
|
-
if (f.n_flds === 0) {
|
|
51923
|
-
out.push(
|
|
51924
|
-
gfmt5(f.b.ll.x + coord.x) + "," + gfmt5(f.b.ll.y + coord.y) + "," + gfmt5(f.b.ur.x + coord.x) + "," + gfmt5(f.b.ur.y + coord.y)
|
|
51925
|
-
);
|
|
51926
|
-
} else if (f.fld !== null) {
|
|
51927
|
-
for (const sub of f.fld) setRecordRects(sub, coord, out);
|
|
51928
|
-
}
|
|
51929
|
-
}
|
|
51930
|
-
function attachNodeRects(o, n) {
|
|
51931
|
-
const shape = n.info.shape;
|
|
51932
|
-
if (shape?.name !== "record") return;
|
|
51933
|
-
const f = n.info.shape_info;
|
|
51934
|
-
if (f === void 0) return;
|
|
51935
|
-
const parts = [];
|
|
51936
|
-
setRecordRects(f, n.info.coord, parts);
|
|
51937
|
-
if (parts.length > 0) o.rects = parts.join(" ");
|
|
51938
|
-
}
|
|
51939
|
-
function nodeEffectiveAttrs(n) {
|
|
51940
|
-
const m = new Map(n.nodeDefaultsSnapshot ?? []);
|
|
51941
|
-
for (const [k, v] of n.attrs) m.set(k, v);
|
|
51942
|
-
if (!m.has("label")) m.set("label", DEFAULT_NODE_LABEL);
|
|
51943
|
-
return m;
|
|
51944
|
-
}
|
|
51945
|
-
function buildNodeObject(n, ctx) {
|
|
51946
|
-
const o = { _gvid: ctx.nodeGid.get(n), name: n.name };
|
|
51947
|
-
const eff = nodeEffectiveAttrs(n);
|
|
51948
|
-
const xd = ctx.draws?.node.get(n.name);
|
|
51949
|
-
if (xd === void 0) {
|
|
51950
|
-
eff.set("pos", printNum(n.info.coord.x) + "," + printNum(n.info.coord.y));
|
|
51951
|
-
eff.set("width", printNum(n.info.width));
|
|
51952
|
-
eff.set("height", printNum(n.info.height));
|
|
51953
|
-
}
|
|
51954
|
-
emitAttrs(o, eff, xd, ctx);
|
|
51955
|
-
attachNodeRects(o, n);
|
|
51956
|
-
attachNodeXlp(o, n);
|
|
51957
|
-
if (ctx.doXDot && ctx.draws === void 0 && !("_draw_" in o)) o._draw_ = [];
|
|
51958
|
-
return o;
|
|
51959
|
-
}
|
|
51960
|
-
function buildEdgeObject(e, ctx) {
|
|
51961
|
-
const o = {
|
|
51962
|
-
_gvid: ctx.edgeGid.get(e),
|
|
51963
|
-
tail: ctx.nodeGid.get(e.tail),
|
|
51964
|
-
head: ctx.nodeGid.get(e.head)
|
|
51965
|
-
};
|
|
51966
|
-
emitAttrs(o, e.attrs, ctx.draws?.edge.get(e), ctx);
|
|
51967
|
-
if (ctx.edgeLabelDeclared && !("label" in o)) o.label = "";
|
|
51968
|
-
attachEdgeLabelPositions(o, e);
|
|
51969
|
-
if (ctx.doXDot && ctx.draws === void 0 && !("_draw_" in o)) o._draw_ = [];
|
|
51970
|
-
return o;
|
|
51971
|
-
}
|
|
51972
|
-
function memberEdgeGids(g, ctx) {
|
|
51973
|
-
const edges = [];
|
|
51974
|
-
for (const [, n] of g.nodes) {
|
|
51975
|
-
for (const e of n.outEdges(g)) {
|
|
51976
|
-
if (ctx.edgeGid.has(e)) edges.push(e);
|
|
51977
|
-
}
|
|
51978
|
-
}
|
|
51979
|
-
edges.sort((a, b) => a.seq - b.seq);
|
|
51980
|
-
return edges.map((e) => ctx.edgeGid.get(e));
|
|
51981
|
-
}
|
|
51982
|
-
function buildEdgesArray(g, ctx) {
|
|
51983
|
-
const edges = canonicalRootEdges(g).map(({ e }) => e);
|
|
51984
|
-
edges.sort((a, b) => a.seq - b.seq);
|
|
51985
|
-
return edges.map((e) => buildEdgeObject(e, ctx));
|
|
51986
|
-
}
|
|
51987
|
-
function buildGraphObject(g, top, ctx) {
|
|
51988
|
-
const o = {};
|
|
51989
|
-
o.name = g.name;
|
|
51990
|
-
if (top) {
|
|
51991
|
-
o.directed = g.kind === "directed" || g.kind === "strict-directed";
|
|
51992
|
-
o.strict = g.kind === "strict-directed" || g.kind === "strict-undirected";
|
|
51993
|
-
}
|
|
51994
|
-
const overlay = top ? ctx.draws?.graph : ctx.draws?.cluster.get(g);
|
|
51995
|
-
emitAttrs(o, graphEffectiveAttrs(g, top), overlay, ctx);
|
|
51996
|
-
attachGraphLabelGeom(o, g);
|
|
51997
|
-
if (ctx.graphLabelDeclared && !("label" in o)) o.label = "";
|
|
51998
|
-
if (top) {
|
|
51999
|
-
o._subgraph_cnt = ctx.sgcnt;
|
|
52000
|
-
} else {
|
|
52001
|
-
o._gvid = ctx.subgGid.get(g);
|
|
52002
|
-
}
|
|
52003
|
-
if (top) {
|
|
52004
|
-
if (ctx.dfsSubs.length > 0 || g.nodes.size > 0) {
|
|
52005
|
-
const objects = [];
|
|
52006
|
-
for (const sg of ctx.dfsSubs) objects.push(buildGraphObject(sg, false, ctx));
|
|
52007
|
-
for (const [, n] of g.nodes) objects.push(buildNodeObject(n, ctx));
|
|
52008
|
-
o.objects = objects;
|
|
52009
|
-
}
|
|
52010
|
-
} else {
|
|
52011
|
-
const childGids = [];
|
|
52012
|
-
for (const [, sg] of g.subgraphs) childGids.push(ctx.subgGid.get(sg));
|
|
52013
|
-
if (childGids.length > 0) o.subgraphs = childGids;
|
|
52014
|
-
const nodeGids = [];
|
|
52015
|
-
for (const [, n] of g.nodes) {
|
|
52016
|
-
const gid = ctx.nodeGid.get(n);
|
|
52017
|
-
if (gid !== void 0) nodeGids.push(gid);
|
|
52018
|
-
}
|
|
52019
|
-
if (nodeGids.length > 0) o.nodes = nodeGids;
|
|
52020
|
-
const eGids = memberEdgeGids(g, ctx);
|
|
52021
|
-
if (eGids.length > 0) o.edges = eGids;
|
|
52022
|
-
}
|
|
52023
|
-
if (top) {
|
|
52024
|
-
const edges = buildEdgesArray(g, ctx);
|
|
52025
|
-
if (edges.length > 0) o.edges = edges;
|
|
52026
|
-
}
|
|
52027
|
-
return o;
|
|
52028
|
-
}
|
|
52029
|
-
function latin1Reencode(s) {
|
|
52030
|
-
const bytes = new TextEncoder().encode(s);
|
|
52031
|
-
let out = "";
|
|
52032
|
-
for (const b of bytes) out += String.fromCharCode(b);
|
|
52033
|
-
return out;
|
|
52034
|
-
}
|
|
52035
|
-
function buildJson(g, doXDot) {
|
|
52036
|
-
const draws = doXDot ? collectDrawLookup(g) : void 0;
|
|
52037
|
-
const ctx = assignIds(g, doXDot, draws);
|
|
52038
|
-
const root = buildGraphObject(g, true, ctx);
|
|
52039
|
-
return JSON.stringify(root, null, 2) + "\n";
|
|
52040
|
-
}
|
|
52041
|
-
var Json0Renderer = class {
|
|
52042
|
-
type = "json0";
|
|
52043
|
-
/** @see plugin/core/gvplugin_core.c registration table */
|
|
52044
|
-
quality = 0;
|
|
52045
|
-
beginGraph(_g, _job) {
|
|
52046
|
-
}
|
|
52047
|
-
endGraph(g, job) {
|
|
52048
|
-
job.write(buildJson(g, false));
|
|
52049
|
-
}
|
|
52050
|
-
beginNode(_n, _job) {
|
|
52051
|
-
}
|
|
52052
|
-
endNode(_n, _job) {
|
|
52053
|
-
}
|
|
52054
|
-
beginEdge(_e, _job) {
|
|
52055
|
-
}
|
|
52056
|
-
endEdge(_e, _job) {
|
|
52057
|
-
}
|
|
52058
|
-
textspan(_pos, _span, _job) {
|
|
52059
|
-
}
|
|
52060
|
-
ellipse(_c, _rx, _ry, _f, _j) {
|
|
52061
|
-
}
|
|
52062
|
-
polygon(_pts, _filled, _job) {
|
|
52063
|
-
}
|
|
52064
|
-
bezier(_pts, _filled, _job) {
|
|
52065
|
-
}
|
|
52066
|
-
polyline(_pts, _job) {
|
|
52067
|
-
}
|
|
52068
|
-
};
|
|
52069
|
-
var JsonRenderer = class {
|
|
52070
|
-
type = "json";
|
|
52071
|
-
/** @see plugin/core/gvplugin_core.c registration table */
|
|
52072
|
-
quality = 0;
|
|
52073
|
-
beginGraph(_g, _job) {
|
|
52074
|
-
}
|
|
52075
|
-
endGraph(g, job) {
|
|
52076
|
-
job.write(buildJson(g, true));
|
|
52077
|
-
}
|
|
52078
|
-
beginNode(_n, _job) {
|
|
52079
|
-
}
|
|
52080
|
-
endNode(_n, _job) {
|
|
52081
|
-
}
|
|
52082
|
-
beginEdge(_e, _job) {
|
|
52083
|
-
}
|
|
52084
|
-
endEdge(_e, _job) {
|
|
52085
|
-
}
|
|
52086
|
-
textspan(_pos, _span, _job) {
|
|
52087
|
-
}
|
|
52088
|
-
ellipse(_c, _rx, _ry, _f, _j) {
|
|
52089
|
-
}
|
|
52090
|
-
polygon(_pts, _filled, _job) {
|
|
52091
|
-
}
|
|
52092
|
-
bezier(_pts, _filled, _job) {
|
|
52093
|
-
}
|
|
52094
|
-
polyline(_pts, _job) {
|
|
52095
|
-
}
|
|
52096
|
-
};
|
|
52097
|
-
function createJson0Renderer() {
|
|
52098
|
-
return new Json0Renderer();
|
|
52099
|
-
}
|
|
52100
|
-
function createJsonRenderer() {
|
|
52101
|
-
return new JsonRenderer();
|
|
52102
|
-
}
|
|
52103
|
-
|
|
52104
|
-
// src/render/map.ts
|
|
52105
|
-
var DQ = '"';
|
|
52106
|
-
function exactDecimalDigits2(v) {
|
|
52107
|
-
const dv = new DataView(new ArrayBuffer(8));
|
|
52108
|
-
dv.setFloat64(0, Math.abs(v));
|
|
52109
|
-
const bits = dv.getBigUint64(0);
|
|
52110
|
-
const expBits = Number(bits >> 52n & 0x7ffn);
|
|
52111
|
-
const mant = bits & 0xfffffffffffffn;
|
|
52112
|
-
const m = expBits === 0 ? mant : mant | 1n << 52n;
|
|
52113
|
-
const e = (expBits === 0 ? 1 : expBits) - 1075;
|
|
52114
|
-
let intDigits;
|
|
52115
|
-
let fracLen = 0;
|
|
52116
|
-
if (e >= 0) {
|
|
52117
|
-
intDigits = (m << BigInt(e)).toString();
|
|
52118
|
-
} else {
|
|
52119
|
-
intDigits = (m * 5n ** BigInt(-e)).toString();
|
|
52120
|
-
fracLen = -e;
|
|
52121
|
-
}
|
|
52122
|
-
if (intDigits.length <= fracLen) {
|
|
52123
|
-
intDigits = "0".repeat(fracLen - intDigits.length + 1) + intDigits;
|
|
52124
|
-
}
|
|
52125
|
-
const pointPos = intDigits.length - fracLen;
|
|
52126
|
-
const firstSig = intDigits.search(/[1-9]/);
|
|
52127
|
-
return { digits: intDigits.slice(firstSig), exp10: pointPos - firstSig - 1 };
|
|
52128
|
-
}
|
|
52129
|
-
function printG5(v) {
|
|
52130
|
-
if (v === 0 || !Number.isFinite(v)) return String(v);
|
|
52131
|
-
const P = 5;
|
|
52132
|
-
const { digits, exp10 } = exactDecimalDigits2(v);
|
|
52133
|
-
let sig = digits.slice(0, P);
|
|
52134
|
-
if (sig.length < P) sig += "0".repeat(P - sig.length);
|
|
52135
|
-
const rest = digits.slice(P);
|
|
52136
|
-
const restNonzeroAfterFirst = /[1-9]/.test(rest.slice(1));
|
|
52137
|
-
const roundUp = rest.length > 0 && (rest[0] > "5" || rest[0] === "5" && restNonzeroAfterFirst || rest[0] === "5" && !restNonzeroAfterFirst && (sig.charCodeAt(P - 1) - 48) % 2 === 1);
|
|
52138
|
-
let exp = exp10;
|
|
52139
|
-
if (roundUp) {
|
|
52140
|
-
const n = String(BigInt(sig) + 1n);
|
|
52141
|
-
sig = n.length > P ? (exp++, n.slice(0, P)) : n;
|
|
52142
|
-
}
|
|
52143
|
-
const sign2 = v < 0 ? "-" : "";
|
|
52144
|
-
if (exp < -4 || exp >= P) {
|
|
52145
|
-
const mant = (sig[0] + "." + sig.slice(1)).replace(/\.?0+$/, "");
|
|
52146
|
-
const as = Math.abs(exp);
|
|
52147
|
-
return sign2 + mant + "e" + (exp < 0 ? "-" : "+") + (as < 10 ? "0" : "") + String(as);
|
|
52148
|
-
}
|
|
52149
|
-
let s;
|
|
52150
|
-
if (exp >= sig.length - 1) {
|
|
52151
|
-
s = sig + "0".repeat(exp - sig.length + 1);
|
|
52152
|
-
} else if (exp >= 0) {
|
|
52153
|
-
s = sig.slice(0, exp + 1) + "." + sig.slice(exp + 1);
|
|
52154
|
-
} else {
|
|
52155
|
-
s = "0." + "0".repeat(-exp - 1) + sig;
|
|
52156
|
-
}
|
|
52157
|
-
if (s.includes(".")) s = s.replace(/\.?0+$/, "");
|
|
52158
|
-
return sign2 + s;
|
|
52159
|
-
}
|
|
52160
|
-
function plainCoord(v) {
|
|
52161
|
-
return printG5(v / 72);
|
|
52162
|
-
}
|
|
52163
|
-
var CANON_KEYWORDS = ["node", "edge", "strict", "graph", "digraph", "subgraph"];
|
|
52164
|
-
var MAX_OUTPUTLINE = 128;
|
|
52165
|
-
function isDigitByte(b) {
|
|
52166
|
-
return b >= 48 && b <= 57;
|
|
52167
|
-
}
|
|
52168
|
-
function isAlnumByte(b) {
|
|
52169
|
-
return isDigitByte(b) || b >= 65 && b <= 90 || b >= 97 && b <= 122;
|
|
52170
|
-
}
|
|
52171
|
-
function isIdCharByte(b) {
|
|
52172
|
-
return isAlnumByte(b) || b === 46 || b === 45 || b >= 128;
|
|
52173
|
-
}
|
|
52174
|
-
function isEscapeAt(bytes, i) {
|
|
52175
|
-
if (bytes[i] !== 92) return false;
|
|
52176
|
-
const c = bytes[i + 1];
|
|
52177
|
-
return c !== void 0 && 'EGHLNTlnr\\"'.includes(String.fromCharCode(c));
|
|
52178
|
-
}
|
|
52179
|
-
function agstrcanonText(arg) {
|
|
52180
|
-
if (arg.length === 0) return '""';
|
|
52181
|
-
const bytes = new TextEncoder().encode(arg);
|
|
52182
|
-
const out = [34];
|
|
52183
|
-
let needsQuotes = false;
|
|
52184
|
-
let partOfEscape = false;
|
|
52185
|
-
let backslashPending = false;
|
|
52186
|
-
let cnt = 0;
|
|
52187
|
-
let dotcnt = 0;
|
|
52188
|
-
let maybeNum = isDigitByte(bytes[0]) || bytes[0] === 46 || bytes[0] === 45;
|
|
52189
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
52190
|
-
const uc = bytes[i];
|
|
52191
|
-
if (uc === 34 && !partOfEscape) {
|
|
52192
|
-
out.push(92);
|
|
52193
|
-
needsQuotes = true;
|
|
52194
|
-
} else if (!partOfEscape && isEscapeAt(bytes, i)) {
|
|
52195
|
-
needsQuotes = true;
|
|
52196
|
-
partOfEscape = true;
|
|
52197
|
-
} else if (maybeNum) {
|
|
52198
|
-
if (uc === 45) {
|
|
52199
|
-
if (cnt) {
|
|
52200
|
-
maybeNum = false;
|
|
52201
|
-
needsQuotes = true;
|
|
52202
|
-
}
|
|
52203
|
-
} else if (uc === 46) {
|
|
52204
|
-
if (dotcnt++) {
|
|
52205
|
-
maybeNum = false;
|
|
52206
|
-
needsQuotes = true;
|
|
52207
|
-
}
|
|
52208
|
-
} else if (!isDigitByte(uc)) {
|
|
52209
|
-
maybeNum = false;
|
|
52210
|
-
needsQuotes = true;
|
|
52211
|
-
}
|
|
52212
|
-
partOfEscape = false;
|
|
52213
|
-
} else if (!(isAlnumByte(uc) || uc === 95 || uc >= 128)) {
|
|
52214
|
-
needsQuotes = true;
|
|
52215
|
-
partOfEscape = false;
|
|
52216
|
-
} else {
|
|
52217
|
-
partOfEscape = false;
|
|
52218
|
-
}
|
|
52219
|
-
out.push(uc);
|
|
52220
|
-
cnt++;
|
|
52221
|
-
const next = bytes[i + 1];
|
|
52222
|
-
if (next !== void 0) {
|
|
52223
|
-
const last = out[out.length - 1];
|
|
52224
|
-
const canBreak = !(isIdCharByte(last) || last === 92) && isIdCharByte(next);
|
|
52225
|
-
if (backslashPending && canBreak) {
|
|
52226
|
-
out.push(92, 10);
|
|
52227
|
-
needsQuotes = true;
|
|
52228
|
-
backslashPending = false;
|
|
52229
|
-
cnt = 0;
|
|
52230
|
-
} else if (cnt >= MAX_OUTPUTLINE) {
|
|
52231
|
-
if (canBreak) {
|
|
52232
|
-
out.push(92, 10);
|
|
52233
|
-
needsQuotes = true;
|
|
52234
|
-
cnt = 0;
|
|
52235
|
-
} else {
|
|
52236
|
-
backslashPending = true;
|
|
52237
|
-
}
|
|
52238
|
-
}
|
|
52239
|
-
}
|
|
52240
|
-
}
|
|
52241
|
-
out.push(34);
|
|
52242
|
-
const first = bytes[0];
|
|
52243
|
-
if (needsQuotes || cnt === 1 && (first === 46 || first === 45)) {
|
|
52244
|
-
return new TextDecoder().decode(new Uint8Array(out));
|
|
52245
|
-
}
|
|
52246
|
-
const lower = arg.toLowerCase();
|
|
52247
|
-
for (const tok of CANON_KEYWORDS) {
|
|
52248
|
-
if (tok === lower) return new TextDecoder().decode(new Uint8Array(out));
|
|
52249
|
-
}
|
|
52250
|
-
return arg;
|
|
52251
|
-
}
|
|
52252
|
-
function lateNN(v, def) {
|
|
52253
|
-
return v !== void 0 && v !== "" ? v : def;
|
|
52254
|
-
}
|
|
52255
|
-
function plainNodeFill(n, g) {
|
|
52256
|
-
const fillcolor = lateNN(nodeAttr(n, g, "fillcolor"), "");
|
|
52257
|
-
if (fillcolor !== "") return fillcolor;
|
|
52258
|
-
return lateNN(nodeAttr(n, g, "color"), "lightgrey");
|
|
52259
|
-
}
|
|
52260
|
-
function plainNodeLabel(n, g) {
|
|
52261
|
-
const lbl = n.info.label;
|
|
52262
|
-
const attr2 = nodeAttr(n, g, "label");
|
|
52263
|
-
if (lbl !== void 0 && lbl.u.kind === "html") {
|
|
52264
|
-
const a = attr2 ?? "";
|
|
52265
|
-
return "<" + (isHtmlValue(a) ? htmlValueContent(a) : a) + ">";
|
|
52266
|
-
}
|
|
52267
|
-
if (attr2 !== void 0 && isHtmlValue(attr2) && lbl !== void 0 && lbl.u.kind === "txt") {
|
|
52268
|
-
return agstrcanonText(htmlValueContent(attr2));
|
|
52269
|
-
}
|
|
52270
|
-
const shape = n.info.shape;
|
|
52271
|
-
if (shape !== void 0 && shape.kind === 2 /* SH_RECORD */) {
|
|
52272
|
-
return agstrcanonText(nodeAttr(n, g, "label") ?? "\\N");
|
|
52836
|
+
function isEdgeLabelDeclared(g) {
|
|
52837
|
+
for (const e of g.edges) if (e.attrs.has("label")) return true;
|
|
52838
|
+
const walk = (sg) => {
|
|
52839
|
+
if (sg.edgeDefaults.has("label")) return true;
|
|
52840
|
+
for (const [, sub] of sg.subgraphs) if (walk(sub)) return true;
|
|
52841
|
+
return false;
|
|
52842
|
+
};
|
|
52843
|
+
return walk(g);
|
|
52844
|
+
}
|
|
52845
|
+
function dfsSubgraphs(g, out) {
|
|
52846
|
+
for (const [, sg] of g.subgraphs) {
|
|
52847
|
+
out.push(sg);
|
|
52848
|
+
dfsSubgraphs(sg, out);
|
|
52273
52849
|
}
|
|
52274
|
-
const text = lbl !== void 0 ? lbl.text : n.attrs.get("label") ?? n.name;
|
|
52275
|
-
return agstrcanonText(text);
|
|
52276
52850
|
}
|
|
52277
|
-
function
|
|
52278
|
-
const
|
|
52279
|
-
|
|
52280
|
-
const
|
|
52851
|
+
function assignIds(g, doXDot, draws) {
|
|
52852
|
+
const dfsSubs = [];
|
|
52853
|
+
dfsSubgraphs(g, dfsSubs);
|
|
52854
|
+
const subgGid = /* @__PURE__ */ new Map();
|
|
52855
|
+
dfsSubs.forEach((sg, i) => subgGid.set(sg, i));
|
|
52856
|
+
const sgcnt = dfsSubs.length;
|
|
52857
|
+
const nodeGid = /* @__PURE__ */ new Map();
|
|
52858
|
+
let ncnt = 0;
|
|
52859
|
+
for (const [, n] of g.nodes) nodeGid.set(n, sgcnt + ncnt++);
|
|
52860
|
+
const edgeGid = /* @__PURE__ */ new Map();
|
|
52861
|
+
let ecnt = 0;
|
|
52862
|
+
for (const { e } of canonicalRootEdges(g)) edgeGid.set(e, ecnt++);
|
|
52281
52863
|
return {
|
|
52282
|
-
|
|
52283
|
-
|
|
52284
|
-
|
|
52285
|
-
|
|
52286
|
-
|
|
52864
|
+
doXDot,
|
|
52865
|
+
draws,
|
|
52866
|
+
sgcnt,
|
|
52867
|
+
subgGid,
|
|
52868
|
+
nodeGid,
|
|
52869
|
+
edgeGid,
|
|
52870
|
+
dfsSubs,
|
|
52871
|
+
edgeLabelDeclared: isEdgeLabelDeclared(g),
|
|
52872
|
+
graphLabelDeclared: isGraphLabelDeclared(g),
|
|
52873
|
+
isLatin: g.info.charset === CHAR_LATIN1
|
|
52287
52874
|
};
|
|
52288
52875
|
}
|
|
52289
|
-
function
|
|
52290
|
-
const
|
|
52291
|
-
const
|
|
52292
|
-
const
|
|
52293
|
-
|
|
52294
|
-
|
|
52295
|
-
|
|
52296
|
-
|
|
52297
|
-
|
|
52298
|
-
|
|
52299
|
-
|
|
52300
|
-
for (const bz of e.info.spl.list) {
|
|
52301
|
-
for (let i = 0; i < bz.size; i++) pts.push(bz.list[i]);
|
|
52302
|
-
}
|
|
52303
|
-
return pts;
|
|
52304
|
-
}
|
|
52305
|
-
function writeNodeAndPort(n, portname, out) {
|
|
52306
|
-
let name = n.name;
|
|
52307
|
-
if (n.info.clustnode) {
|
|
52308
|
-
const i = name.indexOf(":");
|
|
52309
|
-
if (i >= 0) name = name.slice(i + 1);
|
|
52876
|
+
function emitAttrs(o, base, overlay, ctx) {
|
|
52877
|
+
const merged = new Map(base);
|
|
52878
|
+
if (overlay !== void 0) for (const [k, v] of overlay) merged.set(k, v);
|
|
52879
|
+
for (const [k, rawV] of merged) {
|
|
52880
|
+
const v = isHtmlValue(rawV) ? htmlValueContent(rawV) : rawV;
|
|
52881
|
+
if (v === "" && k !== "label") continue;
|
|
52882
|
+
if (ctx.doXDot && isXDot(k)) {
|
|
52883
|
+
o[k] = drawStringToOps(v, ctx.isLatin);
|
|
52884
|
+
} else if (!isXDot(k)) {
|
|
52885
|
+
o[k] = v;
|
|
52886
|
+
}
|
|
52310
52887
|
}
|
|
52311
|
-
out.push(" " + agstrcanonText(name));
|
|
52312
|
-
if (portname !== "") out.push(":" + agstrcanonText(portname));
|
|
52313
52888
|
}
|
|
52314
|
-
function
|
|
52315
|
-
|
|
52316
|
-
|
|
52317
|
-
|
|
52318
|
-
out.push(" " + String(pts.length));
|
|
52319
|
-
for (const pt of pts) {
|
|
52320
|
-
out.push(" " + plainCoord(pt.x) + " " + plainCoord(pt.y));
|
|
52889
|
+
function graphLabel(info) {
|
|
52890
|
+
const l = info.label;
|
|
52891
|
+
if (l !== void 0 && l !== null && typeof l === "object" && "pos" in l) {
|
|
52892
|
+
return l;
|
|
52321
52893
|
}
|
|
52894
|
+
return void 0;
|
|
52322
52895
|
}
|
|
52323
|
-
function
|
|
52324
|
-
|
|
52325
|
-
const hport = extend ? e.attrs.get("headport") ?? "" : "";
|
|
52326
|
-
const pts = collectSplinePts(e);
|
|
52327
|
-
if (pts.length > 0) writePlainEdgeHead(e, tport, hport, pts, out);
|
|
52328
|
-
const lbl = e.info.label;
|
|
52329
|
-
if (lbl !== void 0) {
|
|
52330
|
-
out.push(" " + agstrcanonText(lbl.text) + " " + plainCoord(lbl.pos.x) + " " + plainCoord(lbl.pos.y));
|
|
52331
|
-
}
|
|
52332
|
-
const style = lateNN(e.attrs.get("style"), "solid");
|
|
52333
|
-
const color = lateNN(e.attrs.get("color"), "black");
|
|
52334
|
-
out.push(" " + style + " " + color + "\n");
|
|
52896
|
+
function labelPos(l) {
|
|
52897
|
+
return gfmt5(l.pos.x) + "," + gfmt5(l.pos.y);
|
|
52335
52898
|
}
|
|
52336
|
-
function
|
|
52337
|
-
|
|
52338
|
-
|
|
52339
|
-
|
|
52340
|
-
|
|
52341
|
-
|
|
52342
|
-
|
|
52343
|
-
);
|
|
52344
|
-
job.write("graph " + printG5(zoom) + " " + w + " " + h + "\n");
|
|
52345
|
-
const nodes = nodesInSeq(g);
|
|
52346
|
-
for (const n of nodes) {
|
|
52347
|
-
if (n.info.clustnode) continue;
|
|
52348
|
-
const buf = [];
|
|
52349
|
-
writePlainNode(n, g, buf);
|
|
52350
|
-
job.write(buf.join(""));
|
|
52351
|
-
}
|
|
52352
|
-
for (const n of nodes) {
|
|
52353
|
-
for (const e of n.outEdges(g)) {
|
|
52354
|
-
const buf = [];
|
|
52355
|
-
writePlainEdge(e, extend, buf);
|
|
52356
|
-
job.write(buf.join(""));
|
|
52357
|
-
}
|
|
52358
|
-
}
|
|
52359
|
-
job.write("stop\n");
|
|
52899
|
+
function attachEdgeLabelPositions(o, e) {
|
|
52900
|
+
if (!e.info.spl) return;
|
|
52901
|
+
if (e.info.label !== void 0) o.lp = labelPos(e.info.label);
|
|
52902
|
+
if (e.info.head_label !== void 0) o.head_lp = labelPos(e.info.head_label);
|
|
52903
|
+
if (e.info.tail_label !== void 0) o.tail_lp = labelPos(e.info.tail_label);
|
|
52904
|
+
const xl = e.info.xlabel;
|
|
52905
|
+
if (xl !== void 0 && xl.set) o.xlp = labelPos(xl);
|
|
52360
52906
|
}
|
|
52361
|
-
function
|
|
52362
|
-
|
|
52363
|
-
|
|
52364
|
-
tooltip: obj.tooltip ?? "",
|
|
52365
|
-
target: obj.target ?? "",
|
|
52366
|
-
id: obj.id ?? ""
|
|
52367
|
-
};
|
|
52907
|
+
function attachNodeXlp(o, n) {
|
|
52908
|
+
const xl = n.info.xlabel;
|
|
52909
|
+
if (xl !== void 0 && xl.set) o.xlp = labelPos(xl);
|
|
52368
52910
|
}
|
|
52369
|
-
function
|
|
52370
|
-
const
|
|
52371
|
-
if (
|
|
52372
|
-
|
|
52911
|
+
function attachGraphLabelGeom(o, g) {
|
|
52912
|
+
const lbl = graphLabel(g.info);
|
|
52913
|
+
if (lbl !== void 0 && lbl.text !== "") {
|
|
52914
|
+
o.lp = gfmt5(lbl.pos.x) + "," + gfmt5(lbl.pos.y);
|
|
52915
|
+
o.lwidth = (lbl.dimen.x / 72).toFixed(2);
|
|
52916
|
+
o.lheight = (lbl.dimen.y / 72).toFixed(2);
|
|
52917
|
+
}
|
|
52373
52918
|
}
|
|
52374
|
-
function
|
|
52375
|
-
if (
|
|
52376
|
-
|
|
52377
|
-
|
|
52919
|
+
function setRecordRects(f, coord, out) {
|
|
52920
|
+
if (f.n_flds === 0) {
|
|
52921
|
+
out.push(
|
|
52922
|
+
gfmt5(f.b.ll.x + coord.x) + "," + gfmt5(f.b.ll.y + coord.y) + "," + gfmt5(f.b.ur.x + coord.x) + "," + gfmt5(f.b.ur.y + coord.y)
|
|
52923
|
+
);
|
|
52924
|
+
} else if (f.fld !== null) {
|
|
52925
|
+
for (const sub of f.fld) setRecordRects(sub, coord, out);
|
|
52926
|
+
}
|
|
52378
52927
|
}
|
|
52379
|
-
function
|
|
52380
|
-
|
|
52928
|
+
function attachNodeRects(o, n) {
|
|
52929
|
+
const shape = n.info.shape;
|
|
52930
|
+
if (shape?.name !== "record") return;
|
|
52931
|
+
const f = n.info.shape_info;
|
|
52932
|
+
if (f === void 0) return;
|
|
52933
|
+
const parts = [];
|
|
52934
|
+
setRecordRects(f, n.info.coord, parts);
|
|
52935
|
+
if (parts.length > 0) o.rects = parts.join(" ");
|
|
52381
52936
|
}
|
|
52382
|
-
function
|
|
52383
|
-
const
|
|
52384
|
-
const
|
|
52385
|
-
|
|
52386
|
-
return
|
|
52937
|
+
function nodeEffectiveAttrs(n) {
|
|
52938
|
+
const m = new Map(n.nodeDefaultsSnapshot ?? []);
|
|
52939
|
+
for (const [k, v] of n.attrs) m.set(k, v);
|
|
52940
|
+
if (!m.has("label")) m.set("label", DEFAULT_NODE_LABEL);
|
|
52941
|
+
return m;
|
|
52387
52942
|
}
|
|
52388
|
-
function
|
|
52389
|
-
|
|
52943
|
+
function buildNodeObject(n, ctx) {
|
|
52944
|
+
const o = { _gvid: ctx.nodeGid.get(n), name: n.name };
|
|
52945
|
+
const eff = nodeEffectiveAttrs(n);
|
|
52946
|
+
const xd = ctx.draws?.node.get(n.name);
|
|
52947
|
+
if (xd === void 0) {
|
|
52948
|
+
eff.set("pos", printNum(n.info.coord.x) + "," + printNum(n.info.coord.y));
|
|
52949
|
+
eff.set("width", printNum(n.info.width));
|
|
52950
|
+
eff.set("height", printNum(n.info.height));
|
|
52951
|
+
}
|
|
52952
|
+
emitAttrs(o, eff, xd, ctx);
|
|
52953
|
+
attachNodeRects(o, n);
|
|
52954
|
+
attachNodeXlp(o, n);
|
|
52955
|
+
if (ctx.doXDot && ctx.draws === void 0 && !("_draw_" in o)) o._draw_ = [];
|
|
52956
|
+
return o;
|
|
52390
52957
|
}
|
|
52391
|
-
function
|
|
52392
|
-
|
|
52393
|
-
|
|
52394
|
-
|
|
52958
|
+
function buildEdgeObject(e, ctx) {
|
|
52959
|
+
const o = {
|
|
52960
|
+
_gvid: ctx.edgeGid.get(e),
|
|
52961
|
+
tail: ctx.nodeGid.get(e.tail),
|
|
52962
|
+
head: ctx.nodeGid.get(e.head)
|
|
52963
|
+
};
|
|
52964
|
+
emitAttrs(o, e.attrs, ctx.draws?.edge.get(e), ctx);
|
|
52965
|
+
if (ctx.edgeLabelDeclared && !("label" in o)) o.label = "";
|
|
52966
|
+
attachEdgeLabelPositions(o, e);
|
|
52967
|
+
if (ctx.doXDot && ctx.draws === void 0 && !("_draw_" in o)) o._draw_ = [];
|
|
52968
|
+
return o;
|
|
52395
52969
|
}
|
|
52396
|
-
function
|
|
52397
|
-
|
|
52398
|
-
|
|
52399
|
-
|
|
52400
|
-
|
|
52970
|
+
function memberEdgeGids(g, ctx) {
|
|
52971
|
+
const edges = [];
|
|
52972
|
+
for (const [, n] of g.nodes) {
|
|
52973
|
+
for (const e of n.outEdges(g)) {
|
|
52974
|
+
if (ctx.edgeGid.has(e)) edges.push(e);
|
|
52975
|
+
}
|
|
52976
|
+
}
|
|
52977
|
+
edges.sort((a, b) => a.seq - b.seq);
|
|
52978
|
+
return edges.map((e) => ctx.edgeGid.get(e));
|
|
52401
52979
|
}
|
|
52402
|
-
function
|
|
52403
|
-
|
|
52404
|
-
|
|
52405
|
-
|
|
52406
|
-
out.push(" coords=" + DQ + cmapxCoords(shape, pts) + DQ);
|
|
52407
|
-
out.push(isXml ? "/>\n" : ">\n");
|
|
52980
|
+
function buildEdgesArray(g, ctx) {
|
|
52981
|
+
const edges = canonicalRootEdges(g).map(({ e }) => e);
|
|
52982
|
+
edges.sort((a, b) => a.seq - b.seq);
|
|
52983
|
+
return edges.map((e) => buildEdgeObject(e, ctx));
|
|
52408
52984
|
}
|
|
52409
|
-
function
|
|
52410
|
-
|
|
52411
|
-
|
|
52412
|
-
|
|
52413
|
-
|
|
52414
|
-
|
|
52415
|
-
|
|
52985
|
+
function buildGraphObject(g, top, ctx) {
|
|
52986
|
+
const o = {};
|
|
52987
|
+
o.name = g.name;
|
|
52988
|
+
if (top) {
|
|
52989
|
+
o.directed = g.kind === "directed" || g.kind === "strict-directed";
|
|
52990
|
+
o.strict = g.kind === "strict-directed" || g.kind === "strict-undirected";
|
|
52991
|
+
}
|
|
52992
|
+
const overlay = top ? ctx.draws?.graph : ctx.draws?.cluster.get(g);
|
|
52993
|
+
emitAttrs(o, graphEffectiveAttrs(g, top), overlay, ctx);
|
|
52994
|
+
attachGraphLabelGeom(o, g);
|
|
52995
|
+
if (ctx.graphLabelDeclared && !("label" in o)) o.label = "";
|
|
52996
|
+
if (top) {
|
|
52997
|
+
o._subgraph_cnt = ctx.sgcnt;
|
|
52416
52998
|
} else {
|
|
52417
|
-
|
|
52418
|
-
|
|
52999
|
+
o._gvid = ctx.subgGid.get(g);
|
|
53000
|
+
}
|
|
53001
|
+
if (top) {
|
|
53002
|
+
if (ctx.dfsSubs.length > 0 || g.nodes.size > 0) {
|
|
53003
|
+
const objects = [];
|
|
53004
|
+
for (const sg of ctx.dfsSubs) objects.push(buildGraphObject(sg, false, ctx));
|
|
53005
|
+
for (const [, n] of g.nodes) objects.push(buildNodeObject(n, ctx));
|
|
53006
|
+
o.objects = objects;
|
|
53007
|
+
}
|
|
53008
|
+
} else {
|
|
53009
|
+
const childGids = [];
|
|
53010
|
+
for (const [, sg] of g.subgraphs) childGids.push(ctx.subgGid.get(sg));
|
|
53011
|
+
if (childGids.length > 0) o.subgraphs = childGids;
|
|
53012
|
+
const nodeGids = [];
|
|
53013
|
+
for (const [, n] of g.nodes) {
|
|
53014
|
+
const gid = ctx.nodeGid.get(n);
|
|
53015
|
+
if (gid !== void 0) nodeGids.push(gid);
|
|
53016
|
+
}
|
|
53017
|
+
if (nodeGids.length > 0) o.nodes = nodeGids;
|
|
53018
|
+
const eGids = memberEdgeGids(g, ctx);
|
|
53019
|
+
if (eGids.length > 0) o.edges = eGids;
|
|
53020
|
+
}
|
|
53021
|
+
if (top) {
|
|
53022
|
+
const edges = buildEdgesArray(g, ctx);
|
|
53023
|
+
if (edges.length > 0) o.edges = edges;
|
|
52419
53024
|
}
|
|
53025
|
+
return o;
|
|
52420
53026
|
}
|
|
52421
|
-
|
|
52422
|
-
|
|
53027
|
+
function latin1Reencode(s) {
|
|
53028
|
+
const bytes = new TextEncoder().encode(s);
|
|
53029
|
+
let out = "";
|
|
53030
|
+
for (const b of bytes) out += String.fromCharCode(b);
|
|
53031
|
+
return out;
|
|
53032
|
+
}
|
|
53033
|
+
function buildJson(g, doXDot) {
|
|
53034
|
+
const draws = doXDot ? collectDrawLookup(g) : void 0;
|
|
53035
|
+
const ctx = assignIds(g, doXDot, draws);
|
|
53036
|
+
const root = buildGraphObject(g, true, ctx);
|
|
53037
|
+
return JSON.stringify(root, null, 2) + "\n";
|
|
53038
|
+
}
|
|
53039
|
+
var Json0Renderer = class {
|
|
53040
|
+
type = "json0";
|
|
53041
|
+
/** @see plugin/core/gvplugin_core.c registration table */
|
|
52423
53042
|
quality = 0;
|
|
52424
53043
|
beginGraph(_g, _job) {
|
|
52425
53044
|
}
|
|
52426
53045
|
endGraph(g, job) {
|
|
52427
|
-
|
|
53046
|
+
job.write(buildJson(g, false));
|
|
52428
53047
|
}
|
|
52429
53048
|
beginNode(_n, _job) {
|
|
52430
53049
|
}
|
|
@@ -52445,13 +53064,14 @@ var PlainRenderer = class {
|
|
|
52445
53064
|
polyline(_pts, _job) {
|
|
52446
53065
|
}
|
|
52447
53066
|
};
|
|
52448
|
-
var
|
|
52449
|
-
type = "
|
|
53067
|
+
var JsonRenderer = class {
|
|
53068
|
+
type = "json";
|
|
53069
|
+
/** @see plugin/core/gvplugin_core.c registration table */
|
|
52450
53070
|
quality = 0;
|
|
52451
53071
|
beginGraph(_g, _job) {
|
|
52452
53072
|
}
|
|
52453
53073
|
endGraph(g, job) {
|
|
52454
|
-
|
|
53074
|
+
job.write(buildJson(g, true));
|
|
52455
53075
|
}
|
|
52456
53076
|
beginNode(_n, _job) {
|
|
52457
53077
|
}
|
|
@@ -52472,183 +53092,11 @@ var PlainExtRenderer = class {
|
|
|
52472
53092
|
polyline(_pts, _job) {
|
|
52473
53093
|
}
|
|
52474
53094
|
};
|
|
52475
|
-
function
|
|
52476
|
-
return
|
|
52477
|
-
}
|
|
52478
|
-
function graphAttr2(g, key) {
|
|
52479
|
-
for (let s = g; s !== null; s = s.parent) {
|
|
52480
|
-
const v = s.attrs.get(key);
|
|
52481
|
-
if (v !== void 0 && v !== "") return v;
|
|
52482
|
-
}
|
|
52483
|
-
return void 0;
|
|
52484
|
-
}
|
|
52485
|
-
function graphMapUrl(g) {
|
|
52486
|
-
const raw = graphAttr2(g, "href") ?? graphAttr2(g, "URL");
|
|
52487
|
-
return raw === void 0 ? null : substObjAnchor(raw, g);
|
|
52488
|
-
}
|
|
52489
|
-
function buildMapCtx(g, job, mapPolygon) {
|
|
52490
|
-
const drawingDpi = lateDouble(g.attrs.get("dpi"), lateDouble(g.attrs.get("resolution"), 0, 0), 0);
|
|
52491
|
-
const mapDpi = drawingDpi > 0 ? drawingDpi : 96;
|
|
52492
|
-
const devscale = mapDpi / POINTS_PER_INCH;
|
|
52493
|
-
const z = initJobViewportZoom(job.bb, parseDrawingSize(g.attrs.get("size")), job.pad);
|
|
52494
|
-
const marginOff = { x: job.margin.x * devscale, y: job.margin.y * devscale };
|
|
52495
|
-
return { bb: job.bb, pad: job.pad, scale: z * devscale, marginOff, mapPolygon };
|
|
52496
|
-
}
|
|
52497
|
-
function anchorOf(url, tooltip, target, id) {
|
|
52498
|
-
return { url: url ?? "", tooltip: tooltip ?? "", target: target ?? "", id: id ?? "" };
|
|
52499
|
-
}
|
|
52500
|
-
var MapRendererBase = class {
|
|
52501
|
-
quality = 0;
|
|
52502
|
-
mapCtx = null;
|
|
52503
|
-
beginGraph(g, job) {
|
|
52504
|
-
job.flags |= EMIT_CLUSTERS_LAST;
|
|
52505
|
-
this.mapCtx = buildMapCtx(g, job, this.mapPolygon);
|
|
52506
|
-
if (this.isCmapx) {
|
|
52507
|
-
const name = escapeXml(mapGraphName(g));
|
|
52508
|
-
job.write("<map id=" + DQ + name + DQ + " name=" + DQ + name + DQ + ">\n");
|
|
52509
|
-
return;
|
|
52510
|
-
}
|
|
52511
|
-
job.write("base referer\n");
|
|
52512
|
-
const url = graphMapUrl(g);
|
|
52513
|
-
if (url) job.write("default " + url + "\n");
|
|
52514
|
-
}
|
|
52515
|
-
endGraph(_g, job) {
|
|
52516
|
-
if (!this.isCmapx) return;
|
|
52517
|
-
const obj = job.obj;
|
|
52518
|
-
if (obj !== null && this.mapCtx !== null && (obj.url !== null || obj.explicitTooltip)) {
|
|
52519
|
-
computeGraphUrlMap(obj, this.mapCtx);
|
|
52520
|
-
}
|
|
52521
|
-
const buf = [];
|
|
52522
|
-
writeCmapxGraphShape(job, buf);
|
|
52523
|
-
job.write(buf.join(""));
|
|
52524
|
-
job.write("</map>\n");
|
|
52525
|
-
}
|
|
52526
|
-
beginNode(n, job) {
|
|
52527
|
-
if (this.mapCtx !== null && job.obj !== null) computeNodeUrlMap(n, job.obj, this.mapCtx);
|
|
52528
|
-
}
|
|
52529
|
-
beginCluster(sg, job) {
|
|
52530
|
-
const bb = sg.info.bb;
|
|
52531
|
-
if (this.mapCtx !== null && job.obj !== null && bb !== void 0) {
|
|
52532
|
-
computeClusterUrlMap(bb, job.obj, this.mapCtx);
|
|
52533
|
-
}
|
|
52534
|
-
}
|
|
52535
|
-
endEdge(e, job) {
|
|
52536
|
-
if (this.mapCtx !== null && job.obj !== null) this.emitEdge(e, job.obj, job);
|
|
52537
|
-
}
|
|
52538
|
-
/** C emit_map_rect: record the HTML table/cell box as the pending hot spot,
|
|
52539
|
-
* so the following beginAnchor emits its <area>. @see emit.c:640 */
|
|
52540
|
-
emitMapRect(box, job) {
|
|
52541
|
-
if (this.mapCtx !== null && job.obj !== null) {
|
|
52542
|
-
computeClusterUrlMap(box, job.obj, this.mapCtx);
|
|
52543
|
-
}
|
|
52544
|
-
}
|
|
52545
|
-
beginAnchor(url, tip2, target, id, job) {
|
|
52546
|
-
const obj = job.obj;
|
|
52547
|
-
if (obj === null || obj.urlMapPts.length === 0) return;
|
|
52548
|
-
this.emitShape(obj.urlMapShape, obj.urlMapPts, { url, tooltip: tip2, target, id }, job);
|
|
52549
|
-
}
|
|
52550
|
-
endNode(_n, _job) {
|
|
52551
|
-
}
|
|
52552
|
-
beginEdge(_e, _job) {
|
|
52553
|
-
}
|
|
52554
|
-
endCluster(_sg, _job) {
|
|
52555
|
-
}
|
|
52556
|
-
textspan(_pos, _span, _job) {
|
|
52557
|
-
}
|
|
52558
|
-
ellipse(_c, _rx, _ry, _f, _j) {
|
|
52559
|
-
}
|
|
52560
|
-
polygon(_pts, _filled, _job) {
|
|
52561
|
-
}
|
|
52562
|
-
bezier(_pts, _filled, _job) {
|
|
52563
|
-
}
|
|
52564
|
-
polyline(_pts, _job) {
|
|
52565
|
-
}
|
|
52566
|
-
/** Emit one hot spot: `<area>` (cmapx) or a plain `keyword url coords` line. */
|
|
52567
|
-
emitShape(shape, pts, a, job) {
|
|
52568
|
-
const buf = [];
|
|
52569
|
-
if (this.isCmapx) mapOutputCmapx(shape, pts, a, true, buf);
|
|
52570
|
-
else mapOutputImap(shape, pts, a.url, buf);
|
|
52571
|
-
job.write(buf.join(""));
|
|
52572
|
-
}
|
|
52573
|
-
/** Whole-edge spline outline(s) then center/head/tail label hot spots.
|
|
52574
|
-
* @see lib/common/emit.c:emit_begin_edge (2851-2872) / emit_end_edge */
|
|
52575
|
-
emitEdge(e, obj, job) {
|
|
52576
|
-
const spl = e.info.spl;
|
|
52577
|
-
const wholeEdge = obj.url !== null || obj.explicitTooltip;
|
|
52578
|
-
if (spl !== void 0 && this.mapPolygon && wholeEdge) {
|
|
52579
|
-
const w2 = Math.max(obj.penWidth / 2, 2);
|
|
52580
|
-
const anchor = anchorOf(obj.url, obj.tooltip, obj.target, obj.id);
|
|
52581
|
-
for (const poly of computeEdgeSplineMaps(spl, w2, this.mapCtx)) {
|
|
52582
|
-
this.emitShape(2 /* Polygon */, poly, anchor, job);
|
|
52583
|
-
}
|
|
52584
|
-
}
|
|
52585
|
-
const centerA = {
|
|
52586
|
-
url: obj.labelUrl,
|
|
52587
|
-
tooltip: obj.labelTooltip,
|
|
52588
|
-
target: obj.labelTarget,
|
|
52589
|
-
explicit: obj.explicitLabelTooltip
|
|
52590
|
-
};
|
|
52591
|
-
this.emitEdgeLabel(e.info.label, centerA, obj, job);
|
|
52592
|
-
this.emitEdgeLabel(e.info.xlabel, centerA, obj, job);
|
|
52593
|
-
this.emitEdgeLabel(
|
|
52594
|
-
e.info.head_label,
|
|
52595
|
-
{ url: obj.headUrl, tooltip: obj.headTooltip, target: obj.headTarget, explicit: obj.explicitHeadTooltip },
|
|
52596
|
-
obj,
|
|
52597
|
-
job
|
|
52598
|
-
);
|
|
52599
|
-
this.emitEdgeLabel(
|
|
52600
|
-
e.info.tail_label,
|
|
52601
|
-
{ url: obj.tailUrl, tooltip: obj.tailTooltip, target: obj.tailTarget, explicit: obj.explicitTailTooltip },
|
|
52602
|
-
obj,
|
|
52603
|
-
job
|
|
52604
|
-
);
|
|
52605
|
-
}
|
|
52606
|
-
/** One edge label hot spot (map_label rect), if placed and url/explicit-tip.
|
|
52607
|
-
* @see lib/common/emit.c:emit_edge_label */
|
|
52608
|
-
emitEdgeLabel(lab, la, obj, job) {
|
|
52609
|
-
const placed = lab !== void 0 && lab.set && this.mapCtx !== null;
|
|
52610
|
-
if (!placed || la.url === null && !la.explicit) return;
|
|
52611
|
-
computeLabelRectMap(lab, obj, this.mapCtx);
|
|
52612
|
-
this.emitShape(0 /* Rectangle */, obj.urlMapPts, anchorOf(la.url, la.tooltip, la.target, obj.id), job);
|
|
52613
|
-
}
|
|
52614
|
-
};
|
|
52615
|
-
var ImapRenderer = class extends MapRendererBase {
|
|
52616
|
-
type = "imap";
|
|
52617
|
-
isCmapx = false;
|
|
52618
|
-
mapPolygon = true;
|
|
52619
|
-
};
|
|
52620
|
-
var ImapNpRenderer = class extends MapRendererBase {
|
|
52621
|
-
type = "imap-np";
|
|
52622
|
-
isCmapx = false;
|
|
52623
|
-
mapPolygon = false;
|
|
52624
|
-
};
|
|
52625
|
-
var CmapxRenderer = class extends MapRendererBase {
|
|
52626
|
-
type = "cmapx";
|
|
52627
|
-
isCmapx = true;
|
|
52628
|
-
mapPolygon = true;
|
|
52629
|
-
};
|
|
52630
|
-
var CmapxNpRenderer = class extends MapRendererBase {
|
|
52631
|
-
type = "cmapx-np";
|
|
52632
|
-
isCmapx = true;
|
|
52633
|
-
mapPolygon = false;
|
|
52634
|
-
};
|
|
52635
|
-
function createPlainRenderer() {
|
|
52636
|
-
return new PlainRenderer();
|
|
52637
|
-
}
|
|
52638
|
-
function createPlainExtRenderer() {
|
|
52639
|
-
return new PlainExtRenderer();
|
|
52640
|
-
}
|
|
52641
|
-
function createImapRenderer() {
|
|
52642
|
-
return new ImapRenderer();
|
|
52643
|
-
}
|
|
52644
|
-
function createImapNpRenderer() {
|
|
52645
|
-
return new ImapNpRenderer();
|
|
52646
|
-
}
|
|
52647
|
-
function createCmapxRenderer() {
|
|
52648
|
-
return new CmapxRenderer();
|
|
53095
|
+
function createJson0Renderer() {
|
|
53096
|
+
return new Json0Renderer();
|
|
52649
53097
|
}
|
|
52650
|
-
function
|
|
52651
|
-
return new
|
|
53098
|
+
function createJsonRenderer() {
|
|
53099
|
+
return new JsonRenderer();
|
|
52652
53100
|
}
|
|
52653
53101
|
|
|
52654
53102
|
// src/gvc/default-context.ts
|