@knowvah/dot-engine 1.8.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +232 -30
- package/dist/api/builder.d.ts +3 -0
- package/dist/api/builder.d.ts.map +1 -1
- package/dist/api/edge-ops.d.ts +7 -0
- package/dist/api/edge-ops.d.ts.map +1 -1
- package/dist/api/geometry.d.ts +5 -2
- package/dist/api/geometry.d.ts.map +1 -1
- package/dist/api.js +159 -31
- package/dist/api.js.map +3 -3
- package/dist/async/collect.d.ts +49 -0
- package/dist/async/collect.d.ts.map +1 -0
- package/dist/async/fonts.d.ts +20 -0
- package/dist/async/fonts.d.ts.map +1 -0
- package/dist/async/render-async.d.ts +91 -0
- package/dist/async/render-async.d.ts.map +1 -0
- package/dist/async/render-into.d.ts +38 -0
- package/dist/async/render-into.d.ts.map +1 -0
- package/dist/async/sanitize.d.ts +28 -0
- package/dist/async/sanitize.d.ts.map +1 -0
- package/dist/common/css-font.d.ts +9 -0
- package/dist/common/css-font.d.ts.map +1 -0
- package/dist/common/htmltable-types.d.ts +3 -3
- package/dist/common/htmltable-types.d.ts.map +1 -1
- package/dist/common/make-label.d.ts.map +1 -1
- package/dist/common/poly-shapes.d.ts.map +1 -1
- package/dist/common/textmeasure-factory.d.ts +2 -0
- package/dist/common/textmeasure-factory.d.ts.map +1 -1
- package/dist/common/textmeasure.d.ts +11 -1
- package/dist/common/textmeasure.d.ts.map +1 -1
- package/dist/common/utils-inputscale.d.ts +19 -0
- package/dist/common/utils-inputscale.d.ts.map +1 -0
- package/dist/errors.d.ts +61 -5
- package/dist/errors.d.ts.map +1 -1
- package/dist/gvc/context.d.ts +36 -4
- package/dist/gvc/context.d.ts.map +1 -1
- package/dist/gvc/device.d.ts +5 -2
- package/dist/gvc/device.d.ts.map +1 -1
- package/dist/gvc/image-resolver.d.ts +16 -16
- package/dist/gvc/image-resolver.d.ts.map +1 -1
- package/dist/gvc/job.d.ts +1 -9
- package/dist/gvc/job.d.ts.map +1 -1
- package/dist/gvc/usershape.d.ts +2 -12
- package/dist/gvc/usershape.d.ts.map +1 -1
- package/dist/index.d.ts +27 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7745 -5550
- package/dist/index.js.map +4 -4
- package/dist/label/index.d.ts.map +1 -1
- package/dist/label/node.d.ts +0 -6
- package/dist/label/node.d.ts.map +1 -1
- package/dist/label/rectangle.d.ts +1 -7
- package/dist/label/rectangle.d.ts.map +1 -1
- package/dist/layout/circo/circular.d.ts +8 -5
- package/dist/layout/circo/circular.d.ts.map +1 -1
- package/dist/layout/dot/pack-components.d.ts +0 -19
- package/dist/layout/dot/pack-components.d.ts.map +1 -1
- package/dist/layout/dot/position.d.ts +7 -2
- package/dist/layout/dot/position.d.ts.map +1 -1
- package/dist/layout/fdp/derive.d.ts.map +1 -1
- package/dist/layout/fdp/index.d.ts.map +1 -1
- package/dist/layout/fdp/init.d.ts.map +1 -1
- package/dist/layout/fdp/layout.d.ts.map +1 -1
- package/dist/layout/fdp/ports.d.ts +0 -10
- package/dist/layout/fdp/ports.d.ts.map +1 -1
- package/dist/layout/fdp/xlayout.d.ts +0 -16
- package/dist/layout/fdp/xlayout.d.ts.map +1 -1
- package/dist/layout/neato/adjust-info.d.ts +117 -0
- package/dist/layout/neato/adjust-info.d.ts.map +1 -0
- package/dist/layout/neato/cdt-surface.d.ts.map +1 -1
- package/dist/layout/neato/constraint-adjust.d.ts +40 -0
- package/dist/layout/neato/constraint-adjust.d.ts.map +1 -0
- package/dist/layout/neato/edge-len.d.ts +20 -0
- package/dist/layout/neato/edge-len.d.ts.map +1 -0
- package/dist/layout/neato/fdp-adjust.d.ts +32 -4
- package/dist/layout/neato/fdp-adjust.d.ts.map +1 -1
- package/dist/layout/neato/index.d.ts +9 -6
- package/dist/layout/neato/index.d.ts.map +1 -1
- package/dist/layout/neato/init.d.ts +4 -11
- package/dist/layout/neato/init.d.ts.map +1 -1
- package/dist/layout/neato/kk-paths.d.ts +50 -0
- package/dist/layout/neato/kk-paths.d.ts.map +1 -0
- package/dist/layout/neato/kk-solve.d.ts +14 -0
- package/dist/layout/neato/kk-solve.d.ts.map +1 -0
- package/dist/layout/neato/kk.d.ts +44 -0
- package/dist/layout/neato/kk.d.ts.map +1 -0
- package/dist/layout/neato/multispline-router.d.ts.map +1 -1
- package/dist/layout/neato/poly.d.ts +50 -0
- package/dist/layout/neato/poly.d.ts.map +1 -0
- package/dist/layout/neato/sgd-dijkstra.d.ts +20 -0
- package/dist/layout/neato/sgd-dijkstra.d.ts.map +1 -0
- package/dist/layout/neato/sgd.d.ts +14 -9
- package/dist/layout/neato/sgd.d.ts.map +1 -1
- package/dist/layout/neato/start.d.ts +51 -0
- package/dist/layout/neato/start.d.ts.map +1 -0
- package/dist/layout/neato/vpsc-adjust.d.ts +21 -0
- package/dist/layout/neato/vpsc-adjust.d.ts.map +1 -0
- package/dist/layout/sfdp/index.d.ts.map +1 -1
- package/dist/layout/sfdp/init.d.ts +0 -9
- package/dist/layout/sfdp/init.d.ts.map +1 -1
- package/dist/layout/sfdp/spring-driver.d.ts.map +1 -1
- package/dist/layout/twopi/circle.d.ts +0 -7
- package/dist/layout/twopi/circle.d.ts.map +1 -1
- package/dist/ortho/ortho-parallel.d.ts.map +1 -1
- package/dist/ortho/trap-query.d.ts.map +1 -1
- package/dist/parser/index.d.ts +9 -5
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/render/index.d.ts +2 -0
- package/dist/render/index.d.ts.map +1 -1
- package/dist/render/public.d.ts +9 -3
- package/dist/render/public.d.ts.map +1 -1
- package/dist/render/xdot-public.d.ts +7 -2
- package/dist/render/xdot-public.d.ts.map +1 -1
- package/dist/render.js +7548 -5588
- package/dist/render.js.map +4 -4
- package/dist/util/xml.d.ts.map +1 -1
- package/dist/vpsc/Solver.d.ts +1 -0
- package/dist/vpsc/Solver.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/api/builder.ts +73 -5
- package/src/api/edge-ops.ts +19 -0
- package/src/api/geometry.ts +22 -5
- package/src/async/collect.ts +204 -0
- package/src/async/fonts.ts +61 -0
- package/src/async/render-async.ts +205 -0
- package/src/async/render-into.ts +115 -0
- package/src/async/sanitize.ts +150 -0
- package/src/common/css-font.ts +109 -0
- package/src/common/htmltable-types.ts +4 -5
- package/src/common/make-label.ts +10 -1
- package/src/common/poly-shapes.ts +4 -1
- package/src/common/textmeasure-factory.ts +11 -0
- package/src/common/textmeasure.ts +41 -8
- package/src/common/utils-inputscale.ts +31 -0
- package/src/errors.ts +188 -5
- package/src/gvc/context.ts +96 -17
- package/src/gvc/device.ts +11 -6
- package/src/gvc/image-resolver.ts +25 -2
- package/src/gvc/job.ts +3 -1
- package/src/gvc/usershape.ts +6 -0
- package/src/index.ts +59 -43
- package/src/label/index.ts +6 -2
- package/src/label/node.ts +2 -1
- package/src/label/rectangle.ts +4 -2
- package/src/layout/circo/circular.ts +9 -6
- package/src/layout/dot/pack-components.ts +6 -2
- package/src/layout/dot/position.ts +21 -3
- package/src/layout/fdp/derive.ts +7 -6
- package/src/layout/fdp/index.ts +45 -5
- package/src/layout/fdp/init.ts +7 -6
- package/src/layout/fdp/layout.ts +2 -1
- package/src/layout/fdp/ports.ts +3 -2
- package/src/layout/fdp/xlayout.ts +3 -1
- package/src/layout/neato/adjust-info.ts +332 -0
- package/src/layout/neato/cdt-surface.ts +50 -30
- package/src/layout/neato/constraint-adjust.ts +466 -0
- package/src/layout/neato/edge-len.ts +36 -0
- package/src/layout/neato/fdp-adjust.ts +117 -12
- package/src/layout/neato/index.ts +36 -54
- package/src/layout/neato/init.ts +21 -36
- package/src/layout/neato/kk-paths.ts +146 -0
- package/src/layout/neato/kk-solve.ts +64 -0
- package/src/layout/neato/kk.ts +322 -0
- package/src/layout/neato/multispline-router.ts +4 -3
- package/src/layout/neato/poly.ts +493 -0
- package/src/layout/neato/sgd-dijkstra.ts +125 -0
- package/src/layout/neato/sgd.ts +63 -40
- package/src/layout/neato/start.ts +222 -0
- package/src/layout/neato/vpsc-adjust.ts +93 -0
- package/src/layout/sfdp/index.ts +4 -5
- package/src/layout/sfdp/init.ts +40 -4
- package/src/layout/sfdp/spring-driver.ts +30 -1
- package/src/layout/twopi/circle.ts +2 -1
- package/src/ortho/ortho-parallel.ts +2 -1
- package/src/ortho/trap-query.ts +2 -1
- package/src/parser/index.ts +11 -11
- package/src/render/index.ts +5 -0
- package/src/render/public.ts +24 -24
- package/src/render/svg.ts +1 -1
- package/src/render/xdot-public.ts +19 -18
- package/src/util/xml.ts +24 -30
- package/src/vpsc/Solver.ts +5 -3
package/dist/util/xml.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"xml.d.ts","sourceRoot":"","sources":["../../src/util/xml.ts"],"names":[],"mappings":"AACA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"xml.d.ts","sourceRoot":"","sources":["../../src/util/xml.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AAIH,sFAAsF;AACtF,MAAM,WAAW,QAAQ;IACvB,uDAAuD;IACvD,GAAG,EAAE,OAAO,CAAC;IACb,2BAA2B;IAC3B,IAAI,EAAE,OAAO,CAAC;IACd,gDAAgD;IAChD,IAAI,EAAE,OAAO,CAAC;IACd,oCAAoC;IACpC,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,GAAG,MAAM,CAE9D"}
|
package/dist/vpsc/Solver.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Solver.d.ts","sourceRoot":"","sources":["../../src/vpsc/Solver.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"Solver.d.ts","sourceRoot":"","sources":["../../src/vpsc/Solver.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAQrC;;;;;GAKG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,IAAI,CAAS;gBAET,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM;IAOtD,OAAO,IAAI,MAAM;IACjB,OAAO,IAAI,MAAM;IACjB,OAAO,IAAI,MAAM;IACjB,OAAO,IAAI,MAAM;IACjB,UAAU,IAAI,MAAM;IACpB,UAAU,IAAI,MAAM;IACpB,KAAK,IAAI,MAAM;IACf,MAAM,IAAI,MAAM;IAEhB,sFAAsF;IACtF,QAAQ,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM;IAQ9B,sFAAsF;IACtF,QAAQ,CAAC,CAAC,EAAE,SAAS,GAAG,MAAM;CAO/B;AAQD;;;;;GAKG;AACH,qBAAa,IAAI;IACf,SAAS,CAAC,EAAE,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,EAAE,UAAU,EAAE,CAAC;IAE3B,oDAAoD;gBACxC,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE;IAK5C;;;;OAIG;IACH,OAAO,IAAI,IAAI;IASf,mDAAmD;IACnD,OAAO,CAAC,MAAM;IAoBd;;;;OAIG;IACH,OAAO,CAAC,iBAAiB;IAMzB;;;OAGG;IACH,KAAK,IAAI,IAAI;CAId;AAQD;;;;;GAKG;AACH,qBAAa,OAAQ,SAAQ,IAAI;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,QAAQ,CAAe;IAE/B,0DAA0D;gBAC9C,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE;IAO5C;;;;OAIG;IACM,OAAO,IAAI,IAAI;IAwBxB;;;OAGG;IACH,UAAU,IAAI,IAAI;IAOlB;;;OAGG;IACH,WAAW,IAAI,IAAI;IAqBnB;;;OAGG;IACM,KAAK,IAAI,IAAI;IAWtB;;;OAGG;IACH,OAAO,CAAC,YAAY;CAkBrB;AAED,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knowvah/dot-engine",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Faithful TypeScript port of Graphviz (dot, neato, fdp, sfdp, circo, twopi, osage, patchwork) — no C: no native binary, no WASM. Oracle-verified against the C implementation, runs in the browser.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"graphviz",
|
package/src/api/builder.ts
CHANGED
|
@@ -16,8 +16,8 @@ import { Graph, type GraphKind } from '../model/graph.js';
|
|
|
16
16
|
import type { Node } from '../model/node.js';
|
|
17
17
|
import type { Edge } from '../model/edge.js';
|
|
18
18
|
import { agnode, agsubg, agsubnode } from '../model/cgraph-ops.js';
|
|
19
|
-
import { addEdge as cgraphAddEdge } from './edge-ops.js';
|
|
20
|
-
import {
|
|
19
|
+
import { addEdge as cgraphAddEdge, requireObject } from './edge-ops.js';
|
|
20
|
+
import { InternalError, invalidArgType } from '../errors.js';
|
|
21
21
|
import { HTML_STRING_MARK } from '../common/html-string.js';
|
|
22
22
|
|
|
23
23
|
// ── Public interfaces ──────────────────────────────────────────────────────────
|
|
@@ -101,14 +101,17 @@ class NodeHandle implements GvNode {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
setAttr(k: string, v: string): void {
|
|
104
|
+
requireKeyValue(k, v);
|
|
104
105
|
this._node.attrs.set(k, v);
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
setHtmlAttr(k: string, v: string): void {
|
|
109
|
+
requireKeyValue(k, v);
|
|
108
110
|
this._node.attrs.set(k, HTML_STRING_MARK + v);
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
getAttr(k: string): string | undefined {
|
|
114
|
+
requireString('k', k);
|
|
112
115
|
return this._node.attrs.get(k);
|
|
113
116
|
}
|
|
114
117
|
}
|
|
@@ -130,18 +133,67 @@ class EdgeHandle implements GvEdge {
|
|
|
130
133
|
}
|
|
131
134
|
|
|
132
135
|
setAttr(k: string, v: string): void {
|
|
136
|
+
requireKeyValue(k, v);
|
|
133
137
|
this._edge.attrs.set(k, v);
|
|
134
138
|
}
|
|
135
139
|
|
|
136
140
|
setHtmlAttr(k: string, v: string): void {
|
|
141
|
+
requireKeyValue(k, v);
|
|
137
142
|
this._edge.attrs.set(k, HTML_STRING_MARK + v);
|
|
138
143
|
}
|
|
139
144
|
|
|
140
145
|
getAttr(k: string): string | undefined {
|
|
146
|
+
requireString('k', k);
|
|
141
147
|
return this._edge.attrs.get(k);
|
|
142
148
|
}
|
|
143
149
|
}
|
|
144
150
|
|
|
151
|
+
// ── Argument checks (ADR-3: typeof / null only) ───────────────────────────────
|
|
152
|
+
|
|
153
|
+
/** `name`, `k`, `v`: must be a string. */
|
|
154
|
+
function requireString(param: string, v: unknown): void {
|
|
155
|
+
if (typeof v !== 'string') throw invalidArgType(param, 'string', v);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** `attrs`: `undefined` or a plain object whose values are strings. */
|
|
159
|
+
function requireAttrs(param: string, attrs: unknown): void {
|
|
160
|
+
if (attrs === undefined) return;
|
|
161
|
+
requireObject(param, attrs);
|
|
162
|
+
if (Array.isArray(attrs)) throw invalidArgType(param, 'plain object', attrs);
|
|
163
|
+
for (const [k, v] of Object.entries(attrs as Record<string, unknown>)) {
|
|
164
|
+
if (typeof v !== 'string') {
|
|
165
|
+
throw invalidArgType(`${param}.${k}`, 'string', v);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Node ref: a string or a handle created by this library. */
|
|
171
|
+
function requireNodeRef(param: string, ref: unknown): void {
|
|
172
|
+
if (typeof ref === 'string' || ref instanceof NodeHandle) return;
|
|
173
|
+
throw invalidArgType(param, 'string or GvNode', ref);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Key/value pair of setAttr/setHtmlAttr. */
|
|
177
|
+
function requireKeyValue(k: unknown, v: unknown): void {
|
|
178
|
+
requireString('k', k);
|
|
179
|
+
requireString('v', v);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** `createGraph` options: `undefined` or an object with typed fields. */
|
|
183
|
+
function requireGraphOptions(opts: unknown): void {
|
|
184
|
+
if (opts === undefined) return;
|
|
185
|
+
requireObject('opts', opts);
|
|
186
|
+
const o = opts as Record<string, unknown>;
|
|
187
|
+
for (const key of ['directed', 'strict'] as const) {
|
|
188
|
+
if (o[key] !== undefined && typeof o[key] !== 'boolean') {
|
|
189
|
+
throw invalidArgType(`opts.${key}`, 'boolean or undefined', o[key]);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (o['name'] !== undefined && typeof o['name'] !== 'string') {
|
|
193
|
+
throw invalidArgType('opts.name', 'string or undefined', o['name']);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
145
197
|
// ── Builder implementation ─────────────────────────────────────────────────────
|
|
146
198
|
|
|
147
199
|
/** Resolve a GvNode handle or a name string to an internal Node. */
|
|
@@ -149,7 +201,9 @@ function resolveNode(g: Graph, ref: GvNode | string): Node {
|
|
|
149
201
|
if (typeof ref === 'string') {
|
|
150
202
|
const node = agnode(g, ref, true);
|
|
151
203
|
if (node === null) {
|
|
152
|
-
throw new
|
|
204
|
+
throw new InternalError(
|
|
205
|
+
`Failed to resolve node '${ref}' in graph '${g.name}'`,
|
|
206
|
+
);
|
|
153
207
|
}
|
|
154
208
|
return node;
|
|
155
209
|
}
|
|
@@ -194,6 +248,9 @@ class GraphBuilder implements GvGraphBuilder {
|
|
|
194
248
|
head: GvNode | string,
|
|
195
249
|
attrs?: Record<string, string>,
|
|
196
250
|
): GvEdge {
|
|
251
|
+
requireNodeRef('tail', tail);
|
|
252
|
+
requireNodeRef('head', head);
|
|
253
|
+
requireAttrs('attrs', attrs);
|
|
197
254
|
const tailNode = resolveNode(this._graph, tail);
|
|
198
255
|
const headNode = resolveNode(this._graph, head);
|
|
199
256
|
const edge = cgraphAddEdge(this._graph, tailNode, headNode);
|
|
@@ -202,23 +259,28 @@ class GraphBuilder implements GvGraphBuilder {
|
|
|
202
259
|
}
|
|
203
260
|
|
|
204
261
|
addSubgraph(name: string, attrs?: Record<string, string>): GvGraphBuilder {
|
|
262
|
+
requireString('name', name);
|
|
263
|
+
requireAttrs('attrs', attrs);
|
|
205
264
|
const sg = agsubg(this._context, name, true);
|
|
206
265
|
if (sg === null) {
|
|
207
|
-
throw new
|
|
266
|
+
throw new InternalError(`Failed to create subgraph '${name}'`);
|
|
208
267
|
}
|
|
209
268
|
applyAttrs(sg.attrs, attrs);
|
|
210
269
|
return new GraphBuilder(this._graph, sg);
|
|
211
270
|
}
|
|
212
271
|
|
|
213
272
|
setAttr(k: string, v: string): void {
|
|
273
|
+
requireKeyValue(k, v);
|
|
214
274
|
this._context.attrs.set(k, v);
|
|
215
275
|
}
|
|
216
276
|
|
|
217
277
|
setHtmlAttr(k: string, v: string): void {
|
|
278
|
+
requireKeyValue(k, v);
|
|
218
279
|
this._context.attrs.set(k, HTML_STRING_MARK + v);
|
|
219
280
|
}
|
|
220
281
|
|
|
221
282
|
getAttr(k: string): string | undefined {
|
|
283
|
+
requireString('k', k);
|
|
222
284
|
return this._context.attrs.get(k);
|
|
223
285
|
}
|
|
224
286
|
}
|
|
@@ -233,8 +295,10 @@ function addNodeToContext(
|
|
|
233
295
|
name: string,
|
|
234
296
|
attrs: Record<string, string> | undefined,
|
|
235
297
|
): GvNode {
|
|
298
|
+
requireString('name', name);
|
|
299
|
+
requireAttrs('attrs', attrs);
|
|
236
300
|
const node = agnode(root, name, true);
|
|
237
|
-
if (node === null) throw new
|
|
301
|
+
if (node === null) throw new InternalError(`Failed to create node '${name}'`);
|
|
238
302
|
applyAttrs(node.attrs, attrs);
|
|
239
303
|
if (context !== root) agsubnode(context, node, true);
|
|
240
304
|
return new NodeHandle(node);
|
|
@@ -269,9 +333,13 @@ function resolveKind(opts: CreateGraphOptions | undefined): GraphKind {
|
|
|
269
333
|
* The builder's `.graph` is a fresh Graph ready for handoff to layout/render.
|
|
270
334
|
* Defaults: directed=true, strict=false, name=''.
|
|
271
335
|
*
|
|
336
|
+
* @throws TypeError `ERR_INVALID_ARG_TYPE` if `opts` or one of its fields has
|
|
337
|
+
* the wrong type. Builder and handle methods throw the same for wrong
|
|
338
|
+
* argument types, and `InternalError` if node/subgraph creation fails.
|
|
272
339
|
* @see lib/cgraph/graph.c:agopen
|
|
273
340
|
*/
|
|
274
341
|
export function createGraph(opts?: CreateGraphOptions): GvGraphBuilder {
|
|
342
|
+
requireGraphOptions(opts);
|
|
275
343
|
const g = new Graph(opts?.name ?? '', resolveKind(opts));
|
|
276
344
|
return new GraphBuilder(g, g);
|
|
277
345
|
}
|
package/src/api/edge-ops.ts
CHANGED
|
@@ -11,6 +11,17 @@
|
|
|
11
11
|
import type { Graph } from '../model/graph.js';
|
|
12
12
|
import type { Node } from '../model/node.js';
|
|
13
13
|
import { Edge } from '../model/edge.js';
|
|
14
|
+
import { invalidArgType } from '../errors.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Argument check shared by the /api entry points: a non-null object.
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
export function requireObject(param: string, v: unknown): void {
|
|
21
|
+
if (typeof v !== 'object' || v === null) {
|
|
22
|
+
throw invalidArgType(param, 'object', v);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
14
25
|
|
|
15
26
|
/** @see lib/cgraph/cgraph.h:agisundirected */
|
|
16
27
|
function isUndirected(g: Graph): boolean {
|
|
@@ -91,6 +102,8 @@ function insertEdge(g: Graph, root: Graph, edge: Edge): void {
|
|
|
91
102
|
* // edge.tail === a, edge.head === b
|
|
92
103
|
* ```
|
|
93
104
|
*
|
|
105
|
+
* @throws TypeError `ERR_INVALID_ARG_TYPE` if `g`, `tail` or `head` is not an
|
|
106
|
+
* object, or `name` is neither undefined nor a string
|
|
94
107
|
* @see lib/cgraph/edge.c:agedge
|
|
95
108
|
*/
|
|
96
109
|
export function addEdge(
|
|
@@ -99,6 +112,12 @@ export function addEdge(
|
|
|
99
112
|
head: Node,
|
|
100
113
|
name?: string,
|
|
101
114
|
): Edge {
|
|
115
|
+
requireObject('g', g);
|
|
116
|
+
requireObject('tail', tail);
|
|
117
|
+
requireObject('head', head);
|
|
118
|
+
if (name !== undefined && typeof name !== 'string') {
|
|
119
|
+
throw invalidArgType('name', 'string or undefined', name);
|
|
120
|
+
}
|
|
102
121
|
const root = g.root;
|
|
103
122
|
const undirected = isUndirected(g);
|
|
104
123
|
|
package/src/api/geometry.ts
CHANGED
|
@@ -34,7 +34,8 @@ import type { Graph } from '../model/graph.js';
|
|
|
34
34
|
import type { Node } from '../model/node.js';
|
|
35
35
|
import type { Edge } from '../model/edge.js';
|
|
36
36
|
import type { TextlabelT } from '../common/types.js';
|
|
37
|
-
import {
|
|
37
|
+
import { invalidArgValue, invalidState } from '../errors.js';
|
|
38
|
+
import { requireObject } from './edge-ops.js';
|
|
38
39
|
|
|
39
40
|
// ---------------------------------------------------------------------------
|
|
40
41
|
// Public coordinate types (canonical home — T5 imports GeometryOptions here)
|
|
@@ -399,6 +400,18 @@ function collectClusters(
|
|
|
399
400
|
// Public API
|
|
400
401
|
// ---------------------------------------------------------------------------
|
|
401
402
|
|
|
403
|
+
const Y_AXES: readonly YAxis[] = ['up', 'down'];
|
|
404
|
+
|
|
405
|
+
/** Argument check for getLayout's options (cheap typeof checks only). */
|
|
406
|
+
function checkGeometryOptions(opts: GeometryOptions | undefined): void {
|
|
407
|
+
if (opts === undefined) return;
|
|
408
|
+
requireObject('opts', opts);
|
|
409
|
+
const y: unknown = opts.yAxis;
|
|
410
|
+
if (y !== undefined && y !== 'up' && y !== 'down') {
|
|
411
|
+
throw invalidArgValue('opts.yAxis', y, Y_AXES);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
402
415
|
/**
|
|
403
416
|
* Returns a plain, JSON-serializable snapshot of the computed geometry for
|
|
404
417
|
* all nodes and edges in graph `g`.
|
|
@@ -406,20 +419,24 @@ function collectClusters(
|
|
|
406
419
|
* Must be called **after** `ctx.layout(g, engine)` (or `render`) has run.
|
|
407
420
|
* Before layout the geometry fields hold calloc-zero defaults (every node at
|
|
408
421
|
* the origin, an empty bounding box), so a not-yet-laid-out graph is rejected
|
|
409
|
-
* with
|
|
422
|
+
* with an `ERR_INVALID_STATE` error rather than returning that all-zero snapshot as if it
|
|
410
423
|
* were real geometry.
|
|
411
424
|
*
|
|
412
425
|
* @param g - Laid-out graph (internal model; not mutated by this function).
|
|
413
426
|
* @param opts - Coordinate options; defaults to `{ yAxis: 'down' }`.
|
|
414
|
-
* @throws
|
|
427
|
+
* @throws TypeError (`ERR_INVALID_ARG_TYPE`) if `g` is not an object or `opts`
|
|
428
|
+
* is not an object; (`ERR_INVALID_ARG_VALUE`) if `opts.yAxis` is not
|
|
429
|
+
* `'up'` or `'down'`.
|
|
430
|
+
* @throws Error (`ERR_INVALID_STATE`) if `g` has not been laid out.
|
|
415
431
|
*
|
|
416
432
|
* @see lib/common/types.h:GD_bb, ND_coord, ED_spl
|
|
417
433
|
*/
|
|
418
434
|
export function getLayout(g: Graph, opts?: GeometryOptions): LayoutSnapshot {
|
|
435
|
+
requireObject('g', g);
|
|
436
|
+
checkGeometryOptions(opts);
|
|
419
437
|
if (g.info?.laidOut !== true) {
|
|
420
|
-
throw
|
|
438
|
+
throw invalidState(
|
|
421
439
|
'getLayout requires a laid-out graph; run ctx.layout(g, engine) or render() first',
|
|
422
|
-
'GENERIC_ERROR',
|
|
423
440
|
);
|
|
424
441
|
}
|
|
425
442
|
const yAxis: YAxis = opts?.yAxis ?? 'down';
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EPL-2.0
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resource collector: the fonts and image sources the synchronous pipeline
|
|
5
|
+
* will ask for, gathered from the parsed graph before any layout runs.
|
|
6
|
+
*
|
|
7
|
+
* Each font request mirrors a measurer call site exactly (same attribute
|
|
8
|
+
* lookup, same defaults and fallbacks), so a prefetch loads the faces the
|
|
9
|
+
* measurer will actually measure.
|
|
10
|
+
*
|
|
11
|
+
* @see src/common/nodeinit.ts:initNodeXLabel / src/common/poly-init.ts:buildNodeLabel
|
|
12
|
+
* @see src/common/edge-label-init.ts (edge, xlabel, head/tail labels)
|
|
13
|
+
* @see src/layout/dot/graph-label.ts:doGraphLabel (root and cluster labels)
|
|
14
|
+
* @see src/common/htmltable.ts / htmltable-pos-runs.ts (HTML item fonts)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { Graph } from '../model/graph.js';
|
|
18
|
+
import type { Edge } from '../model/edge.js';
|
|
19
|
+
import type { Node } from '../model/node.js';
|
|
20
|
+
import type { TextVariantFlags } from '../common/textmeasure.js';
|
|
21
|
+
import { canvasFont } from '../common/css-font.js';
|
|
22
|
+
import { DEFAULT_FONTNAME, DEFAULT_FONTSIZE } from '../common/make-label.js';
|
|
23
|
+
import { isHtmlValue, htmlValueContent } from '../common/html-string.js';
|
|
24
|
+
import { nodeAttr, readFontAttrs } from '../common/poly-init.js';
|
|
25
|
+
import { initFontEdgeAttr, initFontLabelEdgeAttr } from '../common/edge-label-init.js';
|
|
26
|
+
import { parseHtmlLabel } from '../common/htmltable-parse.js';
|
|
27
|
+
import { isACluster } from '../layout/dot/rank.js';
|
|
28
|
+
import type {
|
|
29
|
+
HtmlCellContent, HtmlLabel, HtmlTable, HtmlTextItem,
|
|
30
|
+
} from '../common/htmltable-types.js';
|
|
31
|
+
|
|
32
|
+
/** One font face the measurer will be asked for. */
|
|
33
|
+
export interface FontRequest {
|
|
34
|
+
/** Graphviz `fontname` as the measurer receives it. */
|
|
35
|
+
readonly fontname: string | null;
|
|
36
|
+
/** Font size in points. */
|
|
37
|
+
readonly fontsize: number;
|
|
38
|
+
/** Bold/italic variant flags; absent for the regular face. */
|
|
39
|
+
readonly flags?: TextVariantFlags;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Everything the sync pipeline needs prefetched. */
|
|
43
|
+
export interface Resources {
|
|
44
|
+
/** Distinct font requests, deduped by their `canvasFont` string. */
|
|
45
|
+
readonly fonts: FontRequest[];
|
|
46
|
+
/** Distinct HTML `<IMG SRC>` values (dimension lookups). */
|
|
47
|
+
readonly sizeSrcs: string[];
|
|
48
|
+
/** Distinct image sources to fetch as bytes; empty unless `inlineImages`. */
|
|
49
|
+
readonly bytesSrcs: string[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Options for {@link collectResources}. */
|
|
53
|
+
export interface CollectOptions {
|
|
54
|
+
/** Also collect `image=` and HTML `<IMG SRC>` values as bytes sources. */
|
|
55
|
+
readonly inlineImages: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface Acc {
|
|
59
|
+
readonly fonts: Map<string, FontRequest>;
|
|
60
|
+
readonly sizeSrcs: Set<string>;
|
|
61
|
+
readonly bytesSrcs: Set<string>;
|
|
62
|
+
readonly inlineImages: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface LabelFont { readonly fontname: string; readonly fontsize: number }
|
|
66
|
+
|
|
67
|
+
function addFont(acc: Acc, fontname: string | null, fontsize: number, flags?: TextVariantFlags): void {
|
|
68
|
+
const key = canvasFont(fontname, fontsize, flags);
|
|
69
|
+
if (acc.fonts.has(key)) return;
|
|
70
|
+
const req: FontRequest = flags === undefined
|
|
71
|
+
? { fontname, fontsize }
|
|
72
|
+
: { fontname, fontsize, flags };
|
|
73
|
+
acc.fonts.set(key, req);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function addImage(acc: Acc, src: string | undefined, sized: boolean): void {
|
|
77
|
+
if (src === undefined || src === '') return;
|
|
78
|
+
if (sized) acc.sizeSrcs.add(src);
|
|
79
|
+
if (acc.inlineImages) acc.bytesSrcs.add(src);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Variant flags as the measurer receives them (undefined when regular). */
|
|
83
|
+
function variantFlags(item: HtmlTextItem): TextVariantFlags | undefined {
|
|
84
|
+
if (item.bold !== true && item.italic !== true) return undefined;
|
|
85
|
+
return { bold: item.bold === true, italic: item.italic === true };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function addItemFont(acc: Acc, item: HtmlTextItem, font: LabelFont): void {
|
|
89
|
+
if (item.text === undefined && item.br !== true) return;
|
|
90
|
+
addFont(acc, item.fontFace ?? font.fontname, item.fontSize ?? font.fontsize, variantFlags(item));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function walkContent(acc: Acc, c: HtmlCellContent, font: LabelFont): void {
|
|
94
|
+
switch (c.kind) {
|
|
95
|
+
case 'text':
|
|
96
|
+
for (const item of c.items) addItemFont(acc, item, font);
|
|
97
|
+
break;
|
|
98
|
+
case 'table':
|
|
99
|
+
walkTable(acc, c, font);
|
|
100
|
+
break;
|
|
101
|
+
case 'image':
|
|
102
|
+
addImage(acc, c.src, true);
|
|
103
|
+
break;
|
|
104
|
+
case 'hr':
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function walkTable(acc: Acc, t: HtmlTable, font: LabelFont): void {
|
|
110
|
+
for (const row of t.rows) {
|
|
111
|
+
for (const cell of row.cells) {
|
|
112
|
+
for (const c of cell.content) walkContent(acc, c, font);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function walkHtmlLabel(acc: Acc, label: HtmlLabel, font: LabelFont): void {
|
|
118
|
+
if (label.kind === 'table') {
|
|
119
|
+
walkTable(acc, label.table, font);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
for (const t of label.texts) walkContent(acc, t, font);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Collect the fonts/images one label string leads the sync pipeline to use. */
|
|
126
|
+
function addLabel(acc: Acc, str: string | undefined, font: LabelFont): void {
|
|
127
|
+
if (!str) return;
|
|
128
|
+
if (!isHtmlValue(str)) {
|
|
129
|
+
addFont(acc, font.fontname, font.fontsize);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const content = htmlValueContent(str);
|
|
133
|
+
if (content === '') return; // labels.c:119 — empty HTML is no label
|
|
134
|
+
let label: HtmlLabel;
|
|
135
|
+
try {
|
|
136
|
+
label = parseHtmlLabel(content);
|
|
137
|
+
} catch {
|
|
138
|
+
// Parse failure: the sync path reverts to a plain-text label (htmltable.c:1892).
|
|
139
|
+
addFont(acc, font.fontname, font.fontsize);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
walkHtmlLabel(acc, label, font);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function collectNode(acc: Acc, n: Node, root: Graph): void {
|
|
146
|
+
const { fontname, fontsize } = readFontAttrs(n, root);
|
|
147
|
+
const label = nodeAttr(n, root, 'label');
|
|
148
|
+
if (label === undefined) addFont(acc, fontname, fontsize); // label defaults to \N
|
|
149
|
+
else addLabel(acc, label, { fontname, fontsize });
|
|
150
|
+
addLabel(acc, nodeAttr(n, root, 'xlabel'), { fontname, fontsize });
|
|
151
|
+
addImage(acc, nodeAttr(n, root, 'image'), false);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function collectEdge(acc: Acc, e: Edge): void {
|
|
155
|
+
const fi = initFontEdgeAttr(e);
|
|
156
|
+
const lfi = initFontLabelEdgeAttr(e, fi);
|
|
157
|
+
addLabel(acc, e.attrs.get('label'), fi);
|
|
158
|
+
addLabel(acc, e.attrs.get('xlabel'), fi);
|
|
159
|
+
addLabel(acc, e.attrs.get('headlabel'), lfi);
|
|
160
|
+
addLabel(acc, e.attrs.get('taillabel'), lfi);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Root/cluster label font. A subgraph reads through its parse-time defaults
|
|
165
|
+
* snapshot, as doGraphLabel does. @see src/layout/dot/graph-label.ts:readFontParams
|
|
166
|
+
*/
|
|
167
|
+
function graphLabelFont(sg: Graph): LabelFont {
|
|
168
|
+
const get = (k: string): string | undefined => sg.attrs.get(k) ?? sg.graphDefaultsSnapshot?.get(k);
|
|
169
|
+
return {
|
|
170
|
+
fontname: get('fontname') ?? DEFAULT_FONTNAME,
|
|
171
|
+
fontsize: parseFloat(get('fontsize') ?? '') || DEFAULT_FONTSIZE,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function collectGraphLabels(acc: Acc, sg: Graph, root: Graph): void {
|
|
176
|
+
if (isACluster(sg)) {
|
|
177
|
+
addLabel(acc, sg.attrs.get('label') ?? sg.graphDefaultsSnapshot?.get('label'), graphLabelFont(sg));
|
|
178
|
+
}
|
|
179
|
+
for (const child of sg.subgraphs.values()) collectGraphLabels(acc, child, root);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Gather every font face and image source the synchronous pipeline will ask
|
|
184
|
+
* for. Pure: reads the parsed graph only; a malformed HTML label is skipped
|
|
185
|
+
* (the sync path falls back to plain text).
|
|
186
|
+
*
|
|
187
|
+
* @param g - A parsed graph (before layout).
|
|
188
|
+
* @param opts - Collection options.
|
|
189
|
+
* @returns The deduped fonts, size sources and bytes sources.
|
|
190
|
+
*/
|
|
191
|
+
export function collectResources(g: Graph, opts: CollectOptions): Resources {
|
|
192
|
+
const acc: Acc = {
|
|
193
|
+
fonts: new Map(), sizeSrcs: new Set(), bytesSrcs: new Set(), inlineImages: opts.inlineImages,
|
|
194
|
+
};
|
|
195
|
+
const root = g.root;
|
|
196
|
+
collectGraphLabels(acc, root, root);
|
|
197
|
+
for (const n of root.nodes.values()) collectNode(acc, n, root);
|
|
198
|
+
for (const e of root.edges) collectEdge(acc, e);
|
|
199
|
+
return {
|
|
200
|
+
fonts: [...acc.fonts.values()],
|
|
201
|
+
sizeSrcs: [...acc.sizeSrcs],
|
|
202
|
+
bytesSrcs: [...acc.bytesSrcs],
|
|
203
|
+
};
|
|
204
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// SPDX-License-Identifier: EPL-2.0
|
|
2
|
+
import { canvasFont } from '../common/css-font.js';
|
|
3
|
+
import type { FontRequest } from './collect.js';
|
|
4
|
+
|
|
5
|
+
/** A font that could not be made available before measuring. */
|
|
6
|
+
export type FontIssue = { face: string; reason: 'failed' | 'timeout' };
|
|
7
|
+
|
|
8
|
+
/** Structural subset of `FontFaceSet`; the real object satisfies it. */
|
|
9
|
+
export interface FontSetLike {
|
|
10
|
+
load(font: string): Promise<readonly { status?: string }[]>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type Outcome = FontIssue['reason'] | 'ok';
|
|
14
|
+
|
|
15
|
+
async function settle(set: FontSetLike, face: string): Promise<Outcome> {
|
|
16
|
+
try {
|
|
17
|
+
const faces = await set.load(face);
|
|
18
|
+
return faces.some((f) => f.status === 'error') ? 'failed' : 'ok';
|
|
19
|
+
} catch {
|
|
20
|
+
// A rejected load is reported as a `failed` issue, not rethrown.
|
|
21
|
+
return 'failed';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function toIssues(faces: readonly string[], outcomes: readonly Outcome[]): FontIssue[] {
|
|
26
|
+
const issues: FontIssue[] = [];
|
|
27
|
+
faces.forEach((face, i) => {
|
|
28
|
+
const reason = outcomes[i];
|
|
29
|
+
if (reason === undefined || reason === 'ok') return;
|
|
30
|
+
console.warn(`dot-engine: font "${face}" ${reason}`);
|
|
31
|
+
issues.push({ face, reason });
|
|
32
|
+
});
|
|
33
|
+
return issues;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Loads every distinct CSS font the requests map to (in parallel, sharing one
|
|
38
|
+
* deadline) so canvas measurement sees real faces rather than fallbacks.
|
|
39
|
+
* Resolves with one {@link FontIssue} per face that failed or timed out;
|
|
40
|
+
* never rejects. An undefined `fontSet` yields `[]` immediately.
|
|
41
|
+
*/
|
|
42
|
+
export async function loadFonts(
|
|
43
|
+
fontSet: FontSetLike | undefined,
|
|
44
|
+
fonts: readonly FontRequest[],
|
|
45
|
+
timeoutMs: number,
|
|
46
|
+
): Promise<FontIssue[]> {
|
|
47
|
+
if (fontSet === undefined) return [];
|
|
48
|
+
const faces = [...new Set(fonts.map((f) => canvasFont(f.fontname, f.fontsize, f.flags)))];
|
|
49
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
50
|
+
const deadline = new Promise<Outcome>((resolve) => {
|
|
51
|
+
timer = setTimeout(() => resolve('timeout'), timeoutMs);
|
|
52
|
+
});
|
|
53
|
+
try {
|
|
54
|
+
const outcomes = await Promise.all(
|
|
55
|
+
faces.map((face) => Promise.race([settle(fontSet, face), deadline])),
|
|
56
|
+
);
|
|
57
|
+
return toIssues(faces, outcomes);
|
|
58
|
+
} finally {
|
|
59
|
+
clearTimeout(timer);
|
|
60
|
+
}
|
|
61
|
+
}
|