@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
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* @see lib/neatogen/overlap.c:remove_overlap (15.0.0)
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
import { RenderError } from '../../errors.js';
|
|
14
15
|
import { fma } from '../../common/fma.js';
|
|
15
16
|
import { cdrand } from '../../common/crand.js';
|
|
16
17
|
import {
|
|
@@ -30,10 +31,38 @@ import {
|
|
|
30
31
|
import {
|
|
31
32
|
type SpringElectricalControl,
|
|
32
33
|
AUTOP,
|
|
34
|
+
QUAD_TREE_NONE,
|
|
35
|
+
QUAD_TREE_FAST,
|
|
33
36
|
springElectricalEmbedding,
|
|
34
37
|
} from './spring-electrical.js';
|
|
35
38
|
import { removeOverlapPrism } from '../neato/overlap-prism.js';
|
|
36
39
|
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Quadtree scheme dispatch (loud for the unported embeddings)
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Mirror of the per-level dispatch: NONE -> _slow, FAST (or HYBRID above
|
|
46
|
+
* QUAD_TREE_HYBRID_SIZE) -> _fast, else the NORMAL embedding. The first two
|
|
47
|
+
* are not ported. HYBRID is unreachable here: sfdp's quadtree parser only
|
|
48
|
+
* yields NONE/NORMAL/FAST (sfdpinit.c:late_quadtree_scheme).
|
|
49
|
+
* @see lib/sfdpgen/spring_electrical.c:multilevel_spring_electrical_embedding (1140-1148)
|
|
50
|
+
*/
|
|
51
|
+
function embedLevel(
|
|
52
|
+
dim: number, A: SpMatrix, ctrl: SpringElectricalControl, xc: number[],
|
|
53
|
+
): void {
|
|
54
|
+
if (ctrl.tscheme === QUAD_TREE_NONE || ctrl.tscheme === QUAD_TREE_FAST) {
|
|
55
|
+
// Name the resolved scheme: "0"/"false" select none, "2" selects fast.
|
|
56
|
+
const [value, what] = ctrl.tscheme === QUAD_TREE_NONE
|
|
57
|
+
? ['none', 'spring_electrical_embedding_slow']
|
|
58
|
+
: ['fast', 'spring_electrical_embedding_fast'];
|
|
59
|
+
throw new RenderError(
|
|
60
|
+
`quadtree=${value}: ${what} is not supported yet`,
|
|
61
|
+
'UNSUPPORTED_FEATURE');
|
|
62
|
+
}
|
|
63
|
+
springElectricalEmbedding(dim, A, ctrl, xc);
|
|
64
|
+
}
|
|
65
|
+
|
|
37
66
|
// ---------------------------------------------------------------------------
|
|
38
67
|
// Multilevel driver helpers
|
|
39
68
|
// ---------------------------------------------------------------------------
|
|
@@ -178,7 +207,7 @@ export function multilevelSpringElectricalEmbedding(
|
|
|
178
207
|
}
|
|
179
208
|
|
|
180
209
|
for (;;) {
|
|
181
|
-
|
|
210
|
+
embedLevel(dim, grid!.A, ctrl, xc);
|
|
182
211
|
if (multilevelIsFinest(grid!)) break;
|
|
183
212
|
const P = grid!.P!;
|
|
184
213
|
grid = grid!.prev;
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* @see lib/twopigen/circle.h
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { InternalError } from '../../errors.js';
|
|
11
12
|
import type { Graph } from '../../model/graph.js';
|
|
12
13
|
import type { Node } from '../../model/node.js';
|
|
13
14
|
import type { Edge } from '../../model/edge.js';
|
|
@@ -23,7 +24,7 @@ export const MIN_RANKSEP = 0.02;
|
|
|
23
24
|
/** Get the TwopiAlgData record for a node; throws if absent. */
|
|
24
25
|
function rdata(n: Node): TwopiAlgData {
|
|
25
26
|
const d = n.info.alg;
|
|
26
|
-
if (!d || d.kind !== 'twopi') throw new
|
|
27
|
+
if (!d || d.kind !== 'twopi') throw new InternalError(`twopi: missing rdata on node ${n.name}`);
|
|
27
28
|
return d;
|
|
28
29
|
}
|
|
29
30
|
|
|
@@ -27,6 +27,7 @@ import { Bend } from "./types.js";
|
|
|
27
27
|
import { chanSearch, chansInOrder } from "./maze-channels.js";
|
|
28
28
|
import { insertEdge, edgeExists, removeRedge } from "./rawgraph.js";
|
|
29
29
|
import { segCmp } from "./ortho-route.js";
|
|
30
|
+
import { InternalError } from "../errors.js";
|
|
30
31
|
|
|
31
32
|
/** @see lib/ortho/ortho.c:next_seg */
|
|
32
33
|
function nextSeg(seg: OrthoSegment, dir: number): OrthoSegment | null {
|
|
@@ -81,7 +82,7 @@ function decidePoint(
|
|
|
81
82
|
sj = np2; // eslint-disable-line no-param-reassign
|
|
82
83
|
}
|
|
83
84
|
if (np1 === null) prec = 0;
|
|
84
|
-
else if (np2 === null) throw new
|
|
85
|
+
else if (np2 === null) throw new InternalError("decide_point: np2 null (C assert(0))");
|
|
85
86
|
else {
|
|
86
87
|
const temp = segCmp(np1, np2);
|
|
87
88
|
if (temp === -2) return -1;
|
package/src/ortho/trap-query.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
cross, TRAP_MAX,
|
|
14
14
|
} from "./trap-types.js";
|
|
15
15
|
import type { SegPoint, SegmentT, TrapT, QNode } from "./trap-types.js";
|
|
16
|
+
import { InternalError } from "../errors.js";
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Test whether point v is to the left of segment segnum.
|
|
@@ -96,7 +97,7 @@ export function locateEndpoint(
|
|
|
96
97
|
}
|
|
97
98
|
return locateEndpoint(v, vo, rptr.left, seg, qs);
|
|
98
99
|
case T_X: return locateX(v, vo, rptr, seg, qs);
|
|
99
|
-
default: throw new
|
|
100
|
+
default: throw new InternalError("locateEndpoint: unreachable");
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
|
package/src/parser/index.ts
CHANGED
|
@@ -14,18 +14,18 @@ import type { Edge } from '../model/edge.js';
|
|
|
14
14
|
import { isHtmlValue, htmlValueContent } from '../common/html-string.js';
|
|
15
15
|
import { buildFromAst } from './builder.js';
|
|
16
16
|
import type { ParsedGraph } from './ast.js';
|
|
17
|
-
import type {
|
|
18
|
-
import { friendlyMessageFor } from '../errors.js';
|
|
17
|
+
import type { GvErrorCode, GvExpectation } from '../errors.js';
|
|
18
|
+
import { DotEngineError, friendlyMessageFor, invalidArgType } from '../errors.js';
|
|
19
19
|
|
|
20
20
|
// ── ParseError ────────────────────────────────────────────────────────────────
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Thrown for syntax errors or edge-direction violations.
|
|
24
24
|
*
|
|
25
|
-
* Implements the structured {@link
|
|
25
|
+
* Implements the structured {@link DotEngineError} / `GvError` contract: `location` is primary;
|
|
26
26
|
* `line`/`column` are convenience getters that delegate to it.
|
|
27
27
|
*/
|
|
28
|
-
export class ParseError extends
|
|
28
|
+
export class ParseError extends DotEngineError {
|
|
29
29
|
readonly type = 'syntax';
|
|
30
30
|
readonly code: GvErrorCode;
|
|
31
31
|
readonly friendlyMessage: string;
|
|
@@ -37,8 +37,9 @@ export class ParseError extends Error implements GvError {
|
|
|
37
37
|
code: GvErrorCode,
|
|
38
38
|
location: { line: number; column: number; offset?: number },
|
|
39
39
|
expected?: GvExpectation[],
|
|
40
|
+
options?: ErrorOptions,
|
|
40
41
|
) {
|
|
41
|
-
super(message);
|
|
42
|
+
super(message, options);
|
|
42
43
|
this.name = 'ParseError';
|
|
43
44
|
this.code = code;
|
|
44
45
|
this.location = location;
|
|
@@ -217,17 +218,16 @@ export function isPeggyError(err: unknown): err is {
|
|
|
217
218
|
|
|
218
219
|
/**
|
|
219
220
|
* Parse a DOT-language string and return a Graph model.
|
|
220
|
-
* @throws ParseError
|
|
221
|
+
* @throws ParseError `SYNTAX_ERROR`, `SYNTAX_UNEXPECTED_EOF`, `EDGE_OP_*` for
|
|
222
|
+
* syntax errors or edge-direction violations; `GENERIC_ERROR` when nesting
|
|
223
|
+
* is too deep
|
|
224
|
+
* @throws TypeError `ERR_INVALID_ARG_TYPE` if `src` is not a string
|
|
221
225
|
*/
|
|
222
226
|
export function parse(src: string): Graph {
|
|
223
227
|
if (typeof src !== 'string') {
|
|
224
228
|
// Runtime guard for JS callers (the TS signature already forbids this):
|
|
225
229
|
// a non-string argument must not surface as an opaque internal TypeError.
|
|
226
|
-
throw
|
|
227
|
-
line: 1,
|
|
228
|
-
column: 1,
|
|
229
|
-
offset: 0,
|
|
230
|
-
});
|
|
230
|
+
throw invalidArgType('dotSource', 'string', src);
|
|
231
231
|
}
|
|
232
232
|
let ast: ParsedGraph;
|
|
233
233
|
try {
|
package/src/render/index.ts
CHANGED
|
@@ -22,6 +22,11 @@
|
|
|
22
22
|
// --- Multi-format render entry (T5) ---------------------------------------
|
|
23
23
|
export { render } from './public.js';
|
|
24
24
|
export type { OutputFormat, RenderOptions } from './public.js';
|
|
25
|
+
export { renderAsync } from '../async/render-async.js';
|
|
26
|
+
export type {
|
|
27
|
+
AsyncRenderOptions, AsyncRenderResult, AsyncImageSize, AsyncImageBytes,
|
|
28
|
+
FontIssue, FontSetLike,
|
|
29
|
+
} from '../async/render-async.js';
|
|
25
30
|
|
|
26
31
|
// --- Structured xdot draw-ops (T6) ----------------------------------------
|
|
27
32
|
export { getDrawOps, DEFAULT_DRAW_ENGINE } from './xdot-public.js';
|
package/src/render/public.ts
CHANGED
|
@@ -13,8 +13,7 @@
|
|
|
13
13
|
import type { Graph } from '../model/graph.js';
|
|
14
14
|
import { createDefaultContext } from '../gvc/default-context.js';
|
|
15
15
|
import { render as deviceRender } from '../gvc/device.js';
|
|
16
|
-
import {
|
|
17
|
-
import type { GvError } from '../errors.js';
|
|
16
|
+
import { invalidArgType, rethrowAtBoundary } from '../errors.js';
|
|
18
17
|
import type { EngineName } from '../gvc/context.js';
|
|
19
18
|
|
|
20
19
|
// ---------------------------------------------------------------------------
|
|
@@ -64,23 +63,19 @@ export interface RenderOptions {
|
|
|
64
63
|
// Private helpers (mirrors index.ts)
|
|
65
64
|
// ---------------------------------------------------------------------------
|
|
66
65
|
|
|
67
|
-
/**
|
|
68
|
-
* Duck-type a thrown value as a {@link GvError}: object with string `type`
|
|
69
|
-
* and string `code`. Re-implemented here (not imported) so this module has
|
|
70
|
-
* no circular dependency on `src/index.ts`.
|
|
71
|
-
*/
|
|
72
|
-
function isGvErrorLike(err: unknown): err is GvError {
|
|
73
|
-
return (
|
|
74
|
-
typeof err === 'object' &&
|
|
75
|
-
err !== null &&
|
|
76
|
-
typeof (err as { type?: unknown }).type === 'string' &&
|
|
77
|
-
typeof (err as { code?: unknown }).code === 'string'
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
66
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
67
|
+
|
|
68
|
+
/** Reject a bad `g` / `format` / `opts` before any work starts. */
|
|
69
|
+
function checkRenderArgs(g: unknown, format: unknown, opts: unknown): void {
|
|
70
|
+
if (typeof g !== 'object' || g === null) {
|
|
71
|
+
throw invalidArgType('g', 'object', g);
|
|
72
|
+
}
|
|
73
|
+
if (typeof format !== 'string') {
|
|
74
|
+
throw invalidArgType('format', 'string', format);
|
|
75
|
+
}
|
|
76
|
+
if (opts !== undefined && (typeof opts !== 'object' || opts === null)) {
|
|
77
|
+
throw invalidArgType('opts', 'object or undefined', opts);
|
|
78
|
+
}
|
|
84
79
|
}
|
|
85
80
|
|
|
86
81
|
// ---------------------------------------------------------------------------
|
|
@@ -91,8 +86,8 @@ function messageOf(err: unknown): string {
|
|
|
91
86
|
* Render a (parsed or built) graph to the requested format string.
|
|
92
87
|
*
|
|
93
88
|
* Lifecycle: createDefaultContext → layout → deviceRender → freeLayout.
|
|
94
|
-
* Error handling mirrors `renderSvg`:
|
|
95
|
-
* unchanged;
|
|
89
|
+
* Error handling mirrors `renderSvg`: usage errors and GvErrors re-surface
|
|
90
|
+
* unchanged; any other throw becomes an `InternalError` with `cause` set.
|
|
96
91
|
*
|
|
97
92
|
* @remarks
|
|
98
93
|
* Security: for the markup formats (`svg`, `cmapx`, `imap`), treat the output
|
|
@@ -106,7 +101,13 @@ function messageOf(err: unknown): string {
|
|
|
106
101
|
* @param format - target output format
|
|
107
102
|
* @param opts - optional engine override (default: `'dot'`)
|
|
108
103
|
* @returns rendered string in the requested format
|
|
109
|
-
* @throws
|
|
104
|
+
* @throws TypeError `ERR_INVALID_ARG_TYPE` if `g` is not an object, `format`
|
|
105
|
+
* is not a string, or `opts` is neither undefined nor an object
|
|
106
|
+
* @throws TypeError `ERR_INVALID_ARG_VALUE` if the engine or format is not
|
|
107
|
+
* registered
|
|
108
|
+
* @throws RenderError `RENDER_ERROR`, `UNKNOWN_LAYOUT` or `UNSUPPORTED_FEATURE`
|
|
109
|
+
* on layout or render failure
|
|
110
|
+
* @throws InternalError `INTERNAL_ERROR` on a dot-engine bug
|
|
110
111
|
*
|
|
111
112
|
* @see lib/gvc/gvc.c:gvRender
|
|
112
113
|
*/
|
|
@@ -115,6 +116,7 @@ export function render(
|
|
|
115
116
|
format: OutputFormat,
|
|
116
117
|
opts?: RenderOptions,
|
|
117
118
|
): string {
|
|
119
|
+
checkRenderArgs(g, format, opts);
|
|
118
120
|
const engine: EngineName = opts?.engine ?? 'dot';
|
|
119
121
|
const inlineImages = opts?.inlineImages ?? false;
|
|
120
122
|
const ctx = createDefaultContext();
|
|
@@ -124,8 +126,6 @@ export function render(
|
|
|
124
126
|
ctx.freeLayout(g, engine);
|
|
125
127
|
return result;
|
|
126
128
|
} catch (err: unknown) {
|
|
127
|
-
|
|
128
|
-
if (isGvErrorLike(err)) throw err;
|
|
129
|
-
throw new RenderError(messageOf(err), 'RENDER_ERROR');
|
|
129
|
+
return rethrowAtBoundary(err);
|
|
130
130
|
}
|
|
131
131
|
}
|
package/src/render/svg.ts
CHANGED
|
@@ -282,7 +282,7 @@ export class SvgRenderer implements RendererPlugin {
|
|
|
282
282
|
const originy = (b.ur.y + b.ll.y + height) / 2;
|
|
283
283
|
let href = src;
|
|
284
284
|
if (job.inlineImages === true) {
|
|
285
|
-
const found = findImageBytes(src);
|
|
285
|
+
const found = findImageBytes(src, job.imageResolver);
|
|
286
286
|
if (found !== null) href = toDataUri(found.bytes, found.mime);
|
|
287
287
|
}
|
|
288
288
|
job.write('<image xlink:href="' + escapeXml(href) + '" width="' + g(width)
|
|
@@ -34,8 +34,7 @@ import { render as gvcRender } from '../gvc/device.js';
|
|
|
34
34
|
import { createDefaultContext } from '../gvc/default-context.js';
|
|
35
35
|
import { parseXDot } from '../xdot/index.js';
|
|
36
36
|
import type { EngineName } from '../gvc/context.js';
|
|
37
|
-
import {
|
|
38
|
-
import type { GvError } from '../errors.js';
|
|
37
|
+
import { invalidArgType, rethrowAtBoundary } from '../errors.js';
|
|
39
38
|
|
|
40
39
|
/**
|
|
41
40
|
* `Xdot` — the parsed result of one xdot attribute stream: `ops` (the
|
|
@@ -76,20 +75,16 @@ const XDOT_DRAW_ATTRS = [
|
|
|
76
75
|
'_draw_', '_ldraw_', '_hdraw_', '_tdraw_', '_hldraw_', '_tldraw_',
|
|
77
76
|
] as const;
|
|
78
77
|
|
|
79
|
-
function isGvErrorLike(err: unknown): err is GvError {
|
|
80
|
-
return (
|
|
81
|
-
typeof err === 'object' &&
|
|
82
|
-
err !== null &&
|
|
83
|
-
typeof (err as { type?: unknown }).type === 'string' &&
|
|
84
|
-
typeof (err as { code?: unknown }).code === 'string'
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
78
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
79
|
+
|
|
80
|
+
/** Reject a bad `g` / `opts` before any work starts. */
|
|
81
|
+
function checkDrawOpsArgs(g: unknown, opts: unknown): void {
|
|
82
|
+
if (typeof g !== 'object' || g === null) {
|
|
83
|
+
throw invalidArgType('g', 'object', g);
|
|
84
|
+
}
|
|
85
|
+
if (opts !== undefined && (typeof opts !== 'object' || opts === null)) {
|
|
86
|
+
throw invalidArgType('opts', 'object or undefined', opts);
|
|
87
|
+
}
|
|
93
88
|
}
|
|
94
89
|
|
|
95
90
|
/** Append XdotOps found in `attrs` for each xdot draw key into `out`. */
|
|
@@ -118,7 +113,7 @@ function layoutAndRenderXdot(g: Graph, engine: EngineName): string {
|
|
|
118
113
|
ctx.freeLayout(g, engine);
|
|
119
114
|
return src;
|
|
120
115
|
} catch (err: unknown) {
|
|
121
|
-
|
|
116
|
+
return rethrowAtBoundary(err);
|
|
122
117
|
}
|
|
123
118
|
}
|
|
124
119
|
|
|
@@ -132,8 +127,13 @@ function layoutAndRenderXdot(g: Graph, engine: EngineName): string {
|
|
|
132
127
|
* @param g - A Graph from `parse()` or the builder API.
|
|
133
128
|
* @param opts - Optional: `{ engine }` overrides the default `'dot'`.
|
|
134
129
|
* @returns Flat typed draw-op array covering the full graph.
|
|
135
|
-
* @throws
|
|
136
|
-
*
|
|
130
|
+
* @throws TypeError `ERR_INVALID_ARG_TYPE` if `g` is not an object or `opts`
|
|
131
|
+
* is neither undefined nor an object
|
|
132
|
+
* @throws TypeError `ERR_INVALID_ARG_VALUE` if `opts.engine` is not registered
|
|
133
|
+
* @throws RenderError `RENDER_ERROR`, `UNKNOWN_LAYOUT` or `UNSUPPORTED_FEATURE`
|
|
134
|
+
* if layout or rendering fails
|
|
135
|
+
* @throws ParseError if the xdot output cannot be re-parsed (a dot-engine bug)
|
|
136
|
+
* @throws InternalError `INTERNAL_ERROR` on any other dot-engine bug
|
|
137
137
|
*
|
|
138
138
|
* @example
|
|
139
139
|
* ```ts
|
|
@@ -159,6 +159,7 @@ function layoutAndRenderXdot(g: Graph, engine: EngineName): string {
|
|
|
159
159
|
* @see lib/gvc/gvc.h:gvRender
|
|
160
160
|
*/
|
|
161
161
|
export function getDrawOps(g: Graph, opts?: DrawOpsOptions): XdotOp[] {
|
|
162
|
+
checkDrawOpsArgs(g, opts);
|
|
162
163
|
const engine = opts?.engine ?? DEFAULT_DRAW_ENGINE;
|
|
163
164
|
return collectGraphOps(parse(layoutAndRenderXdot(g, engine)));
|
|
164
165
|
}
|
package/src/util/xml.ts
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* @see lib/util/xml.c
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { RenderError } from '../errors.js';
|
|
10
|
+
|
|
9
11
|
/** Options to tweak the behaviour of XML escaping. @see lib/util/xml.h:xml_flags_t */
|
|
10
12
|
export interface XmlFlags {
|
|
11
13
|
/** Escape & unconditionally; also escape \n and \r. */
|
|
@@ -125,45 +127,37 @@ class XmlEscaper {
|
|
|
125
127
|
}
|
|
126
128
|
|
|
127
129
|
/**
|
|
128
|
-
*
|
|
130
|
+
* Emit &#xNNNN; for the Unicode code point at s[pos]. JS strings are
|
|
131
|
+
* UTF-16, so the code point (what C decodes from UTF-8) comes from
|
|
132
|
+
* codePointAt, and an astral character consumes two units.
|
|
129
133
|
* @see lib/util/xml.c:xml_core (UTF-8 block)
|
|
130
134
|
*/
|
|
131
135
|
static utf8Entity(s: string, pos: number): [string, number] {
|
|
132
|
-
const
|
|
133
|
-
const
|
|
134
|
-
const consumed = XmlEscaper.utf8ByteLen(b0);
|
|
136
|
+
const cp = XmlEscaper.decodeUtf8(s, pos);
|
|
137
|
+
const consumed = XmlEscaper.utf8ByteLen(cp);
|
|
135
138
|
return [`&#x${cp.toString(16)};`, consumed];
|
|
136
139
|
}
|
|
137
140
|
|
|
138
|
-
/**
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
(s.charCodeAt(pos + 2) & 0x3f)
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
if ((b0 >> 3) === 30) {
|
|
151
|
-
return (
|
|
152
|
-
((b0 & 0x07) << 18) |
|
|
153
|
-
((s.charCodeAt(pos + 1) & 0x3f) << 12) |
|
|
154
|
-
((s.charCodeAt(pos + 2) & 0x3f) << 6) |
|
|
155
|
-
(s.charCodeAt(pos + 3) & 0x3f)
|
|
156
|
-
);
|
|
141
|
+
/**
|
|
142
|
+
* Return the code point at s[pos]. A lone surrogate is the analogue of
|
|
143
|
+
* malformed UTF-8, where C reports an error and exits (xml.c:135).
|
|
144
|
+
* @see lib/util/xml.c:xml_core (fprintf + graphviz_exit at xml.c:135)
|
|
145
|
+
*/
|
|
146
|
+
static decodeUtf8(s: string, pos: number): number {
|
|
147
|
+
const cp = s.codePointAt(pos)!; // caller: pos < s.length
|
|
148
|
+
if (cp >= 0xd800 && cp <= 0xdfff) {
|
|
149
|
+
throw new RenderError(`gvXmlEscape: malformed UTF-8 at position ${pos}`, 'RENDER_ERROR');
|
|
157
150
|
}
|
|
158
|
-
|
|
151
|
+
return cp;
|
|
159
152
|
}
|
|
160
153
|
|
|
161
|
-
/**
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Return how many UTF-16 units encode the code point (C: the UTF-8 byte
|
|
156
|
+
* length from the leading byte).
|
|
157
|
+
* @see lib/util/xml.c:xml_core (length computation)
|
|
158
|
+
*/
|
|
159
|
+
static utf8ByteLen(cp: number): number {
|
|
160
|
+
return cp > 0xffff ? 2 : 1;
|
|
167
161
|
}
|
|
168
162
|
|
|
169
163
|
static isDecDigit(c: string): boolean {
|
package/src/vpsc/Solver.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { Variable } from "./Variable.js";
|
|
|
13
13
|
import { Constraint } from "./Constraint.js";
|
|
14
14
|
import { Block } from "./Block.js";
|
|
15
15
|
import { Blocks } from "./Blocks.js";
|
|
16
|
+
import { InternalError } from "../errors.js";
|
|
16
17
|
|
|
17
18
|
// ---------------------------------------------------------------------------
|
|
18
19
|
// Rectangle
|
|
@@ -126,10 +127,11 @@ export class VPSC {
|
|
|
126
127
|
/**
|
|
127
128
|
* Throw if any constraint has slack < -1e-7.
|
|
128
129
|
* Shared between satisfy() and refine().
|
|
130
|
+
* @see lib/vpsc/solve_VPSC.cpp:VPSC::satisfy (uncaught std::runtime_error)
|
|
129
131
|
*/
|
|
130
132
|
private verifyConstraints(): void {
|
|
131
133
|
for (const c of this.cs) {
|
|
132
|
-
if (c.slack() < -0.0000001) throw new
|
|
134
|
+
if (c.slack() < -0.0000001) throw new InternalError("Unsatisfied constraint");
|
|
133
135
|
}
|
|
134
136
|
}
|
|
135
137
|
|
|
@@ -183,7 +185,7 @@ export class IncVPSC extends VPSC {
|
|
|
183
185
|
if (lb !== rb) {
|
|
184
186
|
lb.mergeTwoArg(rb, vc);
|
|
185
187
|
} else {
|
|
186
|
-
if (++splitCtr > 10000) throw new
|
|
188
|
+
if (++splitCtr > 10000) throw new InternalError("Cycle Error!");
|
|
187
189
|
const [splitC, newLb, newRb] = lb.splitBetween(vc.left, vc.right);
|
|
188
190
|
this.inactive.push(splitC);
|
|
189
191
|
newLb.mergeTwoArg(newRb, vc);
|
|
@@ -192,7 +194,7 @@ export class IncVPSC extends VPSC {
|
|
|
192
194
|
}
|
|
193
195
|
this.bs.cleanup();
|
|
194
196
|
for (const c of this.cs) {
|
|
195
|
-
if (c.slack() < -0.0000001) throw new
|
|
197
|
+
if (c.slack() < -0.0000001) throw new InternalError("Unsatisfied constraint");
|
|
196
198
|
}
|
|
197
199
|
}
|
|
198
200
|
|