@weasel-js/geom 1.9.3 → 1.10.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/3d/index.js +1 -1
- package/dist/booleans/index.d.ts +51 -29
- package/dist/booleans/index.js +562 -482
- package/dist/booleans/index.js.map +1 -1
- package/dist/{chunk-7ROQRYOA.js → chunk-755Y4YZS.js} +6 -3
- package/dist/chunk-755Y4YZS.js.map +1 -0
- package/dist/{chunk-6T5I3PR5.js → chunk-MDT7F32A.js} +16 -5
- package/dist/chunk-MDT7F32A.js.map +1 -0
- package/dist/{chunk-HEZTRG2M.js → chunk-OXH3NOXC.js} +10 -3
- package/dist/chunk-OXH3NOXC.js.map +1 -0
- package/dist/{chunk-RQYCDPU5.js → chunk-WQMYOCFB.js} +27 -18
- package/dist/chunk-WQMYOCFB.js.map +1 -0
- package/dist/{chunk-HPVINMOM.js → chunk-XS6ERH3X.js} +2 -2
- package/dist/chunk-XS6ERH3X.js.map +1 -0
- package/dist/cubicMath-CFXbq6vp.d.ts +116 -0
- package/dist/curves/index.d.ts +1 -1
- package/dist/curves/index.js +18 -71
- package/dist/curves/index.js.map +1 -1
- package/dist/index.d.ts +612 -116
- package/dist/index.js +904 -112
- package/dist/index.js.map +1 -1
- package/dist/{path-D0CoudGM.d.ts → path-Bf9KeVxC.d.ts} +10 -11
- package/dist/tessellate/index.d.ts +8 -12
- package/dist/tessellate/index.js +36 -10
- package/dist/tessellate/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-6T5I3PR5.js.map +0 -1
- package/dist/chunk-7ROQRYOA.js.map +0 -1
- package/dist/chunk-HEZTRG2M.js.map +0 -1
- package/dist/chunk-HPVINMOM.js.map +0 -1
- package/dist/chunk-RQYCDPU5.js.map +0 -1
- package/dist/cubicMath-CpCvuEl8.d.ts +0 -37
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { P as Point } from './cubicMath-
|
|
3
|
-
export { c as cubicPointAt, f as fitCubicThroughDeletion, s as splitCubicAtT } from './cubicMath-
|
|
1
|
+
import { b as PathOpenFill, P as PolygonPath, c as PathFillRule, R as RectPath, a as Path } from './path-Bf9KeVxC.js';
|
|
2
|
+
import { P as Point, a as PenAnchor } from './cubicMath-CFXbq6vp.js';
|
|
3
|
+
export { b as anchorsToPath, c as countPathAnchors, d as cubicPointAt, f as fitCubicThroughDeletion, i as isAnchorSmooth, e as isStraightSegment, n as nearestSegmentT, p as pathToAnchors, s as segmentEnds, g as splitCubicAtT } from './cubicMath-CFXbq6vp.js';
|
|
4
4
|
export { PORT_REACH } from './nd/index.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -8,11 +8,12 @@ export { PORT_REACH } from './nd/index.js';
|
|
|
8
8
|
*
|
|
9
9
|
* Points are scalar pairs (px, py); there is no point struct. All math is
|
|
10
10
|
* f64 (JS-native). Epsilons are f32-SCALE and magnitude-relative, because
|
|
11
|
-
* stored coords are quantized to Float32 (~7 significant digits)
|
|
12
|
-
* geometry-kernel spec.
|
|
11
|
+
* stored coords are quantized to Float32 (~7 significant digits).
|
|
13
12
|
*/
|
|
14
13
|
/** Base relative epsilon, sized for Float32 storage (~1 part in 1e-6). */
|
|
15
14
|
declare const EPS = 0.000001;
|
|
15
|
+
/** `v` held to `lo`..`hi`. `lo` wins when the bounds cross; `NaN` passes through. */
|
|
16
|
+
declare function clamp(v: number, lo: number, hi: number): number;
|
|
16
17
|
/** 2D cross (wedge) product of vectors (ax,ay) and (bx,by). */
|
|
17
18
|
declare function cross(ax: number, ay: number, bx: number, by: number): number;
|
|
18
19
|
/** 2D dot product. */
|
|
@@ -31,16 +32,47 @@ declare function sign(n: number): -1 | 0 | 1;
|
|
|
31
32
|
*
|
|
32
33
|
* The exception is a guard against an exact algebraic value that a formula
|
|
33
34
|
* produces bit-for-bit: a homogeneous `w === 1`, a ray's `d === 0`. Those are
|
|
34
|
-
* testing a branch, not comparing coordinates
|
|
35
|
-
*
|
|
36
|
-
* runs, not how precisely it runs.
|
|
35
|
+
* testing a branch, not comparing coordinates. Widening one to `approxEq`
|
|
36
|
+
* would change which branch runs, not how precisely it runs.
|
|
37
37
|
*/
|
|
38
38
|
declare function approxEq(a: number, b: number, eps?: number): boolean;
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Viewport state. `(view.x, view.y)` is the world point currently rendered at
|
|
42
|
+
* the canvas top-left; `view.scale.x` / `view.scale.y` is pixels per world
|
|
43
|
+
* unit on each axis.
|
|
44
|
+
*
|
|
45
|
+
* A `View` names no orientation, so a kernel with a camera cannot express its
|
|
46
|
+
* viewport as one.
|
|
47
|
+
*/
|
|
48
|
+
interface View {
|
|
49
|
+
x: number;
|
|
50
|
+
y: number;
|
|
51
|
+
scale: {
|
|
52
|
+
x: number;
|
|
53
|
+
y: number;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The smallest zoom magnitude a view can hold, in `View.scale` units.
|
|
58
|
+
*
|
|
59
|
+
* A view's zoom is always finite and at least this far from 0. Nothing can be
|
|
60
|
+
* drawn or picked through a zoom of 0, and every screen↔world conversion
|
|
61
|
+
* divides by it, so it is ruled out where views are made rather than handled
|
|
62
|
+
* by each conversion.
|
|
63
|
+
*/
|
|
64
|
+
declare const ZOOM_FLOOR = 1e-9;
|
|
65
|
+
/**
|
|
66
|
+
* The one rule for a zoom magnitude: a finite number no smaller than
|
|
67
|
+
* {@link ZOOM_FLOOR}. Anything else (0, negative, NaN, ±Infinity) becomes the
|
|
68
|
+
* floor.
|
|
69
|
+
*/
|
|
70
|
+
declare function normalizeZoom(zoom: number): number;
|
|
71
|
+
|
|
40
72
|
/** `[a, b, c, d, e, f]`: `x' = a·x + c·y + e`, `y' = b·x + d·y + f`. */
|
|
41
73
|
type Mat3 = readonly [number, number, number, number, number, number];
|
|
42
74
|
/** The transform that leaves a point where it is. */
|
|
43
|
-
declare function identity(): Mat3;
|
|
75
|
+
declare function identity$1(): Mat3;
|
|
44
76
|
/** A pure translation by `(tx, ty)`. */
|
|
45
77
|
declare function translate(tx: number, ty: number): Mat3;
|
|
46
78
|
/** A pure scale about the origin. Scale about another point by composing
|
|
@@ -50,10 +82,10 @@ declare function scale(sx: number, sy: number): Mat3;
|
|
|
50
82
|
* coordinates (y down). */
|
|
51
83
|
declare function rotate(rad: number): Mat3;
|
|
52
84
|
/** Compose: result applies `n` first, then `m` (m·n). */
|
|
53
|
-
declare function multiply(m: Mat3, n: Mat3): Mat3;
|
|
85
|
+
declare function multiply$1(m: Mat3, n: Mat3): Mat3;
|
|
54
86
|
/** Inverse, or null when the matrix is singular, non-finite, or too
|
|
55
87
|
* ill-conditioned to invert meaningfully at its own scale. */
|
|
56
|
-
declare function invert(m: Mat3): Mat3 | null;
|
|
88
|
+
declare function invert$1(m: Mat3): Mat3 | null;
|
|
57
89
|
/** Apply to a point, returning a tuple. Cold-path use; hot loops inline. */
|
|
58
90
|
declare function applyToPoint(m: Mat3, x: number, y: number): [number, number];
|
|
59
91
|
/** Affine that maps source box (sx,sy,sw,sh) onto destination box (dx,dy,dw,dh). */
|
|
@@ -88,12 +120,12 @@ declare function rectToContour(x: number, y: number, w: number, h: number): Floa
|
|
|
88
120
|
|
|
89
121
|
/**
|
|
90
122
|
* SVG-style command-stream encoding — the kit's single declaration of the path
|
|
91
|
-
* opcodes.
|
|
123
|
+
* opcodes.
|
|
92
124
|
*
|
|
93
125
|
* The codes index a `Uint8Array` command stream whose coords live in a
|
|
94
126
|
* parallel float array, so a code declared anywhere but `PATH_COMMANDS` makes
|
|
95
127
|
* every walker that doesn't know it misread the coord stream from that command
|
|
96
|
-
* on — silently, for every path.
|
|
128
|
+
* on — silently, for every path.
|
|
97
129
|
*/
|
|
98
130
|
/** Opcode table: command code and the float coords it consumes. */
|
|
99
131
|
declare const PATH_COMMANDS: {
|
|
@@ -144,16 +176,17 @@ declare const PATH_CMD_LENGTHS: readonly number[];
|
|
|
144
176
|
declare function pathCommandCoordCount(cmd: number): number;
|
|
145
177
|
/**
|
|
146
178
|
* Visit each command with its coord offset and the pen position BEFORE the
|
|
147
|
-
* command consumes its coords (the segment start). The callback
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
179
|
+
* command consumes its coords (the segment start). The callback stops the
|
|
180
|
+
* walk by returning `false`. `Z` returns the pen to the subpath's opening `M`.
|
|
181
|
+
*
|
|
182
|
+
* A command following a `Z` without an intervening `M` opens a new subpath at
|
|
183
|
+
* the pen, as does a drawing command no `M` precedes at all. The visitor is
|
|
184
|
+
* told with `opensSubpath`, and has to start its contour at `(penX, penY)`
|
|
185
|
+
* then: there is no `M` to do it.
|
|
152
186
|
*
|
|
153
|
-
* A code `PATH_COMMANDS` does not declare throws once the visitor has seen it
|
|
154
|
-
* since the walker cannot tell how many coords it consumes.
|
|
187
|
+
* A code `PATH_COMMANDS` does not declare throws once the visitor has seen it.
|
|
155
188
|
*/
|
|
156
|
-
declare function forEachSegment(commands: ArrayLike<number>, coords: ArrayLike<number>, visit: (cmd: number, coordIndex: number, penX: number, penY: number, commandIndex: number) => void | boolean): void;
|
|
189
|
+
declare function forEachSegment(commands: ArrayLike<number>, coords: ArrayLike<number>, visit: (cmd: number, coordIndex: number, penX: number, penY: number, commandIndex: number, opensSubpath: boolean) => void | boolean): void;
|
|
157
190
|
|
|
158
191
|
/** Cubic Bezier point at parameter t (de Casteljau / Bernstein form). */
|
|
159
192
|
declare function cubicEvalAt(x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, t: number): [number, number];
|
|
@@ -236,13 +269,10 @@ declare function portCurvePoints(ax: number, ay: number, bx: number, by: number,
|
|
|
236
269
|
|
|
237
270
|
/**
|
|
238
271
|
* Bezier flattening — subdivide cubic and quadratic segments into polyline
|
|
239
|
-
* approximations within a flatness tolerance.
|
|
240
|
-
* (eventually) any kernel that doesn't want to special-case curves.
|
|
272
|
+
* approximations within a flatness tolerance.
|
|
241
273
|
*
|
|
242
|
-
* The flatness metric is the maximum
|
|
243
|
-
*
|
|
244
|
-
* below `tolerance`, the segment is "flat enough" and emitted as a single
|
|
245
|
-
* line segment.
|
|
274
|
+
* The flatness metric is the maximum distance from any control point to the
|
|
275
|
+
* chord between endpoints.
|
|
246
276
|
*
|
|
247
277
|
* Tolerance is in world units. 0.5 is a sensible default for screen-rate
|
|
248
278
|
* rendering at 1× zoom; consumers that zoom in heavily should pass a
|
|
@@ -258,16 +288,10 @@ declare function flattenCubic(x0: number, y0: number, x1: number, y1: number, x2
|
|
|
258
288
|
/** Recursively subdivide a quadratic bezier (P0, P1, P2). */
|
|
259
289
|
declare function flattenQuadratic(x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, tolerance: number, out: number[]): void;
|
|
260
290
|
/**
|
|
261
|
-
* Like `flattenCubic` but also appends
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
* to yield t ∈ (0, 1].
|
|
266
|
-
*
|
|
267
|
-
* The returned values are *cumulative distance from the segment start*, not
|
|
268
|
-
* normalized fractions. Two-pass design (accumulate, then divide) keeps the
|
|
269
|
-
* recursive splitter simple — it doesn't need to know the total length up
|
|
270
|
-
* front.
|
|
291
|
+
* Like `flattenCubic` but also appends to `arcOut`, for each new flattened
|
|
292
|
+
* point, its *cumulative distance from the segment start* — not a normalized
|
|
293
|
+
* fraction. The caller divides each by the segment's total flattened arc
|
|
294
|
+
* length to yield t ∈ (0, 1].
|
|
271
295
|
*/
|
|
272
296
|
declare function flattenCubicWithArcLen(x0: number, y0: number, x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, tolerance: number, out: number[], arcOut: number[]): number;
|
|
273
297
|
/** Flatten a quadratic bezier into line segments within `tolerance`, appending
|
|
@@ -295,6 +319,15 @@ declare function segmentsCross(ax: number, ay: number, bx: number, by: number, c
|
|
|
295
319
|
/** Squared distance from (px,py) to segment (ax,ay)-(bx,by), endpoint-clamped. */
|
|
296
320
|
declare function pointSegmentDist2(px: number, py: number, ax: number, ay: number, bx: number, by: number): number;
|
|
297
321
|
|
|
322
|
+
/** Set whether a fill closes an open subpath, for every path that names no
|
|
323
|
+
* `openFill` of its own. Starts at `'close'`. */
|
|
324
|
+
declare function setDefaultOpenFill(rule: PathOpenFill): void;
|
|
325
|
+
/** The rule a path with no `openFill` of its own follows. */
|
|
326
|
+
declare function defaultOpenFill(): PathOpenFill;
|
|
327
|
+
/** Whether `path`'s open subpaths fill, each closed by a straight edge back to
|
|
328
|
+
* its start. What painting, area, and the filled-region hit tests all ask. */
|
|
329
|
+
declare function fillsOpenSubpaths(path: PolygonPath): boolean;
|
|
330
|
+
|
|
298
331
|
/**
|
|
299
332
|
* Fluent builder for `PolygonPath`. Hides the `Uint8Array` / `Float32Array`
|
|
300
333
|
* encoding behind move/line/curve/close calls and a final `build()`. Use
|
|
@@ -378,29 +411,10 @@ declare function linePath(a: {
|
|
|
378
411
|
}): PolygonPath;
|
|
379
412
|
|
|
380
413
|
/**
|
|
381
|
-
* AABB
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
* `RectPath` is its own bounds — O(1). For `PolygonPath`, walk segment by
|
|
385
|
-
* segment, computing the tight bound per segment:
|
|
386
|
-
*
|
|
387
|
-
* - `M` / `L`: just the endpoint.
|
|
388
|
-
* - `Q`: quadratic Bezier — endpoints plus axis-aligned extrema (one
|
|
389
|
-
* possible inflection per axis).
|
|
390
|
-
* - `C`: cubic Bezier — endpoints plus axis-aligned extrema (up to two
|
|
391
|
-
* possible inflections per axis, found via the quadratic formula on
|
|
392
|
-
* the derivative).
|
|
393
|
-
*
|
|
394
|
-
* Control points are NOT included directly; they're used only to compute
|
|
395
|
-
* extrema that actually lie on the curve. This avoids the classic "AABB
|
|
396
|
-
* extends past where the curve visibly reaches" bug for curved paths whose
|
|
397
|
-
* control points poke outside the visible extent.
|
|
398
|
-
*
|
|
399
|
-
* Empty paths (no commands) return a zero-size rect anchored at the
|
|
400
|
-
* origin — the convention used elsewhere in the kit for missing geometry.
|
|
414
|
+
* AABB of a `Path`, returned as a `RectPath` for direct reuse with rect-fast-path machinery.
|
|
415
|
+
* Control points are NOT included directly; they're used only to compute extrema that actually
|
|
416
|
+
* lie on the curve. Empty paths (no commands) return a zero-size rect anchored at the origin.
|
|
401
417
|
*/
|
|
402
|
-
|
|
403
|
-
/** AABB of a `Path`, returned as a `RectPath` for direct reuse with rect-fast-path machinery. */
|
|
404
418
|
declare function boundsOfPath(path: Path): RectPath;
|
|
405
419
|
|
|
406
420
|
/**
|
|
@@ -438,18 +452,11 @@ declare function translatePath(path: Path, dx: number, dy: number): Path;
|
|
|
438
452
|
*/
|
|
439
453
|
declare function scalePathToBounds(path: Path, target: RectPath): Path;
|
|
440
454
|
|
|
441
|
-
/** Apply an affine `Mat3` to a path's geometry.
|
|
455
|
+
/** Apply an affine `Mat3` to a path's geometry. Never mutates the input.
|
|
442
456
|
*
|
|
443
|
-
* Axis-aligned maps (no rotation/shear
|
|
444
|
-
* `RectPath` a rect
|
|
445
|
-
*
|
|
446
|
-
* shear promotes the rect's four corners to a `PolygonPath`. Polygon paths map
|
|
447
|
-
* every coord via the kernel's `transformCoords`, preserving the command stream
|
|
448
|
-
* and `fillRule`. Never mutates the input.
|
|
449
|
-
*
|
|
450
|
-
* `Mat3` here is the 6-tuple `number[]` from `@weasel-js/geom` (DOMMatrix order:
|
|
451
|
-
* `[a, b, c, d, e, f]` where `x' = a·x + c·y + e`, `y' = b·x + d·y + f`),
|
|
452
|
-
* not the renderer's 9-element `Float32Array`. */
|
|
457
|
+
* Axis-aligned maps (no rotation/shear: `m[1]` and `m[2]` within 1e-9 of 0) keep a
|
|
458
|
+
* `RectPath` a rect, normalizing negative extent (mirror). Rotation or
|
|
459
|
+
* shear promotes the rect's four corners to a `PolygonPath`. */
|
|
453
460
|
declare function transformPath(path: Path, m: Mat3): Path;
|
|
454
461
|
|
|
455
462
|
/**
|
|
@@ -468,24 +475,24 @@ declare function composePath(parent: Path, child: Path): Path;
|
|
|
468
475
|
/** Inverse of `composePath` — undo `parent`'s translation from a world pose. */
|
|
469
476
|
declare function decomposePath(parent: Path, world: Path): Path;
|
|
470
477
|
|
|
471
|
-
/**
|
|
472
|
-
* Split a `PolygonPath` at each `M` command, returning one `PolygonPath` per
|
|
473
|
-
* subpath. Inverse-ish of compound-path construction: a multi-region
|
|
474
|
-
* (compound) path becomes N independent paths.
|
|
475
|
-
*
|
|
476
|
-
* `fillRule` is propagated unchanged to every output. Even-odd compound paths
|
|
477
|
-
* with interior "holes" lose the hole semantics — each subpath becomes its
|
|
478
|
-
* own filled region. (Same behavior as Illustrator's Release Compound Path.)
|
|
479
|
-
*
|
|
480
|
-
* If the input has zero `M` commands (which would already be malformed) the
|
|
481
|
-
* input is returned wrapped in a single-element array. A path with exactly
|
|
482
|
-
* one subpath is also returned as a single-element array — a trivial pass.
|
|
483
|
-
*/
|
|
484
|
-
|
|
485
478
|
/** Split a path into one path per subpath, cutting at each `M`. A path with a
|
|
486
|
-
* single subpath comes back as a one-element array.
|
|
479
|
+
* single subpath comes back as a one-element array. `fillRule` is propagated
|
|
480
|
+
* unchanged, so even-odd compound paths with interior "holes" lose the hole
|
|
481
|
+
* semantics — each subpath becomes its own filled region. */
|
|
487
482
|
declare function splitSubpaths(path: PolygonPath): PolygonPath[];
|
|
488
483
|
|
|
484
|
+
/** Whether any subpath of `path` lacks the `Z` that closes it. */
|
|
485
|
+
declare function hasOpenSubpath(path: Path): boolean;
|
|
486
|
+
/**
|
|
487
|
+
* `path` as two paths: the subpaths a `Z` closes, and those left open, each in
|
|
488
|
+
* the order `path` holds them and with its fill rules. A side with no subpaths
|
|
489
|
+
* is `null`. A fill that does not close an open subpath covers `closed` alone.
|
|
490
|
+
*/
|
|
491
|
+
declare function splitByClosure(path: PolygonPath): {
|
|
492
|
+
closed: PolygonPath | null;
|
|
493
|
+
open: PolygonPath | null;
|
|
494
|
+
};
|
|
495
|
+
|
|
489
496
|
/**
|
|
490
497
|
* Contour orientation: which way a path winds, and the same path wound the
|
|
491
498
|
* other way. Under `'nonzero'` two overlapping contours fill their overlap
|
|
@@ -497,7 +504,8 @@ declare function splitSubpaths(path: PolygonPath): PolygonPath[];
|
|
|
497
504
|
* Signed area enclosed by `path`, summed over its contours. Positive for a
|
|
498
505
|
* contour that turns clockwise on a y-down screen (the shoelace sign), so a
|
|
499
506
|
* hole wound against its outer contour subtracts. An open contour counts as
|
|
500
|
-
* closed by a straight edge back to its start
|
|
507
|
+
* closed by a straight edge back to its start where a fill closes it, and as
|
|
508
|
+
* nothing where one does not: see `fillsOpenSubpaths`.
|
|
501
509
|
* Curved segments are integrated exactly, not through their control polygon.
|
|
502
510
|
* A `RectPath` has no direction and counts as positive.
|
|
503
511
|
*/
|
|
@@ -513,31 +521,19 @@ declare function reversePath(path: PolygonPath): PolygonPath;
|
|
|
513
521
|
* `pathFromD` — build a weasel `Path` from an **SVG path-data string** (the
|
|
514
522
|
* value of SVG's `d` attribute, e.g. `"M0 0 L100 0 Z"`).
|
|
515
523
|
*
|
|
516
|
-
* This is the terse, declarative composition surface for geometry: a
|
|
517
|
-
* consumer authors a shape as `d` and hands it straight to a scene/op API,
|
|
518
|
-
* instead of chaining imperative builder calls. SVG `d` is deliberately the
|
|
519
|
-
* language — it's a standard every consumer already knows, so the kit gets
|
|
520
|
-
* path-language expressiveness without minting a new one. See
|
|
521
|
-
* `docs/conventions.md` ("Compose scenes in a terse path language").
|
|
522
|
-
*
|
|
523
524
|
* Lowers every supported command (M/m L/l H/h V/v C/c S/s Q/q T/t A/a Z/z)
|
|
524
525
|
* into weasel's `PathBuilder` primitives (move, line, cubic, quadratic,
|
|
525
|
-
* close).
|
|
526
|
-
*
|
|
527
|
-
*
|
|
528
|
-
* endpoint-parameterization formulas (F.6.5 in the SVG spec): we reconstruct
|
|
529
|
-
* the center, sweep angle, and a fixed number of (≤ 90°) cubic segments per
|
|
530
|
-
* arc.
|
|
531
|
-
*
|
|
532
|
-
* No runtime dependency — a small hand-rolled parser. `@weasel-js/svg`'s
|
|
533
|
-
* document parser (`parseSvg`) calls this for `<path d=…>` elements, so the
|
|
534
|
-
* `d` grammar coverage is shared between the two entry points.
|
|
526
|
+
* close). Arc commands (`A`/`a`) convert to a series of cubic Bezier curves
|
|
527
|
+
* using the standard endpoint-parameterization formulas (F.6.5 in the SVG
|
|
528
|
+
* spec).
|
|
535
529
|
*/
|
|
536
530
|
|
|
537
531
|
/**
|
|
538
532
|
* Build a weasel `PolygonPath` from an SVG path-data (`d`) string. Unknown
|
|
539
533
|
* command letters are skipped (and reported via `onWarn` when provided); the
|
|
540
|
-
* builder still returns whatever valid commands preceded them.
|
|
534
|
+
* builder still returns whatever valid commands preceded them. Data that does
|
|
535
|
+
* not begin with a moveto builds an empty path, and a command with the wrong
|
|
536
|
+
* number of arguments ends the path before it, as SVG renders both.
|
|
541
537
|
*/
|
|
542
538
|
declare function pathFromD(d: string, onWarn?: (message: string) => void): ReturnType<PathBuilder['build']>;
|
|
543
539
|
|
|
@@ -607,19 +603,22 @@ declare function schneiderFit(samples: ReadonlyArray<Point>, errorTolerance: num
|
|
|
607
603
|
* A `rect` path short-circuits to AABB arithmetic. A `polygon` path is its
|
|
608
604
|
* filled region: beziers are flattened, `fillRule` is honored (default
|
|
609
605
|
* `nonzero`), and every closed subpath counts — the hole of a donut is not
|
|
610
|
-
* part of the shape under `evenodd`.
|
|
611
|
-
*
|
|
606
|
+
* part of the shape under `evenodd`. An open subpath counts as closed where a
|
|
607
|
+
* fill closes it and encloses nothing where one does not: see
|
|
608
|
+
* `fillsOpenSubpaths`.
|
|
612
609
|
*/
|
|
613
610
|
|
|
614
|
-
type XY = {
|
|
615
|
-
x: number;
|
|
616
|
-
y: number;
|
|
617
|
-
};
|
|
618
611
|
/** Options for the path hit-tests. */
|
|
619
612
|
interface PointInPathOptions {
|
|
620
613
|
/** Bezier flattening tolerance in world units. Default 0.5. */
|
|
621
614
|
tolerance?: number;
|
|
622
615
|
}
|
|
616
|
+
/** Options for {@link polygonIntersectsPath}. */
|
|
617
|
+
interface PolygonIntersectsPathOptions extends PointInPathOptions {
|
|
618
|
+
/** False when nothing paints the path's interior: only its outline can meet
|
|
619
|
+
* the polygon, and a polygon lying wholly inside meets nothing. Default true. */
|
|
620
|
+
filled?: boolean;
|
|
621
|
+
}
|
|
623
622
|
/** Options for {@link strokeHitTest}. */
|
|
624
623
|
interface StrokeHitTestOptions {
|
|
625
624
|
/** Bezier flattening tolerance in world units. Default 0.5. */
|
|
@@ -651,22 +650,23 @@ declare function pathContainsRect(path: Path, rect: Rect, opts?: PointInPathOpti
|
|
|
651
650
|
/** Returns true if `rect` overlaps (intersects or contains) `path`. */
|
|
652
651
|
declare function pathIntersectsRect(path: Path, rect: Rect, opts?: PointInPathOptions): boolean;
|
|
653
652
|
/** Returns true if every vertex of `polygon` lies inside `path`. */
|
|
654
|
-
declare function pathContainsPolygon(path: Path, polygon: readonly
|
|
653
|
+
declare function pathContainsPolygon(path: Path, polygon: readonly Point[], opts?: PointInPathOptions): boolean;
|
|
655
654
|
/** Returns true if `polygon` overlaps (intersects or is contained by) `path`. */
|
|
656
|
-
declare function pathIntersectsPolygon(path: Path, polygon: readonly
|
|
655
|
+
declare function pathIntersectsPolygon(path: Path, polygon: readonly Point[], opts?: PointInPathOptions): boolean;
|
|
657
656
|
/**
|
|
658
657
|
* Returns true if all of `path` — every subpath, open ones included, beziers
|
|
659
658
|
* flattened — lies inside the closed `polygon`: the inverse of
|
|
660
659
|
* {@link pathContainsPolygon}. A lasso enclosing a shape asks this.
|
|
661
660
|
*/
|
|
662
|
-
declare function polygonContainsPath(polygon: readonly
|
|
661
|
+
declare function polygonContainsPath(polygon: readonly Point[], path: Path, opts?: PointInPathOptions): boolean;
|
|
663
662
|
/**
|
|
664
663
|
* Returns true if any of `path` meets the closed `polygon`: its filled region,
|
|
665
|
-
* or the line of an open subpath, which encloses nothing.
|
|
664
|
+
* or the line of an open subpath, which encloses nothing. With `filled: false`
|
|
665
|
+
* a closed subpath is a line too. Beziers are
|
|
666
666
|
* flattened, so a control point off the curve never counts. The companion of
|
|
667
667
|
* {@link polygonContainsPath}; a lasso touching a shape asks this.
|
|
668
668
|
*/
|
|
669
|
-
declare function polygonIntersectsPath(polygon: readonly
|
|
669
|
+
declare function polygonIntersectsPath(polygon: readonly Point[], path: Path, opts?: PolygonIntersectsPathOptions): boolean;
|
|
670
670
|
|
|
671
671
|
/**
|
|
672
672
|
* Apply an affine to an interleaved coord stream, returning a fresh f64
|
|
@@ -676,6 +676,115 @@ declare function polygonIntersectsPath(polygon: readonly XY[], path: Path, opts?
|
|
|
676
676
|
*/
|
|
677
677
|
declare function transformCoords(coords: ArrayLike<number>, m: Mat3): Float64Array;
|
|
678
678
|
|
|
679
|
+
/**
|
|
680
|
+
* Ergonomic builders for tiny shape paths used by chrome / overlays / markers.
|
|
681
|
+
* They all return `PolygonPath`.
|
|
682
|
+
*
|
|
683
|
+
* Not appropriate for *user-drawn* shapes (those have a pose and live in the
|
|
684
|
+
* scene).
|
|
685
|
+
*/
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* N-segment polygon approximation of a circle. Default 32 segments → max
|
|
689
|
+
* radial deviation of ≈0.005·r (≈0.05 px at r=10). Bump higher for large
|
|
690
|
+
* filled circles where the facets become visible.
|
|
691
|
+
*/
|
|
692
|
+
declare function circlePath(cx: number, cy: number, r: number, segments?: number): PolygonPath;
|
|
693
|
+
/** Axis-aligned square centred at (cx, cy) with side length `size`. */
|
|
694
|
+
declare function squarePath(cx: number, cy: number, size: number): PolygonPath;
|
|
695
|
+
/** Axis-aligned rectangle. Top-left at (x, y), width/height as given. */
|
|
696
|
+
declare function rectMarkerPath(x: number, y: number, width: number, height: number): PolygonPath;
|
|
697
|
+
/**
|
|
698
|
+
* Axis-aligned rounded rectangle. Top-left at (x, y); `r` is clamped to half
|
|
699
|
+
* the shorter side. Each corner is `cornerSamples` line segments — the
|
|
700
|
+
* default 8 gives the same angular resolution as `circlePath`'s 32.
|
|
701
|
+
*/
|
|
702
|
+
declare function roundRectPath(x: number, y: number, width: number, height: number, r: number, cornerSamples?: number): PolygonPath;
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* 2D affine matrix utilities. Column-major 9 elements, matching
|
|
706
|
+
* `WebGL2RenderingContext.uniformMatrix3fv` element order so an upload needs
|
|
707
|
+
* no transpose flag.
|
|
708
|
+
*
|
|
709
|
+
* Layout (column-major):
|
|
710
|
+
* [m00, m10, 0,
|
|
711
|
+
* m01, m11, 0,
|
|
712
|
+
* tx, ty, 1]
|
|
713
|
+
*/
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* A 2D affine transform as a column-major 9-element array, laid out as above.
|
|
717
|
+
*
|
|
718
|
+
* Every matrix the kit builds is a `Float64Array`: a view translation far
|
|
719
|
+
* from the origin at high zoom needs more than a float32's 24 bits to hold a
|
|
720
|
+
* fraction of a pixel. A `Float32Array` is accepted wherever one is read.
|
|
721
|
+
* `mat3.toUniform` makes the float32 copy a GL uniform upload wants.
|
|
722
|
+
*/
|
|
723
|
+
type GlMat3 = Float32Array | Float64Array;
|
|
724
|
+
declare function identity(): GlMat3;
|
|
725
|
+
declare function multiply(out: GlMat3, m: GlMat3): GlMat3;
|
|
726
|
+
/** `m · T(tx, ty)`: composes onto `m`, where `@weasel-js/geom`'s `translate`
|
|
727
|
+
* constructs a fresh matrix. */
|
|
728
|
+
declare function translated(m: GlMat3, tx: number, ty: number): GlMat3;
|
|
729
|
+
/** `m · S(sx, sy)`: composes onto `m`, where geom's `scale` constructs one. */
|
|
730
|
+
declare function scaled(m: GlMat3, sx: number, sy: number): GlMat3;
|
|
731
|
+
/** This layout read as the kernel's 6-element affine. The two carry the same
|
|
732
|
+
* logical order, so the conversion is a repack and nothing else. */
|
|
733
|
+
declare function toAffine(m: GlMat3): Mat3;
|
|
734
|
+
/** The kernel's affine in this layout. The inverse of {@link toAffine}. */
|
|
735
|
+
declare function fromAffine(a: Mat3): GlMat3;
|
|
736
|
+
/** Inverse of an affine matrix, or `null` when `@weasel-js/geom`'s `invert`
|
|
737
|
+
* finds it singular — the same rule, read through this layout. */
|
|
738
|
+
declare function invert(m: GlMat3): GlMat3 | null;
|
|
739
|
+
/** A float32 copy of `m`, for `uniformMatrix3fv` or a shader uniform value. */
|
|
740
|
+
declare function toUniform(m: GlMat3): Float32Array;
|
|
741
|
+
declare function apply(m: GlMat3, x: number, y: number): [number, number];
|
|
742
|
+
/**
|
|
743
|
+
* Map screen pixel coords (0..width on X, 0..height on Y, top-left origin)
|
|
744
|
+
* into clip space (-1..1 on X, 1..-1 on Y — note Y flip so screen-down
|
|
745
|
+
* matches clip-down).
|
|
746
|
+
*/
|
|
747
|
+
declare function screenToClip(width: number, height: number): GlMat3;
|
|
748
|
+
/**
|
|
749
|
+
* Uniform-equivalent scale factor: the square root of the absolute
|
|
750
|
+
* determinant of the linear part, i.e. the geometric mean of the two axis
|
|
751
|
+
* scales. Rotation-invariant. Under non-uniform scale it is between the two
|
|
752
|
+
* axes and exact on neither — the same compromise `meanScale` documents.
|
|
753
|
+
*/
|
|
754
|
+
declare function meanScaleOf(m: GlMat3): number;
|
|
755
|
+
/** The least any direction is scaled by the linear part — its smaller singular
|
|
756
|
+
* value. What a screen length is worth in world units, at most. */
|
|
757
|
+
declare function minScaleOf(m: GlMat3): number;
|
|
758
|
+
/** The renderer's 3x3 matrix operations, as one namespace. These work on the
|
|
759
|
+
* 9-element form the GL uniform upload wants — distinct from the 6-element
|
|
760
|
+
* affine `Mat3`, though the logical element order is the same. */
|
|
761
|
+
declare const mat3: {
|
|
762
|
+
identity: typeof identity;
|
|
763
|
+
toAffine: typeof toAffine;
|
|
764
|
+
fromAffine: typeof fromAffine;
|
|
765
|
+
multiply: typeof multiply;
|
|
766
|
+
translated: typeof translated;
|
|
767
|
+
scaled: typeof scaled;
|
|
768
|
+
invert: typeof invert;
|
|
769
|
+
toUniform: typeof toUniform;
|
|
770
|
+
apply: typeof apply;
|
|
771
|
+
screenToClip: typeof screenToClip;
|
|
772
|
+
meanScaleOf: typeof meanScaleOf;
|
|
773
|
+
minScaleOf: typeof minScaleOf;
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* Build the world→screen transform matrix for a `View`.
|
|
778
|
+
* Use as the `transform` field of a `kind: 'group'` DrawCommand to wrap
|
|
779
|
+
* world-space content emitted from a layer `draw` implementation.
|
|
780
|
+
*
|
|
781
|
+
* Mapping: `screen = (world − {view.x, view.y}) × {view.scale.x, view.scale.y}`.
|
|
782
|
+
*
|
|
783
|
+
* Column-major layout (matches `mat3.identity()`):
|
|
784
|
+
* `[scale.x, 0, 0, 0, scale.y, 0, -view.x*scale.x, -view.y*scale.y, 1]`.
|
|
785
|
+
*/
|
|
786
|
+
declare function viewToMat3(view: View): GlMat3;
|
|
787
|
+
|
|
679
788
|
/** Which side of the anchor the overlay sits on. */
|
|
680
789
|
type PlacementSide = 'top' | 'bottom' | 'left' | 'right';
|
|
681
790
|
/** How the overlay lines up along the side it sits on. */
|
|
@@ -867,4 +976,391 @@ declare function cubicBezierEasing(x1: number, y1: number, x2: number, y2: numbe
|
|
|
867
976
|
/** Resolve a spec to the function that shapes progress. `undefined` is linear. */
|
|
868
977
|
declare function resolveEasing(spec?: EasingSpec): EasingFn;
|
|
869
978
|
|
|
870
|
-
|
|
979
|
+
/**
|
|
980
|
+
* Geometric mean of a per-axis scale's magnitudes, so a flipped (negative)
|
|
981
|
+
* axis counts by its size.
|
|
982
|
+
*
|
|
983
|
+
* **Not for hit-testing.** A screen-pixel length is not one world distance
|
|
984
|
+
* under non-uniform zoom, and collapsing it to a scalar makes a pickable
|
|
985
|
+
* region too generous on one axis and too mean on the other. Use `pxExtent`
|
|
986
|
+
* for a per-axis world length, or `withinPxBox` / `withinPxRadius` to compare
|
|
987
|
+
* in screen space directly. Nor for placing chrome or measuring a pick slop:
|
|
988
|
+
* `standoff` and `strokeHitTest`'s `slop` work on screen.
|
|
989
|
+
*
|
|
990
|
+
* What legitimately remains is a stroke width for a line that is not
|
|
991
|
+
* axis-aligned: the renderer resolves a `{ px }` width through this same
|
|
992
|
+
* mean, so a picker reading ink widths must too.
|
|
993
|
+
*/
|
|
994
|
+
declare function meanScale(s: {
|
|
995
|
+
x: number;
|
|
996
|
+
y: number;
|
|
997
|
+
}): number;
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* Screen-pixel lengths in world units, per axis.
|
|
1001
|
+
*
|
|
1002
|
+
* Chrome declares its hit zones in screen pixels — an 8px handle stays 8px at
|
|
1003
|
+
* every zoom, because it paints in screen space. Converting one such length to
|
|
1004
|
+
* world units with a single scalar (see `meanScale`) is exact only under
|
|
1005
|
+
* uniform zoom; per-axis it is too generous on one axis and too mean on the
|
|
1006
|
+
* other, so the pickable region stops matching the painted one.
|
|
1007
|
+
*
|
|
1008
|
+
* Prefer {@link withinPxBox} / {@link withinPxRadius}, which compare in screen
|
|
1009
|
+
* space and so can't drift from the paint at all. {@link pxExtent} is for the
|
|
1010
|
+
* cases that must stay in world units — a tolerance handed to geometry that
|
|
1011
|
+
* doesn't know about the view.
|
|
1012
|
+
*/
|
|
1013
|
+
interface Scale2 {
|
|
1014
|
+
x: number;
|
|
1015
|
+
y: number;
|
|
1016
|
+
}
|
|
1017
|
+
/** One screen-pixel length as world-space extents, per axis. `scale` is a
|
|
1018
|
+
* view's, so neither axis is zero (see `View`).
|
|
1019
|
+
*
|
|
1020
|
+
* With `rotation`, the axes are a frame turned that far from world, and each
|
|
1021
|
+
* extent is how far along it an edge across it must move to move `px` on
|
|
1022
|
+
* screen — which, under non-uniform zoom, is not the extent along the screen
|
|
1023
|
+
* axis nearest to it. */
|
|
1024
|
+
declare function pxExtent(px: number, scale: Scale2, rotation?: number): {
|
|
1025
|
+
x: number;
|
|
1026
|
+
y: number;
|
|
1027
|
+
};
|
|
1028
|
+
/** World delta → screen delta. Only the scale participates: translation
|
|
1029
|
+
* cancels in a difference, and the kit's views carry no skew. */
|
|
1030
|
+
declare function scaleDelta(dx: number, dy: number, scale: Scale2): {
|
|
1031
|
+
x: number;
|
|
1032
|
+
y: number;
|
|
1033
|
+
};
|
|
1034
|
+
/**
|
|
1035
|
+
* Is a world-space delta inside a screen-space square of half-extent `px`?
|
|
1036
|
+
*
|
|
1037
|
+
* The square is axis-aligned **on screen**, which is what a handle painted in
|
|
1038
|
+
* screen space actually is — so this stays true under non-uniform zoom and
|
|
1039
|
+
* under a rotated target, where an axis-aligned world-space test is a rotated
|
|
1040
|
+
* rectangle on screen.
|
|
1041
|
+
*/
|
|
1042
|
+
declare function withinPxBox(dx: number, dy: number, px: number, scale: Scale2): boolean;
|
|
1043
|
+
/** Is a world-space delta inside a screen-space circle of radius `px`? */
|
|
1044
|
+
declare function withinPxRadius(dx: number, dy: number, px: number, scale: Scale2): boolean;
|
|
1045
|
+
/**
|
|
1046
|
+
* A world point pushed `px` screen pixels off an edge, along the edge's normal
|
|
1047
|
+
* as it lands on screen. `normal` is the edge's outward normal in world space.
|
|
1048
|
+
*
|
|
1049
|
+
* Scaling the world normal by the view is not the same thing: under
|
|
1050
|
+
* non-uniform zoom the image of a normal is no longer perpendicular to the
|
|
1051
|
+
* image of its edge, so chrome placed that way drifts sideways and sits closer
|
|
1052
|
+
* than `px`. Returns the point in world coords and the on-screen unit normal.
|
|
1053
|
+
*/
|
|
1054
|
+
declare function standoff(anchor: {
|
|
1055
|
+
x: number;
|
|
1056
|
+
y: number;
|
|
1057
|
+
}, normal: {
|
|
1058
|
+
x: number;
|
|
1059
|
+
y: number;
|
|
1060
|
+
}, px: number, scale: Scale2): {
|
|
1061
|
+
x: number;
|
|
1062
|
+
y: number;
|
|
1063
|
+
nx: number;
|
|
1064
|
+
ny: number;
|
|
1065
|
+
};
|
|
1066
|
+
/** The screen angle a frame turned `rotation` from world lands its x axis at.
|
|
1067
|
+
* Equals `rotation` under uniform zoom; non-uniform zoom bends it. */
|
|
1068
|
+
declare function screenAngleOf(rotation: number, scale: Scale2): number;
|
|
1069
|
+
|
|
1070
|
+
/** Pivot (`cx`, `cy`) and angle resolved from a pose's rotation convention. */
|
|
1071
|
+
interface PoseRotation {
|
|
1072
|
+
cx: number;
|
|
1073
|
+
cy: number;
|
|
1074
|
+
rotation: number;
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Resolve a pose's rotation transform, or `null` when the pose carries no
|
|
1078
|
+
* effective rotation. Non-null requires a nonzero `rotation` **and** all four
|
|
1079
|
+
* AABB fields (`x`, `y`, `width`, `height`) — the pivot is the unrotated AABB
|
|
1080
|
+
* center.
|
|
1081
|
+
*/
|
|
1082
|
+
declare function poseRotationOf(pose: unknown): PoseRotation | null;
|
|
1083
|
+
/**
|
|
1084
|
+
* Rotate every coordinate of `path` about `(cx, cy)` by `rotation` radians.
|
|
1085
|
+
* A `rect` promotes to a four-corner polygon (axis-aligned rects can't encode
|
|
1086
|
+
* rotation). Returns a fresh `PolygonPath`; never mutates the input.
|
|
1087
|
+
*/
|
|
1088
|
+
declare function rotatePathAround(path: Path, cx: number, cy: number, rotation: number): PolygonPath;
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* The one composition of a box pose into the matrix that carries its own
|
|
1092
|
+
* frame to its parent's: the rotation convention of `poseRotationOf`, and the
|
|
1093
|
+
* pose's residual `transform` inside it. Painting, culling, silhouettes, and
|
|
1094
|
+
* picking all read a pose through `poseFrameOf` so they agree on where the
|
|
1095
|
+
* node is.
|
|
1096
|
+
*/
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* The linear part of a box pose that its box, rotation, and flips cannot
|
|
1100
|
+
* hold: a skew, or a scale that differs by axis. `[a, b, c, d]` in
|
|
1101
|
+
* `Mat3` order (`x' = a·x + c·y`, `y' = b·x + d·y`), applied about the
|
|
1102
|
+
* box center to what the node draws in its unrotated box, before the
|
|
1103
|
+
* rotation. Absent is the identity.
|
|
1104
|
+
*/
|
|
1105
|
+
type PoseLinear = readonly [a: number, b: number, c: number, d: number];
|
|
1106
|
+
/** `transform` when it changes anything, `null` for absent or the identity. */
|
|
1107
|
+
declare function poseLinearOf(pose: unknown): PoseLinear | null;
|
|
1108
|
+
/**
|
|
1109
|
+
* The matrix taking a point in the pose's own unrotated box frame to where
|
|
1110
|
+
* the node draws it: `rotate(rotation) · transform`, about the box center.
|
|
1111
|
+
* `null` when the pose has neither, or is not a box, so a caller can skip
|
|
1112
|
+
* the step.
|
|
1113
|
+
*/
|
|
1114
|
+
declare function poseFrameOf(pose: unknown): Mat3 | null;
|
|
1115
|
+
/**
|
|
1116
|
+
* The inverse of {@link poseFrameOf}: where a parent-frame point falls in the
|
|
1117
|
+
* pose's unrotated box frame. `null` when there is no frame to undo, or when
|
|
1118
|
+
* the `transform` is singular and nothing maps back.
|
|
1119
|
+
*/
|
|
1120
|
+
declare function poseFrameInverseOf(pose: unknown): Mat3 | null;
|
|
1121
|
+
/** `path`, given in the pose's unrotated box frame, as the node draws it. */
|
|
1122
|
+
declare function pathThroughPoseFrame(path: Path, pose: unknown): Path;
|
|
1123
|
+
|
|
1124
|
+
/**
|
|
1125
|
+
* Pure anchor-set edits on the model `pathToAnchors` produces: subpath-major
|
|
1126
|
+
* `PenAnchor[][]` plus a parallel `closed: boolean[]`. Every function here
|
|
1127
|
+
* mutates that structure in place; callers decode with `pathToAnchors`,
|
|
1128
|
+
* apply, and re-encode with `anchorsToPath` (see {@link editAnchorSet}).
|
|
1129
|
+
*
|
|
1130
|
+
* Callers address anchors by flat index — the sequential position in walk
|
|
1131
|
+
* order across all subpaths, which is what `enumerateAnchors` reports.
|
|
1132
|
+
* {@link locateAnchor} and {@link flatAnchorIndex} convert to and from the
|
|
1133
|
+
* `(sub, idx)` pair.
|
|
1134
|
+
*
|
|
1135
|
+
* The two orders agree because `pathToAnchors` and `enumerateAnchors`
|
|
1136
|
+
* walk the same command stream and emit one anchor per M/L/C/Q, less the
|
|
1137
|
+
* end of a closing curve that lands on its subpath's first point.
|
|
1138
|
+
* `countPathAnchors`, which sizes the renderer's per-anchor arrays, counts
|
|
1139
|
+
* that end too and so can be one higher per closed subpath.
|
|
1140
|
+
*/
|
|
1141
|
+
|
|
1142
|
+
/** Subpath-major anchor model — the decoded form of a `PolygonPath`. */
|
|
1143
|
+
interface AnchorSet {
|
|
1144
|
+
anchors: PenAnchor[][];
|
|
1145
|
+
closed: boolean[];
|
|
1146
|
+
}
|
|
1147
|
+
/** A world-space rectangle, as produced by a drag-rect gesture. */
|
|
1148
|
+
interface AnchorRect {
|
|
1149
|
+
x: number;
|
|
1150
|
+
y: number;
|
|
1151
|
+
width: number;
|
|
1152
|
+
height: number;
|
|
1153
|
+
}
|
|
1154
|
+
/** Total anchors across every subpath. */
|
|
1155
|
+
declare function anchorCount(set: AnchorSet): number;
|
|
1156
|
+
/** Flat index → `(sub, idx)`. Returns null when out of range. */
|
|
1157
|
+
declare function locateAnchor(set: AnchorSet, flatIndex: number): {
|
|
1158
|
+
sub: number;
|
|
1159
|
+
idx: number;
|
|
1160
|
+
} | null;
|
|
1161
|
+
/** `(sub, idx)` → flat index. Returns -1 when out of range. */
|
|
1162
|
+
declare function flatAnchorIndex(set: AnchorSet, sub: number, idx: number): number;
|
|
1163
|
+
/** Resolve a flat index to the anchor itself. Null when out of range. */
|
|
1164
|
+
declare function anchorAt(set: AnchorSet, flatIndex: number): PenAnchor | null;
|
|
1165
|
+
/**
|
|
1166
|
+
* Decode `path`, hand the mutable anchor set to `edit`, and re-encode.
|
|
1167
|
+
* Returns null when `edit` returns `false` (it changed nothing), so callers
|
|
1168
|
+
* can skip a no-op commit.
|
|
1169
|
+
*/
|
|
1170
|
+
declare function editAnchorSet(path: PolygonPath, edit: (set: AnchorSet) => boolean | void): PolygonPath | null;
|
|
1171
|
+
/** Translate an anchor and both of its handles by `(dx, dy)`. */
|
|
1172
|
+
declare function translateAnchorBy(set: AnchorSet, flatIndex: number, dx: number, dy: number): boolean;
|
|
1173
|
+
/**
|
|
1174
|
+
* Move one control handle to an absolute world position. When the anchor was
|
|
1175
|
+
* smooth (handles collinear through the anchor) and `breakSmoothness` is
|
|
1176
|
+
* false, the opposite handle is mirrored so the curve stays smooth.
|
|
1177
|
+
*/
|
|
1178
|
+
declare function moveHandleTo(set: AnchorSet, flatIndex: number, side: 'in' | 'out', toX: number, toY: number, breakSmoothness: boolean): boolean;
|
|
1179
|
+
/**
|
|
1180
|
+
* Split the segment `(sub, segIdx) → (sub, segIdx + 1)` at parameter `t`,
|
|
1181
|
+
* inserting a new anchor there. On a closed subpath `segIdx` may also name
|
|
1182
|
+
* the closing segment (`sub.length - 1`); its new anchor is appended.
|
|
1183
|
+
*
|
|
1184
|
+
* A straight segment stays straight — the new anchor has no handles. A curved
|
|
1185
|
+
* one splits by de Casteljau, so the two halves trace the original path.
|
|
1186
|
+
*
|
|
1187
|
+
* Returns the new anchor's flat index, or -1 when the segment doesn't
|
|
1188
|
+
* exist.
|
|
1189
|
+
*/
|
|
1190
|
+
declare function insertAnchorOnSegment(set: AnchorSet, args: {
|
|
1191
|
+
sub: number;
|
|
1192
|
+
segIdx: number;
|
|
1193
|
+
t: number;
|
|
1194
|
+
}): number;
|
|
1195
|
+
/** A point on a segment, as {@link segmentAt} reports it. */
|
|
1196
|
+
interface SegmentHit {
|
|
1197
|
+
sub: number;
|
|
1198
|
+
segIdx: number;
|
|
1199
|
+
/** Parameter on the segment — what {@link insertAnchorOnSegment} takes. */
|
|
1200
|
+
t: number;
|
|
1201
|
+
/** The point on the path at `t`, in the set's own space. */
|
|
1202
|
+
x: number;
|
|
1203
|
+
y: number;
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* The segment point under `(x, y)`, when it lies within `tolerancePx` of
|
|
1207
|
+
* the path on screen and no nearer than that to either of the segment's
|
|
1208
|
+
* own anchors — a press on an anchor is about the anchor, not the segment
|
|
1209
|
+
* beside it.
|
|
1210
|
+
*
|
|
1211
|
+
* `scale` is the view's, so the tolerance holds per axis under
|
|
1212
|
+
* non-uniform zoom. The search runs in screen-scaled space: scaling is
|
|
1213
|
+
* affine, so the curve's parameterization, and therefore `t`, carries
|
|
1214
|
+
* straight back to world space.
|
|
1215
|
+
*/
|
|
1216
|
+
declare function segmentAt(set: AnchorSet, x: number, y: number, opts: {
|
|
1217
|
+
tolerancePx: number;
|
|
1218
|
+
scale?: Scale2;
|
|
1219
|
+
}): SegmentHit | null;
|
|
1220
|
+
/**
|
|
1221
|
+
* Delete the anchors at `flatIndexes`. Where a deleted anchor had
|
|
1222
|
+
* neighbors on both sides — across the seam too, on a closed subpath — the
|
|
1223
|
+
* surviving segment is refitted through the gap (`fitCubicThroughDeletion`)
|
|
1224
|
+
* so the path keeps its shape instead of snapping to a straight line.
|
|
1225
|
+
*
|
|
1226
|
+
* Subpaths left with fewer than two anchors are dropped entirely. If that
|
|
1227
|
+
* would leave the path with no subpaths at all, the whole deletion is refused
|
|
1228
|
+
* (returns false, `set` untouched): an empty path leaves the node selected
|
|
1229
|
+
* and in the scene with nothing on screen and no anchors to grab it by.
|
|
1230
|
+
*/
|
|
1231
|
+
declare function deleteAnchorsAt(set: AnchorSet, flatIndexes: Iterable<number>): boolean;
|
|
1232
|
+
/**
|
|
1233
|
+
* Scissors: open a closed subpath at the given anchor. The anchor list is
|
|
1234
|
+
* rotated to start there and a second anchor at the same point ends it, so
|
|
1235
|
+
* every segment survives and the two ends can be pulled apart. No-ops on an
|
|
1236
|
+
* already-open subpath — there's nothing to cut.
|
|
1237
|
+
*/
|
|
1238
|
+
declare function openSubpathAt(set: AnchorSet, flatIndex: number): boolean;
|
|
1239
|
+
/** Flat indices of every anchor whose on-curve point falls inside `rect`. */
|
|
1240
|
+
declare function anchorsInRect(set: AnchorSet, rect: AnchorRect): number[];
|
|
1241
|
+
|
|
1242
|
+
/**
|
|
1243
|
+
* Anchor lookup across several paths, measured on screen: which existing
|
|
1244
|
+
* anchor is nearest a point, and whether it ends an open subpath. Feeds the
|
|
1245
|
+
* pen's anchor snapping and its pick-up of an open path's endpoint.
|
|
1246
|
+
*/
|
|
1247
|
+
|
|
1248
|
+
/** An existing anchor found near a point. */
|
|
1249
|
+
interface PathAnchorHit {
|
|
1250
|
+
/** The id the path was supplied under. */
|
|
1251
|
+
id: string;
|
|
1252
|
+
/** Subpath index, then anchor index within that subpath. */
|
|
1253
|
+
sub: number;
|
|
1254
|
+
idx: number;
|
|
1255
|
+
x: number;
|
|
1256
|
+
y: number;
|
|
1257
|
+
/** Which end of an open subpath this anchor is, or null when it is an
|
|
1258
|
+
* interior anchor or its subpath is closed. A lone anchor reads `'last'`. */
|
|
1259
|
+
end: 'first' | 'last' | null;
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* The anchor nearest `point` within a screen-space circle of radius `px`,
|
|
1263
|
+
* across every subpath of every path in `paths`. Paths and `point` share one
|
|
1264
|
+
* world frame; `scale` is the view's, so the radius stays round under
|
|
1265
|
+
* non-uniform zoom. `accept` narrows the candidates (endpoints only, say).
|
|
1266
|
+
* Ties go to the earlier path, so pass paths front-to-back.
|
|
1267
|
+
*/
|
|
1268
|
+
declare function nearestPathAnchor(paths: Iterable<{
|
|
1269
|
+
id: string;
|
|
1270
|
+
path: PolygonPath;
|
|
1271
|
+
}>, point: {
|
|
1272
|
+
x: number;
|
|
1273
|
+
y: number;
|
|
1274
|
+
}, px: number, scale: Scale2, accept?: (hit: PathAnchorHit) => boolean): PathAnchorHit | null;
|
|
1275
|
+
/** Reverse a subpath's direction without changing its shape: the anchor
|
|
1276
|
+
* order flips and each anchor's in and out handles trade places. */
|
|
1277
|
+
declare function reverseAnchors<A extends PenAnchor>(anchors: readonly A[]): A[];
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* Project a leaf path from its source frame into world coordinates given a
|
|
1281
|
+
* `{x, y, width, height, rotation?}` pose.
|
|
1282
|
+
*
|
|
1283
|
+
* RectPaths can't carry rotation as-is (they're axis-aligned by definition);
|
|
1284
|
+
* a non-zero `pose.rotation` on a rect promotes the result to a polygon so
|
|
1285
|
+
* the four corners are baked into world coords.
|
|
1286
|
+
*/
|
|
1287
|
+
|
|
1288
|
+
/** Subset of pose fields this helper consumes: `x/y/width/height` define an
|
|
1289
|
+
* AABB whose unrotated center is the rotation pivot. */
|
|
1290
|
+
interface PathInWorldPose {
|
|
1291
|
+
x: number;
|
|
1292
|
+
y: number;
|
|
1293
|
+
width: number;
|
|
1294
|
+
height: number;
|
|
1295
|
+
rotation?: number;
|
|
1296
|
+
transform?: PoseLinear;
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Project a stored path into the pose's **local frame**: rebase its AABB onto
|
|
1300
|
+
* the pose box. It does NOT apply `pose.rotation`; `pathInWorld` composes
|
|
1301
|
+
* this with the rotation wrap.
|
|
1302
|
+
*
|
|
1303
|
+
* Rects rebase onto the pose's `width`/`height` because resize updates the pose,
|
|
1304
|
+
* not the stored `RectPath` — so the live size lives on the pose.
|
|
1305
|
+
*/
|
|
1306
|
+
declare function pathInPoseFrame(path: Path, pose: PathInWorldPose): Path;
|
|
1307
|
+
/**
|
|
1308
|
+
* Bake `pose` into `path`'s coordinates so the returned path is positioned
|
|
1309
|
+
* in world space. Returns a fresh `Path` instance; never mutates inputs.
|
|
1310
|
+
*/
|
|
1311
|
+
declare function pathInWorld(path: Path, pose: PathInWorldPose): Path;
|
|
1312
|
+
/**
|
|
1313
|
+
* Inverse of `pathInWorld` for a path in **world** space: the stored path
|
|
1314
|
+
* in the pose's own frame plus the pose that draws it there. The pose's
|
|
1315
|
+
* `rotation` and `transform` are preserved, and its AABB fields are fit to
|
|
1316
|
+
* the result. The new box's center is placed where the old frame carries it,
|
|
1317
|
+
* since the pivot moves with the box: `pathInWorld(result.path, result.pose)`
|
|
1318
|
+
* reproduces `worldPath` whether or not its bounds changed.
|
|
1319
|
+
*/
|
|
1320
|
+
declare function worldEditToStorage<P extends PathInWorldPose>(pose: P, worldPath: PolygonPath): {
|
|
1321
|
+
pose: P;
|
|
1322
|
+
path: PolygonPath;
|
|
1323
|
+
};
|
|
1324
|
+
declare function worldEditToStorage<P extends PathInWorldPose>(pose: P, worldPath: Path): {
|
|
1325
|
+
pose: P;
|
|
1326
|
+
path: Path;
|
|
1327
|
+
};
|
|
1328
|
+
|
|
1329
|
+
/** The constructor of each typed array of numbers, under the tag its type is written by. */
|
|
1330
|
+
declare const TYPED_ARRAY_CONSTRUCTORS: {
|
|
1331
|
+
readonly i8: Int8ArrayConstructor;
|
|
1332
|
+
readonly u8: Uint8ArrayConstructor;
|
|
1333
|
+
readonly u8c: Uint8ClampedArrayConstructor;
|
|
1334
|
+
readonly i16: Int16ArrayConstructor;
|
|
1335
|
+
readonly u16: Uint16ArrayConstructor;
|
|
1336
|
+
readonly i32: Int32ArrayConstructor;
|
|
1337
|
+
readonly u32: Uint32ArrayConstructor;
|
|
1338
|
+
readonly f32: Float32ArrayConstructor;
|
|
1339
|
+
readonly f64: Float64ArrayConstructor;
|
|
1340
|
+
};
|
|
1341
|
+
/** The name a typed array's type is written under. */
|
|
1342
|
+
type TypedArrayTag = keyof typeof TYPED_ARRAY_CONSTRUCTORS;
|
|
1343
|
+
/** A typed array of numbers: what a `Path` keeps its commands and coords in. */
|
|
1344
|
+
type NumberTypedArray = InstanceType<(typeof TYPED_ARRAY_CONSTRUCTORS)[TypedArrayTag]>;
|
|
1345
|
+
/** A typed array as JSON holds it: its type's tag and its numbers. */
|
|
1346
|
+
interface TaggedTypedArray {
|
|
1347
|
+
__ta: TypedArrayTag;
|
|
1348
|
+
data: number[];
|
|
1349
|
+
}
|
|
1350
|
+
/** `value` as a tag and its numbers where it is a typed array of numbers, else `undefined`. */
|
|
1351
|
+
declare function tagTypedArray(value: unknown): TaggedTypedArray | undefined;
|
|
1352
|
+
/** The typed array a `TaggedTypedArray` describes, or `undefined` for anything else. */
|
|
1353
|
+
declare function untagTypedArray(value: unknown): NumberTypedArray | undefined;
|
|
1354
|
+
/**
|
|
1355
|
+
* A copy of `value` that `JSON.stringify` writes whole: every typed array in it, at any
|
|
1356
|
+
* depth of arrays and plain objects, becomes a `TaggedTypedArray`. A `Path` goes through it
|
|
1357
|
+
* to be saved, and `reviveTypedArrays` reads it back.
|
|
1358
|
+
*/
|
|
1359
|
+
declare function tagTypedArrays(value: unknown): unknown;
|
|
1360
|
+
/** A copy of `value` with every `TaggedTypedArray` in it a typed array again: what
|
|
1361
|
+
* `JSON.parse` answered for something `tagTypedArrays` or `typedArrayReplacer` wrote. */
|
|
1362
|
+
declare function reviveTypedArrays(value: unknown): unknown;
|
|
1363
|
+
/** `tagTypedArrays` as a `JSON.stringify` replacer. */
|
|
1364
|
+
declare function typedArrayReplacer(_key: string, value: unknown): unknown;
|
|
1365
|
+
|
|
1366
|
+
export { type AnchorRect, type AnchorSet, type BezierEasing, type Box, type CubicCoords, type CurveNearest, DEFAULT_FLATTEN_TOLERANCE, EASINGS, EPS, type EasingFn, type EasingName, type EasingSpec, type GlMat3, type LineCoords, type Mat3, type NumberTypedArray, PATH_C, PATH_CMD_LENGTHS, PATH_COMMANDS, PATH_L, PATH_M, PATH_Q, PATH_Z, Path, type PathAnchorHit, PathBuilder, type PathCommandCode, type PathCommandName, PathFillRule, type PathInWorldPose, type PathNearest, PathOpenFill, type PathStation, PenAnchor, type PlaceRectOptions, type PlacedRect, type Placement, type PlacementAlign, type PlacementSide, Point, type PointAlongPathOptions, type PointInPathOptions, type PolygonIntersectsPathOptions, PolygonPath, type PoseLinear, type PoseRotation, type QuadraticCoords, type Rect, RectPath, SPRING_PRESETS, type Scale2, type SegmentHit, type SpringPreset, type SpringPresetName, type StrokeHitTestOptions, TYPED_ARRAY_CONSTRUCTORS, type TaggedTypedArray, type TypedArrayTag, type View, ZOOM_FLOOR, anchorAt, anchorCount, anchorsInRect, applyToPoint, approxEq, boundsOfCoords, boundsOfPath, boxContainsBox, boxContainsPoint, boxToBox, circlePath, clamp, clampRectWithin, composePath, cross, cubicBezierEasing, cubicBounds, cubicEvalAt, decomposePath, defaultOpenFill, deleteAnchorsAt, dot, easeIn, easeInBack, easeInBounce, easeInCirc, easeInCubic, easeInElastic, easeInExpo, easeInOut, easeInOutBack, easeInOutBounce, easeInOutCirc, easeInOutCubic, easeInOutElastic, easeInOutExpo, easeInOutQuad, easeInOutQuart, easeInOutQuint, easeInOutSine, easeInQuad, easeInQuart, easeInQuint, easeInSine, easeOut, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic, easeOutElastic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, editAnchorSet, elevateQuadraticToCubic, ellipsePath, fillsOpenSubpaths, flatAnchorIndex, flattenCubic, flattenCubicWithArcLen, flattenQuadratic, flattenQuadraticWithArcLen, forEachSegment, hasOpenSubpath, identity$1 as identity, insertAnchorOnSegment, invert$1 as invert, len2, linePath, linear, locateAnchor, mat3, meanScale, moveHandleTo, multiply$1 as multiply, nearestOnCubic, nearestOnLine, nearestOnPath, nearestOnQuadratic, nearestPathAnchor, normalizeZoom, openSubpathAt, pathCommandCoordCount, pathContainsPoint, pathContainsPolygon, pathContainsRect, pathDistanceToPoint, pathFromD, pathInPoseFrame, pathInWorld, pathIntersectsPolygon, pathIntersectsRect, pathSignedArea, pathThroughPoseFrame, placeRect, pointAlongPath, pointInPath, pointInPolygon, pointSegmentDist2, polygonContainsPath, polygonFromPoints, polygonIntersectsPath, polylineFromPoints, portControls, portCurvePoints, poseFrameInverseOf, poseFrameOf, poseLinearOf, poseRotationOf, pxExtent, quadraticEvalAt, rectMarkerPath, rectPath, rectToContour, regularPolygonPath, resolveEasing, reverseAnchors, reversePath, reviveTypedArrays, rotate, rotateAboutPoint, rotatePathAround, roundRectPath, scale, scaleDelta, scalePathToBounds, schneiderFit, screenAngleOf, segmentAt, segmentsCross, setDefaultOpenFill, sign, splitByClosure, splitCubicAt, splitLineAt, splitQuadraticAt, splitSubpaths, squarePath, standoff, starPath, strokeHitTest, sub, tagTypedArray, tagTypedArrays, transformCoords, transformPath, translate, translateAnchorBy, translatePath, typedArrayReplacer, unionBoundsPath, unionBox, untagTypedArray, viewToMat3, withinPxBox, withinPxRadius, worldEditToStorage };
|