@pixerate/editor 0.106.0 → 0.108.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/THIRD_PARTY_NOTICES.md +4 -0
  2. package/dist/animation-editor/index.cjs +39 -39
  3. package/dist/animation-editor/index.d.cts +1 -1
  4. package/dist/animation-editor/index.d.ts +1 -1
  5. package/dist/animation-editor/index.js +1 -1
  6. package/dist/{chunk-DS7KDKPX.js → chunk-3PHA3UKI.js} +2 -2
  7. package/dist/{chunk-ZID226ZG.cjs → chunk-FRD3FUXI.cjs} +172 -172
  8. package/dist/{chunk-ZID226ZG.cjs.map → chunk-FRD3FUXI.cjs.map} +1 -1
  9. package/dist/{chunk-67FRIOPZ.js → chunk-G4BIYE6R.js} +2 -2
  10. package/dist/{chunk-PV42JITT.cjs → chunk-HUFQD4CB.cjs} +44 -44
  11. package/dist/{chunk-PV42JITT.cjs.map → chunk-HUFQD4CB.cjs.map} +1 -1
  12. package/dist/{chunk-63EFQCT5.js → chunk-HUQ7EHVI.js} +1140 -371
  13. package/dist/chunk-HUQ7EHVI.js.map +1 -0
  14. package/dist/{chunk-4X5MIVCE.js → chunk-SOROZ2BF.js} +3 -3
  15. package/dist/{chunk-CRMAXIUV.cjs → chunk-SZ6KMZNM.cjs} +157 -8
  16. package/dist/chunk-SZ6KMZNM.cjs.map +1 -0
  17. package/dist/{chunk-WSTEXEKD.cjs → chunk-T3QZ6S47.cjs} +92 -92
  18. package/dist/{chunk-WSTEXEKD.cjs.map → chunk-T3QZ6S47.cjs.map} +1 -1
  19. package/dist/{chunk-KANRAHTL.cjs → chunk-UTEVPQTS.cjs} +1353 -584
  20. package/dist/chunk-UTEVPQTS.cjs.map +1 -0
  21. package/dist/{chunk-HEW6KDNI.js → chunk-WMBIJCHA.js} +155 -6
  22. package/dist/chunk-WMBIJCHA.js.map +1 -0
  23. package/dist/{compositor-Bc3PNP3C.d.cts → compositor-D92URMlm.d.cts} +1 -1
  24. package/dist/{compositor-BsYYllvR.d.ts → compositor-Kt_M4ruo.d.ts} +1 -1
  25. package/dist/{export-_m3Es4ru.d.cts → export-JGWQEPJn.d.cts} +3 -3
  26. package/dist/{export-iMrop3Jm.d.ts → export-a9bnF7Cl.d.ts} +3 -3
  27. package/dist/image-editor/index.cjs +627 -619
  28. package/dist/image-editor/index.cjs.map +1 -1
  29. package/dist/image-editor/index.d.cts +8 -8
  30. package/dist/image-editor/index.d.ts +8 -8
  31. package/dist/image-editor/index.js +12 -4
  32. package/dist/image-editor/index.js.map +1 -1
  33. package/dist/{mixer-C1M1Nua_.d.cts → mixer-5ziHU8OS.d.cts} +1 -1
  34. package/dist/{mixer-Cu5a9Bzd.d.ts → mixer-DNqiYXYQ.d.ts} +1 -1
  35. package/dist/paint/index.cjs +3 -3
  36. package/dist/paint/index.d.cts +2 -2
  37. package/dist/paint/index.d.ts +2 -2
  38. package/dist/paint/index.js +2 -2
  39. package/dist/paint-editor/index.cjs +365 -132
  40. package/dist/paint-editor/index.cjs.map +1 -1
  41. package/dist/paint-editor/index.d.cts +113 -8
  42. package/dist/paint-editor/index.d.ts +113 -8
  43. package/dist/paint-editor/index.js +249 -16
  44. package/dist/paint-editor/index.js.map +1 -1
  45. package/dist/raster/index.cjs +11 -3
  46. package/dist/raster/index.cjs.map +1 -1
  47. package/dist/raster/index.d.cts +4 -4
  48. package/dist/raster/index.d.ts +4 -4
  49. package/dist/raster/index.js +10 -2
  50. package/dist/{surface-BBJN9wxg.d.ts → surface-CkKOHGPm.d.cts} +20 -2
  51. package/dist/{surface-BBJN9wxg.d.cts → surface-CkKOHGPm.d.ts} +20 -2
  52. package/dist/{svg-Bj1MQFh6.d.cts → svg-ClVIG4T7.d.cts} +26 -2
  53. package/dist/{svg-vme0PLNo.d.ts → svg-SseJTIrA.d.ts} +26 -2
  54. package/dist/vector/index.cjs +977 -948
  55. package/dist/vector/index.cjs.map +1 -1
  56. package/dist/vector/index.d.cts +266 -7
  57. package/dist/vector/index.d.ts +266 -7
  58. package/dist/vector/index.js +427 -398
  59. package/dist/vector/index.js.map +1 -1
  60. package/package.json +1 -1
  61. package/skills/pixerate-image-editor/SKILL.md +50 -7
  62. package/skills/pixerate-paint-editor/SKILL.md +21 -1
  63. package/skills/pixerate-vector/SKILL.md +35 -5
  64. package/dist/chunk-63EFQCT5.js.map +0 -1
  65. package/dist/chunk-CRMAXIUV.cjs.map +0 -1
  66. package/dist/chunk-HEW6KDNI.js.map +0 -1
  67. package/dist/chunk-KANRAHTL.cjs.map +0 -1
  68. /package/dist/{chunk-DS7KDKPX.js.map → chunk-3PHA3UKI.js.map} +0 -0
  69. /package/dist/{chunk-67FRIOPZ.js.map → chunk-G4BIYE6R.js.map} +0 -0
  70. /package/dist/{chunk-4X5MIVCE.js.map → chunk-SOROZ2BF.js.map} +0 -0
@@ -32,6 +32,7 @@ import {
32
32
  VECTOR_GRADIENT_MIN_STOPS,
33
33
  VECTOR_GRADIENT_SPREADS,
34
34
  VECTOR_GRADIENT_TYPES,
35
+ VECTOR_MASK_TYPES,
35
36
  VECTOR_MERGE_MODES,
36
37
  VECTOR_PATHFINDER_OPERATIONS,
37
38
  VECTOR_PRESSURE_NOMINAL,
@@ -77,6 +78,9 @@ import {
77
78
  effectPathVertexCount,
78
79
  effectiveStrokeAlign,
79
80
  ellipseSubpath,
81
+ eraseFromFill,
82
+ eraseFromOutline,
83
+ eraserRegion,
80
84
  evaluateVectorPathKeyframes,
81
85
  expandVectorAppearance,
82
86
  expandVectorAppearances,
@@ -122,6 +126,10 @@ import {
122
126
  isVectorFilterType,
123
127
  isVectorGradient,
124
128
  isVectorLiveBoolean,
129
+ isVectorMaskActive,
130
+ isVectorMaskGroup,
131
+ isVectorMaskType,
132
+ knifePath,
125
133
  lineSubpath,
126
134
  locateVectorNode,
127
135
  mapBezierPath,
@@ -154,6 +162,7 @@ import {
154
162
  periodicVectorGradientStops,
155
163
  pinVectorGradient,
156
164
  polygonSubpath,
165
+ polylineBounds,
157
166
  pressureWidthProfile,
158
167
  puckerBloatPaths,
159
168
  rectSubpath,
@@ -177,6 +186,7 @@ import {
177
186
  sanitizeVectorFills,
178
187
  sanitizeVectorFilters,
179
188
  sanitizeVectorGradient,
189
+ sanitizeVectorMask,
180
190
  sanitizeVectorStrokeSettings,
181
191
  sanitizeVectorStrokes,
182
192
  sanitizeWidthProfile,
@@ -184,8 +194,10 @@ import {
184
194
  setSubpathCorners,
185
195
  setVectorGradientVector,
186
196
  setWidthPoint,
197
+ simplifyPolyline,
187
198
  sortedVectorGradientStops,
188
199
  spanVectorGradientStops,
200
+ splitIntoFaces,
189
201
  starSubpath,
190
202
  strokePositionPoint,
191
203
  strokeWidthProfileOf,
@@ -213,6 +225,7 @@ import {
213
225
  vectorBlendModeCss,
214
226
  vectorBlendModeFromCss,
215
227
  vectorBoxMatrix,
228
+ vectorChildAppearanceShapes,
216
229
  vectorCornerAt,
217
230
  vectorCorners,
218
231
  vectorCornersAttribute,
@@ -243,6 +256,19 @@ import {
243
256
  vectorGradientTAt,
244
257
  vectorGradientUnitsMatrix,
245
258
  vectorLiveBooleanSteps,
259
+ vectorMaskContains,
260
+ vectorMaskContentBounds,
261
+ vectorMaskContents,
262
+ vectorMaskOwner,
263
+ vectorMaskRegion,
264
+ vectorMaskRegionPath,
265
+ vectorMaskRegionShapes,
266
+ vectorMaskRenderData,
267
+ vectorMaskSource,
268
+ vectorMaskSummary,
269
+ vectorMaskedGeometry,
270
+ vectorMaskedLocalBounds,
271
+ vectorMaskedShapes,
246
272
  vectorMeshFromOutline,
247
273
  vectorMeshOutline,
248
274
  vectorNodeBounds,
@@ -284,7 +310,7 @@ import {
284
310
  withVectorStrokes,
285
311
  withVectorTargetPaint,
286
312
  zigzagPaths
287
- } from "../chunk-63EFQCT5.js";
313
+ } from "../chunk-HUQ7EHVI.js";
288
314
  import {
289
315
  bezierPathBounds,
290
316
  bezierPathToSvg,
@@ -294,20 +320,14 @@ import {
294
320
  distanceToBezierPath,
295
321
  fitCubicWithTangents,
296
322
  fitCubics,
297
- normalizeBezierPath,
298
- outlineStroke,
299
323
  pathBoolean,
300
324
  pathToContours,
301
- pointInBezierPath,
302
- segmentBounds,
303
325
  segmentDerivative,
304
326
  segmentIntersections,
305
327
  segmentNearest,
306
328
  segmentPoint,
307
329
  segmentTangent,
308
- subSegment,
309
- transformBezierPath,
310
- windingNumber
330
+ transformBezierPath
311
331
  } from "../chunk-YCSFS3GK.js";
312
332
  import {
313
333
  IDENTITY_MATRIX,
@@ -408,14 +428,14 @@ function vectorGradientBarPosition(a, p) {
408
428
  return { offset: (d.x * v.x + d.y * v.y) / l2, across: d.x * a.normal.x + d.y * a.normal.y };
409
429
  }
410
430
  function hitVectorGradientAnnotator(a, p) {
411
- const near2 = (q, r) => !!q && Math.hypot(q.x - p.x, q.y - p.y) <= r;
412
- for (let i = a.stops.length - 1; i >= 0; i--) if (near2(a.stops[i], STOP_HIT)) return { kind: "stop", index: a.stops[i].index };
413
- for (const m of a.midpoints) if (near2(m, MIDPOINT_HIT)) return { kind: "midpoint", index: m.index };
431
+ const near = (q, r) => !!q && Math.hypot(q.x - p.x, q.y - p.y) <= r;
432
+ for (let i = a.stops.length - 1; i >= 0; i--) if (near(a.stops[i], STOP_HIT)) return { kind: "stop", index: a.stops[i].index };
433
+ for (const m of a.midpoints) if (near(m, MIDPOINT_HIT)) return { kind: "midpoint", index: m.index };
414
434
  const centred = isCentredVectorGradient(a.gradient.type);
415
- if (near2(a.end, HANDLE_HIT)) return { kind: "handle", handle: centred ? "radius" : "end" };
416
- if (a.focal && near2(a.focal, HANDLE_HIT) && !near2(a.start, 1)) return { kind: "handle", handle: "focal" };
417
- if (near2(a.start, HANDLE_HIT)) return { kind: "handle", handle: "start" };
418
- if (near2(a.aspect, HANDLE_HIT)) return { kind: "handle", handle: "aspect" };
435
+ if (near(a.end, HANDLE_HIT)) return { kind: "handle", handle: centred ? "radius" : "end" };
436
+ if (a.focal && near(a.focal, HANDLE_HIT) && !near(a.start, 1)) return { kind: "handle", handle: "focal" };
437
+ if (near(a.start, HANDLE_HIT)) return { kind: "handle", handle: "start" };
438
+ if (near(a.aspect, HANDLE_HIT)) return { kind: "handle", handle: "aspect" };
419
439
  const { offset, across } = vectorGradientBarPosition(a, p);
420
440
  if (offset >= 0 && offset <= 1 && across >= -BAR_HIT && across <= VECTOR_GRADIENT_STOP_GAP + STOP_HIT) return { kind: "bar", offset };
421
441
  return null;
@@ -955,6 +975,10 @@ var VectorSnapTargets = class _VectorSnapTargets {
955
975
  const visit = (list, parent) => {
956
976
  for (const stored of list) {
957
977
  if (exclude.has(stored.id) || stored.visible === false) continue;
978
+ if (isVectorMaskActive(stored) && !contains(stored, exclude)) {
979
+ visit(vectorAppearanceNodes(stored), multiplyMatrices(nodeMatrix(stored), parent));
980
+ continue;
981
+ }
958
982
  const n = contains(stored, exclude) ? stored : expandVectorAppearance(stored);
959
983
  const m = multiplyMatrices(nodeMatrix(n), parent);
960
984
  if (n.type === "group") {
@@ -1025,14 +1049,14 @@ var VectorSnapTargets = class _VectorSnapTargets {
1025
1049
  }
1026
1050
  const point = this.nearestPoint(p, tol);
1027
1051
  if (point) return at(point.p, point.kind);
1028
- const near2 = this.segmentsNear(p, tol);
1029
- const crossing = this.crossing(near2, p, tol);
1052
+ const near = this.segmentsNear(p, tol);
1053
+ const crossing = this.crossing(near, p, tol);
1030
1054
  if (crossing) return at(crossing, "intersect");
1031
1055
  if (leave && leave.curves !== false) {
1032
- const foot = this.curvePoint(near2, leave.at, p, tol);
1056
+ const foot = this.curvePoint(near, leave.at, p, tol);
1033
1057
  if (foot) return at(foot.point, foot.kind, [line("construction", leave.at, foot.point)]);
1034
1058
  }
1035
- const on = this.onSegment(near2);
1059
+ const on = this.onSegment(near);
1036
1060
  if (on) return at(on, "path");
1037
1061
  if (leave?.angles?.length) {
1038
1062
  const c = construction(leave, p, tol);
@@ -1115,12 +1139,12 @@ var VectorSnapTargets = class _VectorSnapTargets {
1115
1139
  }
1116
1140
  return out.sort((a, b) => a.distance - b.distance);
1117
1141
  }
1118
- onSegment(near2) {
1119
- return near2.length ? toVec(segmentPoint(near2[0].seg, near2[0].t)) : null;
1142
+ onSegment(near) {
1143
+ return near.length ? toVec(segmentPoint(near[0].seg, near[0].t)) : null;
1120
1144
  }
1121
1145
  /** Where two of the target segments near `p` cross, the crossing nearest `p` within `tol`. */
1122
- crossing(near2, p, tol) {
1123
- const list = near2.slice(0, CROSSING_PAIRS);
1146
+ crossing(near, p, tol) {
1147
+ const list = near.slice(0, CROSSING_PAIRS);
1124
1148
  let best = null;
1125
1149
  let bestD = Infinity;
1126
1150
  for (let i = 0; i < list.length; i++) {
@@ -1142,9 +1166,9 @@ var VectorSnapTargets = class _VectorSnapTargets {
1142
1166
  * (within `tol` of it): roots of `(B(t) − base) · B′(t)` and `(B(t) − base) × B′(t)`
1143
1167
  * bracketed by sign changes of samples and narrowed by bisection.
1144
1168
  */
1145
- curvePoint(near2, base, p, tol) {
1169
+ curvePoint(near, base, p, tol) {
1146
1170
  let best = null;
1147
- for (const { seg } of near2) {
1171
+ for (const { seg } of near) {
1148
1172
  for (const kind of ["perpendicular", "tangent"]) {
1149
1173
  if (kind === "tangent" && seg.length === 2) continue;
1150
1174
  for (const q of solveOnSegment(seg, base, kind === "perpendicular" ? dotCondition : crossCondition)) {
@@ -1886,346 +1910,6 @@ function removeRedundantPoints(sp, tolerance) {
1886
1910
  return segsToSubpath(segs, sp.closed, types) ?? { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed };
1887
1911
  }
1888
1912
 
1889
- // src/vector/pathCut.ts
1890
- var loopArea = (l) => contourSignedArea(l.segs);
1891
- function loopSample(l) {
1892
- const k = l.chord.findIndex((c) => !c);
1893
- return segmentPoint(l.segs[k >= 0 ? k : 0], 0.5);
1894
- }
1895
- var contains2 = (l, p) => windingNumber([l.segs], p) !== 0;
1896
- function groupFaces(loops) {
1897
- const usable2 = loops.filter((l) => l.segs.length && Math.abs(loopArea(l)) > 1e-12);
1898
- if (!usable2.length) return [];
1899
- const largest = usable2.reduce((a, b) => Math.abs(loopArea(b)) > Math.abs(loopArea(a)) ? b : a);
1900
- const sign = Math.sign(loopArea(largest));
1901
- const outers = usable2.filter((l) => Math.sign(loopArea(l)) === sign).sort((a, b) => Math.abs(loopArea(a)) - Math.abs(loopArea(b)));
1902
- const faces = new Map(outers.map((o) => [o, { loops: [o] }]));
1903
- for (const hole of usable2.filter((l) => Math.sign(loopArea(l)) !== sign)) {
1904
- const p = loopSample(hole);
1905
- const parent = outers.find((o) => Math.abs(loopArea(o)) > Math.abs(loopArea(hole)) && contains2(o, p));
1906
- if (parent) faces.get(parent).loops.push(hole);
1907
- }
1908
- return outers.reverse().map((o) => faces.get(o));
1909
- }
1910
- function faceToPath(face) {
1911
- return contoursToPath(face.loops.map((l) => ({ segments: l.segs, closed: true })));
1912
- }
1913
- function facesOf(path, fillRule) {
1914
- const normalized = normalizeBezierPath(path, fillRule);
1915
- const loops = pathToContours(normalized).map((c) => {
1916
- const segs = closeContour(c, true).map((s) => s.slice());
1917
- return { segs, chord: segs.map(() => false) };
1918
- });
1919
- return groupFaces(loops);
1920
- }
1921
- function splitIntoFaces(path, fillRule = "nonzero") {
1922
- return facesOf(path, fillRule).map(faceToPath);
1923
- }
1924
- function rootsInUnit(a, b, c, d, scale) {
1925
- const f = (t) => ((a * t + b) * t + c) * t + d;
1926
- const tol = 1e-12 * Math.max(1, scale);
1927
- const cuts = [0];
1928
- const qa = 3 * a;
1929
- const qb = 2 * b;
1930
- if (Math.abs(qa) > 1e-18) {
1931
- const disc = qb * qb - 4 * qa * c;
1932
- if (disc >= 0) {
1933
- const r = Math.sqrt(disc);
1934
- for (const t of [(-qb - r) / (2 * qa), (-qb + r) / (2 * qa)].sort((x, y) => x - y)) if (t > 0 && t < 1) cuts.push(t);
1935
- }
1936
- } else if (Math.abs(qb) > 1e-18) {
1937
- const t = -c / qb;
1938
- if (t > 0 && t < 1) cuts.push(t);
1939
- }
1940
- cuts.push(1);
1941
- const roots = [];
1942
- const push = (t) => {
1943
- if (!roots.some((r) => Math.abs(r - t) < 1e-9)) roots.push(t);
1944
- };
1945
- for (let i = 0; i < cuts.length - 1; i++) {
1946
- let lo = cuts[i];
1947
- let hi = cuts[i + 1];
1948
- let flo = f(lo);
1949
- const fhi = f(hi);
1950
- if (Math.abs(flo) <= tol) push(lo);
1951
- if (Math.abs(fhi) <= tol) push(hi);
1952
- if (flo * fhi >= 0) continue;
1953
- for (let k = 0; k < 80; k++) {
1954
- const m = (lo + hi) / 2;
1955
- const fm = f(m);
1956
- if (fm === 0) {
1957
- lo = hi = m;
1958
- break;
1959
- }
1960
- if (fm * flo < 0) hi = m;
1961
- else {
1962
- lo = m;
1963
- flo = fm;
1964
- }
1965
- }
1966
- push((lo + hi) / 2);
1967
- }
1968
- return roots.sort((x, y) => x - y);
1969
- }
1970
- function lineHits(p, q, seg) {
1971
- const dx = q[0] - p[0];
1972
- const dy = q[1] - p[1];
1973
- const len2 = dx * dx + dy * dy;
1974
- if (len2 < 1e-24) return [];
1975
- const h = (pt) => (pt[0] - p[0]) * -dy + (pt[1] - p[1]) * dx;
1976
- let coeffs;
1977
- if (seg.length === 2) coeffs = [0, 0, h(seg[1]) - h(seg[0]), h(seg[0])];
1978
- else {
1979
- const [h0, h1, h2, h3] = seg.map(h);
1980
- coeffs = [-h0 + 3 * h1 - 3 * h2 + h3, 3 * h0 - 6 * h1 + 3 * h2, -3 * h0 + 3 * h1, h0];
1981
- }
1982
- if (coeffs.every((c) => Math.abs(c) < 1e-12 * Math.max(1, len2))) return [];
1983
- const scale = Math.sqrt(len2) * Math.max(1, ...seg.map((pt) => Math.abs(pt[0]) + Math.abs(pt[1])));
1984
- const out = [];
1985
- for (const t of rootsInUnit(...coeffs, scale)) {
1986
- const point = segmentPoint(seg, t);
1987
- const s = ((point[0] - p[0]) * dx + (point[1] - p[1]) * dy) / len2;
1988
- if (s >= -1e-9 && s <= 1 + 1e-9) out.push({ s: Math.min(1, Math.max(0, s)), t, point });
1989
- }
1990
- return out;
1991
- }
1992
- var near = (a, b, eps) => Math.abs(a[0] - b[0]) <= eps && Math.abs(a[1] - b[1]) <= eps;
1993
- var degenerate = (seg, eps) => seg.every((p) => near(p, seg[0], eps));
1994
- function arc(l, a, b, eps) {
1995
- const n = l.segs.length;
1996
- const segs = [];
1997
- const chord = [];
1998
- const push = (k, t0, t1) => {
1999
- const piece = t0 <= 0 && t1 >= 1 ? l.segs[k].slice() : subSegment(l.segs[k], t0, t1);
2000
- if (!degenerate(piece, eps)) {
2001
- segs.push(piece);
2002
- chord.push(l.chord[k]);
2003
- }
2004
- };
2005
- if (a.seg === b.seg && b.t >= a.t + 1e-12) {
2006
- push(a.seg, a.t, b.t);
2007
- return { segs, chord };
2008
- }
2009
- push(a.seg, a.t, 1);
2010
- for (let k = (a.seg + 1) % n; k !== b.seg; k = (k + 1) % n) push(k, 0, 1);
2011
- push(b.seg, 0, b.t);
2012
- return { segs, chord };
2013
- }
2014
- function polySegs(points, eps) {
2015
- const out = [];
2016
- for (let i = 1; i < points.length; i++) if (!near(points[i - 1], points[i], eps)) out.push([points[i - 1], points[i]]);
2017
- return out;
2018
- }
2019
- function knifePath(path, fillRule, knife) {
2020
- const pts = knife.filter((p) => Number.isFinite(p[0]) && Number.isFinite(p[1]));
2021
- if (pts.length < 2) return null;
2022
- let faces = facesOf(path, fillRule);
2023
- if (!faces.length) return null;
2024
- const b = bezierPathBounds(path);
2025
- const size = b ? Math.max(b.maxX - b.minX, b.maxY - b.minY, 1) : 1;
2026
- const eps = size * 1e-9;
2027
- let cursor = -Infinity;
2028
- let cuts = 0;
2029
- for (let guard = 0; guard < 512; guard++) {
2030
- let best = null;
2031
- faces.forEach((face2, fi2) => {
2032
- const hits = [];
2033
- for (let m = 0; m < pts.length - 1; m++) {
2034
- if (m + 1 < cursor - 1e-9) continue;
2035
- const kb = { minX: Math.min(pts[m][0], pts[m + 1][0]), maxX: Math.max(pts[m][0], pts[m + 1][0]), minY: Math.min(pts[m][1], pts[m + 1][1]), maxY: Math.max(pts[m][1], pts[m + 1][1]) };
2036
- face2.loops.forEach(
2037
- (l, li) => l.segs.forEach((seg, si) => {
2038
- const sb = segmentBounds(seg);
2039
- if (sb.maxX < kb.minX - eps || sb.minX > kb.maxX + eps || sb.maxY < kb.minY - eps || sb.minY > kb.maxY + eps) return;
2040
- for (const h of lineHits(pts[m], pts[m + 1], seg)) {
2041
- const s = m + h.s;
2042
- if (s >= cursor - 1e-9) hits.push({ s, loop: li, seg: si, t: h.t, point: h.point });
2043
- }
2044
- })
2045
- );
2046
- }
2047
- hits.sort((x, y) => x.s - y.s);
2048
- const unique = hits.filter((h, i) => i === 0 || h.s - hits[i - 1].s > 1e-9 || !near(h.point, hits[i - 1].point, eps * 10));
2049
- const inside = (s) => {
2050
- const m = Math.min(pts.length - 2, Math.floor(s));
2051
- const u = s - m;
2052
- const q = [pts[m][0] + (pts[m + 1][0] - pts[m][0]) * u, pts[m][1] + (pts[m + 1][1] - pts[m][1]) * u];
2053
- return windingNumber(face2.loops.map((l) => l.segs), q) !== 0;
2054
- };
2055
- for (let i = 0; i < unique.length - 1; i++) {
2056
- const from2 = unique[i];
2057
- const to2 = unique[i + 1];
2058
- if (near(from2.point, to2.point, eps * 10) || !inside((from2.s + to2.s) / 2)) continue;
2059
- if (!best || from2.s < best.from.s) best = { face: fi2, from: from2, to: to2 };
2060
- break;
2061
- }
2062
- });
2063
- if (!best) break;
2064
- const { face: fi, from, to } = best;
2065
- const face = faces[fi];
2066
- const corners2 = [from.point];
2067
- for (let m = Math.floor(from.s) + 1; m <= to.s - 1e-12 && m < pts.length; m++) if (m > from.s) corners2.push(pts[m]);
2068
- corners2.push(to.point);
2069
- const forward = polySegs(corners2, eps);
2070
- const back = polySegs(corners2.slice().reverse(), eps);
2071
- const la = face.loops[from.loop];
2072
- const loops = face.loops.filter((_, i) => i !== from.loop && i !== to.loop);
2073
- if (from.loop === to.loop) {
2074
- const ab = arc(la, from, to, eps);
2075
- const ba = arc(la, to, from, eps);
2076
- loops.push(
2077
- { segs: [...ab.segs, ...back], chord: [...ab.chord, ...back.map(() => true)] },
2078
- { segs: [...ba.segs, ...forward], chord: [...ba.chord, ...forward.map(() => true)] }
2079
- );
2080
- } else {
2081
- const lb = face.loops[to.loop];
2082
- const roundA = arc(la, from, from, eps);
2083
- const roundB = arc(lb, to, to, eps);
2084
- loops.push({
2085
- segs: [...roundA.segs, ...forward, ...roundB.segs, ...back],
2086
- chord: [...roundA.chord, ...forward.map(() => true), ...roundB.chord, ...back.map(() => true)]
2087
- });
2088
- }
2089
- faces = [...faces.slice(0, fi), ...groupFaces(loops), ...faces.slice(fi + 1)];
2090
- cursor = to.s;
2091
- cuts++;
2092
- }
2093
- return cuts ? faces.map(faceToPath) : null;
2094
- }
2095
- function eraserRegion(points, width) {
2096
- const pts = points.filter((p) => Number.isFinite(p[0]) && Number.isFinite(p[1]));
2097
- const r = width / 2;
2098
- if (!pts.length || !(r > 0)) return [];
2099
- const moved = pts.some((p) => !near(p, pts[0], 1e-9));
2100
- if (!moved) {
2101
- const [cx, cy] = pts[0];
2102
- const k = r * 0.5522847498307936;
2103
- return [
2104
- { type: "M", x: cx, y: cy - r },
2105
- { type: "C", x1: cx + k, y1: cy - r, x2: cx + r, y2: cy - k, x: cx + r, y: cy },
2106
- { type: "C", x1: cx + r, y1: cy + k, x2: cx + k, y2: cy + r, x: cx, y: cy + r },
2107
- { type: "C", x1: cx - k, y1: cy + r, x2: cx - r, y2: cy + k, x: cx - r, y: cy },
2108
- { type: "C", x1: cx - r, y1: cy - k, x2: cx - k, y2: cy - r, x: cx, y: cy - r },
2109
- { type: "Z" }
2110
- ];
2111
- }
2112
- const line2 = [{ type: "M", x: pts[0][0], y: pts[0][1] }, ...pts.slice(1).map((p) => ({ type: "L", x: p[0], y: p[1] }))];
2113
- return outlineStroke(line2, { width, cap: "round", join: "round" });
2114
- }
2115
- function eraseFromFill(path, fillRule, region) {
2116
- const overlap = pathBoolean("intersection", [{ path, fillRule }, { path: region }]).path;
2117
- if (!pathToContours(overlap).some((c) => Math.abs(contourSignedArea(closeContour(c, true))) > 1e-9)) return null;
2118
- const rest = pathBoolean("difference", [{ path, fillRule }, { path: region }]).path;
2119
- return splitIntoFaces(rest);
2120
- }
2121
- function eraseFromOutline(sp, region) {
2122
- const regionSegs = pathToContours(region).flatMap((c) => closeContour(c, true));
2123
- if (!regionSegs.length) return null;
2124
- const rb = bezierPathBounds(region);
2125
- const count = subpathSegmentCount(sp);
2126
- if (!rb || count === 0) return null;
2127
- const removed = (p) => pointInBezierPath(region, p, "nonzero");
2128
- const runs = [];
2129
- let cur = [];
2130
- let any = false;
2131
- for (let i = 0; i < count; i++) {
2132
- const seg = subpathSegment(sp, i);
2133
- const sb = segmentBounds(seg);
2134
- const ts = [0, 1];
2135
- if (!(sb.maxX < rb.minX || sb.minX > rb.maxX || sb.maxY < rb.minY || sb.minY > rb.maxY)) {
2136
- for (const r of regionSegs) {
2137
- const b = segmentBounds(r);
2138
- if (b.maxX < sb.minX || b.minX > sb.maxX || b.maxY < sb.minY || b.minY > sb.maxY) continue;
2139
- for (const hit of segmentIntersections(seg, r)) if (hit.ta > 1e-9 && hit.ta < 1 - 1e-9) ts.push(hit.ta);
2140
- }
2141
- }
2142
- ts.sort((a, b) => a - b);
2143
- for (let k = 0; k < ts.length - 1; k++) {
2144
- const t0 = ts[k];
2145
- const t1 = ts[k + 1];
2146
- if (t1 - t0 < 1e-9) continue;
2147
- if (removed(segmentPoint(seg, (t0 + t1) / 2))) {
2148
- any = true;
2149
- if (cur.length) runs.push(cur);
2150
- cur = [];
2151
- } else cur.push({ seg: i, t0, t1 });
2152
- }
2153
- }
2154
- if (cur.length) runs.push(cur);
2155
- if (!any) return null;
2156
- if (sp.closed && runs.length >= 2) {
2157
- const first = runs[0][0];
2158
- const lastRun = runs[runs.length - 1];
2159
- const last = lastRun[lastRun.length - 1];
2160
- if (first.seg === 0 && first.t0 === 0 && last.seg === count - 1 && last.t1 === 1) lastRun.push(...runs.shift());
2161
- }
2162
- const out = [];
2163
- for (const run of runs) {
2164
- const anchors = [];
2165
- for (const { seg, t0, t1 } of run) {
2166
- const c = subSegment(subpathSegment(sp, seg), t0, t1);
2167
- const line2 = c.length === 2;
2168
- const start = c[0];
2169
- const end = c[c.length - 1];
2170
- if (!anchors.length) anchors.push({ x: start[0], y: start[1] });
2171
- if (!line2) anchors[anchors.length - 1].out = { x: c[1][0], y: c[1][1] };
2172
- const next = { x: end[0], y: end[1] };
2173
- if (!line2) next.in = { x: c[2][0], y: c[2][1] };
2174
- anchors.push(next);
2175
- }
2176
- if (anchors.length < 2) continue;
2177
- for (const a of anchors) {
2178
- const type = inferAnchorType(a);
2179
- if (type !== "corner") a.type = type;
2180
- }
2181
- out.push({ anchors, closed: false });
2182
- }
2183
- return out;
2184
- }
2185
- function polylineBounds(points, pad = 0) {
2186
- if (!points.length) return null;
2187
- let minX = Infinity;
2188
- let minY = Infinity;
2189
- let maxX = -Infinity;
2190
- let maxY = -Infinity;
2191
- for (const [x, y] of points) {
2192
- minX = Math.min(minX, x);
2193
- minY = Math.min(minY, y);
2194
- maxX = Math.max(maxX, x);
2195
- maxY = Math.max(maxY, y);
2196
- }
2197
- return { x: minX - pad, y: minY - pad, width: maxX - minX + 2 * pad, height: maxY - minY + 2 * pad };
2198
- }
2199
- function simplifyPolyline(points, tolerance) {
2200
- const n = points.length;
2201
- if (n < 3) return points.slice();
2202
- const keep = new Array(n).fill(false);
2203
- keep[0] = keep[n - 1] = true;
2204
- const stack = [[0, n - 1]];
2205
- while (stack.length) {
2206
- const [a, b] = stack.pop();
2207
- const [ax, ay] = points[a];
2208
- const dx = points[b][0] - ax;
2209
- const dy = points[b][1] - ay;
2210
- const len = Math.hypot(dx, dy);
2211
- let far = -1;
2212
- let farD = 0;
2213
- for (let i = a + 1; i < b; i++) {
2214
- const [px, py] = points[i];
2215
- const d = len < 1e-12 ? Math.hypot(px - ax, py - ay) : Math.abs((px - ax) * dy - (py - ay) * dx) / len;
2216
- if (d > farD) {
2217
- farD = d;
2218
- far = i;
2219
- }
2220
- }
2221
- if (far >= 0 && farD > tolerance) {
2222
- keep[far] = true;
2223
- stack.push([a, far], [far, b]);
2224
- }
2225
- }
2226
- return points.filter((_, i) => keep[i]);
2227
- }
2228
-
2229
1913
  // src/vector/ink.ts
2230
1914
  var LazyMouse = class {
2231
1915
  /** `length` is the string length in the units of the points (0 turns smoothing off). */
@@ -2457,7 +2141,7 @@ function vectorPathEntries(doc, roots, options = {}) {
2457
2141
  const out = [];
2458
2142
  const visit = (stored, parent, inside) => {
2459
2143
  if (stored.visible === false || stored.locked && !options.locked) return;
2460
- const node = options.expand === false ? stored : expandVectorAppearance(stored);
2144
+ const node = options.expand === false ? stored : expandVectorAppearance(vectorMaskedGeometry(stored));
2461
2145
  const picked = inside || roots === null || roots.includes(node.id);
2462
2146
  if (node.type === "group") {
2463
2147
  const m = multiplyMatrices(nodeMatrix(node), parent);
@@ -4538,7 +4222,7 @@ var VectorEditorController = class {
4538
4222
  const drop = new Set(ids.filter((id) => this.getNode(id)));
4539
4223
  if (!drop.size) return { ok: false, message: "Nothing to delete." };
4540
4224
  this.change(options.label ?? "Delete", () => {
4541
- this.setDoc(mapTree(this.doc.nodes, (n) => drop.has(n.id) ? null : n));
4225
+ this.setDoc(mapTree(this.doc.nodes, (n) => drop.has(n.id) ? null : isVectorMaskGroup(n) && drop.has(n.children[0].id) ? { ...n, mask: null } : n));
4542
4226
  this.selection = this.selection.filter((id) => !drop.has(id) && this.getNode(id));
4543
4227
  this.anchors = this.anchors.filter((r) => getAnchor(this.doc, r));
4544
4228
  if (this.editingId && !this.getNode(this.editingId)) this.editingId = null;
@@ -5197,7 +4881,7 @@ var VectorEditorController = class {
5197
4881
  /** Whether stored node `rootId` holds `id` as drawn (`id` may be an expanded path inside it). */
5198
4882
  holds(rootId, id) {
5199
4883
  const n = this.getNode(rootId);
5200
- return !!n && (!!findVectorNode([n], id) || !!findVectorNode([expandVectorAppearance(n)], id));
4884
+ return !!n && (!!findVectorNode([n], id) || !!findVectorNode([expandVectorAppearance(n)], id) || !!findVectorNode([vectorMaskedGeometry(n)], id));
5201
4885
  }
5202
4886
  liveBooleanOp(op, roots) {
5203
4887
  const nodes = roots.map((id2) => this.getNode(id2));
@@ -5394,6 +5078,153 @@ var VectorEditorController = class {
5394
5078
  });
5395
5079
  return id;
5396
5080
  }
5081
+ // -------------------------------------------------------------------------
5082
+ // Masks
5083
+ // -------------------------------------------------------------------------
5084
+ /**
5085
+ * Make Clipping Mask (`clip`) / Make Opacity Mask (`alpha`, `luminance`): groups the selected (or
5086
+ * given) objects into a mask group whose mask is the topmost of them (or `maskId`), moved to the
5087
+ * bottom of the group. They must be two or more, unlocked, with one parent. One undo step.
5088
+ * Returns the group's id, or why it could not.
5089
+ */
5090
+ makeMask(type, options = {}) {
5091
+ if (!isVectorMaskType(type)) return { ok: false, message: `Unknown mask type "${type}": use clip, alpha or luminance.` };
5092
+ const ids = [...new Set(options.ids ?? this.selectedRoots())];
5093
+ if (ids.length < 2) return { ok: false, message: "Select at least two objects: the topmost becomes the mask, the ones below show through it." };
5094
+ for (const id2 of ids) {
5095
+ const n = this.getNode(id2);
5096
+ if (!n) return { ok: false, message: `There is no node "${id2}".` };
5097
+ if (n.locked) return { ok: false, message: `"${id2}" is locked; unlock it first.` };
5098
+ if (n.blendStep) return { ok: false, message: `"${id2}" is a blend step; select its blend instead.` };
5099
+ }
5100
+ const locs = ids.map((id2) => locateVectorNode(this.doc.nodes, id2));
5101
+ const parent = locs[0].parent?.id ?? null;
5102
+ if (locs.some((l) => (l.parent?.id ?? null) !== parent)) return { ok: false, message: "The objects must share a parent (group them, or pick objects at one level)." };
5103
+ const order = ids.slice().sort((a, b) => locs[ids.indexOf(a)].index - locs[ids.indexOf(b)].index);
5104
+ const maskId = options.maskId ?? order[order.length - 1];
5105
+ if (!order.includes(maskId)) return { ok: false, message: `The mask "${maskId}" must be one of the objects (${order.map((id2) => `"${id2}"`).join(", ")}).` };
5106
+ const id = this.newId();
5107
+ const mask = options.invert ? { type, invert: true } : { type };
5108
+ this.change(type === "clip" ? "Make Clipping Mask" : "Make Opacity Mask", () => {
5109
+ const set = new Set(order);
5110
+ const regroup = (list) => {
5111
+ const members = order.map((m) => list.find((n) => n.id === m));
5112
+ const top = Math.max(...members.map((m) => list.indexOf(m)));
5113
+ const children = [members.find((m) => m.id === maskId), ...members.filter((m) => m.id !== maskId)];
5114
+ const out = [];
5115
+ list.forEach((n, i) => {
5116
+ if (i === top) out.push({ id, type: "group", name: options.name ?? (type === "clip" ? "Clip Group" : "Opacity Mask"), mask, children });
5117
+ if (!set.has(n.id)) out.push(n);
5118
+ });
5119
+ return out;
5120
+ };
5121
+ if (parent === null) this.setDoc(regroup(this.doc.nodes));
5122
+ else this.setDoc(mapTree(this.doc.nodes, (n) => n.id === parent && n.type === "group" ? { ...n, children: regroup(n.children) } : n));
5123
+ this.selection = [id];
5124
+ this.anchors = [];
5125
+ this.editingId = null;
5126
+ });
5127
+ return { ok: true, id };
5128
+ }
5129
+ /** Mask groups among `ids` (default: the selection), or holding one of them directly. */
5130
+ maskTargets(ids) {
5131
+ const out = /* @__PURE__ */ new Map();
5132
+ for (const id of ids ?? this.selection) {
5133
+ const n = this.getNode(id);
5134
+ if (isVectorMaskGroup(n)) out.set(n.id, n);
5135
+ else {
5136
+ const parent = locateVectorNode(this.doc.nodes, id)?.parent;
5137
+ if (isVectorMaskGroup(parent)) out.set(parent.id, parent);
5138
+ }
5139
+ }
5140
+ return [...out.values()];
5141
+ }
5142
+ /**
5143
+ * Release: the selected (or given) mask groups stop masking. The mask goes back on top of the
5144
+ * contents, drawn as itself again. A group with appearance of its own (filters, a blend mode,
5145
+ * path effects) stays a plain group; otherwise it is replaced by its children. One undo step.
5146
+ * Returns the released nodes' ids (the mask last).
5147
+ */
5148
+ releaseMask(options = {}) {
5149
+ const groups = this.maskTargets(options.ids);
5150
+ if (!groups.length) return [];
5151
+ const released = [];
5152
+ this.change("Release Mask", () => {
5153
+ for (const g of groups) {
5154
+ const [mask, ...contents] = g.children;
5155
+ const children = [...contents, mask];
5156
+ released.push(...children.map((c) => c.id));
5157
+ const { mask: _m, ...rest } = g;
5158
+ const keep = !!(g.filters?.length || g.blendMode && g.blendMode !== "normal" || g.effects?.length);
5159
+ if (keep) {
5160
+ this.replaceNode(g.id, { ...rest, children });
5161
+ continue;
5162
+ }
5163
+ const m = nodeMatrix(g);
5164
+ const opacity = g.opacity;
5165
+ const flat = children.map((c) => g.transform ? transformVectorNode(c, m) : c).map((c) => opacity !== void 0 && opacity < 1 ? { ...c, opacity: (c.opacity ?? 1) * opacity } : c);
5166
+ this.setDoc(spliceTree(this.doc.nodes, g.id, flat));
5167
+ }
5168
+ this.selection = released;
5169
+ this.anchors = [];
5170
+ this.editingId = null;
5171
+ });
5172
+ return released;
5173
+ }
5174
+ /**
5175
+ * Changes the type or inversion of the selected (or given) mask groups, or turns their masks off
5176
+ * (`enabled: false` hides the mask, so the contents draw whole) and on. One undo step.
5177
+ */
5178
+ setMaskOptions(patch, options = {}) {
5179
+ if (patch.type !== void 0 && !isVectorMaskType(patch.type)) return { ok: false, message: `Unknown mask type "${patch.type}": use clip, alpha or luminance.` };
5180
+ if (patch.invert !== void 0 && typeof patch.invert !== "boolean") return { ok: false, message: "invert must be true or false." };
5181
+ if (patch.enabled !== void 0 && typeof patch.enabled !== "boolean") return { ok: false, message: "enabled must be true or false." };
5182
+ const groups = this.maskTargets(options.ids);
5183
+ if (!groups.length) return { ok: false, message: "Select a mask group (made with Make Clipping Mask or Make Opacity Mask)." };
5184
+ const set = new Set(groups.map((g) => g.id));
5185
+ this.change("Mask Options", () => {
5186
+ this.setDoc(
5187
+ mapTree(this.doc.nodes, (n) => {
5188
+ if (!set.has(n.id) || !isVectorMaskGroup(n)) return n;
5189
+ const type = patch.type ?? n.mask.type;
5190
+ const invert = patch.invert ?? !!n.mask.invert;
5191
+ const [source, ...rest] = n.children;
5192
+ const shown = patch.enabled === void 0 || patch.enabled === (source.visible !== false) ? source : patch.enabled ? (({ visible: _v, ...r }) => r)(source) : { ...source, visible: false };
5193
+ return { ...n, mask: invert ? { type, invert } : { type }, ...shown !== source ? { children: [shown, ...rest] } : {} };
5194
+ })
5195
+ );
5196
+ });
5197
+ return { ok: true };
5198
+ }
5199
+ /**
5200
+ * Edit Mask: selects the mask of the selected (or given) mask group, or of the group holding the
5201
+ * selection. A path mask opens in the direct tool for anchor editing (`editPath: false` keeps the
5202
+ * tool). Returns the mask's id, or null.
5203
+ */
5204
+ editMask(options = {}) {
5205
+ const group = this.maskTargets(options.id ? [options.id] : void 0)[0];
5206
+ if (!group) return null;
5207
+ const mask = group.children[0];
5208
+ this.selection = [mask.id];
5209
+ this.anchors = [];
5210
+ this.editingId = null;
5211
+ if (mask.type === "path" && options.editPath !== false && !mask.locked) {
5212
+ this.tool = "direct";
5213
+ this.editingId = mask.id;
5214
+ }
5215
+ this.notify();
5216
+ return mask.id;
5217
+ }
5218
+ /** Edit Contents: selects what the selected (or given) mask group masks. Returns their ids. */
5219
+ editMaskContents(options = {}) {
5220
+ const group = this.maskTargets(options.id ? [options.id] : void 0)[0];
5221
+ if (!group) return [];
5222
+ this.selection = group.children.slice(1).map((c) => c.id);
5223
+ this.anchors = [];
5224
+ this.editingId = null;
5225
+ this.notify();
5226
+ return [...this.selection];
5227
+ }
5397
5228
  /** Sets the options new blends get (spacing, steps, distance, orientation, morph). */
5398
5229
  setBlendDefaults(patch) {
5399
5230
  this.blendDefaults = { ...this.blendDefaults, ...blendOptionsOf(patch) };
@@ -6018,11 +5849,11 @@ var VectorEditorController = class {
6018
5849
  if (!hit) return null;
6019
5850
  const sp = node.subpaths[hit.subpath];
6020
5851
  const n = sp.anchors.length;
6021
- const near2 = [hit.segment, (hit.segment + 1) % n].find((i) => Math.hypot(sp.anchors[i].x - hit.point.x, sp.anchors[i].y - hit.point.y) <= this.tolerance());
6022
- if (!sp.closed && n > 1 && (near2 === 0 || near2 === n - 1) && !(hit.t > 1e-6 && hit.t < 1 - 1e-6)) return null;
5852
+ const near = [hit.segment, (hit.segment + 1) % n].find((i) => Math.hypot(sp.anchors[i].x - hit.point.x, sp.anchors[i].y - hit.point.y) <= this.tolerance());
5853
+ if (!sp.closed && n > 1 && (near === 0 || near === n - 1) && !(hit.t > 1e-6 && hit.t < 1 - 1e-6)) return null;
6023
5854
  let ids = null;
6024
5855
  this.change("Cut Path", () => {
6025
- ids = this.applyCuts(node, (s, i) => i !== hit.subpath ? null : near2 !== void 0 ? cutSubpathAt(s, [near2]) : splitSubpathAt(s, hit.segment, hit.t));
5856
+ ids = this.applyCuts(node, (s, i) => i !== hit.subpath ? null : near !== void 0 ? cutSubpathAt(s, [near]) : splitSubpathAt(s, hit.segment, hit.t));
6026
5857
  if (ids) {
6027
5858
  this.selection = ids;
6028
5859
  this.anchors = [];
@@ -6306,7 +6137,7 @@ var VectorEditorController = class {
6306
6137
  const n = list[i];
6307
6138
  if (n.visible === false || n.locked) continue;
6308
6139
  if (n.type === "group") {
6309
- if (hasOwnVectorAppearance(n) && !entered.has(n.id) && !hitTestVectorNode(n, p, tol, parent, { entered })) continue;
6140
+ if ((hasOwnVectorAppearance(n) || isVectorMaskActive(n)) && !entered.has(n.id) && !hitTestVectorNode(n, p, tol, parent, { entered })) continue;
6310
6141
  const found = search(n.children, multiplyMatrices(nodeMatrix(n), parent));
6311
6142
  if (found) return found;
6312
6143
  } else if (hitTestVectorNode(n, p, tol, parent, { entered })) return n;
@@ -6428,24 +6259,24 @@ var VectorEditorController = class {
6428
6259
  if (!g) return;
6429
6260
  const m = this.worldOf(n.id);
6430
6261
  const local = applyMatrix(invertMatrix(m), p);
6431
- const near2 = nearestStrokePosition(g.line, [local.x, local.y]);
6432
- if (!near2) return;
6262
+ const near = nearestStrokePosition(g.line, [local.x, local.y]);
6263
+ if (!near) return;
6433
6264
  const profile = strokeWidthProfileOf(n);
6434
- const w = widthProfileAt(profile, near2.t);
6435
- const reach = Math.max(near2.side === "left" ? w.left : w.right, 0) * g.unit + tol;
6436
- if (near2.distance > reach || near2.distance > widthProfileMaxScale(profile) * g.unit + tol) return;
6437
- if (best && near2.distance >= best.distance) return;
6438
- const nx = near2.tangent[1];
6439
- const ny = -near2.tangent[0];
6440
- const [cx, cy] = near2.point;
6265
+ const w = widthProfileAt(profile, near.t);
6266
+ const reach = Math.max(near.side === "left" ? w.left : w.right, 0) * g.unit + tol;
6267
+ if (near.distance > reach || near.distance > widthProfileMaxScale(profile) * g.unit + tol) return;
6268
+ if (best && near.distance >= best.distance) return;
6269
+ const nx = near.tangent[1];
6270
+ const ny = -near.tangent[0];
6271
+ const [cx, cy] = near.point;
6441
6272
  best = {
6442
6273
  nodeId: n.id,
6443
- subpath: near2.contour,
6444
- t: near2.t,
6274
+ subpath: near.contour,
6275
+ t: near.t,
6445
6276
  point: applyMatrix(m, { x: cx, y: cy }),
6446
6277
  left: applyMatrix(m, { x: cx + nx * w.left * g.unit, y: cy + ny * w.left * g.unit }),
6447
6278
  right: applyMatrix(m, { x: cx - nx * w.right * g.unit, y: cy - ny * w.right * g.unit }),
6448
- distance: near2.distance
6279
+ distance: near.distance
6449
6280
  };
6450
6281
  });
6451
6282
  if (!best) return null;
@@ -6494,13 +6325,13 @@ var VectorEditorController = class {
6494
6325
  } else if (mode === "copy" || !selected) this.widthPoints = [ref];
6495
6326
  if (mode !== "copy") group = this.widthPoints.filter((r) => r.index !== ref.index).map((r) => r.index);
6496
6327
  } else {
6497
- const near2 = nearestStrokePosition(g.line, [local.x, local.y]);
6498
- const inserted = insertWidthPoint(strokeWidthProfileOf(node), near2.t);
6328
+ const near = nearestStrokePosition(g.line, [local.x, local.y]);
6329
+ const inserted = insertWidthPoint(strokeWidthProfileOf(node), near.t);
6499
6330
  profile = inserted.points;
6500
- subpath = near2.contour;
6501
- t = near2.t;
6331
+ subpath = near.contour;
6332
+ t = near.t;
6502
6333
  ref = { nodeId: node.id, index: inserted.index, subpath };
6503
- if (input.alt) side = near2.side;
6334
+ if (input.alt) side = near.side;
6504
6335
  this.widthPoints = [ref];
6505
6336
  }
6506
6337
  const at = strokePositionPoint(g.line, subpath, t);
@@ -7095,9 +6926,9 @@ var VectorEditorController = class {
7095
6926
  let index = drag.source;
7096
6927
  if (drag.mode !== "width") {
7097
6928
  const g = vectorStrokeWidthGeometry(node);
7098
- const near2 = g ? nearestStrokePosition(g.line, [local.x, local.y], drag.ref.subpath) : null;
7099
- if (!g || !near2) return;
7100
- const t = drag.group.length ? near2.t : this.snapWidthT(drag, node, g.line, near2.t, drag.source);
6929
+ const near = g ? nearestStrokePosition(g.line, [local.x, local.y], drag.ref.subpath) : null;
6930
+ if (!g || !near) return;
6931
+ const t = drag.group.length ? near.t : this.snapWidthT(drag, node, g.line, near.t, drag.source);
7101
6932
  if (drag.mode === "copy") {
7102
6933
  ({ points: profile, index } = insertWidthPoint(drag.profile, t, { left: src.left, right: src.right }, { step: true }));
7103
6934
  } else if (drag.group.length) {
@@ -7456,6 +7287,10 @@ var VectorEditorController = class {
7456
7287
  return true;
7457
7288
  case "j":
7458
7289
  return input.alt ? this.averageAnchors("both").ok : this.joinPaths().ok;
7290
+ // Make Clipping Mask; with Alt, Release (Option-7 types ¶ on a Mac).
7291
+ case "7":
7292
+ case "\xB6":
7293
+ return input.alt || key === "\xB6" ? this.releaseMask().length > 0 : this.makeMask("clip").ok;
7459
7294
  case "]":
7460
7295
  this.arrange(input.alt ? "front" : "forward");
7461
7296
  return true;
@@ -7663,13 +7498,17 @@ function renderNode(stored, defs, filters, idPrefix) {
7663
7498
  if (stored.visible === false) return null;
7664
7499
  const node = expandVectorAppearance(stored);
7665
7500
  if (node.type === "group") {
7666
- const children = node.children.map((c) => renderNode(c, defs, filters, idPrefix)).filter((c) => !!c);
7501
+ const finish2 = nodeFinish(node, filters, idPrefix);
7502
+ const masked = isVectorMaskGroup(node) ? renderMask(node, defs, filters, idPrefix) : null;
7503
+ const list = isVectorMaskGroup(node) ? node.children.slice(1) : node.children;
7504
+ const children = list.map((c) => renderNode(c, defs, filters, idPrefix)).filter((c) => !!c);
7667
7505
  return {
7668
7506
  kind: "group",
7669
7507
  id: node.id,
7670
7508
  ...node.transform ? { transform: fmtMatrix(node.transform) } : {},
7671
7509
  opacity: node.opacity ?? 1,
7672
- ...nodeFinish(node, filters, idPrefix),
7510
+ ...finish2,
7511
+ ...masked ? { mask: masked } : {},
7673
7512
  children
7674
7513
  };
7675
7514
  }
@@ -7711,6 +7550,16 @@ function renderNode(stored, defs, filters, idPrefix) {
7711
7550
  ...lineStyle(node)
7712
7551
  };
7713
7552
  }
7553
+ function renderMask(group, defs, filters, idPrefix) {
7554
+ if (!isVectorMaskActive(group)) return null;
7555
+ const data = vectorMaskRenderData(group, `${idPrefix}mask-${group.id}`, `${idPrefix}mask-filter-${group.id}`);
7556
+ if (!data) return null;
7557
+ const { invertFilter, ...mask } = data;
7558
+ if (invertFilter) filters.push(invertFilter);
7559
+ if (mask.type === "clip") return mask;
7560
+ const source = renderNode(group.children[0], defs, filters, idPrefix);
7561
+ return { ...mask, nodes: source ? [source] : [] };
7562
+ }
7714
7563
  function getVectorSceneRenderData(state, options = {}) {
7715
7564
  const defs = [];
7716
7565
  const filters = [];
@@ -7737,7 +7586,7 @@ function screenOutline(state, node, entered) {
7737
7586
  function outlinePaths(node, parent, entered) {
7738
7587
  if (node.type === "path") return [vectorPathWorldBezier(node, parent, { effects: false })];
7739
7588
  const m = multiplyMatrices(nodeMatrix(node), parent);
7740
- if (hasOwnVectorAppearance(node) && !entered.has(node.id)) return [transformBezierPath(vectorAppearanceOutline(node), m)];
7589
+ if ((hasOwnVectorAppearance(node) || isVectorMaskActive(node)) && !entered.has(node.id)) return [transformBezierPath(vectorAppearanceOutline(node), m)];
7741
7590
  return node.children.filter((c) => c.visible !== false).flatMap((c) => outlinePaths(c, m, entered));
7742
7591
  }
7743
7592
  function getVectorEnteredGroups(state) {
@@ -7901,7 +7750,35 @@ function getVectorOverlayRenderData(state) {
7901
7750
  }
7902
7751
  const envelope = state.tool === "envelope" ? envelopeOverlay(state) : null;
7903
7752
  if (state.tool === "envelope" && !envelope && !it) status = "Select an object with an envelope to edit its mesh";
7904
- return { outlines, selectionBox, distort, anchors, handles, marquee, penPreview, artboard, guides, status, toolStroke, pencilString, cutPoint, cornerWidgets, widthPoints, widthSpot, builder, blend, gradient, envelope };
7753
+ return { outlines, selectionBox, distort, anchors, handles, marquee, penPreview, artboard, guides, status, toolStroke, pencilString, cutPoint, cornerWidgets, widthPoints, widthSpot, builder, blend, gradient, envelope, masks: maskOverlay(state) };
7754
+ }
7755
+ function maskOverlay(state) {
7756
+ const ids = state.editingId ? [...state.selection, state.editingId] : state.selection;
7757
+ if (!ids.length) return [];
7758
+ const want = new Set(ids);
7759
+ const v = state.viewport;
7760
+ const view = [v.zoom, 0, 0, v.zoom, v.x, v.y];
7761
+ const out = [];
7762
+ const visit = (list, parent) => {
7763
+ let any = false;
7764
+ for (const n of list) {
7765
+ if (n.type !== "group") {
7766
+ if (want.has(n.id)) any = true;
7767
+ continue;
7768
+ }
7769
+ const m = multiplyMatrices(nodeMatrix(n), parent);
7770
+ const inside = visit(n.children, m);
7771
+ const hit = want.has(n.id) || inside;
7772
+ if (hit) any = true;
7773
+ if (!hit || !isVectorMaskGroup(n)) continue;
7774
+ const summary = vectorMaskSummary(n);
7775
+ const d = outlinePaths(n.children[0], m, /* @__PURE__ */ new Set()).map((path) => bezierPathToSvg(transformBezierPath(path, view), { precision: 2 })).join(" ");
7776
+ out.push({ id: n.id, maskId: summary.maskId, d, type: summary.type, invert: summary.invert, enabled: summary.enabled, editing: want.has(summary.maskId) });
7777
+ }
7778
+ return any;
7779
+ };
7780
+ visit(state.document.nodes, IDENTITY_MATRIX);
7781
+ return out;
7905
7782
  }
7906
7783
  function envelopeOverlay(state) {
7907
7784
  const target = getVectorEnvelopeTarget(state.document, state.selection);
@@ -8069,7 +7946,10 @@ var VECTOR_OVERLAY_THEME = {
8069
7946
  builderErase: "#ef4444",
8070
7947
  builderEraseFill: "rgba(239, 68, 68, 0.22)",
8071
7948
  /** Opacity of the hatch lines over Shape Builder faces (drawn in the face's outline colour). */
8072
- builderHatchOpacity: 0.6
7949
+ builderHatchOpacity: 0.6,
7950
+ /** Mask indicators: the outline of a selected mask group's mask (dashed; solid while the mask is selected). */
7951
+ mask: "#8b5cf6",
7952
+ maskDash: "5 3"
8073
7953
  };
8074
7954
  function vectorDiamondPath(x, y, r = VECTOR_OVERLAY_THEME.widthPointSize) {
8075
7955
  return `M${fmt2(x)} ${fmt2(y - r)}L${fmt2(x + r)} ${fmt2(y)}L${fmt2(x)} ${fmt2(y + r)}L${fmt2(x - r)} ${fmt2(y)}Z`;
@@ -8318,6 +8198,32 @@ function getVectorAppearancePanel(state) {
8318
8198
  };
8319
8199
  }
8320
8200
  var VECTOR_BLEND_MODE_MENU = VECTOR_BLEND_MODES.map((mode) => ({ mode, label: VECTOR_BLEND_MODE_LABELS[mode] }));
8201
+ var VECTOR_MASK_TYPE_LABELS = { clip: "Clip", alpha: "Alpha", luminance: "Luminance" };
8202
+ var VECTOR_MASK_TYPE_OPTIONS = [
8203
+ { value: "clip", label: "Clip", title: "Clipping mask: the contents show inside the mask's outline" },
8204
+ { value: "luminance", label: "Luminance", title: "Opacity mask by brightness: white shows, black hides" },
8205
+ { value: "alpha", label: "Alpha", title: "Opacity mask by opacity: opaque shows, transparent hides" }
8206
+ ];
8207
+ function getVectorMaskPanel(state) {
8208
+ const doc = state.document;
8209
+ if (!state.selection.length) return null;
8210
+ const locs = state.selection.map((id) => locateVectorNode(doc.nodes, id));
8211
+ let makeHint = null;
8212
+ if (state.selection.length < 2) makeHint = "Select two or more objects: the topmost becomes the mask.";
8213
+ else if (locs.some((l) => !l)) makeHint = "Select objects in the document.";
8214
+ else if (locs.some((l) => l.list[l.index].locked || l.list[l.index].blendStep)) makeHint = "Unlock the selected objects first.";
8215
+ else if (locs.some((l) => (l.parent?.id ?? null) !== (locs[0].parent?.id ?? null))) makeHint = "Select objects at one level (sharing a parent).";
8216
+ let group = null;
8217
+ const first = locs[0];
8218
+ const node = first ? first.list[first.index] : null;
8219
+ const owner = isVectorMaskGroup(node) ? node : isVectorMaskGroup(first?.parent) ? first.parent : null;
8220
+ if (owner && state.selection.every((id) => id === owner.id || owner.children.some((c) => c.id === id))) {
8221
+ const summary = vectorMaskSummary(owner);
8222
+ const editing = state.selection.includes(owner.id) ? "group" : state.selection.includes(summary.maskId) ? "mask" : "contents";
8223
+ group = { ...summary, id: owner.id, ...owner.name ? { name: owner.name } : {}, editing };
8224
+ }
8225
+ return { canMake: makeHint === null, makeHint, group };
8226
+ }
8321
8227
 
8322
8228
  // src/vector/commands.ts
8323
8229
  var EFFECT_PARAMS_DESCRIPTION = "Effect parameters (missing ones keep their value or default). trim: start, end (percent), offset (degrees), mode simultaneous|individual. roundCorners: radius. puckerBloat: amount (\u2212100\u2026100). twist: angle, center {x, y} (offset from the middle). zigzag: size, ridges, smooth. wiggle: size, detail, smooth, speed, correlation, phase, seed, time (seconds). offset: amount, join miter|round|bevel, miterLimit, copies, copyOffset. merge: mode merge|add|subtract|intersect|exclude. repeater: copies (fractions fade the last), offset, position {x, y}, anchor {x, y} (offset from the middle), rotation, scale {x, y} (percent), startOpacity, endOpacity (percent), composite below|above. envelope: style (a warp preset: arc|arcLower|arcUpper|arch|bulge|shellLower|shellUpper|flag|wave|fish|rise|fisheye|inflate|squeeze|twist, or mesh), bend, horizontal, vertical (percent, \u2212100\u2026100), orientation horizontal|vertical, rows, columns, mesh { us, vs, points } (node space; prefer vector.envelope).";
@@ -8504,7 +8410,8 @@ function createVectorCommands(controller, options = {}) {
8504
8410
  ...n.type === "path" && Array.isArray(n.fills) ? { fills: n.fills } : {},
8505
8411
  ...n.type === "path" && Array.isArray(n.strokes) ? { strokes: n.strokes } : {},
8506
8412
  ...n.blendMode && n.blendMode !== "normal" ? { blendMode: n.blendMode } : {},
8507
- ...n.filters?.length ? { filters: n.filters } : {}
8413
+ ...n.filters?.length ? { filters: n.filters } : {},
8414
+ ...isVectorMaskGroup(n) ? { mask: vectorMaskSummary(n) } : {}
8508
8415
  });
8509
8416
  if (n.type === "group") visit(n.children, depth + 1);
8510
8417
  }
@@ -8652,6 +8559,105 @@ function createVectorCommands(controller, options = {}) {
8652
8559
  const node = controller.getNode(nodeId) ?? fail(`There is no node "${nodeId}".`);
8653
8560
  return node.filters?.find((f) => f.id === filterId) ?? fail(`Node "${nodeId}" has no filter "${filterId}"; it has ${(node.filters ?? []).map((f) => `"${f.id}" (${f.type})`).join(", ") || "none"}.`);
8654
8561
  };
8562
+ const maskResult = (id) => ({ id, mask: vectorMaskSummary(controller.getNode(id)) ?? fail(`"${id}" is not a mask group.`) });
8563
+ const maskInputs = (params) => {
8564
+ const list2 = [...new Set(ids(params))];
8565
+ if (list2.length < 2) fail(`Masking needs at least two objects (the mask and what it masks); got ${list2.length}. Pass ids, or select more.`);
8566
+ for (const id of list2) if (controller.getNode(id).locked) fail(`"${id}" is locked; ask the user to unlock it, or leave it out of ids.`);
8567
+ if (params?.maskId !== void 0 && !list2.includes(params.maskId)) fail(`maskId "${params.maskId}" must be one of ids (${list2.map((id) => `"${id}"`).join(", ")}).`);
8568
+ return list2;
8569
+ };
8570
+ const maskTargets = (params) => {
8571
+ const list2 = ids(params);
8572
+ const groups = list2.map((id) => controller.getNode(id)).filter((n) => isVectorMaskGroup(n));
8573
+ if (!groups.length) fail(`None of ${list2.map((id) => `"${id}"`).join(", ")} is a mask group. Mask groups have a \`mask\` in vector.list; make one with vector.makeClippingMask or vector.makeOpacityMask.`);
8574
+ return groups.map((g) => g.id);
8575
+ };
8576
+ const MASK_INPUT = {
8577
+ ids: { ...IDS, description: "The objects to mask with, two or more sharing a parent (default: the selection). The topmost becomes the mask unless maskId says otherwise." },
8578
+ maskId: { type: "string", description: "Which of ids is the mask (default: the topmost)." },
8579
+ invert: { type: "boolean", description: "Show the contents only where the mask hides them (default false)." }
8580
+ };
8581
+ const maskCommands = () => [
8582
+ {
8583
+ ...base,
8584
+ id: `${prefix}makeClippingMask`,
8585
+ label: "Make Clipping Mask",
8586
+ keywords: ["mask", "clip", "clipping", "clip path", "crop", "inside"],
8587
+ shortcut: "Mod+7",
8588
+ description: "Groups objects into a clipping mask group: the mask (the topmost object, or maskId) moves to the bottom of the new group and the others show only inside its outline (its paint is not drawn; every path in it counts, by its fill rule). Hit-testing, bounds, snapping and booleans use the clipped result. One undo step. Returns { id, mask: { type, invert, maskId, contents, enabled } }.",
8589
+ params: { type: "object", properties: MASK_INPUT, additionalProperties: false },
8590
+ undoable: true,
8591
+ enabled: enabledFor,
8592
+ run: (_ctx, params) => {
8593
+ const list2 = maskInputs(params);
8594
+ const r = controller.makeMask("clip", { ids: list2, ...params?.maskId ? { maskId: params.maskId } : {}, invert: params?.invert });
8595
+ if (!r.ok) fail(r.message);
8596
+ return maskResult(r.id);
8597
+ }
8598
+ },
8599
+ {
8600
+ ...base,
8601
+ id: `${prefix}makeOpacityMask`,
8602
+ label: "Make Opacity Mask",
8603
+ keywords: ["mask", "opacity mask", "alpha", "luminance", "fade", "gradient mask", "soft mask"],
8604
+ description: "Groups objects into an opacity mask group: the mask (the topmost object, or maskId) moves to the bottom of the new group and hides the others where it is dark (luminance: white shows, black hides) or transparent (alpha). A gradient mask fades the contents. One undo step. Returns { id, mask: { type, invert, maskId, contents, enabled } }.",
8605
+ params: {
8606
+ type: "object",
8607
+ properties: { ...MASK_INPUT, type: { type: "string", enum: ["luminance", "alpha"], description: "luminance (by brightness, the default) or alpha (by opacity)." } },
8608
+ additionalProperties: false
8609
+ },
8610
+ undoable: true,
8611
+ enabled: enabledFor,
8612
+ run: (_ctx, params) => {
8613
+ const list2 = maskInputs(params);
8614
+ const r = controller.makeMask(params?.type ?? "luminance", { ids: list2, ...params?.maskId ? { maskId: params.maskId } : {}, invert: params?.invert });
8615
+ if (!r.ok) fail(r.message);
8616
+ return maskResult(r.id);
8617
+ }
8618
+ },
8619
+ {
8620
+ ...base,
8621
+ id: `${prefix}setMaskOptions`,
8622
+ label: "Mask Options\u2026",
8623
+ keywords: ["mask", "clip", "invert", "alpha", "luminance", "opacity mask"],
8624
+ description: "Changes how mask groups mask: `type` clip (by outline), alpha or luminance, `invert`, and `enabled` (false hides the mask so the contents draw whole; true shows it again). Pass at least one. One undo step. Returns { groups: [{ id, mask }] }.",
8625
+ params: {
8626
+ type: "object",
8627
+ properties: {
8628
+ ids: { ...IDS, description: "Mask group ids (default: the selection)." },
8629
+ type: { type: "string", enum: ["clip", "alpha", "luminance"], description: "clip (the mask's outline), alpha (its opacity) or luminance (its brightness)." },
8630
+ invert: { type: "boolean", description: "true: show the contents where the mask hides them." },
8631
+ enabled: { type: "boolean", description: "false: turn the mask off (it is hidden and the contents draw whole); true: on again." }
8632
+ },
8633
+ additionalProperties: false
8634
+ },
8635
+ undoable: true,
8636
+ enabled: enabledFor,
8637
+ run: (_ctx, params) => {
8638
+ if (params?.type === void 0 && params?.invert === void 0 && params?.enabled === void 0) fail("Pass at least one of type, invert, enabled.");
8639
+ const groups = maskTargets(params);
8640
+ const patch = { ...params?.type ? { type: params.type } : {}, ...params?.invert !== void 0 ? { invert: params.invert } : {}, ...params?.enabled !== void 0 ? { enabled: params.enabled } : {} };
8641
+ check(controller.setMaskOptions(patch, { ids: groups }));
8642
+ return { groups: groups.map(maskResult) };
8643
+ }
8644
+ },
8645
+ {
8646
+ ...base,
8647
+ id: `${prefix}releaseMask`,
8648
+ label: "Release Mask",
8649
+ keywords: ["mask", "clip", "release", "unmask", "remove mask"],
8650
+ shortcut: "Mod+Alt+7",
8651
+ description: "Releases mask groups: the mask goes back on top of the contents and draws as itself again; the group is replaced by its children unless it has filters, a blend mode or path effects (then it stays a plain group). One undo step. Returns { ids } of the released objects (the mask last).",
8652
+ params: { type: "object", properties: { ids: { ...IDS, description: "Mask group ids (default: the selection)." } }, additionalProperties: false },
8653
+ undoable: true,
8654
+ enabled: enabledFor,
8655
+ run: (_ctx, params) => {
8656
+ const groups = maskTargets(params);
8657
+ return { ids: controller.releaseMask({ ids: groups }) };
8658
+ }
8659
+ }
8660
+ ];
8655
8661
  const appearanceCommands = () => [
8656
8662
  {
8657
8663
  ...base,
@@ -8888,6 +8894,7 @@ function createVectorCommands(controller, options = {}) {
8888
8894
  }
8889
8895
  },
8890
8896
  ...appearanceCommands(),
8897
+ ...maskCommands(),
8891
8898
  {
8892
8899
  ...base,
8893
8900
  id: `${prefix}setCorners`,
@@ -9709,6 +9716,9 @@ export {
9709
9716
  VECTOR_GRADIENT_TYPES,
9710
9717
  VECTOR_GRADIENT_TYPE_LABELS,
9711
9718
  VECTOR_INK_MAX_CORNERS,
9719
+ VECTOR_MASK_TYPES,
9720
+ VECTOR_MASK_TYPE_LABELS,
9721
+ VECTOR_MASK_TYPE_OPTIONS,
9712
9722
  VECTOR_MERGE_MODES,
9713
9723
  VECTOR_OVERLAY_THEME,
9714
9724
  VECTOR_PATHFINDER_ACTIONS,
@@ -9814,6 +9824,7 @@ export {
9814
9824
  getVectorEnvelopeTarget,
9815
9825
  getVectorGradientAnnotator,
9816
9826
  getVectorGradientEditor,
9827
+ getVectorMaskPanel,
9817
9828
  getVectorOverlayRenderData,
9818
9829
  getVectorSceneRenderData,
9819
9830
  getVectorSelectionBoxGeometry,
@@ -9860,6 +9871,9 @@ export {
9860
9871
  isVectorFilterType,
9861
9872
  isVectorGradient,
9862
9873
  isVectorLiveBoolean,
9874
+ isVectorMaskActive,
9875
+ isVectorMaskGroup,
9876
+ isVectorMaskType,
9863
9877
  isVectorTextTarget,
9864
9878
  joinSubpaths,
9865
9879
  knifePath,
@@ -9937,6 +9951,7 @@ export {
9937
9951
  sanitizeVectorFills,
9938
9952
  sanitizeVectorFilters,
9939
9953
  sanitizeVectorGradient,
9954
+ sanitizeVectorMask,
9940
9955
  sanitizeVectorStrokeSettings,
9941
9956
  sanitizeVectorStrokes,
9942
9957
  sanitizeWidthProfile,
@@ -9997,6 +10012,7 @@ export {
9997
10012
  vectorBoxCorners,
9998
10013
  vectorBoxMatrix,
9999
10014
  vectorBuilderErasedEdges,
10015
+ vectorChildAppearanceShapes,
10000
10016
  vectorCornerAt,
10001
10017
  vectorCorners,
10002
10018
  vectorCornersAttribute,
@@ -10036,6 +10052,19 @@ export {
10036
10052
  identityWarpMesh as vectorIdentityMesh,
10037
10053
  vectorInkShapeName,
10038
10054
  vectorLiveBooleanSteps,
10055
+ vectorMaskContains,
10056
+ vectorMaskContentBounds,
10057
+ vectorMaskContents,
10058
+ vectorMaskOwner,
10059
+ vectorMaskRegion,
10060
+ vectorMaskRegionPath,
10061
+ vectorMaskRegionShapes,
10062
+ vectorMaskRenderData,
10063
+ vectorMaskSource,
10064
+ vectorMaskSummary,
10065
+ vectorMaskedGeometry,
10066
+ vectorMaskedLocalBounds,
10067
+ vectorMaskedShapes,
10039
10068
  warpMeshColumns as vectorMeshColumns,
10040
10069
  warpMeshControlBounds as vectorMeshControlBounds,
10041
10070
  warpMeshEditPoints as vectorMeshEditPoints,