@weasel-js/geom 1.11.0 → 1.12.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/index.js CHANGED
@@ -1,21 +1,21 @@
1
- import { carryCorners, evalSeg, parseSubpaths, keepCorners, anchorRange, subSeg, writePath, derivSeg } from './chunk-7KLJZFBY.js';
2
- export { CORNER_RESTORE_TOLERANCE, cornerTraces, keepCorners, restoreCorners } from './chunk-7KLJZFBY.js';
1
+ import { carryCorners, evalSeg, parseSubpaths, keepCorners, anchorRange, subSeg, writePath, derivSeg } from './chunk-YPWFGFQX.js';
2
+ export { CORNER_RESTORE_TOLERANCE, cornerTraces, keepCorners, restoreCorners } from './chunk-YPWFGFQX.js';
3
3
  import { PORT_REACH, portControls, portCurvePoints } from './chunk-YQADYJJ7.js';
4
4
  export { PORT_REACH } from './chunk-YQADYJJ7.js';
5
5
  import { cubicDerivativeAt, cubicCurvatureAt } from './chunk-UI3FUWXM.js';
6
6
  export { SPIRO_KINDS, combTeeth, cubicCurvatureAt, cubicCurvatureSource, cubicDerivativeAt, curvatureComb, eulerAngleAt, eulerCurvatureAt, eulerCurvatureDerivativesAt, eulerPointAt, fitEulerSegment, isSpiroKind, placeEulerSegment, solveSpiro, spiroContoursFromControl, spiroContoursOf, spiroControlPath, spiroToPath } from './chunk-UI3FUWXM.js';
7
- import { extractPolylines } from './chunk-6XSJY3TC.js';
8
- import { boundsOfPath, fitPointRun, keepCorner } from './chunk-HGF5OVEN.js';
9
- export { boundsOfPath } from './chunk-HGF5OVEN.js';
7
+ import { extractPolylines } from './chunk-VHXOCDZ2.js';
8
+ import { boundsOfPath, fitPointRun, keepCorner } from './chunk-STGSBIOL.js';
9
+ export { boundsOfPath } from './chunk-STGSBIOL.js';
10
10
  import { countPathAnchors, pathToAnchors, anchorsToPath, isAnchorSmooth, segmentEnds, isStraightSegment, nearestSegmentT } from './chunk-Q66NA2FU.js';
11
11
  export { anchorsToPath, countPathAnchors, isAnchorSmooth, isStraightSegment, nearestSegmentT, pathToAnchors, segmentEnds } from './chunk-Q66NA2FU.js';
12
- import { nearestOnPath, nearestOnLine, nearestOnQuadratic, nearestOnCubic } from './chunk-CE7IY7TB.js';
12
+ import { nearestOnLine, nearestOnPath, nearestOnQuadratic, nearestOnCubic } from './chunk-CE7IY7TB.js';
13
13
  export { nearestOnCubic, nearestOnLine, nearestOnPath, nearestOnQuadratic } from './chunk-CE7IY7TB.js';
14
- import { pointInPolygon, resolveCorners, cornerCuts, fillsOpenSubpaths, schneiderFit, fitCubicWithTangents, pointSegmentDist2, flattenQuadratic, flattenCubic, DEFAULT_FLATTEN_TOLERANCE, segmentsCross } from './chunk-J2O2B54M.js';
15
- export { CORNER_FIT_TOLERANCE, CORNER_KEYWORDS, DEFAULT_FLATTEN_TOLERANCE, cornerCenter, cornerCuts, cornerFrameAt, cornerFrames, cornerKeywordOf, cornerShapeK, cornerShapePieces, cornerShapePoint, defaultOpenFill, fillsOpenSubpaths, fitCubicWithTangents, flattenCubic, flattenCubicWithArcLen, flattenQuadratic, flattenQuadraticWithArcLen, isCornerShape, nextCornerShape, pointInPolygon, pointSegmentDist2, remapPerAnchor, resolveCorners, resolveCornersWithSources, schneiderFit, segmentsCross, setDefaultOpenFill } from './chunk-J2O2B54M.js';
16
- import { SINGULAR_RATIO } from './chunk-755Y4YZS.js';
14
+ import { resolveCorners, pointInPolygon, cornerCuts, fillsOpenSubpaths, schneiderFit, fitCubicWithTangents, flattenCubic, pointSegmentDist2, flattenQuadratic, DEFAULT_FLATTEN_TOLERANCE, segmentsCross } from './chunk-2PKPKU4P.js';
15
+ export { CORNER_FIT_TOLERANCE, CORNER_KEYWORDS, DEFAULT_FLATTEN_TOLERANCE, cornerCenter, cornerCuts, cornerFrameAt, cornerFrames, cornerKeywordOf, cornerShapeK, cornerShapePieces, cornerShapePoint, defaultOpenFill, fillsOpenSubpaths, fitCubicWithTangents, flattenCubic, flattenCubicWithArcLen, flattenQuadratic, flattenQuadraticWithArcLen, isCornerShape, nextCornerShape, pointInPolygon, pointSegmentDist2, remapPerAnchor, resolveCorners, resolveCornersWithSources, schneiderFit, segmentsCross, setDefaultOpenFill } from './chunk-2PKPKU4P.js';
16
+ import { SINGULAR_RATIO, clamp } from './chunk-755Y4YZS.js';
17
17
  export { EPS, approxEq, clamp, cross, dot, len2, sign, sub } from './chunk-755Y4YZS.js';
18
- import { PathBuilder, withCorner, scaleCorners, forEachSegment, pathCommandCoordCount, withoutCorners, PATH_M, PATH_Z, PATH_L, PATH_C, PATH_Q, elevateQuadraticToCubic, cubicEvalAt, cubicPointAt, splitCubicAtT, cubicAxisExtremaTs, rectToContour, splitCubicAt } from './chunk-B53BERKG.js';
18
+ import { PATH_M, PATH_L, PATH_C, PATH_Q, PATH_Z, PathBuilder, withCorner, scaleCorners, forEachSegment, pathCommandCoordCount, withoutCorners, elevateQuadraticToCubic, cubicEvalAt, cubicPointAt, splitCubicAtT, cubicAxisExtremaTs, rectToContour, splitCubicAt } from './chunk-B53BERKG.js';
19
19
  export { PATH_C, PATH_CMD_LENGTHS, PATH_COMMANDS, PATH_L, PATH_M, PATH_Q, PATH_Z, PathBuilder, asPolygonPath, boundsOfCoords, boxContainsBox, boxContainsPoint, compactCorners, cornerAt, cornerSpecAt, cubicAxisExtremaTs, cubicBounds, cubicEvalAt, cubicPointAt, elevateQuadraticToCubic, ellipsePath, forEachSegment, hasCorners, lerpCorners, linePath, pathCommandCoordCount, polygonFromPoints, polylineFromPoints, quadraticEvalAt, rectPath, rectToContour, regularPolygonPath, scaleCorners, splitCubicAt, splitCubicAtT, splitLineAt, splitQuadraticAt, starPath, unionBox, withCorner, withoutCorners } from './chunk-B53BERKG.js';
20
20
 
21
21
  // src/view.ts
@@ -82,6 +82,516 @@ function transformCoords(coords, m) {
82
82
  return out;
83
83
  }
84
84
 
85
+ // src/track/shape.ts
86
+ function lineShape(from, to) {
87
+ const dx2 = to.x - from.x;
88
+ const dy2 = to.y - from.y;
89
+ const length2 = Math.hypot(dx2, dy2);
90
+ const tangent = length2 === 0 ? { x: 1, y: 0 } : { x: dx2 / length2, y: dy2 / length2 };
91
+ const normal2 = { x: tangent.y, y: -tangent.x };
92
+ return {
93
+ kind: "line",
94
+ length: length2,
95
+ closed: false,
96
+ at(f) {
97
+ const t = clamp(f, 0, 1);
98
+ return {
99
+ point: { x: from.x + t * dx2, y: from.y + t * dy2 },
100
+ tangent: { ...tangent },
101
+ normal: { ...normal2 }
102
+ };
103
+ },
104
+ nearest(p, options) {
105
+ const n = nearestOnLine(p.x, p.y, from.x, from.y, to.x, to.y);
106
+ if (!options?.open || length2 === 0) return { f: n.t, dist: n.dist };
107
+ const f = ((p.x - from.x) * dx2 + (p.y - from.y) * dy2) / (length2 * length2);
108
+ return { f, dist: n.dist };
109
+ }
110
+ };
111
+ }
112
+
113
+ // src/track/arc.ts
114
+ var RAD = Math.PI / 180;
115
+ var mod360 = (a) => (a % 360 + 360) % 360;
116
+ function arcShape(spec) {
117
+ const { center, radius, start } = spec;
118
+ const sign2 = spec.direction === "ccw" ? -1 : 1;
119
+ const raw = mod360(sign2 * (spec.end - start));
120
+ const sweep = raw === 0 ? 360 : raw;
121
+ const length2 = radius * sweep * RAD;
122
+ const at2 = (f) => {
123
+ const t = clamp(f, 0, 1);
124
+ const theta = (start + sign2 * t * sweep) * RAD;
125
+ const s = Math.sin(theta);
126
+ const c = Math.cos(theta);
127
+ return {
128
+ point: { x: center.x + radius * s, y: center.y - radius * c },
129
+ tangent: { x: sign2 * c, y: sign2 * s },
130
+ normal: { x: s, y: -c }
131
+ };
132
+ };
133
+ const shape = {
134
+ kind: "arc",
135
+ length: length2,
136
+ closed: raw === 0,
137
+ at: at2,
138
+ nearest(p, options) {
139
+ const dx2 = p.x - center.x;
140
+ const dy2 = p.y - center.y;
141
+ const angle = dx2 === 0 && dy2 === 0 ? 0 : Math.atan2(dx2, -dy2) / RAD;
142
+ const offset2 = mod360(sign2 * (angle - start));
143
+ let f;
144
+ if (offset2 <= sweep) f = offset2 / sweep;
145
+ else f = 360 - offset2 < offset2 - sweep ? 0 : 1;
146
+ const q = at2(f).point;
147
+ const dist = Math.hypot(p.x - q.x, p.y - q.y);
148
+ if (!options?.open) return { f, dist };
149
+ const mid = (options.about ?? 0.5) * sweep;
150
+ const unwrapped = mid + mod360(offset2 - mid + 180) - 180;
151
+ return { f: unwrapped / sweep, dist };
152
+ }
153
+ };
154
+ Object.defineProperty(shape, "arc", { value: spec, enumerable: false });
155
+ return shape;
156
+ }
157
+
158
+ // src/track/ellipse.ts
159
+ var RAD2 = Math.PI / 180;
160
+ var mod3602 = (a) => (a % 360 + 360) % 360;
161
+ var PIECES = 128;
162
+ var PANELS = 4;
163
+ var RING = 256;
164
+ var pointAt = ({ center, a, b, t0, sign: sign2 }, u) => {
165
+ const t = (t0 + sign2 * u) * RAD2;
166
+ return { x: center.x + a * Math.sin(t), y: center.y - b * Math.cos(t) };
167
+ };
168
+ var speed = ({ a, b, t0, sign: sign2 }, u) => {
169
+ const t = (t0 + sign2 * u) * RAD2;
170
+ return Math.hypot(a * Math.cos(t), b * Math.sin(t)) * RAD2;
171
+ };
172
+ function lengthTable(e, sweep) {
173
+ const simpson = (u0, u1) => {
174
+ const h = (u1 - u0) / PANELS;
175
+ let sum = speed(e, u0) + speed(e, u1);
176
+ for (let i = 1; i < PANELS; i++) sum += speed(e, u0 + i * h) * (i % 2 ? 4 : 2);
177
+ return sum * h / 3;
178
+ };
179
+ const du = sweep / PIECES;
180
+ const table = [0];
181
+ for (let i = 0; i < PIECES; i++) table.push(table[i] + simpson(i * du, (i + 1) * du));
182
+ const length2 = table[PIECES];
183
+ const lengthTo = (u) => {
184
+ if (u >= 0 && u <= sweep) {
185
+ const i = Math.min(PIECES - 1, Math.floor(u / du));
186
+ return table[i] + simpson(i * du, u);
187
+ }
188
+ const n = Math.max(1, Math.ceil(Math.abs(u) / du));
189
+ let s = 0;
190
+ for (let i = 0; i < n; i++) s += simpson(u * i / n, u * (i + 1) / n);
191
+ return s;
192
+ };
193
+ const offsetAt = (f) => {
194
+ if (length2 === 0) return f * sweep;
195
+ const s = f * length2;
196
+ let lo = 0;
197
+ let hi = PIECES;
198
+ while (hi - lo > 1) {
199
+ const mid = lo + hi >> 1;
200
+ if (table[mid] <= s) lo = mid;
201
+ else hi = mid;
202
+ }
203
+ const piece = table[lo + 1] - table[lo];
204
+ let u = (lo + (piece === 0 ? 0 : (s - table[lo]) / piece)) * du;
205
+ for (let i = 0; i < 3; i++) {
206
+ const v = speed(e, u);
207
+ if (!(v > 0)) break;
208
+ u -= (lengthTo(u) - s) / v;
209
+ }
210
+ return u;
211
+ };
212
+ return { length: length2, lengthTo, offsetAt };
213
+ }
214
+ var dist2 = (e, p, u) => {
215
+ const q = pointAt(e, u);
216
+ return (p.x - q.x) ** 2 + (p.y - q.y) ** 2;
217
+ };
218
+ function footBetween(e, p, lo, hi, n, late) {
219
+ let best = lo;
220
+ let bestD = dist2(e, p, lo);
221
+ const step = (hi - lo) / n;
222
+ for (let i = 1; i <= n; i++) {
223
+ const v = i === n ? hi : lo + i * step;
224
+ const d = dist2(e, p, v);
225
+ if (late ? d <= bestD * (1 + 1e-12) : d < bestD * (1 - 1e-12)) [best, bestD] = [v, d];
226
+ }
227
+ const { a, b, t0, sign: sign2 } = e;
228
+ const dx2 = p.x - e.center.x;
229
+ const dy2 = p.y - e.center.y;
230
+ let u = best;
231
+ for (let i = 0; i < 12; i++) {
232
+ const t = (t0 + sign2 * u) * RAD2;
233
+ const s = Math.sin(t);
234
+ const c = Math.cos(t);
235
+ const g = (a * a - b * b) * s * c - dx2 * a * c - dy2 * b * s;
236
+ const g1 = (a * a - b * b) * (c * c - s * s) + dx2 * a * s - dy2 * b * c;
237
+ if (!(g1 > 0)) break;
238
+ const next = clamp(u - sign2 * g / g1 / RAD2, Math.max(lo, best - step), Math.min(hi, best + step));
239
+ if (Math.abs(next - u) < 1e-12) break;
240
+ u = next;
241
+ }
242
+ return dist2(e, p, u) <= bestD ? u : best;
243
+ }
244
+ function ellipseShape(spec) {
245
+ const { center, radiusX: a, radiusY: b } = spec;
246
+ const sign2 = spec.direction === "ccw" ? -1 : 1;
247
+ const param = (seenDeg) => Math.atan2(b * Math.sin(seenDeg * RAD2), a * Math.cos(seenDeg * RAD2)) / RAD2;
248
+ const e = { center, a, b, t0: param(spec.start), sign: sign2 };
249
+ const raw = mod3602(sign2 * (param(spec.end) - e.t0));
250
+ const sweep = raw === 0 ? 360 : raw;
251
+ const { length: length2, lengthTo, offsetAt } = lengthTable(e, sweep);
252
+ const at2 = (f) => {
253
+ const u = offsetAt(clamp(f, 0, 1));
254
+ const t = (e.t0 + sign2 * u) * RAD2;
255
+ const s = Math.sin(t);
256
+ const c = Math.cos(t);
257
+ const k = Math.hypot(a * c, b * s);
258
+ const tangent = k === 0 ? { x: sign2 * c, y: sign2 * s } : { x: sign2 * a * c / k, y: sign2 * b * s / k };
259
+ const normal2 = k === 0 ? { x: s, y: -c } : { x: b * s / k, y: -a * c / k };
260
+ return { point: pointAt(e, u), tangent, normal: normal2 };
261
+ };
262
+ const shape = {
263
+ kind: "ellipse",
264
+ length: length2,
265
+ closed: raw === 0,
266
+ at: at2,
267
+ nearest(p, options) {
268
+ const u = footBetween(e, p, 0, sweep, PIECES, true);
269
+ const f = length2 === 0 ? 0 : clamp(lengthTo(u) / length2, 0, 1);
270
+ const dist = Math.sqrt(dist2(e, p, u));
271
+ if (!options?.open || length2 === 0) return { f, dist };
272
+ const mid = offsetAt(options.about ?? 0.5);
273
+ return { f: lengthTo(footBetween(e, p, mid - 180, mid + 180, RING, false)) / length2, dist };
274
+ }
275
+ };
276
+ Object.defineProperty(shape, "ellipse", { value: spec, enumerable: false });
277
+ return shape;
278
+ }
279
+
280
+ // src/paths/pathMeasure.ts
281
+ function measurePath(path, opts = {}) {
282
+ const polylines = extractPolylines(
283
+ path,
284
+ opts.flattenTolerance === void 0 ? {} : { flattenTolerance: opts.flattenTolerance }
285
+ );
286
+ const segments = [];
287
+ let total = 0;
288
+ let first;
289
+ let contours = 0;
290
+ let lastClosed = false;
291
+ for (const pl of polylines) {
292
+ const count = pl.points.length / 2;
293
+ if (count === 0) continue;
294
+ contours++;
295
+ lastClosed = pl.closed;
296
+ first ??= { x: pl.points[0], y: pl.points[1] };
297
+ const startIndex = segments.length;
298
+ const last = pl.closed ? count : count - 1;
299
+ for (let i = 0; i < last; i++) {
300
+ const j = (i + 1) % count;
301
+ const ax = pl.points[i * 2];
302
+ const ay = pl.points[i * 2 + 1];
303
+ const bx = pl.points[j * 2];
304
+ const by = pl.points[j * 2 + 1];
305
+ const len = Math.hypot(bx - ax, by - ay);
306
+ if (len === 0) continue;
307
+ const tx = (bx - ax) / len;
308
+ const ty = (by - ay) / len;
309
+ segments.push({ ax, ay, bx, by, start: total, len, tx, ty, t0x: tx, t0y: ty, t1x: tx, t1y: ty });
310
+ total += len;
311
+ }
312
+ smoothVertexHeadings(segments, startIndex, pl.closed, opts.keepCorners ? opts.flattenTolerance ?? 0.5 : null);
313
+ }
314
+ if (first === void 0) return null;
315
+ const origin = first;
316
+ const closed = contours === 1 && lastClosed;
317
+ if (segments.length === 0) {
318
+ return {
319
+ length: 0,
320
+ closed,
321
+ at: () => ({ point: { ...origin }, tangent: { x: 1, y: 0 } }),
322
+ nearest: (p) => ({
323
+ s: 0,
324
+ dist: Math.hypot(p.x - origin.x, p.y - origin.y),
325
+ point: { ...origin },
326
+ tangent: { x: 1, y: 0 }
327
+ })
328
+ };
329
+ }
330
+ return {
331
+ length: total,
332
+ closed,
333
+ at(s, smooth = false) {
334
+ const target = Math.min(Math.max(s, 0), total);
335
+ const seg = segments[segmentAt(segments, target)];
336
+ const u = Math.min(Math.max((target - seg.start) / seg.len, 0), 1);
337
+ const point = { x: seg.ax + (seg.bx - seg.ax) * u, y: seg.ay + (seg.by - seg.ay) * u };
338
+ if (!smooth) return { point, tangent: { x: seg.tx, y: seg.ty } };
339
+ const x = seg.t0x + (seg.t1x - seg.t0x) * u;
340
+ const y = seg.t0y + (seg.t1y - seg.t0y) * u;
341
+ const l = Math.hypot(x, y);
342
+ return { point, tangent: l === 0 ? { x: seg.tx, y: seg.ty } : { x: x / l, y: y / l } };
343
+ },
344
+ nearest(p) {
345
+ let best = segments[0];
346
+ let bestU = 0;
347
+ let bestDist = Infinity;
348
+ for (const seg of segments) {
349
+ const u = Math.min(Math.max(((p.x - seg.ax) * seg.tx + (p.y - seg.ay) * seg.ty) / seg.len, 0), 1);
350
+ const d = Math.hypot(p.x - (seg.ax + (seg.bx - seg.ax) * u), p.y - (seg.ay + (seg.by - seg.ay) * u));
351
+ if (d < bestDist) {
352
+ best = seg;
353
+ bestU = u;
354
+ bestDist = d;
355
+ }
356
+ }
357
+ return {
358
+ s: best.start + best.len * bestU,
359
+ dist: bestDist,
360
+ point: { x: best.ax + (best.bx - best.ax) * bestU, y: best.ay + (best.by - best.ay) * bestU },
361
+ tangent: { x: best.tx, y: best.ty }
362
+ };
363
+ }
364
+ };
365
+ }
366
+ function smoothVertexHeadings(segments, from, closed, cornerTolerance) {
367
+ const n = segments.length - from;
368
+ if (n < 2) return;
369
+ const pairs = closed ? n : n - 1;
370
+ for (let k = 0; k < pairs; k++) {
371
+ const a = segments[from + k];
372
+ const b = segments[from + (k + 1) % n];
373
+ if (cornerTolerance !== null && isCorner(a, b, cornerTolerance)) continue;
374
+ let x = a.tx + b.tx;
375
+ let y = a.ty + b.ty;
376
+ const l = Math.hypot(x, y);
377
+ if (l < 1e-9) continue;
378
+ x /= l;
379
+ y /= l;
380
+ a.t1x = x;
381
+ a.t1y = y;
382
+ b.t0x = x;
383
+ b.t0y = y;
384
+ }
385
+ }
386
+ function isCorner(a, b, tolerance) {
387
+ const turn = Math.acos(Math.min(1, Math.max(-1, a.tx * b.tx + a.ty * b.ty)));
388
+ return turn > 16 * tolerance / Math.min(a.len, b.len);
389
+ }
390
+ function segmentAt(segments, s) {
391
+ let lo = 0;
392
+ let hi = segments.length - 1;
393
+ while (lo < hi) {
394
+ const mid = lo + hi + 1 >> 1;
395
+ if (segments[mid].start <= s) lo = mid;
396
+ else hi = mid - 1;
397
+ }
398
+ return lo;
399
+ }
400
+
401
+ // src/track/path.ts
402
+ function pathShape(path, options = {}) {
403
+ const measure2 = measurePath(path, { ...options, keepCorners: true });
404
+ if (!measure2) return null;
405
+ const { length: length2, closed } = measure2;
406
+ const shape = {
407
+ kind: "path",
408
+ length: length2,
409
+ closed,
410
+ at(f) {
411
+ const { point, tangent } = measure2.at(clamp(f, 0, 1) * length2, true);
412
+ return { point, tangent, normal: { x: tangent.y, y: -tangent.x } };
413
+ },
414
+ nearest(p, opts) {
415
+ const n = measure2.nearest(p);
416
+ const f = length2 === 0 ? 0 : n.s / length2;
417
+ if (!opts?.open || !closed || length2 === 0) return { f, dist: n.dist };
418
+ const about = opts.about ?? 0.5;
419
+ const unwrapped = [f - 1, f + 1].reduce((best, g) => Math.abs(g - about) < Math.abs(best - about) ? g : best, f);
420
+ return { f: unwrapped, dist: n.dist };
421
+ }
422
+ };
423
+ Object.defineProperty(shape, "path", { value: path, enumerable: false });
424
+ return shape;
425
+ }
426
+
427
+ // src/track/run.ts
428
+ function trackRun(shape, from = 0, to = 1, options = {}) {
429
+ const { side = "auto", about = (from + to) / 2 } = options;
430
+ const mid = shape.at(about);
431
+ const flipped = side === "auto" ? mid.normal.y > 1e-9 : side === "flipped";
432
+ const sgn = flipped ? -1 : 1;
433
+ const reading = (n) => ({ x: -(sgn * n.y), y: sgn * n.x });
434
+ const r0 = reading(mid.normal);
435
+ const reversed3 = r0.x * mid.tangent.x + r0.y * mid.tangent.y < 0;
436
+ const span = to - from;
437
+ const lo = Math.min(from, to);
438
+ const hi = Math.max(from, to);
439
+ const toF = (g) => reversed3 ? to - g * span : from + g * span;
440
+ const at2 = (g) => {
441
+ const s = shape.at(toF(clamp(g, 0, 1)));
442
+ const dir = reading(s.normal);
443
+ const angle = Math.atan2(dir.y, dir.x) * 180 / Math.PI;
444
+ return {
445
+ point: s.point,
446
+ tangent: dir,
447
+ normal: { x: sgn * s.normal.x, y: sgn * s.normal.y },
448
+ angle: angle === 0 ? 0 : angle === -180 ? 180 : angle
449
+ };
450
+ };
451
+ const toG = (f) => span === 0 ? 0 : reversed3 ? (to - f) / span : (f - from) / span;
452
+ const nearest = (p, options2) => {
453
+ const f = clamp(shape.nearest(p).f, lo, hi);
454
+ const q = shape.at(f).point;
455
+ const dist = Math.hypot(p.x - q.x, p.y - q.y);
456
+ if (!options2?.open) return { f: toG(f), dist };
457
+ return { f: toG(shape.nearest(p, { open: true, about: toF(options2.about ?? 0.5) }).f), dist };
458
+ };
459
+ return {
460
+ kind: shape.kind,
461
+ length: shape.length * Math.abs(span),
462
+ closed: shape.closed && Math.abs(span) === 1,
463
+ shape,
464
+ from,
465
+ to,
466
+ flipped,
467
+ reversed: reversed3,
468
+ at: at2,
469
+ nearest
470
+ };
471
+ }
472
+
473
+ // src/paths/pathToD.ts
474
+ var num = (n) => Number.isFinite(n) ? String(Math.round(n * 1e6) / 1e6) : "0";
475
+ function pathToD(given) {
476
+ const path = resolveCorners(given);
477
+ if (path.kind === "rect") {
478
+ const { x, y, width, height } = path;
479
+ return `M${num(x)} ${num(y)}h${num(width)}v${num(height)}h${num(-width)}Z`;
480
+ }
481
+ const cmds = path.commands;
482
+ const xs = path.coords;
483
+ const out = [];
484
+ let ci = 0;
485
+ for (let i = 0; i < cmds.length; i++) {
486
+ const c = cmds[i];
487
+ if (c === PATH_M) {
488
+ out.push(`M${num(xs[ci])} ${num(xs[ci + 1])}`);
489
+ ci += 2;
490
+ } else if (c === PATH_L) {
491
+ out.push(`L${num(xs[ci])} ${num(xs[ci + 1])}`);
492
+ ci += 2;
493
+ } else if (c === PATH_C) {
494
+ out.push(
495
+ `C${num(xs[ci])} ${num(xs[ci + 1])} ${num(xs[ci + 2])} ${num(xs[ci + 3])} ${num(xs[ci + 4])} ${num(xs[ci + 5])}`
496
+ );
497
+ ci += 6;
498
+ } else if (c === PATH_Q) {
499
+ out.push(`Q${num(xs[ci])} ${num(xs[ci + 1])} ${num(xs[ci + 2])} ${num(xs[ci + 3])}`);
500
+ ci += 4;
501
+ } else if (c === PATH_Z) {
502
+ out.push("Z");
503
+ }
504
+ }
505
+ return out.join("");
506
+ }
507
+
508
+ // src/track/pathData.ts
509
+ var SAMPLES = 64;
510
+ var num2 = (v) => {
511
+ const r = Math.round(v * 1e4) / 1e4;
512
+ return String(r === 0 ? 0 : r);
513
+ };
514
+ var pt = (p) => `${num2(p.x)} ${num2(p.y)}`;
515
+ var isArc = (s) => s.kind === "arc" && "arc" in s;
516
+ var isPath = (s) => s.kind === "path" && "path" in s;
517
+ var isEllipse = (s) => s.kind === "ellipse" && "ellipse" in s;
518
+ function arcsD(a, b, m, rx, ry, turn, sweep) {
519
+ const r = `${num2(rx)} ${num2(ry)}`;
520
+ if (turn >= 2 * Math.PI - 1e-9) return `M ${pt(a)} A ${r} 0 0 ${sweep} ${pt(m())} A ${r} 0 0 ${sweep} ${pt(b)}`;
521
+ return `M ${pt(a)} A ${r} 0 ${turn > Math.PI + 1e-9 ? 1 : 0} ${sweep} ${pt(b)}`;
522
+ }
523
+ function trackShapeD(shape, from = 0, to = 1) {
524
+ if (isPath(shape) && from === 0 && to === 1) return pathToD(shape.path);
525
+ const a = shape.at(from).point;
526
+ const b = shape.at(to).point;
527
+ if (shape.kind === "line") return `M ${pt(a)} L ${pt(b)}`;
528
+ const m = () => shape.at((from + to) / 2).point;
529
+ if (isArc(shape)) {
530
+ const { radius, direction } = shape.arc;
531
+ const turn = shape.length * Math.abs(to - from) / radius;
532
+ return arcsD(a, b, m, radius, radius, turn, direction === "ccw" === to < from ? 1 : 0);
533
+ }
534
+ if (isEllipse(shape)) {
535
+ const { radiusX, radiusY, direction, center } = shape.ellipse;
536
+ const param = (p) => Math.atan2((p.x - center.x) / radiusX, (center.y - p.y) / radiusY);
537
+ const sign2 = direction === "ccw" ? -1 : 1;
538
+ const whole = (shape.ellipse.start - shape.ellipse.end) % 360 === 0 && Math.abs(to - from) >= 1 - 1e-12;
539
+ const raw = (sign2 * Math.sign(to - from) * (param(b) - param(a)) + 4 * Math.PI) % (2 * Math.PI);
540
+ return arcsD(a, b, m, radiusX, radiusY, whole ? 2 * Math.PI : raw, direction === "ccw" === to < from ? 1 : 0);
541
+ }
542
+ let d = `M ${pt(a)}`;
543
+ for (let i = 1; i <= SAMPLES; i++)
544
+ d += ` L ${pt(shape.at(from + (to - from) * i / SAMPLES).point)}`;
545
+ return d;
546
+ }
547
+
548
+ // src/track/place.ts
549
+ var clipOf = (lo, hi, start, end, hair = 0) => lo >= start - hair && hi <= end + hair ? "none" : hi <= start + hair || lo >= end - hair ? "past" : "part";
550
+ function runPlaces(lengths, gap, room, align, offset2, at2) {
551
+ const total = runLength(lengths, gap);
552
+ const fits = at2 === void 0 && total <= room;
553
+ const lead = align === "start" ? 0 : align === "end" ? total : total / 2;
554
+ let lo = at2 !== void 0 ? at2 - offset2 - lead : !fits ? -offset2 : align === "start" ? 0 : align === "end" ? room - total : (room - total) / 2;
555
+ return lengths.map((length2) => {
556
+ const start = lo;
557
+ const hi = start + length2;
558
+ lo = hi + gap;
559
+ return { center: start + length2 / 2, clipped: fits ? "none" : clipOf(start, hi, 0, room) };
560
+ });
561
+ }
562
+ function runLength(lengths, gap) {
563
+ return lengths.reduce((sum, length2, i) => sum + length2 + (i > 0 ? gap : 0), 0);
564
+ }
565
+ function runStationAt(run2, distance, shift = 0) {
566
+ const { length: length2 } = run2;
567
+ const place = run2.at(length2 === 0 ? 0 : distance / length2);
568
+ const past = distance < 0 ? distance : distance > length2 ? distance - length2 : 0;
569
+ const { point, tangent, normal: normal2 } = place;
570
+ return {
571
+ ...place,
572
+ point: { x: point.x + past * tangent.x + shift * normal2.x, y: point.y + past * tangent.y + shift * normal2.y }
573
+ };
574
+ }
575
+ function placeAlongRun(run2, lengths, options = {}) {
576
+ const { gap = 0, align = "center", offset: offset2 = 0, at: at2, shift = 0, wrap = false, past = "extend", order = "reading" } = options;
577
+ const { length: length2 } = run2;
578
+ const mirror = order === "track" && run2.reversed;
579
+ const spots = runPlaces(lengths, gap, length2, align, offset2, at2);
580
+ const lap = wrap && run2.closed && spots.length > 0 ? wrapWindow(spots[0], lengths[0], length2) : null;
581
+ return spots.map((spot, i) => {
582
+ const half = lengths[i] / 2;
583
+ const clipped = lap ? clipOf(spot.center - half, spot.center + half, lap.start, lap.end, lap.hair) : spot.clipped;
584
+ let distance = spot.center;
585
+ if (past === "hold" && clipped === "past") distance = clamp(distance, lap?.start ?? 0, lap?.end ?? length2);
586
+ if (lap && length2 > 0) distance = (distance % length2 + length2) % length2;
587
+ return { ...runStationAt(run2, mirror ? length2 - distance : distance, shift), center: spot.center, clipped };
588
+ });
589
+ }
590
+ function wrapWindow(first, firstLength, length2) {
591
+ const start = first.center - firstLength / 2;
592
+ return { start, end: start + length2, hair: 1e-9 * Math.max(1, length2) };
593
+ }
594
+
85
595
  // src/portCurve2.ts
86
596
  function portControls2(ax, ay, bx, by, outNormal, inNormal, reach = PORT_REACH) {
87
597
  const [c1, c2] = portControls([ax, ay], [bx, by], outNormal, inNormal, reach);
@@ -270,7 +780,7 @@ function pointInPath(path, x, y, opts = {}) {
270
780
  return x >= path.x && x <= path.x + path.width && y >= path.y && y <= path.y + path.height;
271
781
  }
272
782
  let crossings = 0;
273
- let winding = 0;
783
+ let winding2 = 0;
274
784
  for (const sub3 of closedSubpaths(path, opts)) {
275
785
  const n = sub3.length;
276
786
  for (let i = 0; i < n; i += 2) {
@@ -280,10 +790,10 @@ function pointInPath(path, x, y, opts = {}) {
280
790
  const ax = sub3[i], bx = sub3[j];
281
791
  if (x >= ax + (y - ay) / (by - ay) * (bx - ax)) continue;
282
792
  crossings++;
283
- winding += ay <= y ? 1 : -1;
793
+ winding2 += ay <= y ? 1 : -1;
284
794
  }
285
795
  }
286
- return path.fillRule === "evenodd" ? (crossings & 1) === 1 : winding !== 0;
796
+ return path.fillRule === "evenodd" ? (crossings & 1) === 1 : winding2 !== 0;
287
797
  }
288
798
  function strokeHitTest(path, x, y, threshold, opts = {}) {
289
799
  const slop = opts.slop;
@@ -620,7 +1130,7 @@ function splitSubpaths(path) {
620
1130
  let coordStart = 0;
621
1131
  let anchorStart = 0;
622
1132
  let anchors = 0;
623
- const emit = (end, coordEnd) => {
1133
+ const emit2 = (end, coordEnd) => {
624
1134
  const sub3 = { ...bare, commands: commands.slice(cmdStart, end), coords: coords.slice(coordStart, coordEnd) };
625
1135
  const count = anchors - anchorStart;
626
1136
  out.push(path.corners ? carryCorners(path, sub3, anchorRange(anchorStart, count)) : sub3);
@@ -628,13 +1138,13 @@ function splitSubpaths(path) {
628
1138
  };
629
1139
  forEachSegment(commands, coords, (cmd, ci, _px, _py, i) => {
630
1140
  if (cmd === PATH_M && i > cmdStart) {
631
- emit(i, ci);
1141
+ emit2(i, ci);
632
1142
  cmdStart = i;
633
1143
  coordStart = ci;
634
1144
  }
635
1145
  if (cmd !== PATH_Z) anchors++;
636
1146
  });
637
- emit(commands.length, coords.length);
1147
+ emit2(commands.length, coords.length);
638
1148
  return out;
639
1149
  }
640
1150
 
@@ -812,8 +1322,8 @@ function reversePath(path) {
812
1322
  anchor++;
813
1323
  });
814
1324
  flush(false);
815
- const reversed2 = { ...path, commands: new Uint8Array(cmds), coords: new Float32Array(out) };
816
- return path.corners ? carryCorners(path, reversed2, sources) : reversed2;
1325
+ const reversed3 = { ...path, commands: new Uint8Array(cmds), coords: new Float32Array(out) };
1326
+ return path.corners ? carryCorners(path, reversed3, sources) : reversed3;
817
1327
  }
818
1328
 
819
1329
  // src/paths/pathFromD.ts
@@ -1074,9 +1584,9 @@ function arcToCubics(x1, y1, x2, y2, rx, ry, xAxisRotationDeg, largeArc, sweep,
1074
1584
  rys = ryFixed * ryFixed;
1075
1585
  }
1076
1586
  const sign2 = largeArc === sweep ? -1 : 1;
1077
- const num = rxs * rys - rxs * y1ps - rys * x1ps;
1587
+ const num3 = rxs * rys - rxs * y1ps - rys * x1ps;
1078
1588
  const den = rxs * y1ps + rys * x1ps;
1079
- const factor = sign2 * Math.sqrt(Math.max(0, num / den));
1589
+ const factor = sign2 * Math.sqrt(Math.max(0, num3 / den));
1080
1590
  const cxp = factor * (rxFixed * y1p) / ryFixed;
1081
1591
  const cyp = factor * -(ryFixed * x1p) / rxFixed;
1082
1592
  const cx = cosPhi * cxp - sinPhi * cyp + (x1 + x2) / 2;
@@ -1124,92 +1634,6 @@ function arcToCubics(x1, y1, x2, y2, rx, ry, xAxisRotationDeg, largeArc, sweep,
1124
1634
  }
1125
1635
  }
1126
1636
 
1127
- // src/paths/pathMeasure.ts
1128
- function measurePath(path, opts = {}) {
1129
- const polylines = extractPolylines(
1130
- path,
1131
- opts.flattenTolerance === void 0 ? {} : { flattenTolerance: opts.flattenTolerance }
1132
- );
1133
- const segments = [];
1134
- let total = 0;
1135
- let first;
1136
- let contours = 0;
1137
- let lastClosed = false;
1138
- for (const pl of polylines) {
1139
- const count = pl.points.length / 2;
1140
- if (count === 0) continue;
1141
- contours++;
1142
- lastClosed = pl.closed;
1143
- first ??= { x: pl.points[0], y: pl.points[1] };
1144
- const startIndex = segments.length;
1145
- const last = pl.closed ? count : count - 1;
1146
- for (let i = 0; i < last; i++) {
1147
- const j = (i + 1) % count;
1148
- const ax = pl.points[i * 2];
1149
- const ay = pl.points[i * 2 + 1];
1150
- const bx = pl.points[j * 2];
1151
- const by = pl.points[j * 2 + 1];
1152
- const len = Math.hypot(bx - ax, by - ay);
1153
- if (len === 0) continue;
1154
- const tx = (bx - ax) / len;
1155
- const ty = (by - ay) / len;
1156
- segments.push({ ax, ay, bx, by, start: total, len, tx, ty, t0x: tx, t0y: ty, t1x: tx, t1y: ty });
1157
- total += len;
1158
- }
1159
- smoothVertexHeadings(segments, startIndex, pl.closed);
1160
- }
1161
- if (first === void 0) return null;
1162
- const origin = first;
1163
- const closed = contours === 1 && lastClosed;
1164
- if (segments.length === 0) {
1165
- return { length: 0, closed, at: () => ({ point: { ...origin }, tangent: { x: 1, y: 0 } }) };
1166
- }
1167
- return {
1168
- length: total,
1169
- closed,
1170
- at(s, smooth = false) {
1171
- const target = Math.min(Math.max(s, 0), total);
1172
- const seg = segments[segmentAt(segments, target)];
1173
- const u = Math.min(Math.max((target - seg.start) / seg.len, 0), 1);
1174
- const point = { x: seg.ax + (seg.bx - seg.ax) * u, y: seg.ay + (seg.by - seg.ay) * u };
1175
- if (!smooth) return { point, tangent: { x: seg.tx, y: seg.ty } };
1176
- const x = seg.t0x + (seg.t1x - seg.t0x) * u;
1177
- const y = seg.t0y + (seg.t1y - seg.t0y) * u;
1178
- const l = Math.hypot(x, y);
1179
- return { point, tangent: l === 0 ? { x: seg.tx, y: seg.ty } : { x: x / l, y: y / l } };
1180
- }
1181
- };
1182
- }
1183
- function smoothVertexHeadings(segments, from, closed) {
1184
- const n = segments.length - from;
1185
- if (n < 2) return;
1186
- const pairs = closed ? n : n - 1;
1187
- for (let k = 0; k < pairs; k++) {
1188
- const a = segments[from + k];
1189
- const b = segments[from + (k + 1) % n];
1190
- let x = a.tx + b.tx;
1191
- let y = a.ty + b.ty;
1192
- const l = Math.hypot(x, y);
1193
- if (l < 1e-9) continue;
1194
- x /= l;
1195
- y /= l;
1196
- a.t1x = x;
1197
- a.t1y = y;
1198
- b.t0x = x;
1199
- b.t0y = y;
1200
- }
1201
- }
1202
- function segmentAt(segments, s) {
1203
- let lo = 0;
1204
- let hi = segments.length - 1;
1205
- while (lo < hi) {
1206
- const mid = lo + hi + 1 >> 1;
1207
- if (segments[mid].start <= s) lo = mid;
1208
- else hi = mid - 1;
1209
- }
1210
- return lo;
1211
- }
1212
-
1213
1637
  // src/paths/pathAt.ts
1214
1638
  function pointAlongPath(path, t, opts = {}) {
1215
1639
  const measure2 = measurePath(path, opts);
@@ -1381,13 +1805,13 @@ function matchContours(a, b, opts) {
1381
1805
  const offset2 = Math.round(opts.startOffset ?? 0);
1382
1806
  const directions = opts.reverse !== void 0 ? [opts.reverse] : auto ? [false, true] : [false];
1383
1807
  let best = null;
1384
- for (const reversed2 of directions) {
1385
- const run3 = reversed2 ? reverseContour(y) : y;
1808
+ for (const reversed3 of directions) {
1809
+ const run3 = reversed3 ? reverseContour(y) : y;
1386
1810
  const turns = closed && auto ? run3.segs.length : 1;
1387
1811
  for (let k = 0; k < turns; k++) {
1388
1812
  const [ma, mb] = equalize(x, rotate2(run3, k));
1389
1813
  const score = auto ? correspondence(ma, mb) : 0;
1390
- if (best === null || score < best.score - 1e-9) best = { score, a: ma, b: mb, reversed: reversed2, turn: k };
1814
+ if (best === null || score < best.score - 1e-9) best = { score, a: ma, b: mb, reversed: reversed3, turn: k };
1391
1815
  }
1392
1816
  }
1393
1817
  const chosen = best;
@@ -1771,17 +2195,17 @@ var GL_W2 = [
1771
2195
  0.2223810344533745,
1772
2196
  0.1012285362903763
1773
2197
  ];
1774
- var speed = (s, t) => {
2198
+ var speed2 = (s, t) => {
1775
2199
  const d = derivSeg(s, t);
1776
2200
  return Math.hypot(d.x, d.y);
1777
2201
  };
1778
2202
  function segmentLength(s, a = 0, b = 1) {
1779
- if (s.length === 2) return speed(s, 0) * (b - a);
2203
+ if (s.length === 2) return speed2(s, 0) * (b - a);
1780
2204
  const panels = 8, h = (b - a) / panels;
1781
2205
  let sum = 0;
1782
2206
  for (let k = 0; k < panels; k++) {
1783
2207
  const mid = a + (k + 0.5) * h;
1784
- for (let i = 0; i < GL_X.length; i++) sum += GL_W2[i] * speed(s, mid + GL_X[i] * h / 2);
2208
+ for (let i = 0; i < GL_X.length; i++) sum += GL_W2[i] * speed2(s, mid + GL_X[i] * h / 2);
1785
2209
  }
1786
2210
  return sum * h / 2;
1787
2211
  }
@@ -1796,7 +2220,7 @@ function segmentTAtLength(s, length2) {
1796
2220
  if (Math.abs(err) < 1e-10 * Math.max(1, total)) break;
1797
2221
  if (err > 0) hi = t;
1798
2222
  else lo = t;
1799
- const v = speed(s, t);
2223
+ const v = speed2(s, t);
1800
2224
  let next = v > 0 ? t - err / v : NaN;
1801
2225
  if (!(next > lo && next < hi)) next = (lo + hi) / 2;
1802
2226
  t = next;
@@ -2803,7 +3227,7 @@ function breaksOf(sub3, closed, cosCorner) {
2803
3227
  const n = sub3.length;
2804
3228
  const breaks = /* @__PURE__ */ new Set();
2805
3229
  for (let i = 0; i < n; i++) {
2806
- if (isCorner(sub3, i, closed, cosCorner)) breaks.add(i);
3230
+ if (isCorner2(sub3, i, closed, cosCorner)) breaks.add(i);
2807
3231
  if (isLongStraight(sub3, i, closed)) {
2808
3232
  breaks.add(i);
2809
3233
  breaks.add((i + 1) % n);
@@ -2823,7 +3247,7 @@ function fitStops(sub3, stops, tol, out, closing) {
2823
3247
  if (!last) keepCorner(out[out.length - 1], sub3[stops[k + 1] % n]);
2824
3248
  }
2825
3249
  }
2826
- function isCorner(sub3, i, closed, cosCorner) {
3250
+ function isCorner2(sub3, i, closed, cosCorner) {
2827
3251
  const n = sub3.length;
2828
3252
  const a = sub3[i];
2829
3253
  const prev = sub3[(i - 1 + n) % n];
@@ -2880,7 +3304,7 @@ function unit(p) {
2880
3304
 
2881
3305
  // src/paths/growRegion.ts
2882
3306
  var MAX_DEPTH2 = 6;
2883
- var SAMPLES = [0.2, 0.35, 0.5, 0.65, 0.8];
3307
+ var SAMPLES2 = [0.2, 0.35, 0.5, 0.65, 0.8];
2884
3308
  function growRegion(path, distance, opts = {}) {
2885
3309
  const b = new PathBuilder();
2886
3310
  if (!(distance > 0)) return b.build();
@@ -3053,7 +3477,7 @@ function offsetSeg(seg, ctx, depth) {
3053
3477
  }
3054
3478
  function offsetError(c, q, ctx) {
3055
3479
  let worst2 = 0;
3056
- for (const t of SAMPLES) {
3480
+ for (const t of SAMPLES2) {
3057
3481
  const [dx2, dy2] = derivative(c, t);
3058
3482
  const len = Math.hypot(dx2, dy2);
3059
3483
  if (len < 1e-12) continue;
@@ -3110,6 +3534,243 @@ function emitContour(b, grown) {
3110
3534
  b.close();
3111
3535
  }
3112
3536
 
3537
+ // src/paths/segmentCrossings.ts
3538
+ var STEPS = 16;
3539
+ var END2 = 1e-7;
3540
+ function segmentCrossings(segs) {
3541
+ const flat = segs.map((s) => flatten2(s));
3542
+ const boxes = flat.map(boxOf2);
3543
+ const out = [];
3544
+ for (let a = 0; a < segs.length; a++) {
3545
+ for (let b = a + 1; b < segs.length; b++) {
3546
+ if (!overlaps(boxes[a], boxes[b])) continue;
3547
+ const pa = flat[a], pb = flat[b];
3548
+ for (let i = 0; i + 1 < pa.length; i++) {
3549
+ for (let j = 0; j + 1 < pb.length; j++) {
3550
+ const hit = linesCross(pa[i], pa[i + 1], pb[j], pb[j + 1]);
3551
+ if (hit === null) continue;
3552
+ const guess = [(i + hit[0]) / (pa.length - 1), (j + hit[1]) / (pb.length - 1)];
3553
+ const [s, t] = segs[a].line && segs[b].line ? guess : refine2(segs[a].c, segs[b].c, guess);
3554
+ if (atEnd(s) && atEnd(t)) continue;
3555
+ if (out.some((x) => x.a === a && x.b === b && Math.abs(x.s - s) < 1e-6 && Math.abs(x.t - t) < 1e-6)) continue;
3556
+ out.push({ a, b, s, t });
3557
+ }
3558
+ }
3559
+ }
3560
+ }
3561
+ return out;
3562
+ }
3563
+ var atEnd = (t) => t < END2 || t > 1 - END2;
3564
+ function flatten2(seg) {
3565
+ if (seg.line) return [[seg.c[0], seg.c[1]], [seg.c[6], seg.c[7]]];
3566
+ const out = [];
3567
+ for (let i = 0; i <= STEPS; i++) out.push(cubicEvalAt(...seg.c, i / STEPS));
3568
+ return out;
3569
+ }
3570
+ function boxOf2(pts) {
3571
+ const b = [Infinity, Infinity, -Infinity, -Infinity];
3572
+ for (const [x, y] of pts) {
3573
+ b[0] = Math.min(b[0], x);
3574
+ b[1] = Math.min(b[1], y);
3575
+ b[2] = Math.max(b[2], x);
3576
+ b[3] = Math.max(b[3], y);
3577
+ }
3578
+ return b;
3579
+ }
3580
+ var overlaps = (p, q) => p[0] <= q[2] && q[0] <= p[2] && p[1] <= q[3] && q[1] <= p[3];
3581
+ function linesCross(a, b, c, d) {
3582
+ const rx = b[0] - a[0], ry = b[1] - a[1], sx = d[0] - c[0], sy = d[1] - c[1];
3583
+ const den = rx * sy - ry * sx;
3584
+ if (Math.abs(den) < 1e-18) return null;
3585
+ const u = ((c[0] - a[0]) * sy - (c[1] - a[1]) * sx) / den;
3586
+ const w = ((c[0] - a[0]) * ry - (c[1] - a[1]) * rx) / den;
3587
+ return u >= 0 && u <= 1 && w >= 0 && w <= 1 ? [u, w] : null;
3588
+ }
3589
+ function refine2(a, b, guess) {
3590
+ const gap = (s2, t2) => {
3591
+ const p = cubicEvalAt(...a, s2), q = cubicEvalAt(...b, t2);
3592
+ return [p[0] - q[0], p[1] - q[1]];
3593
+ };
3594
+ let [s, t] = guess;
3595
+ let f = gap(s, t);
3596
+ const start = Math.hypot(...f);
3597
+ for (let k = 0; k < 8 && Math.hypot(...f) > 1e-13; k++) {
3598
+ const da = derivative2(a, s), db = derivative2(b, t);
3599
+ const det = -da[0] * db[1] + db[0] * da[1];
3600
+ if (Math.abs(det) < 1e-18) break;
3601
+ s = Math.min(1, Math.max(0, s - (-db[1] * f[0] + db[0] * f[1]) / det));
3602
+ t = Math.min(1, Math.max(0, t - (-da[1] * f[0] + da[0] * f[1]) / det));
3603
+ f = gap(s, t);
3604
+ }
3605
+ return Math.hypot(...f) <= start ? [s, t] : guess;
3606
+ }
3607
+ function derivative2(c, t) {
3608
+ const u = 1 - t;
3609
+ const a = 3 * u * u, b = 6 * u * t, e = 3 * t * t;
3610
+ return [
3611
+ a * (c[2] - c[0]) + b * (c[4] - c[2]) + e * (c[6] - c[4]),
3612
+ a * (c[3] - c[1]) + b * (c[5] - c[3]) + e * (c[7] - c[5])
3613
+ ];
3614
+ }
3615
+
3616
+ // src/paths/mergeRegion.ts
3617
+ function mergeRegion(path, opts = {}) {
3618
+ const tol = opts.tolerance ?? 0.01;
3619
+ const contours = pathToCubicContours(path).filter((c) => c.segs.length > 0);
3620
+ const segs = contours.flatMap((c) => closeContour(c.segs, tol * 1e-3));
3621
+ const fillLeft = pathSignedArea(path) < 0;
3622
+ const cuts = segs.map(() => []);
3623
+ for (const x of segmentCrossings(segs)) {
3624
+ const p = crossingPoint(segs[x.a], x.s, segs[x.b], x.t);
3625
+ cuts[x.a].push({ t: x.s, p });
3626
+ cuts[x.b].push({ t: x.t, p });
3627
+ }
3628
+ const rings = flattenAll(segs, tol / 4);
3629
+ const pieces = [];
3630
+ let changed = false;
3631
+ segs.forEach((seg, i) => {
3632
+ const parts = cutSegment(seg, cuts[i]);
3633
+ if (parts.length > 1) changed = true;
3634
+ for (const part of parts) {
3635
+ const side = filledSide(part, rings, tol);
3636
+ if (side === null) changed = true;
3637
+ else if (side === fillLeft) pieces.push(part);
3638
+ else {
3639
+ changed = true;
3640
+ pieces.push(reversed2(part));
3641
+ }
3642
+ }
3643
+ });
3644
+ const b = new PathBuilder();
3645
+ if (!changed) {
3646
+ for (const c of contours) emit(b, closeContour(c.segs, tol * 1e-3));
3647
+ return b.build();
3648
+ }
3649
+ for (const ring of chain(pieces)) emit(b, ring);
3650
+ return b.build();
3651
+ }
3652
+ function closeContour(given, snap2) {
3653
+ const short = (s) => s.line && Math.hypot(s.c[6] - s.c[0], s.c[7] - s.c[1]) <= snap2;
3654
+ const segs = given.filter((s) => !short(s));
3655
+ if (segs.length === 0) return [];
3656
+ for (let i = 0; i < segs.length; i++) {
3657
+ const prev = segs[(i + segs.length - 1) % segs.length].c, s = segs[i];
3658
+ if (prev[6] !== s.c[0] || prev[7] !== s.c[1]) {
3659
+ if (Math.hypot(prev[6] - s.c[0], prev[7] - s.c[1]) <= snap2) segs[i] = moveEnds(s, [prev[6], prev[7]], [s.c[6], s.c[7]]);
3660
+ else if (i === 0) segs.push(lineSegment(prev[6], prev[7], s.c[0], s.c[1]));
3661
+ }
3662
+ }
3663
+ return segs;
3664
+ }
3665
+ function crossingPoint(a, s, b, t) {
3666
+ const end = (seg, u) => u <= 1e-7 ? [seg.c[0], seg.c[1]] : u >= 1 - 1e-7 ? [seg.c[6], seg.c[7]] : null;
3667
+ const fixed = end(a, s) ?? end(b, t);
3668
+ if (fixed) return fixed;
3669
+ const p = cubicEvalAt(...a.c, s), q = cubicEvalAt(...b.c, t);
3670
+ return [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];
3671
+ }
3672
+ function cutSegment(seg, cuts) {
3673
+ const inner = cuts.filter((c) => c.t > 1e-7 && c.t < 1 - 1e-7).sort((x, y) => x.t - y.t);
3674
+ const out = [];
3675
+ let rest = seg, from = 0;
3676
+ let start = [seg.c[0], seg.c[1]];
3677
+ for (const cut of inner) {
3678
+ if (cut.t - from < 1e-9) continue;
3679
+ const [l, r] = splitSegment(rest, (cut.t - from) / (1 - from));
3680
+ out.push(moveEnds(l, start, cut.p));
3681
+ rest = r;
3682
+ from = cut.t;
3683
+ start = cut.p;
3684
+ }
3685
+ out.push(moveEnds(rest, start, [seg.c[6], seg.c[7]]));
3686
+ return out;
3687
+ }
3688
+ function moveEnds(s, a, b) {
3689
+ const c = s.c;
3690
+ return {
3691
+ c: [a[0], a[1], c[2] + a[0] - c[0], c[3] + a[1] - c[1], c[4] + b[0] - c[6], c[5] + b[1] - c[7], b[0], b[1]],
3692
+ line: s.line
3693
+ };
3694
+ }
3695
+ function reversed2(s) {
3696
+ const c = s.c;
3697
+ return { c: [c[6], c[7], c[4], c[5], c[2], c[3], c[0], c[1]], line: s.line };
3698
+ }
3699
+ function flattenAll(segs, tol) {
3700
+ const rings = [];
3701
+ let ring = [];
3702
+ for (const { c, line } of segs) {
3703
+ if (ring.length === 0) ring.push(c[0], c[1]);
3704
+ else if (ring[ring.length - 2] !== c[0] || ring[ring.length - 1] !== c[1]) {
3705
+ rings.push(ring);
3706
+ ring = [c[0], c[1]];
3707
+ }
3708
+ if (line) ring.push(c[6], c[7]);
3709
+ else flattenCubic(...c, tol, ring);
3710
+ }
3711
+ if (ring.length > 0) rings.push(ring);
3712
+ return rings;
3713
+ }
3714
+ function winding(rings, x, y) {
3715
+ let w = 0;
3716
+ for (const r of rings) {
3717
+ for (let i = 0; i + 3 < r.length; i += 2) {
3718
+ const ay = r[i + 1], by = r[i + 3];
3719
+ if (ay > y === by > y) continue;
3720
+ const ax = r[i], bx = r[i + 2];
3721
+ if (x >= ax + (y - ay) / (by - ay) * (bx - ax)) continue;
3722
+ w += ay <= y ? 1 : -1;
3723
+ }
3724
+ }
3725
+ return w;
3726
+ }
3727
+ function filledSide(seg, rings, tol) {
3728
+ const c = seg.c;
3729
+ const [x, y] = cubicEvalAt(...c, 0.5);
3730
+ const dx2 = c[6] + c[4] - c[2] - c[0], dy2 = c[7] + c[5] - c[3] - c[1];
3731
+ const len = Math.hypot(dx2, dy2);
3732
+ if (len < 1e-12) return null;
3733
+ const nx = dy2 / len * tol, ny = -dx2 / len * tol;
3734
+ const left = winding(rings, x + nx, y + ny) !== 0, right = winding(rings, x - nx, y - ny) !== 0;
3735
+ return left === right ? null : left;
3736
+ }
3737
+ function chain(pieces) {
3738
+ const key = (x, y) => `${x},${y}`;
3739
+ const from = /* @__PURE__ */ new Map();
3740
+ pieces.forEach((p, i) => {
3741
+ const k = key(p.c[0], p.c[1]);
3742
+ const list = from.get(k);
3743
+ if (list) list.push(i);
3744
+ else from.set(k, [i]);
3745
+ });
3746
+ const used = new Uint8Array(pieces.length);
3747
+ const rings = [];
3748
+ for (let i = 0; i < pieces.length; i++) {
3749
+ if (used[i]) continue;
3750
+ const ring = [];
3751
+ let at2 = i;
3752
+ while (at2 !== void 0 && !used[at2]) {
3753
+ used[at2] = 1;
3754
+ const p = pieces[at2];
3755
+ ring.push(p);
3756
+ at2 = from.get(key(p.c[6], p.c[7]))?.find((j) => !used[j]);
3757
+ }
3758
+ rings.push(ring);
3759
+ }
3760
+ return rings;
3761
+ }
3762
+ function emit(b, ring) {
3763
+ if (ring.length === 0) return;
3764
+ const [x0, y0] = ring[0].c;
3765
+ b.moveTo(x0, y0);
3766
+ for (const [i, { c, line }] of ring.entries()) {
3767
+ if (line && i === ring.length - 1 && c[6] === x0 && c[7] === y0) break;
3768
+ if (line) b.lineTo(c[6], c[7]);
3769
+ else b.curveTo(c[2], c[3], c[4], c[5], c[6], c[7]);
3770
+ }
3771
+ b.close();
3772
+ }
3773
+
3113
3774
  // src/paths/simplifyAnchors.ts
3114
3775
  function simplifyAnchorsAt(set, flatIndexes, tolerance, opts = {}) {
3115
3776
  const bySub = /* @__PURE__ */ new Map();
@@ -3164,7 +3825,7 @@ var unit3 = (x, y) => {
3164
3825
  const len = Math.hypot(x, y);
3165
3826
  return len === 0 ? { x: 0, y: 0 } : { x: x / len, y: y / len };
3166
3827
  };
3167
- function derivative2(seg, t) {
3828
+ function derivative3(seg, t) {
3168
3829
  const u = 1 - t;
3169
3830
  const { a, c1, c2, b } = seg;
3170
3831
  return {
@@ -3174,8 +3835,8 @@ function derivative2(seg, t) {
3174
3835
  }
3175
3836
  function headingOn(seg, t) {
3176
3837
  if (seg.straight) return unit3(seg.b.x - seg.a.x, seg.b.y - seg.a.y);
3177
- let d = derivative2(seg, t);
3178
- if (Math.hypot(d.x, d.y) < 1e-9) d = derivative2(seg, t < 0.5 ? t + 1e-3 : t - 1e-3);
3838
+ let d = derivative3(seg, t);
3839
+ if (Math.hypot(d.x, d.y) < 1e-9) d = derivative3(seg, t < 0.5 ? t + 1e-3 : t - 1e-3);
3179
3840
  const h = unit3(d.x, d.y);
3180
3841
  return h.x === 0 && h.y === 0 ? unit3(seg.b.x - seg.a.x, seg.b.y - seg.a.y) : h;
3181
3842
  }
@@ -4338,7 +4999,7 @@ function smudgePath(path, opts) {
4338
4999
  if (!(radius > 0) || strength === 0 || delta.x === 0 && delta.y === 0) return path;
4339
5000
  const detail = opts.detail ?? radius / 3;
4340
5001
  return editAnchorSet(path, (set) => {
4341
- refine2(set, center, radius, detail);
5002
+ refine3(set, center, radius, detail);
4342
5003
  let touched = false;
4343
5004
  let flat = 0;
4344
5005
  for (const sub3 of set.anchors) {
@@ -4358,7 +5019,7 @@ function weight(p, c, radius) {
4358
5019
  const u2 = ((p.x - c.x) ** 2 + (p.y - c.y) ** 2) / (radius * radius);
4359
5020
  return u2 >= 1 ? 0 : (1 - u2) ** 2;
4360
5021
  }
4361
- function refine2(set, c, radius, detail) {
5022
+ function refine3(set, c, radius, detail) {
4362
5023
  if (!Number.isFinite(detail) || detail <= 0) return;
4363
5024
  let added = 0;
4364
5025
  for (let sub3 = 0; sub3 < set.anchors.length; sub3++) {
@@ -5579,7 +6240,8 @@ function constrainPathsEdit(items, links = []) {
5579
6240
  const sliding = new Set(kept.flatMap((c) => c.kind === "onPath" ? [c.anchor] : []));
5580
6241
  const still = (i) => {
5581
6242
  const { item } = itemOf(i, nowStart);
5582
- return items.length > 1 && items[item].before === items[item].after && !sliding.has(i);
6243
+ const it = items[item];
6244
+ return items.length > 1 && it.before === it.after && !it.yields && !sliding.has(i);
5583
6245
  };
5584
6246
  const held = nowAll.flatMap((p, i) => {
5585
6247
  const r = reference[i];
@@ -6001,6 +6663,6 @@ function typedArrayReplacer(_key, value) {
6001
6663
  return tagTypedArray(value) ?? value;
6002
6664
  }
6003
6665
 
6004
- export { ANCHOR_FIELDS, CONSTRAINT_TOLERANCE, EASINGS, PATH_SNAP_KINDS, SPRING_PRESETS, TYPED_ARRAY_CONSTRUCTORS, ZOOM_FLOOR, addExtremaAnchors, anchorAt, anchorCount, anchorOriginsOf, anchorSymmetries, anchorSymmetry, anchorsBefore, anchorsInRect, anchorsUnderBrush, anchorsWhere, applyToPoint, averageAnchorsAt, balanceFactorToward, bendAlongPath, bendSegment, blendPaths, boxToBox, circlePath, clampRectWithin, closestOpenEnds, composePath, conflictingConstraints, constrainPathEdit, constrainPathsEdit, constraintAnchors, constraintApplies, constraintFits, convertSegments, cornerAnchors, createPathSnapIndex, cubicBezierEasing, cubicContoursToPath, decomposePath, deleteAnchorsAt, directionMarks, 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, editAnchorSetTracked, equalizeSegmentHandles, everyNthAnchor, extendToMeet, findPathSnap, findPathSnaps, fitAffine, fitCubicThroughDeletion, flatAnchorIndex, flatIndexesAfterReverse, growAnchorSelection, growRegion, handleAlongItsAngle, handleAtItsLength, handleBalances, harmonizeAnchors, hasOpenSubpath, identity, insertAnchorOnSegment, intersectSegments, invert, invertAnchorSelection, isSegmentStraight, joinAnchorsAt, joinSubpathEnds, lengthToward, lengthenOpenEnd, lengthenPathEnd, linear, locateAnchor, makeHandlesSymmetric, mapConstraintAnchors, mat3, meanScale, measureAnchorPath, measurePath, moveHandleSymmetric, moveHandleTo, multiply, nearestPathAnchor, normalizeZoom, offsetCornerTraces, openEndAt, openEndRay, openPathEnds, openSubpathAt, pathAnchorPoints, pathAwayFromAnchors, pathContainsPoint, pathContainsPolygon, pathContainsRect, pathCrossings, pathDistanceToPoint, pathEndAnchorIndex, pathEndFrame, pathFromD, pathInPoseFrame, pathInWorld, pathIntersectsPolygon, pathIntersectsRect, pathSegments, pathSignedArea, pathThroughPoseFrame, pathToCubicContours, placeRect, pointAlongPath, pointInPath, polygonContainsPath, polygonIntersectsPath, portControls2 as portControls, portCurvePoints2 as portCurvePoints, poseFrameInverseOf, poseFrameOf, poseLinearOf, poseRotationOf, prepareBlend, pxExtent, readAnchorFields, rectMarkerPath, remapAnchorValues, remapConstraints, remapPointValues, repeatAlongPath, resamplePath, resolveEasing, retractHandles, reverseAnchors, reversePath, reverseSubpaths, reviveTypedArrays, rotate, rotateAboutPoint, rotatePathAround, roundRectPath, scale, scaleDelta, scalePathToBounds, scaleSegmentHandles, screenAngleOf, segmentAt2 as segmentAt, segmentBetween, segmentExtremaTs, segmentsBetweenAnchors, setSubpathStart, shrinkAnchorSelection, simplifyAnchorsAt, simplifyPath, simplifySpan, simplifySubpath, smoothAnchors, smudgePath, softFalloff, softSelectionBand, softSelectionWeights, solveConstraints, solvePathConstraints, splitByClosure, splitSubpaths, squarePath, standoff, stationsAlongPath, strokeHitTest, subpathEndAt, subpathsOfAnchors, tagTypedArray, tagTypedArrays, transformCoords, transformPath, translate, translateAnchorBy, translateAnchorsSymmetric, translatePath, trimPathAt, typedArrayReplacer, unionBoundsPath, unsatisfiedConstraints, untagTypedArray, viewToMat3, withAnchorPoints, withinPxBox, withinPxRadius, worldEditToStorage, writeAnchorField };
6666
+ export { ANCHOR_FIELDS, CONSTRAINT_TOLERANCE, EASINGS, PATH_SNAP_KINDS, SPRING_PRESETS, TYPED_ARRAY_CONSTRUCTORS, ZOOM_FLOOR, addExtremaAnchors, anchorAt, anchorCount, anchorOriginsOf, anchorSymmetries, anchorSymmetry, anchorsBefore, anchorsInRect, anchorsUnderBrush, anchorsWhere, applyToPoint, arcShape, averageAnchorsAt, balanceFactorToward, bendAlongPath, bendSegment, blendPaths, boxToBox, circlePath, clampRectWithin, closestOpenEnds, composePath, conflictingConstraints, constrainPathEdit, constrainPathsEdit, constraintAnchors, constraintApplies, constraintFits, convertSegments, cornerAnchors, createPathSnapIndex, cubicBezierEasing, cubicContoursToPath, decomposePath, deleteAnchorsAt, directionMarks, 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, editAnchorSetTracked, ellipseShape, equalizeSegmentHandles, everyNthAnchor, extendToMeet, findPathSnap, findPathSnaps, fitAffine, fitCubicThroughDeletion, flatAnchorIndex, flatIndexesAfterReverse, growAnchorSelection, growRegion, handleAlongItsAngle, handleAtItsLength, handleBalances, harmonizeAnchors, hasOpenSubpath, identity, insertAnchorOnSegment, intersectSegments, invert, invertAnchorSelection, isSegmentStraight, joinAnchorsAt, joinSubpathEnds, lengthToward, lengthenOpenEnd, lengthenPathEnd, lineShape, linear, locateAnchor, makeHandlesSymmetric, mapConstraintAnchors, mat3, meanScale, measureAnchorPath, measurePath, mergeRegion, moveHandleSymmetric, moveHandleTo, multiply, nearestPathAnchor, normalizeZoom, offsetCornerTraces, openEndAt, openEndRay, openPathEnds, openSubpathAt, pathAnchorPoints, pathAwayFromAnchors, pathContainsPoint, pathContainsPolygon, pathContainsRect, pathCrossings, pathDistanceToPoint, pathEndAnchorIndex, pathEndFrame, pathFromD, pathInPoseFrame, pathInWorld, pathIntersectsPolygon, pathIntersectsRect, pathSegments, pathShape, pathSignedArea, pathThroughPoseFrame, pathToCubicContours, pathToD, placeAlongRun, placeRect, pointAlongPath, pointInPath, polygonContainsPath, polygonIntersectsPath, portControls2 as portControls, portCurvePoints2 as portCurvePoints, poseFrameInverseOf, poseFrameOf, poseLinearOf, poseRotationOf, prepareBlend, pxExtent, readAnchorFields, rectMarkerPath, remapAnchorValues, remapConstraints, remapPointValues, repeatAlongPath, resamplePath, resolveEasing, retractHandles, reverseAnchors, reversePath, reverseSubpaths, reviveTypedArrays, rotate, rotateAboutPoint, rotatePathAround, roundRectPath, runLength, runPlaces, runStationAt, scale, scaleDelta, scalePathToBounds, scaleSegmentHandles, screenAngleOf, segmentAt2 as segmentAt, segmentBetween, segmentExtremaTs, segmentsBetweenAnchors, setSubpathStart, shrinkAnchorSelection, simplifyAnchorsAt, simplifyPath, simplifySpan, simplifySubpath, smoothAnchors, smudgePath, softFalloff, softSelectionBand, softSelectionWeights, solveConstraints, solvePathConstraints, splitByClosure, splitSubpaths, squarePath, standoff, stationsAlongPath, strokeHitTest, subpathEndAt, subpathsOfAnchors, tagTypedArray, tagTypedArrays, trackRun, trackShapeD, transformCoords, transformPath, translate, translateAnchorBy, translateAnchorsSymmetric, translatePath, trimPathAt, typedArrayReplacer, unionBoundsPath, unsatisfiedConstraints, untagTypedArray, viewToMat3, withAnchorPoints, withinPxBox, withinPxRadius, worldEditToStorage, writeAnchorField };
6005
6667
  //# sourceMappingURL=index.js.map
6006
6668
  //# sourceMappingURL=index.js.map