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