@weasel-js/geom 1.15.2 → 1.16.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,18 +1,23 @@
1
- import { carryCorners, parseSubpaths, intersectSegments, evalSeg, keepCorners, anchorRange, subSeg, writePath, derivSeg } from './chunk-OQP5M5VD.js';
2
- export { CORNER_RESTORE_TOLERANCE, cornerTraces, intersectSegments, keepCorners, restoreCorners } from './chunk-OQP5M5VD.js';
1
+ import { carryCorners, parseSubpaths, intersectSegments, evalSeg, keepCorners, anchorRange, subSeg, writePath, derivSeg } from './chunk-OBPXEPUH.js';
2
+ export { CORNER_RESTORE_TOLERANCE, cornerTraces, intersectSegments, keepCorners, restoreCorners } from './chunk-OBPXEPUH.js';
3
+ import { lengthTable, closingAnchor, tableInternals, segmentArc, pointOnSegment } from './chunk-FOXMA54X.js';
4
+ export { anchorAtLength, closingAnchor, lengthAtAnchor, lengthAtSegmentFraction, lengthAtSubpath, lengthTable, pointAtLength } from './chunk-FOXMA54X.js';
3
5
  import { createBvh } from './chunk-XXNK4N7Z.js';
4
6
  import { PORT_REACH, portControls, portCurvePoints } from './chunk-YQADYJJ7.js';
5
7
  export { PORT_REACH } from './chunk-YQADYJJ7.js';
6
- import { wrapPositive, eulerAngleAt, eulerSegmentCubic, eulerCubicError, integrateEuler, eulerPointAt, eulerCurvatureAt, DEG_TO_RAD, cubicDerivativeAt, cubicCurvatureAt } from './chunk-MOCSUHEI.js';
8
+ import { wrapPositive, eulerAngleAt, eulerSegmentCubic, eulerCubicError, integrateEuler, eulerPointAt, eulerCurvatureAt, DEG_TO_RAD, RAD_TO_DEG, cubicDerivativeAt, cubicCurvatureAt } from './chunk-MOCSUHEI.js';
7
9
  export { DEG_TO_RAD, RAD_TO_DEG, SPIRO_KINDS, combTeeth, cubicCurvatureAt, cubicCurvatureSource, cubicDerivativeAt, curvatureComb, eulerAngleAt, eulerCurvatureAt, eulerCurvatureDerivativesAt, eulerPointAt, fitEulerSegment, isSpiroKind, placeEulerSegment, solveSpiro, spiroContoursFromControl, spiroContoursOf, spiroControlPath, spiroToPath, wrapAngle, wrapPositive } from './chunk-MOCSUHEI.js';
8
- import { extractPolylines } from './chunk-IDSKOHA4.js';
9
- import { boundsOfPath, fitPointRun, keepCorner } from './chunk-STYO7J4Y.js';
10
- export { boundsOfPath } from './chunk-STYO7J4Y.js';
10
+ import { extractPolylines, polylineAtCurve } from './chunk-5W425HJA.js';
11
+ import './chunk-QSVZO7QZ.js';
12
+ import { boundsOfPath, fitPointRun, keepCorner } from './chunk-ZV24QZ6X.js';
13
+ export { boundsOfPath } from './chunk-ZV24QZ6X.js';
11
14
  import { countPathAnchors, pathToAnchors, anchorsToPath, isAnchorSmooth, segmentEnds, isStraightSegment, nearestSegmentT } from './chunk-HTT5AQIU.js';
12
15
  export { anchorsToPath, countPathAnchors, isAnchorSmooth, isStraightSegment, nearestSegmentT, pathToAnchors, segmentEnds } from './chunk-HTT5AQIU.js';
13
- import { resolveCorners, pointInPolygon, cornerCuts, fillsOpenSubpaths, schneiderFit, fitCubicWithTangents, flattenCubic, flattenQuadratic, DEFAULT_FLATTEN_TOLERANCE, segmentsCross, tangentAt } from './chunk-GA5R5KWD.js';
14
- 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, remapPerAnchor, resolveCorners, resolveCornersWithSources, ringSignedArea, schneiderFit, segmentsCross, setDefaultOpenFill } from './chunk-GA5R5KWD.js';
15
- import { nearestOnLine, worldCoords, PATH_M, PATH_L, PATH_C, PATH_Q, PATH_Z, PathBuilder, withCorner, scaleCorners, originFor, coordsFrom, forEachSegment, pathCommandCoordCount, withoutCorners, elevateQuadraticToCubic, cubicEvalAt, nearestOnPath, cubicPointAt, splitCubicAtT, nearestOnCubic, cubicAxisExtremaTs, rectToContour, pointSegmentDist2, lerpPoint, distance, splitCubicAt, nearestOnQuadratic, boxesIntersect } from './chunk-346XYYMX.js';
16
+ import { resolveCornersWithSources, resolveCorners, pointInPolygon, cornerCuts, fillsOpenSubpaths, schneiderFit, fitCubicWithTangents, flattenCubic, DEFAULT_FLATTEN_TOLERANCE, segmentsCross, flattenQuadratic, tangentAt } from './chunk-TBS4NHGQ.js';
17
+ 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, remapPerAnchor, resolveCorners, resolveCornersWithSources, ringSignedArea, schneiderFit, segmentsCross, setDefaultOpenFill } from './chunk-TBS4NHGQ.js';
18
+ import { lookupLength, lookupT, flatPoints, segmentLength, segmentTAtLength } from './chunk-IOD2NFHQ.js';
19
+ export { segmentLength, segmentTAtLength } from './chunk-IOD2NFHQ.js';
20
+ import { nearestOnLine, forEachSegment, PATH_M, PATH_L, PATH_Q, PATH_C, worldCoords, PATH_Z, PathBuilder, withCorner, scaleCorners, originFor, coordsFrom, pathCommandCoordCount, withoutCorners, elevateQuadraticToCubic, cubicEvalAt, nearestOnPath, cubicPointAt, splitCubicAtT, nearestOnCubic, cubicAxisExtremaTs, rectToContour, pointSegmentDist2, lerpPoint, distance, originOf, splitCubicAt, nearestOnQuadratic, boxesIntersect } from './chunk-346XYYMX.js';
16
21
  export { ORIGIN_THRESHOLD, PATH_C, PATH_CMD_LENGTHS, PATH_COMMANDS, PATH_L, PATH_M, PATH_Q, PATH_Z, PathBuilder, addOrigin, asPolygonPath, boundsOfCoords, boxContainsBox, boxContainsPoint, boxesIntersect, compactCorners, coordsFrom, cornerAt, cornerSpecAt, cubicAxisExtremaTs, cubicBounds, cubicEvalAt, cubicPointAt, distance, distance2, elevateQuadraticToCubic, ellipsePath, forEachSegment, hasCorners, joinPaths, lerpCorners, lerpPoint, linePath, nearestOnCubic, nearestOnLine, nearestOnPath, nearestOnQuadratic, originFor, originOf, pathCommandCoordCount, pathPoint, pointSegmentDist, pointSegmentDist2, polygonFromPoints, polylineFromPoints, quadraticEvalAt, rectContainsPoint, rectContainsPointHalfOpen, rectPath, rectToContour, rectsIntersect, rectsOverlap, regularPolygonPath, scaleCorners, segmentT, splitCubicAt, splitCubicAtT, splitLineAt, splitQuadraticAt, starPath, unionBox, unionRects, withCorner, withoutCorners, worldCoords } from './chunk-346XYYMX.js';
17
22
  import { SINGULAR_RATIO, clamp } from './chunk-DNX24KBT.js';
18
23
  export { EPS, approxEq, clamp, clamp01, cross, dot, len2, lerp, sign, sub } from './chunk-DNX24KBT.js';
@@ -182,7 +187,7 @@ var speed = ({ a, b, t0, sign: sign2 }, u) => {
182
187
  const t = (t0 + sign2 * u) * DEG_TO_RAD;
183
188
  return Math.hypot(a * Math.cos(t), b * Math.sin(t)) * DEG_TO_RAD;
184
189
  };
185
- function lengthTable(e, sweep) {
190
+ function lengthTable2(e, sweep) {
186
191
  const simpson = (u0, u1) => {
187
192
  const h = (u1 - u0) / PANELS;
188
193
  let sum = speed(e, u0) + speed(e, u1);
@@ -261,7 +266,7 @@ function ellipseShape(spec) {
261
266
  const e = { center, a, b, t0: param(spec.start), sign: sign2 };
262
267
  const raw = wrapPositive(sign2 * (param(spec.end) - e.t0), 360);
263
268
  const sweep = raw === 0 ? 360 : raw;
264
- const { length, lengthTo, offsetAt } = lengthTable(e, sweep);
269
+ const { length, lengthTo, offsetAt } = lengthTable2(e, sweep);
265
270
  const offsetPast = (f) => {
266
271
  if (raw === 0) return offsetAt((f % 1 + 1) % 1);
267
272
  let u = f * sweep;
@@ -300,40 +305,78 @@ function ellipseShape(spec) {
300
305
  return shape;
301
306
  }
302
307
 
308
+ // src/along/pathTable.ts
309
+ var tables = /* @__PURE__ */ new WeakMap();
310
+ function pathLengthTable(path) {
311
+ const hit = tables.get(path);
312
+ if (hit) return hit;
313
+ let table;
314
+ if (path.kind === "rect") {
315
+ table = lengthTable(extractPolylines(path), 2);
316
+ } else {
317
+ const { path: resolved, anchorSources } = resolveCornersWithSources(path);
318
+ const controls2 = controlsByAnchor(resolved);
319
+ table = lengthTable(extractPolylines(resolved).map((pl) => anchorRun(pl, controls2)), 2, anchorSources);
320
+ }
321
+ tables.set(path, table);
322
+ return table;
323
+ }
324
+ function controlsByAnchor(path) {
325
+ const out = [];
326
+ const { commands, coords } = path;
327
+ forEachSegment(commands, coords, (cmd, ci) => {
328
+ if (cmd === PATH_M || cmd === PATH_L) out.push(null);
329
+ else if (cmd === PATH_Q) out.push([coords[ci], coords[ci + 1]]);
330
+ else if (cmd === PATH_C) out.push([coords[ci], coords[ci + 1], coords[ci + 2], coords[ci + 3]]);
331
+ });
332
+ return out;
333
+ }
334
+ function anchorRun(pl, controls2) {
335
+ const n = pl.points.length / 2;
336
+ const { anchorA: A, anchorB: B } = pl;
337
+ const points = [];
338
+ const ids = [];
339
+ for (let i = 0; i < n; i++) {
340
+ if (A && B && A[i] !== B[i]) continue;
341
+ points.push(pl.points[i * 2], pl.points[i * 2 + 1]);
342
+ ids.push(A ? A[i] : i);
343
+ }
344
+ const segControls = ids.slice(1).map((id) => controls2[id] ?? null);
345
+ const close = closingAnchor(pl);
346
+ if (pl.closed && n >= 2) segControls.push(close === void 0 ? null : controls2[close] ?? null);
347
+ const anchors = Uint32Array.from(ids);
348
+ return {
349
+ points,
350
+ closed: pl.closed,
351
+ anchorA: anchors,
352
+ anchorB: anchors.slice(),
353
+ anchorT: new Float32Array(ids.length),
354
+ ...close !== void 0 ? { closeAnchor: close } : {},
355
+ controls: segControls
356
+ };
357
+ }
358
+
303
359
  // src/paths/pathMeasure.ts
304
360
  function measurePath(path, opts = {}) {
305
361
  const polylines = extractPolylines(
306
362
  path,
307
363
  opts.flattenTolerance === void 0 ? {} : { flattenTolerance: opts.flattenTolerance }
308
364
  );
365
+ const table = pathLengthTable(path);
366
+ const cornerTolerance = opts.keepCorners ? opts.flattenTolerance ?? 0.5 : null;
309
367
  const segments = [];
310
- let total = 0;
311
368
  let first;
312
369
  let contours = 0;
313
370
  let lastClosed = false;
314
- for (const pl of polylines) {
315
- const count = pl.points.length / 2;
316
- if (count === 0) continue;
371
+ polylines.forEach((pl, r) => {
372
+ if (pl.points.length === 0) return;
317
373
  contours++;
318
374
  lastClosed = pl.closed;
319
375
  first ??= { x: pl.points[0], y: pl.points[1] };
320
376
  const startIndex = segments.length;
321
- const last = pl.closed ? count : count - 1;
322
- for (let i = 0; i < last; i++) {
323
- const j = (i + 1) % count;
324
- const ax = pl.points[i * 2];
325
- const ay = pl.points[i * 2 + 1];
326
- const bx = pl.points[j * 2];
327
- const by = pl.points[j * 2 + 1];
328
- const len = Math.hypot(bx - ax, by - ay);
329
- if (len === 0) continue;
330
- const tx = (bx - ax) / len;
331
- const ty = (by - ay) / len;
332
- segments.push({ ax, ay, bx, by, start: total, len, tx, ty, t0x: tx, t0y: ty, t1x: tx, t1y: ty });
333
- total += len;
334
- }
335
- smoothVertexHeadings(segments, startIndex, pl.closed, opts.keepCorners ? opts.flattenTolerance ?? 0.5 : null);
336
- }
377
+ pushChords(pl, r, table, segments);
378
+ smoothVertexHeadings(segments, startIndex, pl.closed, cornerTolerance);
379
+ });
337
380
  if (first === void 0) return null;
338
381
  const origin = first;
339
382
  const closed = contours === 1 && lastClosed;
@@ -350,15 +393,20 @@ function measurePath(path, opts = {}) {
350
393
  })
351
394
  };
352
395
  }
396
+ const total = table.total;
397
+ const pointAt2 = (seg, s) => {
398
+ const { point } = pointOnSegment(table, seg.run, seg.seg, s);
399
+ return { x: point[0], y: point[1] };
400
+ };
353
401
  return {
354
402
  length: total,
355
403
  closed,
356
404
  at(s, smooth = false) {
357
405
  const target = Math.min(Math.max(s, 0), total);
358
406
  const seg = segments[segmentAt(segments, target)];
359
- const u = Math.min(Math.max((target - seg.start) / seg.len, 0), 1);
360
- const point = { x: seg.ax + (seg.bx - seg.ax) * u, y: seg.ay + (seg.by - seg.ay) * u };
407
+ const point = pointAt2(seg, target);
361
408
  if (!smooth) return { point, tangent: { x: seg.tx, y: seg.ty } };
409
+ const u = seg.arc > 0 ? Math.min(Math.max((target - seg.start) / seg.arc, 0), 1) : 0;
362
410
  const x = seg.t0x + (seg.t1x - seg.t0x) * u;
363
411
  const y = seg.t0y + (seg.t1y - seg.t0y) * u;
364
412
  const l = Math.hypot(x, y);
@@ -377,15 +425,58 @@ function measurePath(path, opts = {}) {
377
425
  bestDist = d;
378
426
  }
379
427
  }
380
- return {
381
- s: best.start + best.len * bestU,
382
- dist: bestDist,
383
- point: { x: best.ax + (best.bx - best.ax) * bestU, y: best.ay + (best.by - best.ay) * bestU },
384
- tangent: { x: best.tx, y: best.ty }
385
- };
428
+ const s = best.start + best.arc * bestU;
429
+ const point = pointAt2(best, s);
430
+ return { s, dist: Math.hypot(p.x - point.x, p.y - point.y), point, tangent: { x: best.tx, y: best.ty } };
386
431
  }
387
432
  };
388
433
  }
434
+ function pushChords(pl, r, table, segments) {
435
+ const c = tableInternals(table).cum[r];
436
+ const { points, anchorA: A, anchorB: B, anchorT: T } = pl;
437
+ const count = points.length / 2;
438
+ const segOf = new Int32Array(count);
439
+ const lenOf = new Float64Array(count);
440
+ let k = -1;
441
+ for (let i = 0; i < count; i++) {
442
+ const aligned = !A || !B || A[i] === B[i];
443
+ if (aligned) k++;
444
+ const at2 = Math.max(k, 0);
445
+ segOf[i] = Math.min(at2, c.length - 2);
446
+ lenOf[i] = aligned || !T ? c[at2] : c[at2] + T[i] * (c[at2 + 1] - c[at2]);
447
+ }
448
+ const last = pl.closed ? count : count - 1;
449
+ for (let i = 0; i < last; i++) {
450
+ const j = (i + 1) % count;
451
+ const ax = points[i * 2];
452
+ const ay = points[i * 2 + 1];
453
+ const bx = points[j * 2];
454
+ const by = points[j * 2 + 1];
455
+ const len = Math.hypot(bx - ax, by - ay);
456
+ if (len === 0) continue;
457
+ const tx = (bx - ax) / len;
458
+ const ty = (by - ay) / len;
459
+ const start = lenOf[i];
460
+ const arc2 = (j === 0 ? c[c.length - 1] : lenOf[j]) - start;
461
+ segments.push({
462
+ ax,
463
+ ay,
464
+ bx,
465
+ by,
466
+ len,
467
+ start,
468
+ arc: arc2,
469
+ run: r,
470
+ seg: segOf[i],
471
+ tx,
472
+ ty,
473
+ t0x: tx,
474
+ t0y: ty,
475
+ t1x: tx,
476
+ t1y: ty
477
+ });
478
+ }
479
+ }
389
480
  function smoothVertexHeadings(segments, from, closed, cornerTolerance) {
390
481
  const n = segments.length - from;
391
482
  if (n < 2) return;
@@ -468,7 +559,7 @@ function trackRun(shape, from = 0, to = 1, options = {}) {
468
559
  const at2 = (g, options2) => {
469
560
  const s = options2?.open ? shape.at(toShape(g), options2) : shape.at(toShape(clamp(g, 0, 1)));
470
561
  const dir = reading(s.normal);
471
- const angle = Math.atan2(dir.y, dir.x) * 180 / Math.PI;
562
+ const angle = Math.atan2(dir.y, dir.x) * RAD_TO_DEG;
472
563
  const normal2 = { x: sgn * s.normal.x, y: sgn * s.normal.y };
473
564
  return {
474
565
  point: s.point,
@@ -745,6 +836,52 @@ function portCurvePoints2(ax, ay, bx, by, outNormal, inNormal, samples, reach =
745
836
  );
746
837
  }
747
838
 
839
+ // src/along/curvePosition.ts
840
+ function runSegments(t, r) {
841
+ return tableInternals(t).cum[r].length - 1;
842
+ }
843
+ function curveSpan(t, span) {
844
+ const from = Math.max(span.from, 0);
845
+ const to = Math.min(span.to, t.total);
846
+ if (!(from < to)) return null;
847
+ const a = curveAtLength(t, from, true);
848
+ const b = curveAtLength(t, to, false);
849
+ return a && b ? { from: a, to: b } : null;
850
+ }
851
+ function lengthAtCurve(t, at2) {
852
+ const { cum } = tableInternals(t);
853
+ const r = Math.min(Math.max(at2.run, 0), t.runs.length - 1);
854
+ if (r < 0) return 0;
855
+ const c = cum[r];
856
+ const segs = c.length - 1;
857
+ if (segs <= 0) return c[0];
858
+ const k = Math.min(Math.max(Math.floor(at2.segment), 0), segs - 1);
859
+ const f = Math.min(Math.max(at2.t, 0), 1);
860
+ const len = c[k + 1] - c[k];
861
+ const arc2 = f <= 0 || f >= 1 || !(len > 0) ? null : segmentArc(t, r, k);
862
+ if (!arc2) return c[k] + f * len;
863
+ return c[k] + Math.min(lookupLength(arc2.coords, t.dim, arc2.lookup, f), len);
864
+ }
865
+ function curveAtLength(t, s, starts2) {
866
+ const { cum } = tableInternals(t);
867
+ for (let r = 0; r < cum.length; r++) {
868
+ const c = cum[r];
869
+ const end = c[c.length - 1];
870
+ if (c.length < 2 || !(end > c[0]) || (starts2 ? s >= end : s > end)) continue;
871
+ for (let k = 0; k < c.length - 1; k++) {
872
+ const len = c[k + 1] - c[k];
873
+ if (!(len > 0) || (starts2 ? s >= c[k + 1] : s > c[k + 1])) continue;
874
+ return { run: r, segment: k, t: paramAt(t, r, k, s - c[k], len) };
875
+ }
876
+ }
877
+ return null;
878
+ }
879
+ function paramAt(t, r, k, local, len) {
880
+ const arc2 = segmentArc(t, r, k);
881
+ if (!arc2) return Math.min(Math.max(local / len, 0), 1);
882
+ return lookupT(arc2.coords, t.dim, arc2.lookup, local);
883
+ }
884
+
748
885
  // src/pathSubpaths.ts
749
886
  function flatSubpaths(path, tolerance) {
750
887
  const drawn = resolveCorners(path);
@@ -813,6 +950,71 @@ function closedSubpaths(path, opts) {
813
950
  return regionSubpaths(path, opts).closed;
814
951
  }
815
952
 
953
+ // src/outlineSegments.ts
954
+ function someSegment(path, tolerance, span, visit) {
955
+ if (path.kind === "rect") return someRectEdge(path, span, visit);
956
+ if (!span) return someFlatEdge(path, tolerance, visit);
957
+ const o = originOf(path);
958
+ const polylines = extractPolylines(path, { flattenTolerance: tolerance ?? DEFAULT_FLATTEN_TOLERANCE });
959
+ const { runs } = pathLengthTable(path);
960
+ for (let r = Math.max(span.from.run, 0); r <= Math.min(span.to.run, polylines.length - 1); r++) {
961
+ const pl = polylines[r];
962
+ const place = (at2) => polylineAtCurve(pl, at2.segment, at2.t, runs[r]);
963
+ const head = r === span.from.run ? place(span.from) : null;
964
+ const tail = r === span.to.run ? place(span.to) : null;
965
+ const from = head?.length ?? 0;
966
+ const to = tail?.length ?? Infinity;
967
+ if (!(from < to)) continue;
968
+ const p = pl.points;
969
+ const n = p.length / 2;
970
+ const end = pl.closed && n >= 2 ? n : n - 1;
971
+ let s = 0;
972
+ for (let i = 0; i < end; i++) {
973
+ const j = (i + 1) % n;
974
+ const ax = p[i * 2], ay = p[i * 2 + 1];
975
+ const bx = p[j * 2], by = p[j * 2 + 1];
976
+ const s0 = s;
977
+ s += Math.hypot(bx - ax, by - ay);
978
+ if (head && s <= from) continue;
979
+ if (tail && s0 >= to) break;
980
+ const [x0, y0] = head && s0 < from ? [head.x, head.y] : [ax, ay];
981
+ const [x1, y1] = tail && s > to ? [tail.x, tail.y] : [bx, by];
982
+ if (visit(x0 + o.x, y0 + o.y, x1 + o.x, y1 + o.y)) return true;
983
+ }
984
+ }
985
+ return false;
986
+ }
987
+ function someFlatEdge(path, tolerance, visit) {
988
+ const { closed, open } = flatSubpaths(path, tolerance ?? DEFAULT_FLATTEN_TOLERANCE);
989
+ const run2 = (sub3, wrap) => {
990
+ const n = sub3.length;
991
+ const last = wrap ? n : n - 2;
992
+ for (let i = 0; i < last; i += 2) {
993
+ const j = (i + 2) % n;
994
+ if (visit(sub3[i], sub3[i + 1], sub3[j], sub3[j + 1])) return true;
995
+ }
996
+ return false;
997
+ };
998
+ return closed.some((sub3) => run2(sub3, true)) || open.some((sub3) => run2(sub3, false));
999
+ }
1000
+ function someRectEdge(rect, span, visit) {
1001
+ const { x, y, width: w, height: h } = rect;
1002
+ const cx = [x, x + w, x + w, x];
1003
+ const cy = [y, y, y + h, y + h];
1004
+ const lo = span ? span.from : { run: 0, segment: 0, t: 0 };
1005
+ const hi = span ? span.to : { run: 0, segment: 3, t: 1 };
1006
+ if (lo.run !== 0 || hi.run !== 0) return false;
1007
+ for (let i = Math.max(lo.segment, 0); i <= Math.min(hi.segment, 3); i++) {
1008
+ const j = i + 1 & 3;
1009
+ const t0 = i === lo.segment ? lo.t : 0;
1010
+ const t1 = i === hi.segment ? hi.t : 1;
1011
+ if (!(t1 > t0) && lo.segment === hi.segment) return false;
1012
+ const at2 = (f) => [cx[i] + (cx[j] - cx[i]) * f, cy[i] + (cy[j] - cy[i]) * f];
1013
+ if (visit(...at2(t0), ...at2(t1))) return true;
1014
+ }
1015
+ return false;
1016
+ }
1017
+
816
1018
  // src/screenBand.ts
817
1019
  function stretchOf(m) {
818
1020
  const e = (m.a + m.d) / 2;
@@ -944,30 +1146,16 @@ function strokeHitTest(path, x, y, threshold, opts = {}) {
944
1146
  const st = stretchOf(m);
945
1147
  if (st.s1 === 0) return false;
946
1148
  if (isSimilarity(st)) {
947
- return strokeHitTest(path, x, y, threshold + slop.px / st.s1, { tolerance: opts.tolerance });
1149
+ return strokeHitTest(path, x, y, threshold + slop.px / st.s1, { tolerance: opts.tolerance, span: opts.span });
948
1150
  }
949
1151
  const sx = a * x + c * y, sy = b * x + d * y;
950
- return someSegment(path, opts.tolerance, (ax, ay, bx, by) => capsuleWithinPx(sx, sy, ax, ay, bx, by, threshold, slop.px, m, st));
1152
+ return walk(path, opts, (ax, ay, bx, by) => capsuleWithinPx(sx, sy, ax, ay, bx, by, threshold, slop.px, m, st));
951
1153
  }
952
1154
  const t2 = threshold * threshold;
953
- return someSegment(path, opts.tolerance, (ax, ay, bx, by) => pointSegmentDist2(x, y, ax, ay, bx, by) <= t2);
1155
+ return walk(path, opts, (ax, ay, bx, by) => pointSegmentDist2(x, y, ax, ay, bx, by) <= t2);
954
1156
  }
955
- function someSegment(path, tolerance, visit) {
956
- if (path.kind === "rect") {
957
- const { x: rx, y: ry, width: w, height: h } = path;
958
- return visit(rx, ry, rx + w, ry) || visit(rx + w, ry, rx + w, ry + h) || visit(rx + w, ry + h, rx, ry + h) || visit(rx, ry + h, rx, ry);
959
- }
960
- const { closed, open } = flatSubpaths(path, tolerance ?? DEFAULT_FLATTEN_TOLERANCE);
961
- const run2 = (sub3, wrap) => {
962
- const n = sub3.length;
963
- const last = wrap ? n : n - 2;
964
- for (let i = 0; i < last; i += 2) {
965
- const j = (i + 2) % n;
966
- if (visit(sub3[i], sub3[i + 1], sub3[j], sub3[j + 1])) return true;
967
- }
968
- return false;
969
- };
970
- return closed.some((sub3) => run2(sub3, true)) || open.some((sub3) => run2(sub3, false));
1157
+ function walk(path, opts, visit) {
1158
+ return someSegment(path, opts.tolerance, opts.span, visit);
971
1159
  }
972
1160
  function rectToVerts(r) {
973
1161
  return [
@@ -1715,7 +1903,7 @@ function arcToCubics(x1, y1, x2, y2, rx, ry, xAxisRotationDeg, largeArc, sweep,
1715
1903
  b.lineTo(x2, y2);
1716
1904
  return;
1717
1905
  }
1718
- const phi = xAxisRotationDeg * Math.PI / 180;
1906
+ const phi = xAxisRotationDeg * DEG_TO_RAD;
1719
1907
  const cosPhi = Math.cos(phi);
1720
1908
  const sinPhi = Math.sin(phi);
1721
1909
  const dx2 = (x1 - x2) / 2;
@@ -2317,64 +2505,11 @@ function crossingStops(path, cutters) {
2317
2505
  return stops;
2318
2506
  }
2319
2507
 
2320
- // src/paths/segmentLength.ts
2321
- var GL_X = [
2322
- -0.9602898564975363,
2323
- -0.7966664774136267,
2324
- -0.525532409916329,
2325
- -0.1834346424956498,
2326
- 0.1834346424956498,
2327
- 0.525532409916329,
2328
- 0.7966664774136267,
2329
- 0.9602898564975363
2330
- ];
2331
- var GL_W2 = [
2332
- 0.1012285362903763,
2333
- 0.2223810344533745,
2334
- 0.3137066458778873,
2335
- 0.362683783378362,
2336
- 0.362683783378362,
2337
- 0.3137066458778873,
2338
- 0.2223810344533745,
2339
- 0.1012285362903763
2340
- ];
2341
- var speed2 = (s, t) => {
2342
- const d = derivSeg(s, t);
2343
- return Math.hypot(d.x, d.y);
2344
- };
2345
- function segmentLength(s, a = 0, b = 1) {
2346
- if (s.length === 2) return speed2(s, 0) * (b - a);
2347
- const panels = 8, h = (b - a) / panels;
2348
- let sum = 0;
2349
- for (let k = 0; k < panels; k++) {
2350
- const mid = a + (k + 0.5) * h;
2351
- for (let i = 0; i < GL_X.length; i++) sum += GL_W2[i] * speed2(s, mid + GL_X[i] * h / 2);
2352
- }
2353
- return sum * h / 2;
2354
- }
2355
- function segmentTAtLength(s, length) {
2356
- const total = segmentLength(s);
2357
- if (length <= 0 || !(total > 0)) return 0;
2358
- if (length >= total) return 1;
2359
- if (s.length === 2) return length / total;
2360
- let lo = 0, hi = 1, t = length / total;
2361
- for (let i = 0; i < 30; i++) {
2362
- const err = segmentLength(s, 0, t) - length;
2363
- if (Math.abs(err) < 1e-10 * Math.max(1, total)) break;
2364
- if (err > 0) hi = t;
2365
- else lo = t;
2366
- const v = speed2(s, t);
2367
- let next = v > 0 ? t - err / v : NaN;
2368
- if (!(next > lo && next < hi)) next = (lo + hi) / 2;
2369
- t = next;
2370
- }
2371
- return t;
2372
- }
2373
-
2374
2508
  // src/snap/segmentFeatures.ts
2375
2509
  function segmentMidT(s) {
2376
2510
  if (s.length === 2) return 0.5;
2377
- return segmentTAtLength(s, segmentLength(s) / 2);
2511
+ const c = flatPoints(s);
2512
+ return segmentTAtLength(c, 2, segmentLength(c, 2) / 2);
2378
2513
  }
2379
2514
  function unitQuadraticRoots(a, b, c) {
2380
2515
  const inside = (t) => t >= -1e-9 && t <= 1 + 1e-9;
@@ -3068,14 +3203,14 @@ function segmentAt2(set, x, y, opts) {
3068
3203
  const hit = nearestSegmentT(screenAnchors, x * scale3.x, y * scale3.y, set.closed);
3069
3204
  if (!hit) return null;
3070
3205
  const ends = segmentEnds(set.anchors, set.closed, hit.sub, hit.segIdx);
3071
- const point = pointOnSegment(ends.a, ends.b, hit.t);
3206
+ const point = pointOnSegment2(ends.a, ends.b, hit.t);
3072
3207
  const tol = opts.tolerancePx;
3073
3208
  if (!withinPxRadius(point.x - x, point.y - y, tol, scale3)) return null;
3074
3209
  if (withinPxRadius(point.x - ends.a.x, point.y - ends.a.y, tol, scale3)) return null;
3075
3210
  if (withinPxRadius(point.x - ends.b.x, point.y - ends.b.y, tol, scale3)) return null;
3076
3211
  return { sub: hit.sub, segIdx: hit.segIdx, t: hit.t, x: point.x, y: point.y };
3077
3212
  }
3078
- function pointOnSegment(a, b, t) {
3213
+ function pointOnSegment2(a, b, t) {
3079
3214
  if (isStraightSegment(a, b)) return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
3080
3215
  return cubicPointAt(a, a.outHandle ?? a, b.inHandle ?? b, b, t);
3081
3216
  }
@@ -3302,8 +3437,9 @@ function g2Cubic(seg) {
3302
3437
  }
3303
3438
  function pieceLengthAt(piece, t) {
3304
3439
  if (piece.pts.length === 2) return piece.length * t;
3305
- const whole = segmentLength(piece.pts);
3306
- return whole > 0 ? piece.length * segmentLength(piece.pts, 0, t) / whole : 0;
3440
+ const c = flatPoints(piece.pts);
3441
+ const whole = segmentLength(c, 2);
3442
+ return whole > 0 ? piece.length * segmentLength(c, 2, 0, t) / whole : 0;
3307
3443
  }
3308
3444
 
3309
3445
  // src/paths/lengthenPathEnd.ts
@@ -3434,7 +3570,7 @@ function appendPieces(sub3, pieces) {
3434
3570
  function tailLength(sub3) {
3435
3571
  const [p0, p1, p2, p3] = tailCubic(sub3);
3436
3572
  if (isStraightSegment(p0, p3)) return Math.hypot(p3.x - p0.x, p3.y - p0.y);
3437
- return segmentLength([p0, p1, p2, p3]);
3573
+ return segmentLength(flatPoints([p0, p1, p2, p3]), 2);
3438
3574
  }
3439
3575
  function subpathLength(sub3) {
3440
3576
  let total = 0;
@@ -3464,7 +3600,7 @@ function cutTail(sub3, keep, len) {
3464
3600
  p3.y = p0.y + (p3.y - p0.y) * f;
3465
3601
  return;
3466
3602
  }
3467
- const t = segmentTAtLength([p0, p1, p2, p3], keep);
3603
+ const t = segmentTAtLength(flatPoints([p0, p1, p2, p3]), 2, keep);
3468
3604
  const { left } = splitCubicAtT(p0, p1, p2, p3, t);
3469
3605
  if (p0.outHandle) p0.outHandle = left[1];
3470
3606
  p3.inHandle = left[2];
@@ -3575,7 +3711,7 @@ function simplifySpan(sub3, closed, from, to, tolerance, opts = {}) {
3575
3711
  function fitParams(tolerance, opts) {
3576
3712
  return {
3577
3713
  tol: Math.max(tolerance, 1e-6),
3578
- cosCorner: Math.cos((opts.cornerAngle ?? 40) * Math.PI / 180)
3714
+ cosCorner: Math.cos((opts.cornerAngle ?? 40) * DEG_TO_RAD)
3579
3715
  };
3580
3716
  }
3581
3717
  function breaksOf(sub3, closed, cosCorner) {
@@ -4214,7 +4350,7 @@ function measure(a, b, from) {
4214
4350
  }
4215
4351
  return { a, c1, c2, b, straight, from, len: table[LENGTH_SAMPLES], table };
4216
4352
  }
4217
- function paramAt(seg, d) {
4353
+ function paramAt2(seg, d) {
4218
4354
  const { table } = seg;
4219
4355
  if (seg.len === 0) return 0;
4220
4356
  let lo = 0;
@@ -4241,7 +4377,7 @@ function arcRun(sub3, from, to) {
4241
4377
  const d = Math.min(Math.max(s, 0), length);
4242
4378
  let k = 0;
4243
4379
  while (k < segs.length - 1 && (arriving ? segs[k].from + segs[k].len < d : segs[k].from + segs[k].len <= d)) k++;
4244
- return { seg: segs[k], t: paramAt(segs[k], d - segs[k].from) };
4380
+ return { seg: segs[k], t: paramAt2(segs[k], d - segs[k].from) };
4245
4381
  };
4246
4382
  const start = { x: sub3[from % n].x, y: sub3[from % n].y };
4247
4383
  return {
@@ -5189,7 +5325,10 @@ function poseFrameInverseOf(pose) {
5189
5325
  function pathThroughPoseFrame(path, pose) {
5190
5326
  if (poseLinearOf(pose)) {
5191
5327
  const frame = poseFrameOf(pose);
5192
- return frame ? transformPath(path, frame) : path;
5328
+ if (!frame) return path;
5329
+ const [a, b, c, d] = frame;
5330
+ const uniform = isSimilarity(stretchOf({ a, b, c, d }));
5331
+ return transformPath(path.kind === "polygon" && !uniform ? resolveCorners(path) : path, frame);
5193
5332
  }
5194
5333
  const r = poseRotationOf(pose);
5195
5334
  return r ? rotatePathAround(path, r.cx, r.cy, r.rotation) : path;
@@ -6918,7 +7057,7 @@ function handleTarget(a, h, part, entry) {
6918
7057
  const angle = angleOf(a, h);
6919
7058
  const nextAngle = part === "Angle" ? apply2(angle, entry) : angle;
6920
7059
  const nextLength = part === "Length" ? Math.max(0, apply2(length, entry)) : length;
6921
- const rad = nextAngle * Math.PI / 180;
7060
+ const rad = nextAngle * DEG_TO_RAD;
6922
7061
  return { x: a.x + Math.cos(rad) * nextLength, y: a.y - Math.sin(rad) * nextLength };
6923
7062
  }
6924
7063
  function apply2(current, entry) {
@@ -6926,7 +7065,7 @@ function apply2(current, entry) {
6926
7065
  }
6927
7066
  function angleOf(a, h) {
6928
7067
  if (h.x === a.x && h.y === a.y) return 0;
6929
- return Math.atan2(a.y - h.y, h.x - a.x) * 180 / Math.PI;
7068
+ return Math.atan2(a.y - h.y, h.x - a.x) * RAD_TO_DEG;
6930
7069
  }
6931
7070
  function agreed(values) {
6932
7071
  const first = values[0];
@@ -7017,6 +7156,6 @@ function typedArrayReplacer(_key, value) {
7017
7156
  return tagTypedArray(value) ?? value;
7018
7157
  }
7019
7158
 
7020
- 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, bendSide, blendPaths, boxFrameOf, 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, easeLength, easeOut, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic, easeOutElastic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, editAnchorSet, editAnchorSetTracked, ellipseShape, equalizeSegmentHandles, everyNthAnchor, extendToMeet, extensionPieces, findPathSnap, findPathSnaps, fitAffine, fitCubicThroughDeletion, flatAnchorIndex, flatIndexesAfterReverse, growAnchorSelection, growRegion, handleAlongItsAngle, handleAtItsLength, handleBalances, harmonizeAnchors, hasOpenSubpath, identity, insertAnchorOnSegment, 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, puckerBloatPath, pxExtent, pxExtentInFrame, readAnchorFields, rectMarkerPath, remapAnchorValues, remapConstraints, remapPointValues, repeatAlongPath, resamplePath, resolveEasing, retractHandles, reverseAnchors, reversePath, reverseSubpaths, reviveTypedArrays, rotate, rotateAboutPoint, rotatePathAround, roughenPath, roundRectPath, runLength, runPlaces, runStationAt, scale, scaleDelta, scalePathToBounds, scaleSegmentHandles, screenAngleOf, segmentAt2 as segmentAt, segmentBetween, segmentExtremaTs, segmentsBetweenAnchors, setSubpathStart, shiftTrack, shrinkAnchorSelection, simplifyAnchorsAt, simplifyPath, simplifySpan, simplifySubpath, smoothAnchors, smudgePath, softFalloff, softSelectionBand, softSelectionWeights, solveConstraints, solvePathConstraints, splitByClosure, splitSubpaths, squarePath, standoff, stationsAlongPath, strokeHitTest, subpathEndAt, subpathsOfAnchors, tagTypedArray, tagTypedArrays, trackRun, trackShapeD, trackSlice, transformCoords, transformPath, translate, translateAnchorBy, translateAnchorsSymmetric, translatePath, trimPathAt, typedArrayReplacer, unionBoundsPath, unsatisfiedConstraints, untagTypedArray, viewRectToScreen, viewScreenToWorld, viewToMat3, viewWorldToScreen, withAnchorPoints, withinPxBox, withinPxRadius, worldEditToStorage, writeAnchorField, zigzagPath };
7159
+ 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, bendSide, blendPaths, boxFrameOf, boxToBox, circlePath, clampRectWithin, closestOpenEnds, composePath, conflictingConstraints, constrainPathEdit, constrainPathsEdit, constraintAnchors, constraintApplies, constraintFits, convertSegments, cornerAnchors, createPathSnapIndex, cubicBezierEasing, cubicContoursToPath, curveSpan, 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, easeLength, easeOut, easeOutBack, easeOutBounce, easeOutCirc, easeOutCubic, easeOutElastic, easeOutExpo, easeOutQuad, easeOutQuart, easeOutQuint, easeOutSine, editAnchorSet, editAnchorSetTracked, ellipseShape, equalizeSegmentHandles, everyNthAnchor, extendToMeet, extensionPieces, findPathSnap, findPathSnaps, fitAffine, fitCubicThroughDeletion, flatAnchorIndex, flatIndexesAfterReverse, growAnchorSelection, growRegion, handleAlongItsAngle, handleAtItsLength, handleBalances, harmonizeAnchors, hasOpenSubpath, identity, insertAnchorOnSegment, invert, invertAnchorSelection, isSegmentStraight, joinAnchorsAt, joinSubpathEnds, lengthAtCurve, 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, pathLengthTable, pathSegments, pathShape, pathSignedArea, pathThroughPoseFrame, pathToCubicContours, pathToD, placeAlongRun, placeRect, pointAlongPath, pointInPath, polygonContainsPath, polygonIntersectsPath, portControls2 as portControls, portCurvePoints2 as portCurvePoints, poseFrameInverseOf, poseFrameOf, poseLinearOf, poseRotationOf, prepareBlend, puckerBloatPath, pxExtent, pxExtentInFrame, readAnchorFields, rectMarkerPath, remapAnchorValues, remapConstraints, remapPointValues, repeatAlongPath, resamplePath, resolveEasing, retractHandles, reverseAnchors, reversePath, reverseSubpaths, reviveTypedArrays, rotate, rotateAboutPoint, rotatePathAround, roughenPath, roundRectPath, runLength, runPlaces, runSegments, runStationAt, scale, scaleDelta, scalePathToBounds, scaleSegmentHandles, screenAngleOf, segmentAt2 as segmentAt, segmentBetween, segmentExtremaTs, segmentsBetweenAnchors, setSubpathStart, shiftTrack, shrinkAnchorSelection, simplifyAnchorsAt, simplifyPath, simplifySpan, simplifySubpath, smoothAnchors, smudgePath, softFalloff, softSelectionBand, softSelectionWeights, solveConstraints, solvePathConstraints, splitByClosure, splitSubpaths, squarePath, standoff, stationsAlongPath, strokeHitTest, subpathEndAt, subpathsOfAnchors, tagTypedArray, tagTypedArrays, trackRun, trackShapeD, trackSlice, transformCoords, transformPath, translate, translateAnchorBy, translateAnchorsSymmetric, translatePath, trimPathAt, typedArrayReplacer, unionBoundsPath, unsatisfiedConstraints, untagTypedArray, viewRectToScreen, viewScreenToWorld, viewToMat3, viewWorldToScreen, withAnchorPoints, withinPxBox, withinPxRadius, worldEditToStorage, writeAnchorField, zigzagPath };
7021
7160
  //# sourceMappingURL=index.js.map
7022
7161
  //# sourceMappingURL=index.js.map