@weasel-js/geom 1.10.0 → 1.10.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,13 +1,13 @@
1
1
  import { PORT_REACH, portControls, portCurvePoints } from './chunk-YQADYJJ7.js';
2
2
  export { PORT_REACH } from './chunk-YQADYJJ7.js';
3
- import { cubicPointAt, splitCubicAtT, fitCubicThroughDeletion } from './chunk-XS6ERH3X.js';
4
- export { cubicPointAt, fitCubicThroughDeletion, splitCubicAtT } from './chunk-XS6ERH3X.js';
3
+ import { cubicPointAt, splitCubicAtT } from './chunk-NJ3IGAKR.js';
4
+ export { cubicPointAt, splitCubicAtT } from './chunk-NJ3IGAKR.js';
5
5
  import { cubicEvalAt, elevateQuadraticToCubic, cubicBounds } from './chunk-URQMDR6Q.js';
6
6
  export { cubicBounds, cubicEvalAt, elevateQuadraticToCubic, quadraticEvalAt, splitCubicAt, splitLineAt, splitQuadraticAt } from './chunk-URQMDR6Q.js';
7
- import { extractPolylines, rectToContour } from './chunk-MDT7F32A.js';
8
- export { boundsOfCoords, boxContainsBox, boxContainsPoint, rectToContour, unionBox } from './chunk-MDT7F32A.js';
9
- import { fillsOpenSubpaths, pointInPolygon, pointSegmentDist2, flattenQuadratic, flattenCubic, DEFAULT_FLATTEN_TOLERANCE, segmentsCross } from './chunk-WQMYOCFB.js';
10
- export { DEFAULT_FLATTEN_TOLERANCE, defaultOpenFill, fillsOpenSubpaths, flattenCubic, flattenCubicWithArcLen, flattenQuadratic, flattenQuadraticWithArcLen, pointInPolygon, pointSegmentDist2, segmentsCross, setDefaultOpenFill } from './chunk-WQMYOCFB.js';
7
+ import { extractPolylines, rectToContour } from './chunk-7WFFCHRL.js';
8
+ export { boundsOfCoords, boxContainsBox, boxContainsPoint, rectToContour, unionBox } from './chunk-7WFFCHRL.js';
9
+ import { fillsOpenSubpaths, pointInPolygon, pointSegmentDist2, flattenQuadratic, flattenCubic, DEFAULT_FLATTEN_TOLERANCE, segmentsCross } from './chunk-IDXY2NCA.js';
10
+ export { DEFAULT_FLATTEN_TOLERANCE, defaultOpenFill, fillsOpenSubpaths, flattenCubic, flattenCubicWithArcLen, flattenQuadratic, flattenQuadraticWithArcLen, pointInPolygon, pointSegmentDist2, segmentsCross, setDefaultOpenFill } from './chunk-IDXY2NCA.js';
11
11
  import { SINGULAR_RATIO } from './chunk-755Y4YZS.js';
12
12
  export { EPS, approxEq, clamp, cross, dot, len2, sign, sub } from './chunk-755Y4YZS.js';
13
13
  import { forEachSegment, PATH_M, PATH_L, PATH_Q, PATH_C, PATH_Z, pathCommandCoordCount } from './chunk-OXH3NOXC.js';
@@ -19,7 +19,7 @@ function normalizeZoom(zoom) {
19
19
  return zoom >= ZOOM_FLOOR && zoom < Infinity ? zoom : ZOOM_FLOOR;
20
20
  }
21
21
 
22
- // src/mat3.ts
22
+ // src/affine.ts
23
23
  function identity() {
24
24
  return [1, 0, 0, 1, 0, 0];
25
25
  }
@@ -66,6 +66,16 @@ function boxToBox(sx, sy, sw, sh, dx, dy, dw, dh) {
66
66
  function rotateAboutPoint(cx, cy, rad) {
67
67
  return multiply(translate(cx, cy), multiply(rotate(rad), translate(-cx, -cy)));
68
68
  }
69
+ function transformCoords(coords, m) {
70
+ const out = new Float64Array(coords.length);
71
+ const a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5];
72
+ for (let i = 0; i + 1 < coords.length; i += 2) {
73
+ const x = coords[i], y = coords[i + 1];
74
+ out[i] = a * x + c * y + e;
75
+ out[i + 1] = b * x + d * y + f;
76
+ }
77
+ return out;
78
+ }
69
79
 
70
80
  // src/nearest.ts
71
81
  function nearestOnLine(px, py, x0, y0, x1, y1) {
@@ -397,18 +407,6 @@ function unionBoundsPath(paths) {
397
407
  return { kind: "rect", x: minX, y: minY, width: maxX - minX, height: maxY - minY };
398
408
  }
399
409
 
400
- // src/affine.ts
401
- function transformCoords(coords, m) {
402
- const out = new Float64Array(coords.length);
403
- const a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5];
404
- for (let i = 0; i + 1 < coords.length; i += 2) {
405
- const x = coords[i], y = coords[i + 1];
406
- out[i] = a * x + c * y + e;
407
- out[i + 1] = b * x + d * y + f;
408
- }
409
- return out;
410
- }
411
-
412
410
  // src/paths/transformPath.ts
413
411
  var AXIS_ALIGNED_EPSILON = 1e-9;
414
412
  function transformPath(path, m) {
@@ -1078,15 +1076,26 @@ function measureError(samples, fit) {
1078
1076
  return { worstIndex, worstError };
1079
1077
  }
1080
1078
  function fitOneCubic(samples) {
1079
+ const n = samples.length;
1080
+ return fitWithTangents(
1081
+ samples,
1082
+ unit(sub2(samples[1], samples[0])),
1083
+ unit(sub2(samples[n - 2], samples[n - 1])),
1084
+ 4
1085
+ );
1086
+ }
1087
+ function fitCubicWithTangents(samples, tStart, tEnd, opts = {}) {
1088
+ const { cp1, cp2 } = fitWithTangents(samples, tStart, tEnd, opts.passes ?? 4, opts.params);
1089
+ return { cp1, cp2 };
1090
+ }
1091
+ function fitWithTangents(samples, tStart, tEnd, passes, params) {
1081
1092
  const n = samples.length;
1082
1093
  const a = samples[0];
1083
1094
  const e = samples[n - 1];
1084
- const tStart = unit(sub2(samples[1], a));
1085
- const tEnd = unit(sub2(samples[n - 2], e));
1086
- const ts = chordLengthParam(samples);
1095
+ const ts = params ? [...params] : chordLengthParam(samples);
1087
1096
  if (ts === null) return { cp1: { ...a }, cp2: { ...e }, end: e, ts: samples.map(() => 0) };
1088
1097
  let fit = solveLS(samples, ts, a, e, tStart, tEnd);
1089
- for (let pass = 0; pass < 4; pass++) {
1098
+ for (let pass = 0; pass < passes; pass++) {
1090
1099
  reparameterize(samples, ts, a, e, fit);
1091
1100
  fit = solveLS(samples, ts, a, e, tStart, tEnd);
1092
1101
  }
@@ -1189,127 +1198,920 @@ function emitStraightCubic(b, a, c) {
1189
1198
  b.curveTo(cp1x, cp1y, cp2x, cp2y, c.x, c.y);
1190
1199
  }
1191
1200
 
1192
- // src/pathSubpaths.ts
1193
- function flatSubpaths(path, tolerance) {
1201
+ // src/paths/spanFit.ts
1202
+ var SAMPLES_PER_SEGMENT = 16;
1203
+ var SPLIT_SEARCH_STEPS = 40;
1204
+ function fitCubicThroughDeletion(prev, removed, next) {
1205
+ const first = segmentControls(prev, removed);
1206
+ const second = segmentControls(removed, next);
1207
+ const samples = [...sampleCubic(first), ...sampleCubic(second).slice(1)];
1208
+ const tStart = startTangent(first);
1209
+ const tEnd = startTangent(reversed(second));
1210
+ const fitAt = (s) => {
1211
+ const params = splitParams(s);
1212
+ const fit = fitCubicWithTangents(samples, tStart, tEnd, { params, passes: 0 });
1213
+ return { fit, error: squaredError(samples, params, prev, fit.cp1, fit.cp2, next) };
1214
+ };
1215
+ let lo = 0.02;
1216
+ let hi = 0.98;
1217
+ const phi = (Math.sqrt(5) - 1) / 2;
1218
+ let m1 = hi - phi * (hi - lo);
1219
+ let m2 = lo + phi * (hi - lo);
1220
+ let e1 = fitAt(m1).error;
1221
+ let e2 = fitAt(m2).error;
1222
+ for (let i = 0; i < SPLIT_SEARCH_STEPS; i++) {
1223
+ if (e1 < e2) {
1224
+ hi = m2;
1225
+ m2 = m1;
1226
+ e2 = e1;
1227
+ m1 = hi - phi * (hi - lo);
1228
+ e1 = fitAt(m1).error;
1229
+ } else {
1230
+ lo = m1;
1231
+ m1 = m2;
1232
+ e1 = e2;
1233
+ m2 = lo + phi * (hi - lo);
1234
+ e2 = fitAt(m2).error;
1235
+ }
1236
+ }
1237
+ const { cp1, cp2 } = fitCubicWithTangents(samples, tStart, tEnd, {
1238
+ params: splitParams((lo + hi) / 2)
1239
+ });
1240
+ return { c1: cp1, c2: cp2 };
1241
+ }
1242
+ function splitParams(s) {
1243
+ const out = [];
1244
+ for (let i = 0; i <= SAMPLES_PER_SEGMENT; i++) out.push(s * i / SAMPLES_PER_SEGMENT);
1245
+ for (let i = 1; i <= SAMPLES_PER_SEGMENT; i++) out.push(s + (1 - s) * i / SAMPLES_PER_SEGMENT);
1246
+ return out;
1247
+ }
1248
+ function squaredError(samples, params, p0, p1, p2, p3) {
1249
+ let sum = 0;
1250
+ for (let i = 0; i < samples.length; i++) {
1251
+ const q = cubicPointAt(p0, p1, p2, p3, params[i]);
1252
+ sum += (q.x - samples[i].x) ** 2 + (q.y - samples[i].y) ** 2;
1253
+ }
1254
+ return sum;
1255
+ }
1256
+ function segmentControls(a, b) {
1257
+ return [
1258
+ { x: a.x, y: a.y },
1259
+ a.outHandle ?? { x: a.x, y: a.y },
1260
+ b.inHandle ?? { x: b.x, y: b.y },
1261
+ { x: b.x, y: b.y }
1262
+ ];
1263
+ }
1264
+ function sampleCubic([p0, p1, p2, p3]) {
1265
+ const out = [];
1266
+ for (let i = 0; i <= SAMPLES_PER_SEGMENT; i++) {
1267
+ out.push(cubicPointAt(p0, p1, p2, p3, i / SAMPLES_PER_SEGMENT));
1268
+ }
1269
+ return out;
1270
+ }
1271
+ function reversed([p0, p1, p2, p3]) {
1272
+ return [p3, p2, p1, p0];
1273
+ }
1274
+ function startTangent([p0, ...rest]) {
1275
+ for (const p of rest) {
1276
+ const dx = p.x - p0.x;
1277
+ const dy = p.y - p0.y;
1278
+ const len = Math.hypot(dx, dy);
1279
+ if (len > 1e-9) return { x: dx / len, y: dy / len };
1280
+ }
1281
+ return { x: 0, y: 0 };
1282
+ }
1283
+
1284
+ // src/paths/anchors.ts
1285
+ function pathToAnchors(path) {
1194
1286
  const { commands, coords } = path;
1287
+ const anchors = [];
1195
1288
  const closed = [];
1196
- const open = [];
1197
- let sub3 = [];
1198
- forEachSegment(commands, coords, (cmd, ci, curX, curY, _i, opensSubpath) => {
1199
- if (opensSubpath) sub3 = [curX, curY];
1289
+ let current = null;
1290
+ forEachSegment(commands, coords, (cmd, ci) => {
1200
1291
  switch (cmd) {
1201
- case PATH_M:
1202
- if (sub3.length >= 2) open.push(sub3);
1203
- sub3 = [coords[ci], coords[ci + 1]];
1292
+ case PATH_M: {
1293
+ if (current) {
1294
+ anchors.push(current);
1295
+ closed.push(false);
1296
+ }
1297
+ current = [{ x: coords[ci], y: coords[ci + 1] }];
1204
1298
  break;
1205
- case PATH_L:
1206
- sub3.push(coords[ci], coords[ci + 1]);
1299
+ }
1300
+ case PATH_L: {
1301
+ if (!current) throw new Error("pathToAnchors: L without prior M");
1302
+ current.push({ x: coords[ci], y: coords[ci + 1] });
1207
1303
  break;
1208
- case PATH_C:
1209
- flattenCubic(
1210
- curX,
1211
- curY,
1212
- coords[ci],
1213
- coords[ci + 1],
1214
- coords[ci + 2],
1215
- coords[ci + 3],
1216
- coords[ci + 4],
1217
- coords[ci + 5],
1218
- tolerance,
1219
- sub3
1220
- );
1304
+ }
1305
+ case PATH_C: {
1306
+ if (!current) throw new Error("pathToAnchors: C without prior M");
1307
+ const prev = current[current.length - 1];
1308
+ prev.outHandle = { x: coords[ci], y: coords[ci + 1] };
1309
+ current.push({
1310
+ x: coords[ci + 4],
1311
+ y: coords[ci + 5],
1312
+ inHandle: { x: coords[ci + 2], y: coords[ci + 3] }
1313
+ });
1221
1314
  break;
1222
- case PATH_Q:
1223
- flattenQuadratic(
1224
- curX,
1225
- curY,
1226
- coords[ci],
1227
- coords[ci + 1],
1228
- coords[ci + 2],
1229
- coords[ci + 3],
1230
- tolerance,
1231
- sub3
1232
- );
1315
+ }
1316
+ case PATH_Q: {
1317
+ if (!current) throw new Error("pathToAnchors: Q without prior M");
1318
+ const handle = { x: coords[ci], y: coords[ci + 1] };
1319
+ const prev = current[current.length - 1];
1320
+ prev.outHandle = handle;
1321
+ current.push({ x: coords[ci + 2], y: coords[ci + 3], inHandle: { ...handle } });
1233
1322
  break;
1234
- case PATH_Z:
1235
- if (sub3.length >= 6) closed.push(sub3);
1236
- else if (sub3.length >= 2) open.push(sub3);
1237
- sub3 = [];
1323
+ }
1324
+ case PATH_Z: {
1325
+ if (current) {
1326
+ foldClosingCurve(current);
1327
+ anchors.push(current);
1328
+ closed.push(true);
1329
+ current = null;
1330
+ }
1238
1331
  break;
1332
+ }
1239
1333
  default:
1240
- throw new Error(`pathHitTest: unknown command ${cmd}`);
1334
+ throw new Error(`pathToAnchors: unknown command ${cmd}`);
1241
1335
  }
1242
1336
  });
1243
- if (sub3.length >= 2) open.push(sub3);
1244
- return { closed, open };
1245
- }
1246
- function regionSubpaths(path, opts) {
1247
- const flat = flatSubpaths(path, opts.tolerance ?? DEFAULT_FLATTEN_TOLERANCE);
1248
- if (!fillsOpenSubpaths(path)) return flat;
1249
- return {
1250
- closed: [...flat.closed, ...flat.open.filter((sub3) => sub3.length >= 6)],
1251
- open: flat.open.filter((sub3) => sub3.length < 6)
1252
- };
1337
+ if (current) {
1338
+ anchors.push(current);
1339
+ closed.push(false);
1340
+ }
1341
+ return { anchors, closed };
1253
1342
  }
1254
- function closedSubpaths(path, opts) {
1255
- return regionSubpaths(path, opts).closed;
1343
+ function foldClosingCurve(sub3) {
1344
+ if (sub3.length < 2) return;
1345
+ const first = sub3[0];
1346
+ const last = sub3[sub3.length - 1];
1347
+ if (!last.inHandle || last.x !== first.x || last.y !== first.y) return;
1348
+ sub3.pop();
1349
+ first.inHandle = last.inHandle;
1256
1350
  }
1257
-
1258
- // src/screenBand.ts
1259
- function stretchOf(m) {
1260
- const e = (m.a + m.d) / 2;
1261
- const f = (m.a - m.d) / 2;
1262
- const g = (m.b + m.c) / 2;
1263
- const h = (m.b - m.c) / 2;
1264
- const q = Math.hypot(e, h);
1265
- const r = Math.hypot(f, g);
1266
- const phi = (Math.atan2(h, e) + Math.atan2(g, f)) / 2;
1267
- return { s1: q + r, s2: Math.abs(q - r), phi };
1351
+ function anchorsToPath(anchors, closed, like) {
1352
+ const b = new PathBuilder();
1353
+ for (let s = 0; s < anchors.length; s++) {
1354
+ const sub3 = anchors[s];
1355
+ if (sub3.length === 0) continue;
1356
+ b.moveTo(sub3[0].x, sub3[0].y);
1357
+ for (let i = 1; i < sub3.length; i++) {
1358
+ const prev = sub3[i - 1];
1359
+ const cur = sub3[i];
1360
+ const hasHandle = prev.outHandle != null || cur.inHandle != null;
1361
+ if (!hasHandle) {
1362
+ b.lineTo(cur.x, cur.y);
1363
+ } else {
1364
+ const c1 = prev.outHandle ?? { x: prev.x, y: prev.y };
1365
+ const c2 = cur.inHandle ?? { x: cur.x, y: cur.y };
1366
+ b.curveTo(c1.x, c1.y, c2.x, c2.y, cur.x, cur.y);
1367
+ }
1368
+ }
1369
+ if (closed[s]) {
1370
+ const last = sub3[sub3.length - 1];
1371
+ const first = sub3[0];
1372
+ const hasHandle = last.outHandle != null || first.inHandle != null;
1373
+ if (hasHandle) {
1374
+ const c1 = last.outHandle ?? { x: last.x, y: last.y };
1375
+ const c2 = first.inHandle ?? { x: first.x, y: first.y };
1376
+ b.curveTo(c1.x, c1.y, c2.x, c2.y, first.x, first.y);
1377
+ }
1378
+ b.close();
1379
+ }
1380
+ }
1381
+ const built = b.build();
1382
+ return like ? { ...like, commands: built.commands, coords: built.coords } : built;
1268
1383
  }
1269
- function isSimilarity(s) {
1270
- return s.s1 - s.s2 <= 1e-9 * s.s1;
1384
+ var SMOOTH_THRESHOLD = 1e-3;
1385
+ function isAnchorSmooth(a) {
1386
+ if (!a.inHandle || !a.outHandle) return false;
1387
+ const inDX = a.inHandle.x - a.x;
1388
+ const inDY = a.inHandle.y - a.y;
1389
+ const outDX = a.outHandle.x - a.x;
1390
+ const outDY = a.outHandle.y - a.y;
1391
+ const inLen = Math.hypot(inDX, inDY);
1392
+ const outLen = Math.hypot(outDX, outDY);
1393
+ if (inLen === 0 || outLen === 0) return false;
1394
+ const cross2 = (inDX * outDY - inDY * outDX) / (inLen * outLen);
1395
+ return Math.abs(cross2) < SMOOTH_THRESHOLD;
1271
1396
  }
1272
- function capsuleWithinPx(px0, py0, ax, ay, bx, by, r, slop, m, st) {
1273
- const sax = m.a * ax + m.c * ay, say = m.b * ax + m.d * ay;
1274
- const sbx = m.a * bx + m.c * by, sby = m.b * bx + m.d * by;
1275
- const slop2 = slop * slop;
1276
- const dCenter2 = pointSegmentDist2(px0, py0, sax, say, sbx, sby);
1277
- if (dCenter2 <= slop2) return true;
1278
- if (r <= 0) return false;
1279
- const reach = slop + r * st.s1;
1280
- if (dCenter2 > reach * reach) return false;
1281
- const wx = bx - ax, wy = by - ay;
1282
- const wl = Math.hypot(wx, wy);
1283
- if (wl > 0) {
1284
- const nx = -wy / wl * r, ny = wx / wl * r;
1285
- const ox = m.a * nx + m.c * ny, oy = m.b * nx + m.d * ny;
1286
- if (quadWithinPx(px0, py0, [
1287
- sax + ox,
1288
- say + oy,
1289
- sbx + ox,
1290
- sby + oy,
1291
- sbx - ox,
1292
- sby - oy,
1293
- sax - ox,
1294
- say - oy
1295
- ], slop2)) return true;
1397
+ function countPathAnchors(path) {
1398
+ if (path.kind === "rect") return 4;
1399
+ const cmds = path.commands;
1400
+ let n = 0;
1401
+ for (let i = 0; i < cmds.length; i++) {
1402
+ const c = cmds[i];
1403
+ if (c === PATH_M || c === PATH_L || c === PATH_C || c === PATH_Q) n++;
1296
1404
  }
1297
- return ellipseWithinPx(px0 - sax, py0 - say, r, slop, st) || ellipseWithinPx(px0 - sbx, py0 - sby, r, slop, st);
1405
+ return n;
1298
1406
  }
1299
- function quadWithinPx(x, y, q, slop2) {
1300
- let pos = 0, neg = 0;
1301
- for (let i = 0; i < 8; i += 2) {
1302
- const j = (i + 2) % 8;
1303
- const cr = (q[j] - q[i]) * (y - q[i + 1]) - (q[j + 1] - q[i + 1]) * (x - q[i]);
1304
- if (cr > 0) pos++;
1305
- else if (cr < 0) neg++;
1306
- }
1307
- if (pos === 0 || neg === 0) return true;
1308
- for (let i = 0; i < 8; i += 2) {
1309
- const j = (i + 2) % 8;
1310
- if (pointSegmentDist2(x, y, q[i], q[i + 1], q[j], q[j + 1]) <= slop2) return true;
1311
- }
1312
- return false;
1407
+ function segmentEnds(anchors, closed, sub3, segIdx) {
1408
+ const s = anchors[sub3];
1409
+ if (!s || segIdx < 0) return null;
1410
+ if (segIdx + 1 < s.length) return { a: s[segIdx], b: s[segIdx + 1] };
1411
+ if (closed?.[sub3] && s.length > 1 && segIdx === s.length - 1) return { a: s[segIdx], b: s[0] };
1412
+ return null;
1413
+ }
1414
+ function isStraightSegment(a, b) {
1415
+ return a.outHandle == null && b.inHandle == null;
1416
+ }
1417
+ function nearestOnSegment(a, b, x, y) {
1418
+ const p1 = a.outHandle ?? a;
1419
+ const p2 = b.inHandle ?? b;
1420
+ const hit = isStraightSegment(a, b) ? nearestOnLine(x, y, a.x, a.y, b.x, b.y) : nearestOnCubic(x, y, a.x, a.y, p1.x, p1.y, p2.x, p2.y, b.x, b.y);
1421
+ return { t: hit.t, d2: hit.dist * hit.dist };
1422
+ }
1423
+ function nearestSegmentT(anchors, wx, wy, closed) {
1424
+ let bestD2 = Infinity;
1425
+ let best = null;
1426
+ for (let s = 0; s < anchors.length; s++) {
1427
+ const segCount = anchors[s].length - (closed?.[s] ? 0 : 1);
1428
+ for (let i = 0; i < segCount; i++) {
1429
+ const ends = segmentEnds(anchors, closed, s, i);
1430
+ if (!ends) continue;
1431
+ const hit = nearestOnSegment(ends.a, ends.b, wx, wy);
1432
+ if (hit.d2 < bestD2) {
1433
+ bestD2 = hit.d2;
1434
+ best = { sub: s, segIdx: i, t: hit.t };
1435
+ }
1436
+ }
1437
+ }
1438
+ return best;
1439
+ }
1440
+
1441
+ // src/paths/simplifyPath.ts
1442
+ var CUBIC_SAMPLES2 = 12;
1443
+ var MAX_LINE_SAMPLES = 8;
1444
+ var LONG_STRAIGHT = 4;
1445
+ function simplifyPath(path, tolerance, opts = {}) {
1446
+ const tol = Math.max(tolerance, 1e-6);
1447
+ const cosCorner = Math.cos((opts.cornerAngle ?? 40) * Math.PI / 180);
1448
+ const { anchors, closed } = pathToAnchors(path);
1449
+ const out = [];
1450
+ const outClosed = [];
1451
+ anchors.forEach((sub3, s) => {
1452
+ if (sub3.length < 2) return;
1453
+ out.push(simplifySubpath(sub3, closed[s], tol, cosCorner));
1454
+ outClosed.push(closed[s]);
1455
+ });
1456
+ return anchorsToPath(out, outClosed, path);
1457
+ }
1458
+ function simplifySubpath(sub3, closed, tol, cosCorner) {
1459
+ const n = sub3.length;
1460
+ const breaks = /* @__PURE__ */ new Set();
1461
+ for (let i = 0; i < n; i++) {
1462
+ if (isCorner(sub3, i, closed, cosCorner)) breaks.add(i);
1463
+ if (isLongStraight(sub3, i, closed)) {
1464
+ breaks.add(i);
1465
+ breaks.add((i + 1) % n);
1466
+ }
1467
+ }
1468
+ const corners = [...breaks].sort((a, b) => a - b);
1469
+ const result = [];
1470
+ if (closed && corners.length === 0) {
1471
+ const pts = sampleRun(sub3, 0, n, tol);
1472
+ const t = unit2(sub22(pts[1], pts[pts.length - 2]));
1473
+ fitRun(pts, t, neg(t), tol, result, true);
1474
+ return result;
1475
+ }
1476
+ const stops = closed ? [...corners, corners[0] + n] : corners;
1477
+ for (let k = 0; k + 1 < stops.length; k++) {
1478
+ const pts = sampleRun(sub3, stops[k], stops[k + 1], tol);
1479
+ const last = closed && k + 2 === stops.length;
1480
+ fitRun(pts, unit2(sub22(pts[1], pts[0])), unit2(sub22(pts[pts.length - 2], pts[pts.length - 1])), tol, result, last);
1481
+ }
1482
+ return result;
1483
+ }
1484
+ function isCorner(sub3, i, closed, cosCorner) {
1485
+ const n = sub3.length;
1486
+ const a = sub3[i];
1487
+ const prev = sub3[(i - 1 + n) % n];
1488
+ const next = sub3[(i + 1) % n];
1489
+ if (!closed && (i === 0 || i === n - 1)) return true;
1490
+ const from = a.inHandle && !same(a.inHandle, a) ? a.inHandle : prev.outHandle && !same(prev.outHandle, a) ? prev.outHandle : prev;
1491
+ const to = a.outHandle && !same(a.outHandle, a) ? a.outHandle : next.inHandle && !same(next.inHandle, a) ? next.inHandle : next;
1492
+ const d1 = unit2(sub22(a, from));
1493
+ const d2 = unit2(sub22(to, a));
1494
+ return d1.x * d2.x + d1.y * d2.y < cosCorner;
1495
+ }
1496
+ function isLongStraight(sub3, i, closed) {
1497
+ const n = sub3.length;
1498
+ const len = (k) => {
1499
+ if (!closed && (k < 0 || k >= n - 1)) return 0;
1500
+ const a2 = sub3[(k + n) % n];
1501
+ const b2 = sub3[(k + 1) % n];
1502
+ return Math.hypot(b2.x - a2.x, b2.y - a2.y);
1503
+ };
1504
+ if (!closed && i >= n - 1) return false;
1505
+ const a = sub3[i];
1506
+ const b = sub3[(i + 1) % n];
1507
+ if (a.outHandle || b.inHandle) return false;
1508
+ return len(i) >= LONG_STRAIGHT * Math.max(len(i - 1), len(i + 1));
1509
+ }
1510
+ function sampleRun(sub3, from, to, tol) {
1511
+ const n = sub3.length;
1512
+ const pts = [{ x: sub3[from % n].x, y: sub3[from % n].y }];
1513
+ for (let i = from; i < to; i++) {
1514
+ const a = sub3[i % n];
1515
+ const b = sub3[(i + 1) % n];
1516
+ if (!a.outHandle && !b.inHandle) {
1517
+ const len = Math.hypot(b.x - a.x, b.y - a.y);
1518
+ const steps = Math.min(MAX_LINE_SAMPLES, Math.max(1, Math.floor(len / (4 * tol))));
1519
+ for (let k = 1; k <= steps; k++) pts.push({ x: a.x + (b.x - a.x) * k / steps, y: a.y + (b.y - a.y) * k / steps });
1520
+ } else {
1521
+ const c1 = a.outHandle ?? a;
1522
+ const c2 = b.inHandle ?? b;
1523
+ for (let k = 1; k <= CUBIC_SAMPLES2; k++) pts.push(cubicPointAt(a, c1, c2, b, k / CUBIC_SAMPLES2));
1524
+ }
1525
+ }
1526
+ return dedupe(pts);
1527
+ }
1528
+ function fitRun(pts, tStart, tEnd, tol, out, closing) {
1529
+ if (out.length === 0) out.push({ x: pts[0].x, y: pts[0].y });
1530
+ const segs = [];
1531
+ fitRecursive2(pts, tStart, tEnd, tol, segs);
1532
+ segs.forEach((seg, i) => {
1533
+ const prev = out[out.length - 1];
1534
+ if (seg.c1) prev.outHandle = seg.c1;
1535
+ const isLast = i === segs.length - 1;
1536
+ if (closing && isLast) {
1537
+ if (seg.c2) out[0].inHandle = seg.c2;
1538
+ return;
1539
+ }
1540
+ out.push(seg.c2 ? { x: seg.end.x, y: seg.end.y, inHandle: seg.c2 } : { x: seg.end.x, y: seg.end.y });
1541
+ });
1542
+ }
1543
+ function fitRecursive2(pts, tStart, tEnd, tol, out) {
1544
+ const a = pts[0];
1545
+ const e = pts[pts.length - 1];
1546
+ if (pts.length <= 2 || worstFromLine(pts, a, e).dist <= tol) {
1547
+ out.push({ end: e });
1548
+ return;
1549
+ }
1550
+ const { cp1, cp2 } = fitCubicWithTangents(pts, tStart, tEnd);
1551
+ const worst = worstFromCubic(pts, a, cp1, cp2, e);
1552
+ if (worst.dist <= tol) {
1553
+ out.push({ c1: cp1, c2: cp2, end: e });
1554
+ return;
1555
+ }
1556
+ const k = worst.index;
1557
+ const center = unit2(sub22(pts[k + 1], pts[k - 1]));
1558
+ fitRecursive2(pts.slice(0, k + 1), tStart, neg(center), tol, out);
1559
+ fitRecursive2(pts.slice(k), center, tEnd, tol, out);
1560
+ }
1561
+ function worstFromLine(pts, a, e) {
1562
+ let index = 1, dist2 = 0;
1563
+ for (let i = 1; i < pts.length - 1; i++) {
1564
+ const d = nearestOnLine(pts[i].x, pts[i].y, a.x, a.y, e.x, e.y).dist;
1565
+ if (d > dist2) {
1566
+ dist2 = d;
1567
+ index = i;
1568
+ }
1569
+ }
1570
+ return { index, dist: dist2 };
1571
+ }
1572
+ function worstFromCubic(pts, a, c1, c2, e) {
1573
+ let index = 1, dist2 = 0;
1574
+ for (let i = 1; i < pts.length - 1; i++) {
1575
+ const d = nearestOnCubic(pts[i].x, pts[i].y, a.x, a.y, c1.x, c1.y, c2.x, c2.y, e.x, e.y).dist;
1576
+ if (d > dist2) {
1577
+ dist2 = d;
1578
+ index = i;
1579
+ }
1580
+ }
1581
+ return { index, dist: dist2 };
1582
+ }
1583
+ function dedupe(pts) {
1584
+ return pts.filter((p, i) => i === 0 || !same(p, pts[i - 1]));
1585
+ }
1586
+ var same = (p, q) => p.x === q.x && p.y === q.y;
1587
+ var sub22 = (p, q) => ({ x: p.x - q.x, y: p.y - q.y });
1588
+ var neg = (p) => ({ x: -p.x, y: -p.y });
1589
+ function unit2(p) {
1590
+ const len = Math.hypot(p.x, p.y);
1591
+ return len === 0 ? { x: 0, y: 0 } : { x: p.x / len, y: p.y / len };
1592
+ }
1593
+
1594
+ // src/pxExtent.ts
1595
+ function pxExtent(px, scale3, rotation = 0) {
1596
+ if (!rotation) {
1597
+ return {
1598
+ x: px / Math.abs(scale3.x),
1599
+ y: px / Math.abs(scale3.y)
1600
+ };
1601
+ }
1602
+ const c = Math.cos(rotation), s = Math.sin(rotation);
1603
+ return {
1604
+ x: px * Math.hypot(c / scale3.x, s / scale3.y),
1605
+ y: px * Math.hypot(s / scale3.x, c / scale3.y)
1606
+ };
1607
+ }
1608
+ function scaleDelta(dx, dy, scale3) {
1609
+ return { x: dx * scale3.x, y: dy * scale3.y };
1610
+ }
1611
+ function withinPxBox(dx, dy, px, scale3) {
1612
+ const s = scaleDelta(dx, dy, scale3);
1613
+ return Math.abs(s.x) <= px && Math.abs(s.y) <= px;
1614
+ }
1615
+ function withinPxRadius(dx, dy, px, scale3) {
1616
+ const s = scaleDelta(dx, dy, scale3);
1617
+ return s.x * s.x + s.y * s.y <= px * px;
1618
+ }
1619
+ function standoff(anchor, normal, px, scale3) {
1620
+ const k = Math.sign(scale3.x * scale3.y) || 1;
1621
+ let nx = normal.x * scale3.y * k;
1622
+ let ny = normal.y * scale3.x * k;
1623
+ const l = Math.hypot(nx, ny) || 1;
1624
+ nx /= l;
1625
+ ny /= l;
1626
+ return { x: anchor.x + nx * px / scale3.x, y: anchor.y + ny * px / scale3.y, nx, ny };
1627
+ }
1628
+ function screenAngleOf(rotation, scale3) {
1629
+ return Math.atan2(Math.sin(rotation) * scale3.y, Math.cos(rotation) * scale3.x);
1630
+ }
1631
+
1632
+ // src/paths/anchorOrigins.ts
1633
+ var at = (x) => ({ x, y: x });
1634
+ var keyOf = (p) => `${p.x},${p.y}`;
1635
+ function heldBy(path, set) {
1636
+ const coords = new Float32Array(path.coords.length);
1637
+ for (let i = 0; i < coords.length; i++) coords[i] = i >> 1;
1638
+ const indexed = pathToAnchors({ ...path, coords }).anchors;
1639
+ return indexed.map((sub3, s) => {
1640
+ if (sub3.length === set.anchors[s].length + 1) sub3[0].inHandle = sub3.pop().inHandle;
1641
+ return sub3.map((a) => ({ point: a.x, in: a.inHandle?.x, out: a.outHandle?.x }));
1642
+ });
1643
+ }
1644
+ function writtenFrom(set) {
1645
+ let flat = 0;
1646
+ const coded = set.anchors.map((sub3) => sub3.map((a) => {
1647
+ const k = 3 * flat++;
1648
+ return {
1649
+ ...at(k),
1650
+ ...a.inHandle ? { inHandle: at(k + 1) } : {},
1651
+ ...a.outHandle ? { outHandle: at(k + 2) } : {}
1652
+ };
1653
+ }));
1654
+ return anchorsToPath(coded, set.closed).coords;
1655
+ }
1656
+ function neighbor(sub3, closed, i, step) {
1657
+ for (let n = 1; n < sub3.length; n++) {
1658
+ let j = i + step * n;
1659
+ if (j < 0 || j >= sub3.length) {
1660
+ if (!closed) return -1;
1661
+ j = (j + sub3.length) % sub3.length;
1662
+ }
1663
+ if (sub3[j]) return j;
1664
+ }
1665
+ return -1;
1666
+ }
1667
+ function blended(anchors, held, closed, i) {
1668
+ const p = neighbor(held, closed, i, -1);
1669
+ const n = neighbor(held, closed, i, 1);
1670
+ if (p === -1 && n === -1) return null;
1671
+ const a = held[p === -1 ? n : p].point;
1672
+ const b = held[n === -1 ? p : n].point;
1673
+ if (p === -1 || n === -1 || a === b) return { a, b: a, t: 0 };
1674
+ const dp = Math.hypot(anchors[i].x - anchors[p].x, anchors[i].y - anchors[p].y);
1675
+ const dn = Math.hypot(anchors[i].x - anchors[n].x, anchors[i].y - anchors[n].y);
1676
+ return { a, b, t: dp + dn > 0 ? dp / (dp + dn) : 0 };
1677
+ }
1678
+ function copied(anchor, byPlace) {
1679
+ const was = byPlace.get(keyOf(anchor));
1680
+ if (!was) return void 0;
1681
+ const handle = (h) => h ? byPlace.get(`${keyOf(anchor)}:${keyOf(h)}`)?.point : void 0;
1682
+ return { point: was.point, in: handle(anchor.inHandle), out: handle(anchor.outHandle) };
1683
+ }
1684
+ function editAnchorSetTracked(path, edit) {
1685
+ const set = pathToAnchors(path);
1686
+ const byAnchor = /* @__PURE__ */ new Map();
1687
+ const byPlace = /* @__PURE__ */ new Map();
1688
+ heldBy(path, set).forEach((sub3, s) => sub3.forEach((held2, i) => {
1689
+ const anchor = set.anchors[s][i];
1690
+ byAnchor.set(anchor, held2);
1691
+ if (byPlace.has(keyOf(anchor))) return;
1692
+ if (anchor.inHandle) byPlace.set(`${keyOf(anchor)}:${keyOf(anchor.inHandle)}`, { point: held2.in });
1693
+ if (anchor.outHandle) byPlace.set(`${keyOf(anchor)}:${keyOf(anchor.outHandle)}`, { point: held2.out });
1694
+ byPlace.set(keyOf(anchor), { point: held2.point });
1695
+ }));
1696
+ if (edit(set) === false) return null;
1697
+ const held = [];
1698
+ const own = [];
1699
+ set.anchors.forEach((sub3, s) => {
1700
+ const subHeld = sub3.map((a) => byAnchor.get(a) ?? copied(a, byPlace));
1701
+ sub3.forEach((_, i) => {
1702
+ const h = subHeld[i];
1703
+ held.push(h);
1704
+ own.push(h ? { a: h.point, b: h.point, t: 0 } : blended(sub3, subHeld, set.closed[s], i));
1705
+ });
1706
+ });
1707
+ const origins = Array.from(writtenFrom(set).filter((_, i) => i % 2 === 0), (code) => {
1708
+ const anchor = Math.floor(code / 3);
1709
+ const handle = code % 3 === 1 ? held[anchor]?.in : code % 3 === 2 ? held[anchor]?.out : void 0;
1710
+ return handle === void 0 ? own[anchor] : { a: handle, b: handle, t: 0 };
1711
+ });
1712
+ return { path: anchorsToPath(set.anchors, set.closed, path), origins };
1713
+ }
1714
+ function remapPointValues(values, origins) {
1715
+ return origins.map((o) => {
1716
+ if (!o) return 0;
1717
+ const a = values[o.a] ?? 0;
1718
+ return o.t === 0 ? a : a + ((values[o.b] ?? 0) - a) * o.t;
1719
+ });
1720
+ }
1721
+
1722
+ // src/paths/anchorEdits.ts
1723
+ function anchorCount(set) {
1724
+ let n = 0;
1725
+ for (const sub3 of set.anchors) n += sub3.length;
1726
+ return n;
1727
+ }
1728
+ function locateAnchor(set, flatIndex) {
1729
+ if (flatIndex < 0) return null;
1730
+ let remaining = flatIndex;
1731
+ for (let sub3 = 0; sub3 < set.anchors.length; sub3++) {
1732
+ const len = set.anchors[sub3].length;
1733
+ if (remaining < len) return { sub: sub3, idx: remaining };
1734
+ remaining -= len;
1735
+ }
1736
+ return null;
1737
+ }
1738
+ function flatAnchorIndex(set, sub3, idx) {
1739
+ if (sub3 < 0 || sub3 >= set.anchors.length) return -1;
1740
+ if (idx < 0 || idx >= set.anchors[sub3].length) return -1;
1741
+ let base = 0;
1742
+ for (let s = 0; s < sub3; s++) base += set.anchors[s].length;
1743
+ return base + idx;
1744
+ }
1745
+ function anchorAt(set, flatIndex) {
1746
+ const loc = locateAnchor(set, flatIndex);
1747
+ if (!loc) return null;
1748
+ return set.anchors[loc.sub][loc.idx] ?? null;
1749
+ }
1750
+ function editAnchorSet(path, edit) {
1751
+ return editAnchorSetTracked(path, edit)?.path ?? null;
1752
+ }
1753
+ function translateAnchorBy(set, flatIndex, dx, dy) {
1754
+ const a = anchorAt(set, flatIndex);
1755
+ if (!a) return false;
1756
+ a.x += dx;
1757
+ a.y += dy;
1758
+ if (a.inHandle) {
1759
+ a.inHandle.x += dx;
1760
+ a.inHandle.y += dy;
1761
+ }
1762
+ if (a.outHandle) {
1763
+ a.outHandle.x += dx;
1764
+ a.outHandle.y += dy;
1765
+ }
1766
+ return true;
1767
+ }
1768
+ function moveHandleTo(set, flatIndex, side, toX, toY, breakSmoothness) {
1769
+ const a = anchorAt(set, flatIndex);
1770
+ if (!a) return false;
1771
+ const wasSmooth = isAnchorSmooth(a);
1772
+ const target = { x: toX, y: toY };
1773
+ if (side === "out") a.outHandle = target;
1774
+ else a.inHandle = target;
1775
+ const oppositeSide = side === "out" ? "in" : "out";
1776
+ const opposite = oppositeSide === "in" ? a.inHandle : a.outHandle;
1777
+ if (!breakSmoothness && wasSmooth && opposite) {
1778
+ const mirrored = { x: 2 * a.x - target.x, y: 2 * a.y - target.y };
1779
+ if (oppositeSide === "in") a.inHandle = mirrored;
1780
+ else a.outHandle = mirrored;
1781
+ }
1782
+ return true;
1783
+ }
1784
+ function insertAnchorOnSegment(set, args) {
1785
+ const ends = segmentEnds(set.anchors, set.closed, args.sub, args.segIdx);
1786
+ if (!ends) return -1;
1787
+ const { a, b } = ends;
1788
+ const at2 = args.segIdx + 1;
1789
+ if (isStraightSegment(a, b)) {
1790
+ set.anchors[args.sub].splice(at2, 0, {
1791
+ x: a.x + (b.x - a.x) * args.t,
1792
+ y: a.y + (b.y - a.y) * args.t
1793
+ });
1794
+ return flatAnchorIndex(set, args.sub, at2);
1795
+ }
1796
+ const p0 = a, p1 = a.outHandle ?? a, p2 = b.inHandle ?? b, p3 = b;
1797
+ const { left, right } = splitCubicAtT(p0, p1, p2, p3, args.t);
1798
+ a.outHandle = { x: left[1].x, y: left[1].y };
1799
+ b.inHandle = { x: right[2].x, y: right[2].y };
1800
+ set.anchors[args.sub].splice(at2, 0, {
1801
+ x: left[3].x,
1802
+ y: left[3].y,
1803
+ inHandle: { x: left[2].x, y: left[2].y },
1804
+ outHandle: { x: right[1].x, y: right[1].y }
1805
+ });
1806
+ return flatAnchorIndex(set, args.sub, at2);
1807
+ }
1808
+ function segmentAt(set, x, y, opts) {
1809
+ const scale3 = opts.scale ?? { x: 1, y: 1 };
1810
+ const onScreen = (p) => ({ x: p.x * scale3.x, y: p.y * scale3.y });
1811
+ const screenAnchors = set.anchors.map((sub3) => sub3.map((a) => ({
1812
+ ...onScreen(a),
1813
+ ...a.inHandle ? { inHandle: onScreen(a.inHandle) } : {},
1814
+ ...a.outHandle ? { outHandle: onScreen(a.outHandle) } : {}
1815
+ })));
1816
+ const hit = nearestSegmentT(screenAnchors, x * scale3.x, y * scale3.y, set.closed);
1817
+ if (!hit) return null;
1818
+ const ends = segmentEnds(set.anchors, set.closed, hit.sub, hit.segIdx);
1819
+ const point = pointOnSegment(ends.a, ends.b, hit.t);
1820
+ const tol = opts.tolerancePx;
1821
+ if (!withinPxRadius(point.x - x, point.y - y, tol, scale3)) return null;
1822
+ if (withinPxRadius(point.x - ends.a.x, point.y - ends.a.y, tol, scale3)) return null;
1823
+ if (withinPxRadius(point.x - ends.b.x, point.y - ends.b.y, tol, scale3)) return null;
1824
+ return { sub: hit.sub, segIdx: hit.segIdx, t: hit.t, x: point.x, y: point.y };
1825
+ }
1826
+ function pointOnSegment(a, b, t) {
1827
+ if (isStraightSegment(a, b)) return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
1828
+ return cubicPointAt(a, a.outHandle ?? a, b.inHandle ?? b, b, t);
1829
+ }
1830
+ function deleteAnchorsAt(set, flatIndexes) {
1831
+ const bySub = /* @__PURE__ */ new Map();
1832
+ for (const flat of new Set(flatIndexes)) {
1833
+ const loc = locateAnchor(set, flat);
1834
+ if (!loc) continue;
1835
+ const list = bySub.get(loc.sub);
1836
+ if (list) list.push(loc.idx);
1837
+ else bySub.set(loc.sub, [loc.idx]);
1838
+ }
1839
+ if (bySub.size === 0) return false;
1840
+ let survivors = 0;
1841
+ for (let s = 0; s < set.anchors.length; s++) {
1842
+ const doomed = bySub.get(s)?.length ?? 0;
1843
+ if (set.anchors[s].length - doomed >= 2) survivors++;
1844
+ }
1845
+ if (survivors === 0) return false;
1846
+ for (const [s, indices] of bySub) {
1847
+ const sub3 = set.anchors[s];
1848
+ if (!sub3) continue;
1849
+ indices.sort((a, b) => b - a);
1850
+ for (const i of indices) {
1851
+ const wraps = set.closed[s] && sub3.length > 2;
1852
+ const prev = wraps ? sub3[(i - 1 + sub3.length) % sub3.length] : sub3[i - 1];
1853
+ const next = wraps ? sub3[(i + 1) % sub3.length] : sub3[i + 1];
1854
+ if (prev && next && !(isStraightSegment(prev, sub3[i]) && isStraightSegment(sub3[i], next))) {
1855
+ const { c1, c2 } = fitCubicThroughDeletion(prev, sub3[i], next);
1856
+ prev.outHandle = c1;
1857
+ next.inHandle = c2;
1858
+ }
1859
+ sub3.splice(i, 1);
1860
+ }
1861
+ }
1862
+ for (let s = set.anchors.length - 1; s >= 0; s--) {
1863
+ if (set.anchors[s].length < 2) {
1864
+ set.anchors.splice(s, 1);
1865
+ set.closed.splice(s, 1);
1866
+ }
1867
+ }
1868
+ return true;
1869
+ }
1870
+ function openSubpathAt(set, flatIndex) {
1871
+ const loc = locateAnchor(set, flatIndex);
1872
+ if (!loc) return false;
1873
+ if (!set.closed[loc.sub]) return false;
1874
+ const sub3 = set.anchors[loc.sub];
1875
+ if (!sub3) return false;
1876
+ const { inHandle, ...start } = sub3[loc.idx];
1877
+ const end = { x: start.x, y: start.y, ...inHandle ? { inHandle } : {} };
1878
+ set.anchors[loc.sub] = [start, ...sub3.slice(loc.idx + 1), ...sub3.slice(0, loc.idx), end];
1879
+ set.closed[loc.sub] = false;
1880
+ return true;
1881
+ }
1882
+ function anchorsInRect(set, rect) {
1883
+ const x2 = rect.x + rect.width;
1884
+ const y2 = rect.y + rect.height;
1885
+ const hits = [];
1886
+ let flat = 0;
1887
+ for (const sub3 of set.anchors) {
1888
+ for (const a of sub3) {
1889
+ if (a.x >= rect.x && a.x <= x2 && a.y >= rect.y && a.y <= y2) hits.push(flat);
1890
+ flat++;
1891
+ }
1892
+ }
1893
+ return hits;
1894
+ }
1895
+
1896
+ // src/paths/joinAnchors.ts
1897
+ var COINCIDENT = 1e-6;
1898
+ function averageAnchorsAt(set, flatIndexes, axis = "both") {
1899
+ const picked = [];
1900
+ for (const flat of new Set(flatIndexes)) {
1901
+ const loc = locateAnchor(set, flat);
1902
+ if (loc) picked.push({ flat, a: set.anchors[loc.sub][loc.idx] });
1903
+ }
1904
+ if (picked.length < 2) return false;
1905
+ const mx = picked.reduce((s, p) => s + p.a.x, 0) / picked.length;
1906
+ const my = picked.reduce((s, p) => s + p.a.y, 0) / picked.length;
1907
+ for (const { flat, a } of picked) {
1908
+ translateAnchorBy(set, flat, axis === "y" ? 0 : mx - a.x, axis === "x" ? 0 : my - a.y);
1909
+ }
1910
+ return true;
1911
+ }
1912
+ function subpathEndAt(set, flatIndex) {
1913
+ const loc = locateAnchor(set, flatIndex);
1914
+ if (!loc || set.closed[loc.sub]) return null;
1915
+ const len = set.anchors[loc.sub].length;
1916
+ if (loc.idx === len - 1) return { sub: loc.sub, end: "last" };
1917
+ if (loc.idx === 0) return { sub: loc.sub, end: "first" };
1918
+ return null;
1919
+ }
1920
+ function joinSubpathEnds(set, a, b) {
1921
+ const subA = set.anchors[a.sub];
1922
+ const subB = set.anchors[b.sub];
1923
+ if (!subA || !subB || set.closed[a.sub] || set.closed[b.sub]) return false;
1924
+ if (a.sub === b.sub) {
1925
+ if (a.end === b.end || subA.length < 2) return false;
1926
+ const first = subA[0];
1927
+ const last = subA[subA.length - 1];
1928
+ if (coincide(first, last) && subA.length > 2) {
1929
+ if (last.inHandle) first.inHandle = last.inHandle;
1930
+ subA.pop();
1931
+ }
1932
+ set.closed[a.sub] = true;
1933
+ return true;
1934
+ }
1935
+ if (a.end === "first") reverseInPlace(subA);
1936
+ if (b.end === "last") reverseInPlace(subB);
1937
+ const tail = subA[subA.length - 1];
1938
+ const head = subB[0];
1939
+ let joined;
1940
+ if (coincide(tail, head)) {
1941
+ if (head.outHandle) tail.outHandle = head.outHandle;
1942
+ joined = [...subA, ...subB.slice(1)];
1943
+ } else {
1944
+ joined = [...subA, ...subB];
1945
+ }
1946
+ const keep = Math.min(a.sub, b.sub);
1947
+ const drop = Math.max(a.sub, b.sub);
1948
+ set.anchors[keep] = joined;
1949
+ set.anchors.splice(drop, 1);
1950
+ set.closed.splice(drop, 1);
1951
+ return true;
1952
+ }
1953
+ function joinAnchorsAt(set, flatA, flatB) {
1954
+ if (flatA === flatB) return false;
1955
+ const a = subpathEndAt(set, flatA);
1956
+ const b = subpathEndAt(set, flatB);
1957
+ if (!a || !b) return false;
1958
+ return joinSubpathEnds(set, a, b);
1959
+ }
1960
+ function closestOpenEnds(set, accept = () => true) {
1961
+ const ends = [];
1962
+ set.anchors.forEach((sub3, s) => {
1963
+ if (set.closed[s] || sub3.length < 2) return;
1964
+ ends.push({ end: { sub: s, end: "first" }, at: sub3[0] });
1965
+ ends.push({ end: { sub: s, end: "last" }, at: sub3[sub3.length - 1] });
1966
+ });
1967
+ let best = null;
1968
+ let bestD = Infinity;
1969
+ for (let i = 0; i < ends.length; i++) {
1970
+ for (let j = i + 1; j < ends.length; j++) {
1971
+ const p = ends[i], q = ends[j];
1972
+ if (p.end.sub === q.end.sub || !accept(p.end, q.end)) continue;
1973
+ const d = Math.hypot(p.at.x - q.at.x, p.at.y - q.at.y);
1974
+ if (d < bestD) {
1975
+ bestD = d;
1976
+ best = [p.end, q.end];
1977
+ }
1978
+ }
1979
+ }
1980
+ return best;
1981
+ }
1982
+ var coincide = (p, q) => Math.hypot(p.x - q.x, p.y - q.y) <= COINCIDENT;
1983
+ function reverseInPlace(sub3) {
1984
+ sub3.reverse();
1985
+ for (const a of sub3) {
1986
+ const { inHandle, outHandle } = a;
1987
+ delete a.inHandle;
1988
+ delete a.outHandle;
1989
+ if (outHandle) a.inHandle = outHandle;
1990
+ if (inHandle) a.outHandle = inHandle;
1991
+ }
1992
+ }
1993
+
1994
+ // src/pathSubpaths.ts
1995
+ function flatSubpaths(path, tolerance) {
1996
+ const { commands, coords } = path;
1997
+ const closed = [];
1998
+ const open = [];
1999
+ let sub3 = [];
2000
+ forEachSegment(commands, coords, (cmd, ci, curX, curY, _i, opensSubpath) => {
2001
+ if (opensSubpath) sub3 = [curX, curY];
2002
+ switch (cmd) {
2003
+ case PATH_M:
2004
+ if (sub3.length >= 2) open.push(sub3);
2005
+ sub3 = [coords[ci], coords[ci + 1]];
2006
+ break;
2007
+ case PATH_L:
2008
+ sub3.push(coords[ci], coords[ci + 1]);
2009
+ break;
2010
+ case PATH_C:
2011
+ flattenCubic(
2012
+ curX,
2013
+ curY,
2014
+ coords[ci],
2015
+ coords[ci + 1],
2016
+ coords[ci + 2],
2017
+ coords[ci + 3],
2018
+ coords[ci + 4],
2019
+ coords[ci + 5],
2020
+ tolerance,
2021
+ sub3
2022
+ );
2023
+ break;
2024
+ case PATH_Q:
2025
+ flattenQuadratic(
2026
+ curX,
2027
+ curY,
2028
+ coords[ci],
2029
+ coords[ci + 1],
2030
+ coords[ci + 2],
2031
+ coords[ci + 3],
2032
+ tolerance,
2033
+ sub3
2034
+ );
2035
+ break;
2036
+ case PATH_Z:
2037
+ if (sub3.length >= 6) closed.push(sub3);
2038
+ else if (sub3.length >= 2) open.push(sub3);
2039
+ sub3 = [];
2040
+ break;
2041
+ default:
2042
+ throw new Error(`pathHitTest: unknown command ${cmd}`);
2043
+ }
2044
+ });
2045
+ if (sub3.length >= 2) open.push(sub3);
2046
+ return { closed, open };
2047
+ }
2048
+ function regionSubpaths(path, opts) {
2049
+ const flat = flatSubpaths(path, opts.tolerance ?? DEFAULT_FLATTEN_TOLERANCE);
2050
+ if (!fillsOpenSubpaths(path)) return flat;
2051
+ return {
2052
+ closed: [...flat.closed, ...flat.open.filter((sub3) => sub3.length >= 6)],
2053
+ open: flat.open.filter((sub3) => sub3.length < 6)
2054
+ };
2055
+ }
2056
+ function closedSubpaths(path, opts) {
2057
+ return regionSubpaths(path, opts).closed;
2058
+ }
2059
+
2060
+ // src/screenBand.ts
2061
+ function stretchOf(m) {
2062
+ const e = (m.a + m.d) / 2;
2063
+ const f = (m.a - m.d) / 2;
2064
+ const g = (m.b + m.c) / 2;
2065
+ const h = (m.b - m.c) / 2;
2066
+ const q = Math.hypot(e, h);
2067
+ const r = Math.hypot(f, g);
2068
+ const phi = (Math.atan2(h, e) + Math.atan2(g, f)) / 2;
2069
+ return { s1: q + r, s2: Math.abs(q - r), phi };
2070
+ }
2071
+ function isSimilarity(s) {
2072
+ return s.s1 - s.s2 <= 1e-9 * s.s1;
2073
+ }
2074
+ function capsuleWithinPx(px0, py0, ax, ay, bx, by, r, slop, m, st) {
2075
+ const sax = m.a * ax + m.c * ay, say = m.b * ax + m.d * ay;
2076
+ const sbx = m.a * bx + m.c * by, sby = m.b * bx + m.d * by;
2077
+ const slop2 = slop * slop;
2078
+ const dCenter2 = pointSegmentDist2(px0, py0, sax, say, sbx, sby);
2079
+ if (dCenter2 <= slop2) return true;
2080
+ if (r <= 0) return false;
2081
+ const reach = slop + r * st.s1;
2082
+ if (dCenter2 > reach * reach) return false;
2083
+ const wx = bx - ax, wy = by - ay;
2084
+ const wl = Math.hypot(wx, wy);
2085
+ if (wl > 0) {
2086
+ const nx = -wy / wl * r, ny = wx / wl * r;
2087
+ const ox = m.a * nx + m.c * ny, oy = m.b * nx + m.d * ny;
2088
+ if (quadWithinPx(px0, py0, [
2089
+ sax + ox,
2090
+ say + oy,
2091
+ sbx + ox,
2092
+ sby + oy,
2093
+ sbx - ox,
2094
+ sby - oy,
2095
+ sax - ox,
2096
+ say - oy
2097
+ ], slop2)) return true;
2098
+ }
2099
+ return ellipseWithinPx(px0 - sax, py0 - say, r, slop, st) || ellipseWithinPx(px0 - sbx, py0 - sby, r, slop, st);
2100
+ }
2101
+ function quadWithinPx(x, y, q, slop2) {
2102
+ let pos = 0, neg2 = 0;
2103
+ for (let i = 0; i < 8; i += 2) {
2104
+ const j = (i + 2) % 8;
2105
+ const cr = (q[j] - q[i]) * (y - q[i + 1]) - (q[j + 1] - q[i + 1]) * (x - q[i]);
2106
+ if (cr > 0) pos++;
2107
+ else if (cr < 0) neg2++;
2108
+ }
2109
+ if (pos === 0 || neg2 === 0) return true;
2110
+ for (let i = 0; i < 8; i += 2) {
2111
+ const j = (i + 2) % 8;
2112
+ if (pointSegmentDist2(x, y, q[i], q[i + 1], q[j], q[j + 1]) <= slop2) return true;
2113
+ }
2114
+ return false;
1313
2115
  }
1314
2116
  function ellipseWithinPx(dx, dy, r, slop, st) {
1315
2117
  const cos = Math.cos(st.phi), sin = Math.sin(st.phi);
@@ -1586,218 +2388,61 @@ function circlePath(cx, cy, r, segments = 32) {
1586
2388
  const coords = new Float32Array(segments * 2);
1587
2389
  cmds[0] = PATH_M;
1588
2390
  coords[0] = cx + r;
1589
- coords[1] = cy;
1590
- for (let i = 1; i < segments; i++) {
1591
- cmds[i] = PATH_L;
1592
- const theta = i / segments * Math.PI * 2;
1593
- coords[i * 2] = cx + r * Math.cos(theta);
1594
- coords[i * 2 + 1] = cy + r * Math.sin(theta);
1595
- }
1596
- cmds[segments] = PATH_Z;
1597
- return { kind: "polygon", commands: cmds, coords, fillRule: "nonzero" };
1598
- }
1599
- function squarePath(cx, cy, size) {
1600
- const half = size / 2;
1601
- const x0 = cx - half, y0 = cy - half;
1602
- const x1 = cx + half, y1 = cy + half;
1603
- return {
1604
- kind: "polygon",
1605
- commands: new Uint8Array([PATH_M, PATH_L, PATH_L, PATH_L, PATH_Z]),
1606
- coords: new Float32Array([x0, y0, x1, y0, x1, y1, x0, y1]),
1607
- fillRule: "nonzero"
1608
- };
1609
- }
1610
- function rectMarkerPath(x, y, width, height) {
1611
- return {
1612
- kind: "polygon",
1613
- commands: new Uint8Array([PATH_M, PATH_L, PATH_L, PATH_L, PATH_Z]),
1614
- coords: Float32Array.from(rectToContour(x, y, width, height)),
1615
- fillRule: "nonzero"
1616
- };
1617
- }
1618
- function roundRectPath(x, y, width, height, r, cornerSamples = 8) {
1619
- const radius = Math.max(0, Math.min(r, Math.min(width, height) / 2));
1620
- if (radius === 0) return rectMarkerPath(x, y, width, height);
1621
- const HALF_PI = Math.PI / 2;
1622
- const corners = [
1623
- [x + radius, y + radius, Math.PI],
1624
- [x + width - radius, y + radius, -HALF_PI],
1625
- [x + width - radius, y + height - radius, 0],
1626
- [x + radius, y + height - radius, HALF_PI]
1627
- ];
1628
- const perCorner = cornerSamples + 1;
1629
- const count = corners.length * perCorner;
1630
- const cmds = new Uint8Array(count + 1);
1631
- const coords = new Float32Array(count * 2);
1632
- let i = 0;
1633
- for (const [cx, cy, start] of corners) {
1634
- for (let s = 0; s <= cornerSamples; s++) {
1635
- const theta = start + s / cornerSamples * HALF_PI;
1636
- cmds[i] = i === 0 ? PATH_M : PATH_L;
1637
- coords[i * 2] = cx + radius * Math.cos(theta);
1638
- coords[i * 2 + 1] = cy + radius * Math.sin(theta);
1639
- i++;
1640
- }
1641
- }
1642
- cmds[count] = PATH_Z;
1643
- return { kind: "polygon", commands: cmds, coords, fillRule: "nonzero" };
1644
- }
1645
-
1646
- // src/paths/anchors.ts
1647
- function pathToAnchors(path) {
1648
- const { commands, coords } = path;
1649
- const anchors = [];
1650
- const closed = [];
1651
- let current = null;
1652
- forEachSegment(commands, coords, (cmd, ci) => {
1653
- switch (cmd) {
1654
- case PATH_M: {
1655
- if (current) {
1656
- anchors.push(current);
1657
- closed.push(false);
1658
- }
1659
- current = [{ x: coords[ci], y: coords[ci + 1] }];
1660
- break;
1661
- }
1662
- case PATH_L: {
1663
- if (!current) throw new Error("pathToAnchors: L without prior M");
1664
- current.push({ x: coords[ci], y: coords[ci + 1] });
1665
- break;
1666
- }
1667
- case PATH_C: {
1668
- if (!current) throw new Error("pathToAnchors: C without prior M");
1669
- const prev = current[current.length - 1];
1670
- prev.outHandle = { x: coords[ci], y: coords[ci + 1] };
1671
- current.push({
1672
- x: coords[ci + 4],
1673
- y: coords[ci + 5],
1674
- inHandle: { x: coords[ci + 2], y: coords[ci + 3] }
1675
- });
1676
- break;
1677
- }
1678
- case PATH_Q: {
1679
- if (!current) throw new Error("pathToAnchors: Q without prior M");
1680
- const handle = { x: coords[ci], y: coords[ci + 1] };
1681
- const prev = current[current.length - 1];
1682
- prev.outHandle = handle;
1683
- current.push({ x: coords[ci + 2], y: coords[ci + 3], inHandle: { ...handle } });
1684
- break;
1685
- }
1686
- case PATH_Z: {
1687
- if (current) {
1688
- foldClosingCurve(current);
1689
- anchors.push(current);
1690
- closed.push(true);
1691
- current = null;
1692
- }
1693
- break;
1694
- }
1695
- default:
1696
- throw new Error(`pathToAnchors: unknown command ${cmd}`);
1697
- }
1698
- });
1699
- if (current) {
1700
- anchors.push(current);
1701
- closed.push(false);
1702
- }
1703
- return { anchors, closed };
1704
- }
1705
- function foldClosingCurve(sub3) {
1706
- if (sub3.length < 2) return;
1707
- const first = sub3[0];
1708
- const last = sub3[sub3.length - 1];
1709
- if (!last.inHandle || last.x !== first.x || last.y !== first.y) return;
1710
- sub3.pop();
1711
- first.inHandle = last.inHandle;
1712
- }
1713
- function anchorsToPath(anchors, closed, like) {
1714
- const b = new PathBuilder();
1715
- for (let s = 0; s < anchors.length; s++) {
1716
- const sub3 = anchors[s];
1717
- if (sub3.length === 0) continue;
1718
- b.moveTo(sub3[0].x, sub3[0].y);
1719
- for (let i = 1; i < sub3.length; i++) {
1720
- const prev = sub3[i - 1];
1721
- const cur = sub3[i];
1722
- const hasHandle = prev.outHandle != null || cur.inHandle != null;
1723
- if (!hasHandle) {
1724
- b.lineTo(cur.x, cur.y);
1725
- } else {
1726
- const c1 = prev.outHandle ?? { x: prev.x, y: prev.y };
1727
- const c2 = cur.inHandle ?? { x: cur.x, y: cur.y };
1728
- b.curveTo(c1.x, c1.y, c2.x, c2.y, cur.x, cur.y);
1729
- }
1730
- }
1731
- if (closed[s]) {
1732
- const last = sub3[sub3.length - 1];
1733
- const first = sub3[0];
1734
- const hasHandle = last.outHandle != null || first.inHandle != null;
1735
- if (hasHandle) {
1736
- const c1 = last.outHandle ?? { x: last.x, y: last.y };
1737
- const c2 = first.inHandle ?? { x: first.x, y: first.y };
1738
- b.curveTo(c1.x, c1.y, c2.x, c2.y, first.x, first.y);
1739
- }
1740
- b.close();
1741
- }
1742
- }
1743
- const built = b.build();
1744
- return like ? { ...like, commands: built.commands, coords: built.coords } : built;
1745
- }
1746
- var SMOOTH_THRESHOLD = 1e-3;
1747
- function isAnchorSmooth(a) {
1748
- if (!a.inHandle || !a.outHandle) return false;
1749
- const inDX = a.inHandle.x - a.x;
1750
- const inDY = a.inHandle.y - a.y;
1751
- const outDX = a.outHandle.x - a.x;
1752
- const outDY = a.outHandle.y - a.y;
1753
- const inLen = Math.hypot(inDX, inDY);
1754
- const outLen = Math.hypot(outDX, outDY);
1755
- if (inLen === 0 || outLen === 0) return false;
1756
- const cross2 = (inDX * outDY - inDY * outDX) / (inLen * outLen);
1757
- return Math.abs(cross2) < SMOOTH_THRESHOLD;
1758
- }
1759
- function countPathAnchors(path) {
1760
- if (path.kind === "rect") return 4;
1761
- const cmds = path.commands;
1762
- let n = 0;
1763
- for (let i = 0; i < cmds.length; i++) {
1764
- const c = cmds[i];
1765
- if (c === PATH_M || c === PATH_L || c === PATH_C || c === PATH_Q) n++;
2391
+ coords[1] = cy;
2392
+ for (let i = 1; i < segments; i++) {
2393
+ cmds[i] = PATH_L;
2394
+ const theta = i / segments * Math.PI * 2;
2395
+ coords[i * 2] = cx + r * Math.cos(theta);
2396
+ coords[i * 2 + 1] = cy + r * Math.sin(theta);
1766
2397
  }
1767
- return n;
1768
- }
1769
- function segmentEnds(anchors, closed, sub3, segIdx) {
1770
- const s = anchors[sub3];
1771
- if (!s || segIdx < 0) return null;
1772
- if (segIdx + 1 < s.length) return { a: s[segIdx], b: s[segIdx + 1] };
1773
- if (closed?.[sub3] && s.length > 1 && segIdx === s.length - 1) return { a: s[segIdx], b: s[0] };
1774
- return null;
2398
+ cmds[segments] = PATH_Z;
2399
+ return { kind: "polygon", commands: cmds, coords, fillRule: "nonzero" };
1775
2400
  }
1776
- function isStraightSegment(a, b) {
1777
- return a.outHandle == null && b.inHandle == null;
2401
+ function squarePath(cx, cy, size) {
2402
+ const half = size / 2;
2403
+ const x0 = cx - half, y0 = cy - half;
2404
+ const x1 = cx + half, y1 = cy + half;
2405
+ return {
2406
+ kind: "polygon",
2407
+ commands: new Uint8Array([PATH_M, PATH_L, PATH_L, PATH_L, PATH_Z]),
2408
+ coords: new Float32Array([x0, y0, x1, y0, x1, y1, x0, y1]),
2409
+ fillRule: "nonzero"
2410
+ };
1778
2411
  }
1779
- function nearestOnSegment(a, b, x, y) {
1780
- const p1 = a.outHandle ?? a;
1781
- const p2 = b.inHandle ?? b;
1782
- const hit = isStraightSegment(a, b) ? nearestOnLine(x, y, a.x, a.y, b.x, b.y) : nearestOnCubic(x, y, a.x, a.y, p1.x, p1.y, p2.x, p2.y, b.x, b.y);
1783
- return { t: hit.t, d2: hit.dist * hit.dist };
2412
+ function rectMarkerPath(x, y, width, height) {
2413
+ return {
2414
+ kind: "polygon",
2415
+ commands: new Uint8Array([PATH_M, PATH_L, PATH_L, PATH_L, PATH_Z]),
2416
+ coords: Float32Array.from(rectToContour(x, y, width, height)),
2417
+ fillRule: "nonzero"
2418
+ };
1784
2419
  }
1785
- function nearestSegmentT(anchors, wx, wy, closed) {
1786
- let bestD2 = Infinity;
1787
- let best = null;
1788
- for (let s = 0; s < anchors.length; s++) {
1789
- const segCount = anchors[s].length - (closed?.[s] ? 0 : 1);
1790
- for (let i = 0; i < segCount; i++) {
1791
- const ends = segmentEnds(anchors, closed, s, i);
1792
- if (!ends) continue;
1793
- const hit = nearestOnSegment(ends.a, ends.b, wx, wy);
1794
- if (hit.d2 < bestD2) {
1795
- bestD2 = hit.d2;
1796
- best = { sub: s, segIdx: i, t: hit.t };
1797
- }
2420
+ function roundRectPath(x, y, width, height, r, cornerSamples = 8) {
2421
+ const radius = Math.max(0, Math.min(r, Math.min(width, height) / 2));
2422
+ if (radius === 0) return rectMarkerPath(x, y, width, height);
2423
+ const HALF_PI = Math.PI / 2;
2424
+ const corners = [
2425
+ [x + radius, y + radius, Math.PI],
2426
+ [x + width - radius, y + radius, -HALF_PI],
2427
+ [x + width - radius, y + height - radius, 0],
2428
+ [x + radius, y + height - radius, HALF_PI]
2429
+ ];
2430
+ const perCorner = cornerSamples + 1;
2431
+ const count = corners.length * perCorner;
2432
+ const cmds = new Uint8Array(count + 1);
2433
+ const coords = new Float32Array(count * 2);
2434
+ let i = 0;
2435
+ for (const [cx, cy, start] of corners) {
2436
+ for (let s = 0; s <= cornerSamples; s++) {
2437
+ const theta = start + s / cornerSamples * HALF_PI;
2438
+ cmds[i] = i === 0 ? PATH_M : PATH_L;
2439
+ coords[i * 2] = cx + radius * Math.cos(theta);
2440
+ coords[i * 2 + 1] = cy + radius * Math.sin(theta);
2441
+ i++;
1798
2442
  }
1799
2443
  }
1800
- return best;
2444
+ cmds[count] = PATH_Z;
2445
+ return { kind: "polygon", commands: cmds, coords, fillRule: "nonzero" };
1801
2446
  }
1802
2447
 
1803
2448
  // src/glMat3.ts
@@ -1920,14 +2565,14 @@ function alignAlong(start, extent, size, align) {
1920
2565
  if (align === "end") return start + extent - size;
1921
2566
  return start + (extent - size) / 2;
1922
2567
  }
1923
- function positionOn(anchor, overlay, placement, offset, crossOffset) {
2568
+ function positionOn(anchor, overlay, placement, offset2, crossOffset) {
1924
2569
  const { width, height } = overlay;
1925
2570
  const { side, align } = placement;
1926
2571
  if (side === "top" || side === "bottom") {
1927
- const y = side === "bottom" ? anchor.y + anchor.height + offset : anchor.y - offset - height;
2572
+ const y = side === "bottom" ? anchor.y + anchor.height + offset2 : anchor.y - offset2 - height;
1928
2573
  return { x: alignAlong(anchor.x, anchor.width, width, align) + crossOffset, y, width, height };
1929
2574
  }
1930
- const x = side === "right" ? anchor.x + anchor.width + offset : anchor.x - offset - width;
2575
+ const x = side === "right" ? anchor.x + anchor.width + offset2 : anchor.x - offset2 - width;
1931
2576
  return { x, y: alignAlong(anchor.y, anchor.height, height, align) + crossOffset, width, height };
1932
2577
  }
1933
2578
  function clampAxis(start, size, low, extent, padding) {
@@ -1964,13 +2609,13 @@ function fitsAlongSide(rect, boundary, side, padding) {
1964
2609
  return start >= low && end <= high;
1965
2610
  }
1966
2611
  function placeRect(options) {
1967
- const { anchor, overlay, boundary, placement, offset = 0, crossOffset = 0 } = options;
2612
+ const { anchor, overlay, boundary, placement, offset: offset2 = 0, crossOffset = 0 } = options;
1968
2613
  const { padding = 0, flip = true } = options;
1969
2614
  let resolved = placement;
1970
- let rect = positionOn(anchor, overlay, resolved, offset, crossOffset);
2615
+ let rect = positionOn(anchor, overlay, resolved, offset2, crossOffset);
1971
2616
  if (flip && !fitsAlongSide(rect, boundary, resolved.side, padding)) {
1972
2617
  const opposite = { side: OPPOSITE[resolved.side], align: resolved.align };
1973
- const flipped = positionOn(anchor, overlay, opposite, offset, crossOffset);
2618
+ const flipped = positionOn(anchor, overlay, opposite, offset2, crossOffset);
1974
2619
  if (fitsAlongSide(flipped, boundary, opposite.side, padding)) {
1975
2620
  resolved = opposite;
1976
2621
  rect = flipped;
@@ -2166,44 +2811,6 @@ function meanScale(s) {
2166
2811
  return Math.sqrt(Math.abs(s.x * s.y));
2167
2812
  }
2168
2813
 
2169
- // src/pxExtent.ts
2170
- function pxExtent(px, scale2, rotation = 0) {
2171
- if (!rotation) {
2172
- return {
2173
- x: px / Math.abs(scale2.x),
2174
- y: px / Math.abs(scale2.y)
2175
- };
2176
- }
2177
- const c = Math.cos(rotation), s = Math.sin(rotation);
2178
- return {
2179
- x: px * Math.hypot(c / scale2.x, s / scale2.y),
2180
- y: px * Math.hypot(s / scale2.x, c / scale2.y)
2181
- };
2182
- }
2183
- function scaleDelta(dx, dy, scale2) {
2184
- return { x: dx * scale2.x, y: dy * scale2.y };
2185
- }
2186
- function withinPxBox(dx, dy, px, scale2) {
2187
- const s = scaleDelta(dx, dy, scale2);
2188
- return Math.abs(s.x) <= px && Math.abs(s.y) <= px;
2189
- }
2190
- function withinPxRadius(dx, dy, px, scale2) {
2191
- const s = scaleDelta(dx, dy, scale2);
2192
- return s.x * s.x + s.y * s.y <= px * px;
2193
- }
2194
- function standoff(anchor, normal, px, scale2) {
2195
- const k = Math.sign(scale2.x * scale2.y) || 1;
2196
- let nx = normal.x * scale2.y * k;
2197
- let ny = normal.y * scale2.x * k;
2198
- const l = Math.hypot(nx, ny) || 1;
2199
- nx /= l;
2200
- ny /= l;
2201
- return { x: anchor.x + nx * px / scale2.x, y: anchor.y + ny * px / scale2.y, nx, ny };
2202
- }
2203
- function screenAngleOf(rotation, scale2) {
2204
- return Math.atan2(Math.sin(rotation) * scale2.y, Math.cos(rotation) * scale2.x);
2205
- }
2206
-
2207
2814
  // src/poseRotation.ts
2208
2815
  function poseRotationOf(pose) {
2209
2816
  const p = pose;
@@ -2260,12 +2867,12 @@ function poseLinearOf(pose) {
2260
2867
  function poseFrameOf(pose) {
2261
2868
  const p = pose;
2262
2869
  if (!p || p.x == null || p.y == null || p.width == null || p.height == null) return null;
2263
- const linear2 = poseLinearOf(p);
2264
- if (!p.rotation && !linear2) return null;
2870
+ const linear3 = poseLinearOf(p);
2871
+ if (!p.rotation && !linear3) return null;
2265
2872
  const cx = p.x + p.width / 2;
2266
2873
  const cy = p.y + p.height / 2;
2267
- if (!linear2) return rotateAboutPoint(cx, cy, p.rotation);
2268
- const [a, b, c, d] = linear2;
2874
+ if (!linear3) return rotateAboutPoint(cx, cy, p.rotation);
2875
+ const [a, b, c, d] = linear3;
2269
2876
  const about = [a, b, c, d, cx - (a * cx + c * cy), cy - (b * cx + d * cy)];
2270
2877
  return p.rotation ? multiply(rotateAboutPoint(cx, cy, p.rotation), about) : about;
2271
2878
  }
@@ -2286,185 +2893,221 @@ function pathThroughPoseFrame(path, pose) {
2286
2893
  return r ? rotatePathAround(path, r.cx, r.cy, r.rotation) : path;
2287
2894
  }
2288
2895
 
2289
- // src/paths/anchorEdits.ts
2290
- function anchorCount(set) {
2291
- let n = 0;
2292
- for (const sub3 of set.anchors) n += sub3.length;
2293
- return n;
2294
- }
2295
- function locateAnchor(set, flatIndex) {
2296
- if (flatIndex < 0) return null;
2297
- let remaining = flatIndex;
2298
- for (let sub3 = 0; sub3 < set.anchors.length; sub3++) {
2299
- const len = set.anchors[sub3].length;
2300
- if (remaining < len) return { sub: sub3, idx: remaining };
2301
- remaining -= len;
2302
- }
2303
- return null;
2304
- }
2305
- function flatAnchorIndex(set, sub3, idx) {
2306
- if (sub3 < 0 || sub3 >= set.anchors.length) return -1;
2307
- if (idx < 0 || idx >= set.anchors[sub3].length) return -1;
2308
- let base = 0;
2309
- for (let s = 0; s < sub3; s++) base += set.anchors[s].length;
2310
- return base + idx;
2896
+ // src/paths/anchorSymmetry.ts
2897
+ function near(a, b, tol) {
2898
+ if (!a || !b) return a === b;
2899
+ return Math.abs(a.x - b.x) <= tol && Math.abs(a.y - b.y) <= tol;
2900
+ }
2901
+ function image(m, p) {
2902
+ if (!p) return void 0;
2903
+ const [x, y] = applyToPoint(m, p.x, p.y);
2904
+ return { x, y };
2311
2905
  }
2312
- function anchorAt(set, flatIndex) {
2313
- const loc = locateAnchor(set, flatIndex);
2314
- if (!loc) return null;
2315
- return set.anchors[loc.sub][loc.idx] ?? null;
2906
+ function linear2(m, d) {
2907
+ return { x: m[0] * d.x + m[2] * d.y, y: m[1] * d.x + m[3] * d.y };
2316
2908
  }
2317
- function editAnchorSet(path, edit) {
2318
- const set = pathToAnchors(path);
2319
- const changed = edit(set);
2320
- if (changed === false) return null;
2321
- return anchorsToPath(set.anchors, set.closed, path);
2909
+ function landing(m, a, b, tol) {
2910
+ if (!near(image(m, a), b, tol)) return null;
2911
+ const i = image(m, a.inHandle);
2912
+ const o = image(m, a.outHandle);
2913
+ if (near(i, b.inHandle, tol) && near(o, b.outHandle, tol)) return false;
2914
+ if (near(i, b.outHandle, tol) && near(o, b.inHandle, tol)) return true;
2915
+ return null;
2322
2916
  }
2323
- function translateAnchorBy(set, flatIndex, dx, dy) {
2324
- const a = anchorAt(set, flatIndex);
2325
- if (!a) return false;
2326
- a.x += dx;
2327
- a.y += dy;
2328
- if (a.inHandle) {
2329
- a.inHandle.x += dx;
2330
- a.inHandle.y += dy;
2331
- }
2332
- if (a.outHandle) {
2333
- a.outHandle.x += dx;
2334
- a.outHandle.y += dy;
2917
+ function anchorSymmetry(set, m, tolerance = 1e-6) {
2918
+ const n = anchorCount(set);
2919
+ const partners = [];
2920
+ const flips = [];
2921
+ const taken = /* @__PURE__ */ new Set();
2922
+ for (let i = 0; i < n; i++) {
2923
+ const a = anchorAt(set, i);
2924
+ let found = -1;
2925
+ let flip = false;
2926
+ for (let j = 0; j < n && found < 0; j++) {
2927
+ if (taken.has(j)) continue;
2928
+ const how = landing(m, a, anchorAt(set, j), tolerance);
2929
+ if (how === null) continue;
2930
+ found = j;
2931
+ flip = how;
2932
+ }
2933
+ if (found < 0) return null;
2934
+ taken.add(found);
2935
+ partners.push(found);
2936
+ flips.push(flip);
2937
+ }
2938
+ return { m, partners, flips };
2939
+ }
2940
+ function anchorSymmetries(set, group, tolerance = 1e-6) {
2941
+ return group.map((m) => anchorSymmetry(set, m, tolerance)).filter((s) => s !== null);
2942
+ }
2943
+ function averaged(d, syms, fixes, apply2) {
2944
+ let x = d.x;
2945
+ let y = d.y;
2946
+ let k = 1;
2947
+ for (const s of syms) {
2948
+ if (!fixes(s)) continue;
2949
+ const p = apply2(s.m, d);
2950
+ x += p.x;
2951
+ y += p.y;
2952
+ k++;
2953
+ }
2954
+ return { x: x / k, y: y / k };
2955
+ }
2956
+ function translateAnchorsSymmetric(set, flats, dx, dy, syms) {
2957
+ const dragged = new Set(flats);
2958
+ const moves = /* @__PURE__ */ new Map();
2959
+ for (const flat of flats) {
2960
+ const d = averaged({ x: dx, y: dy }, syms, (s) => s.partners[flat] === flat, linear2);
2961
+ moves.set(flat, d);
2962
+ for (const s of syms) {
2963
+ const j = s.partners[flat];
2964
+ if (j === void 0 || dragged.has(j) || moves.has(j)) continue;
2965
+ moves.set(j, linear2(s.m, d));
2966
+ }
2335
2967
  }
2336
- return true;
2337
- }
2338
- function moveHandleTo(set, flatIndex, side, toX, toY, breakSmoothness) {
2339
- const a = anchorAt(set, flatIndex);
2340
- if (!a) return false;
2341
- const wasSmooth = isAnchorSmooth(a);
2342
- const target = { x: toX, y: toY };
2343
- if (side === "out") a.outHandle = target;
2344
- else a.inHandle = target;
2345
- const oppositeSide = side === "out" ? "in" : "out";
2346
- const opposite = oppositeSide === "in" ? a.inHandle : a.outHandle;
2347
- if (!breakSmoothness && wasSmooth && opposite) {
2348
- const mirrored = { x: 2 * a.x - target.x, y: 2 * a.y - target.y };
2349
- if (oppositeSide === "in") a.inHandle = mirrored;
2350
- else a.outHandle = mirrored;
2968
+ let changed = false;
2969
+ for (const [flat, d] of moves) changed = translateAnchorBy(set, flat, d.x, d.y) || changed;
2970
+ return changed;
2971
+ }
2972
+ var other = (side) => side === "in" ? "out" : "in";
2973
+ function moveHandleSymmetric(set, flat, side, toX, toY, breakSmoothness, syms) {
2974
+ const a = anchorAt(set, flat);
2975
+ if (!a || !(side === "in" ? a.inHandle : a.outHandle)) return false;
2976
+ const sideUnder = (s, sd) => s.flips[flat] ? other(sd) : sd;
2977
+ const fixesHandle = (s) => s.partners[flat] === flat && sideUnder(s, side) === side;
2978
+ const to = averaged({ x: toX, y: toY }, syms, fixesHandle, image);
2979
+ const targets = /* @__PURE__ */ new Map();
2980
+ const put = (f, sd, p) => {
2981
+ const key = `${f}:${sd}`;
2982
+ if (!targets.has(key)) targets.set(key, { flat: f, side: sd, to: p });
2983
+ };
2984
+ put(flat, side, to);
2985
+ for (const s of syms) put(s.partners[flat], sideUnder(s, side), image(s.m, to));
2986
+ const opposite = side === "in" ? a.outHandle : a.inHandle;
2987
+ const drivenBySymmetry = targets.has(`${flat}:${other(side)}`);
2988
+ if (!drivenBySymmetry && !breakSmoothness && opposite && isAnchorSmooth(a)) {
2989
+ const opp = { x: 2 * a.x - to.x, y: 2 * a.y - to.y };
2990
+ put(flat, other(side), opp);
2991
+ for (const s of syms) put(s.partners[flat], sideUnder(s, other(side)), image(s.m, opp));
2992
+ }
2993
+ for (const t of targets.values()) {
2994
+ const b = anchorAt(set, t.flat);
2995
+ if (!b) continue;
2996
+ if (t.side === "in") b.inHandle = t.to;
2997
+ else b.outHandle = t.to;
2351
2998
  }
2352
2999
  return true;
2353
3000
  }
2354
- function insertAnchorOnSegment(set, args) {
2355
- const ends = segmentEnds(set.anchors, set.closed, args.sub, args.segIdx);
2356
- if (!ends) return -1;
3001
+
3002
+ // src/paths/bendSegment.ts
3003
+ var T_MARGIN = 0.05;
3004
+ function bendSegment(set, grip, dx, dy) {
3005
+ const ends = segmentEnds(set.anchors, set.closed, grip.sub, grip.segIdx);
3006
+ if (!ends) return false;
2357
3007
  const { a, b } = ends;
2358
- const at = args.segIdx + 1;
2359
- if (isStraightSegment(a, b)) {
2360
- set.anchors[args.sub].splice(at, 0, {
2361
- x: a.x + (b.x - a.x) * args.t,
2362
- y: a.y + (b.y - a.y) * args.t
2363
- });
2364
- return flatAnchorIndex(set, args.sub, at);
2365
- }
2366
- const p0 = a, p1 = a.outHandle ?? a, p2 = b.inHandle ?? b, p3 = b;
2367
- const { left, right } = splitCubicAtT(p0, p1, p2, p3, args.t);
2368
- a.outHandle = { x: left[1].x, y: left[1].y };
2369
- b.inHandle = { x: right[2].x, y: right[2].y };
2370
- set.anchors[args.sub].splice(at, 0, {
2371
- x: left[3].x,
2372
- y: left[3].y,
2373
- inHandle: { x: left[2].x, y: left[2].y },
2374
- outHandle: { x: right[1].x, y: right[1].y }
2375
- });
2376
- return flatAnchorIndex(set, args.sub, at);
3008
+ const aSmooth = isAnchorSmooth(a);
3009
+ const bSmooth = isAnchorSmooth(b);
3010
+ const straight = isStraightSegment(a, b);
3011
+ const p1 = a.outHandle ?? (straight ? lerp(a, b, 1 / 3) : { x: a.x, y: a.y });
3012
+ const p2 = b.inHandle ?? (straight ? lerp(a, b, 2 / 3) : { x: b.x, y: b.y });
3013
+ const t = Math.min(1 - T_MARGIN, Math.max(T_MARGIN, grip.t));
3014
+ const u = 1 - t;
3015
+ const k = 1 / (3 * t * u * (u * u + t * t));
3016
+ a.outHandle = { x: p1.x + k * u * dx, y: p1.y + k * u * dy };
3017
+ b.inHandle = { x: p2.x + k * t * dx, y: p2.y + k * t * dy };
3018
+ if (aSmooth) a.inHandle = alignedOpposite(a, a.outHandle, a.inHandle);
3019
+ if (bSmooth) b.outHandle = alignedOpposite(b, b.inHandle, b.outHandle);
3020
+ return true;
2377
3021
  }
2378
- function segmentAt(set, x, y, opts) {
2379
- const scale2 = opts.scale ?? { x: 1, y: 1 };
2380
- const onScreen = (p) => ({ x: p.x * scale2.x, y: p.y * scale2.y });
2381
- const screenAnchors = set.anchors.map((sub3) => sub3.map((a) => ({
2382
- ...onScreen(a),
2383
- ...a.inHandle ? { inHandle: onScreen(a.inHandle) } : {},
2384
- ...a.outHandle ? { outHandle: onScreen(a.outHandle) } : {}
2385
- })));
2386
- const hit = nearestSegmentT(screenAnchors, x * scale2.x, y * scale2.y, set.closed);
2387
- if (!hit) return null;
2388
- const ends = segmentEnds(set.anchors, set.closed, hit.sub, hit.segIdx);
2389
- const point = pointOnSegment(ends.a, ends.b, hit.t);
2390
- const tol = opts.tolerancePx;
2391
- if (!withinPxRadius(point.x - x, point.y - y, tol, scale2)) return null;
2392
- if (withinPxRadius(point.x - ends.a.x, point.y - ends.a.y, tol, scale2)) return null;
2393
- if (withinPxRadius(point.x - ends.b.x, point.y - ends.b.y, tol, scale2)) return null;
2394
- return { sub: hit.sub, segIdx: hit.segIdx, t: hit.t, x: point.x, y: point.y };
3022
+ function lerp(a, b, f) {
3023
+ return { x: a.x + (b.x - a.x) * f, y: a.y + (b.y - a.y) * f };
2395
3024
  }
2396
- function pointOnSegment(a, b, t) {
2397
- if (isStraightSegment(a, b)) return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
2398
- return cubicPointAt(a, a.outHandle ?? a, b.inHandle ?? b, b, t);
3025
+ function alignedOpposite(anchor, moved, other2) {
3026
+ const mx = moved.x - anchor.x;
3027
+ const my = moved.y - anchor.y;
3028
+ const len = Math.hypot(mx, my);
3029
+ if (len === 0) return other2;
3030
+ const keep = Math.hypot(other2.x - anchor.x, other2.y - anchor.y);
3031
+ return { x: anchor.x - mx / len * keep, y: anchor.y - my / len * keep };
2399
3032
  }
2400
- function deleteAnchorsAt(set, flatIndexes) {
2401
- const bySub = /* @__PURE__ */ new Map();
3033
+
3034
+ // src/paths/handleEdits.ts
3035
+ function retractHandles(set, flatIndexes) {
3036
+ let changed = false;
2402
3037
  for (const flat of new Set(flatIndexes)) {
2403
- const loc = locateAnchor(set, flat);
2404
- if (!loc) continue;
2405
- const list = bySub.get(loc.sub);
2406
- if (list) list.push(loc.idx);
2407
- else bySub.set(loc.sub, [loc.idx]);
2408
- }
2409
- if (bySub.size === 0) return false;
2410
- let survivors = 0;
2411
- for (let s = 0; s < set.anchors.length; s++) {
2412
- const doomed = bySub.get(s)?.length ?? 0;
2413
- if (set.anchors[s].length - doomed >= 2) survivors++;
2414
- }
2415
- if (survivors === 0) return false;
2416
- for (const [s, indices] of bySub) {
2417
- const sub3 = set.anchors[s];
2418
- if (!sub3) continue;
2419
- indices.sort((a, b) => b - a);
2420
- for (const i of indices) {
2421
- const wraps = set.closed[s] && sub3.length > 2;
2422
- const prev = wraps ? sub3[(i - 1 + sub3.length) % sub3.length] : sub3[i - 1];
2423
- const next = wraps ? sub3[(i + 1) % sub3.length] : sub3[i + 1];
2424
- if (prev && next) {
2425
- const { c1, c2 } = fitCubicThroughDeletion(prev, next);
2426
- prev.outHandle = c1;
2427
- next.inHandle = c2;
2428
- }
2429
- sub3.splice(i, 1);
3038
+ const a = anchorAt(set, flat);
3039
+ if (!a) continue;
3040
+ if (a.inHandle) {
3041
+ changed = changed || !same2(a.inHandle, a);
3042
+ delete a.inHandle;
2430
3043
  }
2431
- }
2432
- for (let s = set.anchors.length - 1; s >= 0; s--) {
2433
- if (set.anchors[s].length < 2) {
2434
- set.anchors.splice(s, 1);
2435
- set.closed.splice(s, 1);
3044
+ if (a.outHandle) {
3045
+ changed = changed || !same2(a.outHandle, a);
3046
+ delete a.outHandle;
2436
3047
  }
2437
3048
  }
2438
- return true;
3049
+ return changed;
2439
3050
  }
2440
- function openSubpathAt(set, flatIndex) {
2441
- const loc = locateAnchor(set, flatIndex);
2442
- if (!loc) return false;
2443
- if (!set.closed[loc.sub]) return false;
2444
- const sub3 = set.anchors[loc.sub];
2445
- if (!sub3) return false;
2446
- const { inHandle, ...start } = sub3[loc.idx];
2447
- const end = { x: start.x, y: start.y, ...inHandle ? { inHandle } : {} };
2448
- set.anchors[loc.sub] = [start, ...sub3.slice(loc.idx + 1), ...sub3.slice(0, loc.idx), end];
2449
- set.closed[loc.sub] = false;
3051
+ function makeHandlesSymmetric(set, flatIndexes) {
3052
+ let changed = false;
3053
+ for (const flat of new Set(flatIndexes)) {
3054
+ const loc = locateAnchor(set, flat);
3055
+ if (!loc) continue;
3056
+ const sub3 = set.anchors[loc.sub];
3057
+ const isEnd = !set.closed[loc.sub] && (loc.idx === 0 || loc.idx === sub3.length - 1);
3058
+ if (isEnd) continue;
3059
+ changed = symmetrize(sub3[loc.idx]) || changed;
3060
+ }
3061
+ return changed;
3062
+ }
3063
+ function symmetrize(a) {
3064
+ const out = a.outHandle ? offset(a, a.outHandle) : null;
3065
+ const inFlipped = a.inHandle ? scale2(offset(a, a.inHandle), -1) : null;
3066
+ const outLen = out ? Math.hypot(out.x, out.y) : 0;
3067
+ const inLen = inFlipped ? Math.hypot(inFlipped.x, inFlipped.y) : 0;
3068
+ if (outLen === 0 && inLen === 0) return false;
3069
+ let dir;
3070
+ let len;
3071
+ if (outLen > 0 && inLen > 0) {
3072
+ const sum = { x: out.x / outLen + inFlipped.x / inLen, y: out.y / outLen + inFlipped.y / inLen };
3073
+ const sumLen = Math.hypot(sum.x, sum.y);
3074
+ dir = sumLen > 1e-9 ? scale2(sum, 1 / sumLen) : scale2(out, 1 / outLen);
3075
+ len = (outLen + inLen) / 2;
3076
+ } else if (outLen > 0) {
3077
+ dir = scale2(out, 1 / outLen);
3078
+ len = outLen;
3079
+ } else {
3080
+ dir = scale2(inFlipped, 1 / inLen);
3081
+ len = inLen;
3082
+ }
3083
+ const nextOut = { x: a.x + dir.x * len, y: a.y + dir.y * len };
3084
+ const nextIn = { x: a.x - dir.x * len, y: a.y - dir.y * len };
3085
+ if (same2(a.outHandle, nextOut) && same2(a.inHandle, nextIn)) return false;
3086
+ a.outHandle = nextOut;
3087
+ a.inHandle = nextIn;
2450
3088
  return true;
2451
3089
  }
2452
- function anchorsInRect(set, rect) {
2453
- const x2 = rect.x + rect.width;
2454
- const y2 = rect.y + rect.height;
2455
- const hits = [];
2456
- let flat = 0;
2457
- for (const sub3 of set.anchors) {
2458
- for (const a of sub3) {
2459
- if (a.x >= rect.x && a.x <= x2 && a.y >= rect.y && a.y <= y2) hits.push(flat);
2460
- flat++;
2461
- }
2462
- }
2463
- return hits;
3090
+ function handleAlongItsAngle(anchor, handle, to) {
3091
+ const d = offset(anchor, handle);
3092
+ const len = Math.hypot(d.x, d.y);
3093
+ if (len === 0) return { x: to.x, y: to.y };
3094
+ const ux = d.x / len;
3095
+ const uy = d.y / len;
3096
+ const along = Math.max(0, (to.x - anchor.x) * ux + (to.y - anchor.y) * uy);
3097
+ return { x: anchor.x + ux * along, y: anchor.y + uy * along };
3098
+ }
3099
+ function offset(from, to) {
3100
+ return { x: to.x - from.x, y: to.y - from.y };
3101
+ }
3102
+ function scale2(p, k) {
3103
+ return { x: p.x * k, y: p.y * k };
3104
+ }
3105
+ function same2(p, q) {
3106
+ return !!p && Math.abs(p.x - q.x) < 1e-9 && Math.abs(p.y - q.y) < 1e-9;
2464
3107
  }
2465
3108
 
2466
3109
  // src/paths/nearestAnchor.ts
2467
- function nearestPathAnchor(paths, point, px, scale2, accept) {
3110
+ function nearestPathAnchor(paths, point, px, scale3, accept) {
2468
3111
  let best = null;
2469
3112
  let bestD2 = px * px;
2470
3113
  for (const { id, path } of paths) {
@@ -2473,7 +3116,7 @@ function nearestPathAnchor(paths, point, px, scale2, accept) {
2473
3116
  const list = anchors[sub3];
2474
3117
  for (let idx = 0; idx < list.length; idx++) {
2475
3118
  const a = list[idx];
2476
- const s = scaleDelta(a.x - point.x, a.y - point.y, scale2);
3119
+ const s = scaleDelta(a.x - point.x, a.y - point.y, scale3);
2477
3120
  const d2 = s.x * s.x + s.y * s.y;
2478
3121
  if (d2 > bestD2 || best !== null && d2 === bestD2) continue;
2479
3122
  const end = closed[sub3] ? null : idx === list.length - 1 ? "last" : idx === 0 ? "first" : null;
@@ -2580,6 +3223,6 @@ function typedArrayReplacer(_key, value) {
2580
3223
  return tagTypedArray(value) ?? value;
2581
3224
  }
2582
3225
 
2583
- export { EASINGS, PathBuilder, SPRING_PRESETS, TYPED_ARRAY_CONSTRUCTORS, ZOOM_FLOOR, anchorAt, anchorCount, anchorsInRect, anchorsToPath, applyToPoint, boundsOfPath, boxToBox, circlePath, clampRectWithin, composePath, countPathAnchors, cubicBezierEasing, decomposePath, deleteAnchorsAt, 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, ellipsePath, flatAnchorIndex, hasOpenSubpath, identity, insertAnchorOnSegment, invert, isAnchorSmooth, isStraightSegment, linePath, linear, locateAnchor, mat3, meanScale, moveHandleTo, multiply, nearestOnCubic, nearestOnLine, nearestOnPath, nearestOnQuadratic, nearestPathAnchor, nearestSegmentT, normalizeZoom, openSubpathAt, pathContainsPoint, pathContainsPolygon, pathContainsRect, pathDistanceToPoint, pathFromD, pathInPoseFrame, pathInWorld, pathIntersectsPolygon, pathIntersectsRect, pathSignedArea, pathThroughPoseFrame, pathToAnchors, placeRect, pointAlongPath, pointInPath, polygonContainsPath, polygonFromPoints, polygonIntersectsPath, polylineFromPoints, portControls2 as portControls, portCurvePoints2 as portCurvePoints, poseFrameInverseOf, poseFrameOf, poseLinearOf, poseRotationOf, pxExtent, rectMarkerPath, rectPath, regularPolygonPath, resolveEasing, reverseAnchors, reversePath, reviveTypedArrays, rotate, rotateAboutPoint, rotatePathAround, roundRectPath, scale, scaleDelta, scalePathToBounds, schneiderFit, screenAngleOf, segmentAt, segmentEnds, splitByClosure, splitSubpaths, squarePath, standoff, starPath, strokeHitTest, tagTypedArray, tagTypedArrays, transformCoords, transformPath, translate, translateAnchorBy, translatePath, typedArrayReplacer, unionBoundsPath, untagTypedArray, viewToMat3, withinPxBox, withinPxRadius, worldEditToStorage };
3226
+ export { EASINGS, PathBuilder, SPRING_PRESETS, TYPED_ARRAY_CONSTRUCTORS, ZOOM_FLOOR, anchorAt, anchorCount, anchorSymmetries, anchorSymmetry, anchorsInRect, anchorsToPath, applyToPoint, averageAnchorsAt, bendSegment, boundsOfPath, boxToBox, circlePath, clampRectWithin, closestOpenEnds, composePath, countPathAnchors, cubicBezierEasing, decomposePath, deleteAnchorsAt, 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, ellipsePath, fitCubicThroughDeletion, fitCubicWithTangents, flatAnchorIndex, handleAlongItsAngle, hasOpenSubpath, identity, insertAnchorOnSegment, invert, isAnchorSmooth, isStraightSegment, joinAnchorsAt, joinSubpathEnds, linePath, linear, locateAnchor, makeHandlesSymmetric, mat3, meanScale, moveHandleSymmetric, moveHandleTo, multiply, nearestOnCubic, nearestOnLine, nearestOnPath, nearestOnQuadratic, nearestPathAnchor, nearestSegmentT, normalizeZoom, openSubpathAt, pathContainsPoint, pathContainsPolygon, pathContainsRect, pathDistanceToPoint, pathFromD, pathInPoseFrame, pathInWorld, pathIntersectsPolygon, pathIntersectsRect, pathSignedArea, pathThroughPoseFrame, pathToAnchors, placeRect, pointAlongPath, pointInPath, polygonContainsPath, polygonFromPoints, polygonIntersectsPath, polylineFromPoints, portControls2 as portControls, portCurvePoints2 as portCurvePoints, poseFrameInverseOf, poseFrameOf, poseLinearOf, poseRotationOf, pxExtent, rectMarkerPath, rectPath, regularPolygonPath, remapPointValues, resolveEasing, retractHandles, reverseAnchors, reversePath, reviveTypedArrays, rotate, rotateAboutPoint, rotatePathAround, roundRectPath, scale, scaleDelta, scalePathToBounds, schneiderFit, screenAngleOf, segmentAt, segmentEnds, simplifyPath, splitByClosure, splitSubpaths, squarePath, standoff, starPath, strokeHitTest, subpathEndAt, tagTypedArray, tagTypedArrays, transformCoords, transformPath, translate, translateAnchorBy, translateAnchorsSymmetric, translatePath, typedArrayReplacer, unionBoundsPath, untagTypedArray, viewToMat3, withinPxBox, withinPxRadius, worldEditToStorage };
2584
3227
  //# sourceMappingURL=index.js.map
2585
3228
  //# sourceMappingURL=index.js.map