@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/render.js
CHANGED
|
@@ -7367,6 +7367,7 @@ function bezierClip(insideCtx, insideFn, sp, leftInside) {
|
|
|
7367
7367
|
|
|
7368
7368
|
// src/common/arrows-constants.ts
|
|
7369
7369
|
var ARROW_LENGTH = 10;
|
|
7370
|
+
var NUMB_OF_ARROW_HEADS = 4;
|
|
7370
7371
|
var BITS_PER_ARROW_TYPE = 4;
|
|
7371
7372
|
var ARR_TYPE_NONE = 0;
|
|
7372
7373
|
var ARR_TYPE_NORM = 1;
|
|
@@ -7480,13 +7481,18 @@ var parseOne = (s) => {
|
|
|
7480
7481
|
function parseArrow(str) {
|
|
7481
7482
|
const result = [];
|
|
7482
7483
|
let s = str;
|
|
7483
|
-
|
|
7484
|
+
let parsedAny = false;
|
|
7485
|
+
while (s.length > 0 && result.length < NUMB_OF_ARROW_HEADS) {
|
|
7484
7486
|
const m = parseOne(s);
|
|
7485
7487
|
if (!m) break;
|
|
7486
|
-
|
|
7488
|
+
parsedAny = true;
|
|
7487
7489
|
s = m.rest;
|
|
7490
|
+
const isGap = m.comp.name === "none";
|
|
7491
|
+
if (isGap && result.length === NUMB_OF_ARROW_HEADS - 1) continue;
|
|
7492
|
+
if (isGap && result.length === 0 && s.length === 0) continue;
|
|
7493
|
+
result.push(m.comp);
|
|
7488
7494
|
}
|
|
7489
|
-
if (
|
|
7495
|
+
if (!parsedAny) result.push({ name: "normal", open: false, left: false, right: false });
|
|
7490
7496
|
return result;
|
|
7491
7497
|
}
|
|
7492
7498
|
var NAME_TO_FLAG = /* @__PURE__ */ new Map([
|
|
@@ -8144,6 +8150,32 @@ function arrowDrawOpsForEnd(e, end, tip2, dir, penwidth) {
|
|
|
8144
8150
|
return arrowDrawOps(comps, tip2, dir, edgeArrowsize(e), penwidth);
|
|
8145
8151
|
}
|
|
8146
8152
|
|
|
8153
|
+
// src/model/layoutParams.ts
|
|
8154
|
+
var FontnameKind = {
|
|
8155
|
+
NativeFonts: 0,
|
|
8156
|
+
PsFonts: 1,
|
|
8157
|
+
SvgFonts: 2
|
|
8158
|
+
};
|
|
8159
|
+
function makeDrawing(overrides) {
|
|
8160
|
+
const zero = { x: 0, y: 0 };
|
|
8161
|
+
return {
|
|
8162
|
+
quantum: 0,
|
|
8163
|
+
scale: 0,
|
|
8164
|
+
ratio: 0,
|
|
8165
|
+
dpi: 0,
|
|
8166
|
+
margin: { ...zero },
|
|
8167
|
+
page: { ...zero },
|
|
8168
|
+
size: { ...zero },
|
|
8169
|
+
filled: false,
|
|
8170
|
+
landscape: false,
|
|
8171
|
+
centered: false,
|
|
8172
|
+
ratioKind: "none",
|
|
8173
|
+
xdots: null,
|
|
8174
|
+
id: null,
|
|
8175
|
+
...overrides
|
|
8176
|
+
};
|
|
8177
|
+
}
|
|
8178
|
+
|
|
8147
8179
|
// src/util/printf-round.ts
|
|
8148
8180
|
function decomposeFloat64(v) {
|
|
8149
8181
|
const buf = new DataView(new ArrayBuffer(8));
|
|
@@ -8376,6 +8408,9 @@ var RenderJob = class {
|
|
|
8376
8408
|
radialGradId = 0;
|
|
8377
8409
|
/** Whether the graph being rendered is directed (edge titles use -> vs --). */
|
|
8378
8410
|
directed = true;
|
|
8411
|
+
/** GD_fontnames of the root graph — selects which PostScript-alias fields the
|
|
8412
|
+
* SVG emitter uses. @see plugin/core/gvrender_core_svg.c:464 */
|
|
8413
|
+
fontnames = FontnameKind.NativeFonts;
|
|
8379
8414
|
/** Root graph's `id` attribute (GD_drawing(root)->id), '' if unset. Used as
|
|
8380
8415
|
* the `<gid>_` prefix for non-root object ids. @see lib/common/emit.c:getObjId */
|
|
8381
8416
|
drawingId = "";
|
|
@@ -8486,32 +8521,6 @@ function boxOverlap(b0, b1) {
|
|
|
8486
8521
|
}
|
|
8487
8522
|
var POINTS_PER_INCH = 72;
|
|
8488
8523
|
|
|
8489
|
-
// src/model/layoutParams.ts
|
|
8490
|
-
var FontnameKind = {
|
|
8491
|
-
NativeFonts: 0,
|
|
8492
|
-
PsFonts: 1,
|
|
8493
|
-
SvgFonts: 2
|
|
8494
|
-
};
|
|
8495
|
-
function makeDrawing(overrides) {
|
|
8496
|
-
const zero = { x: 0, y: 0 };
|
|
8497
|
-
return {
|
|
8498
|
-
quantum: 0,
|
|
8499
|
-
scale: 0,
|
|
8500
|
-
ratio: 0,
|
|
8501
|
-
dpi: 0,
|
|
8502
|
-
margin: { ...zero },
|
|
8503
|
-
page: { ...zero },
|
|
8504
|
-
size: { ...zero },
|
|
8505
|
-
filled: false,
|
|
8506
|
-
landscape: false,
|
|
8507
|
-
centered: false,
|
|
8508
|
-
ratioKind: "none",
|
|
8509
|
-
xdots: null,
|
|
8510
|
-
id: null,
|
|
8511
|
-
...overrides
|
|
8512
|
-
};
|
|
8513
|
-
}
|
|
8514
|
-
|
|
8515
8524
|
// src/model/graphInfo.ts
|
|
8516
8525
|
function makeGraphInfo() {
|
|
8517
8526
|
return {
|
|
@@ -9879,6 +9888,13 @@ function emitHtmlImg(img, pos, renderer, job) {
|
|
|
9879
9888
|
}
|
|
9880
9889
|
renderer.usershape?.(img.src, b, job);
|
|
9881
9890
|
}
|
|
9891
|
+
function htmlStyleInvisible(style) {
|
|
9892
|
+
if (style === void 0) return false;
|
|
9893
|
+
return style.split(/[ ,]+/).some((tok) => {
|
|
9894
|
+
const t = tok.toUpperCase();
|
|
9895
|
+
return t === "INVIS" || t === "INVISIBLE";
|
|
9896
|
+
});
|
|
9897
|
+
}
|
|
9882
9898
|
function emitHtmlCell(cell2, pos, renderer, job) {
|
|
9883
9899
|
const inAnchor = initHtmlAnchor(
|
|
9884
9900
|
{ href: cell2.href, title: cell2.title, target: cell2.target, id: cell2.id },
|
|
@@ -9886,6 +9902,10 @@ function emitHtmlCell(cell2, pos, renderer, job) {
|
|
|
9886
9902
|
renderer,
|
|
9887
9903
|
job
|
|
9888
9904
|
);
|
|
9905
|
+
if (htmlStyleInvisible(cell2.style)) {
|
|
9906
|
+
if (inAnchor) endHtmlAnchor(renderer, job);
|
|
9907
|
+
return;
|
|
9908
|
+
}
|
|
9889
9909
|
emitCellDecoration(cell2, pos, renderer, job);
|
|
9890
9910
|
if (cell2.nested !== void 0) {
|
|
9891
9911
|
emitHtmlLabel(cell2.nested, pos, renderer, job);
|
|
@@ -9918,22 +9938,25 @@ function emitHtmlLabel(placed, pos, renderer, job) {
|
|
|
9918
9938
|
const tblBox = absBox(placed.box, pos);
|
|
9919
9939
|
const clustersLast = (job.flags & EMIT_CLUSTERS_LAST) !== 0;
|
|
9920
9940
|
const inAnchor = clustersLast ? false : initHtmlAnchor(anchorData, tblBox, renderer, job);
|
|
9921
|
-
|
|
9922
|
-
|
|
9923
|
-
|
|
9924
|
-
|
|
9925
|
-
|
|
9926
|
-
|
|
9927
|
-
|
|
9928
|
-
|
|
9929
|
-
|
|
9930
|
-
|
|
9931
|
-
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
9935
|
-
|
|
9936
|
-
|
|
9941
|
+
const tblInvisible = htmlStyleInvisible(placed.style);
|
|
9942
|
+
if (!tblInvisible) {
|
|
9943
|
+
if (placed.bgcolor !== void 0) {
|
|
9944
|
+
emitBgFill({
|
|
9945
|
+
bgcolor: placed.bgcolor,
|
|
9946
|
+
box: placed.box,
|
|
9947
|
+
pos,
|
|
9948
|
+
border: placed.border,
|
|
9949
|
+
renderer,
|
|
9950
|
+
job,
|
|
9951
|
+
gradientangle: placed.gradientangle,
|
|
9952
|
+
style: placed.style
|
|
9953
|
+
});
|
|
9954
|
+
}
|
|
9955
|
+
for (const cell2 of placed.cells) emitHtmlCell(cell2, pos, renderer, job);
|
|
9956
|
+
emitTableRules(placed, pos, renderer, job);
|
|
9957
|
+
if (placed.border > 0) {
|
|
9958
|
+
emitBorder({ box: placed.box, pos, border: placed.border, color: placed.color, sides: placed.sides, style: placed.style }, renderer, job);
|
|
9959
|
+
}
|
|
9937
9960
|
}
|
|
9938
9961
|
if (inAnchor) endHtmlAnchor(renderer, job);
|
|
9939
9962
|
if (clustersLast && initHtmlAnchor(anchorData, tblBox, renderer, job)) {
|
|
@@ -14285,11 +14308,9 @@ function applyPenState(obj, styleAttr, colorAttr, penwidthAttr) {
|
|
|
14285
14308
|
function applyNodeStyle(obj, n, job) {
|
|
14286
14309
|
const styleAttr = nodeAttr(n, n.root, "style");
|
|
14287
14310
|
const colorAttr = nodeAttr(n, n.root, "color");
|
|
14288
|
-
|
|
14289
|
-
|
|
14290
|
-
|
|
14291
|
-
return filled;
|
|
14292
|
-
});
|
|
14311
|
+
const filled = applyFillState(obj, n, job);
|
|
14312
|
+
applyPenState(obj, styleAttr, colorAttr, nodeAttr(n, n.root, "penwidth"));
|
|
14313
|
+
return filled;
|
|
14293
14314
|
}
|
|
14294
14315
|
function prepareDrawPoly(poly, filled, obj) {
|
|
14295
14316
|
if (poly.peripheries !== 0 || !filled) return poly;
|
|
@@ -15084,6 +15105,30 @@ var Graph = class {
|
|
|
15084
15105
|
* @see lib/cgraph/graph.c:agsubg (attr defval copy)
|
|
15085
15106
|
*/
|
|
15086
15107
|
graphDefaultsSnapshot;
|
|
15108
|
+
/**
|
|
15109
|
+
* Keys in `attrs` the BUILDER seeded from `graphDefaultsSnapshot` rather than
|
|
15110
|
+
* the input declaring them in this scope (see builder.ts GRAPH_LABEL_INHERIT_
|
|
15111
|
+
* KEYS). cgraph distinguishes these structurally — a seeded value is inherited
|
|
15112
|
+
* through the dict view and has no local symbol, while a local declaration
|
|
15113
|
+
* creates one even when its value equals the inherited value — so the
|
|
15114
|
+
* serializer needs the provenance to decide what write_dict would print.
|
|
15115
|
+
* Undefined on the root graph and on any scope that seeded nothing.
|
|
15116
|
+
* @see lib/cgraph/write.c:262 write_dict
|
|
15117
|
+
*/
|
|
15118
|
+
seededAttrs;
|
|
15119
|
+
/**
|
|
15120
|
+
* Graph-attribute keys whose declaration in THIS scope was the first anywhere
|
|
15121
|
+
* in the graph. cgraph's `setattr` splits on exactly this: a key with no
|
|
15122
|
+
* existing symbol takes the global-declaration branch, which ends up seeding
|
|
15123
|
+
* every direct subgraph that already exists with a local empty value (see
|
|
15124
|
+
* `eagerEmptyParts`); a key some earlier scope already declared takes the
|
|
15125
|
+
* "new local definition" branch and touches no subgraph at all. So an
|
|
15126
|
+
* identical `graph [fill=x]` statement makes a child print `fill=""` or print
|
|
15127
|
+
* nothing purely according to whether a SIBLING declared `fill` first.
|
|
15128
|
+
* Undefined on any scope that declared nothing new.
|
|
15129
|
+
* @see lib/cgraph/attr.c:257 setattr (lsym / psym / global branches)
|
|
15130
|
+
*/
|
|
15131
|
+
firstGraphDecl;
|
|
15087
15132
|
/**
|
|
15088
15133
|
* Layout-engine info; replaces GD_* macros via agbindrec (AD-1).
|
|
15089
15134
|
* @see lib/cgraph/cgraph.h:agbindrec
|
|
@@ -16256,7 +16301,7 @@ function applyLabel(e, g, fi, measurer) {
|
|
|
16256
16301
|
const content = isHtml ? htmlValueContent(str) : str;
|
|
16257
16302
|
e.info.label = makeAnyLabel(content, isHtml, fi, measurer, e);
|
|
16258
16303
|
g.root.info.has_labels |= EDGE_LABEL;
|
|
16259
|
-
e.info.label_ontop = mapbool2(e.attrs.get("
|
|
16304
|
+
e.info.label_ontop = mapbool2(e.attrs.get("labelfloat")) ? 1 : 0;
|
|
16260
16305
|
}
|
|
16261
16306
|
function applyXLabel(e, g, fi, measurer) {
|
|
16262
16307
|
const str = e.attrs.get("xlabel");
|
|
@@ -17213,60 +17258,89 @@ function commonInitNodeEdge(g) {
|
|
|
17213
17258
|
|
|
17214
17259
|
// src/common/ps-fontalias.ts
|
|
17215
17260
|
var ALIAS_ROWS = [
|
|
17216
|
-
["AvantGarde-Book", "URW Gothic L", "book", null, null, "sans-Serif"],
|
|
17217
|
-
["AvantGarde-BookOblique", "URW Gothic L", "book", null, "oblique", "sans-Serif"],
|
|
17218
|
-
["AvantGarde-Demi", "URW Gothic L", "demi", null, null, "sans-Serif"],
|
|
17219
|
-
["AvantGarde-DemiOblique", "URW Gothic L", "demi", null, "oblique", "sans-Serif"],
|
|
17220
|
-
["Bookman-Demi", "URW Bookman L", "demi", null, null, "serif"],
|
|
17221
|
-
["Bookman-DemiItalic", "URW Bookman L", "demi", null, "italic", "serif"],
|
|
17222
|
-
["Bookman-Light", "URW Bookman L", "light", null, null, "serif"],
|
|
17223
|
-
["Bookman-LightItalic", "URW Bookman L", "light", null, "italic", "serif"],
|
|
17224
|
-
["Courier", "Courier", null, null, null, "monospace"],
|
|
17225
|
-
["Courier-Bold", "Courier", "bold", null, null, "monospace"],
|
|
17226
|
-
["Courier-BoldOblique", "Courier", "bold", null, "oblique", "monospace"],
|
|
17227
|
-
["Courier-Oblique", "Courier", null, null, "oblique", "monospace"],
|
|
17228
|
-
["Helvetica", "Helvetica", null, null, null, "sans-Serif"],
|
|
17229
|
-
["Helvetica-Bold", "Helvetica", "bold", null, null, "sans-Serif"],
|
|
17230
|
-
["Helvetica-BoldOblique", "Helvetica", "bold", null, "oblique", "sans-Serif"],
|
|
17231
|
-
["Helvetica-Narrow", "Helvetica", null, "condensed", null, "sans-Serif"],
|
|
17232
|
-
["Helvetica-Narrow-Bold", "Helvetica", "bold", "condensed", null, "sans-Serif"],
|
|
17233
|
-
["Helvetica-Narrow-BoldOblique", "Helvetica", "bold", "condensed", "oblique", "sans-Serif"],
|
|
17234
|
-
["Helvetica-Narrow-Oblique", "Helvetica", null, "condensed", "oblique", "sans-Serif"],
|
|
17235
|
-
["Helvetica-Oblique", "Helvetica", null, null, "oblique", "sans-Serif"],
|
|
17236
|
-
["NewCenturySchlbk-Bold", "Century Schoolbook L", "bold", null, null, "serif"],
|
|
17237
|
-
["NewCenturySchlbk-BoldItalic", "Century Schoolbook L", "bold", null, "italic", "serif"],
|
|
17238
|
-
["NewCenturySchlbk-Italic", "Century Schoolbook L", null, null, "italic", "serif"],
|
|
17239
|
-
["NewCenturySchlbk-Roman", "Century Schoolbook L", "roman", null, null, "serif"],
|
|
17240
|
-
["Palatino-Bold", "Palatino Linotype", "bold", null, null, "serif"],
|
|
17241
|
-
["Palatino-BoldItalic", "Palatino Linotype", "bold", null, "italic", "serif"],
|
|
17242
|
-
["Palatino-Italic", "Palatino Linotype", null, null, "italic", "serif"],
|
|
17243
|
-
["Palatino-Roman", "Palatino Linotype", "roman", null, null, "serif"],
|
|
17244
|
-
["Symbol", "Symbol", null, null, null, "fantasy"],
|
|
17245
|
-
["Times-Bold", "Times", "bold", null, null, "serif"],
|
|
17246
|
-
["Times-BoldItalic", "Times", "bold", null, "italic", "serif"],
|
|
17247
|
-
["Times-Italic", "Times", null, null, "italic", "serif"],
|
|
17248
|
-
["Times-Roman", "Times", null, null, null, "serif"],
|
|
17249
|
-
["ZapfChancery-MediumItalic", "URW Chancery L", "medium", null, "italic", "serif"],
|
|
17250
|
-
["ZapfDingbats", "Dingbats", null, null, null, "fantasy"]
|
|
17261
|
+
["AvantGarde-Book", "URW Gothic L", "book", null, null, "sans-Serif", null, null],
|
|
17262
|
+
["AvantGarde-BookOblique", "URW Gothic L", "book", null, "oblique", "sans-Serif", null, "italic"],
|
|
17263
|
+
["AvantGarde-Demi", "URW Gothic L", "demi", null, null, "sans-Serif", "bold", null],
|
|
17264
|
+
["AvantGarde-DemiOblique", "URW Gothic L", "demi", null, "oblique", "sans-Serif", "bold", "italic"],
|
|
17265
|
+
["Bookman-Demi", "URW Bookman L", "demi", null, null, "serif", "bold", null],
|
|
17266
|
+
["Bookman-DemiItalic", "URW Bookman L", "demi", null, "italic", "serif", "bold", "italic"],
|
|
17267
|
+
["Bookman-Light", "URW Bookman L", "light", null, null, "serif", null, null],
|
|
17268
|
+
["Bookman-LightItalic", "URW Bookman L", "light", null, "italic", "serif", null, "italic"],
|
|
17269
|
+
["Courier", "Courier", null, null, null, "monospace", null, null],
|
|
17270
|
+
["Courier-Bold", "Courier", "bold", null, null, "monospace", "bold", null],
|
|
17271
|
+
["Courier-BoldOblique", "Courier", "bold", null, "oblique", "monospace", "bold", "italic"],
|
|
17272
|
+
["Courier-Oblique", "Courier", null, null, "oblique", "monospace", null, "italic"],
|
|
17273
|
+
["Helvetica", "Helvetica", null, null, null, "sans-Serif", null, null],
|
|
17274
|
+
["Helvetica-Bold", "Helvetica", "bold", null, null, "sans-Serif", "bold", null],
|
|
17275
|
+
["Helvetica-BoldOblique", "Helvetica", "bold", null, "oblique", "sans-Serif", "bold", "italic"],
|
|
17276
|
+
["Helvetica-Narrow", "Helvetica", null, "condensed", null, "sans-Serif", null, null],
|
|
17277
|
+
["Helvetica-Narrow-Bold", "Helvetica", "bold", "condensed", null, "sans-Serif", "bold", null],
|
|
17278
|
+
["Helvetica-Narrow-BoldOblique", "Helvetica", "bold", "condensed", "oblique", "sans-Serif", "bold", "italic"],
|
|
17279
|
+
["Helvetica-Narrow-Oblique", "Helvetica", null, "condensed", "oblique", "sans-Serif", null, "italic"],
|
|
17280
|
+
["Helvetica-Oblique", "Helvetica", null, null, "oblique", "sans-Serif", null, "italic"],
|
|
17281
|
+
["NewCenturySchlbk-Bold", "Century Schoolbook L", "bold", null, null, "serif", "bold", null],
|
|
17282
|
+
["NewCenturySchlbk-BoldItalic", "Century Schoolbook L", "bold", null, "italic", "serif", "bold", "italic"],
|
|
17283
|
+
["NewCenturySchlbk-Italic", "Century Schoolbook L", null, null, "italic", "serif", null, "italic"],
|
|
17284
|
+
["NewCenturySchlbk-Roman", "Century Schoolbook L", "roman", null, null, "serif", null, null],
|
|
17285
|
+
["Palatino-Bold", "Palatino Linotype", "bold", null, null, "serif", "bold", null],
|
|
17286
|
+
["Palatino-BoldItalic", "Palatino Linotype", "bold", null, "italic", "serif", "bold", "italic"],
|
|
17287
|
+
["Palatino-Italic", "Palatino Linotype", null, null, "italic", "serif", null, "italic"],
|
|
17288
|
+
["Palatino-Roman", "Palatino Linotype", "roman", null, null, "serif", null, null],
|
|
17289
|
+
["Symbol", "Symbol", null, null, null, "fantasy", null, null],
|
|
17290
|
+
["Times-Bold", "Times", "bold", null, null, "serif", "bold", null],
|
|
17291
|
+
["Times-BoldItalic", "Times", "bold", null, "italic", "serif", "bold", "italic"],
|
|
17292
|
+
["Times-Italic", "Times", null, null, "italic", "serif", null, "italic"],
|
|
17293
|
+
["Times-Roman", "Times", null, null, null, "serif", null, null],
|
|
17294
|
+
["ZapfChancery-MediumItalic", "URW Chancery L", "medium", null, "italic", "serif", null, "italic"],
|
|
17295
|
+
["ZapfDingbats", "Dingbats", null, null, null, "fantasy", null, null]
|
|
17251
17296
|
];
|
|
17252
17297
|
var ALIAS_BY_NAME = new Map(
|
|
17253
17298
|
ALIAS_ROWS.map((r) => [
|
|
17254
17299
|
r[0].toLowerCase(),
|
|
17255
|
-
{
|
|
17300
|
+
{
|
|
17301
|
+
name: r[0],
|
|
17302
|
+
family: r[1],
|
|
17303
|
+
weight: r[2],
|
|
17304
|
+
stretch: r[3],
|
|
17305
|
+
style: r[4],
|
|
17306
|
+
svgFontFamily: r[5],
|
|
17307
|
+
svgFontWeight: r[6],
|
|
17308
|
+
svgFontStyle: r[7]
|
|
17309
|
+
}
|
|
17256
17310
|
])
|
|
17257
17311
|
);
|
|
17258
17312
|
function translatePostscriptFontname(name) {
|
|
17259
17313
|
return ALIAS_BY_NAME.get(name.toLowerCase()) ?? null;
|
|
17260
17314
|
}
|
|
17261
|
-
function fontFamilyAttrs(fontName) {
|
|
17315
|
+
function fontFamilyAttrs(fontName, mode = FontnameKind.NativeFonts) {
|
|
17262
17316
|
const a = fontName !== null ? translatePostscriptFontname(fontName) : null;
|
|
17263
17317
|
if (a === null) return null;
|
|
17264
|
-
|
|
17318
|
+
let family;
|
|
17319
|
+
let weight;
|
|
17320
|
+
let style;
|
|
17321
|
+
switch (mode) {
|
|
17322
|
+
case FontnameKind.PsFonts:
|
|
17323
|
+
family = a.name;
|
|
17324
|
+
weight = a.weight;
|
|
17325
|
+
style = a.style;
|
|
17326
|
+
break;
|
|
17327
|
+
case FontnameKind.SvgFonts:
|
|
17328
|
+
family = a.svgFontFamily;
|
|
17329
|
+
weight = a.svgFontWeight;
|
|
17330
|
+
style = a.svgFontStyle;
|
|
17331
|
+
break;
|
|
17332
|
+
default:
|
|
17333
|
+
family = a.family;
|
|
17334
|
+
weight = a.weight;
|
|
17335
|
+
style = a.style;
|
|
17336
|
+
break;
|
|
17337
|
+
}
|
|
17338
|
+
const fam = a.svgFontFamily !== family ? family + "," + a.svgFontFamily : family;
|
|
17265
17339
|
let attrs = ' font-family="' + fam + '"';
|
|
17266
|
-
if (
|
|
17340
|
+
if (weight !== null) attrs += ' font-weight="' + weight + '"';
|
|
17267
17341
|
if (a.stretch !== null) attrs += ' font-stretch="' + a.stretch + '"';
|
|
17268
|
-
if (
|
|
17269
|
-
return { attrs, weight:
|
|
17342
|
+
if (style !== null) attrs += ' font-style="' + style + '"';
|
|
17343
|
+
return { attrs, weight: weight !== null, style: style !== null };
|
|
17270
17344
|
}
|
|
17271
17345
|
|
|
17272
17346
|
// src/render/svg-gradient.ts
|
|
@@ -17911,6 +17985,7 @@ function emitBgcolorBackground(g, bb, bgcolorAttr, job) {
|
|
|
17911
17985
|
function svgBeginGraph(g, job) {
|
|
17912
17986
|
job.devscale = { x: 1, y: -1 };
|
|
17913
17987
|
job.directed = g.kind === "directed" || g.kind === "strict-directed";
|
|
17988
|
+
job.fontnames = g.info.fontnames ?? FontnameKind.NativeFonts;
|
|
17914
17989
|
job.drawingId = g.attrs.get("id") ?? "";
|
|
17915
17990
|
job.write(SVG_XML_DECL);
|
|
17916
17991
|
const stylesheet = g.attrs.get("stylesheet");
|
|
@@ -18200,7 +18275,7 @@ function svgTextspan(pos, span, job) {
|
|
|
18200
18275
|
job.write('" y="');
|
|
18201
18276
|
job.printDouble(y);
|
|
18202
18277
|
job.write('"');
|
|
18203
|
-
const ff = fontFamilyAttrs(span.fontName);
|
|
18278
|
+
const ff = fontFamilyAttrs(span.fontName, job.fontnames);
|
|
18204
18279
|
if (ff !== null) job.write(ff.attrs);
|
|
18205
18280
|
else job.write(' font-family="' + escapeXml(span.fontName ?? "Times,serif") + '"');
|
|
18206
18281
|
emitFontAttrs(span.fontFlags, job, ff ?? { weight: false, style: false });
|
|
@@ -18950,17 +19025,19 @@ function nodeInBox(n, job) {
|
|
|
18950
19025
|
function emitNodeBody(n, renderer, job) {
|
|
18951
19026
|
setHtmlAnchorObj(svgNodeId(n, job), labelTextOf2(n.info.label), job.obj ?? void 0);
|
|
18952
19027
|
setHtmlObjImgscale(nodeAttr(n, n.root, "imagescale"));
|
|
18953
|
-
|
|
18954
|
-
|
|
18955
|
-
|
|
18956
|
-
|
|
18957
|
-
|
|
19028
|
+
withColorScheme(nodeAttr(n, n.root, "colorscheme"), () => {
|
|
19029
|
+
renderer.beginNode(n, job);
|
|
19030
|
+
const shape = n.info.shape;
|
|
19031
|
+
if (shape?.fns?.codefn) shape.fns.codefn(job, n);
|
|
19032
|
+
renderNodeXLabel(n, renderer, job);
|
|
19033
|
+
renderer.endNode(n, job);
|
|
19034
|
+
});
|
|
18958
19035
|
}
|
|
18959
19036
|
function renderNode(n, renderer, job, done) {
|
|
18960
19037
|
if (done.has(n)) return;
|
|
18961
19038
|
done.add(n);
|
|
18962
19039
|
if (!nodeInBox(n, job)) return;
|
|
18963
|
-
const nodeStyle = n.
|
|
19040
|
+
const nodeStyle = nodeAttr(n, n.root, "style");
|
|
18964
19041
|
if (parseStyleFlags(nodeStyle).invis) return;
|
|
18965
19042
|
if (!isPointNode(n) && styleHasInvisibleAlias(nodeStyle)) return;
|
|
18966
19043
|
const obj = createObjState(2 /* Node */);
|
|
@@ -19583,6 +19660,21 @@ var SplineClipHelper = class _SplineClipHelper {
|
|
|
19583
19660
|
}
|
|
19584
19661
|
return [sflag, eflag];
|
|
19585
19662
|
}
|
|
19663
|
+
/**
|
|
19664
|
+
* The ORIGINAL edge's arrow end whose TYPE applies at a geometric spline end.
|
|
19665
|
+
*
|
|
19666
|
+
* C resolves both arrow types once from the original edge (arrow_flags) and
|
|
19667
|
+
* encodes each type in its flag, so swapping the two flags when swapEnds is
|
|
19668
|
+
* true carries the type along with it. This port keeps only the presence bit
|
|
19669
|
+
* in the flag and re-reads the type from the `arrowhead`/`arrowtail`
|
|
19670
|
+
* attribute, so the same swap has to be applied to the NAME lookup — without
|
|
19671
|
+
* it a reversed back edge (`C->A [arrowhead=tee]`, head ranked above tail)
|
|
19672
|
+
* draws its `arrowtail` type at the end where C draws the arrowhead.
|
|
19673
|
+
* @see lib/common/splines.c:arrow_clip (if (j) SWAP(sflag, eflag))
|
|
19674
|
+
*/
|
|
19675
|
+
static arrowAttrEnd(isTail, j) {
|
|
19676
|
+
return isTail !== j ? "tail" : "head";
|
|
19677
|
+
}
|
|
19586
19678
|
static resolveArrowFlags(info, hn, fe, orig, j) {
|
|
19587
19679
|
let [sflag, eflag] = _SplineClipHelper.arrowFlags(orig);
|
|
19588
19680
|
if (info.splineMerge != null) {
|
|
@@ -19623,10 +19715,10 @@ var SplineClipHelper = class _SplineClipHelper {
|
|
|
19623
19715
|
* Clip the spline end to the head arrowhead base; records the tip in
|
|
19624
19716
|
* spl.ep. @see lib/common/arrows.c:arrowEndClip
|
|
19625
19717
|
*/
|
|
19626
|
-
static arrowEndClip(e, ps, startp, endpIn, spl, eflag) {
|
|
19718
|
+
static arrowEndClip(e, ps, startp, endpIn, spl, eflag, attrEnd = "head") {
|
|
19627
19719
|
let endp = endpIn;
|
|
19628
19720
|
const elen = arrowClipLength(
|
|
19629
|
-
edgeArrowName(e,
|
|
19721
|
+
edgeArrowName(e, attrEnd),
|
|
19630
19722
|
_SplineClipHelper.clipArrowsize(e),
|
|
19631
19723
|
_SplineClipHelper.clipPenwidth(e)
|
|
19632
19724
|
);
|
|
@@ -19650,10 +19742,10 @@ var SplineClipHelper = class _SplineClipHelper {
|
|
|
19650
19742
|
* Clip the spline start to the tail arrowhead base; records the tip
|
|
19651
19743
|
* in spl.sp. @see lib/common/arrows.c:arrowStartClip
|
|
19652
19744
|
*/
|
|
19653
|
-
static arrowStartClip(e, ps, startpIn, endp, spl, sflag) {
|
|
19745
|
+
static arrowStartClip(e, ps, startpIn, endp, spl, sflag, attrEnd = "tail") {
|
|
19654
19746
|
let startp = startpIn;
|
|
19655
19747
|
const slen = arrowClipLength(
|
|
19656
|
-
edgeArrowName(e,
|
|
19748
|
+
edgeArrowName(e, attrEnd),
|
|
19657
19749
|
_SplineClipHelper.clipArrowsize(e),
|
|
19658
19750
|
_SplineClipHelper.clipPenwidth(e)
|
|
19659
19751
|
);
|
|
@@ -19702,6 +19794,8 @@ var SplineClipHelper = class _SplineClipHelper {
|
|
|
19702
19794
|
const [sflag, eflag] = _SplineClipHelper.resolveArrowFlags(info, hn, fe, orig, j);
|
|
19703
19795
|
newspl.sflag = sflag;
|
|
19704
19796
|
newspl.eflag = eflag;
|
|
19797
|
+
const startEnd = _SplineClipHelper.arrowAttrEnd(true, j);
|
|
19798
|
+
const endEnd = _SplineClipHelper.arrowAttrEnd(false, j);
|
|
19705
19799
|
if (info.isOrtho) {
|
|
19706
19800
|
if (sflag || eflag) {
|
|
19707
19801
|
_SplineClipHelper.arrowOrthoClip(
|
|
@@ -19713,8 +19807,8 @@ var SplineClipHelper = class _SplineClipHelper {
|
|
|
19713
19807
|
sflag,
|
|
19714
19808
|
eflag
|
|
19715
19809
|
);
|
|
19716
|
-
if (sflag) _SplineClipHelper.stashArrow(orig, newspl.sp, ps[bounds.start], true);
|
|
19717
|
-
if (eflag) _SplineClipHelper.stashArrow(orig, newspl.ep, ps[bounds.end + 3], false);
|
|
19810
|
+
if (sflag) _SplineClipHelper.stashArrow(orig, newspl.sp, ps[bounds.start], true, startEnd);
|
|
19811
|
+
if (eflag) _SplineClipHelper.stashArrow(orig, newspl.ep, ps[bounds.end + 3], false, endEnd);
|
|
19718
19812
|
}
|
|
19719
19813
|
return;
|
|
19720
19814
|
}
|
|
@@ -19725,9 +19819,10 @@ var SplineClipHelper = class _SplineClipHelper {
|
|
|
19725
19819
|
bounds.start,
|
|
19726
19820
|
bounds.end,
|
|
19727
19821
|
newspl,
|
|
19728
|
-
sflag
|
|
19822
|
+
sflag,
|
|
19823
|
+
startEnd
|
|
19729
19824
|
);
|
|
19730
|
-
_SplineClipHelper.stashArrow(orig, newspl.sp, ps[bounds.start], true);
|
|
19825
|
+
_SplineClipHelper.stashArrow(orig, newspl.sp, ps[bounds.start], true, startEnd);
|
|
19731
19826
|
}
|
|
19732
19827
|
if (eflag) {
|
|
19733
19828
|
bounds.end = _SplineClipHelper.arrowEndClip(
|
|
@@ -19736,9 +19831,10 @@ var SplineClipHelper = class _SplineClipHelper {
|
|
|
19736
19831
|
bounds.start,
|
|
19737
19832
|
bounds.end,
|
|
19738
19833
|
newspl,
|
|
19739
|
-
eflag
|
|
19834
|
+
eflag,
|
|
19835
|
+
endEnd
|
|
19740
19836
|
);
|
|
19741
|
-
_SplineClipHelper.stashArrow(orig, newspl.ep, ps[bounds.end + 3], false);
|
|
19837
|
+
_SplineClipHelper.stashArrow(orig, newspl.ep, ps[bounds.end + 3], false, endEnd);
|
|
19742
19838
|
}
|
|
19743
19839
|
}
|
|
19744
19840
|
/**
|
|
@@ -19837,9 +19933,10 @@ var SplineClipHelper = class _SplineClipHelper {
|
|
|
19837
19933
|
}
|
|
19838
19934
|
}
|
|
19839
19935
|
/** Record an arrow polygon on the edge for svgArrowPolygons. */
|
|
19840
|
-
static stashArrow(e, tip2, base, isTail) {
|
|
19936
|
+
static stashArrow(e, tip2, base, isTail, attrEnd) {
|
|
19841
19937
|
const dir = { x: base.x - tip2.x, y: base.y - tip2.y };
|
|
19842
|
-
const
|
|
19938
|
+
const end = attrEnd ?? (isTail ? "tail" : "head");
|
|
19939
|
+
const ops = arrowDrawOpsForEnd(e, end, tip2, dir, _SplineClipHelper.renderPenwidth(e));
|
|
19843
19940
|
if (isTail) e.info.tailArrowOps = ops;
|
|
19844
19941
|
else e.info.headArrowOps = ops;
|
|
19845
19942
|
}
|
|
@@ -33839,18 +33936,18 @@ function buildSubgraph(root, nodes, name) {
|
|
|
33839
33936
|
sg.edges = root.edges.filter((e) => ns.has(e.tail) && ns.has(e.head));
|
|
33840
33937
|
return sg;
|
|
33841
33938
|
}
|
|
33842
|
-
function ccomps(g,
|
|
33939
|
+
function ccomps(g, pfx) {
|
|
33843
33940
|
const visited = /* @__PURE__ */ new Set();
|
|
33844
33941
|
const result = [];
|
|
33845
33942
|
let idx = 0;
|
|
33846
33943
|
for (const n of g.nodes.values()) {
|
|
33847
33944
|
if (visited.has(n.name)) continue;
|
|
33848
33945
|
const comp = dfsCollect(g, n, visited);
|
|
33849
|
-
result.push(buildSubgraph(g, comp, `${
|
|
33946
|
+
result.push(buildSubgraph(g, comp, `${pfx}_${idx++}`));
|
|
33850
33947
|
}
|
|
33851
33948
|
return result;
|
|
33852
33949
|
}
|
|
33853
|
-
function pccomps(g,
|
|
33950
|
+
function pccomps(g, pfx) {
|
|
33854
33951
|
const visited = /* @__PURE__ */ new Set();
|
|
33855
33952
|
const result = [];
|
|
33856
33953
|
let idx = 0;
|
|
@@ -33862,12 +33959,12 @@ function pccomps(g, pfx2) {
|
|
|
33862
33959
|
pinned = true;
|
|
33863
33960
|
}
|
|
33864
33961
|
if (pinnedNodes.length > 0) {
|
|
33865
|
-
result.push(buildSubgraph(g, pinnedNodes, `${
|
|
33962
|
+
result.push(buildSubgraph(g, pinnedNodes, `${pfx}_${idx++}`));
|
|
33866
33963
|
}
|
|
33867
33964
|
for (const n of g.nodes.values()) {
|
|
33868
33965
|
if (visited.has(n.name)) continue;
|
|
33869
33966
|
const comp = dfsCollect(g, n, visited);
|
|
33870
|
-
result.push(buildSubgraph(g, comp, `${
|
|
33967
|
+
result.push(buildSubgraph(g, comp, `${pfx}_${idx++}`));
|
|
33871
33968
|
}
|
|
33872
33969
|
return { graphs: result, pinned };
|
|
33873
33970
|
}
|
|
@@ -34141,12 +34238,6 @@ function setEdgeTypeFromAttr(g, defaultValue) {
|
|
|
34141
34238
|
else et = edgeTypeFromString(s, defaultValue);
|
|
34142
34239
|
setEdgeType(g, et);
|
|
34143
34240
|
}
|
|
34144
|
-
function getAttrInt(g, key, defaultVal) {
|
|
34145
|
-
const v = g.attrs.get(key);
|
|
34146
|
-
if (v === void 0 || v === "") return defaultVal;
|
|
34147
|
-
const n = parseInt(v, 10);
|
|
34148
|
-
return isNaN(n) ? defaultVal : n;
|
|
34149
|
-
}
|
|
34150
34241
|
function dotPhaseInit(g) {
|
|
34151
34242
|
dotGraphInit(g);
|
|
34152
34243
|
setEdgeTypeFromAttr(g, EDGETYPE_SPLINE);
|
|
@@ -34164,17 +34255,33 @@ function dotPhasePost(g) {
|
|
|
34164
34255
|
dotPhasePostNoFinish(g);
|
|
34165
34256
|
gvPostprocess(g);
|
|
34166
34257
|
}
|
|
34258
|
+
function graphPhase(g) {
|
|
34259
|
+
const v = g.attrs.get("phase");
|
|
34260
|
+
if (v === void 0 || v === "") return -1;
|
|
34261
|
+
const n = parseInt(v, 10);
|
|
34262
|
+
if (isNaN(n) || n > 2147483647) return -1;
|
|
34263
|
+
return n < 1 ? 1 : n;
|
|
34264
|
+
}
|
|
34167
34265
|
function dotLayoutPipeline(g) {
|
|
34168
|
-
const maxphase =
|
|
34266
|
+
const maxphase = graphPhase(g);
|
|
34169
34267
|
dotPhaseInit(g);
|
|
34170
34268
|
dotRank(g);
|
|
34171
|
-
if (maxphase === 1)
|
|
34269
|
+
if (maxphase === 1) {
|
|
34270
|
+
gvPostprocess(g);
|
|
34271
|
+
return;
|
|
34272
|
+
}
|
|
34172
34273
|
const rc = dotMincross(g);
|
|
34173
34274
|
if (rc !== 0) return;
|
|
34174
|
-
if (maxphase === 2)
|
|
34275
|
+
if (maxphase === 2) {
|
|
34276
|
+
gvPostprocess(g);
|
|
34277
|
+
return;
|
|
34278
|
+
}
|
|
34175
34279
|
const posRc = dotPosition(g);
|
|
34176
34280
|
if (posRc !== 0) return;
|
|
34177
|
-
if (maxphase === 3)
|
|
34281
|
+
if (maxphase === 3) {
|
|
34282
|
+
gvPostprocess(g);
|
|
34283
|
+
return;
|
|
34284
|
+
}
|
|
34178
34285
|
dotPhasePost(g);
|
|
34179
34286
|
}
|
|
34180
34287
|
var CL_OFFSET2 = 8;
|
|
@@ -40762,6 +40869,11 @@ function tuneControl(g, ctrl) {
|
|
|
40762
40869
|
ctrl.beautifyLeaves = mapBool(aggetGraph(g, "beautify"), false);
|
|
40763
40870
|
ctrl.doShrinking = mapBool(aggetGraph(g, "overlap_shrink"), true);
|
|
40764
40871
|
ctrl.rotation = lateDouble(aggetGraph(g, "rotation"), 0, -Number.MAX_VALUE);
|
|
40872
|
+
if (ctrl.rotation !== 0) {
|
|
40873
|
+
throw new Error(
|
|
40874
|
+
`sfdp rotation="${ctrl.rotation}": rotate() is not ported (unreachable at sfdp defaults); see plans/port-catalog/README.md`
|
|
40875
|
+
);
|
|
40876
|
+
}
|
|
40765
40877
|
ctrl.edgeLabelingScheme = lateInt(aggetGraph(g, "label_scheme"), 0, 0);
|
|
40766
40878
|
if (ctrl.edgeLabelingScheme > 4) ctrl.edgeLabelingScheme = 0;
|
|
40767
40879
|
}
|
|
@@ -46197,256 +46309,461 @@ function taper(bez, radfunc, initwid) {
|
|
|
46197
46309
|
return emitStroke(pp);
|
|
46198
46310
|
}
|
|
46199
46311
|
|
|
46200
|
-
// src/render/
|
|
46201
|
-
var
|
|
46202
|
-
|
|
46203
|
-
|
|
46204
|
-
|
|
46205
|
-
|
|
46206
|
-
|
|
46207
|
-
|
|
46208
|
-
|
|
46209
|
-
|
|
46210
|
-
|
|
46211
|
-
|
|
46212
|
-
|
|
46213
|
-
|
|
46214
|
-
|
|
46215
|
-
|
|
46216
|
-
|
|
46217
|
-
const dot = s.indexOf(".");
|
|
46218
|
-
if (dot < 0) return s;
|
|
46219
|
-
let end = s.length;
|
|
46220
|
-
while (end > dot + 1 && s[end - 1] === "0") end--;
|
|
46221
|
-
if (s[end - 1] === ".") end--;
|
|
46222
|
-
const t = s.slice(0, end);
|
|
46223
|
-
if (t.startsWith("0.")) return t.slice(1);
|
|
46224
|
-
if (t.startsWith("-0.")) return "-" + t.slice(2);
|
|
46225
|
-
return t;
|
|
46226
|
-
}
|
|
46227
|
-
function printNum(n) {
|
|
46228
|
-
if (Math.abs(n) > MAX_NEGNUM) {
|
|
46229
|
-
return n < 0 ? String(-MAX_NEGNUM) : String(MAX_NEGNUM);
|
|
46312
|
+
// src/render/map.ts
|
|
46313
|
+
var DQ = '"';
|
|
46314
|
+
function exactDecimalDigits2(v) {
|
|
46315
|
+
const dv = new DataView(new ArrayBuffer(8));
|
|
46316
|
+
dv.setFloat64(0, Math.abs(v));
|
|
46317
|
+
const bits = dv.getBigUint64(0);
|
|
46318
|
+
const expBits = Number(bits >> 52n & 0x7ffn);
|
|
46319
|
+
const mant = bits & 0xfffffffffffffn;
|
|
46320
|
+
const m = expBits === 0 ? mant : mant | 1n << 52n;
|
|
46321
|
+
const e = (expBits === 0 ? 1 : expBits) - 1075;
|
|
46322
|
+
let intDigits;
|
|
46323
|
+
let fracLen = 0;
|
|
46324
|
+
if (e >= 0) {
|
|
46325
|
+
intDigits = (m << BigInt(e)).toString();
|
|
46326
|
+
} else {
|
|
46327
|
+
intDigits = (m * 5n ** BigInt(-e)).toString();
|
|
46328
|
+
fracLen = -e;
|
|
46230
46329
|
}
|
|
46231
|
-
if (
|
|
46232
|
-
|
|
46233
|
-
}
|
|
46234
|
-
function makeXbufs() {
|
|
46235
|
-
const bufs = Array.from({ length: NUM_XBUFS + 4 }, () => []);
|
|
46236
|
-
bufs[8 /* NDraw */] = bufs[1 /* CDraw */];
|
|
46237
|
-
bufs[9 /* EDraw */] = bufs[1 /* CDraw */];
|
|
46238
|
-
bufs[10 /* NLabel */] = bufs[5 /* CLabel */];
|
|
46239
|
-
bufs[11 /* ELabel */] = bufs[5 /* CLabel */];
|
|
46240
|
-
return bufs;
|
|
46241
|
-
}
|
|
46242
|
-
function xdotNum(v) {
|
|
46243
|
-
let s = toFixed2HalfEven(v);
|
|
46244
|
-
if (s.indexOf(".") >= 0) {
|
|
46245
|
-
let end = s.length;
|
|
46246
|
-
while (end > 0 && s[end - 1] === "0") end--;
|
|
46247
|
-
if (s[end - 1] === ".") end--;
|
|
46248
|
-
s = s.slice(0, end);
|
|
46330
|
+
if (intDigits.length <= fracLen) {
|
|
46331
|
+
intDigits = "0".repeat(fracLen - intDigits.length + 1) + intDigits;
|
|
46249
46332
|
}
|
|
46250
|
-
|
|
46251
|
-
|
|
46252
|
-
|
|
46253
|
-
return xdotNum(p.x) + " " + xdotNum(p.y) + " ";
|
|
46254
|
-
}
|
|
46255
|
-
function xdotPoints(c, pts) {
|
|
46256
|
-
let s = c + " " + String(pts.length) + " ";
|
|
46257
|
-
for (const p of pts) s += xdotPoint(p);
|
|
46258
|
-
return s;
|
|
46333
|
+
const pointPos = intDigits.length - fracLen;
|
|
46334
|
+
const firstSig = intDigits.search(/[1-9]/);
|
|
46335
|
+
return { digits: intDigits.slice(firstSig), exp10: pointPos - firstSig - 1 };
|
|
46259
46336
|
}
|
|
46260
|
-
function
|
|
46261
|
-
|
|
46262
|
-
|
|
46263
|
-
|
|
46264
|
-
|
|
46265
|
-
|
|
46266
|
-
|
|
46267
|
-
|
|
46268
|
-
|
|
46269
|
-
|
|
46337
|
+
function printG5(v) {
|
|
46338
|
+
if (v === 0 || !Number.isFinite(v)) return String(v);
|
|
46339
|
+
const P = 5;
|
|
46340
|
+
const { digits, exp10 } = exactDecimalDigits2(v);
|
|
46341
|
+
let sig = digits.slice(0, P);
|
|
46342
|
+
if (sig.length < P) sig += "0".repeat(P - sig.length);
|
|
46343
|
+
const rest = digits.slice(P);
|
|
46344
|
+
const restNonzeroAfterFirst = /[1-9]/.test(rest.slice(1));
|
|
46345
|
+
const roundUp = rest.length > 0 && (rest[0] > "5" || rest[0] === "5" && restNonzeroAfterFirst || rest[0] === "5" && !restNonzeroAfterFirst && (sig.charCodeAt(P - 1) - 48) % 2 === 1);
|
|
46346
|
+
let exp = exp10;
|
|
46347
|
+
if (roundUp) {
|
|
46348
|
+
const n = String(BigInt(sig) + 1n);
|
|
46349
|
+
sig = n.length > P ? (exp++, n.slice(0, P)) : n;
|
|
46270
46350
|
}
|
|
46271
|
-
|
|
46272
|
-
|
|
46273
|
-
|
|
46274
|
-
|
|
46275
|
-
|
|
46276
|
-
|
|
46277
|
-
|
|
46278
|
-
if (
|
|
46279
|
-
|
|
46280
|
-
|
|
46281
|
-
|
|
46282
|
-
}
|
|
46283
|
-
|
|
46284
|
-
|
|
46285
|
-
|
|
46286
|
-
return
|
|
46351
|
+
const sign2 = v < 0 ? "-" : "";
|
|
46352
|
+
if (exp < -4 || exp >= P) {
|
|
46353
|
+
const mant = (sig[0] + "." + sig.slice(1)).replace(/\.?0+$/, "");
|
|
46354
|
+
const as = Math.abs(exp);
|
|
46355
|
+
return sign2 + mant + "e" + (exp < 0 ? "-" : "+") + (as < 10 ? "0" : "") + String(as);
|
|
46356
|
+
}
|
|
46357
|
+
let s;
|
|
46358
|
+
if (exp >= sig.length - 1) {
|
|
46359
|
+
s = sig + "0".repeat(exp - sig.length + 1);
|
|
46360
|
+
} else if (exp >= 0) {
|
|
46361
|
+
s = sig.slice(0, exp + 1) + "." + sig.slice(exp + 1);
|
|
46362
|
+
} else {
|
|
46363
|
+
s = "0." + "0".repeat(-exp - 1) + sig;
|
|
46364
|
+
}
|
|
46365
|
+
if (s.includes(".")) s = s.replace(/\.?0+$/, "");
|
|
46366
|
+
return sign2 + s;
|
|
46287
46367
|
}
|
|
46288
|
-
function
|
|
46289
|
-
return
|
|
46368
|
+
function plainCoord(v) {
|
|
46369
|
+
return printG5(v / 72);
|
|
46290
46370
|
}
|
|
46291
|
-
|
|
46292
|
-
|
|
46293
|
-
|
|
46294
|
-
return
|
|
46371
|
+
var CANON_KEYWORDS = ["node", "edge", "strict", "graph", "digraph", "subgraph"];
|
|
46372
|
+
var MAX_OUTPUTLINE = 128;
|
|
46373
|
+
function isDigitByte(b) {
|
|
46374
|
+
return b >= 48 && b <= 57;
|
|
46295
46375
|
}
|
|
46296
|
-
function
|
|
46297
|
-
|
|
46298
|
-
const stop = gvColorRgba(stopColor);
|
|
46299
|
-
const stops = frac > 0 ? [[frac, fill], [frac, stop]] : [[0, fill], [1, stop]];
|
|
46300
|
-
return stops.map(([f, c]) => trimFixed3(f) + " " + xdotColorBody(c)).join(" ");
|
|
46376
|
+
function isAlnumByte(b) {
|
|
46377
|
+
return isDigitByte(b) || b >= 65 && b <= 90 || b >= 97 && b <= 122;
|
|
46301
46378
|
}
|
|
46302
|
-
function
|
|
46303
|
-
|
|
46304
|
-
const gp = getGradientPoints(pts, rad2, true, true);
|
|
46305
|
-
const cx = gp.g0.x;
|
|
46306
|
-
const cy = gp.g0.y;
|
|
46307
|
-
const r1 = gp.g1.x;
|
|
46308
|
-
const r2 = gp.g1.y;
|
|
46309
|
-
const c1x = angleDeg === 0 ? cx : cx + r1 * Math.cos(rad2);
|
|
46310
|
-
const c1y = angleDeg === 0 ? cy : cy + r1 * Math.sin(rad2);
|
|
46311
|
-
const inner = "(" + xdotNum(c1x) + " " + xdotNum(c1y) + " " + xdotNum(r1) + " " + xdotNum(cx) + " " + xdotNum(cy) + " " + xdotNum(r2) + " 2 " + gradientStops(fillColor, stopColor, frac) + ")";
|
|
46312
|
-
return "C " + String(utf8Len(inner)) + " -" + inner + " ";
|
|
46379
|
+
function isIdCharByte(b) {
|
|
46380
|
+
return isAlnumByte(b) || b === 46 || b === 45 || b >= 128;
|
|
46313
46381
|
}
|
|
46314
|
-
function
|
|
46315
|
-
|
|
46382
|
+
function isEscapeAt(bytes, i) {
|
|
46383
|
+
if (bytes[i] !== 92) return false;
|
|
46384
|
+
const c = bytes[i + 1];
|
|
46385
|
+
return c !== void 0 && 'EGHLNTlnr\\"'.includes(String.fromCharCode(c));
|
|
46316
46386
|
}
|
|
46317
|
-
function
|
|
46318
|
-
|
|
46387
|
+
function agstrcanon(arg) {
|
|
46388
|
+
if (isHtmlValue(arg)) return "<" + htmlValueContent(arg) + ">";
|
|
46389
|
+
return agstrcanonText(arg);
|
|
46319
46390
|
}
|
|
46320
|
-
function
|
|
46321
|
-
|
|
46391
|
+
function agstrcanonText(arg) {
|
|
46392
|
+
if (arg.length === 0) return '""';
|
|
46393
|
+
const bytes = new TextEncoder().encode(arg);
|
|
46394
|
+
const out = [34];
|
|
46395
|
+
let needsQuotes = false;
|
|
46396
|
+
let partOfEscape = false;
|
|
46397
|
+
let backslashPending = false;
|
|
46398
|
+
let cnt = 0;
|
|
46399
|
+
let dotcnt = 0;
|
|
46400
|
+
let maybeNum = isDigitByte(bytes[0]) || bytes[0] === 46 || bytes[0] === 45;
|
|
46401
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
46402
|
+
const uc = bytes[i];
|
|
46403
|
+
if (uc === 34 && !partOfEscape) {
|
|
46404
|
+
out.push(92);
|
|
46405
|
+
needsQuotes = true;
|
|
46406
|
+
} else if (!partOfEscape && isEscapeAt(bytes, i)) {
|
|
46407
|
+
needsQuotes = true;
|
|
46408
|
+
partOfEscape = true;
|
|
46409
|
+
} else if (maybeNum) {
|
|
46410
|
+
if (uc === 45) {
|
|
46411
|
+
if (cnt) {
|
|
46412
|
+
maybeNum = false;
|
|
46413
|
+
needsQuotes = true;
|
|
46414
|
+
}
|
|
46415
|
+
} else if (uc === 46) {
|
|
46416
|
+
if (dotcnt++) {
|
|
46417
|
+
maybeNum = false;
|
|
46418
|
+
needsQuotes = true;
|
|
46419
|
+
}
|
|
46420
|
+
} else if (!isDigitByte(uc)) {
|
|
46421
|
+
maybeNum = false;
|
|
46422
|
+
needsQuotes = true;
|
|
46423
|
+
}
|
|
46424
|
+
partOfEscape = false;
|
|
46425
|
+
} else if (!(isAlnumByte(uc) || uc === 95 || uc >= 128)) {
|
|
46426
|
+
needsQuotes = true;
|
|
46427
|
+
partOfEscape = false;
|
|
46428
|
+
} else {
|
|
46429
|
+
partOfEscape = false;
|
|
46430
|
+
}
|
|
46431
|
+
out.push(uc);
|
|
46432
|
+
cnt++;
|
|
46433
|
+
const next = bytes[i + 1];
|
|
46434
|
+
if (next !== void 0) {
|
|
46435
|
+
const last = out[out.length - 1];
|
|
46436
|
+
const canBreak = !(isIdCharByte(last) || last === 92) && isIdCharByte(next);
|
|
46437
|
+
if (backslashPending && canBreak) {
|
|
46438
|
+
out.push(92, 10);
|
|
46439
|
+
needsQuotes = true;
|
|
46440
|
+
backslashPending = false;
|
|
46441
|
+
cnt = 0;
|
|
46442
|
+
} else if (cnt >= MAX_OUTPUTLINE) {
|
|
46443
|
+
if (canBreak) {
|
|
46444
|
+
out.push(92, 10);
|
|
46445
|
+
needsQuotes = true;
|
|
46446
|
+
cnt = 0;
|
|
46447
|
+
} else {
|
|
46448
|
+
backslashPending = true;
|
|
46449
|
+
}
|
|
46450
|
+
}
|
|
46451
|
+
}
|
|
46452
|
+
}
|
|
46453
|
+
out.push(34);
|
|
46454
|
+
const first = bytes[0];
|
|
46455
|
+
if (needsQuotes || cnt === 1 && (first === 46 || first === 45)) {
|
|
46456
|
+
return new TextDecoder().decode(new Uint8Array(out));
|
|
46457
|
+
}
|
|
46458
|
+
const lower = arg.toLowerCase();
|
|
46459
|
+
for (const tok of CANON_KEYWORDS) {
|
|
46460
|
+
if (tok === lower) return new TextDecoder().decode(new Uint8Array(out));
|
|
46461
|
+
}
|
|
46462
|
+
return arg;
|
|
46322
46463
|
}
|
|
46323
|
-
function
|
|
46324
|
-
return
|
|
46464
|
+
function lateNN(v, def) {
|
|
46465
|
+
return v !== void 0 && v !== "" ? v : def;
|
|
46325
46466
|
}
|
|
46326
|
-
function
|
|
46327
|
-
|
|
46328
|
-
if (
|
|
46329
|
-
return
|
|
46467
|
+
function plainNodeFill(n, g) {
|
|
46468
|
+
const fillcolor = lateNN(nodeAttr(n, g, "fillcolor"), "");
|
|
46469
|
+
if (fillcolor !== "") return fillcolor;
|
|
46470
|
+
return lateNN(nodeAttr(n, g, "color"), "lightgrey");
|
|
46330
46471
|
}
|
|
46331
|
-
function
|
|
46332
|
-
|
|
46333
|
-
|
|
46334
|
-
|
|
46335
|
-
|
|
46336
|
-
|
|
46337
|
-
let mant = s.slice(0, e);
|
|
46338
|
-
if (mant.indexOf(".") >= 0) mant = mant.replace(/0+$/, "").replace(/\.$/, "");
|
|
46339
|
-
const ei = parseInt(s.slice(e + 1), 10);
|
|
46340
|
-
return mant + "e" + (ei < 0 ? "-" : "+") + String(Math.abs(ei)).padStart(2, "0");
|
|
46472
|
+
function plainNodeLabel(n, g) {
|
|
46473
|
+
const lbl = n.info.label;
|
|
46474
|
+
const attr2 = nodeAttr(n, g, "label");
|
|
46475
|
+
if (lbl !== void 0 && lbl.u.kind === "html") {
|
|
46476
|
+
const a = attr2 ?? "";
|
|
46477
|
+
return "<" + (isHtmlValue(a) ? htmlValueContent(a) : a) + ">";
|
|
46341
46478
|
}
|
|
46342
|
-
if (
|
|
46343
|
-
|
|
46344
|
-
}
|
|
46345
|
-
|
|
46346
|
-
|
|
46479
|
+
if (attr2 !== void 0 && isHtmlValue(attr2) && lbl !== void 0 && lbl.u.kind === "txt") {
|
|
46480
|
+
return agstrcanonText(htmlValueContent(attr2));
|
|
46481
|
+
}
|
|
46482
|
+
const shape = n.info.shape;
|
|
46483
|
+
if (shape !== void 0 && shape.kind === 2 /* SH_RECORD */) {
|
|
46484
|
+
return agstrcanonText(nodeAttr(n, g, "label") ?? "\\N");
|
|
46485
|
+
}
|
|
46486
|
+
const text = lbl !== void 0 ? lbl.text : n.attrs.get("label") ?? n.name;
|
|
46487
|
+
return agstrcanonText(text);
|
|
46347
46488
|
}
|
|
46348
|
-
function
|
|
46349
|
-
|
|
46489
|
+
function plainNodeAttrs(n, g) {
|
|
46490
|
+
const shapeAttr = lateNN(nodeAttr(n, g, "shape"), "ellipse");
|
|
46491
|
+
const shapefile = nodeAttr(n, g, "shapefile");
|
|
46492
|
+
const shapeName = shapefile !== void 0 && shapefile !== "" && shapeAttr !== "epsf" ? "custom" : shapeAttr;
|
|
46493
|
+
return {
|
|
46494
|
+
label: plainNodeLabel(n, g),
|
|
46495
|
+
style: lateNN(nodeAttr(n, g, "style"), "solid"),
|
|
46496
|
+
shape: shapeName,
|
|
46497
|
+
color: lateNN(nodeAttr(n, g, "color"), "black"),
|
|
46498
|
+
fill: plainNodeFill(n, g)
|
|
46499
|
+
};
|
|
46350
46500
|
}
|
|
46351
|
-
function
|
|
46352
|
-
|
|
46501
|
+
function writePlainNode(n, g, out) {
|
|
46502
|
+
const x = plainCoord(n.info.coord.x);
|
|
46503
|
+
const y = plainCoord(n.info.coord.y);
|
|
46504
|
+
const w = printG5(n.info.width);
|
|
46505
|
+
const h = printG5(n.info.height);
|
|
46506
|
+
const a = plainNodeAttrs(n, g);
|
|
46507
|
+
out.push("node " + agstrcanonText(n.name) + " " + x + " " + y + " " + w + " " + h + " " + a.label + " " + a.style + " " + a.shape + " " + a.color + " " + a.fill + "\n");
|
|
46353
46508
|
}
|
|
46354
|
-
function
|
|
46355
|
-
|
|
46356
|
-
|
|
46357
|
-
|
|
46358
|
-
|
|
46359
|
-
);
|
|
46509
|
+
function collectSplinePts(e) {
|
|
46510
|
+
if (!e.info.spl) return [];
|
|
46511
|
+
const pts = [];
|
|
46512
|
+
for (const bz of e.info.spl.list) {
|
|
46513
|
+
for (let i = 0; i < bz.size; i++) pts.push(bz.list[i]);
|
|
46360
46514
|
}
|
|
46361
|
-
|
|
46515
|
+
return pts;
|
|
46362
46516
|
}
|
|
46363
|
-
function
|
|
46364
|
-
|
|
46365
|
-
if (
|
|
46366
|
-
|
|
46517
|
+
function writeNodeAndPort(n, portname, out) {
|
|
46518
|
+
let name = n.name;
|
|
46519
|
+
if (n.info.clustnode) {
|
|
46520
|
+
const i = name.indexOf(":");
|
|
46521
|
+
if (i >= 0) name = name.slice(i + 1);
|
|
46522
|
+
}
|
|
46523
|
+
out.push(" " + agstrcanonText(name));
|
|
46524
|
+
if (portname !== "") out.push(":" + agstrcanonText(portname));
|
|
46367
46525
|
}
|
|
46368
|
-
function
|
|
46369
|
-
|
|
46526
|
+
function writePlainEdgeHead(e, tport, hport, pts, out) {
|
|
46527
|
+
out.push("edge");
|
|
46528
|
+
writeNodeAndPort(e.tail, tport, out);
|
|
46529
|
+
writeNodeAndPort(e.head, hport, out);
|
|
46530
|
+
out.push(" " + String(pts.length));
|
|
46531
|
+
for (const pt of pts) {
|
|
46532
|
+
out.push(" " + plainCoord(pt.x) + " " + plainCoord(pt.y));
|
|
46533
|
+
}
|
|
46370
46534
|
}
|
|
46371
|
-
function
|
|
46372
|
-
const
|
|
46373
|
-
|
|
46374
|
-
|
|
46375
|
-
|
|
46376
|
-
|
|
46377
|
-
|
|
46378
|
-
|
|
46535
|
+
function writePlainEdge(e, extend, out) {
|
|
46536
|
+
const tport = extend ? e.attrs.get("tailport") ?? "" : "";
|
|
46537
|
+
const hport = extend ? e.attrs.get("headport") ?? "" : "";
|
|
46538
|
+
const pts = collectSplinePts(e);
|
|
46539
|
+
if (pts.length > 0) writePlainEdgeHead(e, tport, hport, pts, out);
|
|
46540
|
+
const lbl = e.info.label;
|
|
46541
|
+
if (lbl !== void 0) {
|
|
46542
|
+
out.push(" " + agstrcanonText(lbl.text) + " " + plainCoord(lbl.pos.x) + " " + plainCoord(lbl.pos.y));
|
|
46379
46543
|
}
|
|
46380
|
-
|
|
46381
|
-
|
|
46382
|
-
|
|
46383
|
-
"lheight=" + gfmt2(label.dimen.y / POINTS_PER_INCH)
|
|
46384
|
-
];
|
|
46544
|
+
const style = lateNN(e.attrs.get("style"), "solid");
|
|
46545
|
+
const color = lateNN(e.attrs.get("color"), "black");
|
|
46546
|
+
out.push(" " + style + " " + color + "\n");
|
|
46385
46547
|
}
|
|
46386
|
-
function
|
|
46387
|
-
const
|
|
46388
|
-
const
|
|
46389
|
-
const
|
|
46390
|
-
|
|
46391
|
-
|
|
46392
|
-
|
|
46393
|
-
|
|
46394
|
-
|
|
46395
|
-
|
|
46548
|
+
function writePlain(g, job, extend) {
|
|
46549
|
+
const w = plainCoord(g.info.bb.ur.x);
|
|
46550
|
+
const h = plainCoord(g.info.bb.ur.y);
|
|
46551
|
+
const zoom = initJobViewportZoom(
|
|
46552
|
+
job.bb,
|
|
46553
|
+
parseDrawingSize(g.attrs.get("size")),
|
|
46554
|
+
{ x: 0, y: 0 }
|
|
46555
|
+
);
|
|
46556
|
+
job.write("graph " + printG5(zoom) + " " + w + " " + h + "\n");
|
|
46557
|
+
const nodes = nodesInSeq(g);
|
|
46558
|
+
for (const n of nodes) {
|
|
46559
|
+
if (n.info.clustnode) continue;
|
|
46560
|
+
const buf = [];
|
|
46561
|
+
writePlainNode(n, g, buf);
|
|
46562
|
+
job.write(buf.join(""));
|
|
46563
|
+
}
|
|
46564
|
+
for (const n of nodes) {
|
|
46565
|
+
for (const e of n.outEdges(g)) {
|
|
46566
|
+
const buf = [];
|
|
46567
|
+
writePlainEdge(e, extend, buf);
|
|
46568
|
+
job.write(buf.join(""));
|
|
46396
46569
|
}
|
|
46397
|
-
});
|
|
46398
|
-
return parts.join(" ");
|
|
46399
|
-
}
|
|
46400
|
-
function formatEdgePos(e) {
|
|
46401
|
-
const spl = e.info.spl;
|
|
46402
|
-
if (!spl || spl.list.length === 0) return "";
|
|
46403
|
-
if (e.info.edge_type === IGNORED) return "";
|
|
46404
|
-
const parts = [];
|
|
46405
|
-
for (const bez of spl.list) {
|
|
46406
|
-
if (bez.sflag) parts.push("s," + gfmt5(bez.sp.x) + "," + gfmt5(bez.sp.y));
|
|
46407
|
-
if (bez.eflag) parts.push("e," + gfmt5(bez.ep.x) + "," + gfmt5(bez.ep.y));
|
|
46408
|
-
const pts = bez.list.slice(0, bez.size);
|
|
46409
|
-
for (const p of pts) parts.push(gfmt5(p.x) + "," + gfmt5(p.y));
|
|
46410
46570
|
}
|
|
46411
|
-
|
|
46412
|
-
}
|
|
46413
|
-
function edgeConnector(directed) {
|
|
46414
|
-
return directed ? "->" : "--";
|
|
46571
|
+
job.write("stop\n");
|
|
46415
46572
|
}
|
|
46416
|
-
function
|
|
46417
|
-
return
|
|
46573
|
+
function cmapxObjAnchor(obj) {
|
|
46574
|
+
return {
|
|
46575
|
+
url: obj.url ?? "",
|
|
46576
|
+
tooltip: obj.tooltip ?? "",
|
|
46577
|
+
target: obj.target ?? "",
|
|
46578
|
+
id: obj.id ?? ""
|
|
46579
|
+
};
|
|
46418
46580
|
}
|
|
46419
|
-
function
|
|
46420
|
-
const
|
|
46421
|
-
|
|
46422
|
-
|
|
46423
|
-
const bbStr = printNum(bb.ll.x) + "," + printNum(bb.ll.y) + "," + printNum(bb.ur.x) + "," + printNum(bb.ur.y);
|
|
46424
|
-
job.write(' graph [bb="' + bbStr + '"];\n');
|
|
46581
|
+
function writeCmapxGraphShape(job, out) {
|
|
46582
|
+
const obj = job.obj;
|
|
46583
|
+
if (!obj || !obj.urlMapPts.length) return;
|
|
46584
|
+
mapOutputCmapx(obj.urlMapShape, obj.urlMapPts, cmapxObjAnchor(obj), true, out);
|
|
46425
46585
|
}
|
|
46426
|
-
|
|
46427
|
-
|
|
46428
|
-
|
|
46586
|
+
function cmapxShape(shape) {
|
|
46587
|
+
if (shape === 1 /* Circle */) return "circle";
|
|
46588
|
+
if (shape === 0 /* Rectangle */) return "rect";
|
|
46589
|
+
return "poly";
|
|
46590
|
+
}
|
|
46591
|
+
function cmapxCoordsRect(pts) {
|
|
46592
|
+
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));
|
|
46593
|
+
}
|
|
46594
|
+
function cmapxCoordsCircle(pts) {
|
|
46595
|
+
const cx = Math.round(pts[0].x);
|
|
46596
|
+
const cy = Math.round(pts[0].y);
|
|
46597
|
+
const r = Math.round(pts[1].x - pts[0].x);
|
|
46598
|
+
return String(cx) + "," + String(cy) + "," + String(r);
|
|
46599
|
+
}
|
|
46600
|
+
function cmapxCoordsPoly(pts) {
|
|
46601
|
+
return pts.map((p) => String(Math.round(p.x)) + "," + String(Math.round(p.y))).join(",");
|
|
46602
|
+
}
|
|
46603
|
+
function cmapxCoords(shape, pts) {
|
|
46604
|
+
if (shape === 1 /* Circle */) return cmapxCoordsCircle(pts);
|
|
46605
|
+
if (shape === 0 /* Rectangle */) return cmapxCoordsRect(pts);
|
|
46606
|
+
return cmapxCoordsPoly(pts);
|
|
46607
|
+
}
|
|
46608
|
+
function mapCmapxAttrs(a, out) {
|
|
46609
|
+
if (a.id) out.push(" id=" + DQ + escapeXml(a.id) + DQ);
|
|
46610
|
+
if (a.url) out.push(" href=" + DQ + escapeXml(a.url) + DQ);
|
|
46611
|
+
if (a.target) out.push(" target=" + DQ + escapeXmlTitle(a.target) + DQ);
|
|
46612
|
+
if (a.tooltip) out.push(" title=" + DQ + escapeXmlTitle(a.tooltip) + DQ);
|
|
46613
|
+
}
|
|
46614
|
+
function mapOutputCmapx(shape, pts, a, isXml, out) {
|
|
46615
|
+
out.push("<area shape=" + DQ + cmapxShape(shape) + DQ);
|
|
46616
|
+
mapCmapxAttrs(a, out);
|
|
46617
|
+
out.push(" alt=" + DQ + DQ);
|
|
46618
|
+
out.push(" coords=" + DQ + cmapxCoords(shape, pts) + DQ);
|
|
46619
|
+
out.push(isXml ? "/>\n" : ">\n");
|
|
46620
|
+
}
|
|
46621
|
+
function mapOutputImap(shape, pts, url, out) {
|
|
46622
|
+
if (!url) return;
|
|
46623
|
+
if (shape === 0 /* Rectangle */) {
|
|
46624
|
+
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");
|
|
46625
|
+
} else if (shape === 1 /* Circle */) {
|
|
46626
|
+
const r = Math.round(pts[1].x - pts[0].x);
|
|
46627
|
+
out.push("circle " + url + " " + Math.round(pts[0].x) + "," + Math.round(pts[0].y) + "," + String(r) + "\n");
|
|
46628
|
+
} else {
|
|
46629
|
+
const pairs = pts.map((p) => Math.round(p.x) + "," + Math.round(p.y)).join(" ");
|
|
46630
|
+
out.push("poly " + url + " " + pairs + "\n");
|
|
46631
|
+
}
|
|
46632
|
+
}
|
|
46633
|
+
var PlainRenderer = class {
|
|
46634
|
+
type = "plain";
|
|
46429
46635
|
quality = 0;
|
|
46430
|
-
beginGraph(
|
|
46431
|
-
dotBeginGraphHeader(g, job);
|
|
46636
|
+
beginGraph(_g, _job) {
|
|
46432
46637
|
}
|
|
46433
|
-
endGraph(
|
|
46434
|
-
job
|
|
46638
|
+
endGraph(g, job) {
|
|
46639
|
+
writePlain(g, job, false);
|
|
46640
|
+
}
|
|
46641
|
+
beginNode(_n, _job) {
|
|
46642
|
+
}
|
|
46643
|
+
endNode(_n, _job) {
|
|
46644
|
+
}
|
|
46645
|
+
beginEdge(_e, _job) {
|
|
46646
|
+
}
|
|
46647
|
+
endEdge(_e, _job) {
|
|
46648
|
+
}
|
|
46649
|
+
textspan(_pos, _span, _job) {
|
|
46650
|
+
}
|
|
46651
|
+
ellipse(_c, _rx, _ry, _f, _j) {
|
|
46652
|
+
}
|
|
46653
|
+
polygon(_pts, _filled, _job) {
|
|
46654
|
+
}
|
|
46655
|
+
bezier(_pts, _filled, _job) {
|
|
46656
|
+
}
|
|
46657
|
+
polyline(_pts, _job) {
|
|
46658
|
+
}
|
|
46659
|
+
};
|
|
46660
|
+
var PlainExtRenderer = class {
|
|
46661
|
+
type = "plain-ext";
|
|
46662
|
+
quality = 0;
|
|
46663
|
+
beginGraph(_g, _job) {
|
|
46664
|
+
}
|
|
46665
|
+
endGraph(g, job) {
|
|
46666
|
+
writePlain(g, job, true);
|
|
46435
46667
|
}
|
|
46436
46668
|
beginNode(_n, _job) {
|
|
46437
46669
|
}
|
|
46438
|
-
endNode(
|
|
46439
|
-
const attrs = formatNodeAttrs(n);
|
|
46440
|
-
job.write(" " + n.name + " [" + attrs + "];\n");
|
|
46670
|
+
endNode(_n, _job) {
|
|
46441
46671
|
}
|
|
46442
46672
|
beginEdge(_e, _job) {
|
|
46443
46673
|
}
|
|
46674
|
+
endEdge(_e, _job) {
|
|
46675
|
+
}
|
|
46676
|
+
textspan(_pos, _span, _job) {
|
|
46677
|
+
}
|
|
46678
|
+
ellipse(_c, _rx, _ry, _f, _j) {
|
|
46679
|
+
}
|
|
46680
|
+
polygon(_pts, _filled, _job) {
|
|
46681
|
+
}
|
|
46682
|
+
bezier(_pts, _filled, _job) {
|
|
46683
|
+
}
|
|
46684
|
+
polyline(_pts, _job) {
|
|
46685
|
+
}
|
|
46686
|
+
};
|
|
46687
|
+
function mapGraphName(g) {
|
|
46688
|
+
return g.anonymous ? "%1" : g.name;
|
|
46689
|
+
}
|
|
46690
|
+
function graphAttr2(g, key) {
|
|
46691
|
+
for (let s = g; s !== null; s = s.parent) {
|
|
46692
|
+
const v = s.attrs.get(key);
|
|
46693
|
+
if (v !== void 0 && v !== "") return v;
|
|
46694
|
+
}
|
|
46695
|
+
return void 0;
|
|
46696
|
+
}
|
|
46697
|
+
function graphMapUrl(g) {
|
|
46698
|
+
const raw = graphAttr2(g, "href") ?? graphAttr2(g, "URL");
|
|
46699
|
+
return raw === void 0 ? null : substObjAnchor(raw, g);
|
|
46700
|
+
}
|
|
46701
|
+
function buildMapCtx(g, job, mapPolygon) {
|
|
46702
|
+
const drawingDpi = lateDouble(g.attrs.get("dpi"), lateDouble(g.attrs.get("resolution"), 0, 0), 0);
|
|
46703
|
+
const mapDpi = drawingDpi > 0 ? drawingDpi : 96;
|
|
46704
|
+
const devscale = mapDpi / POINTS_PER_INCH;
|
|
46705
|
+
const z = initJobViewportZoom(job.bb, parseDrawingSize(g.attrs.get("size")), job.pad);
|
|
46706
|
+
const marginOff = { x: job.margin.x * devscale, y: job.margin.y * devscale };
|
|
46707
|
+
return { bb: job.bb, pad: job.pad, scale: z * devscale, marginOff, mapPolygon };
|
|
46708
|
+
}
|
|
46709
|
+
function anchorOf(url, tooltip, target, id) {
|
|
46710
|
+
return { url: url ?? "", tooltip: tooltip ?? "", target: target ?? "", id: id ?? "" };
|
|
46711
|
+
}
|
|
46712
|
+
var MapRendererBase = class {
|
|
46713
|
+
quality = 0;
|
|
46714
|
+
mapCtx = null;
|
|
46715
|
+
beginGraph(g, job) {
|
|
46716
|
+
job.flags |= EMIT_CLUSTERS_LAST;
|
|
46717
|
+
this.mapCtx = buildMapCtx(g, job, this.mapPolygon);
|
|
46718
|
+
if (this.isCmapx) {
|
|
46719
|
+
const name = escapeXml(mapGraphName(g));
|
|
46720
|
+
job.write("<map id=" + DQ + name + DQ + " name=" + DQ + name + DQ + ">\n");
|
|
46721
|
+
return;
|
|
46722
|
+
}
|
|
46723
|
+
job.write("base referer\n");
|
|
46724
|
+
const url = graphMapUrl(g);
|
|
46725
|
+
if (url) job.write("default " + url + "\n");
|
|
46726
|
+
}
|
|
46727
|
+
endGraph(_g, job) {
|
|
46728
|
+
if (!this.isCmapx) return;
|
|
46729
|
+
const obj = job.obj;
|
|
46730
|
+
if (obj !== null && this.mapCtx !== null && (obj.url !== null || obj.explicitTooltip)) {
|
|
46731
|
+
computeGraphUrlMap(obj, this.mapCtx);
|
|
46732
|
+
}
|
|
46733
|
+
const buf = [];
|
|
46734
|
+
writeCmapxGraphShape(job, buf);
|
|
46735
|
+
job.write(buf.join(""));
|
|
46736
|
+
job.write("</map>\n");
|
|
46737
|
+
}
|
|
46738
|
+
beginNode(n, job) {
|
|
46739
|
+
if (this.mapCtx !== null && job.obj !== null) computeNodeUrlMap(n, job.obj, this.mapCtx);
|
|
46740
|
+
}
|
|
46741
|
+
beginCluster(sg, job) {
|
|
46742
|
+
const bb = sg.info.bb;
|
|
46743
|
+
if (this.mapCtx !== null && job.obj !== null && bb !== void 0) {
|
|
46744
|
+
computeClusterUrlMap(bb, job.obj, this.mapCtx);
|
|
46745
|
+
}
|
|
46746
|
+
}
|
|
46444
46747
|
endEdge(e, job) {
|
|
46445
|
-
|
|
46446
|
-
|
|
46447
|
-
|
|
46448
|
-
|
|
46449
|
-
|
|
46748
|
+
if (this.mapCtx !== null && job.obj !== null) this.emitEdge(e, job.obj, job);
|
|
46749
|
+
}
|
|
46750
|
+
/** C emit_map_rect: record the HTML table/cell box as the pending hot spot,
|
|
46751
|
+
* so the following beginAnchor emits its <area>. @see emit.c:640 */
|
|
46752
|
+
emitMapRect(box, job) {
|
|
46753
|
+
if (this.mapCtx !== null && job.obj !== null) {
|
|
46754
|
+
computeClusterUrlMap(box, job.obj, this.mapCtx);
|
|
46755
|
+
}
|
|
46756
|
+
}
|
|
46757
|
+
beginAnchor(url, tip2, target, id, job) {
|
|
46758
|
+
const obj = job.obj;
|
|
46759
|
+
if (obj === null || obj.urlMapPts.length === 0) return;
|
|
46760
|
+
this.emitShape(obj.urlMapShape, obj.urlMapPts, { url, tooltip: tip2, target, id }, job);
|
|
46761
|
+
}
|
|
46762
|
+
endNode(_n, _job) {
|
|
46763
|
+
}
|
|
46764
|
+
beginEdge(_e, _job) {
|
|
46765
|
+
}
|
|
46766
|
+
endCluster(_sg, _job) {
|
|
46450
46767
|
}
|
|
46451
46768
|
textspan(_pos, _span, _job) {
|
|
46452
46769
|
}
|
|
@@ -46458,7 +46775,247 @@ var DotRenderer = class {
|
|
|
46458
46775
|
}
|
|
46459
46776
|
polyline(_pts, _job) {
|
|
46460
46777
|
}
|
|
46778
|
+
/** Emit one hot spot: `<area>` (cmapx) or a plain `keyword url coords` line. */
|
|
46779
|
+
emitShape(shape, pts, a, job) {
|
|
46780
|
+
const buf = [];
|
|
46781
|
+
if (this.isCmapx) mapOutputCmapx(shape, pts, a, true, buf);
|
|
46782
|
+
else mapOutputImap(shape, pts, a.url, buf);
|
|
46783
|
+
job.write(buf.join(""));
|
|
46784
|
+
}
|
|
46785
|
+
/** Whole-edge spline outline(s) then center/head/tail label hot spots.
|
|
46786
|
+
* @see lib/common/emit.c:emit_begin_edge (2851-2872) / emit_end_edge */
|
|
46787
|
+
emitEdge(e, obj, job) {
|
|
46788
|
+
const spl = e.info.spl;
|
|
46789
|
+
const wholeEdge = obj.url !== null || obj.explicitTooltip;
|
|
46790
|
+
if (spl !== void 0 && this.mapPolygon && wholeEdge) {
|
|
46791
|
+
const w2 = Math.max(obj.penWidth / 2, 2);
|
|
46792
|
+
const anchor = anchorOf(obj.url, obj.tooltip, obj.target, obj.id);
|
|
46793
|
+
for (const poly of computeEdgeSplineMaps(spl, w2, this.mapCtx)) {
|
|
46794
|
+
this.emitShape(2 /* Polygon */, poly, anchor, job);
|
|
46795
|
+
}
|
|
46796
|
+
}
|
|
46797
|
+
const centerA = {
|
|
46798
|
+
url: obj.labelUrl,
|
|
46799
|
+
tooltip: obj.labelTooltip,
|
|
46800
|
+
target: obj.labelTarget,
|
|
46801
|
+
explicit: obj.explicitLabelTooltip
|
|
46802
|
+
};
|
|
46803
|
+
this.emitEdgeLabel(e.info.label, centerA, obj, job);
|
|
46804
|
+
this.emitEdgeLabel(e.info.xlabel, centerA, obj, job);
|
|
46805
|
+
this.emitEdgeLabel(
|
|
46806
|
+
e.info.head_label,
|
|
46807
|
+
{ url: obj.headUrl, tooltip: obj.headTooltip, target: obj.headTarget, explicit: obj.explicitHeadTooltip },
|
|
46808
|
+
obj,
|
|
46809
|
+
job
|
|
46810
|
+
);
|
|
46811
|
+
this.emitEdgeLabel(
|
|
46812
|
+
e.info.tail_label,
|
|
46813
|
+
{ url: obj.tailUrl, tooltip: obj.tailTooltip, target: obj.tailTarget, explicit: obj.explicitTailTooltip },
|
|
46814
|
+
obj,
|
|
46815
|
+
job
|
|
46816
|
+
);
|
|
46817
|
+
}
|
|
46818
|
+
/** One edge label hot spot (map_label rect), if placed and url/explicit-tip.
|
|
46819
|
+
* @see lib/common/emit.c:emit_edge_label */
|
|
46820
|
+
emitEdgeLabel(lab, la, obj, job) {
|
|
46821
|
+
const placed = lab !== void 0 && lab.set && this.mapCtx !== null;
|
|
46822
|
+
if (!placed || la.url === null && !la.explicit) return;
|
|
46823
|
+
computeLabelRectMap(lab, obj, this.mapCtx);
|
|
46824
|
+
this.emitShape(0 /* Rectangle */, obj.urlMapPts, anchorOf(la.url, la.tooltip, la.target, obj.id), job);
|
|
46825
|
+
}
|
|
46826
|
+
};
|
|
46827
|
+
var ImapRenderer = class extends MapRendererBase {
|
|
46828
|
+
type = "imap";
|
|
46829
|
+
isCmapx = false;
|
|
46830
|
+
mapPolygon = true;
|
|
46831
|
+
};
|
|
46832
|
+
var ImapNpRenderer = class extends MapRendererBase {
|
|
46833
|
+
type = "imap-np";
|
|
46834
|
+
isCmapx = false;
|
|
46835
|
+
mapPolygon = false;
|
|
46836
|
+
};
|
|
46837
|
+
var CmapxRenderer = class extends MapRendererBase {
|
|
46838
|
+
type = "cmapx";
|
|
46839
|
+
isCmapx = true;
|
|
46840
|
+
mapPolygon = true;
|
|
46841
|
+
};
|
|
46842
|
+
var CmapxNpRenderer = class extends MapRendererBase {
|
|
46843
|
+
type = "cmapx-np";
|
|
46844
|
+
isCmapx = true;
|
|
46845
|
+
mapPolygon = false;
|
|
46461
46846
|
};
|
|
46847
|
+
function createPlainRenderer() {
|
|
46848
|
+
return new PlainRenderer();
|
|
46849
|
+
}
|
|
46850
|
+
function createPlainExtRenderer() {
|
|
46851
|
+
return new PlainExtRenderer();
|
|
46852
|
+
}
|
|
46853
|
+
function createImapRenderer() {
|
|
46854
|
+
return new ImapRenderer();
|
|
46855
|
+
}
|
|
46856
|
+
function createImapNpRenderer() {
|
|
46857
|
+
return new ImapNpRenderer();
|
|
46858
|
+
}
|
|
46859
|
+
function createCmapxRenderer() {
|
|
46860
|
+
return new CmapxRenderer();
|
|
46861
|
+
}
|
|
46862
|
+
function createCmapxNpRenderer() {
|
|
46863
|
+
return new CmapxNpRenderer();
|
|
46864
|
+
}
|
|
46865
|
+
|
|
46866
|
+
// src/render/dot/xdot-ops.ts
|
|
46867
|
+
var XDOT_VERSION = "1.7";
|
|
46868
|
+
var MAX_NEGNUM = 1e15;
|
|
46869
|
+
var NEAR_ZERO = 5e-3;
|
|
46870
|
+
var NUM_XBUFS = 8;
|
|
46871
|
+
var NON_LINE_STYLES = /* @__PURE__ */ new Set([
|
|
46872
|
+
"filled",
|
|
46873
|
+
"bold",
|
|
46874
|
+
"rounded",
|
|
46875
|
+
"diagonals",
|
|
46876
|
+
"striped",
|
|
46877
|
+
"wedged",
|
|
46878
|
+
"invis",
|
|
46879
|
+
"invisible",
|
|
46880
|
+
"radial"
|
|
46881
|
+
]);
|
|
46882
|
+
function trimAndStrip(s) {
|
|
46883
|
+
const dot = s.indexOf(".");
|
|
46884
|
+
if (dot < 0) return s;
|
|
46885
|
+
let end = s.length;
|
|
46886
|
+
while (end > dot + 1 && s[end - 1] === "0") end--;
|
|
46887
|
+
if (s[end - 1] === ".") end--;
|
|
46888
|
+
const t = s.slice(0, end);
|
|
46889
|
+
if (t.startsWith("0.")) return t.slice(1);
|
|
46890
|
+
if (t.startsWith("-0.")) return "-" + t.slice(2);
|
|
46891
|
+
return t;
|
|
46892
|
+
}
|
|
46893
|
+
function printNum(n) {
|
|
46894
|
+
if (Math.abs(n) > MAX_NEGNUM) {
|
|
46895
|
+
return n < 0 ? String(-MAX_NEGNUM) : String(MAX_NEGNUM);
|
|
46896
|
+
}
|
|
46897
|
+
if (Math.abs(n) < NEAR_ZERO) return "0";
|
|
46898
|
+
return trimAndStrip(n.toFixed(3));
|
|
46899
|
+
}
|
|
46900
|
+
function makeXbufs() {
|
|
46901
|
+
const bufs = Array.from({ length: NUM_XBUFS + 4 }, () => []);
|
|
46902
|
+
bufs[8 /* NDraw */] = bufs[1 /* CDraw */];
|
|
46903
|
+
bufs[9 /* EDraw */] = bufs[1 /* CDraw */];
|
|
46904
|
+
bufs[10 /* NLabel */] = bufs[5 /* CLabel */];
|
|
46905
|
+
bufs[11 /* ELabel */] = bufs[5 /* CLabel */];
|
|
46906
|
+
return bufs;
|
|
46907
|
+
}
|
|
46908
|
+
function xdotNum(v) {
|
|
46909
|
+
let s = toFixed2HalfEven(v);
|
|
46910
|
+
if (s.indexOf(".") >= 0) {
|
|
46911
|
+
let end = s.length;
|
|
46912
|
+
while (end > 0 && s[end - 1] === "0") end--;
|
|
46913
|
+
if (s[end - 1] === ".") end--;
|
|
46914
|
+
s = s.slice(0, end);
|
|
46915
|
+
}
|
|
46916
|
+
return s === "-0" ? "0" : s;
|
|
46917
|
+
}
|
|
46918
|
+
function xdotPoint(p) {
|
|
46919
|
+
return xdotNum(p.x) + " " + xdotNum(p.y) + " ";
|
|
46920
|
+
}
|
|
46921
|
+
function xdotPoints(c, pts) {
|
|
46922
|
+
let s = c + " " + String(pts.length) + " ";
|
|
46923
|
+
for (const p of pts) s += xdotPoint(p);
|
|
46924
|
+
return s;
|
|
46925
|
+
}
|
|
46926
|
+
function utf8Len(s) {
|
|
46927
|
+
let n = 0;
|
|
46928
|
+
for (let i = 0; i < s.length; i++) {
|
|
46929
|
+
const c = s.charCodeAt(i);
|
|
46930
|
+
if (c < 128) n += 1;
|
|
46931
|
+
else if (c < 2048) n += 2;
|
|
46932
|
+
else if (c >= 55296 && c <= 56319) {
|
|
46933
|
+
n += 4;
|
|
46934
|
+
i++;
|
|
46935
|
+
} else n += 3;
|
|
46936
|
+
}
|
|
46937
|
+
return n;
|
|
46938
|
+
}
|
|
46939
|
+
function chanByte(v) {
|
|
46940
|
+
return Math.round(Math.max(0, Math.min(1, v)) * 255);
|
|
46941
|
+
}
|
|
46942
|
+
function gvColorRgba(c) {
|
|
46943
|
+
if (c.type === "rgba") return [chanByte(c.r), chanByte(c.g), chanByte(c.b), chanByte(c.a)];
|
|
46944
|
+
if (c.type === "none") return [255, 255, 254, 0];
|
|
46945
|
+
const out = { type: "rgba", r: 0, g: 0, b: 0, a: 0 };
|
|
46946
|
+
colorxlate(c.type === "string" ? c.s : "", out, "rgba");
|
|
46947
|
+
return out.type === "rgba" ? [chanByte(out.r), chanByte(out.g), chanByte(out.b), chanByte(out.a)] : [0, 0, 0, 255];
|
|
46948
|
+
}
|
|
46949
|
+
function xdotColorBody(rgba) {
|
|
46950
|
+
const hx = (n) => n.toString(16).padStart(2, "0");
|
|
46951
|
+
const body = "#" + hx(rgba[0]) + hx(rgba[1]) + hx(rgba[2]);
|
|
46952
|
+
return rgba[3] === 255 ? "7 -" + body : "9 -" + body + hx(rgba[3]);
|
|
46953
|
+
}
|
|
46954
|
+
function xdotColorOp(prefix, rgba) {
|
|
46955
|
+
return prefix + xdotColorBody(rgba) + " ";
|
|
46956
|
+
}
|
|
46957
|
+
function linearGradientOp(pts, fillColor, stopColor, frac, angleDeg) {
|
|
46958
|
+
const { g0, g1 } = getGradientPoints(pts, angleDeg * Math.PI / 180, false, true);
|
|
46959
|
+
const inner = "[" + xdotNum(g0.x) + " " + xdotNum(g0.y) + " " + xdotNum(g1.x) + " " + xdotNum(g1.y) + " 2 " + gradientStops(fillColor, stopColor, frac) + "]";
|
|
46960
|
+
return "C " + String(utf8Len(inner)) + " -" + inner + " ";
|
|
46961
|
+
}
|
|
46962
|
+
function gradientStops(fillColor, stopColor, frac) {
|
|
46963
|
+
const fill = gvColorRgba(fillColor);
|
|
46964
|
+
const stop = gvColorRgba(stopColor);
|
|
46965
|
+
const stops = frac > 0 ? [[frac, fill], [frac, stop]] : [[0, fill], [1, stop]];
|
|
46966
|
+
return stops.map(([f, c]) => trimFixed3(f) + " " + xdotColorBody(c)).join(" ");
|
|
46967
|
+
}
|
|
46968
|
+
function radialGradientOp(pts, fillColor, stopColor, frac, angleDeg) {
|
|
46969
|
+
const rad2 = angleDeg * Math.PI / 180;
|
|
46970
|
+
const gp = getGradientPoints(pts, rad2, true, true);
|
|
46971
|
+
const cx = gp.g0.x;
|
|
46972
|
+
const cy = gp.g0.y;
|
|
46973
|
+
const r1 = gp.g1.x;
|
|
46974
|
+
const r2 = gp.g1.y;
|
|
46975
|
+
const c1x = angleDeg === 0 ? cx : cx + r1 * Math.cos(rad2);
|
|
46976
|
+
const c1y = angleDeg === 0 ? cy : cy + r1 * Math.sin(rad2);
|
|
46977
|
+
const inner = "(" + xdotNum(c1x) + " " + xdotNum(c1y) + " " + xdotNum(r1) + " " + xdotNum(cx) + " " + xdotNum(cy) + " " + xdotNum(r2) + " 2 " + gradientStops(fillColor, stopColor, frac) + ")";
|
|
46978
|
+
return "C " + String(utf8Len(inner)) + " -" + inner + " ";
|
|
46979
|
+
}
|
|
46980
|
+
function xdotPenColor(c) {
|
|
46981
|
+
return xdotColorOp("c ", gvColorRgba(c));
|
|
46982
|
+
}
|
|
46983
|
+
function xdotFillColor(c) {
|
|
46984
|
+
return xdotColorOp("C ", gvColorRgba(c));
|
|
46985
|
+
}
|
|
46986
|
+
function xdotFont(size, name) {
|
|
46987
|
+
return "F " + xdotNum(size > 0 ? size : 0) + " " + String(utf8Len(name)) + " -" + name + " ";
|
|
46988
|
+
}
|
|
46989
|
+
function xdotStrOp(prefix, s) {
|
|
46990
|
+
return prefix + String(utf8Len(s)) + " -" + s + " ";
|
|
46991
|
+
}
|
|
46992
|
+
function xdotId(s) {
|
|
46993
|
+
if (/^[A-Za-z_][A-Za-z_0-9]*$/.test(s)) return s;
|
|
46994
|
+
if (/^-?(\.[0-9]+|[0-9]+(\.[0-9]*)?)$/.test(s)) return s;
|
|
46995
|
+
const body = s.replace(/"/g, '\\"');
|
|
46996
|
+
const trailingBackslashes = /\\*$/.exec(body)[0].length;
|
|
46997
|
+
return '"' + body + (trailingBackslashes % 2 === 1 ? "\\" : "") + '"';
|
|
46998
|
+
}
|
|
46999
|
+
function gfmt5(v) {
|
|
47000
|
+
if (!Number.isFinite(v)) return String(v);
|
|
47001
|
+
if (v === 0) return "0";
|
|
47002
|
+
let s = printfSig(v, 5);
|
|
47003
|
+
const e = s.indexOf("e");
|
|
47004
|
+
if (e >= 0) {
|
|
47005
|
+
let mant = s.slice(0, e);
|
|
47006
|
+
if (mant.indexOf(".") >= 0) mant = mant.replace(/0+$/, "").replace(/\.$/, "");
|
|
47007
|
+
const ei = parseInt(s.slice(e + 1), 10);
|
|
47008
|
+
return mant + "e" + (ei < 0 ? "-" : "+") + String(Math.abs(ei)).padStart(2, "0");
|
|
47009
|
+
}
|
|
47010
|
+
if (s.indexOf(".") >= 0) s = s.replace(/0+$/, "").replace(/\.$/, "");
|
|
47011
|
+
return s;
|
|
47012
|
+
}
|
|
47013
|
+
function gfmt2(v) {
|
|
47014
|
+
return printfFixed(v, 2);
|
|
47015
|
+
}
|
|
47016
|
+
function lpStr(p) {
|
|
47017
|
+
return gfmt5(p.x) + "," + gfmt5(p.y);
|
|
47018
|
+
}
|
|
46462
47019
|
function escBackslash(s) {
|
|
46463
47020
|
return s.replace(/\\/g, "\\\\");
|
|
46464
47021
|
}
|
|
@@ -46491,27 +47048,741 @@ function trimFixed3(v) {
|
|
|
46491
47048
|
if (s[end - 1] === ".") end--;
|
|
46492
47049
|
return s.slice(0, end);
|
|
46493
47050
|
}
|
|
46494
|
-
|
|
47051
|
+
|
|
47052
|
+
// src/render/dot/attrs.ts
|
|
47053
|
+
function edgeAttrsAttached(e) {
|
|
47054
|
+
return e.info.edge_type !== IGNORED && e.info.spl != null;
|
|
47055
|
+
}
|
|
47056
|
+
function appendRecordRects(n, f, out) {
|
|
47057
|
+
const c = n.info.coord;
|
|
47058
|
+
if (f.n_flds === 0) {
|
|
47059
|
+
out.push(
|
|
47060
|
+
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)
|
|
47061
|
+
);
|
|
47062
|
+
}
|
|
47063
|
+
for (let i = 0; i < f.n_flds; i++) appendRecordRects(n, f.fld[i], out);
|
|
47064
|
+
}
|
|
47065
|
+
var COMPUTED_NODE_ATTRS = /* @__PURE__ */ new Set(["pos", "width", "height", "xlp", "rects", "_draw_", "_ldraw_"]);
|
|
47066
|
+
var COMPUTED_EDGE_ATTRS = /* @__PURE__ */ new Set([
|
|
47067
|
+
"pos",
|
|
47068
|
+
"lp",
|
|
47069
|
+
"xlp",
|
|
47070
|
+
"head_lp",
|
|
47071
|
+
"tail_lp",
|
|
47072
|
+
"tailport",
|
|
47073
|
+
"headport",
|
|
47074
|
+
"_draw_",
|
|
47075
|
+
"_ldraw_",
|
|
47076
|
+
"_hdraw_",
|
|
47077
|
+
"_tdraw_",
|
|
47078
|
+
"_hldraw_",
|
|
47079
|
+
"_tldraw_"
|
|
47080
|
+
]);
|
|
47081
|
+
var NODENAME_ESC = "\\N";
|
|
47082
|
+
function effectiveEdgeDefaults(scope) {
|
|
47083
|
+
const eff = /* @__PURE__ */ new Map();
|
|
47084
|
+
for (let g = scope; g !== null; g = g.parent) {
|
|
47085
|
+
for (const [k, v] of g.edgeDefaults) if (!eff.has(k)) eff.set(k, v);
|
|
47086
|
+
}
|
|
47087
|
+
return eff;
|
|
47088
|
+
}
|
|
47089
|
+
function effectiveNodeDefaults(scope) {
|
|
47090
|
+
const eff = /* @__PURE__ */ new Map();
|
|
47091
|
+
for (let g = scope; g !== null; g = g.parent) {
|
|
47092
|
+
for (const [k, v] of g.nodeDefaults) if (!eff.has(k)) eff.set(k, v);
|
|
47093
|
+
}
|
|
47094
|
+
if (!eff.has("label")) eff.set("label", NODENAME_ESC);
|
|
47095
|
+
return eff;
|
|
47096
|
+
}
|
|
47097
|
+
function nodeRecord(n) {
|
|
47098
|
+
const rec = new Map(n.nodeDefaultsSnapshot ?? []);
|
|
47099
|
+
if (!rec.has("label")) rec.set("label", NODENAME_ESC);
|
|
47100
|
+
for (const [k, v] of n.attrs) rec.set(k, v);
|
|
47101
|
+
return rec;
|
|
47102
|
+
}
|
|
47103
|
+
function objInputParts(record, defaults, computed) {
|
|
47104
|
+
const parts = [];
|
|
47105
|
+
for (const k of /* @__PURE__ */ new Set([...record.keys(), ...defaults.keys()])) {
|
|
47106
|
+
if (computed.has(k)) continue;
|
|
47107
|
+
const v = record.get(k) ?? "";
|
|
47108
|
+
if (v === (defaults.get(k) ?? "")) continue;
|
|
47109
|
+
parts.push(k + "=" + agstrcanon(v));
|
|
47110
|
+
}
|
|
47111
|
+
return parts;
|
|
47112
|
+
}
|
|
47113
|
+
function computedPart(key, raw, defaults) {
|
|
47114
|
+
if (defaults !== void 0 && defaults.get(key) === raw) return [];
|
|
47115
|
+
return [key + "=" + agstrcanon(raw)];
|
|
47116
|
+
}
|
|
47117
|
+
var COMPUTED_GRAPH_ATTRS = /* @__PURE__ */ new Set([
|
|
47118
|
+
"_draw_",
|
|
47119
|
+
"_ldraw_",
|
|
47120
|
+
"bb",
|
|
47121
|
+
"lp",
|
|
47122
|
+
"lwidth",
|
|
47123
|
+
"lheight"
|
|
47124
|
+
]);
|
|
47125
|
+
var COMPUTED_GRAPH_ATTRS_XDOT = /* @__PURE__ */ new Set([...COMPUTED_GRAPH_ATTRS, "xdotversion"]);
|
|
47126
|
+
function computedGraphAttrs(emitDraws) {
|
|
47127
|
+
return emitDraws ? COMPUTED_GRAPH_ATTRS_XDOT : COMPUTED_GRAPH_ATTRS;
|
|
47128
|
+
}
|
|
47129
|
+
function graphInputParts(g, top, emitDraws) {
|
|
47130
|
+
const computed = computedGraphAttrs(emitDraws);
|
|
47131
|
+
const parts = [];
|
|
47132
|
+
const snap = g.graphDefaultsSnapshot;
|
|
47133
|
+
const seeded = g.seededAttrs;
|
|
47134
|
+
for (const [k, v] of g.attrs) {
|
|
47135
|
+
if (computed.has(k)) continue;
|
|
47136
|
+
if (!top && seeded !== void 0 && seeded.has(k)) continue;
|
|
47137
|
+
if (writeDictSkips(v, snap?.get(k))) continue;
|
|
47138
|
+
parts.push(k + "=" + agstrcanon(v));
|
|
47139
|
+
}
|
|
47140
|
+
if (!top && snap !== void 0) parts.push(...eagerEmptyParts(g, snap, computed));
|
|
47141
|
+
return parts;
|
|
47142
|
+
}
|
|
47143
|
+
function eagerEmptyParts(g, snap, computed) {
|
|
47144
|
+
const parts = [];
|
|
47145
|
+
const p = g.parent;
|
|
47146
|
+
if (p === null) return parts;
|
|
47147
|
+
const keys = p.parent === null ? p.attrs.keys() : p.firstGraphDecl ?? [];
|
|
47148
|
+
for (const k of keys) {
|
|
47149
|
+
if (computed.has(k)) continue;
|
|
47150
|
+
if (snap.has(k) || g.attrs.has(k)) continue;
|
|
47151
|
+
parts.push(k + "=" + agstrcanon(""));
|
|
47152
|
+
}
|
|
47153
|
+
return parts;
|
|
47154
|
+
}
|
|
47155
|
+
function dictParts(defaults) {
|
|
47156
|
+
const parts = [];
|
|
47157
|
+
for (const [k, v] of defaults) parts.push(k + "=" + agstrcanon(v));
|
|
47158
|
+
return parts;
|
|
47159
|
+
}
|
|
47160
|
+
function nodeDictParts(g, top) {
|
|
47161
|
+
const defs = new Map(g.nodeDefaults);
|
|
47162
|
+
if (top && !defs.has("label")) defs.set("label", NODENAME_ESC);
|
|
47163
|
+
return dictParts(defs);
|
|
47164
|
+
}
|
|
47165
|
+
function echoAttr(attrs, key) {
|
|
47166
|
+
const v = attrs.get(key);
|
|
47167
|
+
if (v === void 0 || v.length === 0) return [];
|
|
47168
|
+
return [key + '="' + agcanonEscape(v) + '"'];
|
|
47169
|
+
}
|
|
47170
|
+
function writeDictSkips(v, inherited) {
|
|
47171
|
+
return v.length === 0 && inherited === "";
|
|
47172
|
+
}
|
|
47173
|
+
function echoGraphAttr(g, key) {
|
|
47174
|
+
const v = g.attrs.get(key);
|
|
47175
|
+
if (v === void 0) return [];
|
|
47176
|
+
if (writeDictSkips(v, g.graphDefaultsSnapshot?.get(key))) return [];
|
|
47177
|
+
return [key + '="' + agcanonEscape(v) + '"'];
|
|
47178
|
+
}
|
|
47179
|
+
function graphLabelAttrs(g) {
|
|
47180
|
+
const label = g.info.label;
|
|
47181
|
+
if (!label || label.text.length === 0) {
|
|
47182
|
+
return [
|
|
47183
|
+
...echoGraphAttr(g, "lp"),
|
|
47184
|
+
...echoGraphAttr(g, "lwidth"),
|
|
47185
|
+
...echoGraphAttr(g, "lheight")
|
|
47186
|
+
];
|
|
47187
|
+
}
|
|
47188
|
+
return [
|
|
47189
|
+
'lp="' + lpStr(label.pos) + '"',
|
|
47190
|
+
"lwidth=" + gfmt2(label.dimen.x / POINTS_PER_INCH),
|
|
47191
|
+
"lheight=" + gfmt2(label.dimen.y / POINTS_PER_INCH)
|
|
47192
|
+
];
|
|
47193
|
+
}
|
|
47194
|
+
function edgePosRaw(e) {
|
|
47195
|
+
const spl = e.info.spl;
|
|
47196
|
+
if (!spl || spl.list.length === 0) return null;
|
|
47197
|
+
if (e.info.edge_type === IGNORED) return null;
|
|
47198
|
+
let out = "";
|
|
47199
|
+
for (let i = 0; i < spl.size; i++) {
|
|
47200
|
+
const bez = spl.list[i];
|
|
47201
|
+
if (i > 0) out += ";";
|
|
47202
|
+
if (bez.sflag) out += "s," + gfmt5(bez.sp.x) + "," + gfmt5(bez.sp.y) + " ";
|
|
47203
|
+
if (bez.eflag) out += "e," + gfmt5(bez.ep.x) + "," + gfmt5(bez.ep.y) + " ";
|
|
47204
|
+
for (let j = 0; j < bez.size; j++) {
|
|
47205
|
+
if (j > 0) out += " ";
|
|
47206
|
+
const p = bez.list[j];
|
|
47207
|
+
out += gfmt5(p.x) + "," + gfmt5(p.y);
|
|
47208
|
+
}
|
|
47209
|
+
}
|
|
47210
|
+
return out;
|
|
47211
|
+
}
|
|
47212
|
+
function edgeConnector(directed) {
|
|
47213
|
+
return directed ? "->" : "--";
|
|
47214
|
+
}
|
|
47215
|
+
function isDirected(g) {
|
|
47216
|
+
return g.kind === "directed" || g.kind === "strict-directed";
|
|
47217
|
+
}
|
|
47218
|
+
|
|
47219
|
+
// src/render/dot/agwrite.ts
|
|
47220
|
+
var DotWriterBase = class {
|
|
47221
|
+
/** Clusters in render order (GD_clust); filled by the renderer's endCluster. */
|
|
47222
|
+
clusters = [];
|
|
47223
|
+
/**
|
|
47224
|
+
* Emit `key="value"`, escaping the value the way agwrite's agcanonStr does: a
|
|
47225
|
+
* `"` becomes `\"` unless it is already part of an escape sequence (see
|
|
47226
|
+
* agcanonEscape). A draw string carries label text that may contain a bare `"`
|
|
47227
|
+
* (would close the attribute early) or a source `\"`/`\\` (must not be
|
|
47228
|
+
* double-escaped). The byte-length prefix stays on the UNescaped text (the
|
|
47229
|
+
* parser un-escapes before parseXDot re-reads it), matching native exactly.
|
|
47230
|
+
* @see lib/cgraph/write.c:_agstrcanon (135-167)
|
|
47231
|
+
*/
|
|
47232
|
+
drawAttr(key, value) {
|
|
47233
|
+
return key + '="' + agcanonEscape(value) + '"';
|
|
47234
|
+
}
|
|
47235
|
+
/** `llx,lly,urx,ury` from a graph's layout bb. */
|
|
47236
|
+
bbStr(g) {
|
|
47237
|
+
const bb = g.info.bb;
|
|
47238
|
+
return gfmt5(bb.ll.x) + "," + gfmt5(bb.ll.y) + "," + gfmt5(bb.ur.x) + "," + gfmt5(bb.ur.y);
|
|
47239
|
+
}
|
|
47240
|
+
/**
|
|
47241
|
+
* Serialize the whole laid-out graph to xdot DOT text — a faithful port of
|
|
47242
|
+
* cgraph's agwrite (lib/cgraph/write.c). Recurses the subgraph tree
|
|
47243
|
+
* (write_subgs/write_body), scoping each node/edge to the subgraph(s) it
|
|
47244
|
+
* belongs to via preorder numbers (write_node_test/write_edge_test), and
|
|
47245
|
+
* re-emits an object bare (no attrs) on any scope after the first
|
|
47246
|
+
* (attrs_written). This reproduces native's per-subgraph edge re-declarations
|
|
47247
|
+
* — e.g. an edge in a rank=same subgraph is drawn once, then re-declared bare.
|
|
47248
|
+
* @see lib/cgraph/write.c:agwrite
|
|
47249
|
+
*/
|
|
47250
|
+
serialize(g) {
|
|
47251
|
+
const ctx = {
|
|
47252
|
+
out: [],
|
|
47253
|
+
preorder: /* @__PURE__ */ new Map(),
|
|
47254
|
+
nodeLW: /* @__PURE__ */ new Map(),
|
|
47255
|
+
edgeLW: /* @__PURE__ */ new Map(),
|
|
47256
|
+
attrsWritten: /* @__PURE__ */ new Set(),
|
|
47257
|
+
level: 0
|
|
47258
|
+
};
|
|
47259
|
+
this.subgdfs(g, 1, ctx.preorder);
|
|
47260
|
+
this.writeHdr(g, true, ctx);
|
|
47261
|
+
this.writeBody(g, ctx);
|
|
47262
|
+
this.writeTrl(ctx);
|
|
47263
|
+
return ctx.out.join("");
|
|
47264
|
+
}
|
|
47265
|
+
/** Preorder-number the subgraph tree. @see write.c:subgdfs */
|
|
47266
|
+
subgdfs(g, ix, preorder) {
|
|
47267
|
+
let ix0 = ix;
|
|
47268
|
+
preorder.set(g, ix0);
|
|
47269
|
+
for (const sub of g.subgraphs.values()) ix0 = this.subgdfs(sub, ix0, preorder);
|
|
47270
|
+
return ix0 + 1;
|
|
47271
|
+
}
|
|
47272
|
+
/** A subgraph is anonymous when its name is empty or a `%N` local name. */
|
|
47273
|
+
isAnonymous(g) {
|
|
47274
|
+
return g.name.length === 0 || g.name.charCodeAt(0) === 37;
|
|
47275
|
+
}
|
|
47276
|
+
/** A graph carries a `bb` attribute when it is the root or has a layout bb
|
|
47277
|
+
* (clusters, and any subgraph output.c computed a box for). Native seeds `bb`
|
|
47278
|
+
* on every graph via safe_dcl, so the value differs (set vs empty "") between
|
|
47279
|
+
* a boxed graph and an unboxed child — the driver that makes an anon subgraph
|
|
47280
|
+
* under the root or a cluster "relevant". @see lib/common/output.c:safe_dcl */
|
|
47281
|
+
hasBb(g) {
|
|
47282
|
+
return g === g.root || this.clusters.includes(g);
|
|
47283
|
+
}
|
|
47284
|
+
/** Anonymous subgraph with no own node/edge defaults and no graph attrs
|
|
47285
|
+
* differing from its parent → inlined into the parent. Native compares every
|
|
47286
|
+
* graph attr over the root attr dict; the load-bearing ones are `bb` (set on
|
|
47287
|
+
* the parent, empty on the child) and `rank`. @see write.c:irrelevant_subgraph */
|
|
47288
|
+
irrelevantSubgraph(g) {
|
|
47289
|
+
if (!this.isAnonymous(g)) return false;
|
|
47290
|
+
if (this.clusters.includes(g)) return false;
|
|
47291
|
+
if (g.nodeDefaults.size > 0 || g.edgeDefaults.size > 0) return false;
|
|
47292
|
+
if (g.parent) {
|
|
47293
|
+
if (this.hasBb(g) !== this.hasBb(g.parent)) return false;
|
|
47294
|
+
for (const [k, v] of g.attrs) {
|
|
47295
|
+
if (g.parent.attrs.get(k) !== v) return false;
|
|
47296
|
+
}
|
|
47297
|
+
} else if (g.attrs.size > 0) {
|
|
47298
|
+
return false;
|
|
47299
|
+
}
|
|
47300
|
+
return true;
|
|
47301
|
+
}
|
|
47302
|
+
/** Non-draw graph attrs a subgraph emits (rank for rank=same; clusters use
|
|
47303
|
+
* clusterAttrs). Only comparator-relevant fields need be exact.
|
|
47304
|
+
*
|
|
47305
|
+
* `rec_attach_bb` walks the ROOT and then `GD_clust` recursively, so a
|
|
47306
|
+
* subgraph the layout did not box is attached NEITHER `bb` NOR the label
|
|
47307
|
+
* triple — both keep the INPUT's values, which agwrite echoes. circo and
|
|
47308
|
+
* twopi lay out no clusters at all (`GD_clust` empty — mirrored here by
|
|
47309
|
+
* `this.clusters`, filled from endCluster), so on a re-fed dot output their
|
|
47310
|
+
* `cluster0` comes back carrying the *previous* run's `bb` and `lp`.
|
|
47311
|
+
* @see lib/common/output.c:249 rec_attach_bb */
|
|
47312
|
+
subgGraphAttrs(sg) {
|
|
47313
|
+
const parts = [];
|
|
47314
|
+
parts.push(...echoGraphAttr(sg, "bb"));
|
|
47315
|
+
parts.push(...graphLabelAttrs(sg));
|
|
47316
|
+
parts.push(...graphInputParts(sg, false, this.emitDraws));
|
|
47317
|
+
return parts;
|
|
47318
|
+
}
|
|
47319
|
+
indent(ctx) {
|
|
47320
|
+
return " ".repeat(ctx.level);
|
|
47321
|
+
}
|
|
47322
|
+
/** @see write.c:write_hdr */
|
|
47323
|
+
writeHdr(g, top, ctx) {
|
|
47324
|
+
if (top) {
|
|
47325
|
+
const strict = g.kind === "strict-directed" || g.kind === "strict-undirected" ? "strict " : "";
|
|
47326
|
+
const kw = isDirected(g) ? "digraph" : "graph";
|
|
47327
|
+
const nm = g.name.length > 0 && !this.isAnonymous(g) ? xdotId(g.name) + " " : "";
|
|
47328
|
+
ctx.out.push(strict + kw + " " + nm + "{\n");
|
|
47329
|
+
ctx.level++;
|
|
47330
|
+
} else {
|
|
47331
|
+
const nm = this.isAnonymous(g) ? "" : "subgraph " + xdotId(g.name) + " ";
|
|
47332
|
+
ctx.out.push(this.indent(ctx) + nm + "{\n");
|
|
47333
|
+
ctx.level++;
|
|
47334
|
+
}
|
|
47335
|
+
this.writeDicts(g, top, ctx);
|
|
47336
|
+
}
|
|
47337
|
+
/**
|
|
47338
|
+
* Emit this scope's `graph` / `node` / `edge` default statements, in that
|
|
47339
|
+
* order. @see lib/cgraph/write.c:307 write_dicts
|
|
47340
|
+
*/
|
|
47341
|
+
writeDicts(g, top, ctx) {
|
|
47342
|
+
const graphParts = top ? this.graphAttrs(g) : this.clusters.includes(g) ? this.clusterAttrs(g) : this.subgGraphAttrs(g);
|
|
47343
|
+
this.writeDict("graph", graphParts, ctx);
|
|
47344
|
+
this.writeDict("node", nodeDictParts(g, top), ctx);
|
|
47345
|
+
this.writeDict("edge", dictParts(g.edgeDefaults), ctx);
|
|
47346
|
+
}
|
|
47347
|
+
/**
|
|
47348
|
+
* One `<name> [...]` default statement. Entries are emitted in **strcmp order
|
|
47349
|
+
* of attribute name**: cgraph's attribute dicts are `Dttree`s keyed on
|
|
47350
|
+
* `Agsym_t.name` with a NULL comparf (attr.c:34), so `dtfirst`/`dtnext` walk
|
|
47351
|
+
* them sorted — the oracle's `graph [bb, rankdir]` and `[height, pos, width]`.
|
|
47352
|
+
*
|
|
47353
|
+
* Layout mirrors write_dict exactly: a single entry stays on one line, and two
|
|
47354
|
+
* or more break after each `,` with the body indented one level deeper and the
|
|
47355
|
+
* closing `];` back at the statement's own level.
|
|
47356
|
+
*
|
|
47357
|
+
* Not ported: the `EMPTY(defval) && !sym->print` skip (write.c:271-280). Every
|
|
47358
|
+
* entry here was explicitly declared or computed, i.e. `print` is set, so that
|
|
47359
|
+
* branch is unreachable until the eager-propagation artifact lands — an
|
|
47360
|
+
* `agapply`-installed empty default is the only `print == false` producer.
|
|
47361
|
+
*
|
|
47362
|
+
* @see lib/cgraph/write.c:262 write_dict
|
|
47363
|
+
*/
|
|
47364
|
+
writeDict(name, parts, ctx) {
|
|
47365
|
+
if (parts.length === 0) return;
|
|
47366
|
+
const sorted = [...parts].sort((a, b) => {
|
|
47367
|
+
const ka = a.slice(0, a.indexOf("="));
|
|
47368
|
+
const kb = b.slice(0, b.indexOf("="));
|
|
47369
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
47370
|
+
});
|
|
47371
|
+
ctx.out.push(this.indent(ctx) + name + " [");
|
|
47372
|
+
ctx.level++;
|
|
47373
|
+
ctx.out.push(sorted.join(",\n" + this.indent(ctx)));
|
|
47374
|
+
ctx.level--;
|
|
47375
|
+
if (sorted.length > 1) ctx.out.push("\n" + this.indent(ctx));
|
|
47376
|
+
ctx.out.push("];\n");
|
|
47377
|
+
}
|
|
47378
|
+
/**
|
|
47379
|
+
* One object's `[...]` attribute block, or '' when it has no attributes.
|
|
47380
|
+
* Entries sort strcmp like every dict (attr.c:34). Layout is
|
|
47381
|
+
* write_nondefault_attrs, which differs from write_dict in two ways: the block
|
|
47382
|
+
* opens with a literal TAB before `[`, and the closing `]` follows the last
|
|
47383
|
+
* value directly — there is no newline+indent before it.
|
|
47384
|
+
* @see lib/cgraph/write.c:471 write_nondefault_attrs
|
|
47385
|
+
*/
|
|
47386
|
+
objAttrBlock(parts, ctx) {
|
|
47387
|
+
if (parts.length === 0) return "";
|
|
47388
|
+
const sorted = [...parts].sort((a, b) => {
|
|
47389
|
+
const ka = a.slice(0, a.indexOf("="));
|
|
47390
|
+
const kb = b.slice(0, b.indexOf("="));
|
|
47391
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
47392
|
+
});
|
|
47393
|
+
ctx.level++;
|
|
47394
|
+
const body = sorted.join(",\n" + this.indent(ctx));
|
|
47395
|
+
ctx.level--;
|
|
47396
|
+
return " [" + body + "]";
|
|
47397
|
+
}
|
|
47398
|
+
/** @see write.c:write_trl */
|
|
47399
|
+
writeTrl(ctx) {
|
|
47400
|
+
ctx.level--;
|
|
47401
|
+
ctx.out.push(this.indent(ctx) + "}\n");
|
|
47402
|
+
}
|
|
47403
|
+
/** @see write.c:write_subgs */
|
|
47404
|
+
writeSubgs(g, ctx) {
|
|
47405
|
+
for (const sub of g.subgraphs.values()) {
|
|
47406
|
+
if (this.irrelevantSubgraph(sub)) {
|
|
47407
|
+
this.writeSubgs(sub, ctx);
|
|
47408
|
+
} else {
|
|
47409
|
+
this.writeHdr(sub, false, ctx);
|
|
47410
|
+
this.writeBody(sub, ctx);
|
|
47411
|
+
this.writeTrl(ctx);
|
|
47412
|
+
}
|
|
47413
|
+
}
|
|
47414
|
+
}
|
|
47415
|
+
/** @see write.c:write_body — subgraphs, then this scope's nodes and edges. */
|
|
47416
|
+
writeBody(g, ctx) {
|
|
47417
|
+
this.writeSubgs(g, ctx);
|
|
47418
|
+
for (const n of nodesInSeq(g)) {
|
|
47419
|
+
if (this.writeNodeTest(g, n, ctx)) this.writeNode(g, n, ctx);
|
|
47420
|
+
let prev = n;
|
|
47421
|
+
for (const e of n.outEdges(g)) {
|
|
47422
|
+
if (prev !== e.head && this.writeNodeTest(g, e.head, ctx)) {
|
|
47423
|
+
this.writeNode(g, e.head, ctx);
|
|
47424
|
+
prev = e.head;
|
|
47425
|
+
}
|
|
47426
|
+
if (this.writeEdgeTest(g, e, ctx)) this.writeEdge(g, e, ctx);
|
|
47427
|
+
}
|
|
47428
|
+
}
|
|
47429
|
+
}
|
|
47430
|
+
/** @see write.c:write_node_test — every xdot node carries pos/size, so it is
|
|
47431
|
+
* never "default"; write it in the first scope that has not yet emitted it.
|
|
47432
|
+
* Cluster nodes (fdp compound proxies, ND_clustnode) are never declared —
|
|
47433
|
+
* C's write_plain/writenodeandport suppress them. @see lib/common/output.c:146 */
|
|
47434
|
+
writeNodeTest(g, n, ctx) {
|
|
47435
|
+
if (n.info.clustnode) return false;
|
|
47436
|
+
return (ctx.nodeLW.get(n) ?? 0) < ctx.preorder.get(g);
|
|
47437
|
+
}
|
|
47438
|
+
/** Emitted node name: a cluster node's synthetic `__i:<cluster>` id is written
|
|
47439
|
+
* as the cluster name it stands for. @see lib/common/output.c:114 */
|
|
47440
|
+
emitNodeName(n) {
|
|
47441
|
+
if (n.info.clustnode) {
|
|
47442
|
+
const i = n.name.indexOf(":");
|
|
47443
|
+
if (i >= 0) return n.name.slice(i + 1);
|
|
47444
|
+
}
|
|
47445
|
+
return n.name;
|
|
47446
|
+
}
|
|
47447
|
+
/** @see write.c:write_edge_test */
|
|
47448
|
+
writeEdgeTest(g, e, ctx) {
|
|
47449
|
+
return (ctx.edgeLW.get(e) ?? 0) < ctx.preorder.get(g);
|
|
47450
|
+
}
|
|
47451
|
+
/** @see write.c:write_node */
|
|
47452
|
+
writeNode(g, n, ctx) {
|
|
47453
|
+
let s = this.indent(ctx) + xdotId(this.emitNodeName(n));
|
|
47454
|
+
if (!ctx.attrsWritten.has(n)) {
|
|
47455
|
+
s += this.objAttrBlock(this.nodeAttrs(n, g), ctx);
|
|
47456
|
+
ctx.attrsWritten.add(n);
|
|
47457
|
+
}
|
|
47458
|
+
ctx.out.push(s + ";\n");
|
|
47459
|
+
ctx.nodeLW.set(n, ctx.preorder.get(g));
|
|
47460
|
+
}
|
|
47461
|
+
/** @see write.c:write_edge — attrs only on first emission, bare thereafter. */
|
|
47462
|
+
/**
|
|
47463
|
+
* The `:port` / `:port:compass` suffix C writes after an endpoint name.
|
|
47464
|
+
*
|
|
47465
|
+
* These are NOT ordinary attributes on the way out: write_nondefault_attrs
|
|
47466
|
+
* skips the tailport/headport symbols (write.c:487-492) precisely because
|
|
47467
|
+
* this function re-emits them as endpoint syntax, so an edge parsed from
|
|
47468
|
+
* `A:f0:ne -> B` round-trips through the port syntax rather than through a
|
|
47469
|
+
* `tailport="f0:ne"` attribute.
|
|
47470
|
+
*
|
|
47471
|
+
* C reads the value with agxget, which falls back to the edge dict default,
|
|
47472
|
+
* and gates on the SYMBOL existing at all — both are subsumed here by the
|
|
47473
|
+
* attr-then-default lookup coming back undefined. An html-like value is
|
|
47474
|
+
* canonicalized whole; a plain one splits on its FIRST `:` (strchr) so the
|
|
47475
|
+
* port and the compass point are canonicalized separately.
|
|
47476
|
+
* @see lib/cgraph/write.c:565 write_port
|
|
47477
|
+
*/
|
|
47478
|
+
portSuffix(e, g, key) {
|
|
47479
|
+
const val2 = e.attrs.get(key) ?? effectiveEdgeDefaults(g).get(key);
|
|
47480
|
+
if (val2 === void 0 || val2.length === 0) return "";
|
|
47481
|
+
if (isHtmlValue(val2)) return ":" + agstrcanon(val2);
|
|
47482
|
+
const i = val2.indexOf(":");
|
|
47483
|
+
if (i < 0) return ":" + agstrcanonText(val2);
|
|
47484
|
+
return ":" + agstrcanonText(val2.slice(0, i)) + ":" + agstrcanonText(val2.slice(i + 1));
|
|
47485
|
+
}
|
|
47486
|
+
writeEdge(g, e, ctx) {
|
|
47487
|
+
const conn = edgeConnector(isDirected(g));
|
|
47488
|
+
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");
|
|
47489
|
+
if (!ctx.attrsWritten.has(e)) {
|
|
47490
|
+
s += this.objAttrBlock(this.edgeAttrStr(e, g), ctx);
|
|
47491
|
+
ctx.attrsWritten.add(e);
|
|
47492
|
+
}
|
|
47493
|
+
ctx.out.push(s + ";\n");
|
|
47494
|
+
ctx.edgeLW.set(e, ctx.preorder.get(g));
|
|
47495
|
+
}
|
|
47496
|
+
/** Root-graph attribute block: `_draw_`, `_ldraw_`, `bb`, `xdotversion`. */
|
|
47497
|
+
graphAttrs(g) {
|
|
47498
|
+
const d = this.drawsOf(g);
|
|
47499
|
+
const parts = [];
|
|
47500
|
+
if (d?.draw) parts.push(this.drawAttr("_draw_", d.draw));
|
|
47501
|
+
if (d?.ldraw) parts.push(this.drawAttr("_ldraw_", d.ldraw));
|
|
47502
|
+
parts.push('bb="' + this.bbStr(g) + '"');
|
|
47503
|
+
parts.push(...graphLabelAttrs(g));
|
|
47504
|
+
if (this.emitDraws) parts.push("xdotversion=" + agstrcanon(XDOT_VERSION));
|
|
47505
|
+
parts.push(...graphInputParts(g, true, this.emitDraws));
|
|
47506
|
+
return parts;
|
|
47507
|
+
}
|
|
47508
|
+
/** Cluster attribute block. C's xdot_end_cluster ALWAYS agsets `_draw_` when
|
|
47509
|
+
* the graph has clusters (even empty, e.g. peripheries=0), so emit it
|
|
47510
|
+
* unconditionally; `_ldraw_` only when the cluster has a label.
|
|
47511
|
+
* @see plugin/core/gvrender_core_dot.c:284 xdot_end_cluster */
|
|
47512
|
+
clusterAttrs(sg) {
|
|
47513
|
+
const d = this.drawsOf(sg);
|
|
47514
|
+
const parts = this.emitDraws ? [this.drawAttr("_draw_", d?.draw ?? "")] : [];
|
|
47515
|
+
if (d?.ldraw) parts.push(this.drawAttr("_ldraw_", d.ldraw));
|
|
47516
|
+
if (sg.info.bb) parts.push('bb="' + this.bbStr(sg) + '"');
|
|
47517
|
+
parts.push(...graphLabelAttrs(sg));
|
|
47518
|
+
parts.push(...graphInputParts(sg, false, this.emitDraws));
|
|
47519
|
+
return parts;
|
|
47520
|
+
}
|
|
47521
|
+
/** Node attribute block: pos/width/height plus `_draw_`/`_ldraw_`. `scope` is
|
|
47522
|
+
* the subgraph the node is being WRITTEN in, whose node dict every value is
|
|
47523
|
+
* compared against. @see lib/cgraph/write.c:537-545 write_node */
|
|
47524
|
+
nodeAttrs(n, scope) {
|
|
47525
|
+
const info = n.info;
|
|
47526
|
+
const defs = effectiveNodeDefaults(scope);
|
|
47527
|
+
const posRaw = gfmt5(info.coord.x) + "," + gfmt5(info.coord.y);
|
|
47528
|
+
const widthRaw = gfmt5((info.lw + info.rw) / 72);
|
|
47529
|
+
const heightRaw = gfmt5(info.ht / 72);
|
|
47530
|
+
const parts = [
|
|
47531
|
+
...computedPart("pos", posRaw, defs),
|
|
47532
|
+
...computedPart("width", widthRaw, defs),
|
|
47533
|
+
...computedPart("height", heightRaw, defs)
|
|
47534
|
+
];
|
|
47535
|
+
parts.push(...this.nodeXlpPart(n, defs));
|
|
47536
|
+
parts.push(...this.nodeRectsPart(n, defs));
|
|
47537
|
+
const d = this.drawsOf(n);
|
|
47538
|
+
if (d?.draw) parts.push(this.drawAttr("_draw_", d.draw));
|
|
47539
|
+
if (d?.ldraw) parts.push(this.drawAttr("_ldraw_", d.ldraw));
|
|
47540
|
+
parts.push(...objInputParts(nodeRecord(n), defs, COMPUTED_NODE_ATTRS));
|
|
47541
|
+
return parts;
|
|
47542
|
+
}
|
|
47543
|
+
/** The `[...]` attribute body for an edge (draw ops + spline `pos`), or ''. */
|
|
47544
|
+
edgeAttrStr(e, g) {
|
|
47545
|
+
const defs = effectiveEdgeDefaults(g);
|
|
47546
|
+
const parts = this.edgeDrawParts(e);
|
|
47547
|
+
const posRaw = edgePosRaw(e);
|
|
47548
|
+
if (posRaw !== null) {
|
|
47549
|
+
parts.push(...computedPart("pos", posRaw, defs));
|
|
47550
|
+
} else {
|
|
47551
|
+
parts.push(...echoAttr(e.attrs, "pos"));
|
|
47552
|
+
}
|
|
47553
|
+
parts.push(...this.edgeLabelPosParts(e, defs));
|
|
47554
|
+
parts.push(...objInputParts(e.attrs, defs, COMPUTED_EDGE_ATTRS));
|
|
47555
|
+
return parts;
|
|
47556
|
+
}
|
|
47557
|
+
/** `xlp` — only when the node HAS an xlabel AND the xlabel placer actually set
|
|
47558
|
+
* its position (`ND_xlabel(n)->set`). An xlabel that was never placed is
|
|
47559
|
+
* omitted entirely, so an input `xlp` survives and is echoed.
|
|
47560
|
+
* @see lib/common/output.c:309-313 */
|
|
47561
|
+
nodeXlpPart(n, defs) {
|
|
47562
|
+
const xlabel = n.info.xlabel;
|
|
47563
|
+
if (!xlabel || !xlabel.set) return echoAttr(n.attrs, "xlp");
|
|
47564
|
+
const raw = lpStr(xlabel.pos);
|
|
47565
|
+
return computedPart("xlp", raw, defs);
|
|
47566
|
+
}
|
|
47567
|
+
/** `rects` — record field boxes. C gates on the SHAPE NAME being exactly
|
|
47568
|
+
* "record" (`strcmp(ND_shape(n)->name, "record") == 0`), so `Mrecord` and
|
|
47569
|
+
* HTML-table nodes get NO rects; confirmed against the native oracle. When
|
|
47570
|
+
* the shape is not a record the input's `rects` is never overwritten and is
|
|
47571
|
+
* echoed — patchwork forces every node to `box`, so a re-fed dot output
|
|
47572
|
+
* returns its old record rects untouched. @see lib/common/output.c:314-317 */
|
|
47573
|
+
nodeRectsPart(n, defs) {
|
|
47574
|
+
const shape = n.info.shape;
|
|
47575
|
+
if (shape?.name !== "record") return echoAttr(n.attrs, "rects");
|
|
47576
|
+
const rects = [];
|
|
47577
|
+
appendRecordRects(n, n.info.shape_info, rects);
|
|
47578
|
+
const raw = rects.join(" ");
|
|
47579
|
+
return computedPart("rects", raw, defs);
|
|
47580
|
+
}
|
|
47581
|
+
/** The six `_draw_`-family attributes of an edge, in C's order. */
|
|
47582
|
+
edgeDrawParts(e) {
|
|
47583
|
+
const d = this.drawsOf(e);
|
|
47584
|
+
const parts = [];
|
|
47585
|
+
if (d?.draw) parts.push(this.drawAttr("_draw_", d.draw));
|
|
47586
|
+
if (d?.ldraw) parts.push(this.drawAttr("_ldraw_", d.ldraw));
|
|
47587
|
+
if (d?.hdraw) parts.push(this.drawAttr("_hdraw_", d.hdraw));
|
|
47588
|
+
if (d?.tdraw) parts.push(this.drawAttr("_tdraw_", d.tdraw));
|
|
47589
|
+
if (d?.hldraw) parts.push(this.drawAttr("_hldraw_", d.hldraw));
|
|
47590
|
+
if (d?.tldraw) parts.push(this.drawAttr("_tldraw_", d.tldraw));
|
|
47591
|
+
return parts;
|
|
47592
|
+
}
|
|
47593
|
+
/**
|
|
47594
|
+
* `lp`/`xlp`/`head_lp`/`tail_lp` for one edge, in C's order.
|
|
47595
|
+
*
|
|
47596
|
+
* Note the asymmetry C encodes: `lp`/`head_lp`/`tail_lp` are emitted whenever
|
|
47597
|
+
* the label EXISTS, but `xlp` additionally requires `->set` — an unplaced
|
|
47598
|
+
* xlabel is omitted. Each gate is independent, and each FAILED gate leaves the
|
|
47599
|
+
* input's own value in the slot for write.c to echo: an unrouted edge
|
|
47600
|
+
* (patchwork routes none) returns the `lp` a previous dot run wrote.
|
|
47601
|
+
* @see lib/common/output.c:377-396
|
|
47602
|
+
*/
|
|
47603
|
+
edgeLabelPosParts(e, defs) {
|
|
47604
|
+
const attached = edgeAttrsAttached(e);
|
|
47605
|
+
const info = e.info;
|
|
47606
|
+
const parts = [];
|
|
47607
|
+
const lpPart = (key, label) => {
|
|
47608
|
+
const raw = lpStr(label.pos);
|
|
47609
|
+
return computedPart(key, raw, defs);
|
|
47610
|
+
};
|
|
47611
|
+
if (attached && info.label) parts.push(...lpPart("lp", info.label));
|
|
47612
|
+
else parts.push(...echoAttr(e.attrs, "lp"));
|
|
47613
|
+
if (attached && info.xlabel && info.xlabel.set) {
|
|
47614
|
+
parts.push(...lpPart("xlp", info.xlabel));
|
|
47615
|
+
} else parts.push(...echoAttr(e.attrs, "xlp"));
|
|
47616
|
+
if (attached && info.head_label) {
|
|
47617
|
+
parts.push(...lpPart("head_lp", info.head_label));
|
|
47618
|
+
} else parts.push(...echoAttr(e.attrs, "head_lp"));
|
|
47619
|
+
if (attached && info.tail_label) {
|
|
47620
|
+
parts.push(...lpPart("tail_lp", info.tail_label));
|
|
47621
|
+
} else parts.push(...echoAttr(e.attrs, "tail_lp"));
|
|
47622
|
+
return parts;
|
|
47623
|
+
}
|
|
47624
|
+
};
|
|
47625
|
+
|
|
47626
|
+
// src/render/dot/edge-draw.ts
|
|
47627
|
+
var DEFAULT_COLOR5 = "black";
|
|
47628
|
+
function emitArrowOp(buf, op, pen) {
|
|
47629
|
+
switch (op.kind) {
|
|
47630
|
+
case "polygon":
|
|
47631
|
+
if (op.filled) buf.push(xdotFillColor(pen));
|
|
47632
|
+
buf.push(xdotPoints(op.filled ? "P" : "p", op.points));
|
|
47633
|
+
break;
|
|
47634
|
+
case "ellipse":
|
|
47635
|
+
if (op.filled) buf.push(xdotFillColor(pen));
|
|
47636
|
+
buf.push(
|
|
47637
|
+
(op.filled ? "E " : "e ") + xdotPoint(op.center) + xdotNum(op.rx) + " " + xdotNum(op.ry) + " "
|
|
47638
|
+
);
|
|
47639
|
+
break;
|
|
47640
|
+
case "polyline":
|
|
47641
|
+
buf.push(xdotPoints("L", op.points));
|
|
47642
|
+
break;
|
|
47643
|
+
case "bezier":
|
|
47644
|
+
buf.push(xdotPoints("B", op.points));
|
|
47645
|
+
break;
|
|
47646
|
+
}
|
|
47647
|
+
}
|
|
47648
|
+
var EdgeDrawBase = class extends DotWriterBase {
|
|
47649
|
+
/** setlinewidth state per emit_state. @see gvrender_core_dot.c penwidth[] */
|
|
47650
|
+
penwidth = new Array(12).fill(1);
|
|
47651
|
+
/**
|
|
47652
|
+
* The plain single-colour spline branch of beginEdge — lifted verbatim so the
|
|
47653
|
+
* four-way spline-kind chain above stays readable; no branch was reordered or
|
|
47654
|
+
* merged. splines=ortho + radius/style=rounded emits straight segments plus
|
|
47655
|
+
* corner arcs as polylines (L), else the bezier itself.
|
|
47656
|
+
* @see lib/common/emit.c:2583
|
|
47657
|
+
*/
|
|
47658
|
+
emitPlainSpline(e, list, edraw, job) {
|
|
47659
|
+
const radius = orthoRoundedRadius(e, job);
|
|
47660
|
+
const obj = job.obj;
|
|
47661
|
+
const origStyle = obj !== null ? [...obj.rawStyle] : [];
|
|
47662
|
+
const multi = list.length > 1;
|
|
47663
|
+
for (const bez of list) {
|
|
47664
|
+
this.emitSplineBezier(bez, radius, edraw, job);
|
|
47665
|
+
if (obj !== null && multi && (bez.sflag || bez.eflag)) {
|
|
47666
|
+
obj.rawStyle = origStyle.length > 0 ? origStyle : ["solid"];
|
|
47667
|
+
}
|
|
47668
|
+
}
|
|
47669
|
+
if (obj !== null) obj.rawStyle = origStyle;
|
|
47670
|
+
}
|
|
47671
|
+
/** One bezier of a plain spline: rounded-ortho corner polylines when a radius
|
|
47672
|
+
* applies and the segment has enough control points, else the bezier. */
|
|
47673
|
+
emitSplineBezier(bez, radius, edraw, job) {
|
|
47674
|
+
const pts = bez.list.slice(0, bez.size);
|
|
47675
|
+
const polys = radius !== null && bez.size >= 4 ? orthoRoundedPolylines(pts, radius) : [];
|
|
47676
|
+
if (polys.length === 0) {
|
|
47677
|
+
edraw.push(this.styleOp(job), this.penOp(job), xdotPoints("B", pts));
|
|
47678
|
+
return;
|
|
47679
|
+
}
|
|
47680
|
+
for (const poly of polys) edraw.push(this.styleOp(job), this.penOp(job), xdotPoints("L", poly));
|
|
47681
|
+
}
|
|
47682
|
+
emitTaperedSpline(e, bz, edraw, job) {
|
|
47683
|
+
if (bz === void 0) return;
|
|
47684
|
+
const radfunc = taperfun(e.attrs.get("dir"), isDirected(e.tail.root));
|
|
47685
|
+
const verts = taper(bz, radfunc, job.obj?.penWidth ?? 1);
|
|
47686
|
+
const edgeColor = job.obj?.penColor ?? { type: "string", s: "black" };
|
|
47687
|
+
edraw.push(this.styleOp(job));
|
|
47688
|
+
edraw.push(xdotPenColor(resolveRenderColor("transparent")));
|
|
47689
|
+
edraw.push(xdotFillColor(edgeColor));
|
|
47690
|
+
edraw.push(xdotPoints("P", verts));
|
|
47691
|
+
}
|
|
47692
|
+
/**
|
|
47693
|
+
* Emit a split-along-length `;` multicolor edge spline: split each routed
|
|
47694
|
+
* bezier along its arc length into one sub-curve per color segment, drawn
|
|
47695
|
+
* under that segment's pen. Reuses the same split geometry
|
|
47696
|
+
* (splitSplineByColor) as the SVG path. @see lib/common/emit.c:1975 multicolor
|
|
47697
|
+
*/
|
|
47698
|
+
emitSplitSpline(bzList, colorAttr, edraw, job) {
|
|
47699
|
+
const segs = parseSegs(colorAttr).segs;
|
|
47700
|
+
const firstColor = segs[0]?.color ?? DEFAULT_COLOR5;
|
|
47701
|
+
let endColor = firstColor;
|
|
47702
|
+
for (const bz of bzList) {
|
|
47703
|
+
if (bz === void 0 || bz.size < 4) continue;
|
|
47704
|
+
const split = splitSplineByColor(bz.list.slice(0, bz.size), segs);
|
|
47705
|
+
endColor = split.endColor;
|
|
47706
|
+
for (const c of split.curves) {
|
|
47707
|
+
edraw.push(this.styleOp(job), xdotPenColor(resolveRenderColor(c.color)), xdotPoints("B", c.points));
|
|
47708
|
+
}
|
|
47709
|
+
}
|
|
47710
|
+
return { firstColor, endColor };
|
|
47711
|
+
}
|
|
47712
|
+
/**
|
|
47713
|
+
* Emit the parallel-multicolor edge spline: one offset Bézier per color,
|
|
47714
|
+
* offset SEP=2.0 perpendicular per pass — reusing the same offset geometry
|
|
47715
|
+
* (buildOffsetLists/advanceTmpList) as the SVG parallel-edge path.
|
|
47716
|
+
* @see lib/common/emit.c:2443 (parallel multicolor) / svg-parallel-edge.ts
|
|
47717
|
+
*/
|
|
47718
|
+
emitParallelSpline(bzList, colorAttr, numc, edraw, job) {
|
|
47719
|
+
const segData = bzList.map(
|
|
47720
|
+
(bz) => bz !== void 0 && bz.size >= 4 ? buildOffsetLists(bz.list, (2 + numc) / 2) : { offlist: [], tmplist: [] }
|
|
47721
|
+
);
|
|
47722
|
+
const colors = parseSegs(colorAttr).segs.map((s) => s.color ?? DEFAULT_COLOR5);
|
|
47723
|
+
for (const color of colors) {
|
|
47724
|
+
const pen = xdotPenColor(resolveRenderColor(color));
|
|
47725
|
+
for (const sd of segData) {
|
|
47726
|
+
if (sd.offlist.length === 0) continue;
|
|
47727
|
+
advanceTmpList(sd.tmplist, sd.offlist);
|
|
47728
|
+
edraw.push(this.styleOp(job), pen, xdotPoints("B", sd.tmplist));
|
|
47729
|
+
}
|
|
47730
|
+
}
|
|
47731
|
+
return {
|
|
47732
|
+
headColor: colors[0] ?? DEFAULT_COLOR5,
|
|
47733
|
+
tailColor: colors[1] ?? colors[0] ?? DEFAULT_COLOR5
|
|
47734
|
+
};
|
|
47735
|
+
}
|
|
47736
|
+
/** Emit one arrow's primitive ops into `buf` (pen/fill from the edge color).
|
|
47737
|
+
* arrow_gen sets the default line style ("solid") and the edge penwidth
|
|
47738
|
+
* before each primitive, so a non-default penwidth emits `S setlinewidth(N)`
|
|
47739
|
+
* once (tracked per HDRAW/TDRAW state). @see arrows.c:arrow_gen */
|
|
47740
|
+
emitArrows(buf, ops, job, state3, penOverride) {
|
|
47741
|
+
if (ops === void 0) return;
|
|
47742
|
+
const pen = penOverride !== void 0 ? resolveRenderColor(penOverride) : job.obj?.penColor ?? { type: "string", s: "black" };
|
|
47743
|
+
const pw = job.obj?.penWidth ?? 1;
|
|
47744
|
+
for (const op of ops) {
|
|
47745
|
+
if (Math.abs(pw - this.penwidth[state3]) >= 5e-4) {
|
|
47746
|
+
this.penwidth[state3] = pw;
|
|
47747
|
+
buf.push(xdotStrOp("S ", "setlinewidth(" + trimFixed3(pw) + ")"));
|
|
47748
|
+
}
|
|
47749
|
+
buf.push(xdotStrOp("S ", "solid"));
|
|
47750
|
+
buf.push(xdotPenColor(pen));
|
|
47751
|
+
emitArrowOp(buf, op, pen);
|
|
47752
|
+
}
|
|
47753
|
+
}
|
|
47754
|
+
};
|
|
47755
|
+
|
|
47756
|
+
// src/render/dot.ts
|
|
47757
|
+
var XdotRenderer = class extends EdgeDrawBase {
|
|
46495
47758
|
type = "xdot";
|
|
46496
47759
|
/** @see plugin/core/gvplugin_core.c registration table */
|
|
46497
47760
|
quality = 0;
|
|
47761
|
+
/**
|
|
47762
|
+
* Whether this format attaches the xdot draw attributes before agwrite.
|
|
47763
|
+
* Mirrors C's `job->render.id` switch: FORMAT_DOT runs `attach_attrs(g)` then
|
|
47764
|
+
* `agwrite`, while FORMAT_XDOT runs `attach_attrs_and_arrows` +
|
|
47765
|
+
* `xdot_begin_graph`/`xdot_end_graph` and *then* the same `agwrite`. The
|
|
47766
|
+
* serializer below is that shared `agwrite`; this flag is the only difference.
|
|
47767
|
+
* @see plugin/core/gvrender_core_dot.c:404,418,475,482
|
|
47768
|
+
*/
|
|
47769
|
+
emitDraws = true;
|
|
47770
|
+
/** Draw strings for `obj`, or undefined in a format that emits none (`-Tdot`).
|
|
47771
|
+
* Single gate so the xdot path cannot drift from the dot path. */
|
|
47772
|
+
drawsOf(obj) {
|
|
47773
|
+
return this.emitDraws ? this.draws.get(obj) : void 0;
|
|
47774
|
+
}
|
|
46498
47775
|
/** Per-render draw buffers. Indices 8/9 alias 1; 10/11 alias 5. */
|
|
46499
47776
|
bufs = makeXbufs();
|
|
46500
|
-
/** setlinewidth state per emit_state. @see gvrender_core_dot.c penwidth[] */
|
|
46501
|
-
penwidth = new Array(12).fill(1);
|
|
46502
47777
|
/** text-flag state per emit_state. @see gvrender_core_dot.c textflags[] */
|
|
46503
47778
|
textflags = new Array(12).fill(0);
|
|
46504
47779
|
/** Accumulated draw strings keyed by model object (C: agset on the object). */
|
|
46505
47780
|
draws = /* @__PURE__ */ new Map();
|
|
46506
|
-
/** Clusters in render order — the graphs `endCluster` was actually called for
|
|
46507
|
-
* (from `renderClusters`/`GD_clust`), which is NOT always reachable via
|
|
46508
|
-
* `g.subgraphs`, so it is tracked here rather than re-walked. */
|
|
46509
|
-
clusters = [];
|
|
46510
47781
|
/** edge_in_box gate for the current edge: emit_edge draws nothing (spline OR
|
|
46511
47782
|
* label) for an edge whose content is outside job->clip. Mirrors svg.ts's
|
|
46512
47783
|
* edgeGroupOpen. @see lib/common/emit.c:emit_edge (3039) */
|
|
46513
47784
|
edgeDrawable = true;
|
|
46514
|
-
beginGraph(
|
|
47785
|
+
beginGraph(_g, _job) {
|
|
46515
47786
|
this.bufs = makeXbufs();
|
|
46516
47787
|
this.penwidth = new Array(12).fill(1);
|
|
46517
47788
|
this.textflags = new Array(12).fill(0);
|
|
@@ -46529,7 +47800,7 @@ var XdotRenderer = class {
|
|
|
46529
47800
|
endGraph(g, job) {
|
|
46530
47801
|
const gd = this.flush(0 /* GDraw */);
|
|
46531
47802
|
const gl = this.flush(4 /* GLabel */);
|
|
46532
|
-
if (gd || gl) {
|
|
47803
|
+
if (this.emitDraws && (gd || gl)) {
|
|
46533
47804
|
const set = this.drawsFor(g);
|
|
46534
47805
|
if (gd) set.draw = gd;
|
|
46535
47806
|
if (gl) set.ldraw = escBackslash(gl);
|
|
@@ -46553,19 +47824,25 @@ var XdotRenderer = class {
|
|
|
46553
47824
|
];
|
|
46554
47825
|
const buf = this.bufs[0 /* GDraw */];
|
|
46555
47826
|
buf.push(xdotPenColor(resolveRenderColor("transparent")));
|
|
46556
|
-
|
|
46557
|
-
if (stop !== null) {
|
|
46558
|
-
const angle = Number(g.attrs.get("gradientangle") ?? 0) || 0;
|
|
46559
|
-
const radial = parseStyleFlags(g.attrs.get("style")).radial;
|
|
46560
|
-
const op = radial ? radialGradientOp : linearGradientOp;
|
|
46561
|
-
buf.push(
|
|
46562
|
-
op(corners, resolveRenderColor(stop.fillColor), resolveRenderColor(stop.stopColor), stop.frac, angle)
|
|
46563
|
-
);
|
|
46564
|
-
} else {
|
|
46565
|
-
buf.push(xdotFillColor(resolveRenderColor(fillSpec)));
|
|
46566
|
-
}
|
|
47827
|
+
buf.push(this.backgroundFillOp(g, fillSpec, corners));
|
|
46567
47828
|
buf.push(xdotPoints("P", corners));
|
|
46568
47829
|
}
|
|
47830
|
+
/** The canvas fill op: a two-colour `bgcolor` is a gradient (emit_background →
|
|
47831
|
+
* findStopColor), linear or radial per style; anything else is a flat fill.
|
|
47832
|
+
* @see lib/common/emit.c:1476 emit_background */
|
|
47833
|
+
backgroundFillOp(g, fillSpec, corners) {
|
|
47834
|
+
const stop = fillSpec.includes(":") ? findStopColor(fillSpec) : null;
|
|
47835
|
+
if (stop === null) return xdotFillColor(resolveRenderColor(fillSpec));
|
|
47836
|
+
const angle = Number(g.attrs.get("gradientangle") ?? 0) || 0;
|
|
47837
|
+
const op = parseStyleFlags(g.attrs.get("style")).radial ? radialGradientOp : linearGradientOp;
|
|
47838
|
+
return op(
|
|
47839
|
+
corners,
|
|
47840
|
+
resolveRenderColor(stop.fillColor),
|
|
47841
|
+
resolveRenderColor(stop.stopColor),
|
|
47842
|
+
stop.frac,
|
|
47843
|
+
angle
|
|
47844
|
+
);
|
|
47845
|
+
}
|
|
46569
47846
|
beginNode(_n, _job) {
|
|
46570
47847
|
}
|
|
46571
47848
|
endNode(n, _job) {
|
|
@@ -46604,25 +47881,17 @@ var XdotRenderer = class {
|
|
|
46604
47881
|
} else if (edgeIsTapered(e)) {
|
|
46605
47882
|
this.emitTaperedSpline(e, spl.list[0], edraw, job);
|
|
46606
47883
|
} else if (numc > 0) {
|
|
46607
|
-
this.emitParallelSpline(
|
|
47884
|
+
const c = this.emitParallelSpline(
|
|
47885
|
+
spl.list,
|
|
47886
|
+
colorAttr,
|
|
47887
|
+
numc,
|
|
47888
|
+
edraw,
|
|
47889
|
+
job
|
|
47890
|
+
);
|
|
47891
|
+
tailArrowColor = c.tailColor;
|
|
47892
|
+
headArrowColor = c.headColor;
|
|
46608
47893
|
} else {
|
|
46609
|
-
|
|
46610
|
-
const obj = job.obj;
|
|
46611
|
-
const origStyle = obj !== null ? [...obj.rawStyle] : [];
|
|
46612
|
-
const multi = spl.list.length > 1;
|
|
46613
|
-
for (const bez of spl.list) {
|
|
46614
|
-
const pts = bez.list.slice(0, bez.size);
|
|
46615
|
-
const polys = radius !== null && bez.size >= 4 ? orthoRoundedPolylines(pts, radius) : [];
|
|
46616
|
-
if (polys.length > 0) {
|
|
46617
|
-
for (const poly of polys) edraw.push(this.styleOp(job), this.penOp(job), xdotPoints("L", poly));
|
|
46618
|
-
} else {
|
|
46619
|
-
edraw.push(this.styleOp(job), this.penOp(job), xdotPoints("B", pts));
|
|
46620
|
-
}
|
|
46621
|
-
if (obj !== null && multi && (bez.sflag || bez.eflag)) {
|
|
46622
|
-
obj.rawStyle = origStyle.length > 0 ? origStyle : ["solid"];
|
|
46623
|
-
}
|
|
46624
|
-
}
|
|
46625
|
-
if (obj !== null) obj.rawStyle = origStyle;
|
|
47894
|
+
this.emitPlainSpline(e, spl.list, edraw, job);
|
|
46626
47895
|
}
|
|
46627
47896
|
this.emitArrows(this.bufs[2 /* TDraw */], e.info.tailArrowOps, job, 2 /* TDraw */, tailArrowColor);
|
|
46628
47897
|
this.emitArrows(this.bufs[3 /* HDraw */], e.info.headArrowOps, job, 3 /* HDraw */, headArrowColor);
|
|
@@ -46632,121 +47901,43 @@ var XdotRenderer = class {
|
|
|
46632
47901
|
* -tapered` (the style), a transparent pen, the edge-color fill, then the
|
|
46633
47902
|
* polygon vertices from `taper()` (y-up). @see lib/common/emit.c:2422
|
|
46634
47903
|
*/
|
|
46635
|
-
emitTaperedSpline(e, bz, edraw, job) {
|
|
46636
|
-
if (bz === void 0) return;
|
|
46637
|
-
const radfunc = taperfun(e.attrs.get("dir"), isDirected(e.tail.root));
|
|
46638
|
-
const verts = taper(bz, radfunc, job.obj?.penWidth ?? 1);
|
|
46639
|
-
const edgeColor = job.obj?.penColor ?? { type: "string", s: "black" };
|
|
46640
|
-
edraw.push(this.styleOp(job));
|
|
46641
|
-
edraw.push(xdotPenColor(resolveRenderColor("transparent")));
|
|
46642
|
-
edraw.push(xdotFillColor(edgeColor));
|
|
46643
|
-
edraw.push(xdotPoints("P", verts));
|
|
46644
|
-
}
|
|
46645
|
-
/**
|
|
46646
|
-
* Emit a split-along-length `;` multicolor edge spline: split each routed
|
|
46647
|
-
* bezier along its arc length into one sub-curve per color segment, drawn
|
|
46648
|
-
* under that segment's pen. Reuses the same split geometry
|
|
46649
|
-
* (splitSplineByColor) as the SVG path. @see lib/common/emit.c:1975 multicolor
|
|
46650
|
-
*/
|
|
46651
|
-
emitSplitSpline(bzList, colorAttr, edraw, job) {
|
|
46652
|
-
const segs = parseSegs(colorAttr).segs;
|
|
46653
|
-
const firstColor = segs[0]?.color ?? "black";
|
|
46654
|
-
let endColor = firstColor;
|
|
46655
|
-
for (const bz of bzList) {
|
|
46656
|
-
if (bz === void 0 || bz.size < 4) continue;
|
|
46657
|
-
const split = splitSplineByColor(bz.list.slice(0, bz.size), segs);
|
|
46658
|
-
endColor = split.endColor;
|
|
46659
|
-
for (const c of split.curves) {
|
|
46660
|
-
edraw.push(this.styleOp(job), xdotPenColor(resolveRenderColor(c.color)), xdotPoints("B", c.points));
|
|
46661
|
-
}
|
|
46662
|
-
}
|
|
46663
|
-
return { firstColor, endColor };
|
|
46664
|
-
}
|
|
46665
|
-
/**
|
|
46666
|
-
* Emit the parallel-multicolor edge spline: one offset Bézier per color,
|
|
46667
|
-
* offset SEP=2.0 perpendicular per pass — reusing the same offset geometry
|
|
46668
|
-
* (buildOffsetLists/advanceTmpList) as the SVG parallel-edge path.
|
|
46669
|
-
* @see lib/common/emit.c:2443 (parallel multicolor) / svg-parallel-edge.ts
|
|
46670
|
-
*/
|
|
46671
|
-
emitParallelSpline(bzList, colorAttr, numc, edraw, job) {
|
|
46672
|
-
const segData = bzList.map(
|
|
46673
|
-
(bz) => bz !== void 0 && bz.size >= 4 ? buildOffsetLists(bz.list, (2 + numc) / 2) : { offlist: [], tmplist: [] }
|
|
46674
|
-
);
|
|
46675
|
-
const colors = parseSegs(colorAttr).segs.map((s) => s.color ?? "black");
|
|
46676
|
-
for (const color of colors) {
|
|
46677
|
-
const pen = xdotPenColor(resolveRenderColor(color));
|
|
46678
|
-
for (const sd of segData) {
|
|
46679
|
-
if (sd.offlist.length === 0) continue;
|
|
46680
|
-
advanceTmpList(sd.tmplist, sd.offlist);
|
|
46681
|
-
edraw.push(this.styleOp(job), pen, xdotPoints("B", sd.tmplist));
|
|
46682
|
-
}
|
|
46683
|
-
}
|
|
46684
|
-
}
|
|
46685
|
-
/** Emit one arrow's primitive ops into `buf` (pen/fill from the edge color).
|
|
46686
|
-
* arrow_gen sets the default line style ("solid") and the edge penwidth
|
|
46687
|
-
* before each primitive, so a non-default penwidth emits `S setlinewidth(N)`
|
|
46688
|
-
* once (tracked per HDRAW/TDRAW state). @see arrows.c:arrow_gen */
|
|
46689
|
-
emitArrows(buf, ops, job, state3, penOverride) {
|
|
46690
|
-
if (ops === void 0) return;
|
|
46691
|
-
const pen = penOverride !== void 0 ? resolveRenderColor(penOverride) : job.obj?.penColor ?? { type: "string", s: "black" };
|
|
46692
|
-
const pw = job.obj?.penWidth ?? 1;
|
|
46693
|
-
for (const op of ops) {
|
|
46694
|
-
if (Math.abs(pw - this.penwidth[state3]) >= 5e-4) {
|
|
46695
|
-
this.penwidth[state3] = pw;
|
|
46696
|
-
buf.push(xdotStrOp("S ", "setlinewidth(" + trimFixed3(pw) + ")"));
|
|
46697
|
-
}
|
|
46698
|
-
buf.push(xdotStrOp("S ", "solid"));
|
|
46699
|
-
buf.push(xdotPenColor(pen));
|
|
46700
|
-
switch (op.kind) {
|
|
46701
|
-
case "polygon":
|
|
46702
|
-
if (op.filled) buf.push(xdotFillColor(pen));
|
|
46703
|
-
buf.push(xdotPoints(op.filled ? "P" : "p", op.points));
|
|
46704
|
-
break;
|
|
46705
|
-
case "ellipse":
|
|
46706
|
-
if (op.filled) buf.push(xdotFillColor(pen));
|
|
46707
|
-
buf.push(
|
|
46708
|
-
(op.filled ? "E " : "e ") + xdotPoint(op.center) + xdotNum(op.rx) + " " + xdotNum(op.ry) + " "
|
|
46709
|
-
);
|
|
46710
|
-
break;
|
|
46711
|
-
case "polyline":
|
|
46712
|
-
buf.push(xdotPoints("L", op.points));
|
|
46713
|
-
break;
|
|
46714
|
-
case "bezier":
|
|
46715
|
-
buf.push(xdotPoints("B", op.points));
|
|
46716
|
-
break;
|
|
46717
|
-
}
|
|
46718
|
-
}
|
|
46719
|
-
}
|
|
46720
47904
|
endEdge(e, job) {
|
|
46721
47905
|
if (!this.edgeDrawable) {
|
|
46722
|
-
this.
|
|
46723
|
-
this.resetState(3 /* HDraw */, 2 /* TDraw */);
|
|
46724
|
-
this.penwidth[7 /* HLabel */] = 1;
|
|
46725
|
-
this.penwidth[6 /* TLabel */] = 1;
|
|
47906
|
+
this.resetEdgeState();
|
|
46726
47907
|
return;
|
|
46727
47908
|
}
|
|
46728
47909
|
renderEdgeLabels(e, this, job);
|
|
46729
|
-
|
|
46730
|
-
|
|
46731
|
-
|
|
46732
|
-
|
|
46733
|
-
|
|
46734
|
-
|
|
46735
|
-
|
|
46736
|
-
|
|
46737
|
-
|
|
46738
|
-
|
|
46739
|
-
|
|
46740
|
-
|
|
46741
|
-
|
|
46742
|
-
|
|
47910
|
+
this.attachEdgeDraws(e);
|
|
47911
|
+
this.resetEdgeState();
|
|
47912
|
+
this.textflags[7 /* HLabel */] = 0;
|
|
47913
|
+
this.textflags[6 /* TLabel */] = 0;
|
|
47914
|
+
}
|
|
47915
|
+
/** Flush the six edge buffers and attach the non-empty ones. Label buffers get
|
|
47916
|
+
* put_escaping_backslashes; shape buffers do not.
|
|
47917
|
+
* @see plugin/core/gvrender_core_dot.c:218 */
|
|
47918
|
+
attachEdgeDraws(e) {
|
|
47919
|
+
const spec = [
|
|
47920
|
+
[9 /* EDraw */, "draw", false],
|
|
47921
|
+
[3 /* HDraw */, "hdraw", false],
|
|
47922
|
+
[2 /* TDraw */, "tdraw", false],
|
|
47923
|
+
[11 /* ELabel */, "ldraw", true],
|
|
47924
|
+
[7 /* HLabel */, "hldraw", true],
|
|
47925
|
+
[6 /* TLabel */, "tldraw", true]
|
|
47926
|
+
];
|
|
47927
|
+
const flushed = spec.map(([state3, field, isLabel]) => [field, this.flush(state3), isLabel]);
|
|
47928
|
+
if (!flushed.some(([, s]) => s)) return;
|
|
47929
|
+
const set = this.drawsFor(e);
|
|
47930
|
+
for (const [field, s, isLabel] of flushed) {
|
|
47931
|
+
if (s) set[field] = isLabel ? escBackslash(s) : s;
|
|
46743
47932
|
}
|
|
47933
|
+
}
|
|
47934
|
+
/** Per-edge emit-state reset, run on both the drawable and the early-return
|
|
47935
|
+
* path — identical in C, so it is one helper here rather than two copies. */
|
|
47936
|
+
resetEdgeState() {
|
|
46744
47937
|
this.resetState(9 /* EDraw */, 11 /* ELabel */);
|
|
46745
47938
|
this.resetState(3 /* HDraw */, 2 /* TDraw */);
|
|
46746
47939
|
this.penwidth[7 /* HLabel */] = 1;
|
|
46747
47940
|
this.penwidth[6 /* TLabel */] = 1;
|
|
46748
|
-
this.textflags[7 /* HLabel */] = 0;
|
|
46749
|
-
this.textflags[6 /* TLabel */] = 0;
|
|
46750
47941
|
}
|
|
46751
47942
|
beginCluster(_sg, _job) {
|
|
46752
47943
|
}
|
|
@@ -46872,273 +48063,19 @@ var XdotRenderer = class {
|
|
|
46872
48063
|
return d;
|
|
46873
48064
|
}
|
|
46874
48065
|
// --- serialization (agwrite-at-end) ------------------------------------
|
|
46875
|
-
|
|
46876
|
-
|
|
46877
|
-
|
|
46878
|
-
|
|
46879
|
-
|
|
46880
|
-
* double-escaped). The byte-length prefix stays on the UNescaped text (the
|
|
46881
|
-
* parser un-escapes before parseXDot re-reads it), matching native exactly.
|
|
46882
|
-
* @see lib/cgraph/write.c:_agstrcanon (135-167)
|
|
46883
|
-
*/
|
|
46884
|
-
drawAttr(key, value) {
|
|
46885
|
-
return key + '="' + agcanonEscape(value) + '"';
|
|
46886
|
-
}
|
|
46887
|
-
/** `llx,lly,urx,ury` from a graph's layout bb. */
|
|
46888
|
-
bbStr(g) {
|
|
46889
|
-
const bb = g.info.bb;
|
|
46890
|
-
return gfmt5(bb.ll.x) + "," + gfmt5(bb.ll.y) + "," + gfmt5(bb.ur.x) + "," + gfmt5(bb.ur.y);
|
|
46891
|
-
}
|
|
46892
|
-
/**
|
|
46893
|
-
* Serialize the whole laid-out graph to xdot DOT text — a faithful port of
|
|
46894
|
-
* cgraph's agwrite (lib/cgraph/write.c). Recurses the subgraph tree
|
|
46895
|
-
* (write_subgs/write_body), scoping each node/edge to the subgraph(s) it
|
|
46896
|
-
* belongs to via preorder numbers (write_node_test/write_edge_test), and
|
|
46897
|
-
* re-emits an object bare (no attrs) on any scope after the first
|
|
46898
|
-
* (attrs_written). This reproduces native's per-subgraph edge re-declarations
|
|
46899
|
-
* — e.g. an edge in a rank=same subgraph is drawn once, then re-declared bare.
|
|
46900
|
-
* @see lib/cgraph/write.c:agwrite
|
|
46901
|
-
*/
|
|
46902
|
-
serialize(g) {
|
|
46903
|
-
const ctx = {
|
|
46904
|
-
out: [],
|
|
46905
|
-
preorder: /* @__PURE__ */ new Map(),
|
|
46906
|
-
nodeLW: /* @__PURE__ */ new Map(),
|
|
46907
|
-
edgeLW: /* @__PURE__ */ new Map(),
|
|
46908
|
-
attrsWritten: /* @__PURE__ */ new Set(),
|
|
46909
|
-
level: 0
|
|
46910
|
-
};
|
|
46911
|
-
this.subgdfs(g, 1, ctx.preorder);
|
|
46912
|
-
this.writeHdr(g, true, ctx);
|
|
46913
|
-
this.writeBody(g, ctx);
|
|
46914
|
-
this.writeTrl(ctx);
|
|
46915
|
-
return ctx.out.join("");
|
|
46916
|
-
}
|
|
46917
|
-
/** Preorder-number the subgraph tree. @see write.c:subgdfs */
|
|
46918
|
-
subgdfs(g, ix, preorder) {
|
|
46919
|
-
let ix0 = ix;
|
|
46920
|
-
preorder.set(g, ix0);
|
|
46921
|
-
for (const sub of g.subgraphs.values()) ix0 = this.subgdfs(sub, ix0, preorder);
|
|
46922
|
-
return ix0 + 1;
|
|
46923
|
-
}
|
|
46924
|
-
/** A subgraph is anonymous when its name is empty or a `%N` local name. */
|
|
46925
|
-
isAnonymous(g) {
|
|
46926
|
-
return g.name.length === 0 || g.name.charCodeAt(0) === 37;
|
|
46927
|
-
}
|
|
46928
|
-
/** A graph carries a `bb` attribute when it is the root or has a layout bb
|
|
46929
|
-
* (clusters, and any subgraph output.c computed a box for). Native seeds `bb`
|
|
46930
|
-
* on every graph via safe_dcl, so the value differs (set vs empty "") between
|
|
46931
|
-
* a boxed graph and an unboxed child — the driver that makes an anon subgraph
|
|
46932
|
-
* under the root or a cluster "relevant". @see lib/common/output.c:safe_dcl */
|
|
46933
|
-
hasBb(g) {
|
|
46934
|
-
return g === g.root || this.clusters.includes(g);
|
|
46935
|
-
}
|
|
46936
|
-
/** Anonymous subgraph with no own node/edge defaults and no graph attrs
|
|
46937
|
-
* differing from its parent → inlined into the parent. Native compares every
|
|
46938
|
-
* graph attr over the root attr dict; the load-bearing ones are `bb` (set on
|
|
46939
|
-
* the parent, empty on the child) and `rank`. @see write.c:irrelevant_subgraph */
|
|
46940
|
-
irrelevantSubgraph(g) {
|
|
46941
|
-
if (!this.isAnonymous(g)) return false;
|
|
46942
|
-
if (this.clusters.includes(g)) return false;
|
|
46943
|
-
if (g.nodeDefaults.size > 0 || g.edgeDefaults.size > 0) return false;
|
|
46944
|
-
if (g.parent) {
|
|
46945
|
-
if (this.hasBb(g) !== this.hasBb(g.parent)) return false;
|
|
46946
|
-
for (const [k, v] of g.attrs) {
|
|
46947
|
-
if (g.parent.attrs.get(k) !== v) return false;
|
|
46948
|
-
}
|
|
46949
|
-
} else if (g.attrs.size > 0) {
|
|
46950
|
-
return false;
|
|
46951
|
-
}
|
|
46952
|
-
return true;
|
|
46953
|
-
}
|
|
46954
|
-
/** Non-draw graph attrs a subgraph emits (rank for rank=same; clusters use
|
|
46955
|
-
* clusterAttrs). Only comparator-relevant fields need be exact.
|
|
46956
|
-
*
|
|
46957
|
-
* `rec_attach_bb` walks the ROOT and then `GD_clust` recursively, so a
|
|
46958
|
-
* subgraph the layout did not box is attached NEITHER `bb` NOR the label
|
|
46959
|
-
* triple — both keep the INPUT's values, which agwrite echoes. circo and
|
|
46960
|
-
* twopi lay out no clusters at all (`GD_clust` empty — mirrored here by
|
|
46961
|
-
* `this.clusters`, filled from endCluster), so on a re-fed dot output their
|
|
46962
|
-
* `cluster0` comes back carrying the *previous* run's `bb` and `lp`.
|
|
46963
|
-
* @see lib/common/output.c:249 rec_attach_bb */
|
|
46964
|
-
subgGraphAttrs(sg) {
|
|
46965
|
-
if (this.clusters.includes(sg)) return this.clusterAttrs(sg);
|
|
46966
|
-
const parts = [];
|
|
46967
|
-
const rank3 = sg.attrs.get("rank");
|
|
46968
|
-
if (rank3 !== void 0) parts.push("rank=" + xdotId(rank3));
|
|
46969
|
-
parts.push(...echoAttr(sg.attrs, "bb"));
|
|
46970
|
-
parts.push(...graphLabelAttrs(sg));
|
|
46971
|
-
return parts.join(" ");
|
|
46972
|
-
}
|
|
46973
|
-
indent(ctx) {
|
|
46974
|
-
return " ".repeat(ctx.level);
|
|
46975
|
-
}
|
|
46976
|
-
/** @see write.c:write_hdr */
|
|
46977
|
-
writeHdr(g, top, ctx) {
|
|
46978
|
-
if (top) {
|
|
46979
|
-
const strict = g.kind === "strict-directed" || g.kind === "strict-undirected" ? "strict " : "";
|
|
46980
|
-
const kw = isDirected(g) ? "digraph" : "graph";
|
|
46981
|
-
const nm = g.name.length > 0 && !this.isAnonymous(g) ? xdotId(g.name) + " " : "";
|
|
46982
|
-
ctx.out.push(strict + kw + " " + nm + "{\n");
|
|
46983
|
-
ctx.level++;
|
|
46984
|
-
ctx.out.push(this.indent(ctx) + "graph [" + this.graphAttrs(g) + "];\n");
|
|
46985
|
-
ctx.out.push(this.indent(ctx) + 'node [label="\\N"];\n');
|
|
46986
|
-
} else {
|
|
46987
|
-
const nm = this.isAnonymous(g) ? "" : "subgraph " + xdotId(g.name) + " ";
|
|
46988
|
-
ctx.out.push(this.indent(ctx) + nm + "{\n");
|
|
46989
|
-
ctx.level++;
|
|
46990
|
-
const ga = this.subgGraphAttrs(g);
|
|
46991
|
-
if (ga) ctx.out.push(this.indent(ctx) + "graph [" + ga + "];\n");
|
|
46992
|
-
}
|
|
46993
|
-
}
|
|
46994
|
-
/** @see write.c:write_trl */
|
|
46995
|
-
writeTrl(ctx) {
|
|
46996
|
-
ctx.level--;
|
|
46997
|
-
ctx.out.push(this.indent(ctx) + "}\n");
|
|
46998
|
-
}
|
|
46999
|
-
/** @see write.c:write_subgs */
|
|
47000
|
-
writeSubgs(g, ctx) {
|
|
47001
|
-
for (const sub of g.subgraphs.values()) {
|
|
47002
|
-
if (this.irrelevantSubgraph(sub)) {
|
|
47003
|
-
this.writeSubgs(sub, ctx);
|
|
47004
|
-
} else {
|
|
47005
|
-
this.writeHdr(sub, false, ctx);
|
|
47006
|
-
this.writeBody(sub, ctx);
|
|
47007
|
-
this.writeTrl(ctx);
|
|
47008
|
-
}
|
|
47009
|
-
}
|
|
47010
|
-
}
|
|
47011
|
-
/** @see write.c:write_body — subgraphs, then this scope's nodes and edges. */
|
|
47012
|
-
writeBody(g, ctx) {
|
|
47013
|
-
this.writeSubgs(g, ctx);
|
|
47014
|
-
for (const n of g.nodes.values()) {
|
|
47015
|
-
if (this.writeNodeTest(g, n, ctx)) this.writeNode(g, n, ctx);
|
|
47016
|
-
let prev = n;
|
|
47017
|
-
for (const e of n.outEdges(g)) {
|
|
47018
|
-
if (prev !== e.head && this.writeNodeTest(g, e.head, ctx)) {
|
|
47019
|
-
this.writeNode(g, e.head, ctx);
|
|
47020
|
-
prev = e.head;
|
|
47021
|
-
}
|
|
47022
|
-
if (this.writeEdgeTest(g, e, ctx)) this.writeEdge(g, e, ctx);
|
|
47023
|
-
}
|
|
47024
|
-
}
|
|
47025
|
-
}
|
|
47026
|
-
/** @see write.c:write_node_test — every xdot node carries pos/size, so it is
|
|
47027
|
-
* never "default"; write it in the first scope that has not yet emitted it.
|
|
47028
|
-
* Cluster nodes (fdp compound proxies, ND_clustnode) are never declared —
|
|
47029
|
-
* C's write_plain/writenodeandport suppress them. @see lib/common/output.c:146 */
|
|
47030
|
-
writeNodeTest(g, n, ctx) {
|
|
47031
|
-
if (n.info.clustnode) return false;
|
|
47032
|
-
return (ctx.nodeLW.get(n) ?? 0) < ctx.preorder.get(g);
|
|
47033
|
-
}
|
|
47034
|
-
/** Emitted node name: a cluster node's synthetic `__i:<cluster>` id is written
|
|
47035
|
-
* as the cluster name it stands for. @see lib/common/output.c:114 */
|
|
47036
|
-
emitNodeName(n) {
|
|
47037
|
-
if (n.info.clustnode) {
|
|
47038
|
-
const i = n.name.indexOf(":");
|
|
47039
|
-
if (i >= 0) return n.name.slice(i + 1);
|
|
47040
|
-
}
|
|
47041
|
-
return n.name;
|
|
47042
|
-
}
|
|
47043
|
-
/** @see write.c:write_edge_test */
|
|
47044
|
-
writeEdgeTest(g, e, ctx) {
|
|
47045
|
-
return (ctx.edgeLW.get(e) ?? 0) < ctx.preorder.get(g);
|
|
47046
|
-
}
|
|
47047
|
-
/** @see write.c:write_node */
|
|
47048
|
-
writeNode(g, n, ctx) {
|
|
47049
|
-
let s = this.indent(ctx) + xdotId(this.emitNodeName(n));
|
|
47050
|
-
if (!ctx.attrsWritten.has(n)) {
|
|
47051
|
-
s += " [" + this.nodeAttrs(n) + "]";
|
|
47052
|
-
ctx.attrsWritten.add(n);
|
|
47053
|
-
}
|
|
47054
|
-
ctx.out.push(s + ";\n");
|
|
47055
|
-
ctx.nodeLW.set(n, ctx.preorder.get(g));
|
|
47056
|
-
}
|
|
47057
|
-
/** @see write.c:write_edge — attrs only on first emission, bare thereafter. */
|
|
47058
|
-
writeEdge(g, e, ctx) {
|
|
47059
|
-
const conn = edgeConnector(isDirected(g));
|
|
47060
|
-
let s = this.indent(ctx) + xdotId(this.emitNodeName(e.tail)) + " " + conn + " " + xdotId(this.emitNodeName(e.head));
|
|
47061
|
-
if (!ctx.attrsWritten.has(e)) {
|
|
47062
|
-
const attrs = this.edgeAttrStr(e);
|
|
47063
|
-
if (attrs) s += " [" + attrs + "]";
|
|
47064
|
-
ctx.attrsWritten.add(e);
|
|
47065
|
-
}
|
|
47066
|
-
ctx.out.push(s + ";\n");
|
|
47067
|
-
ctx.edgeLW.set(e, ctx.preorder.get(g));
|
|
48066
|
+
};
|
|
48067
|
+
var DotRenderer = class extends XdotRenderer {
|
|
48068
|
+
type = "dot";
|
|
48069
|
+
emitDraws = false;
|
|
48070
|
+
textspan(_pos, _span, _job) {
|
|
47068
48071
|
}
|
|
47069
|
-
|
|
47070
|
-
graphAttrs(g) {
|
|
47071
|
-
const d = this.draws.get(g);
|
|
47072
|
-
const parts = [];
|
|
47073
|
-
if (d?.draw) parts.push(this.drawAttr("_draw_", d.draw));
|
|
47074
|
-
if (d?.ldraw) parts.push(this.drawAttr("_ldraw_", d.ldraw));
|
|
47075
|
-
parts.push('bb="' + this.bbStr(g) + '"');
|
|
47076
|
-
parts.push(...graphLabelAttrs(g));
|
|
47077
|
-
parts.push('xdotversion="' + XDOT_VERSION + '"');
|
|
47078
|
-
return parts.join(" ");
|
|
48072
|
+
ellipse(_c, _rx, _ry, _f, _j) {
|
|
47079
48073
|
}
|
|
47080
|
-
|
|
47081
|
-
* the graph has clusters (even empty, e.g. peripheries=0), so emit it
|
|
47082
|
-
* unconditionally; `_ldraw_` only when the cluster has a label.
|
|
47083
|
-
* @see plugin/core/gvrender_core_dot.c:284 xdot_end_cluster */
|
|
47084
|
-
clusterAttrs(sg) {
|
|
47085
|
-
const d = this.draws.get(sg);
|
|
47086
|
-
const parts = [this.drawAttr("_draw_", d?.draw ?? "")];
|
|
47087
|
-
if (d?.ldraw) parts.push(this.drawAttr("_ldraw_", d.ldraw));
|
|
47088
|
-
if (sg.info.bb) parts.push('bb="' + this.bbStr(sg) + '"');
|
|
47089
|
-
parts.push(...graphLabelAttrs(sg));
|
|
47090
|
-
return parts.join(" ");
|
|
48074
|
+
polygon(_pts, _filled, _job) {
|
|
47091
48075
|
}
|
|
47092
|
-
|
|
47093
|
-
nodeAttrs(n) {
|
|
47094
|
-
const info = n.info;
|
|
47095
|
-
let s = 'pos="' + gfmt5(info.coord.x) + "," + gfmt5(info.coord.y) + '" width=' + gfmt5((info.lw + info.rw) / 72) + " height=" + gfmt5(info.ht / 72);
|
|
47096
|
-
const xlabel = info.xlabel;
|
|
47097
|
-
if (xlabel && xlabel.set) s += ' xlp="' + lpStr(xlabel.pos) + '"';
|
|
47098
|
-
else s += pfx(echoAttr(n.attrs, "xlp"));
|
|
47099
|
-
const shape = info.shape;
|
|
47100
|
-
if (shape?.name === "record") {
|
|
47101
|
-
const rects = [];
|
|
47102
|
-
appendRecordRects(n, info.shape_info, rects);
|
|
47103
|
-
s += ' rects="' + rects.join(" ") + '"';
|
|
47104
|
-
} else {
|
|
47105
|
-
s += pfx(echoAttr(n.attrs, "rects"));
|
|
47106
|
-
}
|
|
47107
|
-
const d = this.draws.get(n);
|
|
47108
|
-
if (d?.draw) s += " " + this.drawAttr("_draw_", d.draw);
|
|
47109
|
-
if (d?.ldraw) s += " " + this.drawAttr("_ldraw_", d.ldraw);
|
|
47110
|
-
return s;
|
|
48076
|
+
bezier(_pts, _filled, _job) {
|
|
47111
48077
|
}
|
|
47112
|
-
|
|
47113
|
-
edgeAttrStr(e) {
|
|
47114
|
-
const d = this.draws.get(e);
|
|
47115
|
-
const parts = [];
|
|
47116
|
-
if (d?.draw) parts.push(this.drawAttr("_draw_", d.draw));
|
|
47117
|
-
if (d?.ldraw) parts.push(this.drawAttr("_ldraw_", d.ldraw));
|
|
47118
|
-
if (d?.hdraw) parts.push(this.drawAttr("_hdraw_", d.hdraw));
|
|
47119
|
-
if (d?.tdraw) parts.push(this.drawAttr("_tdraw_", d.tdraw));
|
|
47120
|
-
if (d?.hldraw) parts.push(this.drawAttr("_hldraw_", d.hldraw));
|
|
47121
|
-
if (d?.tldraw) parts.push(this.drawAttr("_tldraw_", d.tldraw));
|
|
47122
|
-
const pos = formatEdgePos(e);
|
|
47123
|
-
if (pos) {
|
|
47124
|
-
parts.push(pos);
|
|
47125
|
-
} else {
|
|
47126
|
-
parts.push(...echoAttr(e.attrs, "pos"));
|
|
47127
|
-
}
|
|
47128
|
-
const attached = edgeAttrsAttached(e);
|
|
47129
|
-
const info = e.info;
|
|
47130
|
-
if (attached && info.label) parts.push('lp="' + lpStr(info.label.pos) + '"');
|
|
47131
|
-
else parts.push(...echoAttr(e.attrs, "lp"));
|
|
47132
|
-
if (attached && info.xlabel && info.xlabel.set) {
|
|
47133
|
-
parts.push('xlp="' + lpStr(info.xlabel.pos) + '"');
|
|
47134
|
-
} else parts.push(...echoAttr(e.attrs, "xlp"));
|
|
47135
|
-
if (attached && info.head_label) {
|
|
47136
|
-
parts.push('head_lp="' + lpStr(info.head_label.pos) + '"');
|
|
47137
|
-
} else parts.push(...echoAttr(e.attrs, "head_lp"));
|
|
47138
|
-
if (attached && info.tail_label) {
|
|
47139
|
-
parts.push('tail_lp="' + lpStr(info.tail_label.pos) + '"');
|
|
47140
|
-
} else parts.push(...echoAttr(e.attrs, "tail_lp"));
|
|
47141
|
-
return parts.join(" ");
|
|
48078
|
+
polyline(_pts, _job) {
|
|
47142
48079
|
}
|
|
47143
48080
|
};
|
|
47144
48081
|
function createDotRenderer() {
|
|
@@ -50893,7 +51830,7 @@ function portStringOf(item) {
|
|
|
50893
51830
|
if (ni.port === null || ni.port === "") return "";
|
|
50894
51831
|
return ni.compass ? ni.port + ":" + ni.compass : ni.port;
|
|
50895
51832
|
}
|
|
50896
|
-
function
|
|
51833
|
+
function effectiveNodeDefaults2(scope) {
|
|
50897
51834
|
const eff = /* @__PURE__ */ new Map();
|
|
50898
51835
|
for (let g = scope; g !== null; g = g.parent) {
|
|
50899
51836
|
for (const [k, v] of g.nodeDefaults) {
|
|
@@ -50921,7 +51858,7 @@ var NodeRegistry = class {
|
|
|
50921
51858
|
const existing = root.nodes.get(name);
|
|
50922
51859
|
if (existing !== void 0) return existing;
|
|
50923
51860
|
const node = new Node(this.nextId++, name, root);
|
|
50924
|
-
node.nodeDefaultsSnapshot =
|
|
51861
|
+
node.nodeDefaultsSnapshot = effectiveNodeDefaults2(scope);
|
|
50925
51862
|
root.nodes.set(name, node);
|
|
50926
51863
|
return node;
|
|
50927
51864
|
}
|
|
@@ -50949,7 +51886,7 @@ var NameCollector = class _NameCollector {
|
|
|
50949
51886
|
}
|
|
50950
51887
|
}
|
|
50951
51888
|
};
|
|
50952
|
-
var StmtProcessor = class {
|
|
51889
|
+
var StmtProcessor = class _StmtProcessor {
|
|
50953
51890
|
constructor(registry) {
|
|
50954
51891
|
this.registry = registry;
|
|
50955
51892
|
}
|
|
@@ -50968,6 +51905,41 @@ var StmtProcessor = class {
|
|
|
50968
51905
|
* @see lib/cgraph/id.c:agnameof (`'%' + id`)
|
|
50969
51906
|
*/
|
|
50970
51907
|
anonCounter = 0;
|
|
51908
|
+
/**
|
|
51909
|
+
* Strict-graph edge index: canonical endpoint-pair key → the edge already
|
|
51910
|
+
* created for it. `agedge` on a strict graph probes for a pre-existing edge
|
|
51911
|
+
* and returns it instead of creating a second one, so `strict digraph
|
|
51912
|
+
* { a->b; a->b }` holds ONE edge. A Map (rather than scanning root.edges)
|
|
51913
|
+
* keeps that probe O(1); it stays empty for non-strict graphs.
|
|
51914
|
+
* @see lib/cgraph/edge.c:agedge (agfindedge_by_key probe, :262-287)
|
|
51915
|
+
*/
|
|
51916
|
+
strictEdges = /* @__PURE__ */ new Map();
|
|
51917
|
+
/**
|
|
51918
|
+
* Canonical key for a strict-graph endpoint pair. C probes (t,h) and then,
|
|
51919
|
+
* for an UNDIRECTED graph, (h,t) — so an undirected pair matches either way
|
|
51920
|
+
* round and is canonicalized here by node id; a directed pair is not.
|
|
51921
|
+
* @see lib/cgraph/edge.c:agedge:274-275 (`if (e == NULL && agisundirected(g))`)
|
|
51922
|
+
*/
|
|
51923
|
+
static strictKey(tail, head, undirected) {
|
|
51924
|
+
const swap2 = undirected && head.id < tail.id;
|
|
51925
|
+
return swap2 ? `${head.id}\0${tail.id}` : `${tail.id}\0${head.id}`;
|
|
51926
|
+
}
|
|
51927
|
+
/**
|
|
51928
|
+
* Apply the DOT-syntax `:port:compass` endpoints to an edge's attrs.
|
|
51929
|
+
*
|
|
51930
|
+
* C's precedence is explicit attr > `:port` syntax > edge default (verified
|
|
51931
|
+
* against the oracle: with `edge [headport=n]` declared, `a:e -> b:sw` yields
|
|
51932
|
+
* headport `sw`, while `c:e -> d:sw [headport=w]` yields `w`). The guard is
|
|
51933
|
+
* therefore against THIS statement's own attribute list — not the edge's
|
|
51934
|
+
* accumulated map, which also holds inherited defaults and, for a strict
|
|
51935
|
+
* duplicate, the earlier statement's values.
|
|
51936
|
+
* @see lib/cgraph/grammar.y:396 (mkport)
|
|
51937
|
+
*/
|
|
51938
|
+
static applySyntaxPorts(target, stmtAttrs, tailPort, headPort) {
|
|
51939
|
+
const setByStmt = (k) => stmtAttrs.some((a) => a.key === k);
|
|
51940
|
+
if (tailPort && !setByStmt("tailport")) target.set("tailport", tailPort);
|
|
51941
|
+
if (headPort && !setByStmt("headport")) target.set("headport", headPort);
|
|
51942
|
+
}
|
|
50971
51943
|
/** Consume one anonymous id, returning cgraph's `2*counter+1`. */
|
|
50972
51944
|
nextAnonId() {
|
|
50973
51945
|
return this.anonCounter++ * 2 + 1;
|
|
@@ -50983,12 +51955,21 @@ var StmtProcessor = class {
|
|
|
50983
51955
|
}
|
|
50984
51956
|
processAssign(stmt, graph) {
|
|
50985
51957
|
graph.attrs.set(stmt.key, normaliseAttrValue(stmt.value));
|
|
50986
|
-
|
|
51958
|
+
this.declareGraphAttr(graph, stmt.key);
|
|
51959
|
+
}
|
|
51960
|
+
/** Record a graph-attribute declaration, noting whether THIS scope was the
|
|
51961
|
+
* first to declare the key anywhere — the branch selector in cgraph's
|
|
51962
|
+
* setattr. @see lib/cgraph/attr.c:257 · model/graph.ts firstGraphDecl */
|
|
51963
|
+
declareGraphAttr(graph, key) {
|
|
51964
|
+
if (!graph.root.declaredGraphAttrs.has(key)) {
|
|
51965
|
+
(graph.firstGraphDecl ??= /* @__PURE__ */ new Set()).add(key);
|
|
51966
|
+
graph.root.declaredGraphAttrs.add(key);
|
|
51967
|
+
}
|
|
50987
51968
|
}
|
|
50988
51969
|
processAttr(stmt, graph) {
|
|
50989
51970
|
if (stmt.target === "graph") {
|
|
50990
51971
|
applyAttrs(stmt.attrs, graph.attrs);
|
|
50991
|
-
for (const a of stmt.attrs)
|
|
51972
|
+
for (const a of stmt.attrs) this.declareGraphAttr(graph, a.key);
|
|
50992
51973
|
} else if (stmt.target === "node") {
|
|
50993
51974
|
applyAttrs(stmt.attrs, graph.nodeDefaults);
|
|
50994
51975
|
} else {
|
|
@@ -51023,7 +52004,10 @@ var StmtProcessor = class {
|
|
|
51023
52004
|
buildGraph(stmt.stmts, sg, root, directed, this);
|
|
51024
52005
|
for (const key of GRAPH_LABEL_INHERIT_KEYS) {
|
|
51025
52006
|
const v = sg.graphDefaultsSnapshot.get(key);
|
|
51026
|
-
if (v !== void 0 && !sg.attrs.has(key))
|
|
52007
|
+
if (v !== void 0 && !sg.attrs.has(key)) {
|
|
52008
|
+
sg.attrs.set(key, v);
|
|
52009
|
+
(sg.seededAttrs ??= /* @__PURE__ */ new Set()).add(key);
|
|
52010
|
+
}
|
|
51027
52011
|
}
|
|
51028
52012
|
graph.subgraphs.set(sgName, sg);
|
|
51029
52013
|
return sg;
|
|
@@ -51073,14 +52057,28 @@ var StmtProcessor = class {
|
|
|
51073
52057
|
connectEndpoints(tailEnd, headEnd, attrs, graph, root) {
|
|
51074
52058
|
const tailPort = tailEnd.port;
|
|
51075
52059
|
const headPort = headEnd.port;
|
|
52060
|
+
const strict = root.kind === "strict-directed" || root.kind === "strict-undirected";
|
|
52061
|
+
const undirected = root.kind === "strict-undirected";
|
|
51076
52062
|
for (const tail of tailEnd.nodes) {
|
|
51077
52063
|
for (const head of headEnd.nodes) {
|
|
52064
|
+
const key = strict ? _StmtProcessor.strictKey(tail, head, undirected) : "";
|
|
52065
|
+
const existing = strict ? this.strictEdges.get(key) : void 0;
|
|
52066
|
+
if (existing !== void 0) {
|
|
52067
|
+
applyAttrs(attrs, existing.attrs);
|
|
52068
|
+
_StmtProcessor.applySyntaxPorts(existing.attrs, attrs, tailPort, headPort);
|
|
52069
|
+
for (let g = graph; g !== null && g !== root; g = g.parent) {
|
|
52070
|
+
g.nodes.set(tail.name, tail);
|
|
52071
|
+
g.nodes.set(head.name, head);
|
|
52072
|
+
if (!g.edges.includes(existing)) g.edges.push(existing);
|
|
52073
|
+
}
|
|
52074
|
+
continue;
|
|
52075
|
+
}
|
|
51078
52076
|
const edge = new Edge(tail, head, "");
|
|
51079
52077
|
this.advanceAnonId();
|
|
51080
52078
|
applyAttrs(attrs, edge.attrs);
|
|
52079
|
+
_StmtProcessor.applySyntaxPorts(edge.attrs, attrs, tailPort, headPort);
|
|
51081
52080
|
this.snapshotEdgeDefaults(edge, graph);
|
|
51082
|
-
if (
|
|
51083
|
-
if (headPort && !edge.attrs.has("headport")) edge.attrs.set("headport", headPort);
|
|
52081
|
+
if (strict) this.strictEdges.set(key, edge);
|
|
51084
52082
|
root.edges.push(edge);
|
|
51085
52083
|
edge.graphSeq = root.edges.length;
|
|
51086
52084
|
for (let g = graph; g !== null && g !== root; g = g.parent) {
|
|
@@ -51823,596 +52821,217 @@ function graphEffectiveAttrs(g, top) {
|
|
|
51823
52821
|
for (const [k, v] of g.attrs) m.set(k, v);
|
|
51824
52822
|
return m;
|
|
51825
52823
|
}
|
|
51826
|
-
function isEdgeLabelDeclared(g) {
|
|
51827
|
-
for (const e of g.edges) if (e.attrs.has("label")) return true;
|
|
51828
|
-
const walk = (sg) => {
|
|
51829
|
-
if (sg.edgeDefaults.has("label")) return true;
|
|
51830
|
-
for (const [, sub] of sg.subgraphs) if (walk(sub)) return true;
|
|
51831
|
-
return false;
|
|
51832
|
-
};
|
|
51833
|
-
return walk(g);
|
|
51834
|
-
}
|
|
51835
|
-
function dfsSubgraphs(g, out) {
|
|
51836
|
-
for (const [, sg] of g.subgraphs) {
|
|
51837
|
-
out.push(sg);
|
|
51838
|
-
dfsSubgraphs(sg, out);
|
|
51839
|
-
}
|
|
51840
|
-
}
|
|
51841
|
-
function assignIds(g, doXDot, draws) {
|
|
51842
|
-
const dfsSubs = [];
|
|
51843
|
-
dfsSubgraphs(g, dfsSubs);
|
|
51844
|
-
const subgGid = /* @__PURE__ */ new Map();
|
|
51845
|
-
dfsSubs.forEach((sg, i) => subgGid.set(sg, i));
|
|
51846
|
-
const sgcnt = dfsSubs.length;
|
|
51847
|
-
const nodeGid = /* @__PURE__ */ new Map();
|
|
51848
|
-
let ncnt = 0;
|
|
51849
|
-
for (const [, n] of g.nodes) nodeGid.set(n, sgcnt + ncnt++);
|
|
51850
|
-
const edgeGid = /* @__PURE__ */ new Map();
|
|
51851
|
-
let ecnt = 0;
|
|
51852
|
-
for (const { e } of canonicalRootEdges(g)) edgeGid.set(e, ecnt++);
|
|
51853
|
-
return {
|
|
51854
|
-
doXDot,
|
|
51855
|
-
draws,
|
|
51856
|
-
sgcnt,
|
|
51857
|
-
subgGid,
|
|
51858
|
-
nodeGid,
|
|
51859
|
-
edgeGid,
|
|
51860
|
-
dfsSubs,
|
|
51861
|
-
edgeLabelDeclared: isEdgeLabelDeclared(g),
|
|
51862
|
-
graphLabelDeclared: isGraphLabelDeclared(g),
|
|
51863
|
-
isLatin: g.info.charset === CHAR_LATIN1
|
|
51864
|
-
};
|
|
51865
|
-
}
|
|
51866
|
-
function emitAttrs(o, base, overlay, ctx) {
|
|
51867
|
-
const merged = new Map(base);
|
|
51868
|
-
if (overlay !== void 0) for (const [k, v] of overlay) merged.set(k, v);
|
|
51869
|
-
for (const [k, rawV] of merged) {
|
|
51870
|
-
const v = isHtmlValue(rawV) ? htmlValueContent(rawV) : rawV;
|
|
51871
|
-
if (v === "" && k !== "label") continue;
|
|
51872
|
-
if (ctx.doXDot && isXDot(k)) {
|
|
51873
|
-
o[k] = drawStringToOps(v, ctx.isLatin);
|
|
51874
|
-
} else if (!isXDot(k)) {
|
|
51875
|
-
o[k] = v;
|
|
51876
|
-
}
|
|
51877
|
-
}
|
|
51878
|
-
}
|
|
51879
|
-
function graphLabel(info) {
|
|
51880
|
-
const l = info.label;
|
|
51881
|
-
if (l !== void 0 && l !== null && typeof l === "object" && "pos" in l) {
|
|
51882
|
-
return l;
|
|
51883
|
-
}
|
|
51884
|
-
return void 0;
|
|
51885
|
-
}
|
|
51886
|
-
function labelPos(l) {
|
|
51887
|
-
return gfmt5(l.pos.x) + "," + gfmt5(l.pos.y);
|
|
51888
|
-
}
|
|
51889
|
-
function attachEdgeLabelPositions(o, e) {
|
|
51890
|
-
if (!e.info.spl) return;
|
|
51891
|
-
if (e.info.label !== void 0) o.lp = labelPos(e.info.label);
|
|
51892
|
-
if (e.info.head_label !== void 0) o.head_lp = labelPos(e.info.head_label);
|
|
51893
|
-
if (e.info.tail_label !== void 0) o.tail_lp = labelPos(e.info.tail_label);
|
|
51894
|
-
const xl = e.info.xlabel;
|
|
51895
|
-
if (xl !== void 0 && xl.set) o.xlp = labelPos(xl);
|
|
51896
|
-
}
|
|
51897
|
-
function attachNodeXlp(o, n) {
|
|
51898
|
-
const xl = n.info.xlabel;
|
|
51899
|
-
if (xl !== void 0 && xl.set) o.xlp = labelPos(xl);
|
|
51900
|
-
}
|
|
51901
|
-
function attachGraphLabelGeom(o, g) {
|
|
51902
|
-
const lbl = graphLabel(g.info);
|
|
51903
|
-
if (lbl !== void 0 && lbl.text !== "") {
|
|
51904
|
-
o.lp = gfmt5(lbl.pos.x) + "," + gfmt5(lbl.pos.y);
|
|
51905
|
-
o.lwidth = (lbl.dimen.x / 72).toFixed(2);
|
|
51906
|
-
o.lheight = (lbl.dimen.y / 72).toFixed(2);
|
|
51907
|
-
}
|
|
51908
|
-
}
|
|
51909
|
-
function setRecordRects(f, coord, out) {
|
|
51910
|
-
if (f.n_flds === 0) {
|
|
51911
|
-
out.push(
|
|
51912
|
-
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)
|
|
51913
|
-
);
|
|
51914
|
-
} else if (f.fld !== null) {
|
|
51915
|
-
for (const sub of f.fld) setRecordRects(sub, coord, out);
|
|
51916
|
-
}
|
|
51917
|
-
}
|
|
51918
|
-
function attachNodeRects(o, n) {
|
|
51919
|
-
const shape = n.info.shape;
|
|
51920
|
-
if (shape?.name !== "record") return;
|
|
51921
|
-
const f = n.info.shape_info;
|
|
51922
|
-
if (f === void 0) return;
|
|
51923
|
-
const parts = [];
|
|
51924
|
-
setRecordRects(f, n.info.coord, parts);
|
|
51925
|
-
if (parts.length > 0) o.rects = parts.join(" ");
|
|
51926
|
-
}
|
|
51927
|
-
function nodeEffectiveAttrs(n) {
|
|
51928
|
-
const m = new Map(n.nodeDefaultsSnapshot ?? []);
|
|
51929
|
-
for (const [k, v] of n.attrs) m.set(k, v);
|
|
51930
|
-
if (!m.has("label")) m.set("label", DEFAULT_NODE_LABEL);
|
|
51931
|
-
return m;
|
|
51932
|
-
}
|
|
51933
|
-
function buildNodeObject(n, ctx) {
|
|
51934
|
-
const o = { _gvid: ctx.nodeGid.get(n), name: n.name };
|
|
51935
|
-
const eff = nodeEffectiveAttrs(n);
|
|
51936
|
-
const xd = ctx.draws?.node.get(n.name);
|
|
51937
|
-
if (xd === void 0) {
|
|
51938
|
-
eff.set("pos", printNum(n.info.coord.x) + "," + printNum(n.info.coord.y));
|
|
51939
|
-
eff.set("width", printNum(n.info.width));
|
|
51940
|
-
eff.set("height", printNum(n.info.height));
|
|
51941
|
-
}
|
|
51942
|
-
emitAttrs(o, eff, xd, ctx);
|
|
51943
|
-
attachNodeRects(o, n);
|
|
51944
|
-
attachNodeXlp(o, n);
|
|
51945
|
-
if (ctx.doXDot && ctx.draws === void 0 && !("_draw_" in o)) o._draw_ = [];
|
|
51946
|
-
return o;
|
|
51947
|
-
}
|
|
51948
|
-
function buildEdgeObject(e, ctx) {
|
|
51949
|
-
const o = {
|
|
51950
|
-
_gvid: ctx.edgeGid.get(e),
|
|
51951
|
-
tail: ctx.nodeGid.get(e.tail),
|
|
51952
|
-
head: ctx.nodeGid.get(e.head)
|
|
51953
|
-
};
|
|
51954
|
-
emitAttrs(o, e.attrs, ctx.draws?.edge.get(e), ctx);
|
|
51955
|
-
if (ctx.edgeLabelDeclared && !("label" in o)) o.label = "";
|
|
51956
|
-
attachEdgeLabelPositions(o, e);
|
|
51957
|
-
if (ctx.doXDot && ctx.draws === void 0 && !("_draw_" in o)) o._draw_ = [];
|
|
51958
|
-
return o;
|
|
51959
|
-
}
|
|
51960
|
-
function memberEdgeGids(g, ctx) {
|
|
51961
|
-
const edges = [];
|
|
51962
|
-
for (const [, n] of g.nodes) {
|
|
51963
|
-
for (const e of n.outEdges(g)) {
|
|
51964
|
-
if (ctx.edgeGid.has(e)) edges.push(e);
|
|
51965
|
-
}
|
|
51966
|
-
}
|
|
51967
|
-
edges.sort((a, b) => a.seq - b.seq);
|
|
51968
|
-
return edges.map((e) => ctx.edgeGid.get(e));
|
|
51969
|
-
}
|
|
51970
|
-
function buildEdgesArray(g, ctx) {
|
|
51971
|
-
const edges = canonicalRootEdges(g).map(({ e }) => e);
|
|
51972
|
-
edges.sort((a, b) => a.seq - b.seq);
|
|
51973
|
-
return edges.map((e) => buildEdgeObject(e, ctx));
|
|
51974
|
-
}
|
|
51975
|
-
function buildGraphObject(g, top, ctx) {
|
|
51976
|
-
const o = {};
|
|
51977
|
-
o.name = g.name;
|
|
51978
|
-
if (top) {
|
|
51979
|
-
o.directed = g.kind === "directed" || g.kind === "strict-directed";
|
|
51980
|
-
o.strict = g.kind === "strict-directed" || g.kind === "strict-undirected";
|
|
51981
|
-
}
|
|
51982
|
-
const overlay = top ? ctx.draws?.graph : ctx.draws?.cluster.get(g);
|
|
51983
|
-
emitAttrs(o, graphEffectiveAttrs(g, top), overlay, ctx);
|
|
51984
|
-
attachGraphLabelGeom(o, g);
|
|
51985
|
-
if (ctx.graphLabelDeclared && !("label" in o)) o.label = "";
|
|
51986
|
-
if (top) {
|
|
51987
|
-
o._subgraph_cnt = ctx.sgcnt;
|
|
51988
|
-
} else {
|
|
51989
|
-
o._gvid = ctx.subgGid.get(g);
|
|
51990
|
-
}
|
|
51991
|
-
if (top) {
|
|
51992
|
-
if (ctx.dfsSubs.length > 0 || g.nodes.size > 0) {
|
|
51993
|
-
const objects = [];
|
|
51994
|
-
for (const sg of ctx.dfsSubs) objects.push(buildGraphObject(sg, false, ctx));
|
|
51995
|
-
for (const [, n] of g.nodes) objects.push(buildNodeObject(n, ctx));
|
|
51996
|
-
o.objects = objects;
|
|
51997
|
-
}
|
|
51998
|
-
} else {
|
|
51999
|
-
const childGids = [];
|
|
52000
|
-
for (const [, sg] of g.subgraphs) childGids.push(ctx.subgGid.get(sg));
|
|
52001
|
-
if (childGids.length > 0) o.subgraphs = childGids;
|
|
52002
|
-
const nodeGids = [];
|
|
52003
|
-
for (const [, n] of g.nodes) {
|
|
52004
|
-
const gid = ctx.nodeGid.get(n);
|
|
52005
|
-
if (gid !== void 0) nodeGids.push(gid);
|
|
52006
|
-
}
|
|
52007
|
-
if (nodeGids.length > 0) o.nodes = nodeGids;
|
|
52008
|
-
const eGids = memberEdgeGids(g, ctx);
|
|
52009
|
-
if (eGids.length > 0) o.edges = eGids;
|
|
52010
|
-
}
|
|
52011
|
-
if (top) {
|
|
52012
|
-
const edges = buildEdgesArray(g, ctx);
|
|
52013
|
-
if (edges.length > 0) o.edges = edges;
|
|
52014
|
-
}
|
|
52015
|
-
return o;
|
|
52016
|
-
}
|
|
52017
|
-
function latin1Reencode(s) {
|
|
52018
|
-
const bytes = new TextEncoder().encode(s);
|
|
52019
|
-
let out = "";
|
|
52020
|
-
for (const b of bytes) out += String.fromCharCode(b);
|
|
52021
|
-
return out;
|
|
52022
|
-
}
|
|
52023
|
-
function buildJson(g, doXDot) {
|
|
52024
|
-
const draws = doXDot ? collectDrawLookup(g) : void 0;
|
|
52025
|
-
const ctx = assignIds(g, doXDot, draws);
|
|
52026
|
-
const root = buildGraphObject(g, true, ctx);
|
|
52027
|
-
return JSON.stringify(root, null, 2) + "\n";
|
|
52028
|
-
}
|
|
52029
|
-
var Json0Renderer = class {
|
|
52030
|
-
type = "json0";
|
|
52031
|
-
/** @see plugin/core/gvplugin_core.c registration table */
|
|
52032
|
-
quality = 0;
|
|
52033
|
-
beginGraph(_g, _job) {
|
|
52034
|
-
}
|
|
52035
|
-
endGraph(g, job) {
|
|
52036
|
-
job.write(buildJson(g, false));
|
|
52037
|
-
}
|
|
52038
|
-
beginNode(_n, _job) {
|
|
52039
|
-
}
|
|
52040
|
-
endNode(_n, _job) {
|
|
52041
|
-
}
|
|
52042
|
-
beginEdge(_e, _job) {
|
|
52043
|
-
}
|
|
52044
|
-
endEdge(_e, _job) {
|
|
52045
|
-
}
|
|
52046
|
-
textspan(_pos, _span, _job) {
|
|
52047
|
-
}
|
|
52048
|
-
ellipse(_c, _rx, _ry, _f, _j) {
|
|
52049
|
-
}
|
|
52050
|
-
polygon(_pts, _filled, _job) {
|
|
52051
|
-
}
|
|
52052
|
-
bezier(_pts, _filled, _job) {
|
|
52053
|
-
}
|
|
52054
|
-
polyline(_pts, _job) {
|
|
52055
|
-
}
|
|
52056
|
-
};
|
|
52057
|
-
var JsonRenderer = class {
|
|
52058
|
-
type = "json";
|
|
52059
|
-
/** @see plugin/core/gvplugin_core.c registration table */
|
|
52060
|
-
quality = 0;
|
|
52061
|
-
beginGraph(_g, _job) {
|
|
52062
|
-
}
|
|
52063
|
-
endGraph(g, job) {
|
|
52064
|
-
job.write(buildJson(g, true));
|
|
52065
|
-
}
|
|
52066
|
-
beginNode(_n, _job) {
|
|
52067
|
-
}
|
|
52068
|
-
endNode(_n, _job) {
|
|
52069
|
-
}
|
|
52070
|
-
beginEdge(_e, _job) {
|
|
52071
|
-
}
|
|
52072
|
-
endEdge(_e, _job) {
|
|
52073
|
-
}
|
|
52074
|
-
textspan(_pos, _span, _job) {
|
|
52075
|
-
}
|
|
52076
|
-
ellipse(_c, _rx, _ry, _f, _j) {
|
|
52077
|
-
}
|
|
52078
|
-
polygon(_pts, _filled, _job) {
|
|
52079
|
-
}
|
|
52080
|
-
bezier(_pts, _filled, _job) {
|
|
52081
|
-
}
|
|
52082
|
-
polyline(_pts, _job) {
|
|
52083
|
-
}
|
|
52084
|
-
};
|
|
52085
|
-
function createJson0Renderer() {
|
|
52086
|
-
return new Json0Renderer();
|
|
52087
|
-
}
|
|
52088
|
-
function createJsonRenderer() {
|
|
52089
|
-
return new JsonRenderer();
|
|
52090
|
-
}
|
|
52091
|
-
|
|
52092
|
-
// src/render/map.ts
|
|
52093
|
-
var DQ = '"';
|
|
52094
|
-
function exactDecimalDigits2(v) {
|
|
52095
|
-
const dv = new DataView(new ArrayBuffer(8));
|
|
52096
|
-
dv.setFloat64(0, Math.abs(v));
|
|
52097
|
-
const bits = dv.getBigUint64(0);
|
|
52098
|
-
const expBits = Number(bits >> 52n & 0x7ffn);
|
|
52099
|
-
const mant = bits & 0xfffffffffffffn;
|
|
52100
|
-
const m = expBits === 0 ? mant : mant | 1n << 52n;
|
|
52101
|
-
const e = (expBits === 0 ? 1 : expBits) - 1075;
|
|
52102
|
-
let intDigits;
|
|
52103
|
-
let fracLen = 0;
|
|
52104
|
-
if (e >= 0) {
|
|
52105
|
-
intDigits = (m << BigInt(e)).toString();
|
|
52106
|
-
} else {
|
|
52107
|
-
intDigits = (m * 5n ** BigInt(-e)).toString();
|
|
52108
|
-
fracLen = -e;
|
|
52109
|
-
}
|
|
52110
|
-
if (intDigits.length <= fracLen) {
|
|
52111
|
-
intDigits = "0".repeat(fracLen - intDigits.length + 1) + intDigits;
|
|
52112
|
-
}
|
|
52113
|
-
const pointPos = intDigits.length - fracLen;
|
|
52114
|
-
const firstSig = intDigits.search(/[1-9]/);
|
|
52115
|
-
return { digits: intDigits.slice(firstSig), exp10: pointPos - firstSig - 1 };
|
|
52116
|
-
}
|
|
52117
|
-
function printG5(v) {
|
|
52118
|
-
if (v === 0 || !Number.isFinite(v)) return String(v);
|
|
52119
|
-
const P = 5;
|
|
52120
|
-
const { digits, exp10 } = exactDecimalDigits2(v);
|
|
52121
|
-
let sig = digits.slice(0, P);
|
|
52122
|
-
if (sig.length < P) sig += "0".repeat(P - sig.length);
|
|
52123
|
-
const rest = digits.slice(P);
|
|
52124
|
-
const restNonzeroAfterFirst = /[1-9]/.test(rest.slice(1));
|
|
52125
|
-
const roundUp = rest.length > 0 && (rest[0] > "5" || rest[0] === "5" && restNonzeroAfterFirst || rest[0] === "5" && !restNonzeroAfterFirst && (sig.charCodeAt(P - 1) - 48) % 2 === 1);
|
|
52126
|
-
let exp = exp10;
|
|
52127
|
-
if (roundUp) {
|
|
52128
|
-
const n = String(BigInt(sig) + 1n);
|
|
52129
|
-
sig = n.length > P ? (exp++, n.slice(0, P)) : n;
|
|
52130
|
-
}
|
|
52131
|
-
const sign2 = v < 0 ? "-" : "";
|
|
52132
|
-
if (exp < -4 || exp >= P) {
|
|
52133
|
-
const mant = (sig[0] + "." + sig.slice(1)).replace(/\.?0+$/, "");
|
|
52134
|
-
const as = Math.abs(exp);
|
|
52135
|
-
return sign2 + mant + "e" + (exp < 0 ? "-" : "+") + (as < 10 ? "0" : "") + String(as);
|
|
52136
|
-
}
|
|
52137
|
-
let s;
|
|
52138
|
-
if (exp >= sig.length - 1) {
|
|
52139
|
-
s = sig + "0".repeat(exp - sig.length + 1);
|
|
52140
|
-
} else if (exp >= 0) {
|
|
52141
|
-
s = sig.slice(0, exp + 1) + "." + sig.slice(exp + 1);
|
|
52142
|
-
} else {
|
|
52143
|
-
s = "0." + "0".repeat(-exp - 1) + sig;
|
|
52144
|
-
}
|
|
52145
|
-
if (s.includes(".")) s = s.replace(/\.?0+$/, "");
|
|
52146
|
-
return sign2 + s;
|
|
52147
|
-
}
|
|
52148
|
-
function plainCoord(v) {
|
|
52149
|
-
return printG5(v / 72);
|
|
52150
|
-
}
|
|
52151
|
-
var CANON_KEYWORDS = ["node", "edge", "strict", "graph", "digraph", "subgraph"];
|
|
52152
|
-
var MAX_OUTPUTLINE = 128;
|
|
52153
|
-
function isDigitByte(b) {
|
|
52154
|
-
return b >= 48 && b <= 57;
|
|
52155
|
-
}
|
|
52156
|
-
function isAlnumByte(b) {
|
|
52157
|
-
return isDigitByte(b) || b >= 65 && b <= 90 || b >= 97 && b <= 122;
|
|
52158
|
-
}
|
|
52159
|
-
function isIdCharByte(b) {
|
|
52160
|
-
return isAlnumByte(b) || b === 46 || b === 45 || b >= 128;
|
|
52161
|
-
}
|
|
52162
|
-
function isEscapeAt(bytes, i) {
|
|
52163
|
-
if (bytes[i] !== 92) return false;
|
|
52164
|
-
const c = bytes[i + 1];
|
|
52165
|
-
return c !== void 0 && 'EGHLNTlnr\\"'.includes(String.fromCharCode(c));
|
|
52166
|
-
}
|
|
52167
|
-
function agstrcanonText(arg) {
|
|
52168
|
-
if (arg.length === 0) return '""';
|
|
52169
|
-
const bytes = new TextEncoder().encode(arg);
|
|
52170
|
-
const out = [34];
|
|
52171
|
-
let needsQuotes = false;
|
|
52172
|
-
let partOfEscape = false;
|
|
52173
|
-
let backslashPending = false;
|
|
52174
|
-
let cnt = 0;
|
|
52175
|
-
let dotcnt = 0;
|
|
52176
|
-
let maybeNum = isDigitByte(bytes[0]) || bytes[0] === 46 || bytes[0] === 45;
|
|
52177
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
52178
|
-
const uc = bytes[i];
|
|
52179
|
-
if (uc === 34 && !partOfEscape) {
|
|
52180
|
-
out.push(92);
|
|
52181
|
-
needsQuotes = true;
|
|
52182
|
-
} else if (!partOfEscape && isEscapeAt(bytes, i)) {
|
|
52183
|
-
needsQuotes = true;
|
|
52184
|
-
partOfEscape = true;
|
|
52185
|
-
} else if (maybeNum) {
|
|
52186
|
-
if (uc === 45) {
|
|
52187
|
-
if (cnt) {
|
|
52188
|
-
maybeNum = false;
|
|
52189
|
-
needsQuotes = true;
|
|
52190
|
-
}
|
|
52191
|
-
} else if (uc === 46) {
|
|
52192
|
-
if (dotcnt++) {
|
|
52193
|
-
maybeNum = false;
|
|
52194
|
-
needsQuotes = true;
|
|
52195
|
-
}
|
|
52196
|
-
} else if (!isDigitByte(uc)) {
|
|
52197
|
-
maybeNum = false;
|
|
52198
|
-
needsQuotes = true;
|
|
52199
|
-
}
|
|
52200
|
-
partOfEscape = false;
|
|
52201
|
-
} else if (!(isAlnumByte(uc) || uc === 95 || uc >= 128)) {
|
|
52202
|
-
needsQuotes = true;
|
|
52203
|
-
partOfEscape = false;
|
|
52204
|
-
} else {
|
|
52205
|
-
partOfEscape = false;
|
|
52206
|
-
}
|
|
52207
|
-
out.push(uc);
|
|
52208
|
-
cnt++;
|
|
52209
|
-
const next = bytes[i + 1];
|
|
52210
|
-
if (next !== void 0) {
|
|
52211
|
-
const last = out[out.length - 1];
|
|
52212
|
-
const canBreak = !(isIdCharByte(last) || last === 92) && isIdCharByte(next);
|
|
52213
|
-
if (backslashPending && canBreak) {
|
|
52214
|
-
out.push(92, 10);
|
|
52215
|
-
needsQuotes = true;
|
|
52216
|
-
backslashPending = false;
|
|
52217
|
-
cnt = 0;
|
|
52218
|
-
} else if (cnt >= MAX_OUTPUTLINE) {
|
|
52219
|
-
if (canBreak) {
|
|
52220
|
-
out.push(92, 10);
|
|
52221
|
-
needsQuotes = true;
|
|
52222
|
-
cnt = 0;
|
|
52223
|
-
} else {
|
|
52224
|
-
backslashPending = true;
|
|
52225
|
-
}
|
|
52226
|
-
}
|
|
52227
|
-
}
|
|
52228
|
-
}
|
|
52229
|
-
out.push(34);
|
|
52230
|
-
const first = bytes[0];
|
|
52231
|
-
if (needsQuotes || cnt === 1 && (first === 46 || first === 45)) {
|
|
52232
|
-
return new TextDecoder().decode(new Uint8Array(out));
|
|
52233
|
-
}
|
|
52234
|
-
const lower = arg.toLowerCase();
|
|
52235
|
-
for (const tok of CANON_KEYWORDS) {
|
|
52236
|
-
if (tok === lower) return new TextDecoder().decode(new Uint8Array(out));
|
|
52237
|
-
}
|
|
52238
|
-
return arg;
|
|
52239
|
-
}
|
|
52240
|
-
function lateNN(v, def) {
|
|
52241
|
-
return v !== void 0 && v !== "" ? v : def;
|
|
52242
|
-
}
|
|
52243
|
-
function plainNodeFill(n, g) {
|
|
52244
|
-
const fillcolor = lateNN(nodeAttr(n, g, "fillcolor"), "");
|
|
52245
|
-
if (fillcolor !== "") return fillcolor;
|
|
52246
|
-
return lateNN(nodeAttr(n, g, "color"), "lightgrey");
|
|
52247
|
-
}
|
|
52248
|
-
function plainNodeLabel(n, g) {
|
|
52249
|
-
const lbl = n.info.label;
|
|
52250
|
-
const attr2 = nodeAttr(n, g, "label");
|
|
52251
|
-
if (lbl !== void 0 && lbl.u.kind === "html") {
|
|
52252
|
-
const a = attr2 ?? "";
|
|
52253
|
-
return "<" + (isHtmlValue(a) ? htmlValueContent(a) : a) + ">";
|
|
52254
|
-
}
|
|
52255
|
-
if (attr2 !== void 0 && isHtmlValue(attr2) && lbl !== void 0 && lbl.u.kind === "txt") {
|
|
52256
|
-
return agstrcanonText(htmlValueContent(attr2));
|
|
52257
|
-
}
|
|
52258
|
-
const shape = n.info.shape;
|
|
52259
|
-
if (shape !== void 0 && shape.kind === 2 /* SH_RECORD */) {
|
|
52260
|
-
return agstrcanonText(nodeAttr(n, g, "label") ?? "\\N");
|
|
52824
|
+
function isEdgeLabelDeclared(g) {
|
|
52825
|
+
for (const e of g.edges) if (e.attrs.has("label")) return true;
|
|
52826
|
+
const walk = (sg) => {
|
|
52827
|
+
if (sg.edgeDefaults.has("label")) return true;
|
|
52828
|
+
for (const [, sub] of sg.subgraphs) if (walk(sub)) return true;
|
|
52829
|
+
return false;
|
|
52830
|
+
};
|
|
52831
|
+
return walk(g);
|
|
52832
|
+
}
|
|
52833
|
+
function dfsSubgraphs(g, out) {
|
|
52834
|
+
for (const [, sg] of g.subgraphs) {
|
|
52835
|
+
out.push(sg);
|
|
52836
|
+
dfsSubgraphs(sg, out);
|
|
52261
52837
|
}
|
|
52262
|
-
const text = lbl !== void 0 ? lbl.text : n.attrs.get("label") ?? n.name;
|
|
52263
|
-
return agstrcanonText(text);
|
|
52264
52838
|
}
|
|
52265
|
-
function
|
|
52266
|
-
const
|
|
52267
|
-
|
|
52268
|
-
const
|
|
52839
|
+
function assignIds(g, doXDot, draws) {
|
|
52840
|
+
const dfsSubs = [];
|
|
52841
|
+
dfsSubgraphs(g, dfsSubs);
|
|
52842
|
+
const subgGid = /* @__PURE__ */ new Map();
|
|
52843
|
+
dfsSubs.forEach((sg, i) => subgGid.set(sg, i));
|
|
52844
|
+
const sgcnt = dfsSubs.length;
|
|
52845
|
+
const nodeGid = /* @__PURE__ */ new Map();
|
|
52846
|
+
let ncnt = 0;
|
|
52847
|
+
for (const [, n] of g.nodes) nodeGid.set(n, sgcnt + ncnt++);
|
|
52848
|
+
const edgeGid = /* @__PURE__ */ new Map();
|
|
52849
|
+
let ecnt = 0;
|
|
52850
|
+
for (const { e } of canonicalRootEdges(g)) edgeGid.set(e, ecnt++);
|
|
52269
52851
|
return {
|
|
52270
|
-
|
|
52271
|
-
|
|
52272
|
-
|
|
52273
|
-
|
|
52274
|
-
|
|
52852
|
+
doXDot,
|
|
52853
|
+
draws,
|
|
52854
|
+
sgcnt,
|
|
52855
|
+
subgGid,
|
|
52856
|
+
nodeGid,
|
|
52857
|
+
edgeGid,
|
|
52858
|
+
dfsSubs,
|
|
52859
|
+
edgeLabelDeclared: isEdgeLabelDeclared(g),
|
|
52860
|
+
graphLabelDeclared: isGraphLabelDeclared(g),
|
|
52861
|
+
isLatin: g.info.charset === CHAR_LATIN1
|
|
52275
52862
|
};
|
|
52276
52863
|
}
|
|
52277
|
-
function
|
|
52278
|
-
const
|
|
52279
|
-
const
|
|
52280
|
-
const
|
|
52281
|
-
|
|
52282
|
-
|
|
52283
|
-
|
|
52284
|
-
|
|
52285
|
-
|
|
52286
|
-
|
|
52287
|
-
|
|
52288
|
-
for (const bz of e.info.spl.list) {
|
|
52289
|
-
for (let i = 0; i < bz.size; i++) pts.push(bz.list[i]);
|
|
52290
|
-
}
|
|
52291
|
-
return pts;
|
|
52292
|
-
}
|
|
52293
|
-
function writeNodeAndPort(n, portname, out) {
|
|
52294
|
-
let name = n.name;
|
|
52295
|
-
if (n.info.clustnode) {
|
|
52296
|
-
const i = name.indexOf(":");
|
|
52297
|
-
if (i >= 0) name = name.slice(i + 1);
|
|
52864
|
+
function emitAttrs(o, base, overlay, ctx) {
|
|
52865
|
+
const merged = new Map(base);
|
|
52866
|
+
if (overlay !== void 0) for (const [k, v] of overlay) merged.set(k, v);
|
|
52867
|
+
for (const [k, rawV] of merged) {
|
|
52868
|
+
const v = isHtmlValue(rawV) ? htmlValueContent(rawV) : rawV;
|
|
52869
|
+
if (v === "" && k !== "label") continue;
|
|
52870
|
+
if (ctx.doXDot && isXDot(k)) {
|
|
52871
|
+
o[k] = drawStringToOps(v, ctx.isLatin);
|
|
52872
|
+
} else if (!isXDot(k)) {
|
|
52873
|
+
o[k] = v;
|
|
52874
|
+
}
|
|
52298
52875
|
}
|
|
52299
|
-
out.push(" " + agstrcanonText(name));
|
|
52300
|
-
if (portname !== "") out.push(":" + agstrcanonText(portname));
|
|
52301
52876
|
}
|
|
52302
|
-
function
|
|
52303
|
-
|
|
52304
|
-
|
|
52305
|
-
|
|
52306
|
-
out.push(" " + String(pts.length));
|
|
52307
|
-
for (const pt of pts) {
|
|
52308
|
-
out.push(" " + plainCoord(pt.x) + " " + plainCoord(pt.y));
|
|
52877
|
+
function graphLabel(info) {
|
|
52878
|
+
const l = info.label;
|
|
52879
|
+
if (l !== void 0 && l !== null && typeof l === "object" && "pos" in l) {
|
|
52880
|
+
return l;
|
|
52309
52881
|
}
|
|
52882
|
+
return void 0;
|
|
52310
52883
|
}
|
|
52311
|
-
function
|
|
52312
|
-
|
|
52313
|
-
const hport = extend ? e.attrs.get("headport") ?? "" : "";
|
|
52314
|
-
const pts = collectSplinePts(e);
|
|
52315
|
-
if (pts.length > 0) writePlainEdgeHead(e, tport, hport, pts, out);
|
|
52316
|
-
const lbl = e.info.label;
|
|
52317
|
-
if (lbl !== void 0) {
|
|
52318
|
-
out.push(" " + agstrcanonText(lbl.text) + " " + plainCoord(lbl.pos.x) + " " + plainCoord(lbl.pos.y));
|
|
52319
|
-
}
|
|
52320
|
-
const style = lateNN(e.attrs.get("style"), "solid");
|
|
52321
|
-
const color = lateNN(e.attrs.get("color"), "black");
|
|
52322
|
-
out.push(" " + style + " " + color + "\n");
|
|
52884
|
+
function labelPos(l) {
|
|
52885
|
+
return gfmt5(l.pos.x) + "," + gfmt5(l.pos.y);
|
|
52323
52886
|
}
|
|
52324
|
-
function
|
|
52325
|
-
|
|
52326
|
-
|
|
52327
|
-
|
|
52328
|
-
|
|
52329
|
-
|
|
52330
|
-
|
|
52331
|
-
);
|
|
52332
|
-
job.write("graph " + printG5(zoom) + " " + w + " " + h + "\n");
|
|
52333
|
-
const nodes = nodesInSeq(g);
|
|
52334
|
-
for (const n of nodes) {
|
|
52335
|
-
if (n.info.clustnode) continue;
|
|
52336
|
-
const buf = [];
|
|
52337
|
-
writePlainNode(n, g, buf);
|
|
52338
|
-
job.write(buf.join(""));
|
|
52339
|
-
}
|
|
52340
|
-
for (const n of nodes) {
|
|
52341
|
-
for (const e of n.outEdges(g)) {
|
|
52342
|
-
const buf = [];
|
|
52343
|
-
writePlainEdge(e, extend, buf);
|
|
52344
|
-
job.write(buf.join(""));
|
|
52345
|
-
}
|
|
52346
|
-
}
|
|
52347
|
-
job.write("stop\n");
|
|
52887
|
+
function attachEdgeLabelPositions(o, e) {
|
|
52888
|
+
if (!e.info.spl) return;
|
|
52889
|
+
if (e.info.label !== void 0) o.lp = labelPos(e.info.label);
|
|
52890
|
+
if (e.info.head_label !== void 0) o.head_lp = labelPos(e.info.head_label);
|
|
52891
|
+
if (e.info.tail_label !== void 0) o.tail_lp = labelPos(e.info.tail_label);
|
|
52892
|
+
const xl = e.info.xlabel;
|
|
52893
|
+
if (xl !== void 0 && xl.set) o.xlp = labelPos(xl);
|
|
52348
52894
|
}
|
|
52349
|
-
function
|
|
52350
|
-
|
|
52351
|
-
|
|
52352
|
-
tooltip: obj.tooltip ?? "",
|
|
52353
|
-
target: obj.target ?? "",
|
|
52354
|
-
id: obj.id ?? ""
|
|
52355
|
-
};
|
|
52895
|
+
function attachNodeXlp(o, n) {
|
|
52896
|
+
const xl = n.info.xlabel;
|
|
52897
|
+
if (xl !== void 0 && xl.set) o.xlp = labelPos(xl);
|
|
52356
52898
|
}
|
|
52357
|
-
function
|
|
52358
|
-
const
|
|
52359
|
-
if (
|
|
52360
|
-
|
|
52899
|
+
function attachGraphLabelGeom(o, g) {
|
|
52900
|
+
const lbl = graphLabel(g.info);
|
|
52901
|
+
if (lbl !== void 0 && lbl.text !== "") {
|
|
52902
|
+
o.lp = gfmt5(lbl.pos.x) + "," + gfmt5(lbl.pos.y);
|
|
52903
|
+
o.lwidth = (lbl.dimen.x / 72).toFixed(2);
|
|
52904
|
+
o.lheight = (lbl.dimen.y / 72).toFixed(2);
|
|
52905
|
+
}
|
|
52361
52906
|
}
|
|
52362
|
-
function
|
|
52363
|
-
if (
|
|
52364
|
-
|
|
52365
|
-
|
|
52907
|
+
function setRecordRects(f, coord, out) {
|
|
52908
|
+
if (f.n_flds === 0) {
|
|
52909
|
+
out.push(
|
|
52910
|
+
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)
|
|
52911
|
+
);
|
|
52912
|
+
} else if (f.fld !== null) {
|
|
52913
|
+
for (const sub of f.fld) setRecordRects(sub, coord, out);
|
|
52914
|
+
}
|
|
52366
52915
|
}
|
|
52367
|
-
function
|
|
52368
|
-
|
|
52916
|
+
function attachNodeRects(o, n) {
|
|
52917
|
+
const shape = n.info.shape;
|
|
52918
|
+
if (shape?.name !== "record") return;
|
|
52919
|
+
const f = n.info.shape_info;
|
|
52920
|
+
if (f === void 0) return;
|
|
52921
|
+
const parts = [];
|
|
52922
|
+
setRecordRects(f, n.info.coord, parts);
|
|
52923
|
+
if (parts.length > 0) o.rects = parts.join(" ");
|
|
52369
52924
|
}
|
|
52370
|
-
function
|
|
52371
|
-
const
|
|
52372
|
-
const
|
|
52373
|
-
|
|
52374
|
-
return
|
|
52925
|
+
function nodeEffectiveAttrs(n) {
|
|
52926
|
+
const m = new Map(n.nodeDefaultsSnapshot ?? []);
|
|
52927
|
+
for (const [k, v] of n.attrs) m.set(k, v);
|
|
52928
|
+
if (!m.has("label")) m.set("label", DEFAULT_NODE_LABEL);
|
|
52929
|
+
return m;
|
|
52375
52930
|
}
|
|
52376
|
-
function
|
|
52377
|
-
|
|
52931
|
+
function buildNodeObject(n, ctx) {
|
|
52932
|
+
const o = { _gvid: ctx.nodeGid.get(n), name: n.name };
|
|
52933
|
+
const eff = nodeEffectiveAttrs(n);
|
|
52934
|
+
const xd = ctx.draws?.node.get(n.name);
|
|
52935
|
+
if (xd === void 0) {
|
|
52936
|
+
eff.set("pos", printNum(n.info.coord.x) + "," + printNum(n.info.coord.y));
|
|
52937
|
+
eff.set("width", printNum(n.info.width));
|
|
52938
|
+
eff.set("height", printNum(n.info.height));
|
|
52939
|
+
}
|
|
52940
|
+
emitAttrs(o, eff, xd, ctx);
|
|
52941
|
+
attachNodeRects(o, n);
|
|
52942
|
+
attachNodeXlp(o, n);
|
|
52943
|
+
if (ctx.doXDot && ctx.draws === void 0 && !("_draw_" in o)) o._draw_ = [];
|
|
52944
|
+
return o;
|
|
52378
52945
|
}
|
|
52379
|
-
function
|
|
52380
|
-
|
|
52381
|
-
|
|
52382
|
-
|
|
52946
|
+
function buildEdgeObject(e, ctx) {
|
|
52947
|
+
const o = {
|
|
52948
|
+
_gvid: ctx.edgeGid.get(e),
|
|
52949
|
+
tail: ctx.nodeGid.get(e.tail),
|
|
52950
|
+
head: ctx.nodeGid.get(e.head)
|
|
52951
|
+
};
|
|
52952
|
+
emitAttrs(o, e.attrs, ctx.draws?.edge.get(e), ctx);
|
|
52953
|
+
if (ctx.edgeLabelDeclared && !("label" in o)) o.label = "";
|
|
52954
|
+
attachEdgeLabelPositions(o, e);
|
|
52955
|
+
if (ctx.doXDot && ctx.draws === void 0 && !("_draw_" in o)) o._draw_ = [];
|
|
52956
|
+
return o;
|
|
52383
52957
|
}
|
|
52384
|
-
function
|
|
52385
|
-
|
|
52386
|
-
|
|
52387
|
-
|
|
52388
|
-
|
|
52958
|
+
function memberEdgeGids(g, ctx) {
|
|
52959
|
+
const edges = [];
|
|
52960
|
+
for (const [, n] of g.nodes) {
|
|
52961
|
+
for (const e of n.outEdges(g)) {
|
|
52962
|
+
if (ctx.edgeGid.has(e)) edges.push(e);
|
|
52963
|
+
}
|
|
52964
|
+
}
|
|
52965
|
+
edges.sort((a, b) => a.seq - b.seq);
|
|
52966
|
+
return edges.map((e) => ctx.edgeGid.get(e));
|
|
52389
52967
|
}
|
|
52390
|
-
function
|
|
52391
|
-
|
|
52392
|
-
|
|
52393
|
-
|
|
52394
|
-
out.push(" coords=" + DQ + cmapxCoords(shape, pts) + DQ);
|
|
52395
|
-
out.push(isXml ? "/>\n" : ">\n");
|
|
52968
|
+
function buildEdgesArray(g, ctx) {
|
|
52969
|
+
const edges = canonicalRootEdges(g).map(({ e }) => e);
|
|
52970
|
+
edges.sort((a, b) => a.seq - b.seq);
|
|
52971
|
+
return edges.map((e) => buildEdgeObject(e, ctx));
|
|
52396
52972
|
}
|
|
52397
|
-
function
|
|
52398
|
-
|
|
52399
|
-
|
|
52400
|
-
|
|
52401
|
-
|
|
52402
|
-
|
|
52403
|
-
|
|
52973
|
+
function buildGraphObject(g, top, ctx) {
|
|
52974
|
+
const o = {};
|
|
52975
|
+
o.name = g.name;
|
|
52976
|
+
if (top) {
|
|
52977
|
+
o.directed = g.kind === "directed" || g.kind === "strict-directed";
|
|
52978
|
+
o.strict = g.kind === "strict-directed" || g.kind === "strict-undirected";
|
|
52979
|
+
}
|
|
52980
|
+
const overlay = top ? ctx.draws?.graph : ctx.draws?.cluster.get(g);
|
|
52981
|
+
emitAttrs(o, graphEffectiveAttrs(g, top), overlay, ctx);
|
|
52982
|
+
attachGraphLabelGeom(o, g);
|
|
52983
|
+
if (ctx.graphLabelDeclared && !("label" in o)) o.label = "";
|
|
52984
|
+
if (top) {
|
|
52985
|
+
o._subgraph_cnt = ctx.sgcnt;
|
|
52404
52986
|
} else {
|
|
52405
|
-
|
|
52406
|
-
|
|
52987
|
+
o._gvid = ctx.subgGid.get(g);
|
|
52988
|
+
}
|
|
52989
|
+
if (top) {
|
|
52990
|
+
if (ctx.dfsSubs.length > 0 || g.nodes.size > 0) {
|
|
52991
|
+
const objects = [];
|
|
52992
|
+
for (const sg of ctx.dfsSubs) objects.push(buildGraphObject(sg, false, ctx));
|
|
52993
|
+
for (const [, n] of g.nodes) objects.push(buildNodeObject(n, ctx));
|
|
52994
|
+
o.objects = objects;
|
|
52995
|
+
}
|
|
52996
|
+
} else {
|
|
52997
|
+
const childGids = [];
|
|
52998
|
+
for (const [, sg] of g.subgraphs) childGids.push(ctx.subgGid.get(sg));
|
|
52999
|
+
if (childGids.length > 0) o.subgraphs = childGids;
|
|
53000
|
+
const nodeGids = [];
|
|
53001
|
+
for (const [, n] of g.nodes) {
|
|
53002
|
+
const gid = ctx.nodeGid.get(n);
|
|
53003
|
+
if (gid !== void 0) nodeGids.push(gid);
|
|
53004
|
+
}
|
|
53005
|
+
if (nodeGids.length > 0) o.nodes = nodeGids;
|
|
53006
|
+
const eGids = memberEdgeGids(g, ctx);
|
|
53007
|
+
if (eGids.length > 0) o.edges = eGids;
|
|
53008
|
+
}
|
|
53009
|
+
if (top) {
|
|
53010
|
+
const edges = buildEdgesArray(g, ctx);
|
|
53011
|
+
if (edges.length > 0) o.edges = edges;
|
|
52407
53012
|
}
|
|
53013
|
+
return o;
|
|
52408
53014
|
}
|
|
52409
|
-
|
|
52410
|
-
|
|
53015
|
+
function latin1Reencode(s) {
|
|
53016
|
+
const bytes = new TextEncoder().encode(s);
|
|
53017
|
+
let out = "";
|
|
53018
|
+
for (const b of bytes) out += String.fromCharCode(b);
|
|
53019
|
+
return out;
|
|
53020
|
+
}
|
|
53021
|
+
function buildJson(g, doXDot) {
|
|
53022
|
+
const draws = doXDot ? collectDrawLookup(g) : void 0;
|
|
53023
|
+
const ctx = assignIds(g, doXDot, draws);
|
|
53024
|
+
const root = buildGraphObject(g, true, ctx);
|
|
53025
|
+
return JSON.stringify(root, null, 2) + "\n";
|
|
53026
|
+
}
|
|
53027
|
+
var Json0Renderer = class {
|
|
53028
|
+
type = "json0";
|
|
53029
|
+
/** @see plugin/core/gvplugin_core.c registration table */
|
|
52411
53030
|
quality = 0;
|
|
52412
53031
|
beginGraph(_g, _job) {
|
|
52413
53032
|
}
|
|
52414
53033
|
endGraph(g, job) {
|
|
52415
|
-
|
|
53034
|
+
job.write(buildJson(g, false));
|
|
52416
53035
|
}
|
|
52417
53036
|
beginNode(_n, _job) {
|
|
52418
53037
|
}
|
|
@@ -52433,13 +53052,14 @@ var PlainRenderer = class {
|
|
|
52433
53052
|
polyline(_pts, _job) {
|
|
52434
53053
|
}
|
|
52435
53054
|
};
|
|
52436
|
-
var
|
|
52437
|
-
type = "
|
|
53055
|
+
var JsonRenderer = class {
|
|
53056
|
+
type = "json";
|
|
53057
|
+
/** @see plugin/core/gvplugin_core.c registration table */
|
|
52438
53058
|
quality = 0;
|
|
52439
53059
|
beginGraph(_g, _job) {
|
|
52440
53060
|
}
|
|
52441
53061
|
endGraph(g, job) {
|
|
52442
|
-
|
|
53062
|
+
job.write(buildJson(g, true));
|
|
52443
53063
|
}
|
|
52444
53064
|
beginNode(_n, _job) {
|
|
52445
53065
|
}
|
|
@@ -52460,183 +53080,11 @@ var PlainExtRenderer = class {
|
|
|
52460
53080
|
polyline(_pts, _job) {
|
|
52461
53081
|
}
|
|
52462
53082
|
};
|
|
52463
|
-
function
|
|
52464
|
-
return
|
|
52465
|
-
}
|
|
52466
|
-
function graphAttr2(g, key) {
|
|
52467
|
-
for (let s = g; s !== null; s = s.parent) {
|
|
52468
|
-
const v = s.attrs.get(key);
|
|
52469
|
-
if (v !== void 0 && v !== "") return v;
|
|
52470
|
-
}
|
|
52471
|
-
return void 0;
|
|
52472
|
-
}
|
|
52473
|
-
function graphMapUrl(g) {
|
|
52474
|
-
const raw = graphAttr2(g, "href") ?? graphAttr2(g, "URL");
|
|
52475
|
-
return raw === void 0 ? null : substObjAnchor(raw, g);
|
|
52476
|
-
}
|
|
52477
|
-
function buildMapCtx(g, job, mapPolygon) {
|
|
52478
|
-
const drawingDpi = lateDouble(g.attrs.get("dpi"), lateDouble(g.attrs.get("resolution"), 0, 0), 0);
|
|
52479
|
-
const mapDpi = drawingDpi > 0 ? drawingDpi : 96;
|
|
52480
|
-
const devscale = mapDpi / POINTS_PER_INCH;
|
|
52481
|
-
const z = initJobViewportZoom(job.bb, parseDrawingSize(g.attrs.get("size")), job.pad);
|
|
52482
|
-
const marginOff = { x: job.margin.x * devscale, y: job.margin.y * devscale };
|
|
52483
|
-
return { bb: job.bb, pad: job.pad, scale: z * devscale, marginOff, mapPolygon };
|
|
52484
|
-
}
|
|
52485
|
-
function anchorOf(url, tooltip, target, id) {
|
|
52486
|
-
return { url: url ?? "", tooltip: tooltip ?? "", target: target ?? "", id: id ?? "" };
|
|
52487
|
-
}
|
|
52488
|
-
var MapRendererBase = class {
|
|
52489
|
-
quality = 0;
|
|
52490
|
-
mapCtx = null;
|
|
52491
|
-
beginGraph(g, job) {
|
|
52492
|
-
job.flags |= EMIT_CLUSTERS_LAST;
|
|
52493
|
-
this.mapCtx = buildMapCtx(g, job, this.mapPolygon);
|
|
52494
|
-
if (this.isCmapx) {
|
|
52495
|
-
const name = escapeXml(mapGraphName(g));
|
|
52496
|
-
job.write("<map id=" + DQ + name + DQ + " name=" + DQ + name + DQ + ">\n");
|
|
52497
|
-
return;
|
|
52498
|
-
}
|
|
52499
|
-
job.write("base referer\n");
|
|
52500
|
-
const url = graphMapUrl(g);
|
|
52501
|
-
if (url) job.write("default " + url + "\n");
|
|
52502
|
-
}
|
|
52503
|
-
endGraph(_g, job) {
|
|
52504
|
-
if (!this.isCmapx) return;
|
|
52505
|
-
const obj = job.obj;
|
|
52506
|
-
if (obj !== null && this.mapCtx !== null && (obj.url !== null || obj.explicitTooltip)) {
|
|
52507
|
-
computeGraphUrlMap(obj, this.mapCtx);
|
|
52508
|
-
}
|
|
52509
|
-
const buf = [];
|
|
52510
|
-
writeCmapxGraphShape(job, buf);
|
|
52511
|
-
job.write(buf.join(""));
|
|
52512
|
-
job.write("</map>\n");
|
|
52513
|
-
}
|
|
52514
|
-
beginNode(n, job) {
|
|
52515
|
-
if (this.mapCtx !== null && job.obj !== null) computeNodeUrlMap(n, job.obj, this.mapCtx);
|
|
52516
|
-
}
|
|
52517
|
-
beginCluster(sg, job) {
|
|
52518
|
-
const bb = sg.info.bb;
|
|
52519
|
-
if (this.mapCtx !== null && job.obj !== null && bb !== void 0) {
|
|
52520
|
-
computeClusterUrlMap(bb, job.obj, this.mapCtx);
|
|
52521
|
-
}
|
|
52522
|
-
}
|
|
52523
|
-
endEdge(e, job) {
|
|
52524
|
-
if (this.mapCtx !== null && job.obj !== null) this.emitEdge(e, job.obj, job);
|
|
52525
|
-
}
|
|
52526
|
-
/** C emit_map_rect: record the HTML table/cell box as the pending hot spot,
|
|
52527
|
-
* so the following beginAnchor emits its <area>. @see emit.c:640 */
|
|
52528
|
-
emitMapRect(box, job) {
|
|
52529
|
-
if (this.mapCtx !== null && job.obj !== null) {
|
|
52530
|
-
computeClusterUrlMap(box, job.obj, this.mapCtx);
|
|
52531
|
-
}
|
|
52532
|
-
}
|
|
52533
|
-
beginAnchor(url, tip2, target, id, job) {
|
|
52534
|
-
const obj = job.obj;
|
|
52535
|
-
if (obj === null || obj.urlMapPts.length === 0) return;
|
|
52536
|
-
this.emitShape(obj.urlMapShape, obj.urlMapPts, { url, tooltip: tip2, target, id }, job);
|
|
52537
|
-
}
|
|
52538
|
-
endNode(_n, _job) {
|
|
52539
|
-
}
|
|
52540
|
-
beginEdge(_e, _job) {
|
|
52541
|
-
}
|
|
52542
|
-
endCluster(_sg, _job) {
|
|
52543
|
-
}
|
|
52544
|
-
textspan(_pos, _span, _job) {
|
|
52545
|
-
}
|
|
52546
|
-
ellipse(_c, _rx, _ry, _f, _j) {
|
|
52547
|
-
}
|
|
52548
|
-
polygon(_pts, _filled, _job) {
|
|
52549
|
-
}
|
|
52550
|
-
bezier(_pts, _filled, _job) {
|
|
52551
|
-
}
|
|
52552
|
-
polyline(_pts, _job) {
|
|
52553
|
-
}
|
|
52554
|
-
/** Emit one hot spot: `<area>` (cmapx) or a plain `keyword url coords` line. */
|
|
52555
|
-
emitShape(shape, pts, a, job) {
|
|
52556
|
-
const buf = [];
|
|
52557
|
-
if (this.isCmapx) mapOutputCmapx(shape, pts, a, true, buf);
|
|
52558
|
-
else mapOutputImap(shape, pts, a.url, buf);
|
|
52559
|
-
job.write(buf.join(""));
|
|
52560
|
-
}
|
|
52561
|
-
/** Whole-edge spline outline(s) then center/head/tail label hot spots.
|
|
52562
|
-
* @see lib/common/emit.c:emit_begin_edge (2851-2872) / emit_end_edge */
|
|
52563
|
-
emitEdge(e, obj, job) {
|
|
52564
|
-
const spl = e.info.spl;
|
|
52565
|
-
const wholeEdge = obj.url !== null || obj.explicitTooltip;
|
|
52566
|
-
if (spl !== void 0 && this.mapPolygon && wholeEdge) {
|
|
52567
|
-
const w2 = Math.max(obj.penWidth / 2, 2);
|
|
52568
|
-
const anchor = anchorOf(obj.url, obj.tooltip, obj.target, obj.id);
|
|
52569
|
-
for (const poly of computeEdgeSplineMaps(spl, w2, this.mapCtx)) {
|
|
52570
|
-
this.emitShape(2 /* Polygon */, poly, anchor, job);
|
|
52571
|
-
}
|
|
52572
|
-
}
|
|
52573
|
-
const centerA = {
|
|
52574
|
-
url: obj.labelUrl,
|
|
52575
|
-
tooltip: obj.labelTooltip,
|
|
52576
|
-
target: obj.labelTarget,
|
|
52577
|
-
explicit: obj.explicitLabelTooltip
|
|
52578
|
-
};
|
|
52579
|
-
this.emitEdgeLabel(e.info.label, centerA, obj, job);
|
|
52580
|
-
this.emitEdgeLabel(e.info.xlabel, centerA, obj, job);
|
|
52581
|
-
this.emitEdgeLabel(
|
|
52582
|
-
e.info.head_label,
|
|
52583
|
-
{ url: obj.headUrl, tooltip: obj.headTooltip, target: obj.headTarget, explicit: obj.explicitHeadTooltip },
|
|
52584
|
-
obj,
|
|
52585
|
-
job
|
|
52586
|
-
);
|
|
52587
|
-
this.emitEdgeLabel(
|
|
52588
|
-
e.info.tail_label,
|
|
52589
|
-
{ url: obj.tailUrl, tooltip: obj.tailTooltip, target: obj.tailTarget, explicit: obj.explicitTailTooltip },
|
|
52590
|
-
obj,
|
|
52591
|
-
job
|
|
52592
|
-
);
|
|
52593
|
-
}
|
|
52594
|
-
/** One edge label hot spot (map_label rect), if placed and url/explicit-tip.
|
|
52595
|
-
* @see lib/common/emit.c:emit_edge_label */
|
|
52596
|
-
emitEdgeLabel(lab, la, obj, job) {
|
|
52597
|
-
const placed = lab !== void 0 && lab.set && this.mapCtx !== null;
|
|
52598
|
-
if (!placed || la.url === null && !la.explicit) return;
|
|
52599
|
-
computeLabelRectMap(lab, obj, this.mapCtx);
|
|
52600
|
-
this.emitShape(0 /* Rectangle */, obj.urlMapPts, anchorOf(la.url, la.tooltip, la.target, obj.id), job);
|
|
52601
|
-
}
|
|
52602
|
-
};
|
|
52603
|
-
var ImapRenderer = class extends MapRendererBase {
|
|
52604
|
-
type = "imap";
|
|
52605
|
-
isCmapx = false;
|
|
52606
|
-
mapPolygon = true;
|
|
52607
|
-
};
|
|
52608
|
-
var ImapNpRenderer = class extends MapRendererBase {
|
|
52609
|
-
type = "imap-np";
|
|
52610
|
-
isCmapx = false;
|
|
52611
|
-
mapPolygon = false;
|
|
52612
|
-
};
|
|
52613
|
-
var CmapxRenderer = class extends MapRendererBase {
|
|
52614
|
-
type = "cmapx";
|
|
52615
|
-
isCmapx = true;
|
|
52616
|
-
mapPolygon = true;
|
|
52617
|
-
};
|
|
52618
|
-
var CmapxNpRenderer = class extends MapRendererBase {
|
|
52619
|
-
type = "cmapx-np";
|
|
52620
|
-
isCmapx = true;
|
|
52621
|
-
mapPolygon = false;
|
|
52622
|
-
};
|
|
52623
|
-
function createPlainRenderer() {
|
|
52624
|
-
return new PlainRenderer();
|
|
52625
|
-
}
|
|
52626
|
-
function createPlainExtRenderer() {
|
|
52627
|
-
return new PlainExtRenderer();
|
|
52628
|
-
}
|
|
52629
|
-
function createImapRenderer() {
|
|
52630
|
-
return new ImapRenderer();
|
|
52631
|
-
}
|
|
52632
|
-
function createImapNpRenderer() {
|
|
52633
|
-
return new ImapNpRenderer();
|
|
52634
|
-
}
|
|
52635
|
-
function createCmapxRenderer() {
|
|
52636
|
-
return new CmapxRenderer();
|
|
53083
|
+
function createJson0Renderer() {
|
|
53084
|
+
return new Json0Renderer();
|
|
52637
53085
|
}
|
|
52638
|
-
function
|
|
52639
|
-
return new
|
|
53086
|
+
function createJsonRenderer() {
|
|
53087
|
+
return new JsonRenderer();
|
|
52640
53088
|
}
|
|
52641
53089
|
|
|
52642
53090
|
// src/gvc/default-context.ts
|