@knowvah/dot-engine 1.1.1 → 1.2.1

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