@weasel-js/geom 1.14.1 → 1.15.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
@@ -3,14 +3,14 @@ export { CORNER_RESTORE_TOLERANCE, cornerTraces, intersectSegments, keepCorners,
3
3
  import { createBvh } from './chunk-XXNK4N7Z.js';
4
4
  import { PORT_REACH, portControls, portCurvePoints } from './chunk-YQADYJJ7.js';
5
5
  export { PORT_REACH } from './chunk-YQADYJJ7.js';
6
- import { cubicDerivativeAt, cubicCurvatureAt } from './chunk-42TZDN5C.js';
7
- export { SPIRO_KINDS, combTeeth, cubicCurvatureAt, cubicCurvatureSource, cubicDerivativeAt, curvatureComb, eulerAngleAt, eulerCurvatureAt, eulerCurvatureDerivativesAt, eulerPointAt, fitEulerSegment, isSpiroKind, placeEulerSegment, solveSpiro, spiroContoursFromControl, spiroContoursOf, spiroControlPath, spiroToPath } from './chunk-42TZDN5C.js';
6
+ import { eulerAngleAt, eulerSegmentCubic, eulerCubicError, integrateEuler, eulerPointAt, eulerCurvatureAt, cubicDerivativeAt, cubicCurvatureAt } from './chunk-HNLJO3E7.js';
7
+ export { SPIRO_KINDS, combTeeth, cubicCurvatureAt, cubicCurvatureSource, cubicDerivativeAt, curvatureComb, eulerAngleAt, eulerCurvatureAt, eulerCurvatureDerivativesAt, eulerPointAt, fitEulerSegment, isSpiroKind, placeEulerSegment, solveSpiro, spiroContoursFromControl, spiroContoursOf, spiroControlPath, spiroToPath } from './chunk-HNLJO3E7.js';
8
8
  import { extractPolylines } from './chunk-FROQPADA.js';
9
9
  import { boundsOfPath, fitPointRun, keepCorner } from './chunk-ZOMUTU7J.js';
10
10
  export { boundsOfPath } from './chunk-ZOMUTU7J.js';
11
11
  import { countPathAnchors, pathToAnchors, anchorsToPath, isAnchorSmooth, segmentEnds, isStraightSegment, nearestSegmentT } from './chunk-3HQJGI63.js';
12
12
  export { anchorsToPath, countPathAnchors, isAnchorSmooth, isStraightSegment, nearestSegmentT, pathToAnchors, segmentEnds } from './chunk-3HQJGI63.js';
13
- import { nearestOnLine, nearestOnPath, nearestOnQuadratic, nearestOnCubic } from './chunk-2N6OWA7E.js';
13
+ import { nearestOnLine, nearestOnPath, nearestOnCubic, nearestOnQuadratic } from './chunk-2N6OWA7E.js';
14
14
  export { nearestOnCubic, nearestOnLine, nearestOnPath, nearestOnQuadratic } from './chunk-2N6OWA7E.js';
15
15
  import { resolveCorners, pointInPolygon, cornerCuts, fillsOpenSubpaths, schneiderFit, fitCubicWithTangents, flattenCubic, pointSegmentDist2, flattenQuadratic, DEFAULT_FLATTEN_TOLERANCE, segmentsCross, tangentAt } from './chunk-EURWZUNY.js';
16
16
  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-EURWZUNY.js';
@@ -1872,8 +1872,8 @@ function cubicContoursToPath(contours, fillRule = "nonzero") {
1872
1872
  for (const { segs, closed } of contours) {
1873
1873
  if (segs.length === 0) continue;
1874
1874
  b.moveTo(segs[0].c[0], segs[0].c[1]);
1875
- for (const { c, line } of segs) {
1876
- if (line) b.lineTo(c[6], c[7]);
1875
+ for (const { c, line: line2 } of segs) {
1876
+ if (line2) b.lineTo(c[6], c[7]);
1877
1877
  else b.curveTo(c[2], c[3], c[4], c[5], c[6], c[7]);
1878
1878
  }
1879
1879
  if (closed) b.close();
@@ -1890,9 +1890,9 @@ function splitSegment(seg, t) {
1890
1890
  function reverseContour(contour) {
1891
1891
  return {
1892
1892
  closed: contour.closed,
1893
- segs: contour.segs.slice().reverse().map(({ c, line }) => ({
1893
+ segs: contour.segs.slice().reverse().map(({ c, line: line2 }) => ({
1894
1894
  c: [c[6], c[7], c[4], c[5], c[2], c[3], c[0], c[1]],
1895
- line
1895
+ line: line2
1896
1896
  }))
1897
1897
  };
1898
1898
  }
@@ -1928,7 +1928,7 @@ function collapsed(contour) {
1928
1928
  const [cx, cy] = centroid(contour);
1929
1929
  return {
1930
1930
  closed: contour.closed,
1931
- segs: contour.segs.map(({ line }) => ({ c: [cx, cy, cx, cy, cx, cy, cx, cy], line }))
1931
+ segs: contour.segs.map(({ line: line2 }) => ({ c: [cx, cy, cx, cy, cx, cy, cx, cy], line: line2 }))
1932
1932
  };
1933
1933
  }
1934
1934
  function centroid(contour) {
@@ -2126,9 +2126,9 @@ function repeatAlongPath(shape, path, opts = {}) {
2126
2126
  }
2127
2127
 
2128
2128
  // src/paths/contourSamples.ts
2129
- function sampleAt({ c, line }, t) {
2129
+ function sampleAt({ c, line: line2 }, t) {
2130
2130
  const [x, y] = cubicEvalAt(...c, t);
2131
- const seg = { line, p0: { x: c[0], y: c[1] }, c1: { x: c[2], y: c[3] }, c2: { x: c[4], y: c[5] }, p3: { x: c[6], y: c[7] } };
2131
+ const seg = { line: line2, p0: { x: c[0], y: c[1] }, c1: { x: c[2], y: c[3] }, c2: { x: c[4], y: c[5] }, p3: { x: c[6], y: c[7] } };
2132
2132
  return { at: { x, y }, dir: tangentAt(seg, t) };
2133
2133
  }
2134
2134
  function sampleContour(contour, pieces) {
@@ -2492,9 +2492,9 @@ function quadrantPoints(segs) {
2492
2492
  const y0 = Math.min(...sub3.map((s) => s.box.y0)), y1 = Math.max(...sub3.map((s) => s.box.y1));
2493
2493
  const cx = (x0 + x1) / 2, cy = (y0 + y1) / 2;
2494
2494
  const across = [[{ x: x0 - 1, y: cy }, { x: x1 + 1, y: cy }], [{ x: cx, y: y0 - 1 }, { x: cx, y: y1 + 1 }]];
2495
- for (const line of across) {
2495
+ for (const line2 of across) {
2496
2496
  for (const s of sub3) {
2497
- for (const h of intersectSegments(s.pts, line)) {
2497
+ for (const h of intersectSegments(s.pts, line2)) {
2498
2498
  if (!out.some((p) => Math.hypot(p.x - h.x, p.y - h.y) < 1e-6)) out.push({ x: h.x, y: h.y });
2499
2499
  }
2500
2500
  }
@@ -3134,11 +3134,170 @@ function anchorsInRect(set, rect) {
3134
3134
  return hits;
3135
3135
  }
3136
3136
 
3137
- // src/paths/lengthenPathEnd.ts
3137
+ // src/paths/pathEndExtension.ts
3138
+ var DEFAULT_EASE_TURN = Math.PI / 6;
3138
3139
  var STRAIGHT_TURN = 1e-6;
3139
- var MIN_KEEP = 1e-3;
3140
3140
  var QUARTER = Math.PI / 2;
3141
3141
  var FULL = 2 * Math.PI;
3142
+ var FIT_SHARE = 1e-4;
3143
+ var FIT_FLOOR = 1e-3;
3144
+ var MAX_SPLITS = 6;
3145
+ function extensionPieces(frame, distance, mode, opts = {}) {
3146
+ if (!(distance > 0)) return [];
3147
+ const bends = Math.abs(frame.curvature) * distance > STRAIGHT_TURN;
3148
+ if (mode === "curvature" && bends) return arcPieces(frame, distance);
3149
+ if (mode === "eased" && bends) return easedPieces(frame, distance, easeTurnOf(opts));
3150
+ return [line(frame.point, frame.tangent, distance)];
3151
+ }
3152
+ function easeLength(frame, opts = {}) {
3153
+ const k = Math.abs(frame.curvature);
3154
+ return k > 0 ? 2 * easeTurnOf(opts) / k : Infinity;
3155
+ }
3156
+ function easeTurnOf(opts) {
3157
+ const turn = opts.easeTurn;
3158
+ return turn !== void 0 && Number.isFinite(turn) && turn > 0 ? Math.min(turn, QUARTER) : DEFAULT_EASE_TURN;
3159
+ }
3160
+ function line(from, dir, length2) {
3161
+ return { pts: [from, { x: from.x + dir.x * length2, y: from.y + dir.y * length2 }], length: length2 };
3162
+ }
3163
+ function arcCenter({ point, tangent, curvature }) {
3164
+ return { x: point.x - tangent.y / curvature, y: point.y + tangent.x / curvature };
3165
+ }
3166
+ function arcPieces(frame, distance) {
3167
+ const s = Math.sign(frame.curvature);
3168
+ const r = 1 / Math.abs(frame.curvature);
3169
+ const sweep = Math.min(distance / r, FULL);
3170
+ const count = Math.max(1, Math.ceil(sweep / QUARTER - 1e-9));
3171
+ const step = sweep / count;
3172
+ const reach = 4 / 3 * Math.tan(step / 4) * r;
3173
+ const c = arcCenter(frame);
3174
+ const rx = frame.point.x - c.x;
3175
+ const ry = frame.point.y - c.y;
3176
+ const at2 = (turn) => {
3177
+ const cos = Math.cos(s * turn);
3178
+ const sin = Math.sin(s * turn);
3179
+ return {
3180
+ p: { x: c.x + rx * cos - ry * sin, y: c.y + rx * sin + ry * cos },
3181
+ t: { x: frame.tangent.x * cos - frame.tangent.y * sin, y: frame.tangent.x * sin + frame.tangent.y * cos }
3182
+ };
3183
+ };
3184
+ const out = [];
3185
+ for (let i = 1; i <= count; i++) {
3186
+ const from = at2((i - 1) * step);
3187
+ const to = at2(i * step);
3188
+ out.push({
3189
+ pts: [
3190
+ from.p,
3191
+ { x: from.p.x + from.t.x * reach, y: from.p.y + from.t.y * reach },
3192
+ { x: to.p.x - to.t.x * reach, y: to.p.y - to.t.y * reach },
3193
+ to.p
3194
+ ],
3195
+ length: step * r
3196
+ });
3197
+ }
3198
+ return out;
3199
+ }
3200
+ function easeSpiral(frame, length2, turn) {
3201
+ const sign2 = Math.sign(frame.curvature);
3202
+ const k0 = sign2 * turn;
3203
+ const k1 = -2 * sign2 * turn;
3204
+ const rotation = Math.atan2(frame.tangent.y, frame.tangent.x) + 3 / 4 * k0;
3205
+ const c = integrateEuler(k0, k1);
3206
+ const seg = {
3207
+ k0,
3208
+ k1,
3209
+ k2: 0,
3210
+ k3: 0,
3211
+ ratio: Math.hypot(c.x, c.y),
3212
+ chordAngle: Math.atan2(c.y, c.x),
3213
+ p0: frame.point,
3214
+ p1: frame.point,
3215
+ length: length2,
3216
+ rotation
3217
+ };
3218
+ return { ...seg, p1: eulerPointAt(seg, 1) };
3219
+ }
3220
+ function easedPieces(frame, distance, turn) {
3221
+ const ease = 2 * turn / Math.abs(frame.curvature);
3222
+ const spiral = easeSpiral(frame, ease, turn);
3223
+ const drawn = distance < ease ? spiralPiece(spiral, 0, distance / ease) : spiral;
3224
+ const out = fitSpiral(drawn, Math.max(FIT_FLOOR, FIT_SHARE * ease), 0);
3225
+ if (distance > ease) {
3226
+ const a = eulerAngleAt(spiral, 1);
3227
+ out.push(line(spiral.p1, { x: Math.cos(a), y: Math.sin(a) }, distance - ease));
3228
+ }
3229
+ return out;
3230
+ }
3231
+ function fitSpiral(seg, tol, depth) {
3232
+ const fitted = g2Cubic(seg) ?? eulerSegmentCubic(seg);
3233
+ if (depth < MAX_SPLITS && eulerCubicError(seg, fitted.c1, fitted.c2) > tol) {
3234
+ return [...fitSpiral(spiralPiece(seg, 0, 0.5), tol, depth + 1), ...fitSpiral(spiralPiece(seg, 0.5, 1), tol, depth + 1)];
3235
+ }
3236
+ return [{ pts: [seg.p0, fitted.c1, fitted.c2, seg.p1], length: seg.length }];
3237
+ }
3238
+ function spiralPiece(seg, s0, s1) {
3239
+ const d = s1 - s0;
3240
+ const m = (s0 + s1) / 2 - 0.5;
3241
+ const k0 = (seg.k0 + seg.k1 * m) * d;
3242
+ const k1 = seg.k1 * d * d;
3243
+ const c = integrateEuler(k0, k1);
3244
+ return {
3245
+ k0,
3246
+ k1,
3247
+ k2: 0,
3248
+ k3: 0,
3249
+ ratio: Math.hypot(c.x, c.y),
3250
+ chordAngle: Math.atan2(c.y, c.x),
3251
+ p0: eulerPointAt(seg, s0),
3252
+ p1: eulerPointAt(seg, s1),
3253
+ length: seg.length * d,
3254
+ rotation: eulerAngleAt(seg, s0) + k0 / 2 - k1 / 8
3255
+ };
3256
+ }
3257
+ var cross2 = (a, b) => a.x * b.y - a.y * b.x;
3258
+ function g2Cubic(seg) {
3259
+ const a0 = eulerAngleAt(seg, 0);
3260
+ const a1 = eulerAngleAt(seg, 1);
3261
+ const u0 = { x: Math.cos(a0), y: Math.sin(a0) };
3262
+ const u1 = { x: Math.cos(a1), y: Math.sin(a1) };
3263
+ const k0 = eulerCurvatureAt(seg, 0);
3264
+ const k1 = eulerCurvatureAt(seg, 1);
3265
+ const d = { x: seg.p1.x - seg.p0.x, y: seg.p1.y - seg.p0.y };
3266
+ const c = cross2(u0, u1);
3267
+ const d0 = cross2(u0, d);
3268
+ const d1 = cross2(d, u1);
3269
+ const chord = Math.hypot(d.x, d.y);
3270
+ if (!(chord > 0) || Math.abs(c) < 1e-9) return null;
3271
+ const start = eulerSegmentCubic(seg);
3272
+ let a = Math.hypot(start.c1.x - seg.p0.x, start.c1.y - seg.p0.y);
3273
+ let b = Math.hypot(start.c2.x - seg.p1.x, start.c2.y - seg.p1.y);
3274
+ for (let i = 0; i < 30; i++) {
3275
+ const f02 = 1.5 * k0 * a * a - d0 + b * c;
3276
+ const f12 = 1.5 * k1 * b * b - d1 + a * c;
3277
+ if (Math.abs(f02) + Math.abs(f12) < 1e-12 * chord * chord) break;
3278
+ const j00 = 3 * k0 * a, j11 = 3 * k1 * b;
3279
+ const det = j00 * j11 - c * c;
3280
+ if (Math.abs(det) < 1e-18) return null;
3281
+ a -= (f02 * j11 - f12 * c) / det;
3282
+ b -= (j00 * f12 - c * f02) / det;
3283
+ }
3284
+ const f0 = 1.5 * k0 * a * a - d0 + b * c;
3285
+ const f1 = 1.5 * k1 * b * b - d1 + a * c;
3286
+ if (!(a > 0 && b > 0) || Math.abs(f0) + Math.abs(f1) > 1e-9 * chord * chord) return null;
3287
+ return {
3288
+ c1: { x: seg.p0.x + a * u0.x, y: seg.p0.y + a * u0.y },
3289
+ c2: { x: seg.p1.x - b * u1.x, y: seg.p1.y - b * u1.y }
3290
+ };
3291
+ }
3292
+ function pieceLengthAt(piece, t) {
3293
+ if (piece.pts.length === 2) return piece.length * t;
3294
+ const whole = segmentLength(piece.pts);
3295
+ return whole > 0 ? piece.length * segmentLength(piece.pts, 0, t) / whole : 0;
3296
+ }
3297
+
3298
+ // src/paths/lengthenPathEnd.ts
3299
+ var MIN_KEEP = 1e-3;
3300
+ var FULL2 = 2 * Math.PI;
3142
3301
  function openEndAt(set, flatIndex) {
3143
3302
  const loc = locateAnchor(set, flatIndex);
3144
3303
  if (!loc || set.closed[loc.sub]) return null;
@@ -3156,25 +3315,27 @@ function pathEndAnchorIndex(set, end) {
3156
3315
  function pathEndFrame(set, end) {
3157
3316
  return atTail(set, end, tailFrame) ?? null;
3158
3317
  }
3159
- function lengthenPathEnd(set, end, distance, mode) {
3318
+ function lengthenPathEnd(set, end, distance, mode, opts = {}) {
3160
3319
  if (!(distance !== 0 && Number.isFinite(distance))) return false;
3161
- return atTail(set, end, (sub3) => distance < 0 ? trimTail(sub3, -distance) : growTail(sub3, distance, mode)) ?? false;
3320
+ return atTail(set, end, (sub3) => distance < 0 ? trimTail(sub3, -distance) : growTail(sub3, distance, mode, opts)) ?? false;
3162
3321
  }
3163
- function lengthToward(set, end, mode, point, near2 = 0) {
3322
+ function lengthToward(set, end, mode, point, near2 = 0, opts = {}) {
3164
3323
  const frame = pathEndFrame(set, end);
3165
3324
  if (!frame) return 0;
3166
3325
  const { point: p, tangent: t, curvature } = frame;
3167
3326
  const vx = point.x - p.x;
3168
3327
  const vy = point.y - p.y;
3169
- if (mode === "tangent" || curvature === 0) return vx * t.x + vy * t.y;
3328
+ const ahead = vx * t.x + vy * t.y;
3329
+ if (mode === "tangent" || curvature === 0 || mode === "eased" && ahead <= 0) return ahead;
3330
+ if (mode === "eased") return lengthAlongEased(frame, point, opts);
3170
3331
  const s = Math.sign(curvature);
3171
3332
  const r = 1 / Math.abs(curvature);
3172
- const c = arcCenter(frame);
3333
+ const c = arcCenter2(frame);
3173
3334
  const ax = p.x - c.x, ay = p.y - c.y;
3174
3335
  const bx = point.x - c.x, by = point.y - c.y;
3175
3336
  const angle = s * Math.atan2(ax * by - ay * bx, ax * bx + ay * by);
3176
- const turns = Math.round((near2 / r - angle) / FULL);
3177
- return (angle + turns * FULL) * r;
3337
+ const turns = Math.round((near2 / r - angle) / FULL2);
3338
+ return (angle + turns * FULL2) * r;
3178
3339
  }
3179
3340
  function atTail(set, end, edit) {
3180
3341
  const sub3 = set.anchors[end.sub];
@@ -3216,50 +3377,45 @@ function frameHeading(point, dx2, dy2, curvature) {
3216
3377
  if (!(len > 1e-9)) return null;
3217
3378
  return { point, tangent: { x: dx2 / len, y: dy2 / len }, curvature };
3218
3379
  }
3219
- function arcCenter({ point, tangent, curvature }) {
3380
+ function arcCenter2({ point, tangent, curvature }) {
3220
3381
  return { x: point.x - tangent.y / curvature, y: point.y + tangent.x / curvature };
3221
3382
  }
3222
- function growTail(sub3, distance, mode) {
3383
+ function lengthAlongEased(frame, point, opts) {
3384
+ const far = Math.hypot(point.x - frame.point.x, point.y - frame.point.y);
3385
+ const reach = Math.min(easeLength(frame, opts), far * 2) + far;
3386
+ let best = { dist: Infinity, length: 0 };
3387
+ let walked = 0;
3388
+ for (const piece of extensionPieces(frame, reach, "eased", opts)) {
3389
+ const [q0, q1, q2, q3] = piece.pts;
3390
+ const hit = piece.pts.length === 2 ? nearestOnLine(point.x, point.y, q0.x, q0.y, q1.x, q1.y) : nearestOnCubic(point.x, point.y, q0.x, q0.y, q1.x, q1.y, q2.x, q2.y, q3.x, q3.y);
3391
+ if (hit.dist < best.dist) best = { dist: hit.dist, length: walked + pieceLengthAt(piece, hit.t) };
3392
+ walked += piece.length;
3393
+ }
3394
+ return best.length;
3395
+ }
3396
+ function growTail(sub3, distance, mode, opts) {
3223
3397
  const frame = tailFrame(sub3);
3224
3398
  if (!frame) return false;
3225
- if (mode === "curvature" && Math.abs(frame.curvature) * distance > STRAIGHT_TURN) {
3226
- appendArc(sub3, frame, distance);
3227
- return true;
3228
- }
3229
- const to = { x: frame.point.x + frame.tangent.x * distance, y: frame.point.y + frame.tangent.y * distance };
3399
+ const pieces = extensionPieces(frame, distance, mode, opts);
3230
3400
  const end = sub3[sub3.length - 1];
3231
- if (isStraightSegment(sub3[sub3.length - 2], end)) {
3232
- end.x = to.x;
3233
- end.y = to.y;
3234
- } else {
3235
- sub3.push(to);
3401
+ const first = pieces[0];
3402
+ if (pieces.length === 1 && first.pts.length === 2 && isStraightSegment(sub3[sub3.length - 2], end)) {
3403
+ end.x = first.pts[1].x;
3404
+ end.y = first.pts[1].y;
3405
+ return true;
3236
3406
  }
3237
- return true;
3407
+ appendPieces(sub3, pieces);
3408
+ return pieces.length > 0;
3238
3409
  }
3239
- function appendArc(sub3, frame, distance) {
3240
- const s = Math.sign(frame.curvature);
3241
- const r = 1 / Math.abs(frame.curvature);
3242
- const sweep = Math.min(distance / r, FULL);
3243
- const pieces = Math.max(1, Math.ceil(sweep / QUARTER - 1e-9));
3244
- const step = sweep / pieces;
3245
- const reach = 4 / 3 * Math.tan(step / 4) * r;
3246
- const c = arcCenter(frame);
3247
- const rx = frame.point.x - c.x;
3248
- const ry = frame.point.y - c.y;
3249
- const at2 = (turn) => {
3250
- const cos = Math.cos(s * turn);
3251
- const sin = Math.sin(s * turn);
3252
- return {
3253
- p: { x: c.x + rx * cos - ry * sin, y: c.y + rx * sin + ry * cos },
3254
- t: { x: frame.tangent.x * cos - frame.tangent.y * sin, y: frame.tangent.x * sin + frame.tangent.y * cos }
3255
- };
3256
- };
3410
+ function appendPieces(sub3, pieces) {
3257
3411
  let prev = sub3[sub3.length - 1];
3258
- for (let i = 1; i <= pieces; i++) {
3259
- const from = at2((i - 1) * step);
3260
- const to = at2(i * step);
3261
- prev.outHandle = { x: from.p.x + from.t.x * reach, y: from.p.y + from.t.y * reach };
3262
- const next = { x: to.p.x, y: to.p.y, inHandle: { x: to.p.x - to.t.x * reach, y: to.p.y - to.t.y * reach } };
3412
+ for (const { pts } of pieces) {
3413
+ const to = pts[pts.length - 1];
3414
+ const next = { x: to.x, y: to.y };
3415
+ if (pts.length === 4) {
3416
+ prev.outHandle = { ...pts[1] };
3417
+ next.inHandle = { ...pts[2] };
3418
+ }
3263
3419
  sub3.push(next);
3264
3420
  prev = next;
3265
3421
  }
@@ -3323,33 +3479,41 @@ function segmentsBesideEnd(path, end) {
3323
3479
  return s === end.sub ? segs.filter((g) => g.index !== (end.end === "last" ? g.count - 1 : 0)) : segs;
3324
3480
  });
3325
3481
  }
3326
- function lengthenOpenEnd(path, end, distance) {
3482
+ function lengthenOpenEnd(path, end, distance, mode = "tangent", opts = {}) {
3327
3483
  if (!(distance > 0) || path.kind !== "polygon") return null;
3328
- return editAnchorSetTracked(path, (set) => lengthenPathEnd(set, end, distance, "tangent"))?.path ?? null;
3484
+ return editAnchorSetTracked(path, (set) => lengthenPathEnd(set, end, distance, mode, opts))?.path ?? null;
3329
3485
  }
3330
3486
  function extendToMeet(path, boundaries, end, opts = {}) {
3331
- const ray = openEndRay(path, end);
3332
- if (!ray || path.kind !== "polygon") return null;
3487
+ if (path.kind !== "polygon") return null;
3488
+ const frame = pathEndFrame(pathToAnchors(path), end);
3489
+ if (!frame) return null;
3490
+ const mode = opts.mode ?? "eased";
3333
3491
  const all = [...boundaries.flatMap((b) => pathSegments(b)), ...segmentsBesideEnd(path, end)];
3334
3492
  let reach = opts.maxDistance ?? 0;
3335
3493
  if (opts.maxDistance === void 0) {
3336
3494
  for (const s of all) {
3337
- for (const p of s.pts) reach = Math.max(reach, Math.hypot(p.x - ray.at.x, p.y - ray.at.y));
3495
+ for (const p of s.pts) reach = Math.max(reach, Math.hypot(p.x - frame.point.x, p.y - frame.point.y));
3338
3496
  }
3497
+ if (mode === "eased") reach += Math.min(easeLength(frame, opts), reach);
3498
+ if (mode === "curvature") reach = Infinity;
3339
3499
  }
3340
3500
  if (!(reach > 0)) return null;
3341
- const line = [ray.at, { x: ray.at.x + ray.dir.x * reach, y: ray.at.y + ray.dir.y * reach }];
3342
- let best = Infinity;
3343
- for (const s of all) {
3344
- for (const h of intersectSegments(line, s.pts)) {
3345
- const d = h.ta * reach;
3346
- if (d > 1e-6 && d < best) best = d;
3501
+ let best = null;
3502
+ let walked = 0;
3503
+ for (const piece of extensionPieces(frame, reach, mode, opts)) {
3504
+ for (const s of all) {
3505
+ for (const h of intersectSegments(piece.pts, s.pts)) {
3506
+ const d = walked + pieceLengthAt(piece, h.ta);
3507
+ if (d > 1e-6 && (best === null || d < best)) best = d;
3508
+ }
3347
3509
  }
3510
+ if (best !== null) break;
3511
+ walked += piece.length;
3348
3512
  }
3349
- if (!Number.isFinite(best)) return null;
3350
- const next = lengthenOpenEnd(path, end, best);
3351
- if (!next) return null;
3352
- return { path: next, at: { x: ray.at.x + ray.dir.x * best, y: ray.at.y + ray.dir.y * best }, distance: best };
3513
+ if (best === null) return null;
3514
+ const next = lengthenOpenEnd(path, end, best, mode, opts);
3515
+ const at2 = next && pathEndFrame(pathToAnchors(next), end)?.point;
3516
+ return next && at2 ? { path: next, at: at2, distance: best } : null;
3353
3517
  }
3354
3518
 
3355
3519
  // src/paths/simplifyPath.ts
@@ -3522,16 +3686,16 @@ function joinPieces(prev, next, v, tin, tout, ctx) {
3522
3686
  const a = endOf(prev.pieces.at(-1)), b = startOf(next.pieces[0]);
3523
3687
  const gap = Math.hypot(b[0] - a[0], b[1] - a[1]);
3524
3688
  if (gap <= ctx.tol * 1e-3) return [];
3525
- const cross2 = tin[0] * tout[1] - tin[1] * tout[0];
3689
+ const cross3 = tin[0] * tout[1] - tin[1] * tout[0];
3526
3690
  const dot3 = tin[0] * tout[0] + tin[1] * tout[1];
3527
- const convex = ctx.sigma * cross2 > 0 || Math.abs(cross2) < 1e-9 && dot3 < 0;
3691
+ const convex = ctx.sigma * cross3 > 0 || Math.abs(cross3) < 1e-9 && dot3 < 0;
3528
3692
  if (gap <= ctx.tol) return snap(prev, next, a);
3529
3693
  if (!convex) {
3530
3694
  if (trimAtCrossing(prev, next)) return [];
3531
3695
  return [lineSegment(a[0], a[1], v[0], v[1]), lineSegment(v[0], v[1], b[0], b[1])];
3532
3696
  }
3533
3697
  const nin = normal(tin, ctx.sigma), nout = normal(tout, ctx.sigma);
3534
- if (ctx.join === "round") return arc(v, ctx.d, nin, Math.atan2(cross2, dot3));
3698
+ if (ctx.join === "round") return arc(v, ctx.d, nin, Math.atan2(cross3, dot3));
3535
3699
  const cos = nin[0] * nout[0] + nin[1] * nout[1];
3536
3700
  if (1 + cos < 1e-9 || Math.sqrt(2 / (1 + cos)) > ctx.miterLimit) return [lineSegment(a[0], a[1], b[0], b[1])];
3537
3701
  const k = ctx.d / (1 + cos);
@@ -3879,13 +4043,13 @@ function reversed2(s) {
3879
4043
  function flattenAll(segs, tol) {
3880
4044
  const rings = [];
3881
4045
  let ring = [];
3882
- for (const { c, line } of segs) {
4046
+ for (const { c, line: line2 } of segs) {
3883
4047
  if (ring.length === 0) ring.push(c[0], c[1]);
3884
4048
  else if (ring[ring.length - 2] !== c[0] || ring[ring.length - 1] !== c[1]) {
3885
4049
  rings.push(ring);
3886
4050
  ring = [c[0], c[1]];
3887
4051
  }
3888
- if (line) ring.push(c[6], c[7]);
4052
+ if (line2) ring.push(c[6], c[7]);
3889
4053
  else flattenCubic(...c, tol, ring);
3890
4054
  }
3891
4055
  if (ring.length > 0) rings.push(ring);
@@ -3943,9 +4107,9 @@ function emit(b, ring) {
3943
4107
  if (ring.length === 0) return;
3944
4108
  const [x0, y0] = ring[0].c;
3945
4109
  b.moveTo(x0, y0);
3946
- for (const [i, { c, line }] of ring.entries()) {
3947
- if (line && i === ring.length - 1 && c[6] === x0 && c[7] === y0) break;
3948
- if (line) b.lineTo(c[6], c[7]);
4110
+ for (const [i, { c, line: line2 }] of ring.entries()) {
4111
+ if (line2 && i === ring.length - 1 && c[6] === x0 && c[7] === y0) break;
4112
+ if (line2) b.lineTo(c[6], c[7]);
3949
4113
  else b.curveTo(c[2], c[3], c[4], c[5], c[6], c[7]);
3950
4114
  }
3951
4115
  b.close();
@@ -4202,17 +4366,17 @@ function worst(samples, dist) {
4202
4366
 
4203
4367
  // src/paths/extremaAnchors.ts
4204
4368
  var END_SLOP = 1e-4;
4205
- function segmentExtremaTs(a, b) {
4369
+ function segmentExtremaTs(a, b, axes = "xy") {
4206
4370
  if (isStraightSegment(a, b)) return [];
4207
4371
  const c1 = a.outHandle ?? a;
4208
4372
  const c2 = b.inHandle ?? b;
4209
4373
  const ts = [
4210
- ...cubicAxisExtremaTs(a.x, c1.x, c2.x, b.x),
4211
- ...cubicAxisExtremaTs(a.y, c1.y, c2.y, b.y)
4374
+ ...axes === "y" ? [] : cubicAxisExtremaTs(a.x, c1.x, c2.x, b.x),
4375
+ ...axes === "x" ? [] : cubicAxisExtremaTs(a.y, c1.y, c2.y, b.y)
4212
4376
  ].filter((t) => t > END_SLOP && t < 1 - END_SLOP).sort((p, q) => p - q);
4213
4377
  return ts.filter((t, i) => i === 0 || t - ts[i - 1] > END_SLOP);
4214
4378
  }
4215
- function addExtremaAnchors(set, include) {
4379
+ function addExtremaAnchors(set, include, axes = "xy") {
4216
4380
  const added = /* @__PURE__ */ new Set();
4217
4381
  set.anchors.forEach((sub3, s) => {
4218
4382
  const segCount = sub3.length - (set.closed[s] && sub3.length > 1 ? 0 : 1);
@@ -4221,7 +4385,7 @@ function addExtremaAnchors(set, include) {
4221
4385
  const ends = segmentEnds(set.anchors, set.closed, s, segIdx);
4222
4386
  if (!ends) continue;
4223
4387
  let end = 1;
4224
- for (const t of segmentExtremaTs(ends.a, ends.b).reverse()) {
4388
+ for (const t of segmentExtremaTs(ends.a, ends.b, axes).reverse()) {
4225
4389
  const at2 = insertAnchorOnSegment(set, { sub: s, segIdx, t: t / end });
4226
4390
  if (at2 >= 0) added.add(sub3[segIdx + 1]);
4227
4391
  end = t;
@@ -5565,7 +5729,8 @@ function scaledFrom(anchor, handle, k) {
5565
5729
  // src/paths/harmonize.ts
5566
5730
  var MAX_PASSES = 64;
5567
5731
  var SETTLED = 1e-12;
5568
- var UNCHANGED = 1e-6;
5732
+ var UNCHANGED = 1e-4;
5733
+ var UNCHANGED_STEPS = 64;
5569
5734
  function harmonizeAnchors(set, flatIndexes) {
5570
5735
  const targets = flatIndexes ? [...new Set(flatIndexes)] : Array.from({ length: anchorCount(set) }, (_, i) => i);
5571
5736
  const located = targets.map((flat) => locateAnchor(set, flat)).filter((loc) => loc !== null);
@@ -5588,7 +5753,9 @@ function harmonizeAnchors(set, flatIndexes) {
5588
5753
  function movedFar(a, from, to) {
5589
5754
  if (!from || !to || from === to) return false;
5590
5755
  const len = Math.hypot(from.x - a.x, from.y - a.y);
5591
- return Math.hypot(to.x - from.x, to.y - from.y) > UNCHANGED * Math.max(len, 1e-9);
5756
+ const magnitude = Math.max(Math.abs(from.x), Math.abs(from.y), Math.abs(a.x), Math.abs(a.y));
5757
+ const tolerance = Math.max(UNCHANGED * len, UNCHANGED_STEPS * magnitude * 2 ** -23, 1e-9);
5758
+ return Math.hypot(to.x - from.x, to.y - from.y) > tolerance;
5592
5759
  }
5593
5760
  function harmonizeOne(set, sub3, idx) {
5594
5761
  const anchors = set.anchors[sub3];
@@ -5803,7 +5970,7 @@ function lengthOf(x, s, grad, row, sign2) {
5803
5970
  if (grad && len > 1e-12) segmentGrad(grad, row, s, sign2 * ux / len, sign2 * uy / len);
5804
5971
  return len;
5805
5972
  }
5806
- function angleRow(x, s, cross2, grad, row) {
5973
+ function angleRow(x, s, cross3, grad, row) {
5807
5974
  const ux = dx(x, s[0]);
5808
5975
  const uy = dy(x, s[0]);
5809
5976
  const vx = dx(x, s[1]);
@@ -5812,10 +5979,10 @@ function angleRow(x, s, cross2, grad, row) {
5812
5979
  const vv = vx * vx + vy * vy;
5813
5980
  const scale3 = Math.sqrt(Math.sqrt(uu) * Math.sqrt(vv));
5814
5981
  if (scale3 < 1e-9) return 0;
5815
- const g = cross2 ? ux * vy - uy * vx : ux * vx + uy * vy;
5982
+ const g = cross3 ? ux * vy - uy * vx : ux * vx + uy * vy;
5816
5983
  const f = g / scale3;
5817
5984
  if (grad) {
5818
- const [gux, guy, gvx, gvy] = cross2 ? [vy, -vx, -uy, ux] : [vx, vy, ux, uy];
5985
+ const [gux, guy, gvx, gvy] = cross3 ? [vy, -vx, -uy, ux] : [vx, vy, ux, uy];
5819
5986
  segmentGrad(grad, row, s[0], gux / scale3 - f / 2 * (ux / uu), guy / scale3 - f / 2 * (uy / uu));
5820
5987
  segmentGrad(grad, row, s[1], gvx / scale3 - f / 2 * (vx / vv), gvy / scale3 - f / 2 * (vy / vv));
5821
5988
  }
@@ -6853,6 +7020,6 @@ function typedArrayReplacer(_key, value) {
6853
7020
  return tagTypedArray(value) ?? value;
6854
7021
  }
6855
7022
 
6856
- 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, 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, 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, viewToMat3, withAnchorPoints, withinPxBox, withinPxRadius, worldEditToStorage, writeAnchorField, zigzagPath };
7023
+ 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, viewToMat3, withAnchorPoints, withinPxBox, withinPxRadius, worldEditToStorage, writeAnchorField, zigzagPath };
6857
7024
  //# sourceMappingURL=index.js.map
6858
7025
  //# sourceMappingURL=index.js.map