@knowvah/dot-engine 2.0.2 → 2.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/dist/async/render-async.d.ts.map +1 -1
- package/dist/common/arith.d.ts +6 -0
- package/dist/common/arith.d.ts.map +1 -1
- package/dist/common/poly-sizing.d.ts.map +1 -1
- package/dist/common/textmeasure-factory.d.ts +2 -1
- package/dist/common/textmeasure-factory.d.ts.map +1 -1
- package/dist/common/textmeasure.d.ts +1 -1
- package/dist/common/textmeasure.d.ts.map +1 -1
- package/dist/index.js +25 -10
- package/dist/index.js.map +3 -3
- package/dist/layout/dot/splines-label.d.ts.map +1 -1
- package/dist/layout/fdp/normalize.d.ts.map +1 -1
- package/dist/render.js +25 -10
- package/dist/render.js.map +3 -3
- package/package.json +1 -1
- package/src/async/render-async.ts +6 -3
- package/src/common/arith.ts +9 -0
- package/src/common/poly-sizing.ts +2 -1
- package/src/common/textmeasure-factory.ts +20 -1
- package/src/common/textmeasure.ts +1 -1
- package/src/layout/dot/splines-label.ts +2 -1
- package/src/layout/fdp/normalize.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knowvah/dot-engine",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.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",
|
|
@@ -63,11 +63,14 @@ export interface AsyncSvgResult {
|
|
|
63
63
|
fontIssues: FontIssue[];
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* `document.fonts` in a page, else `self.fonts` (WorkerGlobalScope.fonts) in a
|
|
68
|
+
* Worker; undefined in Node. @see plans/async-api/DESIGN.md D5
|
|
69
|
+
*/
|
|
67
70
|
function defaultFontSet(): FontSetLike | undefined {
|
|
68
71
|
const doc: unknown = Reflect.get(globalThis, 'document');
|
|
69
|
-
|
|
70
|
-
const fonts: unknown = Reflect.get(
|
|
72
|
+
const owner: unknown = typeof doc === 'object' && doc !== null ? doc : globalThis;
|
|
73
|
+
const fonts: unknown = Reflect.get(owner as object, 'fonts');
|
|
71
74
|
if (typeof fonts !== 'object' || fonts === null) return undefined;
|
|
72
75
|
return fonts as FontSetLike; // structural: FontFaceSet.load
|
|
73
76
|
}
|
package/src/common/arith.ts
CHANGED
|
@@ -37,3 +37,12 @@
|
|
|
37
37
|
export function cround(v: number): number {
|
|
38
38
|
return v >= 0 ? Math.floor(v + 0.5) : Math.ceil(v - 0.5);
|
|
39
39
|
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Degrees to radians in C's operation order, `(deg)/180.0 * M_PI`.
|
|
43
|
+
* `deg * Math.PI / 180` rounds differently for some angles (e.g. 33°).
|
|
44
|
+
* @see lib/common/arith.h:49 (RADIANS)
|
|
45
|
+
*/
|
|
46
|
+
export function RADIANS(deg: number): number {
|
|
47
|
+
return (deg / 180.0) * Math.PI;
|
|
48
|
+
}
|
|
@@ -22,6 +22,7 @@ import type { Point } from '../model/geom.js';
|
|
|
22
22
|
import { STAR, CYLINDER } from './shapeData.js';
|
|
23
23
|
import { starVertices } from './poly-vertices.js';
|
|
24
24
|
import { fma } from './fma.js';
|
|
25
|
+
import { RADIANS } from './arith.js';
|
|
25
26
|
|
|
26
27
|
/** Whitespace in points around labels / between peripheries. @see lib/common/const.h:GAP */
|
|
27
28
|
export const GAP = 4;
|
|
@@ -277,7 +278,7 @@ export interface PolyGeom {
|
|
|
277
278
|
/** Distort, skew, orient, and scale one unit vertex. @see shapes.c:poly_init */
|
|
278
279
|
function transformUnitVertex(R: Point, g: PolyGeom, c: { skewdist: number; gdistortion: number; gskew: number }, bb: Point): Point {
|
|
279
280
|
const D = { x: R.x * (c.skewdist + R.y * c.gdistortion) + R.y * c.gskew, y: R.y };
|
|
280
|
-
const alpha = (g.orientation
|
|
281
|
+
const alpha = RADIANS(g.orientation) + Math.atan2(D.y, D.x);
|
|
281
282
|
// C uses libm hypot(D.x, D.y). V8's Math.hypot is a scaled algorithm that
|
|
282
283
|
// returns a result 1 ULP off from libm hypot for in-range polygon vertices
|
|
283
284
|
// (e.g. hypot(0.35355…, 0.35355…): libm = 0.5 exactly, Math.hypot = 0.5+1ULP),
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* 1. explicit override via setTextMeasurer (tests, host-faithful Node opt-in)
|
|
7
7
|
* 2. GV_TEXT_MEASURER=estimate|lut — test/CI hook to force a specific measurer
|
|
8
8
|
* 3. browser (document present) — CanvasTextMeasurer over the page canvas
|
|
9
|
-
* (host-faithful: the same font the browser renders the SVG with)
|
|
9
|
+
* (host-faithful: the same font the browser renders the SVG with);
|
|
10
|
+
* Worker (no document) — the same over an OffscreenCanvas
|
|
10
11
|
* 4. Node default — EstimateTextMeasurer (deterministic; matches
|
|
11
12
|
* graphviz's own headless estimate_textspan_size)
|
|
12
13
|
*
|
|
@@ -77,6 +78,21 @@ function adviseHostFaithful(): void {
|
|
|
77
78
|
);
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
/**
|
|
82
|
+
* A CanvasTextMeasurer over an OffscreenCanvas 2d context, for Workers (no
|
|
83
|
+
* `document`); undefined where OffscreenCanvas is missing or unusable (Node).
|
|
84
|
+
* @see plans/async-api/DESIGN.md D5
|
|
85
|
+
*/
|
|
86
|
+
function offscreenMeasurer(): CanvasTextMeasurer | undefined {
|
|
87
|
+
if (typeof OffscreenCanvas === 'undefined') return undefined;
|
|
88
|
+
try {
|
|
89
|
+
const ctx2d = new OffscreenCanvas(0, 0).getContext('2d');
|
|
90
|
+
return ctx2d ? new CanvasTextMeasurer(ctx2d) : undefined;
|
|
91
|
+
} catch {
|
|
92
|
+
return undefined; // OffscreenCanvas present but no 2d context
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
80
96
|
/** Resolve the text measurer for this render (see resolution order above). */
|
|
81
97
|
export function createMeasurer(): TextMeasurer {
|
|
82
98
|
if (override) return override;
|
|
@@ -88,6 +104,9 @@ export function createMeasurer(): TextMeasurer {
|
|
|
88
104
|
const ctx2d = document.createElement('canvas').getContext('2d');
|
|
89
105
|
if (ctx2d) return new CanvasTextMeasurer(ctx2d);
|
|
90
106
|
} catch { /* canvas unavailable */ }
|
|
107
|
+
} else {
|
|
108
|
+
const offscreen = offscreenMeasurer(); // Worker: no document, same fonts API
|
|
109
|
+
if (offscreen) return offscreen;
|
|
91
110
|
}
|
|
92
111
|
adviseHostFaithful(); // Node, no host canvas → built-in deterministic metrics
|
|
93
112
|
// Node default = EstimateTextMeasurer: the deterministic reference that matches
|
|
@@ -285,7 +285,7 @@ export class CanvasTextMeasurer implements TextMeasurer {
|
|
|
285
285
|
*/
|
|
286
286
|
private readonly fonts = new Map<string, string>();
|
|
287
287
|
|
|
288
|
-
constructor(private readonly ctx: CanvasRenderingContext2D) {}
|
|
288
|
+
constructor(private readonly ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D) {}
|
|
289
289
|
|
|
290
290
|
measure(
|
|
291
291
|
text: string,
|
|
@@ -16,6 +16,7 @@ import { VIRTUAL } from './fastgr.js';
|
|
|
16
16
|
import { IGNORED } from './rank.js';
|
|
17
17
|
import { lateDouble } from '../../common/nodeinit.js';
|
|
18
18
|
import { nodesInSeq } from './decomp.js';
|
|
19
|
+
import { RADIANS } from '../../common/arith.js';
|
|
19
20
|
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
21
22
|
// TextLabel interface (minimal — full type deferred to Batch 5b)
|
|
@@ -286,7 +287,7 @@ function writePortlabelPos(
|
|
|
286
287
|
distanceStr: string,
|
|
287
288
|
): void {
|
|
288
289
|
const angleDeg = lateDouble(angleStr, PORT_LABEL_ANGLE, -180.0);
|
|
289
|
-
const angle = Math.atan2(pf.y - pe.y, pf.x - pe.x) + (
|
|
290
|
+
const angle = Math.atan2(pf.y - pe.y, pf.x - pe.x) + RADIANS(angleDeg);
|
|
290
291
|
const distMult = lateDouble(distanceStr || undefined, 1.0, 0.0);
|
|
291
292
|
const dist = PORT_LABEL_DISTANCE * distMult;
|
|
292
293
|
l.pos.x = pe.x + dist * Math.cos(angle);
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import type { Graph } from '../../model/graph.js';
|
|
15
15
|
import type { Node } from '../../model/node.js';
|
|
16
16
|
import { aggetGraph } from './fdp-model.js';
|
|
17
|
+
import { RADIANS } from '../../common/arith.js';
|
|
17
18
|
|
|
18
19
|
/** mapbool for the "normalize" attr. @see lib/common/utils.c:mapbool */
|
|
19
20
|
function mapbool(s: string): boolean {
|
|
@@ -34,7 +35,7 @@ function angleSet(g: Graph): number | null {
|
|
|
34
35
|
if (a === undefined || a === '') return null;
|
|
35
36
|
const ang = parseAngle(a);
|
|
36
37
|
if (ang === null) return null;
|
|
37
|
-
return (wrapDegrees(ang)
|
|
38
|
+
return RADIANS(wrapDegrees(ang));
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
/** strtod (leading whitespace, sign) with the mapbool fallback of angleSet. */
|