@knowvah/dot-engine 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +277 -0
- package/README.md +292 -0
- package/dist/api/builder.d.ts +78 -0
- package/dist/api/builder.d.ts.map +1 -0
- package/dist/api/edge-ops.d.ts +45 -0
- package/dist/api/edge-ops.d.ts.map +1 -0
- package/dist/api/geometry.d.ts +159 -0
- package/dist/api/geometry.d.ts.map +1 -0
- package/dist/api/index.d.ts +24 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api.js +719 -0
- package/dist/api.js.map +7 -0
- package/dist/bundle.js +101 -0
- package/dist/cdt/bag.d.ts +58 -0
- package/dist/cdt/bag.d.ts.map +1 -0
- package/dist/cdt/hash-core.d.ts +40 -0
- package/dist/cdt/hash-core.d.ts.map +1 -0
- package/dist/cdt/hash.d.ts +74 -0
- package/dist/cdt/hash.d.ts.map +1 -0
- package/dist/cdt/index.d.ts +16 -0
- package/dist/cdt/index.d.ts.map +1 -0
- package/dist/cdt/splay-core.d.ts +78 -0
- package/dist/cdt/splay-core.d.ts.map +1 -0
- package/dist/cdt/splay.d.ts +63 -0
- package/dist/cdt/splay.d.ts.map +1 -0
- package/dist/cdt/strhash.d.ts +26 -0
- package/dist/cdt/strhash.d.ts.map +1 -0
- package/dist/cdt/types.d.ts +11 -0
- package/dist/cdt/types.d.ts.map +1 -0
- package/dist/common/arith.d.ts +35 -0
- package/dist/common/arith.d.ts.map +1 -0
- package/dist/common/arm-pow-data.d.ts +17 -0
- package/dist/common/arm-pow-data.d.ts.map +1 -0
- package/dist/common/arm-pow.d.ts +12 -0
- package/dist/common/arm-pow.d.ts.map +1 -0
- package/dist/common/arrows-constants.d.ts +97 -0
- package/dist/common/arrows-constants.d.ts.map +1 -0
- package/dist/common/arrows-geometry.d.ts +24 -0
- package/dist/common/arrows-geometry.d.ts.map +1 -0
- package/dist/common/arrows-miter.d.ts +24 -0
- package/dist/common/arrows-miter.d.ts.map +1 -0
- package/dist/common/arrows-shapes-poly.d.ts +24 -0
- package/dist/common/arrows-shapes-poly.d.ts.map +1 -0
- package/dist/common/arrows-shapes-util.d.ts +77 -0
- package/dist/common/arrows-shapes-util.d.ts.map +1 -0
- package/dist/common/arrows-shapes.d.ts +48 -0
- package/dist/common/arrows-shapes.d.ts.map +1 -0
- package/dist/common/arrows-types.d.ts +59 -0
- package/dist/common/arrows-types.d.ts.map +1 -0
- package/dist/common/arrows.d.ts +69 -0
- package/dist/common/arrows.d.ts.map +1 -0
- package/dist/common/color.d.ts +66 -0
- package/dist/common/color.d.ts.map +1 -0
- package/dist/common/colorData.d.ts +10 -0
- package/dist/common/colorData.d.ts.map +1 -0
- package/dist/common/compass-port.d.ts +39 -0
- package/dist/common/compass-port.d.ts.map +1 -0
- package/dist/common/crand.d.ts +42 -0
- package/dist/common/crand.d.ts.map +1 -0
- package/dist/common/edge-label-init.d.ts +51 -0
- package/dist/common/edge-label-init.d.ts.map +1 -0
- package/dist/common/edge-offset.d.ts +66 -0
- package/dist/common/edge-offset.d.ts.map +1 -0
- package/dist/common/ellipse-wedge.d.ts +10 -0
- package/dist/common/ellipse-wedge.d.ts.map +1 -0
- package/dist/common/emit-types.d.ts +255 -0
- package/dist/common/emit-types.d.ts.map +1 -0
- package/dist/common/fma.d.ts +10 -0
- package/dist/common/fma.d.ts.map +1 -0
- package/dist/common/graph-init.d.ts +61 -0
- package/dist/common/graph-init.d.ts.map +1 -0
- package/dist/common/html-entities.d.ts +17 -0
- package/dist/common/html-entities.d.ts.map +1 -0
- package/dist/common/html-string.d.ts +14 -0
- package/dist/common/html-string.d.ts.map +1 -0
- package/dist/common/htmltable-emit-fill.d.ts +84 -0
- package/dist/common/htmltable-emit-fill.d.ts.map +1 -0
- package/dist/common/htmltable-emit-rules.d.ts +79 -0
- package/dist/common/htmltable-emit-rules.d.ts.map +1 -0
- package/dist/common/htmltable-emit.d.ts +63 -0
- package/dist/common/htmltable-emit.d.ts.map +1 -0
- package/dist/common/htmltable-lex.d.ts +16 -0
- package/dist/common/htmltable-lex.d.ts.map +1 -0
- package/dist/common/htmltable-parse.d.ts +37 -0
- package/dist/common/htmltable-parse.d.ts.map +1 -0
- package/dist/common/htmltable-port.d.ts +15 -0
- package/dist/common/htmltable-port.d.ts.map +1 -0
- package/dist/common/htmltable-pos-runs.d.ts +88 -0
- package/dist/common/htmltable-pos-runs.d.ts.map +1 -0
- package/dist/common/htmltable-pos.d.ts +192 -0
- package/dist/common/htmltable-pos.d.ts.map +1 -0
- package/dist/common/htmltable-types.d.ts +237 -0
- package/dist/common/htmltable-types.d.ts.map +1 -0
- package/dist/common/htmltable.d.ts +74 -0
- package/dist/common/htmltable.d.ts.map +1 -0
- package/dist/common/layers.d.ts +20 -0
- package/dist/common/layers.d.ts.map +1 -0
- package/dist/common/make-label.d.ts +60 -0
- package/dist/common/make-label.d.ts.map +1 -0
- package/dist/common/multicolor.d.ts +46 -0
- package/dist/common/multicolor.d.ts.map +1 -0
- package/dist/common/nodeinit.d.ts +45 -0
- package/dist/common/nodeinit.d.ts.map +1 -0
- package/dist/common/poly-gencode.d.ts +40 -0
- package/dist/common/poly-gencode.d.ts.map +1 -0
- package/dist/common/poly-init.d.ts +51 -0
- package/dist/common/poly-init.d.ts.map +1 -0
- package/dist/common/poly-inside.d.ts +37 -0
- package/dist/common/poly-inside.d.ts.map +1 -0
- package/dist/common/poly-shapes-cases.d.ts +31 -0
- package/dist/common/poly-shapes-cases.d.ts.map +1 -0
- package/dist/common/poly-shapes-cases2.d.ts +25 -0
- package/dist/common/poly-shapes-cases2.d.ts.map +1 -0
- package/dist/common/poly-shapes-util.d.ts +42 -0
- package/dist/common/poly-shapes-util.d.ts.map +1 -0
- package/dist/common/poly-shapes.d.ts +39 -0
- package/dist/common/poly-shapes.d.ts.map +1 -0
- package/dist/common/poly-sizing.d.ts +189 -0
- package/dist/common/poly-sizing.d.ts.map +1 -0
- package/dist/common/poly-vertices.d.ts +42 -0
- package/dist/common/poly-vertices.d.ts.map +1 -0
- package/dist/common/postproc.d.ts +49 -0
- package/dist/common/postproc.d.ts.map +1 -0
- package/dist/common/ps-fontalias.d.ts +36 -0
- package/dist/common/ps-fontalias.d.ts.map +1 -0
- package/dist/common/random.d.ts +8 -0
- package/dist/common/random.d.ts.map +1 -0
- package/dist/common/record-port.d.ts +34 -0
- package/dist/common/record-port.d.ts.map +1 -0
- package/dist/common/record.d.ts +105 -0
- package/dist/common/record.d.ts.map +1 -0
- package/dist/common/shapeData.d.ts +186 -0
- package/dist/common/shapeData.d.ts.map +1 -0
- package/dist/common/shapes.d.ts +36 -0
- package/dist/common/shapes.d.ts.map +1 -0
- package/dist/common/spline-midpoint.d.ts +31 -0
- package/dist/common/spline-midpoint.d.ts.map +1 -0
- package/dist/common/splines-clip.d.ts +21 -0
- package/dist/common/splines-clip.d.ts.map +1 -0
- package/dist/common/splines-constants.d.ts +42 -0
- package/dist/common/splines-constants.d.ts.map +1 -0
- package/dist/common/splines-geom.d.ts +51 -0
- package/dist/common/splines-geom.d.ts.map +1 -0
- package/dist/common/splines-path-begin.d.ts +17 -0
- package/dist/common/splines-path-begin.d.ts.map +1 -0
- package/dist/common/splines-path-end.d.ts +17 -0
- package/dist/common/splines-path-end.d.ts.map +1 -0
- package/dist/common/splines-path-shared.d.ts +53 -0
- package/dist/common/splines-path-shared.d.ts.map +1 -0
- package/dist/common/splines-routespl.d.ts +40 -0
- package/dist/common/splines-routespl.d.ts.map +1 -0
- package/dist/common/splines-selfedge.d.ts +21 -0
- package/dist/common/splines-selfedge.d.ts.map +1 -0
- package/dist/common/splines.d.ts +18 -0
- package/dist/common/splines.d.ts.map +1 -0
- package/dist/common/style-resolve.d.ts +197 -0
- package/dist/common/style-resolve.d.ts.map +1 -0
- package/dist/common/subst.d.ts +31 -0
- package/dist/common/subst.d.ts.map +1 -0
- package/dist/common/taper.d.ts +23 -0
- package/dist/common/taper.d.ts.map +1 -0
- package/dist/common/textmeasure-factory.d.ts +33 -0
- package/dist/common/textmeasure-factory.d.ts.map +1 -0
- package/dist/common/textmeasure-lookup.d.ts +17 -0
- package/dist/common/textmeasure-lookup.d.ts.map +1 -0
- package/dist/common/textmeasure-lut-data-a.d.ts +13 -0
- package/dist/common/textmeasure-lut-data-a.d.ts.map +1 -0
- package/dist/common/textmeasure-lut-data-b.d.ts +12 -0
- package/dist/common/textmeasure-lut-data-b.d.ts.map +1 -0
- package/dist/common/textmeasure-lut-data-c.d.ts +13 -0
- package/dist/common/textmeasure-lut-data-c.d.ts.map +1 -0
- package/dist/common/textmeasure-lut-data.d.ts +27 -0
- package/dist/common/textmeasure-lut-data.d.ts.map +1 -0
- package/dist/common/textmeasure.d.ts +120 -0
- package/dist/common/textmeasure.d.ts.map +1 -0
- package/dist/common/types.d.ts +316 -0
- package/dist/common/types.d.ts.map +1 -0
- package/dist/common/xlabels-place.d.ts +125 -0
- package/dist/common/xlabels-place.d.ts.map +1 -0
- package/dist/debug.d.ts +58 -0
- package/dist/debug.d.ts.map +1 -0
- package/dist/errors.d.ts +63 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/gvc/anchor.d.ts +86 -0
- package/dist/gvc/anchor.d.ts.map +1 -0
- package/dist/gvc/context.d.ts +177 -0
- package/dist/gvc/context.d.ts.map +1 -0
- package/dist/gvc/default-context.d.ts +20 -0
- package/dist/gvc/default-context.d.ts.map +1 -0
- package/dist/gvc/device-cluster.d.ts +25 -0
- package/dist/gvc/device-cluster.d.ts.map +1 -0
- package/dist/gvc/device.d.ts +99 -0
- package/dist/gvc/device.d.ts.map +1 -0
- package/dist/gvc/edge-labels.d.ts +10 -0
- package/dist/gvc/edge-labels.d.ts.map +1 -0
- package/dist/gvc/emit-walk.d.ts +22 -0
- package/dist/gvc/emit-walk.d.ts.map +1 -0
- package/dist/gvc/image-resolver.d.ts +44 -0
- package/dist/gvc/image-resolver.d.ts.map +1 -0
- package/dist/gvc/job.d.ts +234 -0
- package/dist/gvc/job.d.ts.map +1 -0
- package/dist/gvc/textlayout.d.ts +33 -0
- package/dist/gvc/textlayout.d.ts.map +1 -0
- package/dist/gvc/textspan-emit.d.ts +41 -0
- package/dist/gvc/textspan-emit.d.ts.map +1 -0
- package/dist/gvc/usershape.d.ts +25 -0
- package/dist/gvc/usershape.d.ts.map +1 -0
- package/dist/gvc/viewport.d.ts +71 -0
- package/dist/gvc/viewport.d.ts.map +1 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +52862 -0
- package/dist/index.js.map +7 -0
- package/dist/label/index.d.ts +74 -0
- package/dist/label/index.d.ts.map +1 -0
- package/dist/label/node.d.ts +108 -0
- package/dist/label/node.d.ts.map +1 -0
- package/dist/label/rectangle.d.ts +60 -0
- package/dist/label/rectangle.d.ts.map +1 -0
- package/dist/label/split-q.d.ts +13 -0
- package/dist/label/split-q.d.ts.map +1 -0
- package/dist/label/xlabels-geom.d.ts +46 -0
- package/dist/label/xlabels-geom.d.ts.map +1 -0
- package/dist/label/xlabels-intersect.d.ts +79 -0
- package/dist/label/xlabels-intersect.d.ts.map +1 -0
- package/dist/label/xlabels.d.ts +67 -0
- package/dist/label/xlabels.d.ts.map +1 -0
- package/dist/layout/circo/blockpath.d.ts +120 -0
- package/dist/layout/circo/blockpath.d.ts.map +1 -0
- package/dist/layout/circo/blocks.d.ts +135 -0
- package/dist/layout/circo/blocks.d.ts.map +1 -0
- package/dist/layout/circo/blocktree.d.ts +51 -0
- package/dist/layout/circo/blocktree.d.ts.map +1 -0
- package/dist/layout/circo/circular.d.ts +59 -0
- package/dist/layout/circo/circular.d.ts.map +1 -0
- package/dist/layout/circo/index.d.ts +35 -0
- package/dist/layout/circo/index.d.ts.map +1 -0
- package/dist/layout/circo/init.d.ts +50 -0
- package/dist/layout/circo/init.d.ts.map +1 -0
- package/dist/layout/circo/position-helpers.d.ts +33 -0
- package/dist/layout/circo/position-helpers.d.ts.map +1 -0
- package/dist/layout/circo/position.d.ts +66 -0
- package/dist/layout/circo/position.d.ts.map +1 -0
- package/dist/layout/dot/acyclic.d.ts +8 -0
- package/dist/layout/dot/acyclic.d.ts.map +1 -0
- package/dist/layout/dot/aspect.d.ts +17 -0
- package/dist/layout/dot/aspect.d.ts.map +1 -0
- package/dist/layout/dot/classify.d.ts +52 -0
- package/dist/layout/dot/classify.d.ts.map +1 -0
- package/dist/layout/dot/cluster-path.d.ts +38 -0
- package/dist/layout/dot/cluster-path.d.ts.map +1 -0
- package/dist/layout/dot/cluster.d.ts +102 -0
- package/dist/layout/dot/cluster.d.ts.map +1 -0
- package/dist/layout/dot/compound-clip.d.ts +69 -0
- package/dist/layout/dot/compound-clip.d.ts.map +1 -0
- package/dist/layout/dot/compound-geom.d.ts +115 -0
- package/dist/layout/dot/compound-geom.d.ts.map +1 -0
- package/dist/layout/dot/compound.d.ts +56 -0
- package/dist/layout/dot/compound.d.ts.map +1 -0
- package/dist/layout/dot/conc.d.ts +88 -0
- package/dist/layout/dot/conc.d.ts.map +1 -0
- package/dist/layout/dot/decomp.d.ts +66 -0
- package/dist/layout/dot/decomp.d.ts.map +1 -0
- package/dist/layout/dot/edge-order.d.ts +27 -0
- package/dist/layout/dot/edge-order.d.ts.map +1 -0
- package/dist/layout/dot/edge-route-arrow.d.ts +46 -0
- package/dist/layout/dot/edge-route-arrow.d.ts.map +1 -0
- package/dist/layout/dot/edge-route-boxes.d.ts +75 -0
- package/dist/layout/dot/edge-route-boxes.d.ts.map +1 -0
- package/dist/layout/dot/edge-route-chain.d.ts +67 -0
- package/dist/layout/dot/edge-route-chain.d.ts.map +1 -0
- package/dist/layout/dot/edge-route-clip.d.ts +88 -0
- package/dist/layout/dot/edge-route-clip.d.ts.map +1 -0
- package/dist/layout/dot/edge-route-faithful.d.ts +94 -0
- package/dist/layout/dot/edge-route-faithful.d.ts.map +1 -0
- package/dist/layout/dot/edge-route-geom.d.ts +43 -0
- package/dist/layout/dot/edge-route-geom.d.ts.map +1 -0
- package/dist/layout/dot/edge-route-helpers.d.ts +50 -0
- package/dist/layout/dot/edge-route-helpers.d.ts.map +1 -0
- package/dist/layout/dot/edge-route-poly.d.ts +48 -0
- package/dist/layout/dot/edge-route-poly.d.ts.map +1 -0
- package/dist/layout/dot/edge-route-rank.d.ts +57 -0
- package/dist/layout/dot/edge-route-rank.d.ts.map +1 -0
- package/dist/layout/dot/edge-route-routing.d.ts +99 -0
- package/dist/layout/dot/edge-route-routing.d.ts.map +1 -0
- package/dist/layout/dot/edge-route.d.ts +57 -0
- package/dist/layout/dot/edge-route.d.ts.map +1 -0
- package/dist/layout/dot/fastgr.d.ts +147 -0
- package/dist/layout/dot/fastgr.d.ts.map +1 -0
- package/dist/layout/dot/flat-utils.d.ts +21 -0
- package/dist/layout/dot/flat-utils.d.ts.map +1 -0
- package/dist/layout/dot/flat.d.ts +152 -0
- package/dist/layout/dot/flat.d.ts.map +1 -0
- package/dist/layout/dot/graph-label.d.ts +18 -0
- package/dist/layout/dot/graph-label.d.ts.map +1 -0
- package/dist/layout/dot/index.d.ts +128 -0
- package/dist/layout/dot/index.d.ts.map +1 -0
- package/dist/layout/dot/init.d.ts +93 -0
- package/dist/layout/dot/init.d.ts.map +1 -0
- package/dist/layout/dot/label-order.d.ts +45 -0
- package/dist/layout/dot/label-order.d.ts.map +1 -0
- package/dist/layout/dot/mincross-build.d.ts +52 -0
- package/dist/layout/dot/mincross-build.d.ts.map +1 -0
- package/dist/layout/dot/mincross-cross.d.ts +80 -0
- package/dist/layout/dot/mincross-cross.d.ts.map +1 -0
- package/dist/layout/dot/mincross-flat.d.ts +27 -0
- package/dist/layout/dot/mincross-flat.d.ts.map +1 -0
- package/dist/layout/dot/mincross-order.d.ts +89 -0
- package/dist/layout/dot/mincross-order.d.ts.map +1 -0
- package/dist/layout/dot/mincross-utils.d.ts +60 -0
- package/dist/layout/dot/mincross-utils.d.ts.map +1 -0
- package/dist/layout/dot/mincross.d.ts +75 -0
- package/dist/layout/dot/mincross.d.ts.map +1 -0
- package/dist/layout/dot/ns-core.d.ts +37 -0
- package/dist/layout/dot/ns-core.d.ts.map +1 -0
- package/dist/layout/dot/ns-range.d.ts +17 -0
- package/dist/layout/dot/ns-range.d.ts.map +1 -0
- package/dist/layout/dot/ns-subtree.d.ts +78 -0
- package/dist/layout/dot/ns-subtree.d.ts.map +1 -0
- package/dist/layout/dot/ns.d.ts +86 -0
- package/dist/layout/dot/ns.d.ts.map +1 -0
- package/dist/layout/dot/ortho-adapter.d.ts +19 -0
- package/dist/layout/dot/ortho-adapter.d.ts.map +1 -0
- package/dist/layout/dot/pack-components.d.ts +100 -0
- package/dist/layout/dot/pack-components.d.ts.map +1 -0
- package/dist/layout/dot/position-aux.d.ts +110 -0
- package/dist/layout/dot/position-aux.d.ts.map +1 -0
- package/dist/layout/dot/position-bbox.d.ts +57 -0
- package/dist/layout/dot/position-bbox.d.ts.map +1 -0
- package/dist/layout/dot/position-cluster.d.ts +48 -0
- package/dist/layout/dot/position-cluster.d.ts.map +1 -0
- package/dist/layout/dot/position-ycoords.d.ts +42 -0
- package/dist/layout/dot/position-ycoords.d.ts.map +1 -0
- package/dist/layout/dot/position.d.ts +47 -0
- package/dist/layout/dot/position.d.ts.map +1 -0
- package/dist/layout/dot/rank-dot2.d.ts +114 -0
- package/dist/layout/dot/rank-dot2.d.ts.map +1 -0
- package/dist/layout/dot/rank.d.ts +119 -0
- package/dist/layout/dot/rank.d.ts.map +1 -0
- package/dist/layout/dot/sameport.d.ts +70 -0
- package/dist/layout/dot/sameport.d.ts.map +1 -0
- package/dist/layout/dot/self-loop.d.ts +54 -0
- package/dist/layout/dot/self-loop.d.ts.map +1 -0
- package/dist/layout/dot/splines-clone.d.ts +33 -0
- package/dist/layout/dot/splines-clone.d.ts.map +1 -0
- package/dist/layout/dot/splines-flat-labeled.d.ts +101 -0
- package/dist/layout/dot/splines-flat-labeled.d.ts.map +1 -0
- package/dist/layout/dot/splines-flat-multi.d.ts +35 -0
- package/dist/layout/dot/splines-flat-multi.d.ts.map +1 -0
- package/dist/layout/dot/splines-flat.d.ts +116 -0
- package/dist/layout/dot/splines-flat.d.ts.map +1 -0
- package/dist/layout/dot/splines-groups.d.ts +38 -0
- package/dist/layout/dot/splines-groups.d.ts.map +1 -0
- package/dist/layout/dot/splines-label.d.ts +105 -0
- package/dist/layout/dot/splines-label.d.ts.map +1 -0
- package/dist/layout/dot/splines-route-type.d.ts +28 -0
- package/dist/layout/dot/splines-route-type.d.ts.map +1 -0
- package/dist/layout/dot/splines-route.d.ts +99 -0
- package/dist/layout/dot/splines-route.d.ts.map +1 -0
- package/dist/layout/dot/splines.d.ts +157 -0
- package/dist/layout/dot/splines.d.ts.map +1 -0
- package/dist/layout/dot/straight-edges.d.ts +39 -0
- package/dist/layout/dot/straight-edges.d.ts.map +1 -0
- package/dist/layout/fdp/cluster-edges.d.ts +31 -0
- package/dist/layout/fdp/cluster-edges.d.ts.map +1 -0
- package/dist/layout/fdp/comp.d.ts +29 -0
- package/dist/layout/fdp/comp.d.ts.map +1 -0
- package/dist/layout/fdp/derive.d.ts +39 -0
- package/dist/layout/fdp/derive.d.ts.map +1 -0
- package/dist/layout/fdp/fdp-model.d.ts +101 -0
- package/dist/layout/fdp/fdp-model.d.ts.map +1 -0
- package/dist/layout/fdp/grid.d.ts +49 -0
- package/dist/layout/fdp/grid.d.ts.map +1 -0
- package/dist/layout/fdp/index.d.ts +36 -0
- package/dist/layout/fdp/index.d.ts.map +1 -0
- package/dist/layout/fdp/init.d.ts +26 -0
- package/dist/layout/fdp/init.d.ts.map +1 -0
- package/dist/layout/fdp/layout.d.ts +57 -0
- package/dist/layout/fdp/layout.d.ts.map +1 -0
- package/dist/layout/fdp/normalize.d.ts +18 -0
- package/dist/layout/fdp/normalize.d.ts.map +1 -0
- package/dist/layout/fdp/ports.d.ts +41 -0
- package/dist/layout/fdp/ports.d.ts.map +1 -0
- package/dist/layout/fdp/tlayout-init.d.ts +21 -0
- package/dist/layout/fdp/tlayout-init.d.ts.map +1 -0
- package/dist/layout/fdp/tlayout-parms.d.ts +98 -0
- package/dist/layout/fdp/tlayout-parms.d.ts.map +1 -0
- package/dist/layout/fdp/tlayout.d.ts +43 -0
- package/dist/layout/fdp/tlayout.d.ts.map +1 -0
- package/dist/layout/fdp/xlayout.d.ts +26 -0
- package/dist/layout/fdp/xlayout.d.ts.map +1 -0
- package/dist/layout/neato/bfs.d.ts +43 -0
- package/dist/layout/neato/bfs.d.ts.map +1 -0
- package/dist/layout/neato/cdt-surface.d.ts +16 -0
- package/dist/layout/neato/cdt-surface.d.ts.map +1 -0
- package/dist/layout/neato/conjgrad.d.ts +61 -0
- package/dist/layout/neato/conjgrad.d.ts.map +1 -0
- package/dist/layout/neato/delaunay.d.ts +28 -0
- package/dist/layout/neato/delaunay.d.ts.map +1 -0
- package/dist/layout/neato/dijkstra.d.ts +80 -0
- package/dist/layout/neato/dijkstra.d.ts.map +1 -0
- package/dist/layout/neato/fdp-adjust.d.ts +31 -0
- package/dist/layout/neato/fdp-adjust.d.ts.map +1 -0
- package/dist/layout/neato/index.d.ts +58 -0
- package/dist/layout/neato/index.d.ts.map +1 -0
- package/dist/layout/neato/init.d.ts +207 -0
- package/dist/layout/neato/init.d.ts.map +1 -0
- package/dist/layout/neato/legal.d.ts +7 -0
- package/dist/layout/neato/legal.d.ts.map +1 -0
- package/dist/layout/neato/matrix-ops.d.ts +28 -0
- package/dist/layout/neato/matrix-ops.d.ts.map +1 -0
- package/dist/layout/neato/multispline-router.d.ts +80 -0
- package/dist/layout/neato/multispline-router.d.ts.map +1 -0
- package/dist/layout/neato/multispline.d.ts +24 -0
- package/dist/layout/neato/multispline.d.ts.map +1 -0
- package/dist/layout/neato/overlap-prism.d.ts +26 -0
- package/dist/layout/neato/overlap-prism.d.ts.map +1 -0
- package/dist/layout/neato/overlap.d.ts +85 -0
- package/dist/layout/neato/overlap.d.ts.map +1 -0
- package/dist/layout/neato/sc-adjust.d.ts +33 -0
- package/dist/layout/neato/sc-adjust.d.ts.map +1 -0
- package/dist/layout/neato/sep-factor.d.ts +41 -0
- package/dist/layout/neato/sep-factor.d.ts.map +1 -0
- package/dist/layout/neato/set-aspect.d.ts +33 -0
- package/dist/layout/neato/set-aspect.d.ts.map +1 -0
- package/dist/layout/neato/sgd.d.ts +166 -0
- package/dist/layout/neato/sgd.d.ts.map +1 -0
- package/dist/layout/neato/splines.d.ts +153 -0
- package/dist/layout/neato/splines.d.ts.map +1 -0
- package/dist/layout/neato/stress-kernel.d.ts +53 -0
- package/dist/layout/neato/stress-kernel.d.ts.map +1 -0
- package/dist/layout/neato/stress.d.ts +158 -0
- package/dist/layout/neato/stress.d.ts.map +1 -0
- package/dist/layout/osage/index.d.ts +158 -0
- package/dist/layout/osage/index.d.ts.map +1 -0
- package/dist/layout/osage/layout.d.ts +49 -0
- package/dist/layout/osage/layout.d.ts.map +1 -0
- package/dist/layout/osage/types.d.ts +28 -0
- package/dist/layout/osage/types.d.ts.map +1 -0
- package/dist/layout/pack/array-pack.d.ts +114 -0
- package/dist/layout/pack/array-pack.d.ts.map +1 -0
- package/dist/layout/pack/index.d.ts +157 -0
- package/dist/layout/pack/index.d.ts.map +1 -0
- package/dist/layout/pack/poly-pack.d.ts +126 -0
- package/dist/layout/pack/poly-pack.d.ts.map +1 -0
- package/dist/layout/pack/poly-place.d.ts +27 -0
- package/dist/layout/pack/poly-place.d.ts.map +1 -0
- package/dist/layout/pack/spiral-search.d.ts +18 -0
- package/dist/layout/pack/spiral-search.d.ts.map +1 -0
- package/dist/layout/pack/test-helpers.d.ts +7 -0
- package/dist/layout/pack/test-helpers.d.ts.map +1 -0
- package/dist/layout/pack/types.d.ts +74 -0
- package/dist/layout/pack/types.d.ts.map +1 -0
- package/dist/layout/patchwork/index.d.ts +136 -0
- package/dist/layout/patchwork/index.d.ts.map +1 -0
- package/dist/layout/patchwork/tree-map.d.ts +32 -0
- package/dist/layout/patchwork/tree-map.d.ts.map +1 -0
- package/dist/layout/patchwork/tree-node.d.ts +46 -0
- package/dist/layout/patchwork/tree-node.d.ts.map +1 -0
- package/dist/layout/sfdp/index.d.ts +39 -0
- package/dist/layout/sfdp/index.d.ts.map +1 -0
- package/dist/layout/sfdp/init.d.ts +61 -0
- package/dist/layout/sfdp/init.d.ts.map +1 -0
- package/dist/layout/sfdp/multilevel.d.ts +23 -0
- package/dist/layout/sfdp/multilevel.d.ts.map +1 -0
- package/dist/layout/sfdp/quadtree.d.ts +45 -0
- package/dist/layout/sfdp/quadtree.d.ts.map +1 -0
- package/dist/layout/sfdp/sparse-matrix-multiply.d.ts +34 -0
- package/dist/layout/sfdp/sparse-matrix-multiply.d.ts.map +1 -0
- package/dist/layout/sfdp/sparse-matrix.d.ts +101 -0
- package/dist/layout/sfdp/sparse-matrix.d.ts.map +1 -0
- package/dist/layout/sfdp/sparse-solve.d.ts +14 -0
- package/dist/layout/sfdp/sparse-solve.d.ts.map +1 -0
- package/dist/layout/sfdp/spring-driver.d.ts +18 -0
- package/dist/layout/sfdp/spring-driver.d.ts.map +1 -0
- package/dist/layout/sfdp/spring-electrical.d.ts +61 -0
- package/dist/layout/sfdp/spring-electrical.d.ts.map +1 -0
- package/dist/layout/twopi/circle.d.ts +83 -0
- package/dist/layout/twopi/circle.d.ts.map +1 -0
- package/dist/layout/twopi/index.d.ts +24 -0
- package/dist/layout/twopi/index.d.ts.map +1 -0
- package/dist/layout/twopi/init.d.ts +85 -0
- package/dist/layout/twopi/init.d.ts.map +1 -0
- package/dist/layout/twopi/pipeline.d.ts +37 -0
- package/dist/layout/twopi/pipeline.d.ts.map +1 -0
- package/dist/layout/twopi/twopi-test-helpers.d.ts +12 -0
- package/dist/layout/twopi/twopi-test-helpers.d.ts.map +1 -0
- package/dist/model/algData.d.ts +175 -0
- package/dist/model/algData.d.ts.map +1 -0
- package/dist/model/cgraph-ops.d.ts +86 -0
- package/dist/model/cgraph-ops.d.ts.map +1 -0
- package/dist/model/edge.d.ts +80 -0
- package/dist/model/edge.d.ts.map +1 -0
- package/dist/model/edgeInfo.d.ts +299 -0
- package/dist/model/edgeInfo.d.ts.map +1 -0
- package/dist/model/geom.d.ts +149 -0
- package/dist/model/geom.d.ts.map +1 -0
- package/dist/model/graph.d.ts +135 -0
- package/dist/model/graph.d.ts.map +1 -0
- package/dist/model/graphInfo.d.ts +418 -0
- package/dist/model/graphInfo.d.ts.map +1 -0
- package/dist/model/index.d.ts +10 -0
- package/dist/model/index.d.ts.map +1 -0
- package/dist/model/layoutParams.d.ts +85 -0
- package/dist/model/layoutParams.d.ts.map +1 -0
- package/dist/model/node.d.ts +101 -0
- package/dist/model/node.d.ts.map +1 -0
- package/dist/model/nodeInfo.d.ts +278 -0
- package/dist/model/nodeInfo.d.ts.map +1 -0
- package/dist/model/rankEntry.d.ts +80 -0
- package/dist/model/rankEntry.d.ts.map +1 -0
- package/dist/ortho/chan-dict.d.ts +82 -0
- package/dist/ortho/chan-dict.d.ts.map +1 -0
- package/dist/ortho/fpq.d.ts +54 -0
- package/dist/ortho/fpq.d.ts.map +1 -0
- package/dist/ortho/index.d.ts +35 -0
- package/dist/ortho/index.d.ts.map +1 -0
- package/dist/ortho/maze-channels.d.ts +47 -0
- package/dist/ortho/maze-channels.d.ts.map +1 -0
- package/dist/ortho/maze.d.ts +27 -0
- package/dist/ortho/maze.d.ts.map +1 -0
- package/dist/ortho/ortho-parallel.d.ts +26 -0
- package/dist/ortho/ortho-parallel.d.ts.map +1 -0
- package/dist/ortho/ortho-route.d.ts +31 -0
- package/dist/ortho/ortho-route.d.ts.map +1 -0
- package/dist/ortho/partition.d.ts +34 -0
- package/dist/ortho/partition.d.ts.map +1 -0
- package/dist/ortho/rawgraph.d.ts +41 -0
- package/dist/ortho/rawgraph.d.ts.map +1 -0
- package/dist/ortho/sgraph.d.ts +74 -0
- package/dist/ortho/sgraph.d.ts.map +1 -0
- package/dist/ortho/trap-query.d.ts +32 -0
- package/dist/ortho/trap-query.d.ts.map +1 -0
- package/dist/ortho/trap-segment.d.ts +23 -0
- package/dist/ortho/trap-segment.d.ts.map +1 -0
- package/dist/ortho/trap-types.d.ts +91 -0
- package/dist/ortho/trap-types.d.ts.map +1 -0
- package/dist/ortho/trap-update.d.ts +12 -0
- package/dist/ortho/trap-update.d.ts.map +1 -0
- package/dist/ortho/trapezoid.d.ts +18 -0
- package/dist/ortho/trapezoid.d.ts.map +1 -0
- package/dist/ortho/types.d.ts +241 -0
- package/dist/ortho/types.d.ts.map +1 -0
- package/dist/parser/ast.d.ts +49 -0
- package/dist/parser/ast.d.ts.map +1 -0
- package/dist/parser/builder.d.ts +75 -0
- package/dist/parser/builder.d.ts.map +1 -0
- package/dist/parser/index.d.ts +97 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/pathplan/index.d.ts +8 -0
- package/dist/pathplan/index.d.ts.map +1 -0
- package/dist/pathplan/route.d.ts +9 -0
- package/dist/pathplan/route.d.ts.map +1 -0
- package/dist/pathplan/shortest.d.ts +5 -0
- package/dist/pathplan/shortest.d.ts.map +1 -0
- package/dist/pathplan/solvers.d.ts +5 -0
- package/dist/pathplan/solvers.d.ts.map +1 -0
- package/dist/pathplan/triang.d.ts +25 -0
- package/dist/pathplan/triang.d.ts.map +1 -0
- package/dist/pathplan/types.d.ts +22 -0
- package/dist/pathplan/types.d.ts.map +1 -0
- package/dist/pathplan/visibility.d.ts +13 -0
- package/dist/pathplan/visibility.d.ts.map +1 -0
- package/dist/pathplan/vispath.d.ts +9 -0
- package/dist/pathplan/vispath.d.ts.map +1 -0
- package/dist/rbtree/index.d.ts +127 -0
- package/dist/rbtree/index.d.ts.map +1 -0
- package/dist/render/color-resolve.d.ts +38 -0
- package/dist/render/color-resolve.d.ts.map +1 -0
- package/dist/render/dot.d.ts +379 -0
- package/dist/render/dot.d.ts.map +1 -0
- package/dist/render/index.d.ts +24 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/json.d.ts +89 -0
- package/dist/render/json.d.ts.map +1 -0
- package/dist/render/map-test-helpers.d.ts +13 -0
- package/dist/render/map-test-helpers.d.ts.map +1 -0
- package/dist/render/map.d.ts +197 -0
- package/dist/render/map.d.ts.map +1 -0
- package/dist/render/public.d.ts +64 -0
- package/dist/render/public.d.ts.map +1 -0
- package/dist/render/svg-arrow-ops.d.ts +10 -0
- package/dist/render/svg-arrow-ops.d.ts.map +1 -0
- package/dist/render/svg-cluster.d.ts +9 -0
- package/dist/render/svg-cluster.d.ts.map +1 -0
- package/dist/render/svg-edge-ortho-radius.d.ts +31 -0
- package/dist/render/svg-edge-ortho-radius.d.ts.map +1 -0
- package/dist/render/svg-edge-split.d.ts +48 -0
- package/dist/render/svg-edge-split.d.ts.map +1 -0
- package/dist/render/svg-gradient.d.ts +46 -0
- package/dist/render/svg-gradient.d.ts.map +1 -0
- package/dist/render/svg-graph.d.ts +97 -0
- package/dist/render/svg-graph.d.ts.map +1 -0
- package/dist/render/svg-helpers.d.ts +122 -0
- package/dist/render/svg-helpers.d.ts.map +1 -0
- package/dist/render/svg-id.d.ts +28 -0
- package/dist/render/svg-id.d.ts.map +1 -0
- package/dist/render/svg-multicolor.d.ts +60 -0
- package/dist/render/svg-multicolor.d.ts.map +1 -0
- package/dist/render/svg-parallel-edge.d.ts +28 -0
- package/dist/render/svg-parallel-edge.d.ts.map +1 -0
- package/dist/render/svg-tapered-edge.d.ts +18 -0
- package/dist/render/svg-tapered-edge.d.ts.map +1 -0
- package/dist/render/svg.d.ts +104 -0
- package/dist/render/svg.d.ts.map +1 -0
- package/dist/render/xdot-public.d.ts +95 -0
- package/dist/render/xdot-public.d.ts.map +1 -0
- package/dist/render/xml-escape.d.ts +21 -0
- package/dist/render/xml-escape.d.ts.map +1 -0
- package/dist/render.js +52556 -0
- package/dist/render.js.map +7 -0
- package/dist/util/agxbuf.d.ts +72 -0
- package/dist/util/agxbuf.d.ts.map +1 -0
- package/dist/util/bsd-qsort.d.ts +22 -0
- package/dist/util/bsd-qsort.d.ts.map +1 -0
- package/dist/util/index.d.ts +11 -0
- package/dist/util/index.d.ts.map +1 -0
- package/dist/util/list.d.ts +47 -0
- package/dist/util/list.d.ts.map +1 -0
- package/dist/util/math.d.ts +54 -0
- package/dist/util/math.d.ts.map +1 -0
- package/dist/util/mt19937.d.ts +40 -0
- package/dist/util/mt19937.d.ts.map +1 -0
- package/dist/util/printf-round.d.ts +45 -0
- package/dist/util/printf-round.d.ts.map +1 -0
- package/dist/util/xml.d.ts +24 -0
- package/dist/util/xml.d.ts.map +1 -0
- package/dist/vpsc/Block.d.ts +116 -0
- package/dist/vpsc/Block.d.ts.map +1 -0
- package/dist/vpsc/Blocks.d.ts +66 -0
- package/dist/vpsc/Blocks.d.ts.map +1 -0
- package/dist/vpsc/Constraint.d.ts +51 -0
- package/dist/vpsc/Constraint.d.ts.map +1 -0
- package/dist/vpsc/HeapUtils.d.ts +37 -0
- package/dist/vpsc/HeapUtils.d.ts.map +1 -0
- package/dist/vpsc/Solver.d.ts +107 -0
- package/dist/vpsc/Solver.d.ts.map +1 -0
- package/dist/vpsc/SweepLine.d.ts +38 -0
- package/dist/vpsc/SweepLine.d.ts.map +1 -0
- package/dist/vpsc/Variable.d.ts +38 -0
- package/dist/vpsc/Variable.d.ts.map +1 -0
- package/dist/vpsc/index.d.ts +54 -0
- package/dist/vpsc/index.d.ts.map +1 -0
- package/dist/vpsc/sweepTypes.d.ts +29 -0
- package/dist/vpsc/sweepTypes.d.ts.map +1 -0
- package/dist/xdot/index.d.ts +6 -0
- package/dist/xdot/index.d.ts.map +1 -0
- package/dist/xdot/lex.d.ts +22 -0
- package/dist/xdot/lex.d.ts.map +1 -0
- package/dist/xdot/misc.d.ts +13 -0
- package/dist/xdot/misc.d.ts.map +1 -0
- package/dist/xdot/parse.d.ts +21 -0
- package/dist/xdot/parse.d.ts.map +1 -0
- package/dist/xdot/types.d.ts +152 -0
- package/dist/xdot/types.d.ts.map +1 -0
- package/package.json +113 -0
- package/src/api/builder.ts +277 -0
- package/src/api/edge-ops.ts +113 -0
- package/src/api/geometry.ts +307 -0
- package/src/api/index.ts +46 -0
- package/src/cdt/bag.ts +196 -0
- package/src/cdt/hash-core.ts +84 -0
- package/src/cdt/hash.ts +207 -0
- package/src/cdt/index.ts +17 -0
- package/src/cdt/splay-core.ts +301 -0
- package/src/cdt/splay.ts +191 -0
- package/src/cdt/strhash.ts +52 -0
- package/src/cdt/types.ts +13 -0
- package/src/common/arith.ts +39 -0
- package/src/common/arm-pow-data.ts +201 -0
- package/src/common/arm-pow.ts +296 -0
- package/src/common/arrows-constants.ts +175 -0
- package/src/common/arrows-geometry.ts +62 -0
- package/src/common/arrows-miter.ts +84 -0
- package/src/common/arrows-shapes-poly.ts +241 -0
- package/src/common/arrows-shapes-util.ts +162 -0
- package/src/common/arrows-shapes.ts +360 -0
- package/src/common/arrows-types.ts +59 -0
- package/src/common/arrows.ts +209 -0
- package/src/common/color.ts +340 -0
- package/src/common/colorData.ts +264 -0
- package/src/common/compass-port.ts +384 -0
- package/src/common/crand.ts +79 -0
- package/src/common/edge-label-init.ts +310 -0
- package/src/common/edge-offset.ts +140 -0
- package/src/common/ellipse-wedge.ts +189 -0
- package/src/common/emit-types.ts +332 -0
- package/src/common/fma.ts +52 -0
- package/src/common/graph-init.ts +359 -0
- package/src/common/html-entities.ts +299 -0
- package/src/common/html-string.ts +22 -0
- package/src/common/htmltable-emit-fill.ts +367 -0
- package/src/common/htmltable-emit-rules.ts +293 -0
- package/src/common/htmltable-emit.ts +345 -0
- package/src/common/htmltable-lex.ts +138 -0
- package/src/common/htmltable-parse.ts +539 -0
- package/src/common/htmltable-port.ts +32 -0
- package/src/common/htmltable-pos-runs.ts +313 -0
- package/src/common/htmltable-pos.ts +601 -0
- package/src/common/htmltable-types.ts +262 -0
- package/src/common/htmltable.ts +477 -0
- package/src/common/layers.ts +113 -0
- package/src/common/make-label.ts +192 -0
- package/src/common/multicolor.ts +167 -0
- package/src/common/nodeinit.ts +339 -0
- package/src/common/poly-gencode.ts +475 -0
- package/src/common/poly-init.ts +133 -0
- package/src/common/poly-inside.ts +202 -0
- package/src/common/poly-shapes-cases.ts +253 -0
- package/src/common/poly-shapes-cases2.ts +170 -0
- package/src/common/poly-shapes-util.ts +124 -0
- package/src/common/poly-shapes.ts +181 -0
- package/src/common/poly-sizing.ts +639 -0
- package/src/common/poly-vertices.ts +237 -0
- package/src/common/postproc.ts +436 -0
- package/src/common/ps-fontalias.ts +112 -0
- package/src/common/random.ts +37 -0
- package/src/common/record-port.ts +89 -0
- package/src/common/record.ts +523 -0
- package/src/common/shapeData.ts +502 -0
- package/src/common/shapes.ts +187 -0
- package/src/common/spline-midpoint.ts +105 -0
- package/src/common/splines-clip.ts +449 -0
- package/src/common/splines-constants.ts +53 -0
- package/src/common/splines-geom.ts +194 -0
- package/src/common/splines-path-begin.ts +243 -0
- package/src/common/splines-path-end.ts +231 -0
- package/src/common/splines-path-shared.ts +258 -0
- package/src/common/splines-routespl.ts +444 -0
- package/src/common/splines-selfedge.ts +403 -0
- package/src/common/splines.ts +33 -0
- package/src/common/style-resolve.ts +481 -0
- package/src/common/subst.ts +126 -0
- package/src/common/taper.ts +219 -0
- package/src/common/textmeasure-factory.ts +86 -0
- package/src/common/textmeasure-lookup.ts +50 -0
- package/src/common/textmeasure-lut-data-a.ts +182 -0
- package/src/common/textmeasure-lut-data-b.ts +140 -0
- package/src/common/textmeasure-lut-data-c.ts +221 -0
- package/src/common/textmeasure-lut-data.ts +78 -0
- package/src/common/textmeasure.ts +321 -0
- package/src/common/types.ts +425 -0
- package/src/common/xlabels-place.ts +475 -0
- package/src/debug.ts +38 -0
- package/src/errors.ts +95 -0
- package/src/gvc/anchor.ts +530 -0
- package/src/gvc/context.ts +260 -0
- package/src/gvc/default-context.ts +70 -0
- package/src/gvc/device-cluster.ts +114 -0
- package/src/gvc/device.ts +598 -0
- package/src/gvc/edge-labels.ts +116 -0
- package/src/gvc/emit-walk.ts +98 -0
- package/src/gvc/image-resolver.ts +137 -0
- package/src/gvc/job.ts +401 -0
- package/src/gvc/textlayout.ts +44 -0
- package/src/gvc/textspan-emit.ts +100 -0
- package/src/gvc/usershape.ts +33 -0
- package/src/gvc/viewport.ts +203 -0
- package/src/index.ts +165 -0
- package/src/label/index.ts +302 -0
- package/src/label/node.ts +278 -0
- package/src/label/rectangle.ts +123 -0
- package/src/label/split-q.ts +237 -0
- package/src/label/xlabels-geom.ts +132 -0
- package/src/label/xlabels-intersect.ts +421 -0
- package/src/label/xlabels.ts +212 -0
- package/src/layout/circo/blockpath.ts +560 -0
- package/src/layout/circo/blocks.ts +234 -0
- package/src/layout/circo/blocktree.ts +246 -0
- package/src/layout/circo/circular.ts +227 -0
- package/src/layout/circo/index.ts +90 -0
- package/src/layout/circo/init.ts +84 -0
- package/src/layout/circo/position-helpers.ts +104 -0
- package/src/layout/circo/position.ts +196 -0
- package/src/layout/dot/__fixtures__/xcoord-pivot-divergence.gv +79 -0
- package/src/layout/dot/acyclic.ts +60 -0
- package/src/layout/dot/aspect.ts +22 -0
- package/src/layout/dot/classify.ts +462 -0
- package/src/layout/dot/cluster-path.ts +200 -0
- package/src/layout/dot/cluster.ts +480 -0
- package/src/layout/dot/compound-clip.ts +224 -0
- package/src/layout/dot/compound-geom.ts +282 -0
- package/src/layout/dot/compound.ts +294 -0
- package/src/layout/dot/conc.ts +433 -0
- package/src/layout/dot/decomp.ts +165 -0
- package/src/layout/dot/edge-order.ts +62 -0
- package/src/layout/dot/edge-route-arrow.ts +87 -0
- package/src/layout/dot/edge-route-boxes.ts +99 -0
- package/src/layout/dot/edge-route-chain.ts +420 -0
- package/src/layout/dot/edge-route-clip.ts +253 -0
- package/src/layout/dot/edge-route-faithful.ts +404 -0
- package/src/layout/dot/edge-route-geom.ts +90 -0
- package/src/layout/dot/edge-route-helpers.ts +170 -0
- package/src/layout/dot/edge-route-poly.ts +174 -0
- package/src/layout/dot/edge-route-rank.ts +136 -0
- package/src/layout/dot/edge-route-routing.ts +219 -0
- package/src/layout/dot/edge-route.ts +490 -0
- package/src/layout/dot/fastgr.ts +377 -0
- package/src/layout/dot/flat-utils.ts +52 -0
- package/src/layout/dot/flat.ts +518 -0
- package/src/layout/dot/graph-label.ts +114 -0
- package/src/layout/dot/index.ts +302 -0
- package/src/layout/dot/init.ts +300 -0
- package/src/layout/dot/label-order.ts +161 -0
- package/src/layout/dot/mincross-build.ts +373 -0
- package/src/layout/dot/mincross-cross.ts +409 -0
- package/src/layout/dot/mincross-flat.ts +213 -0
- package/src/layout/dot/mincross-order.ts +401 -0
- package/src/layout/dot/mincross-utils.ts +170 -0
- package/src/layout/dot/mincross.ts +386 -0
- package/src/layout/dot/ns-core.ts +122 -0
- package/src/layout/dot/ns-range.ts +228 -0
- package/src/layout/dot/ns-subtree.ts +347 -0
- package/src/layout/dot/ns.ts +506 -0
- package/src/layout/dot/ortho-adapter.ts +133 -0
- package/src/layout/dot/pack-components.ts +433 -0
- package/src/layout/dot/position-aux.ts +382 -0
- package/src/layout/dot/position-bbox.ts +221 -0
- package/src/layout/dot/position-cluster.ts +286 -0
- package/src/layout/dot/position-ycoords.ts +287 -0
- package/src/layout/dot/position.ts +194 -0
- package/src/layout/dot/rank-dot2.ts +528 -0
- package/src/layout/dot/rank.ts +547 -0
- package/src/layout/dot/sameport.ts +278 -0
- package/src/layout/dot/self-loop.ts +147 -0
- package/src/layout/dot/splines-clone.ts +121 -0
- package/src/layout/dot/splines-flat-labeled.ts +408 -0
- package/src/layout/dot/splines-flat-multi.ts +188 -0
- package/src/layout/dot/splines-flat.ts +598 -0
- package/src/layout/dot/splines-groups.ts +190 -0
- package/src/layout/dot/splines-label.ts +432 -0
- package/src/layout/dot/splines-route-type.ts +44 -0
- package/src/layout/dot/splines-route.ts +478 -0
- package/src/layout/dot/splines.ts +538 -0
- package/src/layout/dot/straight-edges.ts +332 -0
- package/src/layout/fdp/cluster-edges.ts +0 -0
- package/src/layout/fdp/comp.ts +160 -0
- package/src/layout/fdp/derive.ts +272 -0
- package/src/layout/fdp/fdp-model.ts +238 -0
- package/src/layout/fdp/grid.ts +77 -0
- package/src/layout/fdp/index.ts +113 -0
- package/src/layout/fdp/init.ts +116 -0
- package/src/layout/fdp/layout.ts +485 -0
- package/src/layout/fdp/normalize.ts +100 -0
- package/src/layout/fdp/ports.ts +161 -0
- package/src/layout/fdp/tlayout-init.ts +230 -0
- package/src/layout/fdp/tlayout-parms.ts +234 -0
- package/src/layout/fdp/tlayout.ts +317 -0
- package/src/layout/fdp/xlayout.ts +350 -0
- package/src/layout/neato/bfs.ts +131 -0
- package/src/layout/neato/cdt-surface.ts +693 -0
- package/src/layout/neato/conjgrad.ts +159 -0
- package/src/layout/neato/delaunay.ts +259 -0
- package/src/layout/neato/dijkstra.ts +296 -0
- package/src/layout/neato/fdp-adjust.ts +124 -0
- package/src/layout/neato/index.ts +310 -0
- package/src/layout/neato/init.ts +505 -0
- package/src/layout/neato/legal.ts +316 -0
- package/src/layout/neato/matrix-ops.ts +112 -0
- package/src/layout/neato/multispline-router.ts +417 -0
- package/src/layout/neato/multispline.ts +427 -0
- package/src/layout/neato/overlap-prism.ts +503 -0
- package/src/layout/neato/overlap.ts +158 -0
- package/src/layout/neato/sc-adjust.ts +206 -0
- package/src/layout/neato/sep-factor.ts +118 -0
- package/src/layout/neato/set-aspect.ts +178 -0
- package/src/layout/neato/sgd.ts +461 -0
- package/src/layout/neato/splines.ts +1014 -0
- package/src/layout/neato/stress-kernel.ts +362 -0
- package/src/layout/neato/stress.ts +420 -0
- package/src/layout/osage/index.ts +548 -0
- package/src/layout/osage/layout.ts +254 -0
- package/src/layout/osage/types.ts +31 -0
- package/src/layout/pack/array-pack.ts +254 -0
- package/src/layout/pack/index.ts +570 -0
- package/src/layout/pack/poly-pack.ts +266 -0
- package/src/layout/pack/poly-place.ts +426 -0
- package/src/layout/pack/spiral-search.ts +106 -0
- package/src/layout/pack/test-helpers.ts +46 -0
- package/src/layout/pack/types.ts +85 -0
- package/src/layout/patchwork/index.ts +441 -0
- package/src/layout/patchwork/tree-map.ts +183 -0
- package/src/layout/patchwork/tree-node.ts +120 -0
- package/src/layout/sfdp/index.ts +231 -0
- package/src/layout/sfdp/init.ts +224 -0
- package/src/layout/sfdp/multilevel.ts +262 -0
- package/src/layout/sfdp/quadtree.ts +267 -0
- package/src/layout/sfdp/sparse-matrix-multiply.ts +160 -0
- package/src/layout/sfdp/sparse-matrix.ts +367 -0
- package/src/layout/sfdp/sparse-solve.ts +122 -0
- package/src/layout/sfdp/spring-driver.ts +209 -0
- package/src/layout/sfdp/spring-electrical.ts +431 -0
- package/src/layout/twopi/circle.ts +339 -0
- package/src/layout/twopi/index.ts +78 -0
- package/src/layout/twopi/init.ts +174 -0
- package/src/layout/twopi/pipeline.ts +98 -0
- package/src/layout/twopi/twopi-test-helpers.ts +44 -0
- package/src/model/algData.ts +194 -0
- package/src/model/cgraph-ops.ts +175 -0
- package/src/model/edge.ts +104 -0
- package/src/model/edgeInfo.ts +431 -0
- package/src/model/geom.ts +204 -0
- package/src/model/graph.ts +172 -0
- package/src/model/graphInfo.ts +528 -0
- package/src/model/index.ts +10 -0
- package/src/model/layoutParams.ts +127 -0
- package/src/model/node.ts +138 -0
- package/src/model/nodeInfo.ts +424 -0
- package/src/model/rankEntry.ts +95 -0
- package/src/ortho/chan-dict.ts +236 -0
- package/src/ortho/fpq.ts +149 -0
- package/src/ortho/index.ts +206 -0
- package/src/ortho/maze-channels.ts +196 -0
- package/src/ortho/maze.ts +311 -0
- package/src/ortho/ortho-parallel.ts +254 -0
- package/src/ortho/ortho-route.ts +349 -0
- package/src/ortho/partition.ts +303 -0
- package/src/ortho/rawgraph.ts +119 -0
- package/src/ortho/sgraph.ts +283 -0
- package/src/ortho/trap-query.ts +174 -0
- package/src/ortho/trap-segment.ts +303 -0
- package/src/ortho/trap-types.ts +171 -0
- package/src/ortho/trap-update.ts +89 -0
- package/src/ortho/trapezoid.ts +120 -0
- package/src/ortho/types.ts +286 -0
- package/src/parser/__fixtures__/directed.dot +10 -0
- package/src/parser/__fixtures__/simple.dot +7 -0
- package/src/parser/__fixtures__/subgraph.dot +17 -0
- package/src/parser/ast.ts +64 -0
- package/src/parser/builder.ts +401 -0
- package/src/parser/dot.d.ts +208 -0
- package/src/parser/dot.js +3321 -0
- package/src/parser/dot.pegjs +263 -0
- package/src/parser/index.ts +353 -0
- package/src/pathplan/index.ts +9 -0
- package/src/pathplan/route.ts +284 -0
- package/src/pathplan/shortest.ts +268 -0
- package/src/pathplan/solvers.ts +65 -0
- package/src/pathplan/triang.ts +221 -0
- package/src/pathplan/types.ts +16 -0
- package/src/pathplan/visibility.ts +170 -0
- package/src/pathplan/vispath.ts +118 -0
- package/src/rbtree/index.ts +418 -0
- package/src/render/color-resolve.ts +171 -0
- package/src/render/dot.ts +1514 -0
- package/src/render/index.ts +29 -0
- package/src/render/json.ts +734 -0
- package/src/render/map-test-helpers.ts +61 -0
- package/src/render/map.ts +528 -0
- package/src/render/public.ts +131 -0
- package/src/render/svg-arrow-ops.ts +113 -0
- package/src/render/svg-cluster.ts +24 -0
- package/src/render/svg-edge-ortho-radius.ts +228 -0
- package/src/render/svg-edge-split.ts +180 -0
- package/src/render/svg-gradient.ts +185 -0
- package/src/render/svg-graph.ts +415 -0
- package/src/render/svg-helpers.ts +623 -0
- package/src/render/svg-id.ts +76 -0
- package/src/render/svg-multicolor.ts +313 -0
- package/src/render/svg-parallel-edge.ts +123 -0
- package/src/render/svg-tapered-edge.ts +56 -0
- package/src/render/svg.ts +307 -0
- package/src/render/xdot-public.ts +164 -0
- package/src/render/xml-escape.ts +56 -0
- package/src/util/agxbuf.ts +138 -0
- package/src/util/bsd-qsort.ts +251 -0
- package/src/util/index.ts +25 -0
- package/src/util/list.ts +122 -0
- package/src/util/math.ts +104 -0
- package/src/util/mt19937.ts +143 -0
- package/src/util/printf-round.ts +163 -0
- package/src/util/xml.ts +184 -0
- package/src/vpsc/Block.ts +375 -0
- package/src/vpsc/Blocks.ts +164 -0
- package/src/vpsc/Constraint.ts +94 -0
- package/src/vpsc/HeapUtils.ts +88 -0
- package/src/vpsc/Solver.ts +274 -0
- package/src/vpsc/SweepLine.ts +214 -0
- package/src/vpsc/Variable.ts +54 -0
- package/src/vpsc/index.ts +122 -0
- package/src/vpsc/sweepTypes.ts +31 -0
- package/src/xdot/index.ts +7 -0
- package/src/xdot/lex.ts +151 -0
- package/src/xdot/misc.ts +226 -0
- package/src/xdot/parse.ts +349 -0
- package/src/xdot/types.ts +158 -0
|
@@ -0,0 +1,1514 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* DOT and XDOT renderer plugins.
|
|
5
|
+
*
|
|
6
|
+
* Ports gvrender_core_dot.c — FORMAT_DOT and FORMAT_XDOT branches.
|
|
7
|
+
* FORMAT_CANON, FORMAT_XDOT12, FORMAT_XDOT14 are out of scope (AD-12).
|
|
8
|
+
*
|
|
9
|
+
* @see plugin/core/gvrender_core_dot.c
|
|
10
|
+
* @see plugin/core/gvplugin_core.c
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { Graph } from '../model/graph.js';
|
|
14
|
+
import type { Node } from '../model/node.js';
|
|
15
|
+
import type { Edge } from '../model/edge.js';
|
|
16
|
+
import type { Point } from '../model/geom.js';
|
|
17
|
+
import type { TextSpan } from '../common/emit-types.js';
|
|
18
|
+
import type { ArrowDrawOp } from '../common/arrows-types.js';
|
|
19
|
+
import type { GVColor } from '../common/color.js';
|
|
20
|
+
import { colorxlate } from '../common/color.js';
|
|
21
|
+
import { resolveRenderColor } from './color-resolve.js';
|
|
22
|
+
import { getGradientPoints } from './svg-gradient.js';
|
|
23
|
+
import { edgeIsTapered } from './svg-tapered-edge.js';
|
|
24
|
+
import { taper, taperfun } from '../common/taper.js';
|
|
25
|
+
import { orthoRoundedRadius } from './svg-helpers.js';
|
|
26
|
+
import { orthoRoundedPolylines } from './svg-edge-ortho-radius.js';
|
|
27
|
+
import { findStopColor, parseStyleFlags } from '../common/style-resolve.js';
|
|
28
|
+
import { parseGraphPad } from '../gvc/viewport.js';
|
|
29
|
+
import { parseSegs } from '../common/multicolor.js';
|
|
30
|
+
import { splitSplineByColor } from './svg-edge-split.js';
|
|
31
|
+
import { buildOffsetLists, advanceTmpList } from '../common/edge-offset.js';
|
|
32
|
+
import { IGNORED } from '../layout/dot/rank.js';
|
|
33
|
+
import { edgeHasDrawableContent } from './svg.js';
|
|
34
|
+
import type { Bezier } from '../model/geom.js';
|
|
35
|
+
import type { RendererPlugin } from '../gvc/context.js';
|
|
36
|
+
import { PenType, FillType } from '../gvc/context.js';
|
|
37
|
+
import type { RenderJob, ObjState } from '../gvc/job.js';
|
|
38
|
+
import { EmitState, toFixed2HalfEven } from '../gvc/job.js';
|
|
39
|
+
import { renderEdgeLabels } from '../gvc/edge-labels.js';
|
|
40
|
+
import { printfSig, printfFixed } from '../util/printf-round.js';
|
|
41
|
+
import { POINTS_PER_INCH } from '../model/geom.js';
|
|
42
|
+
import type { TextlabelT, FieldT, ShapeDesc } from '../common/types.js';
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Constants
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
const XDOT_VERSION = '1.7';
|
|
49
|
+
|
|
50
|
+
/** Max magnitude cap — matches maxnegnum in C source. */
|
|
51
|
+
const MAX_NEGNUM = 999999999999999.99;
|
|
52
|
+
|
|
53
|
+
/** Near-zero suppression threshold (same as gvprintdouble). */
|
|
54
|
+
const NEAR_ZERO = 0.005;
|
|
55
|
+
|
|
56
|
+
/** Indices 8/9 alias 1; 10/11 alias 5. NUM_XBUFS covers 0–7. */
|
|
57
|
+
const NUM_XBUFS = 8;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Style tokens that never become an xdot `S` op: `filled`/`bold`/`setlinewidth`
|
|
61
|
+
* are filtered by xdot_style itself; the polygon/fill styles are consumed by the
|
|
62
|
+
* shape/fill code before gvrender_set_style, so they never reach the render
|
|
63
|
+
* style. Only line styles (solid/dashed/dotted + unknown) emit an `S` op.
|
|
64
|
+
* @see plugin/core/gvrender_core_dot.c:184 xdot_style · lib/common/shapes.c
|
|
65
|
+
*/
|
|
66
|
+
const NON_LINE_STYLES: ReadonlySet<string> = new Set([
|
|
67
|
+
'filled', 'bold', 'rounded', 'diagonals', 'striped', 'wedged',
|
|
68
|
+
'invis', 'invisible', 'radial',
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// printNum — @see lib/gvc/gvdevice.c:gvprintnum
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Strip trailing zeros (and the decimal point) from a toFixed(3) string,
|
|
77
|
+
* then collapse a leading "0." or "-0." prefix.
|
|
78
|
+
*/
|
|
79
|
+
function trimAndStrip(s: string): string {
|
|
80
|
+
const dot = s.indexOf('.');
|
|
81
|
+
if (dot < 0) return s;
|
|
82
|
+
let end = s.length;
|
|
83
|
+
while (end > dot + 1 && s[end - 1] === '0') end--;
|
|
84
|
+
if (s[end - 1] === '.') end--;
|
|
85
|
+
const t = s.slice(0, end);
|
|
86
|
+
if (t.startsWith('0.')) return t.slice(1);
|
|
87
|
+
if (t.startsWith('-0.')) return '-' + t.slice(2);
|
|
88
|
+
return t;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Convert a double to compact string form for DOT/XDOT output.
|
|
93
|
+
*
|
|
94
|
+
* Rules (porting gvprintnum from lib/gvc/gvdevice.c):
|
|
95
|
+
* - |n| > MAX_NEGNUM → clamped to ±MAX_NEGNUM
|
|
96
|
+
* - |n| < NEAR_ZERO → "0" (suppresses -0)
|
|
97
|
+
* - 3 decimal places, trailing zeros and point stripped
|
|
98
|
+
* - Leading "0." collapsed to "." (e.g. 0.5 → ".5")
|
|
99
|
+
*
|
|
100
|
+
* Exported for json.ts (T30) and map.ts (T31).
|
|
101
|
+
*
|
|
102
|
+
* @see lib/gvc/gvdevice.c:gvprintnum
|
|
103
|
+
*/
|
|
104
|
+
export function printNum(n: number): string {
|
|
105
|
+
if (Math.abs(n) > MAX_NEGNUM) {
|
|
106
|
+
return n < 0 ? String(-MAX_NEGNUM) : String(MAX_NEGNUM);
|
|
107
|
+
}
|
|
108
|
+
if (Math.abs(n) < NEAR_ZERO) return '0';
|
|
109
|
+
return trimAndStrip(n.toFixed(3));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// XDot buffer management
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Create a 12-slot xbufs array with the canonical aliasing.
|
|
118
|
+
*
|
|
119
|
+
* Indices 8–9 (NDraw, EDraw) alias index 1 (CDraw).
|
|
120
|
+
* Indices 10–11 (NLabel, ELabel) alias index 5 (CLabel).
|
|
121
|
+
*
|
|
122
|
+
* @see plugin/core/gvrender_core_dot.c:xbufs
|
|
123
|
+
*/
|
|
124
|
+
export function makeXbufs(): string[][] {
|
|
125
|
+
const bufs: string[][] = Array.from({ length: NUM_XBUFS + 4 }, () => []);
|
|
126
|
+
bufs[EmitState.NDraw] = bufs[EmitState.CDraw]!;
|
|
127
|
+
bufs[EmitState.EDraw] = bufs[EmitState.CDraw]!;
|
|
128
|
+
bufs[EmitState.NLabel] = bufs[EmitState.CLabel]!;
|
|
129
|
+
bufs[EmitState.ELabel] = bufs[EmitState.CLabel]!;
|
|
130
|
+
return bufs;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// XDOT op helpers — @see plugin/core/gvrender_core_dot.c
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Format one xdot draw-op number: 2 decimals, trailing zeros and point trimmed
|
|
139
|
+
* — mirroring xdot_fmt_num ("%.02f" + agxbuf_trim_zeros). Distinct from
|
|
140
|
+
* `printNum` (used for the DOT `pos`/`bb`/`width`/`height` attributes), which
|
|
141
|
+
* keeps more precision; xdot's DRAW ops are emitted at 2 dp by the C engine.
|
|
142
|
+
* @see plugin/core/gvrender_core_dot.c:126 xdot_fmt_num
|
|
143
|
+
*/
|
|
144
|
+
export function xdotNum(v: number): string {
|
|
145
|
+
// Round half-to-even like C's printf %.02f (FE_TONEAREST); JS toFixed rounds
|
|
146
|
+
// half-away-from-zero, which diverges at exact .xx5 ties (2323.125 → native
|
|
147
|
+
// 2323.12, not 2323.13). @see lib/gvc/gvdevice.c gvprintdouble
|
|
148
|
+
let s = toFixed2HalfEven(v);
|
|
149
|
+
if (s.indexOf('.') >= 0) {
|
|
150
|
+
let end = s.length;
|
|
151
|
+
while (end > 0 && s[end - 1] === '0') end--;
|
|
152
|
+
if (s[end - 1] === '.') end--;
|
|
153
|
+
s = s.slice(0, end);
|
|
154
|
+
}
|
|
155
|
+
return s === '-0' ? '0' : s;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Format a single xdot point "x y ". xdot is y-up: `Y_invert` defaults false, so
|
|
160
|
+
* `yDir(y, yOff)` returns `y` unchanged for xdot (only `-Ty` plain/dot invert).
|
|
161
|
+
* The layout coordinate passes through with NO inversion — unlike the SVG path.
|
|
162
|
+
* @see lib/common/output.c:36 yDir · plugin/core/gvrender_core_dot.c:132 xdot_point
|
|
163
|
+
*/
|
|
164
|
+
export function xdotPoint(p: Point): string {
|
|
165
|
+
return xdotNum(p.x) + ' ' + xdotNum(p.y) + ' ';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Format N points preceded by opcode and count: "<c> <n> x0 y0 x1 y1 …". */
|
|
169
|
+
export function xdotPoints(c: string, pts: Point[]): string {
|
|
170
|
+
let s = c + ' ' + String(pts.length) + ' ';
|
|
171
|
+
for (const p of pts) s += xdotPoint(p);
|
|
172
|
+
return s;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* UTF-8 byte length of a string — the value C's `xdot_str` writes as the length
|
|
177
|
+
* prefix (`strlen(s)` over the UTF-8 bytes), NOT the JS UTF-16 code-unit count.
|
|
178
|
+
* A label like `ÿ` (U+00FF) is 2 UTF-8 bytes, so its `T`/`F` op prefix is 2.
|
|
179
|
+
* @see plugin/core/gvrender_core_dot.c:83 xdot_str_xbuf (`%zu`, strlen)
|
|
180
|
+
*/
|
|
181
|
+
export function utf8Len(s: string): number {
|
|
182
|
+
let n = 0;
|
|
183
|
+
for (let i = 0; i < s.length; i++) {
|
|
184
|
+
const c = s.charCodeAt(i);
|
|
185
|
+
if (c < 0x80) n += 1;
|
|
186
|
+
else if (c < 0x800) n += 2;
|
|
187
|
+
else if (c >= 0xd800 && c <= 0xdbff) { n += 4; i++; } // surrogate pair → 4 bytes
|
|
188
|
+
else n += 3;
|
|
189
|
+
}
|
|
190
|
+
return n;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Clamp a normalized [0,1] float channel to a 0-255 byte (round-to-nearest). */
|
|
194
|
+
function chanByte(v: number): number {
|
|
195
|
+
return Math.round(Math.max(0, Math.min(1, v)) * 255);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Resolve a GVColor to RGBA bytes — the value the C xdot callbacks read from
|
|
200
|
+
* `job->obj->pencolor.u.rgba` (already resolved by gvrender_set_pencolor). A
|
|
201
|
+
* plain `rgba` passes through; named/hex/HSV specs run through colorxlate; a
|
|
202
|
+
* `none` color is fully transparent black (callers gate on PEN_NONE).
|
|
203
|
+
*/
|
|
204
|
+
export function gvColorRgba(c: GVColor): [number, number, number, number] {
|
|
205
|
+
if (c.type === 'rgba') return [chanByte(c.r), chanByte(c.g), chanByte(c.b), chanByte(c.a)];
|
|
206
|
+
// A `none`/transparent paint emits graphviz's "transparent" bytes (ff ff fe 00),
|
|
207
|
+
// matching native's `#fffffe00` — not fully-zero black. @see colxlate.c transparent
|
|
208
|
+
if (c.type === 'none') return [0xff, 0xff, 0xfe, 0x00];
|
|
209
|
+
const out: GVColor = { type: 'rgba', r: 0, g: 0, b: 0, a: 0 };
|
|
210
|
+
colorxlate(c.type === 'string' ? c.s : '', out, 'rgba');
|
|
211
|
+
return out.type === 'rgba'
|
|
212
|
+
? [chanByte(out.r), chanByte(out.g), chanByte(out.b), chanByte(out.a)]
|
|
213
|
+
: [0, 0, 0, 255];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Bare xdot color body (no `c `/`C ` prefix): the CONSTANT length prefix
|
|
218
|
+
* (7 for `#rrggbb`, 9 for `#rrggbbaa`) plus `-#hex`, the alpha byte present only
|
|
219
|
+
* when not fully opaque. Used both by the color ops and by gradient color stops.
|
|
220
|
+
* @see plugin/core/gvrender_core_dot.c:99 xdot_str_color_xbuf
|
|
221
|
+
*/
|
|
222
|
+
export function xdotColorBody(rgba: [number, number, number, number]): string {
|
|
223
|
+
const hx = (n: number): string => n.toString(16).padStart(2, '0');
|
|
224
|
+
const body = '#' + hx(rgba[0]) + hx(rgba[1]) + hx(rgba[2]);
|
|
225
|
+
return rgba[3] === 0xff ? '7 -' + body : '9 -' + body + hx(rgba[3]);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Format an xdot color op ("c "/"C ") from RGBA bytes.
|
|
230
|
+
* @see plugin/core/gvrender_core_dot.c:99 xdot_str_color_xbuf
|
|
231
|
+
*/
|
|
232
|
+
export function xdotColorOp(prefix: 'c ' | 'C ', rgba: [number, number, number, number]): string {
|
|
233
|
+
return prefix + xdotColorBody(rgba) + ' ';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Build the linear-gradient `C len -[x0 y0 x1 y1 2 <stops>]` fill op. Endpoints
|
|
238
|
+
* come from getGradientPoints (the same geometry the SVG gradient uses); stops
|
|
239
|
+
* are (frac,fill)/(frac,stop) when frac>0, else (0,fill)/(1,stop). Shared by
|
|
240
|
+
* node/cluster gradient fills and the graph-background gradient.
|
|
241
|
+
* @see plugin/core/gvrender_core_dot.c:544-598 xdot_gradient_fillcolor
|
|
242
|
+
*/
|
|
243
|
+
export function linearGradientOp(
|
|
244
|
+
pts: Point[],
|
|
245
|
+
fillColor: GVColor,
|
|
246
|
+
stopColor: GVColor,
|
|
247
|
+
frac: number,
|
|
248
|
+
angleDeg: number,
|
|
249
|
+
): string {
|
|
250
|
+
// isRHS=true: native y-up coords, matching C's get_gradient_points(A,G,n,angle,2)
|
|
251
|
+
// for the xdot device path (the SVG path uses isRHS=false + a container flip).
|
|
252
|
+
const { g0, g1 } = getGradientPoints(pts, (angleDeg * Math.PI) / 180, false, true);
|
|
253
|
+
const inner =
|
|
254
|
+
'[' + xdotNum(g0.x) + ' ' + xdotNum(g0.y) + ' ' + xdotNum(g1.x) + ' ' + xdotNum(g1.y) +
|
|
255
|
+
' 2 ' + gradientStops(fillColor, stopColor, frac) + ']';
|
|
256
|
+
return 'C ' + String(utf8Len(inner)) + ' -' + inner + ' ';
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** The `<frac> <colorbody>` stop pairs for a gradient (frac>0 vs the 0/1 form). */
|
|
260
|
+
function gradientStops(fillColor: GVColor, stopColor: GVColor, frac: number): string {
|
|
261
|
+
const fill = gvColorRgba(fillColor);
|
|
262
|
+
const stop = gvColorRgba(stopColor);
|
|
263
|
+
const stops: Array<[number, [number, number, number, number]]> =
|
|
264
|
+
frac > 0 ? [[frac, fill], [frac, stop]] : [[0, fill], [1, stop]];
|
|
265
|
+
return stops.map(([f, c]) => trimFixed3(f) + ' ' + xdotColorBody(c)).join(' ');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Build the radial-gradient `C len -(c1x c1y r1 c2x c2y r2 2 <stops>)` fill op.
|
|
270
|
+
* Reuses getGradientPoints (radial) for the center/radii, un-negating its SVG
|
|
271
|
+
* y. r1 = outerR/4, r2 = outerR; c2 is the center, c1 the center offset by r1
|
|
272
|
+
* along the gradient angle (== center when angle 0).
|
|
273
|
+
* @see plugin/core/gvrender_core_dot.c:562-585 xdot_gradient_fillcolor (radial)
|
|
274
|
+
*/
|
|
275
|
+
export function radialGradientOp(
|
|
276
|
+
pts: Point[],
|
|
277
|
+
fillColor: GVColor,
|
|
278
|
+
stopColor: GVColor,
|
|
279
|
+
frac: number,
|
|
280
|
+
angleDeg: number,
|
|
281
|
+
): string {
|
|
282
|
+
const rad = (angleDeg * Math.PI) / 180;
|
|
283
|
+
// isRHS=true: native y-up coords, matching C's get_gradient_points(A,G,n,0,3).
|
|
284
|
+
const gp = getGradientPoints(pts, rad, true, true);
|
|
285
|
+
const cx = gp.g0.x;
|
|
286
|
+
const cy = gp.g0.y;
|
|
287
|
+
const r1 = gp.g1.x;
|
|
288
|
+
const r2 = gp.g1.y;
|
|
289
|
+
const c1x = angleDeg === 0 ? cx : cx + r1 * Math.cos(rad);
|
|
290
|
+
const c1y = angleDeg === 0 ? cy : cy + r1 * Math.sin(rad);
|
|
291
|
+
const inner =
|
|
292
|
+
'(' + xdotNum(c1x) + ' ' + xdotNum(c1y) + ' ' + xdotNum(r1) + ' ' +
|
|
293
|
+
xdotNum(cx) + ' ' + xdotNum(cy) + ' ' + xdotNum(r2) + ' 2 ' +
|
|
294
|
+
gradientStops(fillColor, stopColor, frac) + ')';
|
|
295
|
+
return 'C ' + String(utf8Len(inner)) + ' -' + inner + ' ';
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Pen ("c ") color op from a resolved GVColor. */
|
|
299
|
+
export function xdotPenColor(c: GVColor): string {
|
|
300
|
+
return xdotColorOp('c ', gvColorRgba(c));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Fill ("C ") color op from a resolved GVColor. */
|
|
304
|
+
export function xdotFillColor(c: GVColor): string {
|
|
305
|
+
return xdotColorOp('C ', gvColorRgba(c));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Build the xdot "F size len -name " font op. Mirrors xdot_textspan's `F` +
|
|
310
|
+
* `xdot_str(job, "", font->name)` — the length prefix is the byte length of the
|
|
311
|
+
* face name. @see plugin/core/gvrender_core_dot.c:498 xdot_textspan
|
|
312
|
+
*/
|
|
313
|
+
export function xdotFont(size: number, name: string): string {
|
|
314
|
+
return 'F ' + xdotNum(size > 0 ? size : 0) + ' ' + String(utf8Len(name)) + ' -' + name + ' ';
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Build an xdot length-prefixed string op ("S "/"" prefix): "<pfx><len> -<s> ".
|
|
319
|
+
* @see plugin/core/gvrender_core_dot.c:83 xdot_str_xbuf
|
|
320
|
+
*/
|
|
321
|
+
function xdotStrOp(prefix: string, s: string): string {
|
|
322
|
+
return prefix + String(utf8Len(s)) + ' -' + s + ' ';
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Quote a DOT identifier unless it is a bare id or numeral, mirroring agwrite's
|
|
327
|
+
* agcanonStr so the serialized graph reparses (the comparator reparses both
|
|
328
|
+
* sides). Only `"` is escaped (→ `\"`); a `\` is left as-is — it is already the
|
|
329
|
+
* start of a stored escape like `\n`/`\l` that agcanonStr keeps verbatim, so
|
|
330
|
+
* doubling it (`\\n`) would change the name (`a\n(b\n"c")` must stay `a\n…`, not
|
|
331
|
+
* `a\\n…`). Over-quoting a value native leaves bare is harmless: both parse to
|
|
332
|
+
* the same name. @see lib/cgraph/write.c:_agstrcanon (escapes '"', keeps '\')
|
|
333
|
+
*/
|
|
334
|
+
export function xdotId(s: string): string {
|
|
335
|
+
if (/^[A-Za-z_][A-Za-z_0-9]*$/.test(s)) return s;
|
|
336
|
+
if (/^-?(\.[0-9]+|[0-9]+(\.[0-9]*)?)$/.test(s)) return s;
|
|
337
|
+
return '"' + s.replace(/"/g, '\\"') + '"';
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Format a number as C's `%.5g` — 5 significant figures, trailing zeros and
|
|
342
|
+
* point trimmed, switching to `e±NN` (min 2 exponent digits) when the exponent
|
|
343
|
+
* is < -4 or ≥ 5. Native writes the DOT `pos`/`bb`/`width`/`height` attributes
|
|
344
|
+
* with `%.5g` (output.c:71/294/302), so large coordinates round to 5 sig figs
|
|
345
|
+
* (`2219962` → `2.2201e+06`); `printNum`'s fixed 3 dp keeps too much precision.
|
|
346
|
+
*
|
|
347
|
+
* Uses {@link printfSig} rather than `Number.prototype.toPrecision(5)` so
|
|
348
|
+
* exact-halfway values (e.g. 1399.25) round half-to-even like C's snprintf,
|
|
349
|
+
* not half-away-from-zero like `toPrecision`. @see docs proven case
|
|
350
|
+
* graphs-b786 (circo): pos coordinate 1399.25 → native "1399.2", `toPrecision`
|
|
351
|
+
* gave "1399.3".
|
|
352
|
+
* @see lib/common/output.c:71 (agxbprint "%.5g")
|
|
353
|
+
*/
|
|
354
|
+
export function gfmt5(v: number): string {
|
|
355
|
+
if (!Number.isFinite(v)) return String(v);
|
|
356
|
+
if (v === 0) return '0';
|
|
357
|
+
let s = printfSig(v, 5);
|
|
358
|
+
const e = s.indexOf('e');
|
|
359
|
+
if (e >= 0) {
|
|
360
|
+
let mant = s.slice(0, e);
|
|
361
|
+
if (mant.indexOf('.') >= 0) mant = mant.replace(/0+$/, '').replace(/\.$/, '');
|
|
362
|
+
const ei = parseInt(s.slice(e + 1), 10);
|
|
363
|
+
return mant + 'e' + (ei < 0 ? '-' : '+') + String(Math.abs(ei)).padStart(2, '0');
|
|
364
|
+
}
|
|
365
|
+
if (s.indexOf('.') >= 0) s = s.replace(/0+$/, '').replace(/\.$/, '');
|
|
366
|
+
return s;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Format a number as C's `%.2f` — fixed 2 decimals, half-to-even on exact ties.
|
|
371
|
+
* `attach_attrs` writes the graph-label size attributes `lwidth`/`lheight` in
|
|
372
|
+
* INCHES with `%.2f`, unlike every other computed attribute (which uses `%.5g`).
|
|
373
|
+
* @see lib/common/output.c:244-247 (agxbprint "%.2f", PS2INCH)
|
|
374
|
+
*/
|
|
375
|
+
function gfmt2(v: number): string {
|
|
376
|
+
return printfFixed(v, 2);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Format a label position as the `x,y` pair every computed `*_lp` attribute
|
|
381
|
+
* uses: both coordinates at `%.5g`. C passes y through `yDir()`, which is the
|
|
382
|
+
* identity unless `Y_invert` — and `Y_invert` is only set by `-Ty` (plain/dot),
|
|
383
|
+
* never for xdot — so no inversion is applied here, exactly as the existing
|
|
384
|
+
* `pos`/`bb` emission does.
|
|
385
|
+
* @see lib/common/output.c:35 yDir · lib/common/output.c:241 (agxbprint "%.5g,%.5g")
|
|
386
|
+
*/
|
|
387
|
+
function lpStr(p: Point): string {
|
|
388
|
+
return gfmt5(p.x) + ',' + gfmt5(p.y);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* C's `attach_attrs` edge loop skips IGNORED edges and edges with no spline
|
|
393
|
+
* (`ED_spl(e) == NULL`) via `continue`, so such an edge is attached NEITHER
|
|
394
|
+
* `pos` NOR any of `lp`/`xlp`/`head_lp`/`tail_lp` — even when it carries a
|
|
395
|
+
* label. All five attributes therefore share this one gate.
|
|
396
|
+
* @see lib/common/output.c:349-353
|
|
397
|
+
*/
|
|
398
|
+
function edgeAttrsAttached(e: Edge): boolean {
|
|
399
|
+
return e.info.edge_type !== IGNORED && e.info.spl != null;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Append the leaf-field rectangles of a record node, mirroring the recursion in
|
|
404
|
+
* `set_record_rects`: a field with sub-fields contributes nothing itself, while
|
|
405
|
+
* a LEAF field (`n_flds == 0`) contributes its box translated by the node centre
|
|
406
|
+
* as `llx,lly,urx,ury` at `%.5g`. C emits each field followed by a space and
|
|
407
|
+
* then pops the trailing one — joining with a single space is equivalent.
|
|
408
|
+
* @see lib/common/output.c:215 set_record_rects
|
|
409
|
+
*/
|
|
410
|
+
function appendRecordRects(n: Node, f: FieldT, out: string[]): void {
|
|
411
|
+
const c = n.info.coord;
|
|
412
|
+
if (f.n_flds === 0) {
|
|
413
|
+
out.push(
|
|
414
|
+
gfmt5(f.b.ll.x + c.x) + ',' + gfmt5(f.b.ll.y + c.y) + ',' +
|
|
415
|
+
gfmt5(f.b.ur.x + c.x) + ',' + gfmt5(f.b.ur.y + c.y),
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
for (let i = 0; i < f.n_flds; i++) appendRecordRects(n, f.fld![i], out);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Echo an attribute that `attach_attrs_and_arrows` did NOT overwrite.
|
|
423
|
+
*
|
|
424
|
+
* Every computed attribute is `agset` behind a gate (`rects` only for a
|
|
425
|
+
* `record` shape; an edge's `lp` only for a routed edge that HAS a label;
|
|
426
|
+
* a graph's `lp` only when `GD_label(g)` exists...). When the gate FAILS, C
|
|
427
|
+
* simply does not write — so the slot keeps whatever the INPUT file parsed
|
|
428
|
+
* into it, and `agwrite` (which serializes the whole attribute table, not
|
|
429
|
+
* just the fields layout computed) prints that stale value verbatim. This is
|
|
430
|
+
* highly visible under patchwork, which forces every shape to `box` and routes
|
|
431
|
+
* no edges: on a re-fed dot output (most of the corpus) native echoes back the
|
|
432
|
+
* `rects` / `lp` a *previous* dot run wrote, in that run's coordinate space.
|
|
433
|
+
*
|
|
434
|
+
* cgraph interns attribute strings, so a value equal to the declared (empty)
|
|
435
|
+
* default is the SAME pointer as the default and write.c's
|
|
436
|
+
* `data->str[sym->id] != sym->defval` test drops it — hence an empty value is
|
|
437
|
+
* never printed.
|
|
438
|
+
* @see lib/cgraph/write.c:427 write_nondefault_attrs · lib/common/output.c:270
|
|
439
|
+
*/
|
|
440
|
+
function echoAttr(attrs: Map<string, string>, key: string): string[] {
|
|
441
|
+
const v = attrs.get(key);
|
|
442
|
+
if (v === undefined || v.length === 0) return [];
|
|
443
|
+
return [key + '="' + agcanonEscape(v) + '"'];
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** `echoAttr`'s result as a space-prefixed suffix for the string-built node block. */
|
|
447
|
+
function pfx(parts: string[]): string {
|
|
448
|
+
return parts.length === 0 ? '' : ' ' + parts.join(' ');
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* The `lp`/`lwidth`/`lheight` triple for a graph or cluster, in C's order.
|
|
453
|
+
* `rec_attach_bb` attaches them to the root graph AND recursively to every
|
|
454
|
+
* cluster, but ONLY when the graph carries a label with non-empty text
|
|
455
|
+
* (`GD_label(g) && GD_label(g)->text[0]`) — an absent or empty-text label emits
|
|
456
|
+
* none of the three. `lp` is the label centre in points (`%.5g`); `lwidth` and
|
|
457
|
+
* `lheight` are the label's `dimen` converted to inches and written `%.2f`.
|
|
458
|
+
* When the gate fails, the input's own values survive and are echoed: patchwork
|
|
459
|
+
* and osage never build a cluster label object, so a re-fed dot file's cluster
|
|
460
|
+
* `lp` comes straight back out.
|
|
461
|
+
* @see lib/common/output.c:239-248 rec_attach_bb
|
|
462
|
+
*/
|
|
463
|
+
function graphLabelAttrs(g: Graph): string[] {
|
|
464
|
+
const label = g.info.label as TextlabelT | undefined;
|
|
465
|
+
if (!label || label.text.length === 0) {
|
|
466
|
+
return [
|
|
467
|
+
...echoAttr(g.attrs, 'lp'),
|
|
468
|
+
...echoAttr(g.attrs, 'lwidth'),
|
|
469
|
+
...echoAttr(g.attrs, 'lheight'),
|
|
470
|
+
];
|
|
471
|
+
}
|
|
472
|
+
return [
|
|
473
|
+
'lp="' + lpStr(label.pos) + '"',
|
|
474
|
+
'lwidth=' + gfmt2(label.dimen.x / POINTS_PER_INCH),
|
|
475
|
+
'lheight=' + gfmt2(label.dimen.y / POINTS_PER_INCH),
|
|
476
|
+
];
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ---------------------------------------------------------------------------
|
|
480
|
+
// DOT attribute helpers
|
|
481
|
+
// ---------------------------------------------------------------------------
|
|
482
|
+
|
|
483
|
+
/** Format node attributes for DOT output. */
|
|
484
|
+
export function formatNodeAttrs(n: Node): string {
|
|
485
|
+
const info = n.info;
|
|
486
|
+
const x = printNum(info.coord.x);
|
|
487
|
+
const y = printNum(info.coord.y);
|
|
488
|
+
const w = printNum(info.width);
|
|
489
|
+
const h = printNum(info.height);
|
|
490
|
+
const parts: string[] = [`pos="${x},${y}"`, `width=${w}`, `height=${h}`];
|
|
491
|
+
n.attrs.forEach((v, k) => {
|
|
492
|
+
if (k !== 'pos' && k !== 'width' && k !== 'height') {
|
|
493
|
+
parts.push(`${k}="${v}"`);
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
return parts.join(' ');
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Format edge spline points for the DOT `pos` attribute. Per bezier: the start
|
|
501
|
+
* endpoint `s,sp` when `sflag` set, then the end endpoint `e,ep` when `eflag`
|
|
502
|
+
* set, then `bez.size` control points — all at `%.5g`, exactly as native's
|
|
503
|
+
* spline serialization. @see lib/common/output.c:357-372
|
|
504
|
+
*/
|
|
505
|
+
export function formatEdgePos(e: Edge): string {
|
|
506
|
+
const spl = e.info.spl;
|
|
507
|
+
if (!spl || spl.list.length === 0) return '';
|
|
508
|
+
// Native's pos loop skips IGNORED edges (output.c:350) — concentrate merges
|
|
509
|
+
// an edge into its opposite and marks the absorbed one IGNORED; it is still
|
|
510
|
+
// drawn (has _draw_) but carries no `pos`. @see lib/common/output.c:349-353
|
|
511
|
+
if (e.info.edge_type === IGNORED) return '';
|
|
512
|
+
const parts: string[] = [];
|
|
513
|
+
for (const bez of spl.list) {
|
|
514
|
+
if (bez.sflag) parts.push('s,' + gfmt5(bez.sp.x) + ',' + gfmt5(bez.sp.y));
|
|
515
|
+
if (bez.eflag) parts.push('e,' + gfmt5(bez.ep.x) + ',' + gfmt5(bez.ep.y));
|
|
516
|
+
const pts = bez.list.slice(0, bez.size);
|
|
517
|
+
for (const p of pts) parts.push(gfmt5(p.x) + ',' + gfmt5(p.y));
|
|
518
|
+
}
|
|
519
|
+
return 'pos="' + parts.join(' ') + '"';
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/** Return the edge connector token. */
|
|
523
|
+
export function edgeConnector(directed: boolean): string {
|
|
524
|
+
return directed ? '->' : '--';
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/** Return true if the graph is directed or strict-directed. */
|
|
528
|
+
export function isDirected(g: Graph): boolean {
|
|
529
|
+
return g.kind === 'directed' || g.kind === 'strict-directed';
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// ---------------------------------------------------------------------------
|
|
533
|
+
// Shared graph header helper
|
|
534
|
+
// ---------------------------------------------------------------------------
|
|
535
|
+
|
|
536
|
+
/** Emit the opening digraph/graph line and bb attribute. */
|
|
537
|
+
export function dotBeginGraphHeader(g: Graph, job: RenderJob): void {
|
|
538
|
+
const kw = isDirected(g) ? 'digraph' : 'graph';
|
|
539
|
+
job.write(kw + ' ' + g.name + ' {\n');
|
|
540
|
+
const bb = g.info.bb;
|
|
541
|
+
const bbStr = printNum(bb.ll.x) + ',' + printNum(bb.ll.y) + ','
|
|
542
|
+
+ printNum(bb.ur.x) + ',' + printNum(bb.ur.y);
|
|
543
|
+
job.write('\tgraph [bb="' + bbStr + '"];\n');
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// ---------------------------------------------------------------------------
|
|
547
|
+
// DotRenderer
|
|
548
|
+
// ---------------------------------------------------------------------------
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* DOT format renderer — writes graphviz DOT language with layout attributes.
|
|
552
|
+
*
|
|
553
|
+
* @see plugin/core/gvrender_core_dot.c:dot_engine
|
|
554
|
+
*/
|
|
555
|
+
export class DotRenderer implements RendererPlugin {
|
|
556
|
+
readonly type = 'dot';
|
|
557
|
+
/** @see plugin/core/gvplugin_core.c registration table */
|
|
558
|
+
readonly quality = 0;
|
|
559
|
+
|
|
560
|
+
beginGraph(g: Graph, job: RenderJob): void {
|
|
561
|
+
dotBeginGraphHeader(g, job);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
endGraph(_g: Graph, job: RenderJob): void {
|
|
565
|
+
job.write('}\n');
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
beginNode(_n: Node, _job: RenderJob): void { /* no-op */ }
|
|
569
|
+
|
|
570
|
+
endNode(n: Node, job: RenderJob): void {
|
|
571
|
+
const attrs = formatNodeAttrs(n);
|
|
572
|
+
job.write('\t' + n.name + ' [' + attrs + '];\n');
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
beginEdge(_e: Edge, _job: RenderJob): void { /* no-op */ }
|
|
576
|
+
|
|
577
|
+
endEdge(e: Edge, job: RenderJob): void {
|
|
578
|
+
const directed = isDirected(e.tail.root);
|
|
579
|
+
const conn = edgeConnector(directed);
|
|
580
|
+
const posAttr = formatEdgePos(e);
|
|
581
|
+
const attrList = posAttr ? ' [' + posAttr + ']' : '';
|
|
582
|
+
job.write('\t' + e.tail.name + ' ' + conn + ' ' + e.head.name + attrList + ';\n');
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
textspan(_pos: Point, _span: TextSpan, _job: RenderJob): void { /* no-op */ }
|
|
586
|
+
ellipse(_c: Point, _rx: number, _ry: number, _f: boolean, _j: RenderJob): void { /* no-op */ }
|
|
587
|
+
polygon(_pts: Point[], _filled: boolean, _job: RenderJob): void { /* no-op */ }
|
|
588
|
+
bezier(_pts: Point[], _filled: boolean, _job: RenderJob): void { /* no-op */ }
|
|
589
|
+
polyline(_pts: Point[], _job: RenderJob): void { /* no-op */ }
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// ---------------------------------------------------------------------------
|
|
593
|
+
// XdotRenderer
|
|
594
|
+
// ---------------------------------------------------------------------------
|
|
595
|
+
|
|
596
|
+
/** Accumulated xdot draw strings for one model object (agset side-table). */
|
|
597
|
+
export interface XdotDraws {
|
|
598
|
+
draw?: string;
|
|
599
|
+
ldraw?: string;
|
|
600
|
+
hdraw?: string;
|
|
601
|
+
tdraw?: string;
|
|
602
|
+
hldraw?: string;
|
|
603
|
+
tldraw?: string;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/** Mutable state threaded through the recursive agwrite serializer. */
|
|
607
|
+
interface SerCtx {
|
|
608
|
+
out: string[];
|
|
609
|
+
/** subgraph → preorder number (write.c:subgdfs). */
|
|
610
|
+
preorder: Map<Graph, number>;
|
|
611
|
+
/** node → preorder of the subgraph it was last written in (node_last_written). */
|
|
612
|
+
nodeLW: Map<Node, number>;
|
|
613
|
+
/** edge → preorder of the subgraph it was last written in (edge_last_written). */
|
|
614
|
+
edgeLW: Map<Edge, number>;
|
|
615
|
+
/** objects whose attributes have already been emitted (AGATTRWF/attrs_written). */
|
|
616
|
+
attrsWritten: Set<Node | Edge>;
|
|
617
|
+
/** current indentation depth. */
|
|
618
|
+
level: number;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* Escape backslashes in a LABEL draw string — C's put_escaping_backslashes,
|
|
623
|
+
* applied to the `_ldraw_`/`_hldraw_`/`_tldraw_` buffers (not the shape draws)
|
|
624
|
+
* before agset. A literal `\` in a label's text (e.g. `WXYZ\nabc`) becomes `\\`
|
|
625
|
+
* so it survives the DOT string round-trip; the T-op byte-length prefix stays on
|
|
626
|
+
* the UNescaped text. @see plugin/core/gvrender_core_dot.c:218 put_escaping_backslashes
|
|
627
|
+
*/
|
|
628
|
+
function escBackslash(s: string): string {
|
|
629
|
+
return s.replace(/\\/g, '\\\\');
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/** True if s[i..] starts an escape sequence agcanonStr keeps verbatim: a `\`
|
|
633
|
+
* followed by one of E G H L N T l n r \ ". @see lib/cgraph/write.c:is_escape */
|
|
634
|
+
function isEscapeSeq(s: string, i: number): boolean {
|
|
635
|
+
if (s[i] !== '\\') return false;
|
|
636
|
+
const n = s[i + 1];
|
|
637
|
+
return n === 'E' || n === 'G' || n === 'H' || n === 'L' || n === 'N' || n === 'T'
|
|
638
|
+
|| n === 'l' || n === 'n' || n === 'r' || n === '\\' || n === '"';
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Escape a value for a DOT attribute exactly as agwrite's agcanonStr does: a `"`
|
|
643
|
+
* becomes `\"` ONLY when it is not already part of an escape sequence, so an
|
|
644
|
+
* existing `\"` or `\\` in the value is passed through verbatim rather than
|
|
645
|
+
* double-escaped. For an already-backslash-doubled value (node/edge/graph labels,
|
|
646
|
+
* post put_escaping_backslashes) this is identical to a naive `"`→`\"` — every
|
|
647
|
+
* `"` follows a doubled `\\`, so none are ever part_of_escape. For raw cluster
|
|
648
|
+
* labels (agset, no put_escaping) it preserves the source `\"`/`\\` unchanged.
|
|
649
|
+
* @see lib/cgraph/write.c:_agstrcanon (135-167)
|
|
650
|
+
*/
|
|
651
|
+
function agcanonEscape(s: string): string {
|
|
652
|
+
let out = '';
|
|
653
|
+
let partOfEscape = false;
|
|
654
|
+
for (let i = 0; i < s.length; i++) {
|
|
655
|
+
const c = s[i]!;
|
|
656
|
+
if (c === '"' && !partOfEscape) {
|
|
657
|
+
out += '\\';
|
|
658
|
+
} else if (!partOfEscape && isEscapeSeq(s, i)) {
|
|
659
|
+
partOfEscape = true;
|
|
660
|
+
} else {
|
|
661
|
+
partOfEscape = false;
|
|
662
|
+
}
|
|
663
|
+
out += c;
|
|
664
|
+
}
|
|
665
|
+
return out;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/** Trim a "%.3f" fixed string like C's agxbuf_trim_zeros (trailing 0s + dot). */
|
|
669
|
+
function trimFixed3(v: number): string {
|
|
670
|
+
let s = v.toFixed(3);
|
|
671
|
+
if (s.indexOf('.') < 0) return s;
|
|
672
|
+
let end = s.length;
|
|
673
|
+
while (end > 0 && s[end - 1] === '0') end--;
|
|
674
|
+
if (s[end - 1] === '.') end--;
|
|
675
|
+
return s.slice(0, end);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* XDOT format renderer — mirrors plugin/core/gvrender_core_dot.c's xdot engine.
|
|
680
|
+
*
|
|
681
|
+
* Draw ops accumulate in per-emit-state xbufs during the shared emit pass; at
|
|
682
|
+
* the end of each object they are attached as `_draw_`/`_ldraw_`/`_hdraw_`/
|
|
683
|
+
* `_tdraw_` strings (a side-table here, `agset` in C), and the whole graph is
|
|
684
|
+
* serialized at `endGraph` — the model-attribute + agwrite-at-end model that
|
|
685
|
+
* lets the graph-level `_draw_` (canvas background, cluster boxes) precede `bb`
|
|
686
|
+
* even though it is only known after the body has been drawn.
|
|
687
|
+
*
|
|
688
|
+
* xdot is y-up (no coordinate inversion); colors come from the resolved
|
|
689
|
+
* graphics state (`job.obj.penColor`/`fillColor`), not hardcoded black.
|
|
690
|
+
*
|
|
691
|
+
* @see plugin/core/gvrender_core_dot.c xdot_begin_graph / xdot_end_graph
|
|
692
|
+
*/
|
|
693
|
+
export class XdotRenderer implements RendererPlugin {
|
|
694
|
+
readonly type = 'xdot';
|
|
695
|
+
/** @see plugin/core/gvplugin_core.c registration table */
|
|
696
|
+
readonly quality = 0;
|
|
697
|
+
|
|
698
|
+
/** Per-render draw buffers. Indices 8/9 alias 1; 10/11 alias 5. */
|
|
699
|
+
private bufs: string[][] = makeXbufs();
|
|
700
|
+
/** setlinewidth state per emit_state. @see gvrender_core_dot.c penwidth[] */
|
|
701
|
+
private penwidth: number[] = new Array(12).fill(1);
|
|
702
|
+
/** text-flag state per emit_state. @see gvrender_core_dot.c textflags[] */
|
|
703
|
+
private textflags: number[] = new Array(12).fill(0);
|
|
704
|
+
/** Accumulated draw strings keyed by model object (C: agset on the object). */
|
|
705
|
+
private draws = new Map<Node | Edge | Graph, XdotDraws>();
|
|
706
|
+
/** Clusters in render order — the graphs `endCluster` was actually called for
|
|
707
|
+
* (from `renderClusters`/`GD_clust`), which is NOT always reachable via
|
|
708
|
+
* `g.subgraphs`, so it is tracked here rather than re-walked. */
|
|
709
|
+
private clusters: Graph[] = [];
|
|
710
|
+
/** edge_in_box gate for the current edge: emit_edge draws nothing (spline OR
|
|
711
|
+
* label) for an edge whose content is outside job->clip. Mirrors svg.ts's
|
|
712
|
+
* edgeGroupOpen. @see lib/common/emit.c:emit_edge (3039) */
|
|
713
|
+
private edgeDrawable = true;
|
|
714
|
+
|
|
715
|
+
beginGraph(g: Graph, _job: RenderJob): void {
|
|
716
|
+
this.bufs = makeXbufs();
|
|
717
|
+
this.penwidth = new Array(12).fill(1);
|
|
718
|
+
this.textflags = new Array(12).fill(0);
|
|
719
|
+
this.draws = new Map();
|
|
720
|
+
this.clusters = [];
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/** The per-object draw strings accumulated during the last render, keyed by
|
|
724
|
+
* the ORIGINAL model object. These are the pre-serialization values C's
|
|
725
|
+
* `-Tjson` reads directly (via agxget) — feeding them to parseXDot avoids the
|
|
726
|
+
* DOT-text round-trip, which cannot represent draw text containing `"` and so
|
|
727
|
+
* drops a preceding backslash (a cluster label's `\"` → `"`, id 2239). */
|
|
728
|
+
drawStringsByObject(): ReadonlyMap<Node | Edge | Graph, XdotDraws> {
|
|
729
|
+
return this.draws;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
endGraph(g: Graph, job: RenderJob): void {
|
|
733
|
+
// Flush the graph-level GDRAW/GLABEL buffers (canvas background + graph
|
|
734
|
+
// label) onto the root graph, then serialize the whole graph.
|
|
735
|
+
// @see gvrender_core_dot.c:427 xdot_end_graph
|
|
736
|
+
const gd = this.flush(EmitState.GDraw);
|
|
737
|
+
const gl = this.flush(EmitState.GLabel);
|
|
738
|
+
if (gd || gl) {
|
|
739
|
+
const set = this.drawsFor(g);
|
|
740
|
+
if (gd) set.draw = gd;
|
|
741
|
+
if (gl) set.ldraw = escBackslash(gl);
|
|
742
|
+
}
|
|
743
|
+
job.write(this.serialize(g));
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
/** Emit the canvas background polygon into the GDRAW buffer.
|
|
747
|
+
* @see lib/common/emit.c:1476 emit_background */
|
|
748
|
+
pageBackground(g: Graph, job: RenderJob): void {
|
|
749
|
+
const bg = g.attrs.get('bgcolor');
|
|
750
|
+
let fillSpec = bg !== undefined && bg !== '' ? bg : 'white';
|
|
751
|
+
// The xdot device is not GVDEVICE_DOES_TRUECOLOR, so emit_background maps an
|
|
752
|
+
// explicit `bgcolor=transparent` to white (a filled white canvas), unlike a
|
|
753
|
+
// truecolor device that paints nothing. @see lib/common/emit.c:1490
|
|
754
|
+
if (fillSpec === 'transparent') fillSpec = 'white';
|
|
755
|
+
// The canvas fill covers job->clip = gvc->bb ± job->pad. The dot/xdot device
|
|
756
|
+
// default pad is 0 (SVG's is 4), so only an explicit `pad` attr expands the
|
|
757
|
+
// background box (e.g. pad=2.0 → ±144). @see gvrender_core_dot.c:739
|
|
758
|
+
// (render_features_dot default_pad 0) · emit.c:3367 (job->bb = bb ± pad)
|
|
759
|
+
const padAttr = g.attrs.get('pad');
|
|
760
|
+
const pad = padAttr !== undefined && padAttr !== '' ? parseGraphPad(padAttr) : { x: 0, y: 0 };
|
|
761
|
+
const bb = job.bb;
|
|
762
|
+
const corners: Point[] = [
|
|
763
|
+
{ x: bb.ll.x - pad.x, y: bb.ll.y - pad.y },
|
|
764
|
+
{ x: bb.ll.x - pad.x, y: bb.ur.y + pad.y },
|
|
765
|
+
{ x: bb.ur.x + pad.x, y: bb.ur.y + pad.y },
|
|
766
|
+
{ x: bb.ur.x + pad.x, y: bb.ll.y - pad.y },
|
|
767
|
+
];
|
|
768
|
+
const buf = this.bufs[EmitState.GDraw]!;
|
|
769
|
+
buf.push(xdotPenColor(resolveRenderColor('transparent')));
|
|
770
|
+
// A two-color bgcolor is a gradient (emit_background → findStopColor); a
|
|
771
|
+
// linear one emits the bracketed fill, a radial one is deferred to the base.
|
|
772
|
+
const stop = fillSpec.includes(':') ? findStopColor(fillSpec) : null;
|
|
773
|
+
if (stop !== null) {
|
|
774
|
+
const angle = Number(g.attrs.get('gradientangle') ?? 0) || 0;
|
|
775
|
+
const radial = parseStyleFlags(g.attrs.get('style')).radial;
|
|
776
|
+
const op = radial ? radialGradientOp : linearGradientOp;
|
|
777
|
+
buf.push(
|
|
778
|
+
op(corners, resolveRenderColor(stop.fillColor), resolveRenderColor(stop.stopColor), stop.frac, angle),
|
|
779
|
+
);
|
|
780
|
+
} else {
|
|
781
|
+
buf.push(xdotFillColor(resolveRenderColor(fillSpec)));
|
|
782
|
+
}
|
|
783
|
+
buf.push(xdotPoints('P', corners));
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
beginNode(_n: Node, _job: RenderJob): void { /* no-op */ }
|
|
787
|
+
|
|
788
|
+
endNode(n: Node, _job: RenderJob): void {
|
|
789
|
+
const draw = this.flush(EmitState.NDraw);
|
|
790
|
+
const ldraw = this.flush(EmitState.NLabel);
|
|
791
|
+
if (draw || ldraw) {
|
|
792
|
+
const set = this.drawsFor(n);
|
|
793
|
+
if (draw) set.draw = draw;
|
|
794
|
+
if (ldraw) set.ldraw = escBackslash(ldraw);
|
|
795
|
+
}
|
|
796
|
+
this.resetState(EmitState.NDraw, EmitState.NLabel);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Emit the edge spline beziers (EDRAW) and arrowhead ops (TDRAW/HDRAW),
|
|
801
|
+
* reading the already-routed geometry from `e.info`. Mirrors
|
|
802
|
+
* emit_edge_graphics: each bezier under the edge pen, then tail/head arrows
|
|
803
|
+
* under the default solid line style. The port draws SVG edges directly in
|
|
804
|
+
* svg.ts (not via shared bezier/polygon callbacks), so the xdot edge draw is
|
|
805
|
+
* self-contained here — the same per-renderer split the port already uses.
|
|
806
|
+
* @see lib/common/emit.c:emit_edge_graphics
|
|
807
|
+
*/
|
|
808
|
+
beginEdge(e: Edge, job: RenderJob): void {
|
|
809
|
+
// emit_edge gates ALL edge drawing (spline + labels) on edge_in_box: an edge
|
|
810
|
+
// whose only content is a label placed outside job->clip draws nothing. The
|
|
811
|
+
// SVG renderer applies the same gate via edgeGroupOpen; the xdot renderer
|
|
812
|
+
// must too, else concentrate-merged edges (no spline, stale off-box label
|
|
813
|
+
// position) emit a spurious _ldraw_. @see lib/common/emit.c:emit_edge (3039)
|
|
814
|
+
this.edgeDrawable = edgeHasDrawableContent(e, job.bb);
|
|
815
|
+
const spl = e.info.spl;
|
|
816
|
+
if (!this.edgeDrawable || spl === undefined) return;
|
|
817
|
+
const edraw = this.bufs[EmitState.EDraw]!;
|
|
818
|
+
const colorAttr = e.attrs.get('color') ?? '';
|
|
819
|
+
const numc = (colorAttr.match(/:/g) ?? []).length;
|
|
820
|
+
const numsemi = (colorAttr.match(/;/g) ?? []).length;
|
|
821
|
+
// Tapered edge (style=tapered) → the first bezier as a filled taper polygon
|
|
822
|
+
// with transparent pen + edge-color fill; else plain `:` multicolor → N
|
|
823
|
+
// parallel offset beziers; else the single-color spline. @see emit.c:2422/2443
|
|
824
|
+
// Split-multicolor arrow colors: tail = first color, head = end color
|
|
825
|
+
// (inverse of the parallel branch). Undefined for all other edge kinds.
|
|
826
|
+
// @see lib/common/emit.c:2400 (multicolor arrow rule)
|
|
827
|
+
let tailArrowColor: string | undefined;
|
|
828
|
+
let headArrowColor: string | undefined;
|
|
829
|
+
if (numsemi > 0 && numc > 0) {
|
|
830
|
+
// Split-along-length `;` multicolor (e.g. `red;0.5:blue`): one bezier per
|
|
831
|
+
// color segment, split along the spline's arc length. Takes precedence
|
|
832
|
+
// over tapered / `:` parallel, matching C's `if (numsemi && numc)` order.
|
|
833
|
+
// @see lib/common/emit.c:2389 multicolor
|
|
834
|
+
const c = this.emitSplitSpline(spl.list as (Bezier | undefined)[], colorAttr, edraw, job);
|
|
835
|
+
tailArrowColor = c.firstColor;
|
|
836
|
+
headArrowColor = c.endColor;
|
|
837
|
+
} else if (edgeIsTapered(e)) {
|
|
838
|
+
this.emitTaperedSpline(e, spl.list[0], edraw, job);
|
|
839
|
+
} else if (numc > 0) {
|
|
840
|
+
this.emitParallelSpline(spl.list as (Bezier | undefined)[], colorAttr, numc, edraw, job);
|
|
841
|
+
} else {
|
|
842
|
+
// splines=ortho + radius/style=rounded → straight segments + corner arcs
|
|
843
|
+
// as polylines (L), else the single bezier. @see emit.c:2583 / svg-helpers
|
|
844
|
+
const radius = orthoRoundedRadius(e, job);
|
|
845
|
+
const obj = job.obj;
|
|
846
|
+
const origStyle = obj !== null ? [...obj.rawStyle] : [];
|
|
847
|
+
const multi = spl.list.length > 1;
|
|
848
|
+
for (const bez of spl.list) {
|
|
849
|
+
const pts = bez.list.slice(0, bez.size);
|
|
850
|
+
const polys = radius !== null && bez.size >= 4 ? orthoRoundedPolylines(pts, radius) : [];
|
|
851
|
+
if (polys.length > 0) {
|
|
852
|
+
for (const poly of polys) edraw.push(this.styleOp(job), this.penOp(job), xdotPoints('L', poly));
|
|
853
|
+
} else {
|
|
854
|
+
edraw.push(this.styleOp(job), this.penOp(job), xdotPoints('B', pts));
|
|
855
|
+
}
|
|
856
|
+
// arrow_gen (drawn to TDRAW/HDRAW below) resets the job style to
|
|
857
|
+
// defaultlinestyle ("solid") as a side effect. For a multi-bezier spline
|
|
858
|
+
// the NEXT segment's xdot_style re-emits the current rawstyle every call,
|
|
859
|
+
// so a solid edge picks up a bare `S 5 -solid`; C restores the edge's own
|
|
860
|
+
// styles afterward only when it has explicit ones. @see emit.c:2668-2677
|
|
861
|
+
if (obj !== null && multi && (bez.sflag || bez.eflag)) {
|
|
862
|
+
obj.rawStyle = origStyle.length > 0 ? origStyle : ['solid'];
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
if (obj !== null) obj.rawStyle = origStyle;
|
|
866
|
+
}
|
|
867
|
+
// Arrows: y-up ops already computed for the shared render path. C sets the
|
|
868
|
+
// default line style ("solid") + penwidth before each arrow primitive.
|
|
869
|
+
this.emitArrows(this.bufs[EmitState.TDraw]!, e.info.tailArrowOps, job, EmitState.TDraw, tailArrowColor);
|
|
870
|
+
this.emitArrows(this.bufs[EmitState.HDraw]!, e.info.headArrowOps, job, EmitState.HDraw, headArrowColor);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
/**
|
|
874
|
+
* Emit a tapered edge's first bezier as a filled taper polygon: `S <n>
|
|
875
|
+
* -tapered` (the style), a transparent pen, the edge-color fill, then the
|
|
876
|
+
* polygon vertices from `taper()` (y-up). @see lib/common/emit.c:2422
|
|
877
|
+
*/
|
|
878
|
+
private emitTaperedSpline(e: Edge, bz: Bezier | undefined, edraw: string[], job: RenderJob): void {
|
|
879
|
+
if (bz === undefined) return;
|
|
880
|
+
const radfunc = taperfun(e.attrs.get('dir'), isDirected(e.tail.root));
|
|
881
|
+
const verts = taper(bz, radfunc, job.obj?.penWidth ?? 1);
|
|
882
|
+
const edgeColor = job.obj?.penColor ?? { type: 'string', s: 'black' };
|
|
883
|
+
edraw.push(this.styleOp(job));
|
|
884
|
+
edraw.push(xdotPenColor(resolveRenderColor('transparent')));
|
|
885
|
+
edraw.push(xdotFillColor(edgeColor));
|
|
886
|
+
edraw.push(xdotPoints('P', verts));
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Emit a split-along-length `;` multicolor edge spline: split each routed
|
|
891
|
+
* bezier along its arc length into one sub-curve per color segment, drawn
|
|
892
|
+
* under that segment's pen. Reuses the same split geometry
|
|
893
|
+
* (splitSplineByColor) as the SVG path. @see lib/common/emit.c:1975 multicolor
|
|
894
|
+
*/
|
|
895
|
+
private emitSplitSpline(
|
|
896
|
+
bzList: (Bezier | undefined)[],
|
|
897
|
+
colorAttr: string,
|
|
898
|
+
edraw: string[],
|
|
899
|
+
job: RenderJob,
|
|
900
|
+
): { firstColor: string; endColor: string } {
|
|
901
|
+
const segs = parseSegs(colorAttr).segs;
|
|
902
|
+
const firstColor = segs[0]?.color ?? 'black';
|
|
903
|
+
let endColor = firstColor;
|
|
904
|
+
for (const bz of bzList) {
|
|
905
|
+
if (bz === undefined || bz.size < 4) continue;
|
|
906
|
+
const split = splitSplineByColor(bz.list.slice(0, bz.size), segs);
|
|
907
|
+
endColor = split.endColor;
|
|
908
|
+
for (const c of split.curves) {
|
|
909
|
+
edraw.push(this.styleOp(job), xdotPenColor(resolveRenderColor(c.color)), xdotPoints('B', c.points));
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
return { firstColor, endColor };
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
/**
|
|
916
|
+
* Emit the parallel-multicolor edge spline: one offset Bézier per color,
|
|
917
|
+
* offset SEP=2.0 perpendicular per pass — reusing the same offset geometry
|
|
918
|
+
* (buildOffsetLists/advanceTmpList) as the SVG parallel-edge path.
|
|
919
|
+
* @see lib/common/emit.c:2443 (parallel multicolor) / svg-parallel-edge.ts
|
|
920
|
+
*/
|
|
921
|
+
private emitParallelSpline(
|
|
922
|
+
bzList: (Bezier | undefined)[],
|
|
923
|
+
colorAttr: string,
|
|
924
|
+
numc: number,
|
|
925
|
+
edraw: string[],
|
|
926
|
+
job: RenderJob,
|
|
927
|
+
): void {
|
|
928
|
+
const segData = bzList.map((bz) =>
|
|
929
|
+
bz !== undefined && bz.size >= 4
|
|
930
|
+
? buildOffsetLists(bz.list, (2 + numc) / 2)
|
|
931
|
+
: { offlist: [] as Point[], tmplist: [] as Point[] },
|
|
932
|
+
);
|
|
933
|
+
const colors = parseSegs(colorAttr).segs.map((s) => s.color ?? 'black');
|
|
934
|
+
for (const color of colors) {
|
|
935
|
+
const pen = xdotPenColor(resolveRenderColor(color));
|
|
936
|
+
for (const sd of segData) {
|
|
937
|
+
if (sd.offlist.length === 0) continue;
|
|
938
|
+
advanceTmpList(sd.tmplist, sd.offlist);
|
|
939
|
+
edraw.push(this.styleOp(job), pen, xdotPoints('B', sd.tmplist));
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/** Emit one arrow's primitive ops into `buf` (pen/fill from the edge color).
|
|
945
|
+
* arrow_gen sets the default line style ("solid") and the edge penwidth
|
|
946
|
+
* before each primitive, so a non-default penwidth emits `S setlinewidth(N)`
|
|
947
|
+
* once (tracked per HDRAW/TDRAW state). @see arrows.c:arrow_gen */
|
|
948
|
+
private emitArrows(
|
|
949
|
+
buf: string[], ops: ArrowDrawOp[] | undefined, job: RenderJob, state: EmitState,
|
|
950
|
+
penOverride?: string,
|
|
951
|
+
): void {
|
|
952
|
+
if (ops === undefined) return;
|
|
953
|
+
const pen = penOverride !== undefined
|
|
954
|
+
? resolveRenderColor(penOverride)
|
|
955
|
+
: job.obj?.penColor ?? { type: 'string', s: 'black' };
|
|
956
|
+
const pw = job.obj?.penWidth ?? 1;
|
|
957
|
+
for (const op of ops) {
|
|
958
|
+
if (Math.abs(pw - this.penwidth[state]!) >= 0.0005) {
|
|
959
|
+
this.penwidth[state] = pw;
|
|
960
|
+
buf.push(xdotStrOp('S ', 'setlinewidth(' + trimFixed3(pw) + ')'));
|
|
961
|
+
}
|
|
962
|
+
buf.push(xdotStrOp('S ', 'solid'));
|
|
963
|
+
buf.push(xdotPenColor(pen));
|
|
964
|
+
switch (op.kind) {
|
|
965
|
+
case 'polygon':
|
|
966
|
+
if (op.filled) buf.push(xdotFillColor(pen));
|
|
967
|
+
buf.push(xdotPoints(op.filled ? 'P' : 'p', op.points));
|
|
968
|
+
break;
|
|
969
|
+
case 'ellipse':
|
|
970
|
+
if (op.filled) buf.push(xdotFillColor(pen));
|
|
971
|
+
buf.push(
|
|
972
|
+
(op.filled ? 'E ' : 'e ') + xdotPoint(op.center) +
|
|
973
|
+
xdotNum(op.rx) + ' ' + xdotNum(op.ry) + ' ',
|
|
974
|
+
);
|
|
975
|
+
break;
|
|
976
|
+
case 'polyline':
|
|
977
|
+
buf.push(xdotPoints('L', op.points));
|
|
978
|
+
break;
|
|
979
|
+
case 'bezier':
|
|
980
|
+
buf.push(xdotPoints('B', op.points));
|
|
981
|
+
break;
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
endEdge(e: Edge, job: RenderJob): void {
|
|
987
|
+
// edge_in_box gate (set in beginEdge): a content-less / off-box edge draws
|
|
988
|
+
// nothing, matching native's emit_edge early return. @see emit.c:emit_edge
|
|
989
|
+
if (!this.edgeDrawable) {
|
|
990
|
+
this.resetState(EmitState.EDraw, EmitState.ELabel);
|
|
991
|
+
this.resetState(EmitState.HDraw, EmitState.TDraw);
|
|
992
|
+
this.penwidth[EmitState.HLabel] = 1;
|
|
993
|
+
this.penwidth[EmitState.TLabel] = 1;
|
|
994
|
+
return;
|
|
995
|
+
}
|
|
996
|
+
// Emit the edge's labels (center/xlabel/head/tail) — the port draws these
|
|
997
|
+
// in svg.ts endEdge, not the shared path, so the xdot renderer runs them
|
|
998
|
+
// itself, mirroring emit_edge's emit_edge_label. gvrenderTextspan routes
|
|
999
|
+
// each span to the edge's ELABEL buffer → _ldraw_. @see emit.c:3010
|
|
1000
|
+
renderEdgeLabels(e, this, job);
|
|
1001
|
+
const draw = this.flush(EmitState.EDraw);
|
|
1002
|
+
const hdraw = this.flush(EmitState.HDraw);
|
|
1003
|
+
const tdraw = this.flush(EmitState.TDraw);
|
|
1004
|
+
const ldraw = this.flush(EmitState.ELabel);
|
|
1005
|
+
const hldraw = this.flush(EmitState.HLabel);
|
|
1006
|
+
const tldraw = this.flush(EmitState.TLabel);
|
|
1007
|
+
if (draw || hdraw || tdraw || ldraw || hldraw || tldraw) {
|
|
1008
|
+
const set = this.drawsFor(e);
|
|
1009
|
+
if (draw) set.draw = draw;
|
|
1010
|
+
if (hdraw) set.hdraw = hdraw;
|
|
1011
|
+
if (tdraw) set.tdraw = tdraw;
|
|
1012
|
+
if (ldraw) set.ldraw = escBackslash(ldraw);
|
|
1013
|
+
if (hldraw) set.hldraw = escBackslash(hldraw);
|
|
1014
|
+
if (tldraw) set.tldraw = escBackslash(tldraw);
|
|
1015
|
+
}
|
|
1016
|
+
this.resetState(EmitState.EDraw, EmitState.ELabel);
|
|
1017
|
+
this.resetState(EmitState.HDraw, EmitState.TDraw);
|
|
1018
|
+
this.penwidth[EmitState.HLabel] = 1;
|
|
1019
|
+
this.penwidth[EmitState.TLabel] = 1;
|
|
1020
|
+
this.textflags[EmitState.HLabel] = 0;
|
|
1021
|
+
this.textflags[EmitState.TLabel] = 0;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
beginCluster(_sg: Graph, _job: RenderJob): void { /* no-op */ }
|
|
1025
|
+
|
|
1026
|
+
endCluster(sg: Graph, _job: RenderJob): void {
|
|
1027
|
+
const draw = this.flush(EmitState.CDraw);
|
|
1028
|
+
const ldraw = this.flush(EmitState.CLabel);
|
|
1029
|
+
if (draw || ldraw) {
|
|
1030
|
+
const set = this.drawsFor(sg);
|
|
1031
|
+
if (draw) set.draw = draw;
|
|
1032
|
+
// Cluster labels are stored via plain agxset (xdot_end_cluster:286), NOT
|
|
1033
|
+
// put_escaping_backslashes like node/edge/graph labels — so NO escBackslash.
|
|
1034
|
+
// The source `\"`/`\\` in the label survive as-is through agcanonEscape on
|
|
1035
|
+
// serialize (drawAttr). @see gvrender_core_dot.c:286
|
|
1036
|
+
if (ldraw) set.ldraw = ldraw;
|
|
1037
|
+
}
|
|
1038
|
+
this.clusters.push(sg);
|
|
1039
|
+
this.resetState(EmitState.CDraw, EmitState.CLabel);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
textspan(pos: Point, span: TextSpan, job: RenderJob): void {
|
|
1043
|
+
const buf = this.getBuf(job);
|
|
1044
|
+
const st = job.obj !== null ? job.obj.emitState : EmitState.GDraw;
|
|
1045
|
+
const j = span.just === 'l' ? -1 : span.just === 'r' ? 1 : 0;
|
|
1046
|
+
const p = { x: pos.x, y: pos.y + span.yoffset_centerline };
|
|
1047
|
+
buf.push(xdotFont(span.fontSize, span.fontName ?? ''));
|
|
1048
|
+
buf.push(xdotPenColor(resolveRenderColor(span.fontColor ?? 'black')));
|
|
1049
|
+
// Text flags (xdot version >= 15): emit `t <bits>` only when they change.
|
|
1050
|
+
// @see gvrender_core_dot.c:520 xdot_textspan
|
|
1051
|
+
const bits = (span.fontFlags ?? 0) & 0x7f;
|
|
1052
|
+
if (this.textflags[st] !== bits) {
|
|
1053
|
+
buf.push('t ' + String(bits) + ' ');
|
|
1054
|
+
this.textflags[st] = bits;
|
|
1055
|
+
}
|
|
1056
|
+
buf.push(
|
|
1057
|
+
'T ' + xdotPoint(p) + String(j) + ' ' + xdotNum(span.size.x) + ' ' +
|
|
1058
|
+
String(utf8Len(span.str)) + ' -' + span.str + ' ',
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
ellipse(center: Point, rx: number, ry: number, filled: boolean, job: RenderJob): void {
|
|
1063
|
+
const buf = this.getBuf(job);
|
|
1064
|
+
buf.push(this.styleOp(job), this.penOp(job));
|
|
1065
|
+
// C passes A=[center, corner] to the gradient; corner = center + (rx,ry).
|
|
1066
|
+
if (filled) buf.push(this.fillOp(job, [center, { x: center.x + rx, y: center.y + ry }]));
|
|
1067
|
+
buf.push(filled ? 'E ' : 'e ', xdotPoint(center), xdotNum(rx) + ' ' + xdotNum(ry) + ' ');
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
polygon(pts: Point[], filled: boolean, job: RenderJob): void {
|
|
1071
|
+
const buf = this.getBuf(job);
|
|
1072
|
+
buf.push(this.styleOp(job), this.penOp(job));
|
|
1073
|
+
if (filled) buf.push(this.fillOp(job, pts));
|
|
1074
|
+
buf.push(xdotPoints(filled ? 'P' : 'p', pts));
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
bezier(pts: Point[], filled: boolean, job: RenderJob): void {
|
|
1078
|
+
const buf = this.getBuf(job);
|
|
1079
|
+
buf.push(this.styleOp(job), this.penOp(job));
|
|
1080
|
+
if (filled) buf.push(this.fillOp(job, pts));
|
|
1081
|
+
// NB 'b'/'B' are reversed vs the other ops. @see gvrender_core_dot.c:632
|
|
1082
|
+
buf.push(xdotPoints(filled ? 'b' : 'B', pts));
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
polyline(pts: Point[], job: RenderJob): void {
|
|
1086
|
+
const buf = this.getBuf(job);
|
|
1087
|
+
buf.push(this.styleOp(job), this.penOp(job), xdotPoints('L', pts));
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// --- graphics-state ops ------------------------------------------------
|
|
1091
|
+
|
|
1092
|
+
/** Pen color op from the resolved graphics state (default black). */
|
|
1093
|
+
private penOp(job: RenderJob): string {
|
|
1094
|
+
return xdotPenColor(job.obj?.penColor ?? { type: 'string', s: 'black' });
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/**
|
|
1098
|
+
* Fill op from the resolved graphics state. A linear gradient emits the
|
|
1099
|
+
* bracketed `C len -[G0 G1 2 <stops>]` form (xdot_gradient_fillcolor); a plain
|
|
1100
|
+
* fill emits `C len -#hex`. `pts` are the shape points the gradient endpoints
|
|
1101
|
+
* derive from. Radial gradients are deferred (emit the base fill).
|
|
1102
|
+
* @see plugin/core/gvrender_core_dot.c:544 xdot_gradient_fillcolor
|
|
1103
|
+
*/
|
|
1104
|
+
private fillOp(job: RenderJob, pts: Point[]): string {
|
|
1105
|
+
const obj = job.obj;
|
|
1106
|
+
if (obj && obj.fill === FillType.Linear) {
|
|
1107
|
+
return linearGradientOp(pts, obj.fillColor, obj.stopColor, obj.gradientFrac, obj.gradientAngle);
|
|
1108
|
+
}
|
|
1109
|
+
if (obj && obj.fill === FillType.Radial) {
|
|
1110
|
+
return radialGradientOp(pts, obj.fillColor, obj.stopColor, obj.gradientFrac, obj.gradientAngle);
|
|
1111
|
+
}
|
|
1112
|
+
return xdotFillColor(obj?.fillColor ?? { type: 'string', s: 'black' });
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
/** Style ops (`S`): setlinewidth on a penwidth change, plus dash/dot pen.
|
|
1116
|
+
* @see gvrender_core_dot.c:161 xdot_style */
|
|
1117
|
+
private styleOp(job: RenderJob): string {
|
|
1118
|
+
const obj = job.obj;
|
|
1119
|
+
if (obj === null) return '';
|
|
1120
|
+
let s = '';
|
|
1121
|
+
const st = obj.emitState;
|
|
1122
|
+
if (Math.abs(obj.penWidth - this.penwidth[st]!) >= 0.0005) {
|
|
1123
|
+
this.penwidth[st] = obj.penWidth;
|
|
1124
|
+
s += xdotStrOp('S ', 'setlinewidth(' + trimFixed3(obj.penWidth) + ')');
|
|
1125
|
+
}
|
|
1126
|
+
// Named styles carried in obj.rawStyle (the parsed style tokens, e.g. an
|
|
1127
|
+
// explicit `style="solid"`/`"dashed"`) emit an `S` op — but only the LINE
|
|
1128
|
+
// styles. xdot_style filters filled/bold/setlinewidth; the polygon/fill
|
|
1129
|
+
// styles (rounded/diagonals/striped/wedged/invis/radial/tapered) are
|
|
1130
|
+
// consumed by the shape/fill code before gvrender_set_style, so native
|
|
1131
|
+
// never emits them as `S` ops either. @see gvrender_core_dot.c:184
|
|
1132
|
+
if (obj.rawStyle.length > 0) {
|
|
1133
|
+
for (const p of obj.rawStyle) {
|
|
1134
|
+
if (NON_LINE_STYLES.has(p) || p.startsWith('setlinewidth')) continue;
|
|
1135
|
+
s += xdotStrOp('S ', p);
|
|
1136
|
+
}
|
|
1137
|
+
} else {
|
|
1138
|
+
// Fallback for paths that set only a PenType (no rawStyle): reconstruct
|
|
1139
|
+
// the dash/dot token C would emit from the pen.
|
|
1140
|
+
if (obj.pen === PenType.Dashed) s += xdotStrOp('S ', 'dashed');
|
|
1141
|
+
else if (obj.pen === PenType.Dotted) s += xdotStrOp('S ', 'dotted');
|
|
1142
|
+
}
|
|
1143
|
+
return s;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
// --- buffer + side-table plumbing --------------------------------------
|
|
1147
|
+
|
|
1148
|
+
/** Active xdot buffer for the current emit state. */
|
|
1149
|
+
private getBuf(job: RenderJob): string[] {
|
|
1150
|
+
const obj = job.obj;
|
|
1151
|
+
return this.bufs[obj !== null ? obj.emitState : EmitState.GDraw]!;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
/** Join and clear the buffer for `state`; returns '' when empty. */
|
|
1155
|
+
private flush(state: EmitState): string {
|
|
1156
|
+
const buf = this.bufs[state]!;
|
|
1157
|
+
const s = buf.join('');
|
|
1158
|
+
buf.length = 0;
|
|
1159
|
+
return s;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
/** Reset per-emit-state penwidth/textflags after an object (C's reset). */
|
|
1163
|
+
private resetState(draw: EmitState, label: EmitState): void {
|
|
1164
|
+
this.penwidth[draw] = 1;
|
|
1165
|
+
this.penwidth[label] = 1;
|
|
1166
|
+
this.textflags[draw] = 0;
|
|
1167
|
+
this.textflags[label] = 0;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
private drawsFor(o: Node | Edge | Graph): XdotDraws {
|
|
1171
|
+
let d = this.draws.get(o);
|
|
1172
|
+
if (d === undefined) {
|
|
1173
|
+
d = {};
|
|
1174
|
+
this.draws.set(o, d);
|
|
1175
|
+
}
|
|
1176
|
+
return d;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
// --- serialization (agwrite-at-end) ------------------------------------
|
|
1180
|
+
|
|
1181
|
+
/**
|
|
1182
|
+
* Emit `key="value"`, escaping the value the way agwrite's agcanonStr does: a
|
|
1183
|
+
* `"` becomes `\"` unless it is already part of an escape sequence (see
|
|
1184
|
+
* agcanonEscape). A draw string carries label text that may contain a bare `"`
|
|
1185
|
+
* (would close the attribute early) or a source `\"`/`\\` (must not be
|
|
1186
|
+
* double-escaped). The byte-length prefix stays on the UNescaped text (the
|
|
1187
|
+
* parser un-escapes before parseXDot re-reads it), matching native exactly.
|
|
1188
|
+
* @see lib/cgraph/write.c:_agstrcanon (135-167)
|
|
1189
|
+
*/
|
|
1190
|
+
private drawAttr(key: string, value: string): string {
|
|
1191
|
+
return key + '="' + agcanonEscape(value) + '"';
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
/** `llx,lly,urx,ury` from a graph's layout bb. */
|
|
1195
|
+
private bbStr(g: Graph): string {
|
|
1196
|
+
const bb = g.info.bb;
|
|
1197
|
+
return gfmt5(bb.ll.x) + ',' + gfmt5(bb.ll.y) + ',' +
|
|
1198
|
+
gfmt5(bb.ur.x) + ',' + gfmt5(bb.ur.y);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
/**
|
|
1202
|
+
* Serialize the whole laid-out graph to xdot DOT text — a faithful port of
|
|
1203
|
+
* cgraph's agwrite (lib/cgraph/write.c). Recurses the subgraph tree
|
|
1204
|
+
* (write_subgs/write_body), scoping each node/edge to the subgraph(s) it
|
|
1205
|
+
* belongs to via preorder numbers (write_node_test/write_edge_test), and
|
|
1206
|
+
* re-emits an object bare (no attrs) on any scope after the first
|
|
1207
|
+
* (attrs_written). This reproduces native's per-subgraph edge re-declarations
|
|
1208
|
+
* — e.g. an edge in a rank=same subgraph is drawn once, then re-declared bare.
|
|
1209
|
+
* @see lib/cgraph/write.c:agwrite
|
|
1210
|
+
*/
|
|
1211
|
+
private serialize(g: Graph): string {
|
|
1212
|
+
const ctx: SerCtx = {
|
|
1213
|
+
out: [],
|
|
1214
|
+
preorder: new Map<Graph, number>(),
|
|
1215
|
+
nodeLW: new Map<Node, number>(),
|
|
1216
|
+
edgeLW: new Map<Edge, number>(),
|
|
1217
|
+
attrsWritten: new Set<Node | Edge>(),
|
|
1218
|
+
level: 0,
|
|
1219
|
+
};
|
|
1220
|
+
this.subgdfs(g, 1, ctx.preorder);
|
|
1221
|
+
this.writeHdr(g, true, ctx);
|
|
1222
|
+
this.writeBody(g, ctx);
|
|
1223
|
+
this.writeTrl(ctx);
|
|
1224
|
+
return ctx.out.join('');
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
/** Preorder-number the subgraph tree. @see write.c:subgdfs */
|
|
1228
|
+
private subgdfs(g: Graph, ix: number, preorder: Map<Graph, number>): number {
|
|
1229
|
+
let ix0 = ix;
|
|
1230
|
+
preorder.set(g, ix0);
|
|
1231
|
+
for (const sub of g.subgraphs.values()) ix0 = this.subgdfs(sub, ix0, preorder);
|
|
1232
|
+
return ix0 + 1;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
/** A subgraph is anonymous when its name is empty or a `%N` local name. */
|
|
1236
|
+
private isAnonymous(g: Graph): boolean {
|
|
1237
|
+
return g.name.length === 0 || g.name.charCodeAt(0) === 0x25 /* % */;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
/** A graph carries a `bb` attribute when it is the root or has a layout bb
|
|
1241
|
+
* (clusters, and any subgraph output.c computed a box for). Native seeds `bb`
|
|
1242
|
+
* on every graph via safe_dcl, so the value differs (set vs empty "") between
|
|
1243
|
+
* a boxed graph and an unboxed child — the driver that makes an anon subgraph
|
|
1244
|
+
* under the root or a cluster "relevant". @see lib/common/output.c:safe_dcl */
|
|
1245
|
+
private hasBb(g: Graph): boolean {
|
|
1246
|
+
return g === g.root || this.clusters.includes(g);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
/** Anonymous subgraph with no own node/edge defaults and no graph attrs
|
|
1250
|
+
* differing from its parent → inlined into the parent. Native compares every
|
|
1251
|
+
* graph attr over the root attr dict; the load-bearing ones are `bb` (set on
|
|
1252
|
+
* the parent, empty on the child) and `rank`. @see write.c:irrelevant_subgraph */
|
|
1253
|
+
private irrelevantSubgraph(g: Graph): boolean {
|
|
1254
|
+
if (!this.isAnonymous(g)) return false;
|
|
1255
|
+
if (this.clusters.includes(g)) return false;
|
|
1256
|
+
if (g.nodeDefaults.size > 0 || g.edgeDefaults.size > 0) return false;
|
|
1257
|
+
if (g.parent) {
|
|
1258
|
+
if (this.hasBb(g) !== this.hasBb(g.parent)) return false;
|
|
1259
|
+
for (const [k, v] of g.attrs) {
|
|
1260
|
+
if (g.parent.attrs.get(k) !== v) return false;
|
|
1261
|
+
}
|
|
1262
|
+
} else if (g.attrs.size > 0) {
|
|
1263
|
+
return false;
|
|
1264
|
+
}
|
|
1265
|
+
return true;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
/** Non-draw graph attrs a subgraph emits (rank for rank=same; clusters use
|
|
1269
|
+
* clusterAttrs). Only comparator-relevant fields need be exact.
|
|
1270
|
+
*
|
|
1271
|
+
* `rec_attach_bb` walks the ROOT and then `GD_clust` recursively, so a
|
|
1272
|
+
* subgraph the layout did not box is attached NEITHER `bb` NOR the label
|
|
1273
|
+
* triple — both keep the INPUT's values, which agwrite echoes. circo and
|
|
1274
|
+
* twopi lay out no clusters at all (`GD_clust` empty — mirrored here by
|
|
1275
|
+
* `this.clusters`, filled from endCluster), so on a re-fed dot output their
|
|
1276
|
+
* `cluster0` comes back carrying the *previous* run's `bb` and `lp`.
|
|
1277
|
+
* @see lib/common/output.c:249 rec_attach_bb */
|
|
1278
|
+
private subgGraphAttrs(sg: Graph): string {
|
|
1279
|
+
if (this.clusters.includes(sg)) return this.clusterAttrs(sg);
|
|
1280
|
+
const parts: string[] = [];
|
|
1281
|
+
const rank = sg.attrs.get('rank');
|
|
1282
|
+
if (rank !== undefined) parts.push('rank=' + xdotId(rank));
|
|
1283
|
+
parts.push(...echoAttr(sg.attrs, 'bb'));
|
|
1284
|
+
parts.push(...graphLabelAttrs(sg));
|
|
1285
|
+
return parts.join(' ');
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
private indent(ctx: SerCtx): string {
|
|
1289
|
+
return '\t'.repeat(ctx.level);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
/** @see write.c:write_hdr */
|
|
1293
|
+
private writeHdr(g: Graph, top: boolean, ctx: SerCtx): void {
|
|
1294
|
+
if (top) {
|
|
1295
|
+
const strict = g.kind === 'strict-directed' || g.kind === 'strict-undirected' ? 'strict ' : '';
|
|
1296
|
+
const kw = isDirected(g) ? 'digraph' : 'graph';
|
|
1297
|
+
const nm = g.name.length > 0 && !this.isAnonymous(g) ? xdotId(g.name) + ' ' : '';
|
|
1298
|
+
ctx.out.push(strict + kw + ' ' + nm + '{\n');
|
|
1299
|
+
ctx.level++;
|
|
1300
|
+
ctx.out.push(this.indent(ctx) + 'graph [' + this.graphAttrs(g) + '];\n');
|
|
1301
|
+
ctx.out.push(this.indent(ctx) + 'node [label="\\N"];\n');
|
|
1302
|
+
} else {
|
|
1303
|
+
const nm = this.isAnonymous(g) ? '' : 'subgraph ' + xdotId(g.name) + ' ';
|
|
1304
|
+
ctx.out.push(this.indent(ctx) + nm + '{\n');
|
|
1305
|
+
ctx.level++;
|
|
1306
|
+
const ga = this.subgGraphAttrs(g);
|
|
1307
|
+
if (ga) ctx.out.push(this.indent(ctx) + 'graph [' + ga + '];\n');
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
/** @see write.c:write_trl */
|
|
1312
|
+
private writeTrl(ctx: SerCtx): void {
|
|
1313
|
+
ctx.level--;
|
|
1314
|
+
ctx.out.push(this.indent(ctx) + '}\n');
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
/** @see write.c:write_subgs */
|
|
1318
|
+
private writeSubgs(g: Graph, ctx: SerCtx): void {
|
|
1319
|
+
for (const sub of g.subgraphs.values()) {
|
|
1320
|
+
if (this.irrelevantSubgraph(sub)) {
|
|
1321
|
+
this.writeSubgs(sub, ctx);
|
|
1322
|
+
} else {
|
|
1323
|
+
this.writeHdr(sub, false, ctx);
|
|
1324
|
+
this.writeBody(sub, ctx);
|
|
1325
|
+
this.writeTrl(ctx);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
/** @see write.c:write_body — subgraphs, then this scope's nodes and edges. */
|
|
1331
|
+
private writeBody(g: Graph, ctx: SerCtx): void {
|
|
1332
|
+
this.writeSubgs(g, ctx);
|
|
1333
|
+
for (const n of g.nodes.values()) {
|
|
1334
|
+
if (this.writeNodeTest(g, n, ctx)) this.writeNode(g, n, ctx);
|
|
1335
|
+
let prev: Node = n;
|
|
1336
|
+
for (const e of n.outEdges(g)) {
|
|
1337
|
+
if (prev !== e.head && this.writeNodeTest(g, e.head, ctx)) {
|
|
1338
|
+
this.writeNode(g, e.head, ctx);
|
|
1339
|
+
prev = e.head;
|
|
1340
|
+
}
|
|
1341
|
+
if (this.writeEdgeTest(g, e, ctx)) this.writeEdge(g, e, ctx);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
/** @see write.c:write_node_test — every xdot node carries pos/size, so it is
|
|
1347
|
+
* never "default"; write it in the first scope that has not yet emitted it.
|
|
1348
|
+
* Cluster nodes (fdp compound proxies, ND_clustnode) are never declared —
|
|
1349
|
+
* C's write_plain/writenodeandport suppress them. @see lib/common/output.c:146 */
|
|
1350
|
+
private writeNodeTest(g: Graph, n: Node, ctx: SerCtx): boolean {
|
|
1351
|
+
if (n.info.clustnode) return false;
|
|
1352
|
+
return (ctx.nodeLW.get(n) ?? 0) < ctx.preorder.get(g)!;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
/** Emitted node name: a cluster node's synthetic `__i:<cluster>` id is written
|
|
1356
|
+
* as the cluster name it stands for. @see lib/common/output.c:114 */
|
|
1357
|
+
private emitNodeName(n: Node): string {
|
|
1358
|
+
if (n.info.clustnode) {
|
|
1359
|
+
const i = n.name.indexOf(':');
|
|
1360
|
+
if (i >= 0) return n.name.slice(i + 1);
|
|
1361
|
+
}
|
|
1362
|
+
return n.name;
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/** @see write.c:write_edge_test */
|
|
1366
|
+
private writeEdgeTest(g: Graph, e: Edge, ctx: SerCtx): boolean {
|
|
1367
|
+
return (ctx.edgeLW.get(e) ?? 0) < ctx.preorder.get(g)!;
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
/** @see write.c:write_node */
|
|
1371
|
+
private writeNode(g: Graph, n: Node, ctx: SerCtx): void {
|
|
1372
|
+
let s = this.indent(ctx) + xdotId(this.emitNodeName(n));
|
|
1373
|
+
if (!ctx.attrsWritten.has(n)) {
|
|
1374
|
+
s += ' [' + this.nodeAttrs(n) + ']';
|
|
1375
|
+
ctx.attrsWritten.add(n);
|
|
1376
|
+
}
|
|
1377
|
+
ctx.out.push(s + ';\n');
|
|
1378
|
+
ctx.nodeLW.set(n, ctx.preorder.get(g)!);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
/** @see write.c:write_edge — attrs only on first emission, bare thereafter. */
|
|
1382
|
+
private writeEdge(g: Graph, e: Edge, ctx: SerCtx): void {
|
|
1383
|
+
const conn = edgeConnector(isDirected(g));
|
|
1384
|
+
let s = this.indent(ctx) + xdotId(this.emitNodeName(e.tail)) + ' ' + conn + ' ' +
|
|
1385
|
+
xdotId(this.emitNodeName(e.head));
|
|
1386
|
+
if (!ctx.attrsWritten.has(e)) {
|
|
1387
|
+
const attrs = this.edgeAttrStr(e);
|
|
1388
|
+
if (attrs) s += ' [' + attrs + ']';
|
|
1389
|
+
ctx.attrsWritten.add(e);
|
|
1390
|
+
}
|
|
1391
|
+
ctx.out.push(s + ';\n');
|
|
1392
|
+
ctx.edgeLW.set(e, ctx.preorder.get(g)!);
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
/** Root-graph attribute block: `_draw_`, `_ldraw_`, `bb`, `xdotversion`. */
|
|
1396
|
+
private graphAttrs(g: Graph): string {
|
|
1397
|
+
const d = this.draws.get(g);
|
|
1398
|
+
const parts: string[] = [];
|
|
1399
|
+
if (d?.draw) parts.push(this.drawAttr('_draw_', d.draw));
|
|
1400
|
+
if (d?.ldraw) parts.push(this.drawAttr('_ldraw_', d.ldraw));
|
|
1401
|
+
parts.push('bb="' + this.bbStr(g) + '"');
|
|
1402
|
+
parts.push(...graphLabelAttrs(g));
|
|
1403
|
+
parts.push('xdotversion="' + XDOT_VERSION + '"');
|
|
1404
|
+
return parts.join(' ');
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
/** Cluster attribute block. C's xdot_end_cluster ALWAYS agsets `_draw_` when
|
|
1408
|
+
* the graph has clusters (even empty, e.g. peripheries=0), so emit it
|
|
1409
|
+
* unconditionally; `_ldraw_` only when the cluster has a label.
|
|
1410
|
+
* @see plugin/core/gvrender_core_dot.c:284 xdot_end_cluster */
|
|
1411
|
+
private clusterAttrs(sg: Graph): string {
|
|
1412
|
+
const d = this.draws.get(sg);
|
|
1413
|
+
const parts: string[] = [this.drawAttr('_draw_', d?.draw ?? '')];
|
|
1414
|
+
if (d?.ldraw) parts.push(this.drawAttr('_ldraw_', d.ldraw));
|
|
1415
|
+
if (sg.info.bb) parts.push('bb="' + this.bbStr(sg) + '"');
|
|
1416
|
+
// rec_attach_bb recurses into GD_clust, so a labelled cluster carries the
|
|
1417
|
+
// same lp/lwidth/lheight triple as the root. @see lib/common/output.c:249
|
|
1418
|
+
parts.push(...graphLabelAttrs(sg));
|
|
1419
|
+
return parts.join(' ');
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
/** Node attribute block: pos/width/height plus `_draw_`/`_ldraw_`. */
|
|
1423
|
+
private nodeAttrs(n: Node): string {
|
|
1424
|
+
const info = n.info;
|
|
1425
|
+
// attach_attrs derives the emitted size from ND_lw+ND_rw / ND_ht, NOT
|
|
1426
|
+
// ND_width/ND_height (output.c:307-308). They coincide except where an
|
|
1427
|
+
// engine leaves them divergent — patchwork's finishNode lets poly_init
|
|
1428
|
+
// clobber ND_width/height while the tile survives in lw/rw/ht.
|
|
1429
|
+
let s = 'pos="' + gfmt5(info.coord.x) + ',' + gfmt5(info.coord.y) + '"' +
|
|
1430
|
+
' width=' + gfmt5((info.lw + info.rw) / 72) + ' height=' + gfmt5(info.ht / 72);
|
|
1431
|
+
// xlp — only when the node HAS an xlabel AND the xlabel placer actually set
|
|
1432
|
+
// its position (`ND_xlabel(n)->set`). An xlabel that was never placed is
|
|
1433
|
+
// omitted entirely — so an input `xlp` survives and is echoed.
|
|
1434
|
+
// @see lib/common/output.c:309-313
|
|
1435
|
+
const xlabel = info.xlabel as TextlabelT | undefined;
|
|
1436
|
+
if (xlabel && xlabel.set) s += ' xlp="' + lpStr(xlabel.pos) + '"';
|
|
1437
|
+
else s += pfx(echoAttr(n.attrs, 'xlp'));
|
|
1438
|
+
// rects — record field boxes. C gates on the SHAPE NAME being exactly
|
|
1439
|
+
// "record" (`strcmp(ND_shape(n)->name, "record") == 0`), so `Mrecord` and
|
|
1440
|
+
// HTML-table nodes get NO rects; confirmed against the native oracle. When
|
|
1441
|
+
// the shape is not a record the input's `rects` is never overwritten and is
|
|
1442
|
+
// echoed — patchwork forces every node to `box`, so a re-fed dot output
|
|
1443
|
+
// returns its old record rects untouched. @see lib/common/output.c:314-317
|
|
1444
|
+
const shape = info.shape as ShapeDesc | undefined;
|
|
1445
|
+
if (shape?.name === 'record') {
|
|
1446
|
+
const rects: string[] = [];
|
|
1447
|
+
appendRecordRects(n, info.shape_info as FieldT, rects);
|
|
1448
|
+
s += ' rects="' + rects.join(' ') + '"';
|
|
1449
|
+
} else {
|
|
1450
|
+
s += pfx(echoAttr(n.attrs, 'rects'));
|
|
1451
|
+
}
|
|
1452
|
+
const d = this.draws.get(n);
|
|
1453
|
+
if (d?.draw) s += ' ' + this.drawAttr('_draw_', d.draw);
|
|
1454
|
+
if (d?.ldraw) s += ' ' + this.drawAttr('_ldraw_', d.ldraw);
|
|
1455
|
+
return s;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
/** The `[...]` attribute body for an edge (draw ops + spline `pos`), or ''. */
|
|
1459
|
+
private edgeAttrStr(e: Edge): string {
|
|
1460
|
+
const d = this.draws.get(e);
|
|
1461
|
+
const parts: string[] = [];
|
|
1462
|
+
if (d?.draw) parts.push(this.drawAttr('_draw_', d.draw));
|
|
1463
|
+
if (d?.ldraw) parts.push(this.drawAttr('_ldraw_', d.ldraw));
|
|
1464
|
+
if (d?.hdraw) parts.push(this.drawAttr('_hdraw_', d.hdraw));
|
|
1465
|
+
if (d?.tdraw) parts.push(this.drawAttr('_tdraw_', d.tdraw));
|
|
1466
|
+
if (d?.hldraw) parts.push(this.drawAttr('_hldraw_', d.hldraw));
|
|
1467
|
+
if (d?.tldraw) parts.push(this.drawAttr('_tldraw_', d.tldraw));
|
|
1468
|
+
const pos = formatEdgePos(e);
|
|
1469
|
+
if (pos) {
|
|
1470
|
+
parts.push(pos);
|
|
1471
|
+
} else {
|
|
1472
|
+
// C's attach_attrs only agsets `pos` when the edge HAS a spline
|
|
1473
|
+
// (output.c:348); an engine that never routes (patchwork) leaves the
|
|
1474
|
+
// INPUT's own pos attribute intact and write.c emits it verbatim.
|
|
1475
|
+
parts.push(...echoAttr(e.attrs, 'pos'));
|
|
1476
|
+
}
|
|
1477
|
+
// The label-position attributes live inside the SAME loop that writes `pos`
|
|
1478
|
+
// (output.c:377-396), so they are attached only for a routed, non-IGNORED
|
|
1479
|
+
// edge. Note the asymmetry C encodes: `lp`/`head_lp`/`tail_lp` are emitted
|
|
1480
|
+
// whenever the label EXISTS, but `xlp` additionally requires `->set` — an
|
|
1481
|
+
// unplaced xlabel is omitted. Order mirrors C: lp, xlp, head_lp, tail_lp.
|
|
1482
|
+
// Each gate is independent, and each FAILED gate leaves the input's own
|
|
1483
|
+
// value in the slot for write.c to echo: an unrouted edge (patchwork routes
|
|
1484
|
+
// none) returns the `lp` a previous dot run wrote.
|
|
1485
|
+
const attached = edgeAttrsAttached(e);
|
|
1486
|
+
const info = e.info;
|
|
1487
|
+
if (attached && info.label) parts.push('lp="' + lpStr(info.label.pos) + '"');
|
|
1488
|
+
else parts.push(...echoAttr(e.attrs, 'lp'));
|
|
1489
|
+
if (attached && info.xlabel && info.xlabel.set) {
|
|
1490
|
+
parts.push('xlp="' + lpStr(info.xlabel.pos) + '"');
|
|
1491
|
+
} else parts.push(...echoAttr(e.attrs, 'xlp'));
|
|
1492
|
+
if (attached && info.head_label) {
|
|
1493
|
+
parts.push('head_lp="' + lpStr(info.head_label.pos) + '"');
|
|
1494
|
+
} else parts.push(...echoAttr(e.attrs, 'head_lp'));
|
|
1495
|
+
if (attached && info.tail_label) {
|
|
1496
|
+
parts.push('tail_lp="' + lpStr(info.tail_label.pos) + '"');
|
|
1497
|
+
} else parts.push(...echoAttr(e.attrs, 'tail_lp'));
|
|
1498
|
+
return parts.join(' ');
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
// ---------------------------------------------------------------------------
|
|
1503
|
+
// Factory functions
|
|
1504
|
+
// ---------------------------------------------------------------------------
|
|
1505
|
+
|
|
1506
|
+
/** @see plugin/core/gvrender_core_dot.c FORMAT_DOT */
|
|
1507
|
+
export function createDotRenderer(): RendererPlugin {
|
|
1508
|
+
return new DotRenderer();
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
/** @see plugin/core/gvrender_core_dot.c FORMAT_XDOT */
|
|
1512
|
+
export function createXdotRenderer(): RendererPlugin {
|
|
1513
|
+
return new XdotRenderer();
|
|
1514
|
+
}
|