@knowvah/dot-engine 1.2.2 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/cdt/bag.d.ts +6 -4
  2. package/dist/cdt/bag.d.ts.map +1 -1
  3. package/dist/common/arrows-shapes-poly.d.ts +1 -1
  4. package/dist/common/arrows-shapes-poly.d.ts.map +1 -1
  5. package/dist/common/arrows-shapes.d.ts +2 -2
  6. package/dist/common/arrows-shapes.d.ts.map +1 -1
  7. package/dist/common/htmltable-lex.d.ts.map +1 -1
  8. package/dist/common/record.d.ts.map +1 -1
  9. package/dist/index.js +2013 -2008
  10. package/dist/index.js.map +4 -4
  11. package/dist/layout/dot/edge-route-arrow.d.ts +8 -0
  12. package/dist/layout/dot/edge-route-arrow.d.ts.map +1 -1
  13. package/dist/model/geom.d.ts +5 -0
  14. package/dist/model/geom.d.ts.map +1 -1
  15. package/dist/render.js +95 -90
  16. package/dist/render.js.map +4 -4
  17. package/dist/xdot/parse.d.ts.map +1 -1
  18. package/package.json +2 -1
  19. package/src/cdt/bag.ts +30 -52
  20. package/src/common/arrows-shapes-poly.ts +20 -1
  21. package/src/common/arrows-shapes.ts +6 -2
  22. package/src/common/htmltable-lex.ts +17 -4
  23. package/src/common/htmltable-parse.ts +1 -1
  24. package/src/common/record.ts +3 -5
  25. package/src/layout/dot/edge-route-arrow.ts +7 -2
  26. package/src/model/geom.ts +14 -0
  27. package/src/xdot/parse.ts +3 -1
  28. package/dist/gvc/textlayout.d.ts +0 -33
  29. package/dist/gvc/textlayout.d.ts.map +0 -1
  30. package/dist/layout/osage/layout.d.ts +0 -49
  31. package/dist/layout/osage/layout.d.ts.map +0 -1
  32. package/dist/layout/pack/test-helpers.d.ts +0 -7
  33. package/dist/layout/pack/test-helpers.d.ts.map +0 -1
  34. package/dist/layout/patchwork/tree-node.d.ts +0 -46
  35. package/dist/layout/patchwork/tree-node.d.ts.map +0 -1
  36. package/dist/xdot/lex.d.ts +0 -22
  37. package/dist/xdot/lex.d.ts.map +0 -1
  38. package/src/gvc/textlayout.ts +0 -44
  39. package/src/layout/osage/layout.ts +0 -254
  40. package/src/layout/pack/test-helpers.ts +0 -46
  41. package/src/layout/patchwork/tree-node.ts +0 -120
  42. package/src/xdot/lex.ts +0 -151
@@ -1 +1 @@
1
- {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/xdot/parse.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,IAAI,EAAU,QAAQ,EAAE,YAAY,EACf,SAAS,EACC,WAAW,EAC3C,MAAM,YAAY,CAAC;AAGpB,KAAK,EAAE,CAAC,CAAC,IAAI;IAAE,GAAG,EAAE,CAAC,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC;AAkU5C,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAyC;AACxG,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,CAA6C;AACpH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC,CAA2C;AAC1G,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,SAAS,CAAC,CAAgD;AACvH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAE5G;AACD,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAE1F;AACD,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAoC;AACrF,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAyC"}
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/xdot/parse.ts"],"names":[],"mappings":"AACA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,IAAI,EAAU,QAAQ,EAAE,YAAY,EACf,SAAS,EACC,WAAW,EAC3C,MAAM,YAAY,CAAC;AAGpB,KAAK,EAAE,CAAC,CAAC,IAAI;IAAE,GAAG,EAAE,CAAC,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC;AAoU5C,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAyC;AACxG,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,CAA6C;AACpH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC,CAA2C;AAC1G,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,SAAS,CAAC,CAAgD;AACvH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAE5G;AACD,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAE1F;AACD,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAoC;AACrF,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAAyC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knowvah/dot-engine",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
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",
@@ -103,6 +103,7 @@
103
103
  "fontkit": "^2.0.4",
104
104
  "peggy": "^5.0.0",
105
105
  "semantic-release": "^25.0.8",
106
+ "tsx": "^4.23.1",
106
107
  "typedoc": "^0.28.20",
107
108
  "typedoc-plugin-markdown": "^4.12.0",
108
109
  "typedoc-vitepress-theme": "^1.1.3",
package/src/cdt/bag.ts CHANGED
@@ -3,10 +3,12 @@
3
3
  * DtBag — ordered multiset (Dtobag) backed by a splay tree.
4
4
  *
5
5
  * Unlike DtSplay (DT_OSET), DtBag allows duplicate keys. On insert of a
6
- * duplicate, the new node is inserted immediately to the LEFT of the
7
- * matching node (mirrors C dttree.c DT_OBAG insert: new node placed in
8
- * link.left, i.e. the right partition, becoming the successor of the
9
- * matching group — faithfully ported from dttree.c lines 226-230).
6
+ * duplicate, the matched (old) node is threaded onto the new node's RIGHT
7
+ * subtree — not the left, as an earlier version of this comment claimed.
8
+ * C dttree.c DT_INSERT's DT_OBAG found-branch does
9
+ * `root->left = NULL; root->right = link.left; link.left = root;`
10
+ * (dttree.c:223-231): the old matched node is pushed onto the head of the
11
+ * RIGHT partition, which becomes the new root's `.right` subtree.
10
12
  *
11
13
  * Iteration (first/next) produces ascending comparator order. Among
12
14
  * equal-key nodes the sub-order is the deterministic order CDT's Dtobag
@@ -32,50 +34,20 @@ import type { SplayNode } from "./splay-core.js";
32
34
  // ---------------------------------------------------------------------------
33
35
 
34
36
  /**
35
- * Walk left-spine from `start` to find a node whose `.obj === target`.
36
- * All nodes on the spine share the same key.
37
- * Returns [node, parent] or [null, null] when not found.
38
- */
39
- function findByIdentity<T, K>(
40
- start: SplayNode<T>,
41
- target: T,
42
- key: K,
43
- keyOf: KeyOf<T, K>,
44
- compare: Comparator<K>,
45
- ): [SplayNode<T> | null, SplayNode<T> | null] {
46
- let cur: SplayNode<T> | null = start;
47
- let parent: SplayNode<T> | null = null;
48
- while (cur !== null) {
49
- if (cur.obj === target) return [cur, parent];
50
- if (compare(key, keyOf(cur.obj)) !== 0) return [null, null];
51
- parent = cur;
52
- cur = cur.left;
53
- }
54
- return [null, null];
55
- }
56
-
57
- /**
58
- * Unlink `cur` from the tree. `parent` is its direct parent (null → root).
37
+ * Remove `root` itself from the tree, joining its two subtrees. delete()
38
+ * always walks the equal-key duplicate group (via splay + next()) until the
39
+ * target identity is splayed to root, so unlinking only ever needs to
40
+ * handle the "match is at root" case — mirrors dttree.c:211-222 dt_delete's
41
+ * reassembly (`l->right = root->left; r->left = root->right; ...`).
59
42
  * Returns the new root.
60
43
  */
61
- function unlinkNode<T>(
62
- root: SplayNode<T>,
63
- cur: SplayNode<T>,
64
- parent: SplayNode<T> | null,
65
- ): SplayNode<T> | null {
66
- const left = cur.left;
67
- const right = cur.right;
68
- let replacement: SplayNode<T> | null;
69
- if (left === null) {
70
- replacement = right;
71
- } else {
72
- const maxLeft = splayMax(left);
73
- maxLeft.right = right;
74
- replacement = maxLeft;
75
- }
76
- if (parent === null) return replacement;
77
- parent.left = replacement;
78
- return root;
44
+ function removeRoot<T>(root: SplayNode<T>): SplayNode<T> | null {
45
+ const left = root.left;
46
+ const right = root.right;
47
+ if (left === null) return right;
48
+ const maxLeft = splayMax(left);
49
+ maxLeft.right = right;
50
+ return maxLeft;
79
51
  }
80
52
 
81
53
  // ---------------------------------------------------------------------------
@@ -123,12 +95,18 @@ export class DtBag<T, K = T> {
123
95
  const key = this._keyOf(obj);
124
96
  this._root = splay(this._root, key, this._keyOf, this._compare);
125
97
  if (this._compare(key, this._keyOf(this._root.obj)) !== 0) return false;
126
- const [cur, parent] = findByIdentity(
127
- this._root, obj, key, this._keyOf, this._compare,
128
- );
129
- if (cur === null) return false;
130
- const newRoot = unlinkNode(this._root, cur, parent);
131
- this._root = newRoot;
98
+ // Mirror dttree.c:67-79's DT_OBAG DELETE/DETACH pre-pass: dtsearch
99
+ // splays an equal-key node to root, then dtnext (also splaying) walks
100
+ // the equal-key duplicate group by pointer identity until it finds
101
+ // `obj`. Each next() call re-splays `this._root`, mirroring C's
102
+ // mutate-on-lookup contract.
103
+ while (this._root.obj !== obj) {
104
+ const nxt = this.next(this._root.obj);
105
+ if (nxt === undefined || this._compare(key, this._keyOf(nxt)) !== 0) {
106
+ return false;
107
+ }
108
+ }
109
+ this._root = removeRoot(this._root);
132
110
  this._size--;
133
111
  return true;
134
112
  }
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import type { Point } from '../model/geom.js';
12
+ import { ccwrotatepf, cwrotatepf } from '../model/geom.js';
12
13
  import type { GenResult } from './arrows-shapes-util.js';
13
14
  import {
14
15
  vsub, vadd, miterShape, negUnit, backwardDelta, axialProjection,
@@ -217,7 +218,25 @@ function curveControlPoints(p: Point, v: Point, w: Point, flag: number): Point[]
217
218
  }
218
219
 
219
220
  /** @see lib/common/arrows.c:arrow_type_curve */
220
- export function genCurve(p0: Point, u: Point, _arrowsize: number, penwidth: number, flag: number): GenResult {
221
+ export function genCurve(
222
+ p0: Point, u: Point, _arrowsize: number, penwidth: number, flag: number,
223
+ rankdir = 0,
224
+ ): GenResult {
225
+ // C generates arrowheads at RENDER time, in the final (post-rankdir) frame;
226
+ // the port generates during routing and postproc rotates the stored ops.
227
+ // That round-trip is exact for every other generator, but this formula is
228
+ // not rotation-equivariant (0.95 shortens the x arm only; the y arm comes
229
+ // from AF0/AF3 verbatim), so run it in the final frame and rotate back.
230
+ // The rankdir maps are pure sign/swaps — rankdir 0 (TB) is untouched.
231
+ if (rankdir !== 0) {
232
+ const rot = rankdir * 90;
233
+ const fin = genCurve(ccwrotatepf(p0, rot), ccwrotatepf(u, rot), _arrowsize, penwidth, flag);
234
+ return {
235
+ ops: fin.ops.map((op) =>
236
+ 'points' in op ? { ...op, points: op.points.map((pt) => cwrotatepf(pt, rot)) } : op),
237
+ q: cwrotatepf(fin.q, rot),
238
+ };
239
+ }
221
240
  const arrowwidth = penwidth > 4 ? (0.5 * penwidth) / 4 : 0.5;
222
241
  const noShift = (flag & ARR_MOD_INV) || (u.x === 0 && u.y === 0);
223
242
  const p = noShift ? p0 : vsub(p0, backwardDelta(u, penwidth));
@@ -319,6 +319,7 @@ export function arrowLength(comps: ResolvedArrow[], arrowsize: number, penwidth:
319
319
  */
320
320
  export function dispatchSimple(
321
321
  r: ResolvedArrow, tip: Point, dir: Point, arrowsize: number, penwidth: number,
322
+ rankdir = 0,
322
323
  ): GenResult {
323
324
  const flag = arrowFlag(r);
324
325
  const u = componentU(dir, r.lenfact, arrowsize);
@@ -328,7 +329,9 @@ export function dispatchSimple(
328
329
  case ARR_TYPE_BOX: return genBox(tip, u, arrowsize, penwidth, flag);
329
330
  case ARR_TYPE_DIAMOND: return genDiamond(tip, u, arrowsize, penwidth, flag);
330
331
  case ARR_TYPE_DOT: return genDot(tip, u, arrowsize, penwidth, flag);
331
- case ARR_TYPE_CURVE: return genCurve(tip, u, arrowsize, penwidth, flag);
332
+ // curve is the one generator that is not rotation-equivariant; it needs
333
+ // the rankdir to reproduce C's render-time (final-frame) generation.
334
+ case ARR_TYPE_CURVE: return genCurve(tip, u, arrowsize, penwidth, flag, rankdir);
332
335
  case ARR_TYPE_GAP: return genGap(tip, u, arrowsize, penwidth, flag);
333
336
  case ARR_TYPE_NORM:
334
337
  default:
@@ -345,6 +348,7 @@ export function dispatchSimple(
345
348
  */
346
349
  export function arrowDrawOps(
347
350
  comps: ResolvedArrow[], tip: Point, dir: Point, arrowsize: number, penwidth: number,
351
+ rankdir = 0,
348
352
  ): ArrowDrawOp[] {
349
353
  const ops: ArrowDrawOp[] = [];
350
354
  let p = tip;
@@ -352,7 +356,7 @@ export function arrowDrawOps(
352
356
  for (let i = 0; i < n; i++) {
353
357
  const r = comps[i];
354
358
  if ((r.type & ARR_TYPE_MASK) === ARR_TYPE_NONE) break;
355
- const res = dispatchSimple(r, p, dir, arrowsize, penwidth);
359
+ const res = dispatchSimple(r, p, dir, arrowsize, penwidth, rankdir);
356
360
  ops.push(...res.ops);
357
361
  p = res.q;
358
362
  }
@@ -66,13 +66,25 @@ function parseAttrs(raw: string) {
66
66
  return out;
67
67
  }
68
68
 
69
+ /**
70
+ * TH is a row-boundary synonym for TR, not a cell tag: the C lexer
71
+ * dispatches both open and close TH identically to TR (T_row/T_end_row).
72
+ * Normalizing here — the single point where tags become tokens — means
73
+ * every downstream consumer (parser) sees TH and TR as structurally
74
+ * identical, mirroring the C lexer's dispatch exactly.
75
+ * @see lib/common/htmllex.c:614,669 (startElement/endElement)
76
+ */
77
+ function normalizeTag(tag: string): string {
78
+ return tag === 'TH' ? 'TR' : tag;
79
+ }
80
+
69
81
  /** Parse close tag. @see lib/common/htmllex.c:endElement */
70
82
  function parseCloseToken(inner: string) {
71
83
  const body = inner.slice(1).trim();
72
84
  const sp = body.search(/\s/);
73
85
  const tag = (sp === -1 ? body : body.slice(0, sp)).toUpperCase();
74
86
  checkTag(tag);
75
- return { type: 'close' as const, tag };
87
+ return { type: 'close' as const, tag: normalizeTag(tag) };
76
88
  }
77
89
 
78
90
  /** @see lib/common/htmllex.c:characterData */
@@ -119,12 +131,13 @@ function scanTag(src: string, start: number, tokens: Token[]) {
119
131
  function parseOpenToken(inner: string) {
120
132
  const sp = inner.search(/\s/);
121
133
  if (sp === -1) {
122
- checkTag(inner.toUpperCase());
123
- return { type: 'open' as const, tag: inner.toUpperCase(), attrs: parseAttrs('') };
134
+ const tag = inner.toUpperCase();
135
+ checkTag(tag);
136
+ return { type: 'open' as const, tag: normalizeTag(tag), attrs: parseAttrs('') };
124
137
  }
125
138
  const tag = inner.slice(0, sp).toUpperCase();
126
139
  checkTag(tag);
127
- return { type: 'open' as const, tag, attrs: parseAttrs(inner.slice(sp + 1)) };
140
+ return { type: 'open' as const, tag: normalizeTag(tag), attrs: parseAttrs(inner.slice(sp + 1)) };
128
141
  }
129
142
 
130
143
  /** @see lib/common/htmllex.c:findNext */
@@ -362,7 +362,7 @@ const parseCell = (
362
362
  const processRowToken = (
363
363
  s: ParseState, t: Token, cells: HtmlCell[], fontStack: FontState[],
364
364
  ): void => {
365
- if (t.type === 'open' && (t.tag === 'TD' || t.tag === 'TH')) {
365
+ if (t.type === 'open' && t.tag === 'TD') {
366
366
  consume(s);
367
367
  cells.push(parseCell(s, t.attrs, fontStack));
368
368
  return;
@@ -25,6 +25,7 @@ import { renderLabel, applyNodeStyle } from './poly-gencode.js';
25
25
  import { transformPoint } from '../gvc/device.js';
26
26
  import { emitRoundedBezier } from './poly-shapes.js';
27
27
  import { parseStyleFlags } from './style-resolve.js';
28
+ import { mapbool } from '../layout/dot/rank.js';
28
29
 
29
30
  // Parse-mode bits. @see lib/common/shapes.c
30
31
  const HASTEXT = 1;
@@ -344,12 +345,9 @@ export function posReclbl(f: FieldT, ul: Point, sides: number): void {
344
345
  // record_init
345
346
  // ---------------------------------------------------------------------------
346
347
 
347
- /** mapbool for attr strings. @see lib/common/utils.c:mapbool */
348
+ /** mapbool for attr strings. @see lib/common/utils.c:mapBool */
348
349
  function attrBool(n: Node, g: Graph, key: string): boolean {
349
- const s = nodeAttr(n, g, key);
350
- if (!s || s.toLowerCase() === 'false' || s.toLowerCase() === 'no') return false;
351
- if (s.toLowerCase() === 'true' || s.toLowerCase() === 'yes') return true;
352
- return parseInt(s, 10) !== 0;
350
+ return mapbool(nodeAttr(n, g, key));
353
351
  }
354
352
 
355
353
  function recAttrSize(n: Node, g: Graph): Point {
@@ -56,7 +56,11 @@ export function arrowheadPolygon(arrowTip: Point, arrowDir: Point, penwidth = 1.
56
56
  // ---------------------------------------------------------------------------
57
57
 
58
58
  /** Minimal edge shape exposing the arrow-related attributes. */
59
- interface ArrowAttrEdge { attrs: Map<string, string> }
59
+ interface ArrowAttrEdge {
60
+ attrs: Map<string, string>;
61
+ /** Present on real edges; the curve generator needs the rankdir frame. */
62
+ tail?: { root?: { info?: { rankdir?: number } } };
63
+ }
60
64
 
61
65
  /** The arrowhead/arrowtail name for an edge end (default "normal"). */
62
66
  export function edgeArrowName(e: ArrowAttrEdge, end: 'head' | 'tail'): string {
@@ -81,7 +85,8 @@ export function arrowDrawOpsForEnd(
81
85
  e: ArrowAttrEdge, end: 'head' | 'tail', tip: Point, dir: Point, penwidth: number,
82
86
  ): ArrowDrawOp[] {
83
87
  const comps = parseArrow(edgeArrowName(e, end)).map(resolveArrowType);
88
+ const rankdir = (e.tail?.root?.info?.rankdir ?? 0) & 0x3;
84
89
  // Pass the RAW direction (not normalized): componentU reproduces arrow_gen's
85
90
  // EPSILON normalization, which is relative to the raw shaft magnitude.
86
- return arrowDrawOps(comps, tip, dir, edgeArrowsize(e), penwidth);
91
+ return arrowDrawOps(comps, tip, dir, edgeArrowsize(e), penwidth, rankdir);
87
92
  }
package/src/model/geom.ts CHANGED
@@ -144,6 +144,20 @@ export function ccwrotatepf(p: Point, ccwrot: number): Point {
144
144
  }
145
145
  }
146
146
 
147
+ /**
148
+ * Clockwise counterpart — the exact inverse of {@link ccwrotatepf} per case.
149
+ * @see lib/common/geom.c:cwrotatepf
150
+ */
151
+ export function cwrotatepf(p: Point, cwrot: number): Point {
152
+ switch (cwrot) {
153
+ case 0: return p;
154
+ case 90: return { x: p.y, y: -p.x };
155
+ case 180: return { x: p.x, y: -p.y };
156
+ case 270: return { x: p.y, y: p.x };
157
+ default: return p;
158
+ }
159
+ }
160
+
147
161
  /**
148
162
  * Returns true if box `b0` completely contains box `b1`.
149
163
  *
package/src/xdot/parse.ts CHANGED
@@ -122,7 +122,9 @@ class XdotParser {
122
122
  let accounted = 0;
123
123
  let j = 0;
124
124
  while (accounted < len) {
125
- if (cur + j >= s.length) break; // C strncpy semantics: stop at end of input
125
+ // C fails hard the moment input runs out before the byte budget is
126
+ // satisfied (`s[j] == '\0'` mid-loop). @see xdot/xdot.c:139-141.
127
+ if (cur + j >= s.length) return null;
126
128
  const ch = s[cur + j];
127
129
  const prev = j > 0 ? s[cur + j - 1] : "";
128
130
  out += ch;
@@ -1,33 +0,0 @@
1
- /**
2
- * Text layout selection and measurement.
3
- *
4
- * Ports gvtextlayout_select and gvtextlayout from lib/gvc/gvtextlayout.c,
5
- * simplified for the TypeScript model where the text-layout engine is always
6
- * the context's registered TextMeasurer (no dynamic plugin loading).
7
- *
8
- * @see lib/gvc/gvtextlayout.c:gvtextlayout_select
9
- * @see lib/gvc/gvtextlayout.c:gvtextlayout
10
- */
11
- import type { GvcContext } from './context.js';
12
- import type { TextMeasurer } from '../common/textmeasure.js';
13
- import type { TextSpan } from '../common/emit-types.js';
14
- /**
15
- * Return the text measurer registered on ctx.
16
- *
17
- * Replaces gvtextlayout_select / plugin load with direct access to the
18
- * context's measurer, per AD-2 (static registration, no libltdl).
19
- *
20
- * @see lib/gvc/gvtextlayout.c:gvtextlayout_select
21
- */
22
- export declare function selectTextLayout(ctx: GvcContext): TextMeasurer;
23
- /**
24
- * Measure a text span and write the result into span.size.
25
- *
26
- * Mirrors gvtextlayout: calls the engine's textlayout method to populate
27
- * the size field on the span. Falls back to 'Times-Roman' / 14pt when
28
- * fontName or fontSize are unspecified.
29
- *
30
- * @see lib/gvc/gvtextlayout.c:gvtextlayout
31
- */
32
- export declare function measure(span: TextSpan, ctx: GvcContext): void;
33
- //# sourceMappingURL=textlayout.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"textlayout.d.ts","sourceRoot":"","sources":["../../src/gvc/textlayout.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAExD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,YAAY,CAE9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,UAAU,GAAG,IAAI,CAK7D"}
@@ -1,49 +0,0 @@
1
- /**
2
- * Osage recursive layout helpers.
3
- *
4
- * @see lib/osage/osageinit.c:layout
5
- * @see lib/osage/osageinit.c:reposition
6
- */
7
- import type { Graph } from '../../model/graph.js';
8
- import type { Box, Point } from '../../model/geom.js';
9
- import type { PackInfo } from '../pack/index.js';
10
- import type { ChildLists } from './types.js';
11
- export type { ChildLists };
12
- /** Build and populate a PackInfo from graph attributes. @see lib/osage/osageinit.c:layout */
13
- export declare const buildPackInfo: (g: Graph) => PackInfo;
14
- /**
15
- * Build child arrays for putRects.
16
- * @see lib/osage/osageinit.c:layout (child list construction)
17
- */
18
- export declare const buildChildLists: (g: Graph, nClust: number, nvs: number, p: PackInfo) => ChildLists;
19
- /**
20
- * Apply putRects offsets to child bbs/coords; return union bounding box.
21
- * @see lib/osage/osageinit.c:layout (offset application loop)
22
- */
23
- export declare const applyOffsets: (lists: ChildLists, pts: Point[]) => Box;
24
- /**
25
- * Widen union bb if the graph label is wider than packed content.
26
- * @see lib/osage/osageinit.c:layout (GD_label block)
27
- */
28
- export declare const applyLabelExpansion: (g: Graph, total: number, u: Box) => Box;
29
- /**
30
- * Add margin and label-border padding to the union bounding box.
31
- * @see lib/osage/osageinit.c:layout (margin/border block)
32
- */
33
- export declare const addMarginAndBorder: (g: Graph, margin: number, u: Box) => Box;
34
- /**
35
- * Subtract union.ll from all child positions so this cluster's LL = (0,0).
36
- * @see lib/osage/osageinit.c:layout (translate-to-origin loop)
37
- */
38
- export declare const translateToOrigin: (lists: ChildLists, u: Box) => void;
39
- /**
40
- * Recursively pack all direct children (subclusters + loose nodes) of g.
41
- * @see lib/osage/osageinit.c:layout
42
- */
43
- export declare const osageLayoutRec: (g: Graph, depth: number) => void;
44
- /**
45
- * Convert cluster-relative coordinates to absolute root-relative coordinates.
46
- * @see lib/osage/osageinit.c:reposition
47
- */
48
- export declare const osageReposition: (g: Graph, depth: number) => void;
49
- //# sourceMappingURL=layout.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"layout.d.ts","sourceRoot":"","sources":["../../../src/layout/osage/layout.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C,YAAY,EAAE,UAAU,EAAE,CAAC;AAsB3B,6FAA6F;AAC7F,eAAO,MAAM,aAAa,GAAI,GAAG,KAAK,KAAG,QAMxC,CAAC;AAiCF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,GAAG,KAAK,EAAE,QAAQ,MAAM,EAAE,KAAK,MAAM,EAAE,GAAG,QAAQ,KAAG,UAMpF,CAAC;AAyBF;;;GAGG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,UAAU,EAAE,KAAK,KAAK,EAAE,KAAG,GAQ9D,CAAC;AAMF;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,GAAG,KAAK,EAAE,OAAO,MAAM,EAAE,GAAG,GAAG,KAAG,GAOrE,CAAC;AAKF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,GAAG,KAAK,EAAE,QAAQ,MAAM,EAAE,GAAG,GAAG,KAAG,GAInE,CAAC;AAeJ;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,UAAU,EAAE,GAAG,GAAG,KAAG,IAG7D,CAAC;AA6BF;;;GAGG;AACH,eAAO,MAAM,cAAc,GAAI,GAAG,KAAK,EAAE,OAAO,MAAM,KAAG,IAQxD,CAAC;AAqBF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,GAAG,KAAK,EAAE,OAAO,MAAM,KAAG,IASzD,CAAC"}
@@ -1,7 +0,0 @@
1
- import type { Box, Point } from '../../model/geom.js';
2
- import { type PackInfo } from './types.js';
3
- export declare function box(llx: number, lly: number, urx: number, ury: number): Box;
4
- export declare function defaultPackInfo(overrides?: Partial<PackInfo>): PackInfo;
5
- export declare function applyPlaces(bbs: Box[], places: Point[]): Box[];
6
- export declare function assertNoOverlap(placed: Box[]): void;
7
- //# sourceMappingURL=test-helpers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"test-helpers.d.ts","sourceRoot":"","sources":["../../../src/layout/pack/test-helpers.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,EAAE,KAAK,QAAQ,EAAY,MAAM,YAAY,CAAC;AAErD,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,GAAG,CAE3E;AAED,wBAAgB,eAAe,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,QAAQ,CAMvE;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,EAAE,CAK9D;AAOD,wBAAgB,eAAe,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,CAUnD"}
@@ -1,46 +0,0 @@
1
- /**
2
- * Internal tree node and construction helpers for the patchwork layout.
3
- *
4
- * Mirrors treenode_t and mkTree from lib/patchwork/patchwork.c.
5
- *
6
- * @see lib/patchwork/patchwork.c:treenode_t
7
- * @see lib/patchwork/patchwork.c:mkTree
8
- */
9
- import type { Graph } from '../../model/graph.js';
10
- import type { Node } from '../../model/node.js';
11
- import type { Rectangle } from './tree-map.js';
12
- /** @see lib/patchwork/patchwork.c:treenode_t */
13
- export interface TreeNode {
14
- area: number;
15
- childArea: number;
16
- r: Rectangle;
17
- leftChild: TreeNode | null;
18
- rightSib: TreeNode | null;
19
- kind: 'graph' | 'node';
20
- ref: Graph | Node;
21
- nChildren: number;
22
- }
23
- /** @see lib/patchwork/patchwork.c:getArea */
24
- export declare function getArea(attrs: Map<string, string>): number;
25
- /** @see lib/patchwork/patchwork.c:fullArea (mp parameter) */
26
- export declare function getInset(attrs: Map<string, string>): number;
27
- /** wid = 2*inset + sqrt(childArea), area = wid². @see lib/patchwork/patchwork.c:fullArea */
28
- export declare function fullArea(childArea: number, inset: number): number;
29
- /** Mutable accumulator used during tree construction. */
30
- export interface ChildAccum {
31
- first: TreeNode | null;
32
- prev: TreeNode | null;
33
- area: number;
34
- nChildren: number;
35
- }
36
- /** Append all cluster children of g to accum. @see lib/patchwork/patchwork.c:mkTree */
37
- export declare function appendClusters(g: Graph, sparent: Map<Node, Graph>, accum: ChildAccum): void;
38
- /** Append unclaimed direct nodes of g to accum. @see lib/patchwork/patchwork.c:mkTree */
39
- export declare function appendNodes(g: Graph, sparent: Map<Node, Graph>, accum: ChildAccum): void;
40
- /**
41
- * Recursively build the internal TreeNode tree from a Graph hierarchy.
42
- * Nodes already owned by a cluster (sparent set) are skipped.
43
- * @see lib/patchwork/patchwork.c:mkTree
44
- */
45
- export declare function mkTree(g: Graph, sparent: Map<Node, Graph>): TreeNode;
46
- //# sourceMappingURL=tree-node.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tree-node.d.ts","sourceRoot":"","sources":["../../../src/layout/patchwork/tree-node.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAE/C,gDAAgD;AAChD,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,CAAC,EAAE,SAAS,CAAC;IACb,SAAS,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC;IACvB,GAAG,EAAE,KAAK,GAAG,IAAI,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAQD,6CAA6C;AAC7C,wBAAgB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAK1D;AAED,6DAA6D;AAC7D,wBAAgB,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAK3D;AAED,4FAA4F;AAC5F,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGjE;AAED,yDAAyD;AACzD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,QAAQ,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,uFAAuF;AACvF,wBAAgB,cAAc,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAS3F;AAED,yFAAyF;AACzF,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI,CAiBxF;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,QAAQ,CAmBpE"}
@@ -1,22 +0,0 @@
1
- /**
2
- * Low-level lexer primitives for xdot attribute string parsing.
3
- * Ported from ~/git/graphviz/lib/xdot/xdot.c helper functions.
4
- */
5
- import type { XdotAlign, XdotRect, XdotPolyline } from "./types.js";
6
- export type PR<T> = {
7
- val: T;
8
- pos: number;
9
- } | null;
10
- export declare function isWs(c: string): boolean;
11
- export declare function isDigit(c: string): boolean;
12
- export declare function isAlnum(c: string): boolean;
13
- export declare function skipWs(s: string, pos: number): number;
14
- export declare function consumeDigits(s: string, i: number): number;
15
- export declare function parseReal(s: string, pos: number): PR<number>;
16
- export declare function parseInt_(s: string, pos: number): PR<number>;
17
- export declare function parseUInt_(s: string, pos: number): PR<number>;
18
- export declare function parseRect(s: string, pos: number): PR<XdotRect>;
19
- export declare function parsePolyline(s: string, pos: number): PR<XdotPolyline>;
20
- export declare function parseString(s: string, pos: number): PR<string>;
21
- export declare function parseAlign(s: string, pos: number): PR<XdotAlign>;
22
- //# sourceMappingURL=lex.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"lex.d.ts","sourceRoot":"","sources":["../../src/xdot/lex.ts"],"names":[],"mappings":"AACA;;;GAGG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAa,QAAQ,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/E,MAAM,MAAM,EAAE,CAAC,CAAC,IAAI;IAAE,GAAG,EAAE,CAAC,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC;AAKnD,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAEvC;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAE1C;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAE1C;AAED,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAGrD;AAKD,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAG1D;AAaD,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC,CAU5D;AAKD,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC,CAQ5D;AAKD,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC,CAK7D;AAKD,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,QAAQ,CAAC,CAM9D;AAWD,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,CAUtE;AAUD,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC,CAiB9D;AAKD,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,SAAS,CAAC,CAIhE"}
@@ -1,44 +0,0 @@
1
- // SPDX-License-Identifier: EPL-2.0
2
-
3
- /**
4
- * Text layout selection and measurement.
5
- *
6
- * Ports gvtextlayout_select and gvtextlayout from lib/gvc/gvtextlayout.c,
7
- * simplified for the TypeScript model where the text-layout engine is always
8
- * the context's registered TextMeasurer (no dynamic plugin loading).
9
- *
10
- * @see lib/gvc/gvtextlayout.c:gvtextlayout_select
11
- * @see lib/gvc/gvtextlayout.c:gvtextlayout
12
- */
13
-
14
- import type { GvcContext } from './context.js';
15
- import type { TextMeasurer } from '../common/textmeasure.js';
16
- import type { TextSpan } from '../common/emit-types.js';
17
-
18
- /**
19
- * Return the text measurer registered on ctx.
20
- *
21
- * Replaces gvtextlayout_select / plugin load with direct access to the
22
- * context's measurer, per AD-2 (static registration, no libltdl).
23
- *
24
- * @see lib/gvc/gvtextlayout.c:gvtextlayout_select
25
- */
26
- export function selectTextLayout(ctx: GvcContext): TextMeasurer {
27
- return ctx.textMeasurer;
28
- }
29
-
30
- /**
31
- * Measure a text span and write the result into span.size.
32
- *
33
- * Mirrors gvtextlayout: calls the engine's textlayout method to populate
34
- * the size field on the span. Falls back to 'Times-Roman' / 14pt when
35
- * fontName or fontSize are unspecified.
36
- *
37
- * @see lib/gvc/gvtextlayout.c:gvtextlayout
38
- */
39
- export function measure(span: TextSpan, ctx: GvcContext): void {
40
- const fontName = span.fontName ?? 'Times-Roman';
41
- const fontSize = span.fontSize > 0 ? span.fontSize : 14;
42
- const size = ctx.textMeasurer.measure(span.str, fontName, fontSize);
43
- span.size = { x: size.w, y: size.h };
44
- }