@weasel-js/geom 1.11.0 → 1.11.1

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,18 @@
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-4Y67BZL5.js';
2
+ export { CORNER_RESTORE_TOLERANCE, cornerTraces, keepCorners, restoreCorners } from './chunk-4Y67BZL5.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-7F66PQCJ.js';
8
+ import { boundsOfPath, fitPointRun, keepCorner } from './chunk-6FGQML7D.js';
9
+ export { boundsOfPath } from './chunk-6FGQML7D.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
12
  import { nearestOnPath, nearestOnLine, 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';
14
+ import { pointInPolygon, resolveCorners, cornerCuts, fillsOpenSubpaths, schneiderFit, fitCubicWithTangents, flattenCubic, pointSegmentDist2, flattenQuadratic, DEFAULT_FLATTEN_TOLERANCE, segmentsCross } from './chunk-EAKE3N7W.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-EAKE3N7W.js';
16
16
  import { SINGULAR_RATIO } from './chunk-755Y4YZS.js';
17
17
  export { EPS, approxEq, clamp, cross, dot, len2, sign, sub } from './chunk-755Y4YZS.js';
18
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';
@@ -270,7 +270,7 @@ function pointInPath(path, x, y, opts = {}) {
270
270
  return x >= path.x && x <= path.x + path.width && y >= path.y && y <= path.y + path.height;
271
271
  }
272
272
  let crossings = 0;
273
- let winding = 0;
273
+ let winding2 = 0;
274
274
  for (const sub3 of closedSubpaths(path, opts)) {
275
275
  const n = sub3.length;
276
276
  for (let i = 0; i < n; i += 2) {
@@ -280,10 +280,10 @@ function pointInPath(path, x, y, opts = {}) {
280
280
  const ax = sub3[i], bx = sub3[j];
281
281
  if (x >= ax + (y - ay) / (by - ay) * (bx - ax)) continue;
282
282
  crossings++;
283
- winding += ay <= y ? 1 : -1;
283
+ winding2 += ay <= y ? 1 : -1;
284
284
  }
285
285
  }
286
- return path.fillRule === "evenodd" ? (crossings & 1) === 1 : winding !== 0;
286
+ return path.fillRule === "evenodd" ? (crossings & 1) === 1 : winding2 !== 0;
287
287
  }
288
288
  function strokeHitTest(path, x, y, threshold, opts = {}) {
289
289
  const slop = opts.slop;
@@ -620,7 +620,7 @@ function splitSubpaths(path) {
620
620
  let coordStart = 0;
621
621
  let anchorStart = 0;
622
622
  let anchors = 0;
623
- const emit = (end, coordEnd) => {
623
+ const emit2 = (end, coordEnd) => {
624
624
  const sub3 = { ...bare, commands: commands.slice(cmdStart, end), coords: coords.slice(coordStart, coordEnd) };
625
625
  const count = anchors - anchorStart;
626
626
  out.push(path.corners ? carryCorners(path, sub3, anchorRange(anchorStart, count)) : sub3);
@@ -628,13 +628,13 @@ function splitSubpaths(path) {
628
628
  };
629
629
  forEachSegment(commands, coords, (cmd, ci, _px, _py, i) => {
630
630
  if (cmd === PATH_M && i > cmdStart) {
631
- emit(i, ci);
631
+ emit2(i, ci);
632
632
  cmdStart = i;
633
633
  coordStart = ci;
634
634
  }
635
635
  if (cmd !== PATH_Z) anchors++;
636
636
  });
637
- emit(commands.length, coords.length);
637
+ emit2(commands.length, coords.length);
638
638
  return out;
639
639
  }
640
640
 
@@ -812,8 +812,8 @@ function reversePath(path) {
812
812
  anchor++;
813
813
  });
814
814
  flush(false);
815
- const reversed2 = { ...path, commands: new Uint8Array(cmds), coords: new Float32Array(out) };
816
- return path.corners ? carryCorners(path, reversed2, sources) : reversed2;
815
+ const reversed3 = { ...path, commands: new Uint8Array(cmds), coords: new Float32Array(out) };
816
+ return path.corners ? carryCorners(path, reversed3, sources) : reversed3;
817
817
  }
818
818
 
819
819
  // src/paths/pathFromD.ts
@@ -1074,9 +1074,9 @@ function arcToCubics(x1, y1, x2, y2, rx, ry, xAxisRotationDeg, largeArc, sweep,
1074
1074
  rys = ryFixed * ryFixed;
1075
1075
  }
1076
1076
  const sign2 = largeArc === sweep ? -1 : 1;
1077
- const num = rxs * rys - rxs * y1ps - rys * x1ps;
1077
+ const num2 = rxs * rys - rxs * y1ps - rys * x1ps;
1078
1078
  const den = rxs * y1ps + rys * x1ps;
1079
- const factor = sign2 * Math.sqrt(Math.max(0, num / den));
1079
+ const factor = sign2 * Math.sqrt(Math.max(0, num2 / den));
1080
1080
  const cxp = factor * (rxFixed * y1p) / ryFixed;
1081
1081
  const cyp = factor * -(ryFixed * x1p) / rxFixed;
1082
1082
  const cx = cosPhi * cxp - sinPhi * cyp + (x1 + x2) / 2;
@@ -1124,6 +1124,41 @@ function arcToCubics(x1, y1, x2, y2, rx, ry, xAxisRotationDeg, largeArc, sweep,
1124
1124
  }
1125
1125
  }
1126
1126
 
1127
+ // src/paths/pathToD.ts
1128
+ var num = (n) => Number.isFinite(n) ? String(Math.round(n * 1e6) / 1e6) : "0";
1129
+ function pathToD(given) {
1130
+ const path = resolveCorners(given);
1131
+ if (path.kind === "rect") {
1132
+ const { x, y, width, height } = path;
1133
+ return `M${num(x)} ${num(y)}h${num(width)}v${num(height)}h${num(-width)}Z`;
1134
+ }
1135
+ const cmds = path.commands;
1136
+ const xs = path.coords;
1137
+ const out = [];
1138
+ let ci = 0;
1139
+ for (let i = 0; i < cmds.length; i++) {
1140
+ const c = cmds[i];
1141
+ if (c === PATH_M) {
1142
+ out.push(`M${num(xs[ci])} ${num(xs[ci + 1])}`);
1143
+ ci += 2;
1144
+ } else if (c === PATH_L) {
1145
+ out.push(`L${num(xs[ci])} ${num(xs[ci + 1])}`);
1146
+ ci += 2;
1147
+ } else if (c === PATH_C) {
1148
+ out.push(
1149
+ `C${num(xs[ci])} ${num(xs[ci + 1])} ${num(xs[ci + 2])} ${num(xs[ci + 3])} ${num(xs[ci + 4])} ${num(xs[ci + 5])}`
1150
+ );
1151
+ ci += 6;
1152
+ } else if (c === PATH_Q) {
1153
+ out.push(`Q${num(xs[ci])} ${num(xs[ci + 1])} ${num(xs[ci + 2])} ${num(xs[ci + 3])}`);
1154
+ ci += 4;
1155
+ } else if (c === PATH_Z) {
1156
+ out.push("Z");
1157
+ }
1158
+ }
1159
+ return out.join("");
1160
+ }
1161
+
1127
1162
  // src/paths/pathMeasure.ts
1128
1163
  function measurePath(path, opts = {}) {
1129
1164
  const polylines = extractPolylines(
@@ -1162,7 +1197,17 @@ function measurePath(path, opts = {}) {
1162
1197
  const origin = first;
1163
1198
  const closed = contours === 1 && lastClosed;
1164
1199
  if (segments.length === 0) {
1165
- return { length: 0, closed, at: () => ({ point: { ...origin }, tangent: { x: 1, y: 0 } }) };
1200
+ return {
1201
+ length: 0,
1202
+ closed,
1203
+ at: () => ({ point: { ...origin }, tangent: { x: 1, y: 0 } }),
1204
+ nearest: (p) => ({
1205
+ s: 0,
1206
+ dist: Math.hypot(p.x - origin.x, p.y - origin.y),
1207
+ point: { ...origin },
1208
+ tangent: { x: 1, y: 0 }
1209
+ })
1210
+ };
1166
1211
  }
1167
1212
  return {
1168
1213
  length: total,
@@ -1177,6 +1222,26 @@ function measurePath(path, opts = {}) {
1177
1222
  const y = seg.t0y + (seg.t1y - seg.t0y) * u;
1178
1223
  const l = Math.hypot(x, y);
1179
1224
  return { point, tangent: l === 0 ? { x: seg.tx, y: seg.ty } : { x: x / l, y: y / l } };
1225
+ },
1226
+ nearest(p) {
1227
+ let best = segments[0];
1228
+ let bestU = 0;
1229
+ let bestDist = Infinity;
1230
+ for (const seg of segments) {
1231
+ const u = Math.min(Math.max(((p.x - seg.ax) * seg.tx + (p.y - seg.ay) * seg.ty) / seg.len, 0), 1);
1232
+ const d = Math.hypot(p.x - (seg.ax + (seg.bx - seg.ax) * u), p.y - (seg.ay + (seg.by - seg.ay) * u));
1233
+ if (d < bestDist) {
1234
+ best = seg;
1235
+ bestU = u;
1236
+ bestDist = d;
1237
+ }
1238
+ }
1239
+ return {
1240
+ s: best.start + best.len * bestU,
1241
+ dist: bestDist,
1242
+ point: { x: best.ax + (best.bx - best.ax) * bestU, y: best.ay + (best.by - best.ay) * bestU },
1243
+ tangent: { x: best.tx, y: best.ty }
1244
+ };
1180
1245
  }
1181
1246
  };
1182
1247
  }
@@ -1381,13 +1446,13 @@ function matchContours(a, b, opts) {
1381
1446
  const offset2 = Math.round(opts.startOffset ?? 0);
1382
1447
  const directions = opts.reverse !== void 0 ? [opts.reverse] : auto ? [false, true] : [false];
1383
1448
  let best = null;
1384
- for (const reversed2 of directions) {
1385
- const run3 = reversed2 ? reverseContour(y) : y;
1449
+ for (const reversed3 of directions) {
1450
+ const run3 = reversed3 ? reverseContour(y) : y;
1386
1451
  const turns = closed && auto ? run3.segs.length : 1;
1387
1452
  for (let k = 0; k < turns; k++) {
1388
1453
  const [ma, mb] = equalize(x, rotate2(run3, k));
1389
1454
  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 };
1455
+ if (best === null || score < best.score - 1e-9) best = { score, a: ma, b: mb, reversed: reversed3, turn: k };
1391
1456
  }
1392
1457
  }
1393
1458
  const chosen = best;
@@ -3110,6 +3175,243 @@ function emitContour(b, grown) {
3110
3175
  b.close();
3111
3176
  }
3112
3177
 
3178
+ // src/paths/segmentCrossings.ts
3179
+ var STEPS = 16;
3180
+ var END2 = 1e-7;
3181
+ function segmentCrossings(segs) {
3182
+ const flat = segs.map((s) => flatten2(s));
3183
+ const boxes = flat.map(boxOf2);
3184
+ const out = [];
3185
+ for (let a = 0; a < segs.length; a++) {
3186
+ for (let b = a + 1; b < segs.length; b++) {
3187
+ if (!overlaps(boxes[a], boxes[b])) continue;
3188
+ const pa = flat[a], pb = flat[b];
3189
+ for (let i = 0; i + 1 < pa.length; i++) {
3190
+ for (let j = 0; j + 1 < pb.length; j++) {
3191
+ const hit = linesCross(pa[i], pa[i + 1], pb[j], pb[j + 1]);
3192
+ if (hit === null) continue;
3193
+ const guess = [(i + hit[0]) / (pa.length - 1), (j + hit[1]) / (pb.length - 1)];
3194
+ const [s, t] = segs[a].line && segs[b].line ? guess : refine2(segs[a].c, segs[b].c, guess);
3195
+ if (atEnd(s) && atEnd(t)) continue;
3196
+ 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;
3197
+ out.push({ a, b, s, t });
3198
+ }
3199
+ }
3200
+ }
3201
+ }
3202
+ return out;
3203
+ }
3204
+ var atEnd = (t) => t < END2 || t > 1 - END2;
3205
+ function flatten2(seg) {
3206
+ if (seg.line) return [[seg.c[0], seg.c[1]], [seg.c[6], seg.c[7]]];
3207
+ const out = [];
3208
+ for (let i = 0; i <= STEPS; i++) out.push(cubicEvalAt(...seg.c, i / STEPS));
3209
+ return out;
3210
+ }
3211
+ function boxOf2(pts) {
3212
+ const b = [Infinity, Infinity, -Infinity, -Infinity];
3213
+ for (const [x, y] of pts) {
3214
+ b[0] = Math.min(b[0], x);
3215
+ b[1] = Math.min(b[1], y);
3216
+ b[2] = Math.max(b[2], x);
3217
+ b[3] = Math.max(b[3], y);
3218
+ }
3219
+ return b;
3220
+ }
3221
+ var overlaps = (p, q) => p[0] <= q[2] && q[0] <= p[2] && p[1] <= q[3] && q[1] <= p[3];
3222
+ function linesCross(a, b, c, d) {
3223
+ const rx = b[0] - a[0], ry = b[1] - a[1], sx = d[0] - c[0], sy = d[1] - c[1];
3224
+ const den = rx * sy - ry * sx;
3225
+ if (Math.abs(den) < 1e-18) return null;
3226
+ const u = ((c[0] - a[0]) * sy - (c[1] - a[1]) * sx) / den;
3227
+ const w = ((c[0] - a[0]) * ry - (c[1] - a[1]) * rx) / den;
3228
+ return u >= 0 && u <= 1 && w >= 0 && w <= 1 ? [u, w] : null;
3229
+ }
3230
+ function refine2(a, b, guess) {
3231
+ const gap = (s2, t2) => {
3232
+ const p = cubicEvalAt(...a, s2), q = cubicEvalAt(...b, t2);
3233
+ return [p[0] - q[0], p[1] - q[1]];
3234
+ };
3235
+ let [s, t] = guess;
3236
+ let f = gap(s, t);
3237
+ const start = Math.hypot(...f);
3238
+ for (let k = 0; k < 8 && Math.hypot(...f) > 1e-13; k++) {
3239
+ const da = derivative2(a, s), db = derivative2(b, t);
3240
+ const det = -da[0] * db[1] + db[0] * da[1];
3241
+ if (Math.abs(det) < 1e-18) break;
3242
+ s = Math.min(1, Math.max(0, s - (-db[1] * f[0] + db[0] * f[1]) / det));
3243
+ t = Math.min(1, Math.max(0, t - (-da[1] * f[0] + da[0] * f[1]) / det));
3244
+ f = gap(s, t);
3245
+ }
3246
+ return Math.hypot(...f) <= start ? [s, t] : guess;
3247
+ }
3248
+ function derivative2(c, t) {
3249
+ const u = 1 - t;
3250
+ const a = 3 * u * u, b = 6 * u * t, e = 3 * t * t;
3251
+ return [
3252
+ a * (c[2] - c[0]) + b * (c[4] - c[2]) + e * (c[6] - c[4]),
3253
+ a * (c[3] - c[1]) + b * (c[5] - c[3]) + e * (c[7] - c[5])
3254
+ ];
3255
+ }
3256
+
3257
+ // src/paths/mergeRegion.ts
3258
+ function mergeRegion(path, opts = {}) {
3259
+ const tol = opts.tolerance ?? 0.01;
3260
+ const contours = pathToCubicContours(path).filter((c) => c.segs.length > 0);
3261
+ const segs = contours.flatMap((c) => closeContour(c.segs, tol * 1e-3));
3262
+ const fillLeft = pathSignedArea(path) < 0;
3263
+ const cuts = segs.map(() => []);
3264
+ for (const x of segmentCrossings(segs)) {
3265
+ const p = crossingPoint(segs[x.a], x.s, segs[x.b], x.t);
3266
+ cuts[x.a].push({ t: x.s, p });
3267
+ cuts[x.b].push({ t: x.t, p });
3268
+ }
3269
+ const rings = flattenAll(segs, tol / 4);
3270
+ const pieces = [];
3271
+ let changed = false;
3272
+ segs.forEach((seg, i) => {
3273
+ const parts = cutSegment(seg, cuts[i]);
3274
+ if (parts.length > 1) changed = true;
3275
+ for (const part of parts) {
3276
+ const side = filledSide(part, rings, tol);
3277
+ if (side === null) changed = true;
3278
+ else if (side === fillLeft) pieces.push(part);
3279
+ else {
3280
+ changed = true;
3281
+ pieces.push(reversed2(part));
3282
+ }
3283
+ }
3284
+ });
3285
+ const b = new PathBuilder();
3286
+ if (!changed) {
3287
+ for (const c of contours) emit(b, closeContour(c.segs, tol * 1e-3));
3288
+ return b.build();
3289
+ }
3290
+ for (const ring of chain(pieces)) emit(b, ring);
3291
+ return b.build();
3292
+ }
3293
+ function closeContour(given, snap2) {
3294
+ const short = (s) => s.line && Math.hypot(s.c[6] - s.c[0], s.c[7] - s.c[1]) <= snap2;
3295
+ const segs = given.filter((s) => !short(s));
3296
+ if (segs.length === 0) return [];
3297
+ for (let i = 0; i < segs.length; i++) {
3298
+ const prev = segs[(i + segs.length - 1) % segs.length].c, s = segs[i];
3299
+ if (prev[6] !== s.c[0] || prev[7] !== s.c[1]) {
3300
+ 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]]);
3301
+ else if (i === 0) segs.push(lineSegment(prev[6], prev[7], s.c[0], s.c[1]));
3302
+ }
3303
+ }
3304
+ return segs;
3305
+ }
3306
+ function crossingPoint(a, s, b, t) {
3307
+ const end = (seg, u) => u <= 1e-7 ? [seg.c[0], seg.c[1]] : u >= 1 - 1e-7 ? [seg.c[6], seg.c[7]] : null;
3308
+ const fixed = end(a, s) ?? end(b, t);
3309
+ if (fixed) return fixed;
3310
+ const p = cubicEvalAt(...a.c, s), q = cubicEvalAt(...b.c, t);
3311
+ return [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];
3312
+ }
3313
+ function cutSegment(seg, cuts) {
3314
+ const inner = cuts.filter((c) => c.t > 1e-7 && c.t < 1 - 1e-7).sort((x, y) => x.t - y.t);
3315
+ const out = [];
3316
+ let rest = seg, from = 0;
3317
+ let start = [seg.c[0], seg.c[1]];
3318
+ for (const cut of inner) {
3319
+ if (cut.t - from < 1e-9) continue;
3320
+ const [l, r] = splitSegment(rest, (cut.t - from) / (1 - from));
3321
+ out.push(moveEnds(l, start, cut.p));
3322
+ rest = r;
3323
+ from = cut.t;
3324
+ start = cut.p;
3325
+ }
3326
+ out.push(moveEnds(rest, start, [seg.c[6], seg.c[7]]));
3327
+ return out;
3328
+ }
3329
+ function moveEnds(s, a, b) {
3330
+ const c = s.c;
3331
+ return {
3332
+ 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]],
3333
+ line: s.line
3334
+ };
3335
+ }
3336
+ function reversed2(s) {
3337
+ const c = s.c;
3338
+ return { c: [c[6], c[7], c[4], c[5], c[2], c[3], c[0], c[1]], line: s.line };
3339
+ }
3340
+ function flattenAll(segs, tol) {
3341
+ const rings = [];
3342
+ let ring = [];
3343
+ for (const { c, line } of segs) {
3344
+ if (ring.length === 0) ring.push(c[0], c[1]);
3345
+ else if (ring[ring.length - 2] !== c[0] || ring[ring.length - 1] !== c[1]) {
3346
+ rings.push(ring);
3347
+ ring = [c[0], c[1]];
3348
+ }
3349
+ if (line) ring.push(c[6], c[7]);
3350
+ else flattenCubic(...c, tol, ring);
3351
+ }
3352
+ if (ring.length > 0) rings.push(ring);
3353
+ return rings;
3354
+ }
3355
+ function winding(rings, x, y) {
3356
+ let w = 0;
3357
+ for (const r of rings) {
3358
+ for (let i = 0; i + 3 < r.length; i += 2) {
3359
+ const ay = r[i + 1], by = r[i + 3];
3360
+ if (ay > y === by > y) continue;
3361
+ const ax = r[i], bx = r[i + 2];
3362
+ if (x >= ax + (y - ay) / (by - ay) * (bx - ax)) continue;
3363
+ w += ay <= y ? 1 : -1;
3364
+ }
3365
+ }
3366
+ return w;
3367
+ }
3368
+ function filledSide(seg, rings, tol) {
3369
+ const c = seg.c;
3370
+ const [x, y] = cubicEvalAt(...c, 0.5);
3371
+ const dx2 = c[6] + c[4] - c[2] - c[0], dy2 = c[7] + c[5] - c[3] - c[1];
3372
+ const len = Math.hypot(dx2, dy2);
3373
+ if (len < 1e-12) return null;
3374
+ const nx = dy2 / len * tol, ny = -dx2 / len * tol;
3375
+ const left = winding(rings, x + nx, y + ny) !== 0, right = winding(rings, x - nx, y - ny) !== 0;
3376
+ return left === right ? null : left;
3377
+ }
3378
+ function chain(pieces) {
3379
+ const key = (x, y) => `${x},${y}`;
3380
+ const from = /* @__PURE__ */ new Map();
3381
+ pieces.forEach((p, i) => {
3382
+ const k = key(p.c[0], p.c[1]);
3383
+ const list = from.get(k);
3384
+ if (list) list.push(i);
3385
+ else from.set(k, [i]);
3386
+ });
3387
+ const used = new Uint8Array(pieces.length);
3388
+ const rings = [];
3389
+ for (let i = 0; i < pieces.length; i++) {
3390
+ if (used[i]) continue;
3391
+ const ring = [];
3392
+ let at2 = i;
3393
+ while (at2 !== void 0 && !used[at2]) {
3394
+ used[at2] = 1;
3395
+ const p = pieces[at2];
3396
+ ring.push(p);
3397
+ at2 = from.get(key(p.c[6], p.c[7]))?.find((j) => !used[j]);
3398
+ }
3399
+ rings.push(ring);
3400
+ }
3401
+ return rings;
3402
+ }
3403
+ function emit(b, ring) {
3404
+ if (ring.length === 0) return;
3405
+ const [x0, y0] = ring[0].c;
3406
+ b.moveTo(x0, y0);
3407
+ for (const [i, { c, line }] of ring.entries()) {
3408
+ if (line && i === ring.length - 1 && c[6] === x0 && c[7] === y0) break;
3409
+ if (line) b.lineTo(c[6], c[7]);
3410
+ else b.curveTo(c[2], c[3], c[4], c[5], c[6], c[7]);
3411
+ }
3412
+ b.close();
3413
+ }
3414
+
3113
3415
  // src/paths/simplifyAnchors.ts
3114
3416
  function simplifyAnchorsAt(set, flatIndexes, tolerance, opts = {}) {
3115
3417
  const bySub = /* @__PURE__ */ new Map();
@@ -3164,7 +3466,7 @@ var unit3 = (x, y) => {
3164
3466
  const len = Math.hypot(x, y);
3165
3467
  return len === 0 ? { x: 0, y: 0 } : { x: x / len, y: y / len };
3166
3468
  };
3167
- function derivative2(seg, t) {
3469
+ function derivative3(seg, t) {
3168
3470
  const u = 1 - t;
3169
3471
  const { a, c1, c2, b } = seg;
3170
3472
  return {
@@ -3174,8 +3476,8 @@ function derivative2(seg, t) {
3174
3476
  }
3175
3477
  function headingOn(seg, t) {
3176
3478
  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);
3479
+ let d = derivative3(seg, t);
3480
+ if (Math.hypot(d.x, d.y) < 1e-9) d = derivative3(seg, t < 0.5 ? t + 1e-3 : t - 1e-3);
3179
3481
  const h = unit3(d.x, d.y);
3180
3482
  return h.x === 0 && h.y === 0 ? unit3(seg.b.x - seg.a.x, seg.b.y - seg.a.y) : h;
3181
3483
  }
@@ -4338,7 +4640,7 @@ function smudgePath(path, opts) {
4338
4640
  if (!(radius > 0) || strength === 0 || delta.x === 0 && delta.y === 0) return path;
4339
4641
  const detail = opts.detail ?? radius / 3;
4340
4642
  return editAnchorSet(path, (set) => {
4341
- refine2(set, center, radius, detail);
4643
+ refine3(set, center, radius, detail);
4342
4644
  let touched = false;
4343
4645
  let flat = 0;
4344
4646
  for (const sub3 of set.anchors) {
@@ -4358,7 +4660,7 @@ function weight(p, c, radius) {
4358
4660
  const u2 = ((p.x - c.x) ** 2 + (p.y - c.y) ** 2) / (radius * radius);
4359
4661
  return u2 >= 1 ? 0 : (1 - u2) ** 2;
4360
4662
  }
4361
- function refine2(set, c, radius, detail) {
4663
+ function refine3(set, c, radius, detail) {
4362
4664
  if (!Number.isFinite(detail) || detail <= 0) return;
4363
4665
  let added = 0;
4364
4666
  for (let sub3 = 0; sub3 < set.anchors.length; sub3++) {
@@ -5579,7 +5881,8 @@ function constrainPathsEdit(items, links = []) {
5579
5881
  const sliding = new Set(kept.flatMap((c) => c.kind === "onPath" ? [c.anchor] : []));
5580
5882
  const still = (i) => {
5581
5883
  const { item } = itemOf(i, nowStart);
5582
- return items.length > 1 && items[item].before === items[item].after && !sliding.has(i);
5884
+ const it = items[item];
5885
+ return items.length > 1 && it.before === it.after && !it.yields && !sliding.has(i);
5583
5886
  };
5584
5887
  const held = nowAll.flatMap((p, i) => {
5585
5888
  const r = reference[i];
@@ -6001,6 +6304,6 @@ function typedArrayReplacer(_key, value) {
6001
6304
  return tagTypedArray(value) ?? value;
6002
6305
  }
6003
6306
 
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 };
6307
+ 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, 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, pathSignedArea, pathThroughPoseFrame, pathToCubicContours, pathToD, 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 };
6005
6308
  //# sourceMappingURL=index.js.map
6006
6309
  //# sourceMappingURL=index.js.map