@knowvah/dot-engine 1.2.1 → 1.2.3
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/common/arrows-shapes-poly.d.ts +1 -1
- package/dist/common/arrows-shapes-poly.d.ts.map +1 -1
- package/dist/common/arrows-shapes.d.ts +2 -2
- package/dist/common/arrows-shapes.d.ts.map +1 -1
- package/dist/common/nodeinit.d.ts.map +1 -1
- package/dist/common/poly-inside.d.ts +6 -0
- package/dist/common/poly-inside.d.ts.map +1 -1
- package/dist/common/poly-sizing.d.ts +8 -0
- package/dist/common/poly-sizing.d.ts.map +1 -1
- package/dist/common/poly-vertices.d.ts.map +1 -1
- package/dist/index.js +95 -24
- package/dist/index.js.map +3 -3
- package/dist/layout/dot/edge-route-arrow.d.ts +8 -0
- package/dist/layout/dot/edge-route-arrow.d.ts.map +1 -1
- package/dist/layout/dot/edge-route-helpers.d.ts.map +1 -1
- package/dist/model/geom.d.ts +5 -0
- package/dist/model/geom.d.ts.map +1 -1
- package/dist/render.js +115 -44
- package/dist/render.js.map +4 -4
- package/package.json +1 -1
- package/src/common/arrows-shapes-poly.ts +20 -1
- package/src/common/arrows-shapes.ts +6 -2
- package/src/common/nodeinit.ts +6 -2
- package/src/common/poly-inside.ts +21 -8
- package/src/common/poly-sizing.ts +10 -0
- package/src/common/poly-vertices.ts +37 -4
- package/src/layout/dot/edge-route-arrow.ts +7 -2
- package/src/layout/dot/edge-route-helpers.ts +19 -0
- package/src/model/geom.ts +14 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knowvah/dot-engine",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.3",
|
|
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",
|
|
@@ -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(
|
|
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
|
-
|
|
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
|
}
|
package/src/common/nodeinit.ts
CHANGED
|
@@ -201,6 +201,7 @@ function pointSizeResult(
|
|
|
201
201
|
return {
|
|
202
202
|
...gvNodesize(sz, sz, false),
|
|
203
203
|
outlineW: szOutline, outlineH: szOutline, baseW: sz0, baseH: sz0,
|
|
204
|
+
fixedshape: false, // C point_init never sets option.fixedshape
|
|
204
205
|
};
|
|
205
206
|
}
|
|
206
207
|
|
|
@@ -262,17 +263,20 @@ function initNodeFromLabel(n: Node, g: Graph, measurer: TextMeasurer): boolean {
|
|
|
262
263
|
// C poly_init installs the ATTR-RESOLVED polygon into ND_shape_info
|
|
263
264
|
// (sides/orientation/skew/distortion/peripheries/regular); edge
|
|
264
265
|
// clipping and rendering read it.
|
|
265
|
-
assignShapeInfo(n, effectivePolygon(poly, params));
|
|
266
|
+
assignShapeInfo(n, effectivePolygon(poly, params, sz.fixedshape));
|
|
266
267
|
return true;
|
|
267
268
|
}
|
|
268
269
|
|
|
269
270
|
/** The attr-resolved polygon poly_init stores. @see shapes.c:poly_init (poly->... assignments) */
|
|
270
|
-
function effectivePolygon(poly: PolygonT, p: PolySizeParams): PolygonT {
|
|
271
|
+
function effectivePolygon(poly: PolygonT, p: PolySizeParams, fixedshape: boolean): PolygonT {
|
|
271
272
|
let sides = p.sides;
|
|
272
273
|
// ellipses with distortion/skew become 120-gons
|
|
273
274
|
if (sides <= 2 && (p.distortion !== 0 || p.skew !== 0)) sides = 120;
|
|
274
275
|
return {
|
|
275
276
|
...poly,
|
|
277
|
+
// C: poly->option.fixedshape = true inside the fixedsize="shape" branch;
|
|
278
|
+
// poly-inside and computeVertices key the shape-box/width split on it.
|
|
279
|
+
option: { ...poly.option, fixedshape },
|
|
276
280
|
regular: p.regular,
|
|
277
281
|
peripheries: p.peripheries,
|
|
278
282
|
sides,
|
|
@@ -32,7 +32,7 @@ export function sameSide(p0: Point, p1: Point, L0: Point, L1: Point): boolean {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/** Vertex-extent box of a fixedshape polygon. @see lib/common/shapes.c:polyBB */
|
|
35
|
-
function fixedShapeSize(poly: PolygonT): { w: number; h: number } {
|
|
35
|
+
export function fixedShapeSize(poly: PolygonT): { w: number; h: number } {
|
|
36
36
|
let xmax = 0;
|
|
37
37
|
let ymax = 0;
|
|
38
38
|
for (const v of poly.vertices ?? []) {
|
|
@@ -50,13 +50,10 @@ interface InsideScale {
|
|
|
50
50
|
boxURy: number;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
/** Node size + outline extents (points) for the inside scale
|
|
54
|
-
|
|
53
|
+
/** Node size + outline extents (points) for the inside scale (non-fixedshape;
|
|
54
|
+
* the fixedshape branch short-circuits in insideScale). */
|
|
55
|
+
function insideDims(n: Node, xsize: number, ysize: number):
|
|
55
56
|
{ w: number; h: number; ow: number; oh: number } {
|
|
56
|
-
if (poly.option.fixedshape) {
|
|
57
|
-
const { w, h } = fixedShapeSize(poly);
|
|
58
|
-
return { w, h, ow: w, oh: h };
|
|
59
|
-
}
|
|
60
57
|
const w = 72 * (n.info.width || xsize / 72);
|
|
61
58
|
const h = 72 * (n.info.height || ysize / 72);
|
|
62
59
|
// Nodes that skipped poly_init (no measurer) have no outline dims.
|
|
@@ -67,10 +64,26 @@ function insideDims(n: Node, poly: PolygonT, xsize: number, ysize: number):
|
|
|
67
64
|
function insideScale(n: Node, poly: PolygonT): InsideScale {
|
|
68
65
|
// C swaps the node extents for flipped (LR/RL) layouts. @see poly_inside (GD_flip)
|
|
69
66
|
const flip = n.root.info.flip === true;
|
|
67
|
+
// C's fixedshape branch derives xsize/ysize from the polyBB shape box
|
|
68
|
+
// itself (NOT ND_lw/rw/ht, which are label-grown there), so the scale
|
|
69
|
+
// collapses to 1 (swapped under flip) and the boundary is the pure vertex
|
|
70
|
+
// extent with no penwidth outline growth.
|
|
71
|
+
// @see shapes.c:poly_inside (option.fixedshape branch)
|
|
72
|
+
if (poly.option.fixedshape) {
|
|
73
|
+
const { w, h } = fixedShapeSize(poly);
|
|
74
|
+
const xsize = flip ? h : w;
|
|
75
|
+
const ysize = flip ? w : h;
|
|
76
|
+
return {
|
|
77
|
+
scalex: xsize !== 0 ? w / xsize : w,
|
|
78
|
+
scaley: ysize !== 0 ? h / ysize : h,
|
|
79
|
+
boxURx: w / 2,
|
|
80
|
+
boxURy: h / 2,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
70
83
|
const lwrw = n.info.lw + n.info.rw;
|
|
71
84
|
const xsize = flip ? n.info.ht : lwrw;
|
|
72
85
|
const ysize = flip ? lwrw : n.info.ht;
|
|
73
|
-
const { w, h, ow, oh } = insideDims(n,
|
|
86
|
+
const { w, h, ow, oh } = insideDims(n, xsize, ysize);
|
|
74
87
|
return {
|
|
75
88
|
scalex: xsize !== 0 ? w / xsize : w,
|
|
76
89
|
scaley: ysize !== 0 ? h / ysize : h,
|
|
@@ -110,6 +110,14 @@ export interface PolySizeResult extends NodeSize {
|
|
|
110
110
|
*/
|
|
111
111
|
baseW: number;
|
|
112
112
|
baseH: number;
|
|
113
|
+
/**
|
|
114
|
+
* fixedsize="shape": the drawn shape stays at the attr box while
|
|
115
|
+
* width/height grow to max(label, shape). Installed on the polygon
|
|
116
|
+
* descriptor as `option.fixedshape`; vertex generation and insideness
|
|
117
|
+
* must read the base box, not the label-grown width.
|
|
118
|
+
* @see lib/common/shapes.c:poly_init (poly->option.fixedshape)
|
|
119
|
+
*/
|
|
120
|
+
fixedshape: boolean;
|
|
113
121
|
/**
|
|
114
122
|
* Justification borders — the space available for the label within the node
|
|
115
123
|
* box. `emit_label` reads this (not `dimen`) for `labelloc` top/bottom and
|
|
@@ -628,6 +636,7 @@ function assembleResult(
|
|
|
628
636
|
outlineW: Math.max(dimen.x, grown.outline.x),
|
|
629
637
|
outlineH: Math.max(dimen.y, grown.outline.y),
|
|
630
638
|
baseW: grown.base.x, baseH: grown.base.y,
|
|
639
|
+
fixedshape,
|
|
631
640
|
};
|
|
632
641
|
}
|
|
633
642
|
return {
|
|
@@ -635,5 +644,6 @@ function assembleResult(
|
|
|
635
644
|
outlineW: grown.outline.x,
|
|
636
645
|
outlineH: grown.outline.y,
|
|
637
646
|
baseW: grown.base.x, baseH: grown.base.y,
|
|
647
|
+
fixedshape,
|
|
638
648
|
};
|
|
639
649
|
}
|
|
@@ -100,11 +100,32 @@ export function generalPolyVertices(poly: PolygonT, w: number, h: number): Point
|
|
|
100
100
|
* which grows bb to the outermost ring after placing rings outward.
|
|
101
101
|
* @see lib/common/shapes.c:poly_init (ellipse peripheries, :2173-2184)
|
|
102
102
|
*/
|
|
103
|
-
function ellipseRings(
|
|
103
|
+
function ellipseRings(
|
|
104
|
+
w: number,
|
|
105
|
+
h: number,
|
|
106
|
+
peripheries: number,
|
|
107
|
+
base?: { w: number; h: number },
|
|
108
|
+
): Point[] {
|
|
109
|
+
if (base === undefined) {
|
|
110
|
+
const out: Point[] = [];
|
|
111
|
+
for (let j = 0; j < peripheries; j++) {
|
|
112
|
+
const inset = (peripheries - 1 - j) * GAP;
|
|
113
|
+
out.push(...ellipseVertices(w - 2 * inset, h - 2 * inset));
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
// C's forward order: the base ring at the shape box, each further ring
|
|
118
|
+
// stepping the half-extent OUTWARD by GAP. Needed under fixedshape, where
|
|
119
|
+
// the stored width is label-grown and the inset-from-width form above
|
|
120
|
+
// cannot recover the shape box. @see lib/common/shapes.c:poly_init
|
|
121
|
+
// (sides < 3: vertices[0/1] = ±bb/2, then P.x += GAP per periphery)
|
|
104
122
|
const out: Point[] = [];
|
|
123
|
+
let px = base.w / 2;
|
|
124
|
+
let py = base.h / 2;
|
|
105
125
|
for (let j = 0; j < peripheries; j++) {
|
|
106
|
-
|
|
107
|
-
|
|
126
|
+
out.push({ x: -px, y: -py }, { x: px, y: py });
|
|
127
|
+
px += GAP;
|
|
128
|
+
py += GAP;
|
|
108
129
|
}
|
|
109
130
|
return out;
|
|
110
131
|
}
|
|
@@ -221,13 +242,25 @@ export function computeVertices(
|
|
|
221
242
|
base?: { w: number; h: number },
|
|
222
243
|
): Point[] {
|
|
223
244
|
const sides = poly.sides;
|
|
245
|
+
// C builds vertices from the constrained shape box (bb) BEFORE the
|
|
246
|
+
// fixedshape branch sets ND_width/ND_height to max(label, bb)
|
|
247
|
+
// (shapes.c:poly_init tail), so under fixedsize="shape" the stored width
|
|
248
|
+
// is label-grown while the drawn shape stays at the attr box. Substitute
|
|
249
|
+
// the stored base box (= that bb) as the vertex source; every other case
|
|
250
|
+
// has width == shape box and is untouched.
|
|
251
|
+
if (poly.option.fixedshape && base !== undefined) {
|
|
252
|
+
w = base.w;
|
|
253
|
+
h = base.h;
|
|
254
|
+
}
|
|
224
255
|
// Cylinder is a 19-point bezier control polygon, not a regular 19-gon.
|
|
225
256
|
if (poly.option.shape === CYLINDER) return cylinderVertices(w, h);
|
|
226
257
|
// C generates one ring even for peripheries=0 (outp >= 1); the draw
|
|
227
258
|
// loop is what skips it. @see lib/common/shapes.c:poly_init (outp)
|
|
228
259
|
const peripheries = Math.max(poly.peripheries, 1);
|
|
229
260
|
if (poly.option.shape === STAR) return starRings({ w, h, base }, peripheries);
|
|
230
|
-
if (sides <= 2)
|
|
261
|
+
if (sides <= 2) {
|
|
262
|
+
return ellipseRings(w, h, peripheries, poly.option.fixedshape ? base : undefined);
|
|
263
|
+
}
|
|
231
264
|
// C's isBox test: right angles only (diamond = orientation 45).
|
|
232
265
|
if (sides === 4 && Math.abs(poly.orientation % 90) < 0.5
|
|
233
266
|
&& poly.distortion === 0 && poly.skew === 0) {
|
|
@@ -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 {
|
|
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
|
}
|
|
@@ -15,6 +15,8 @@ import type { Graph } from '../../model/graph.js';
|
|
|
15
15
|
import type { Node } from '../../model/node.js';
|
|
16
16
|
import type { Edge as GraphEdge } from '../../model/edge.js';
|
|
17
17
|
import { nodeAttr } from '../../common/poly-init.js';
|
|
18
|
+
import { fixedShapeSize } from '../../common/poly-inside.js';
|
|
19
|
+
import type { PolygonT } from '../../common/types.js';
|
|
18
20
|
import type { Point, Bezier, Spline } from '../../model/geom.js';
|
|
19
21
|
import { routeSpline } from '../../pathplan/route.js';
|
|
20
22
|
import type { Edge as BarrierEdge } from '../../pathplan/types.js';
|
|
@@ -69,6 +71,23 @@ export function nodeBoxOf(n: Node, g: Graph): NodeBox {
|
|
|
69
71
|
const rb = recordClipBox(n);
|
|
70
72
|
if (rb) return { center: n.info.coord, ...rb, isEllipse: false, penwidth };
|
|
71
73
|
}
|
|
74
|
+
// C poly_inside sizes the boundary from polyBB (the vertex extent) under
|
|
75
|
+
// fixedshape — ND_lw/rw/ht are label-grown there and must not clip the
|
|
76
|
+
// edge, and the boundary carries NO penwidth outline growth.
|
|
77
|
+
// @see lib/common/shapes.c:poly_inside (option.fixedshape branch)
|
|
78
|
+
const si = n.info.shape_info as PolygonT | undefined;
|
|
79
|
+
if (si?.option?.fixedshape === true && si.vertices) {
|
|
80
|
+
const ext = fixedShapeSize(si);
|
|
81
|
+
const flip = g.root.info.flip === true;
|
|
82
|
+
const w = flip ? ext.h : ext.w;
|
|
83
|
+
const h = flip ? ext.w : ext.h;
|
|
84
|
+
return {
|
|
85
|
+
center: n.info.coord,
|
|
86
|
+
lw: w / 2, rw: w / 2, ht: h,
|
|
87
|
+
isEllipse: ELLIPSE_SHAPES.has(shapeName),
|
|
88
|
+
penwidth: 0,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
72
91
|
return {
|
|
73
92
|
center: n.info.coord,
|
|
74
93
|
lw: n.info.lw > 0 ? n.info.lw : 27,
|
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
|
*
|