@pixerate/editor 0.212.0 → 0.213.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.
@@ -1,8 +1,10 @@
1
1
  import {
2
+ bendSegment,
2
3
  loadOpenTypeFont,
3
4
  parseOpenTypeFont,
5
+ removeAnchorsKeepingShape,
4
6
  traceField
5
- } from "../chunk-5OC22IEO.js";
7
+ } from "../chunk-DJBYJOHM.js";
6
8
  import {
7
9
  ColorGmm,
8
10
  DEFAULT_REFINE_EDGE_PARAMS,
@@ -668,6 +670,8 @@ import {
668
670
  applyMatrix,
669
671
  bezierToSubpaths,
670
672
  caretAt,
673
+ cloneAnchor,
674
+ convertAnchor,
671
675
  createCanvasTextShaper,
672
676
  createMetricTextShaper,
673
677
  createOutlineTextShaper,
@@ -679,23 +683,30 @@ import {
679
683
  fitRuns,
680
684
  fontOf,
681
685
  identityWarpMesh,
686
+ inferAnchorType,
687
+ insertAnchor,
682
688
  invertMatrix,
683
689
  layoutText,
684
690
  lineToTextSpace,
691
+ moveAnchorHandle,
685
692
  multiplyMatrices,
686
693
  paragraphRanges,
687
694
  pullWarpMesh,
695
+ removeAnchors,
688
696
  resizeWarpMesh,
689
697
  restyleRange,
690
698
  rotateMatrix,
691
699
  roundSubpaths,
692
700
  spliceRuns,
693
701
  styleAt,
702
+ subpathSegment,
703
+ subpathSegmentCount,
694
704
  subpathsToBezier,
695
705
  textArrowStep,
696
706
  textIndexAt,
697
707
  textLineMove,
698
708
  textSelectionQuads,
709
+ translateAnchor,
699
710
  translateMatrix,
700
711
  warpMeshColumns,
701
712
  warpMeshEditPoints,
@@ -707,7 +718,9 @@ import {
707
718
  import "../chunk-F7YA3MMC.js";
708
719
  import "../chunk-M6DNZNVY.js";
709
720
  import {
710
- bezierPathToSvg
721
+ bezierPathToSvg,
722
+ pointInBezierPath,
723
+ segmentNearest
711
724
  } from "../chunk-WEIK3UYB.js";
712
725
  import "../chunk-PWBZH3FK.js";
713
726
  import {
@@ -1175,10 +1188,10 @@ function emptyHistogram() {
1175
1188
  max: 0
1176
1189
  };
1177
1190
  }
1178
- function addToHistogram(h, data, stride, rect) {
1179
- for (let y = rect.y; y < rect.y + rect.height; y++) {
1180
- let i = (y * stride + rect.x) * 4;
1181
- for (let x = 0; x < rect.width; x++, i += 4) {
1191
+ function addToHistogram(h, data, stride, rect2) {
1192
+ for (let y = rect2.y; y < rect2.y + rect2.height; y++) {
1193
+ let i = (y * stride + rect2.x) * 4;
1194
+ for (let x = 0; x < rect2.width; x++, i += 4) {
1182
1195
  if (!(data[i + 3] > 0)) {
1183
1196
  h.transparent++;
1184
1197
  continue;
@@ -1639,6 +1652,63 @@ var ImageEditorRenderer = class {
1639
1652
  }
1640
1653
  ctx.restore();
1641
1654
  }
1655
+ if (options.pathEdit) {
1656
+ const e = options.pathEdit;
1657
+ ctx.save();
1658
+ try {
1659
+ const scr = (p) => projectImagePoint(p, projection);
1660
+ const trace2 = (c) => {
1661
+ const [a, b, d, f] = c.map(scr);
1662
+ ctx.moveTo(a.x, a.y);
1663
+ if (f) ctx.bezierCurveTo(b.x, b.y, d.x, d.y, f.x, f.y);
1664
+ else ctx.lineTo(b.x, b.y);
1665
+ };
1666
+ ctx.beginPath();
1667
+ e.curves.forEach(trace2);
1668
+ ctx.lineWidth = 1.5 * u;
1669
+ ctx.strokeStyle = OVERLAY_HANDLE_EDGE;
1670
+ ctx.stroke();
1671
+ if (e.preview) {
1672
+ ctx.beginPath();
1673
+ trace2(e.preview);
1674
+ ctx.setLineDash([4 * u, 3 * u]);
1675
+ ctx.stroke();
1676
+ ctx.setLineDash([]);
1677
+ }
1678
+ ctx.beginPath();
1679
+ for (const h of e.handles) {
1680
+ const a = scr(h.anchor);
1681
+ const q = scr(h.point);
1682
+ ctx.moveTo(a.x, a.y);
1683
+ ctx.lineTo(q.x, q.y);
1684
+ }
1685
+ ctx.lineWidth = 1 * u;
1686
+ ctx.stroke();
1687
+ for (const h of e.handles) {
1688
+ const q = scr(h.point);
1689
+ ctx.beginPath();
1690
+ ctx.arc(q.x, q.y, 3 * u, 0, Math.PI * 2);
1691
+ ctx.fillStyle = OVERLAY_HANDLE_EDGE;
1692
+ ctx.fill();
1693
+ }
1694
+ const size = 6 * u;
1695
+ ctx.lineWidth = 1 * u;
1696
+ for (const a of e.anchors) {
1697
+ const q = scr(a.point);
1698
+ ctx.fillStyle = a.selected ? OVERLAY_HANDLE_EDGE : "#ffffff";
1699
+ ctx.fillRect(q.x - size / 2, q.y - size / 2, size, size);
1700
+ ctx.strokeRect(q.x - size / 2, q.y - size / 2, size, size);
1701
+ }
1702
+ if (e.marquee) {
1703
+ const a = scr({ x: e.marquee.x, y: e.marquee.y });
1704
+ const b = scr({ x: e.marquee.x + e.marquee.width, y: e.marquee.y + e.marquee.height });
1705
+ ctx.setLineDash([4 * u, 3 * u]);
1706
+ ctx.strokeRect(Math.min(a.x, b.x), Math.min(a.y, b.y), Math.abs(b.x - a.x), Math.abs(b.y - a.y));
1707
+ }
1708
+ } catch {
1709
+ }
1710
+ ctx.restore();
1711
+ }
1642
1712
  if (isActivelyCropping) {
1643
1713
  const activeCrop = options.draftCrop !== void 0 && options.draftCrop !== null ? options.draftCrop : crop ?? { x: 0, y: 0, width: naturalWidth, height: naturalHeight };
1644
1714
  this.drawCropOverlay(ctx, getViewportMetrics(geoState, vpWidth, vpHeight, true, activeCrop), u);
@@ -1742,7 +1812,7 @@ var ImageEditorRenderer = class {
1742
1812
  if (!source.enabled || !source.maskSource) return null;
1743
1813
  return this.depthMaps.get(`${source.maskSource}|${width}x${height}`) ?? null;
1744
1814
  },
1745
- rasterizeAnnotation: (annotation, rect) => this.rasterizeAnnotation(annotation, rect),
1815
+ rasterizeAnnotation: (annotation, rect2) => this.rasterizeAnnotation(annotation, rect2),
1746
1816
  version: `${host?.version() ?? ""}:${this.assetVersion}`,
1747
1817
  globalLight: host?.globalLight?.(),
1748
1818
  pattern: host?.pattern ? (id) => host.pattern(id) : void 0
@@ -1754,15 +1824,15 @@ var ImageEditorRenderer = class {
1754
1824
  for (const l of flattenLayers(layers)) {
1755
1825
  const source = l.mask?.source;
1756
1826
  if (source?.enabled && source.maskSource) {
1757
- const key = `${source.maskSource}|${width}x${height}`;
1758
- if (this.depthMaps.has(key)) continue;
1827
+ const key2 = `${source.maskSource}|${width}x${height}`;
1828
+ if (this.depthMaps.has(key2)) continue;
1759
1829
  try {
1760
1830
  const maskImg = await this.imageCache.get(source.maskSource);
1761
1831
  const canvas = this.createCanvas(width, height);
1762
1832
  const mctx = canvas.getContext("2d");
1763
1833
  const data = mctx ? (mctx.drawImage(maskImg, 0, 0, width, height), mctx.getImageData?.(0, 0, width, height)) : null;
1764
1834
  if (data) {
1765
- this.depthMaps.set(key, { width, height, data: data.data });
1835
+ this.depthMaps.set(key2, { width, height, data: data.data });
1766
1836
  this.assetVersion++;
1767
1837
  }
1768
1838
  } catch {
@@ -1783,34 +1853,34 @@ var ImageEditorRenderer = class {
1783
1853
  return Boolean(ctx && typeof ctx.getImageData === "function");
1784
1854
  }
1785
1855
  /** One annotation drawn into a tile-sized RGBA buffer at `rect` (document space), or `null` without a canvas. */
1786
- rasterizeAnnotation(annotation, rect) {
1787
- if (!this.scratch) this.scratch = this.createCanvas(rect.width, rect.height);
1856
+ rasterizeAnnotation(annotation, rect2) {
1857
+ if (!this.scratch) this.scratch = this.createCanvas(rect2.width, rect2.height);
1788
1858
  const canvas = this.scratch;
1789
- if (canvas.width !== rect.width || canvas.height !== rect.height) {
1790
- canvas.width = rect.width;
1791
- canvas.height = rect.height;
1859
+ if (canvas.width !== rect2.width || canvas.height !== rect2.height) {
1860
+ canvas.width = rect2.width;
1861
+ canvas.height = rect2.height;
1792
1862
  }
1793
1863
  const ctx = canvas.getContext("2d");
1794
1864
  if (!ctx || typeof ctx.getImageData !== "function") return null;
1795
1865
  try {
1796
1866
  ctx.setTransform(1, 0, 0, 1, 0, 0);
1797
- ctx.clearRect(0, 0, rect.width, rect.height);
1798
- ctx.setTransform(1, 0, 0, 1, -rect.x, -rect.y);
1867
+ ctx.clearRect(0, 0, rect2.width, rect2.height);
1868
+ ctx.setTransform(1, 0, 0, 1, -rect2.x, -rect2.y);
1799
1869
  const img = annotation.type === "image" ? this.imageCache.peek(annotation.src) ?? null : null;
1800
1870
  this.drawAnnotationShape(ctx, annotation, img);
1801
1871
  ctx.setTransform(1, 0, 0, 1, 0, 0);
1802
- return new Uint8ClampedArray(ctx.getImageData(0, 0, rect.width, rect.height).data);
1872
+ return new Uint8ClampedArray(ctx.getImageData(0, 0, rect2.width, rect2.height).data);
1803
1873
  } catch {
1804
1874
  return null;
1805
1875
  }
1806
1876
  }
1807
- putTile(ctx, data, tx, ty, rect) {
1808
- const ox = tx * TILE - rect.x;
1809
- const oy = ty * TILE - rect.y;
1877
+ putTile(ctx, data, tx, ty, rect2) {
1878
+ const ox = tx * TILE - rect2.x;
1879
+ const oy = ty * TILE - rect2.y;
1810
1880
  const x0 = Math.max(0, -ox);
1811
1881
  const y0 = Math.max(0, -oy);
1812
- const x1 = Math.min(TILE, rect.width - ox);
1813
- const y1 = Math.min(TILE, rect.height - oy);
1882
+ const x1 = Math.min(TILE, rect2.width - ox);
1883
+ const y1 = Math.min(TILE, rect2.height - oy);
1814
1884
  if (x1 <= x0 || y1 <= y0) return;
1815
1885
  const img = ctx.createImageData(TILE, TILE);
1816
1886
  img.data.set(data);
@@ -1822,23 +1892,23 @@ var ImageEditorRenderer = class {
1822
1892
  * uploading only the tiles that changed since the last render.
1823
1893
  */
1824
1894
  updateComposite(layers, region, width, height, options = {}) {
1825
- const rect = coveringRect(region);
1895
+ const rect2 = coveringRect(region);
1826
1896
  let comp = this.comp;
1827
1897
  let fresh = false;
1828
- if (!comp || comp.rect.x !== rect.x || comp.rect.y !== rect.y || comp.rect.width !== rect.width || comp.rect.height !== rect.height) {
1829
- const canvas = this.createCanvas(rect.width, rect.height);
1830
- comp = this.comp = new CompositeCanvas(canvas, rect);
1898
+ if (!comp || comp.rect.x !== rect2.x || comp.rect.y !== rect2.y || comp.rect.width !== rect2.width || comp.rect.height !== rect2.height) {
1899
+ const canvas = this.createCanvas(rect2.width, rect2.height);
1900
+ comp = this.comp = new CompositeCanvas(canvas, rect2);
1831
1901
  fresh = true;
1832
1902
  }
1833
1903
  const ctx = comp.canvas.getContext("2d");
1834
1904
  if (!ctx || typeof ctx.createImageData !== "function") return null;
1835
1905
  let changed = false;
1836
- this.compositor.compositeTiles(layers, rect, this.compositorSources(width, height), (tx, ty, data) => {
1837
- const key = tileKey(tx, ty);
1838
- if (comp.tiles.get(key) === data) return;
1839
- comp.tiles.set(key, data);
1840
- comp.histograms.delete(key);
1841
- this.putTile(ctx, data, tx, ty, rect);
1906
+ this.compositor.compositeTiles(layers, rect2, this.compositorSources(width, height), (tx, ty, data) => {
1907
+ const key2 = tileKey(tx, ty);
1908
+ if (comp.tiles.get(key2) === data) return;
1909
+ comp.tiles.set(key2, data);
1910
+ comp.histograms.delete(key2);
1911
+ this.putTile(ctx, data, tx, ty, rect2);
1842
1912
  changed = true;
1843
1913
  }, { checkpointLayerId: options.checkpointLayerId, dirty: fresh ? void 0 : options.dirty });
1844
1914
  if (changed) comp.version++;
@@ -1857,8 +1927,8 @@ var ImageEditorRenderer = class {
1857
1927
  const rotated = getRotatedSize(region.width, region.height, transform);
1858
1928
  const baseW = Math.max(1, Math.round(rotated.width));
1859
1929
  const baseH = Math.max(1, Math.round(rotated.height));
1860
- const key = `${this.displayKey}:${comp.version}|${region.x},${region.y},${region.width},${region.height}|${normalizeAngle(transform.rotate)},${transform.flipH},${transform.flipV}|${opacity}|${blur}`;
1861
- if (this.cachedPostCanvas && this.cachedPostKey === key) return { canvas: this.cachedPostCanvas, histogram: this.cachedPostHistogram };
1930
+ const key2 = `${this.displayKey}:${comp.version}|${region.x},${region.y},${region.width},${region.height}|${normalizeAngle(transform.rotate)},${transform.flipH},${transform.flipV}|${opacity}|${blur}`;
1931
+ if (this.cachedPostCanvas && this.cachedPostKey === key2) return { canvas: this.cachedPostCanvas, histogram: this.cachedPostHistogram };
1862
1932
  const canvas = this.cachedPostCanvas && this.cachedPostCanvas.width === baseW && this.cachedPostCanvas.height === baseH ? this.cachedPostCanvas : this.createCanvas(baseW, baseH);
1863
1933
  const ctx = canvas.getContext("2d");
1864
1934
  if (!ctx) return null;
@@ -1872,7 +1942,7 @@ var ImageEditorRenderer = class {
1872
1942
  ctx.restore();
1873
1943
  const histogram = this.applyPost(ctx, baseW, baseH, state);
1874
1944
  this.cachedPostCanvas = canvas;
1875
- this.cachedPostKey = key;
1945
+ this.cachedPostKey = key2;
1876
1946
  this.cachedPostHistogram = histogram;
1877
1947
  return { canvas, histogram };
1878
1948
  }
@@ -1908,11 +1978,11 @@ var ImageEditorRenderer = class {
1908
1978
  /** A layer's mask as canvas-sized coverage for its mask view, kept while the mask is unchanged. */
1909
1979
  maskViewSelection(layer, width, height) {
1910
1980
  if (!layer.mask || layer.mask.source || width < 1 || height < 1) return null;
1911
- const key = `${width}x${height}:${layer.type === "pixel" ? `${layer.offset.x},${layer.offset.y}` : ""}`;
1981
+ const key2 = `${width}x${height}:${layer.type === "pixel" ? `${layer.offset.x},${layer.offset.y}` : ""}`;
1912
1982
  const cached = this.maskViewCache.get(layer.mask.surface);
1913
- if (cached?.key === key) return cached.mask;
1983
+ if (cached?.key === key2) return cached.mask;
1914
1984
  const mask = layerMaskSelection(layer, width, height);
1915
- if (mask) this.maskViewCache.set(layer.mask.surface, { key, mask });
1985
+ if (mask) this.maskViewCache.set(layer.mask.surface, { key: key2, mask });
1916
1986
  return mask;
1917
1987
  }
1918
1988
  /** A mask alone in grey (white = revealed), opaque, drawn in natural image space. */
@@ -1931,16 +2001,16 @@ var ImageEditorRenderer = class {
1931
2001
  }
1932
2002
  /** A channel's tint (colour, opacity, masked or selected area), drawn in natural image space. */
1933
2003
  drawMaskOverlay(ctx, mask, style) {
1934
- const key = `${style.color?.join(",")}|${style.opacity}|${style.indicates}`;
2004
+ const key2 = `${style.color?.join(",")}|${style.opacity}|${style.indicates}`;
1935
2005
  let cached = this.overlayCache.get(mask);
1936
- if (!cached || cached.key !== key) {
2006
+ if (!cached || cached.key !== key2) {
1937
2007
  const canvas = this.createCanvas(mask.width, mask.height);
1938
2008
  const mctx = canvas.getContext("2d");
1939
2009
  if (!mctx || typeof mctx.createImageData !== "function") return;
1940
2010
  const img = mctx.createImageData(mask.width, mask.height);
1941
2011
  img.data.set(quickMaskPixels(mask, style));
1942
2012
  mctx.putImageData(img, 0, 0);
1943
- cached = { key, canvas };
2013
+ cached = { key: key2, canvas };
1944
2014
  this.overlayCache.set(mask, cached);
1945
2015
  }
1946
2016
  ctx.drawImage(cached.canvas, 0, 0, mask.width, mask.height);
@@ -2001,11 +2071,11 @@ var ImageEditorRenderer = class {
2001
2071
  if (typeof ctx.setLineDash === "function") ctx.setLineDash([]);
2002
2072
  }
2003
2073
  getSourceId(img) {
2004
- const key = img;
2005
- let id = this.sourceIds.get(key);
2074
+ const key2 = img;
2075
+ let id = this.sourceIds.get(key2);
2006
2076
  if (id === void 0) {
2007
2077
  id = ++this.nextSourceId;
2008
- this.sourceIds.set(key, id);
2078
+ this.sourceIds.set(key2, id);
2009
2079
  }
2010
2080
  return id;
2011
2081
  }
@@ -2104,15 +2174,15 @@ var ImageEditorRenderer = class {
2104
2174
  break;
2105
2175
  }
2106
2176
  case "rect": {
2107
- const rect = ann;
2108
- ctx.strokeStyle = rect.strokeColor;
2109
- ctx.lineWidth = rect.strokeWidth;
2110
- if (rect.fillColor) {
2111
- ctx.fillStyle = rect.fillColor;
2112
- ctx.fillRect(rect.x, rect.y, rect.width, rect.height);
2177
+ const rect2 = ann;
2178
+ ctx.strokeStyle = rect2.strokeColor;
2179
+ ctx.lineWidth = rect2.strokeWidth;
2180
+ if (rect2.fillColor) {
2181
+ ctx.fillStyle = rect2.fillColor;
2182
+ ctx.fillRect(rect2.x, rect2.y, rect2.width, rect2.height);
2113
2183
  }
2114
- if (rect.strokeWidth > 0) {
2115
- ctx.strokeRect(rect.x, rect.y, rect.width, rect.height);
2184
+ if (rect2.strokeWidth > 0) {
2185
+ ctx.strokeRect(rect2.x, rect2.y, rect2.width, rect2.height);
2116
2186
  }
2117
2187
  break;
2118
2188
  }
@@ -2303,12 +2373,12 @@ var ImageEditorRenderer = class {
2303
2373
  const split = splitOverlayAnnotations(layers);
2304
2374
  overlay = split.overlay.map((l) => l.annotation);
2305
2375
  await this.prepareLayerAssets(split.composite, naturalWidth, naturalHeight);
2306
- const rect = coveringRect(region);
2307
- const comp = this.createCanvas(rect.width, rect.height);
2376
+ const rect2 = coveringRect(region);
2377
+ const comp = this.createCanvas(rect2.width, rect2.height);
2308
2378
  const cctx = comp.getContext("2d");
2309
2379
  if (cctx && typeof cctx.createImageData === "function") {
2310
- this.exportCompositor.compositeTiles(split.composite, rect, this.compositorSources(naturalWidth, naturalHeight), (tx, ty, data) => {
2311
- this.putTile(cctx, data, tx, ty, rect);
2380
+ this.exportCompositor.compositeTiles(split.composite, rect2, this.compositorSources(naturalWidth, naturalHeight), (tx, ty, data) => {
2381
+ this.putTile(cctx, data, tx, ty, rect2);
2312
2382
  });
2313
2383
  }
2314
2384
  baseCanvas = this.createCanvas(baseW, baseH);
@@ -2319,7 +2389,7 @@ var ImageEditorRenderer = class {
2319
2389
  bctx.beginPath();
2320
2390
  bctx.rect(region.x, region.y, region.width, region.height);
2321
2391
  bctx.clip();
2322
- bctx.drawImage(comp, rect.x, rect.y);
2392
+ bctx.drawImage(comp, rect2.x, rect2.y);
2323
2393
  bctx.restore();
2324
2394
  this.applyPost(bctx, baseW, baseH, state);
2325
2395
  }
@@ -2447,10 +2517,10 @@ function getBaseProjection(region, transform, baseW, baseH) {
2447
2517
  };
2448
2518
  }
2449
2519
  var CompositeCanvas = class {
2450
- constructor(canvas, rect) {
2520
+ constructor(canvas, rect2) {
2451
2521
  this.canvas = canvas;
2452
- this.rect = rect;
2453
- this.region = rect;
2522
+ this.rect = rect2;
2523
+ this.region = rect2;
2454
2524
  }
2455
2525
  canvas;
2456
2526
  rect;
@@ -2463,18 +2533,18 @@ var CompositeCanvas = class {
2463
2533
  if (!this.tiles.size) return null;
2464
2534
  const r = coveringRect(this.region);
2465
2535
  const parts = [];
2466
- for (const [key, data] of this.tiles) {
2467
- let h = this.histograms.get(key);
2536
+ for (const [key2, data] of this.tiles) {
2537
+ let h = this.histograms.get(key2);
2468
2538
  if (!h) {
2469
- const tx = key % 65536 - 32768;
2470
- const ty = Math.floor(key / 65536) - 32768;
2539
+ const tx = key2 % 65536 - 32768;
2540
+ const ty = Math.floor(key2 / 65536) - 32768;
2471
2541
  const x0 = Math.max(r.x, tx * TILE);
2472
2542
  const y0 = Math.max(r.y, ty * TILE);
2473
2543
  const x1 = Math.min(r.x + r.width, tx * TILE + TILE);
2474
2544
  const y1 = Math.min(r.y + r.height, ty * TILE + TILE);
2475
2545
  h = emptyHistogram();
2476
2546
  if (x1 > x0 && y1 > y0) addToHistogram(h, data, TILE, { x: x0 - tx * TILE, y: y0 - ty * TILE, width: x1 - x0, height: y1 - y0 });
2477
- this.histograms.set(key, h);
2547
+ this.histograms.set(key2, h);
2478
2548
  }
2479
2549
  parts.push(h);
2480
2550
  }
@@ -2547,12 +2617,12 @@ function pathToSelection(components, width, height, options = {}) {
2547
2617
  const rx1 = Math.min(width, Math.ceil(x1));
2548
2618
  const ry1 = Math.min(height, Math.ceil(y1));
2549
2619
  if (!(rx1 > rx0 && ry1 > ry0)) return createSelectionMask(width, height);
2550
- const rect = { x: rx0, y: ry0, width: rx1 - rx0, height: ry1 - ry0 };
2551
- const cov = componentsCoverage(components, rect, options.fillRule ?? "nonzero");
2620
+ const rect2 = { x: rx0, y: ry0, width: rx1 - rx0, height: ry1 - ry0 };
2621
+ const cov = componentsCoverage(components, rect2, options.fillRule ?? "nonzero");
2552
2622
  const hard = options.antiAlias === false;
2553
2623
  const bytes = new Uint8Array(cov.length);
2554
2624
  for (let i = 0; i < cov.length; i++) bytes[i] = hard ? cov[i] >= 0.5 ? 255 : 0 : coverageToByte(cov[i]);
2555
- const mask = selectionFromRegion(width, height, rect, bytes);
2625
+ const mask = selectionFromRegion(width, height, rect2, bytes);
2556
2626
  const feather = options.feather ?? 0;
2557
2627
  return feather > 0 && mask.bounds ? modifySelection(mask, "feather", feather) : mask;
2558
2628
  }
@@ -2591,11 +2661,11 @@ var EdgeMap = class {
2591
2661
  block(x, y) {
2592
2662
  const bx = Math.floor(x / BLOCK);
2593
2663
  const by = Math.floor(y / BLOCK);
2594
- const key = by * 65536 + bx;
2595
- let b = this.blocks.get(key);
2664
+ const key2 = by * 65536 + bx;
2665
+ let b = this.blocks.get(key2);
2596
2666
  if (!b) {
2597
2667
  b = this.computeBlock(bx * BLOCK, by * BLOCK);
2598
- this.blocks.set(key, b);
2668
+ this.blocks.set(key2, b);
2599
2669
  }
2600
2670
  return b;
2601
2671
  }
@@ -2689,7 +2759,7 @@ var Heap = class {
2689
2759
  ids = new Int32Array(256);
2690
2760
  keys = new Float64Array(256);
2691
2761
  size = 0;
2692
- push(id, key) {
2762
+ push(id, key2) {
2693
2763
  if (this.size === this.ids.length) {
2694
2764
  const ids = new Int32Array(this.size * 2);
2695
2765
  ids.set(this.ids);
@@ -2701,31 +2771,31 @@ var Heap = class {
2701
2771
  let i = this.size++;
2702
2772
  while (i > 0) {
2703
2773
  const p = i - 1 >> 1;
2704
- if (this.keys[p] <= key) break;
2774
+ if (this.keys[p] <= key2) break;
2705
2775
  this.ids[i] = this.ids[p];
2706
2776
  this.keys[i] = this.keys[p];
2707
2777
  i = p;
2708
2778
  }
2709
2779
  this.ids[i] = id;
2710
- this.keys[i] = key;
2780
+ this.keys[i] = key2;
2711
2781
  }
2712
2782
  /** Removes the cheapest entry; read it from `topId` / `topKey` first. */
2713
2783
  pop() {
2714
2784
  const n = --this.size;
2715
2785
  const id = this.ids[n];
2716
- const key = this.keys[n];
2786
+ const key2 = this.keys[n];
2717
2787
  let i = 0;
2718
2788
  for (; ; ) {
2719
2789
  let c = 2 * i + 1;
2720
2790
  if (c >= n) break;
2721
2791
  if (c + 1 < n && this.keys[c + 1] < this.keys[c]) c++;
2722
- if (this.keys[c] >= key) break;
2792
+ if (this.keys[c] >= key2) break;
2723
2793
  this.ids[i] = this.ids[c];
2724
2794
  this.keys[i] = this.keys[c];
2725
2795
  i = c;
2726
2796
  }
2727
2797
  this.ids[i] = id;
2728
- this.keys[i] = key;
2798
+ this.keys[i] = key2;
2729
2799
  }
2730
2800
  get topId() {
2731
2801
  return this.ids[0];
@@ -3885,29 +3955,29 @@ var ImageEffectsEngine = class {
3885
3955
  return this.pending.size > 0;
3886
3956
  }
3887
3957
  /** See `SmartEffectResolver`. */
3888
- resolve(filter, input, width, height, key) {
3889
- const hit = this.cache.get(key);
3958
+ resolve(filter, input, width, height, key2) {
3959
+ const hit = this.cache.get(key2);
3890
3960
  if (hit) {
3891
- this.cache.delete(key);
3892
- this.cache.set(key, hit);
3961
+ this.cache.delete(key2);
3962
+ this.cache.set(key2, hit);
3893
3963
  return { result: hit, pending: false };
3894
3964
  }
3895
- if (this.pending.has(key)) return { result: null, pending: true };
3965
+ if (this.pending.has(key2)) return { result: null, pending: true };
3896
3966
  const provider = this.slot.provider;
3897
- if (!provider || this.failures.has(key)) return { result: null, pending: false };
3967
+ if (!provider || this.failures.has(key2)) return { result: null, pending: false };
3898
3968
  const generation = this.generation;
3899
3969
  const source = { width, height, data: new Uint8ClampedArray(input) };
3900
3970
  const run = this.filter(provider, filter.effect, filter.values, source).then((out) => {
3901
3971
  if (generation !== this.generation) return;
3902
- this.cache.set(key, out);
3972
+ this.cache.set(key2, out);
3903
3973
  while (this.cache.size > this.maxEntries) this.cache.delete(this.cache.keys().next().value);
3904
3974
  }).catch((error) => {
3905
- if (generation === this.generation) this.failures.set(key, error instanceof Error ? error : new Error(String(error)));
3975
+ if (generation === this.generation) this.failures.set(key2, error instanceof Error ? error : new Error(String(error)));
3906
3976
  }).finally(() => {
3907
- if (this.pending.get(key) === run) this.pending.delete(key);
3977
+ if (this.pending.get(key2) === run) this.pending.delete(key2);
3908
3978
  if (generation === this.generation) this.notify();
3909
3979
  });
3910
- this.pending.set(key, run);
3980
+ this.pending.set(key2, run);
3911
3981
  return { result: null, pending: true };
3912
3982
  }
3913
3983
  /** A filter effect over `source`: its result, or (for an overlay) the effect drawn over it. */
@@ -3928,12 +3998,12 @@ var ImageEffectsEngine = class {
3928
3998
  return out.width === size.width && out.height === size.height ? out : createEffectPixels(size.width, size.height);
3929
3999
  }
3930
4000
  /** Why the result for `key` failed, if it did. */
3931
- failure(key) {
3932
- return this.failures.get(key);
4001
+ failure(key2) {
4002
+ return this.failures.get(key2);
3933
4003
  }
3934
4004
  /** Every failure so far. */
3935
4005
  failuresList() {
3936
- return [...this.failures].map(([key, error]) => ({ key, error }));
4006
+ return [...this.failures].map(([key2, error]) => ({ key: key2, error }));
3937
4007
  }
3938
4008
  /** Resolves when nothing is rendering. */
3939
4009
  async idle() {
@@ -4556,10 +4626,10 @@ function resolveProfile(doc) {
4556
4626
  let p;
4557
4627
  if (doc.builtin) p = builtinProfile(doc.builtin);
4558
4628
  else if (doc.icc) {
4559
- const key = bytesToBase64(doc.icc);
4560
- p = fromBytes.get(key) ?? parseProfile(doc.icc);
4629
+ const key2 = bytesToBase64(doc.icc);
4630
+ p = fromBytes.get(key2) ?? parseProfile(doc.icc);
4561
4631
  if (fromBytes.size > 32) fromBytes.clear();
4562
- fromBytes.set(key, p);
4632
+ fromBytes.set(key2, p);
4563
4633
  } else p = srgbProfile();
4564
4634
  resolved.set(doc, p);
4565
4635
  return p;
@@ -4649,12 +4719,12 @@ function convertHex(hex, from, to, intent = "relative", bpc = true) {
4649
4719
  return `#${c(out[0])}${c(out[1])}${c(out[2])}`;
4650
4720
  }
4651
4721
  function convertColorFields(value, convert) {
4652
- const walk = (v, key) => {
4653
- if (typeof v === "string") return /color$/i.test(key) && /^#[0-9a-f]{6}$/i.test(v) ? convert(v) : v;
4722
+ const walk = (v, key2) => {
4723
+ if (typeof v === "string") return /color$/i.test(key2) && /^#[0-9a-f]{6}$/i.test(v) ? convert(v) : v;
4654
4724
  if (Array.isArray(v)) {
4655
4725
  let changed = false;
4656
4726
  const out = v.map((x) => {
4657
- const y = walk(x, key);
4727
+ const y = walk(x, key2);
4658
4728
  if (y !== x) changed = true;
4659
4729
  return y;
4660
4730
  });
@@ -4676,7 +4746,7 @@ function convertSurfaceColors(surface, t) {
4676
4746
  if (surface.channels !== 4 || t.inputs !== 3 || t.outputs !== 3) return surface;
4677
4747
  const tiles = /* @__PURE__ */ new Map();
4678
4748
  const memo = /* @__PURE__ */ new Map();
4679
- for (const [key, tile] of surface.tiles) {
4749
+ for (const [key2, tile] of surface.tiles) {
4680
4750
  let next = memo.get(tile);
4681
4751
  if (next === void 0) {
4682
4752
  if (tile.deep instanceof Uint16Array) {
@@ -4699,7 +4769,7 @@ function convertSurfaceColors(surface, t) {
4699
4769
  }
4700
4770
  memo.set(tile, next);
4701
4771
  }
4702
- if (next) tiles.set(key, next);
4772
+ if (next) tiles.set(key2, next);
4703
4773
  }
4704
4774
  return new TiledSurface(4, tiles, surface.defaultValue);
4705
4775
  }
@@ -4716,22 +4786,22 @@ function paintCoverageInto(base, coverage, apply, rects, depth = null) {
4716
4786
  const done = /* @__PURE__ */ new Set();
4717
4787
  for (const r of rects) {
4718
4788
  for (const t of tilesInRect({ x: r.x - apply.offset.x, y: r.y - apply.offset.y, width: r.width, height: r.height })) {
4719
- const key = tileKey(t.tx, t.ty);
4720
- if (done.has(key)) continue;
4721
- done.add(key);
4789
+ const key2 = tileKey(t.tx, t.ty);
4790
+ if (done.has(key2)) continue;
4791
+ done.add(key2);
4722
4792
  if (depth) {
4723
4793
  const ch = base.channels;
4724
- const d = applyStrokeToDeepTile(coverage, tileSamples(base.tiles.get(key), ch, depth, base.defaultValue), t.tx, t.ty, apply, { channels: ch });
4794
+ const d = applyStrokeToDeepTile(coverage, tileSamples(base.tiles.get(key2), ch, depth, base.defaultValue), t.tx, t.ty, apply, { channels: ch });
4725
4795
  const tile2 = deepTile(d, ch, base.defaultValue);
4726
- if (tile2) tiles.set(key, tile2);
4727
- else tiles.delete(key);
4796
+ if (tile2) tiles.set(key2, tile2);
4797
+ else tiles.delete(key2);
4728
4798
  continue;
4729
4799
  }
4730
- const src = base.tiles.get(key)?.data ?? defaultTile(base);
4800
+ const src = base.tiles.get(key2)?.data ?? defaultTile(base);
4731
4801
  const data = applyStrokeToTile(coverage, src, base.channels, t.tx, t.ty, apply);
4732
4802
  const tile = finishTile(data, base.channels, base.defaultValue);
4733
- if (tile) tiles.set(key, tile);
4734
- else tiles.delete(key);
4803
+ if (tile) tiles.set(key2, tile);
4804
+ else tiles.delete(key2);
4735
4805
  }
4736
4806
  }
4737
4807
  return new TiledSurface(base.channels, tiles, base.defaultValue);
@@ -4802,19 +4872,19 @@ var PaintStrokeSession = class {
4802
4872
  work = /* @__PURE__ */ new Map();
4803
4873
  workRead = /* @__PURE__ */ new Map();
4804
4874
  /** The pre-stroke tile as a float working tile (cached; `null`: transparent). */
4805
- readWork(key) {
4806
- if (!this.workRead.has(key)) this.workRead.set(key, workTileOf(this.base.tiles.get(key)));
4807
- return this.workRead.get(key);
4875
+ readWork(key2) {
4876
+ if (!this.workRead.has(key2)) this.workRead.set(key2, workTileOf(this.base.tiles.get(key2)));
4877
+ return this.workRead.get(key2);
4808
4878
  }
4809
4879
  /** Clone, healing and the spot-heal trail at depth: `paint` through the coverage on the float working tile. */
4810
- retouchDeepTile(key, tx, ty, paint, options) {
4880
+ retouchDeepTile(key2, tx, ty, paint, options) {
4811
4881
  const r = this.retouch;
4812
- const read = this.readWork(key);
4882
+ const read = this.readWork(key2);
4813
4883
  const out = applyRetouchToTile(this.stroke.coverage, read ?? new Float32Array(TILE * TILE * 4), 4, tx, ty, paint, r.apply, options);
4814
- this.work.set(key, out);
4815
- const d = deepOfWorkTile(out, this.deep, read, this.base.tiles.get(key));
4816
- this.liveDeep.set(key, d);
4817
- this.live.set(key, { data: proxyOf(d, 4), uniform: null, deep: d });
4884
+ this.work.set(key2, out);
4885
+ const d = deepOfWorkTile(out, this.deep, read, this.base.tiles.get(key2));
4886
+ this.liveDeep.set(key2, d);
4887
+ this.live.set(key2, { data: proxyOf(d, 4), uniform: null, deep: d });
4818
4888
  return d;
4819
4889
  }
4820
4890
  /** The new tile of a coverage stroke over the pre-stroke tile `src`. */
@@ -4828,23 +4898,23 @@ var PaintStrokeSession = class {
4828
4898
  const a = this.apply;
4829
4899
  const depth = this.dabDeep;
4830
4900
  if (depth) {
4831
- const read = (key) => this.readWork(key);
4901
+ const read = (key2) => this.readWork(key2);
4832
4902
  return {
4833
4903
  offset: a.offset,
4834
4904
  lockTransparency: a.lockTransparency,
4835
4905
  selection: a.selection,
4836
4906
  readTile: (tx, ty) => {
4837
- const key = tileKey(tx, ty);
4838
- return this.work.get(key) ?? read(key);
4907
+ const key2 = tileKey(tx, ty);
4908
+ return this.work.get(key2) ?? read(key2);
4839
4909
  },
4840
4910
  writeTile: (tx, ty) => {
4841
- const key = tileKey(tx, ty);
4842
- if (!this.touched.has(key)) {
4843
- this.work.set(key, read(key)?.slice() ?? new Float32Array(TILE * TILE * 4));
4844
- this.touched.add(key);
4911
+ const key2 = tileKey(tx, ty);
4912
+ if (!this.touched.has(key2)) {
4913
+ this.work.set(key2, read(key2)?.slice() ?? new Float32Array(TILE * TILE * 4));
4914
+ this.touched.add(key2);
4845
4915
  }
4846
- this.fresh.add(key);
4847
- return this.work.get(key);
4916
+ this.fresh.add(key2);
4917
+ return this.work.get(key2);
4848
4918
  }
4849
4919
  };
4850
4920
  }
@@ -4854,14 +4924,14 @@ var PaintStrokeSession = class {
4854
4924
  selection: a.selection,
4855
4925
  readTile: (tx, ty) => this.live.get(tileKey(tx, ty))?.data ?? (this.base.defaultValue ? defaultTile(this.base) : null),
4856
4926
  writeTile: (tx, ty) => {
4857
- const key = tileKey(tx, ty);
4858
- if (!this.touched.has(key)) {
4859
- const src = this.base.tiles.get(key)?.data ?? defaultTile(this.base);
4860
- this.live.set(key, { data: new Uint8ClampedArray(src), uniform: null });
4861
- this.touched.add(key);
4927
+ const key2 = tileKey(tx, ty);
4928
+ if (!this.touched.has(key2)) {
4929
+ const src = this.base.tiles.get(key2)?.data ?? defaultTile(this.base);
4930
+ this.live.set(key2, { data: new Uint8ClampedArray(src), uniform: null });
4931
+ this.touched.add(key2);
4862
4932
  }
4863
- this.fresh.add(key);
4864
- return this.live.get(key).data;
4933
+ this.fresh.add(key2);
4934
+ return this.live.get(key2).data;
4865
4935
  }
4866
4936
  };
4867
4937
  }
@@ -4885,12 +4955,12 @@ var PaintStrokeSession = class {
4885
4955
  const ch = this.base.channels;
4886
4956
  if (this.mixer) {
4887
4957
  if (this.dabDeep) {
4888
- for (const key of this.fresh) {
4889
- const d = deepOfWorkTile(this.work.get(key), this.dabDeep, this.workRead.get(key) ?? null, this.base.tiles.get(key));
4890
- this.liveDeep.set(key, d);
4891
- this.live.set(key, { data: proxyOf(d, 4), uniform: null, deep: d });
4958
+ for (const key2 of this.fresh) {
4959
+ const d = deepOfWorkTile(this.work.get(key2), this.dabDeep, this.workRead.get(key2) ?? null, this.base.tiles.get(key2));
4960
+ this.liveDeep.set(key2, d);
4961
+ this.live.set(key2, { data: proxyOf(d, 4), uniform: null, deep: d });
4892
4962
  }
4893
- } else for (const key of this.fresh) this.live.set(key, { data: this.live.get(key).data, uniform: null });
4963
+ } else for (const key2 of this.fresh) this.live.set(key2, { data: this.live.get(key2).data, uniform: null });
4894
4964
  this.fresh.clear();
4895
4965
  this.controller._strokeUpdate(this.target.id, this.target.channel, new TiledSurface(ch, new Map(this.live), this.base.defaultValue), rects);
4896
4966
  return;
@@ -4899,35 +4969,35 @@ var PaintStrokeSession = class {
4899
4969
  const regions = /* @__PURE__ */ new Map();
4900
4970
  for (const r of rects) {
4901
4971
  for (const t of tilesInRect({ x: r.x - this.apply.offset.x, y: r.y - this.apply.offset.y, width: r.width, height: r.height })) {
4902
- const key = tileKey(t.tx, t.ty);
4903
- const cur = regions.get(key);
4904
- regions.set(key, { tx: t.tx, ty: t.ty, r: cur ? unionRect(cur.r, r) : r });
4972
+ const key2 = tileKey(t.tx, t.ty);
4973
+ const cur = regions.get(key2);
4974
+ regions.set(key2, { tx: t.tx, ty: t.ty, r: cur ? unionRect(cur.r, r) : r });
4905
4975
  }
4906
4976
  }
4907
- for (const [key, { tx, ty, r }] of regions) {
4977
+ for (const [key2, { tx, ty, r }] of regions) {
4908
4978
  if (this.deep) {
4909
4979
  const rt = this.retouch;
4910
4980
  const paint = rt?.kind === "coverage" ? rt.paint() : null;
4911
4981
  if (paint && paint.kind !== "tone") {
4912
- const prevWork = this.touched.has(key) ? this.work.get(key) : void 0;
4913
- this.touched.add(key);
4914
- this.retouchDeepTile(key, tx, ty, paint, prevWork ? { into: prevWork, region: r } : {});
4982
+ const prevWork = this.touched.has(key2) ? this.work.get(key2) : void 0;
4983
+ this.touched.add(key2);
4984
+ this.retouchDeepTile(key2, tx, ty, paint, prevWork ? { into: prevWork, region: r } : {});
4915
4985
  continue;
4916
4986
  }
4917
- const src2 = tileSamples(this.base.tiles.get(key), ch, this.deep, this.base.defaultValue);
4918
- const prev2 = this.touched.has(key) ? this.liveDeep.get(key) : void 0;
4919
- this.touched.add(key);
4987
+ const src2 = tileSamples(this.base.tiles.get(key2), ch, this.deep, this.base.defaultValue);
4988
+ const prev2 = this.touched.has(key2) ? this.liveDeep.get(key2) : void 0;
4989
+ this.touched.add(key2);
4920
4990
  const opts = prev2 ? { into: prev2, region: r, channels: ch } : { channels: ch };
4921
4991
  const d = rt?.kind === "coverage" && paint?.kind === "tone" ? applyToneToDeepTile(stroke.coverage, src2, tx, ty, paint.tone, rt.apply, opts) : applyStrokeToDeepTile(stroke.coverage, src2, tx, ty, this.apply, opts);
4922
- this.liveDeep.set(key, d);
4923
- this.live.set(key, { data: proxyOf(d, ch), uniform: null, deep: d });
4992
+ this.liveDeep.set(key2, d);
4993
+ this.live.set(key2, { data: proxyOf(d, ch), uniform: null, deep: d });
4924
4994
  continue;
4925
4995
  }
4926
- const src = this.base.tiles.get(key)?.data ?? defaultTile(this.base);
4927
- const prev = this.touched.has(key) ? this.live.get(key)?.data : void 0;
4928
- this.touched.add(key);
4996
+ const src = this.base.tiles.get(key2)?.data ?? defaultTile(this.base);
4997
+ const prev = this.touched.has(key2) ? this.live.get(key2)?.data : void 0;
4998
+ this.touched.add(key2);
4929
4999
  const data = this.compositeTile(src, tx, ty, prev ? { into: prev, region: r } : {});
4930
- this.live.set(key, { data, uniform: null });
5000
+ this.live.set(key2, { data, uniform: null });
4931
5001
  }
4932
5002
  this.controller._strokeUpdate(this.target.id, this.target.channel, new TiledSurface(ch, new Map(this.live), this.base.defaultValue), rects);
4933
5003
  }
@@ -4944,26 +5014,26 @@ var PaintStrokeSession = class {
4944
5014
  if (r?.kind === "coverage" && r.finalPaint) {
4945
5015
  const final = r.finalPaint(this.stroke.coverage);
4946
5016
  if (final) {
4947
- for (const key of this.touched) {
4948
- const { tx, ty } = tileCoord(key);
5017
+ for (const key2 of this.touched) {
5018
+ const { tx, ty } = tileCoord(key2);
4949
5019
  if (this.deep) {
4950
- this.retouchDeepTile(key, tx, ty, final, {});
5020
+ this.retouchDeepTile(key2, tx, ty, final, {});
4951
5021
  continue;
4952
5022
  }
4953
- const src = this.base.tiles.get(key)?.data ?? defaultTile(this.base);
4954
- this.live.set(key, { data: this.compositeTile(src, tx, ty, {}, final), uniform: null });
5023
+ const src = this.base.tiles.get(key2)?.data ?? defaultTile(this.base);
5024
+ this.live.set(key2, { data: this.compositeTile(src, tx, ty, {}, final), uniform: null });
4955
5025
  }
4956
5026
  }
4957
5027
  }
4958
5028
  const tiles = new Map(this.base.tiles);
4959
5029
  let changed = false;
4960
- for (const key of this.touched) {
4961
- const live = this.live.get(key);
4962
- const deep = this.liveDeep.get(key);
5030
+ for (const key2 of this.touched) {
5031
+ const live = this.live.get(key2);
5032
+ const deep = this.liveDeep.get(key2);
4963
5033
  const tile = deep ? deepTile(deep, ch, this.base.defaultValue) : finishTile(live.data, ch, this.base.defaultValue);
4964
- const prev = this.base.tiles.get(key);
4965
- if (tile) tiles.set(key, tile);
4966
- else tiles.delete(key);
5034
+ const prev = this.base.tiles.get(key2);
5035
+ if (tile) tiles.set(key2, tile);
5036
+ else tiles.delete(key2);
4967
5037
  if (!sameTile(prev, tile)) changed = true;
4968
5038
  }
4969
5039
  const surface = changed ? new TiledSurface(ch, tiles, this.base.defaultValue) : null;
@@ -4982,9 +5052,9 @@ var PaintStrokeSession = class {
4982
5052
  this.controller._strokeCommit(this.before, this.target.id, this.target.channel, null);
4983
5053
  }
4984
5054
  };
4985
- function workRegionOf(surface, rect, depth) {
4986
- if (!depth) return Float32Array.from(readRegion(surface, rect));
4987
- const d = readDeepRegion(surface, rect, depth);
5055
+ function workRegionOf(surface, rect2, depth) {
5056
+ if (!depth) return Float32Array.from(readRegion(surface, rect2));
5057
+ const d = readDeepRegion(surface, rect2, depth);
4988
5058
  const out = new Float32Array(d.length);
4989
5059
  if (d instanceof Uint16Array) {
4990
5060
  for (let i = 0; i < d.length; i++) out[i] = d[i] / 257;
@@ -5671,9 +5741,9 @@ var ImageEditorController = class {
5671
5741
  /** Recomputes the compatibility views (`adjustments`, `depthMask`, `annotations`) when the stack changed. */
5672
5742
  syncViews() {
5673
5743
  const s = this.state;
5674
- const key = [s.layers, s.outputOpacity, s.legacyOutputBlur, s.activeLayerId, s.imageDimensions.width, s.imageDimensions.height];
5675
- if (key.length === this.viewKey.length && key.every((v, i) => v === this.viewKey[i])) return;
5676
- this.viewKey = key;
5744
+ const key2 = [s.layers, s.outputOpacity, s.legacyOutputBlur, s.activeLayerId, s.imageDimensions.width, s.imageDimensions.height];
5745
+ if (key2.length === this.viewKey.length && key2.every((v, i) => v === this.viewKey[i])) return;
5746
+ this.viewKey = key2;
5677
5747
  const { width, height } = this.naturalSize;
5678
5748
  const view = legacyView(this.stack(), width, height, s.activeLayerId);
5679
5749
  s.adjustments = view.adjustments;
@@ -6000,7 +6070,7 @@ var ImageEditorController = class {
6000
6070
  getLayerFrame(id) {
6001
6071
  const layer = this.getLayer(id);
6002
6072
  if (!layer) return null;
6003
- const rect = (b2) => [
6073
+ const rect2 = (b2) => [
6004
6074
  { x: b2.x, y: b2.y },
6005
6075
  { x: b2.x + b2.width, y: b2.y },
6006
6076
  { x: b2.x + b2.width, y: b2.y + b2.height },
@@ -6015,10 +6085,10 @@ var ImageEditorController = class {
6015
6085
  }
6016
6086
  if (layer.type === "shape") {
6017
6087
  const g = shapeGeometryBounds(layer);
6018
- return g ? rect(g) : null;
6088
+ return g ? rect2(g) : null;
6019
6089
  }
6020
6090
  const b = this.getLayerBounds(id);
6021
- return b ? rect(b) : null;
6091
+ return b ? rect2(b) : null;
6022
6092
  }
6023
6093
  /**
6024
6094
  * Hit-tests the layer stack at `point` (in document space) and returns the id of the topmost
@@ -6099,9 +6169,9 @@ var ImageEditorController = class {
6099
6169
  * and are left out): `rect` (default: the canvas) as RGBA, straight alpha. Crop, rotation and
6100
6170
  * output opacity are not applied. With `layerIds`, only those layers are composited.
6101
6171
  */
6102
- compositePixels(rect, options = {}) {
6172
+ compositePixels(rect2, options = {}) {
6103
6173
  const { width, height } = this.naturalSize;
6104
- const r = rect ?? { x: 0, y: 0, width, height };
6174
+ const r = rect2 ?? { x: 0, y: 0, width, height };
6105
6175
  let layers = this.state.layers;
6106
6176
  if (options.layerIds) {
6107
6177
  const keep = new Set(options.layerIds);
@@ -6125,12 +6195,12 @@ var ImageEditorController = class {
6125
6195
  nextSourceId = 1;
6126
6196
  /** Changes whenever the Background's source pixels can have changed. */
6127
6197
  sourceVersion() {
6128
- const key = this.samplePixelsOverride ? this.samplePixels : this.imageElement ?? this.samplePixels;
6129
- if (!key) return "0";
6130
- let id = this.sourceIds.get(key);
6198
+ const key2 = this.samplePixelsOverride ? this.samplePixels : this.imageElement ?? this.samplePixels;
6199
+ if (!key2) return "0";
6200
+ let id = this.sourceIds.get(key2);
6131
6201
  if (!id) {
6132
6202
  id = this.nextSourceId++;
6133
- this.sourceIds.set(key, id);
6203
+ this.sourceIds.set(key2, id);
6134
6204
  }
6135
6205
  return String(id);
6136
6206
  }
@@ -6572,8 +6642,8 @@ var ImageEditorController = class {
6572
6642
  this.state.activePathId = id;
6573
6643
  this.notify();
6574
6644
  }
6575
- commitPaths(next, active) {
6576
- this.recordHistory();
6645
+ commitPaths(next, active, commitToHistory = true) {
6646
+ if (commitToHistory) this.recordHistory();
6577
6647
  if (next.length) this.state.paths = next;
6578
6648
  else delete this.state.paths;
6579
6649
  if (active !== void 0) this.state.activePathId = active;
@@ -6609,6 +6679,64 @@ var ImageEditorController = class {
6609
6679
  this.commitPaths(this.getPaths().map((p) => p.id === id ? path : p));
6610
6680
  return path;
6611
6681
  }
6682
+ /**
6683
+ * What the path tools (Path Selection, Direct Selection, Pen) edit: the active path in the Paths
6684
+ * panel, else the active layer when it is a shape, else the active layer's vector mask; null
6685
+ * when there is none of these (the Pen then starts a new Work Path).
6686
+ */
6687
+ pathEditTarget() {
6688
+ const id = this.state.activePathId;
6689
+ if (id && this.getPath(id)) return { kind: "path", id };
6690
+ const layer = this.getActiveLayer();
6691
+ if (!layer) return null;
6692
+ if (layer.type === "shape") return { kind: "shape", layerId: layer.id };
6693
+ if (layer.vectorMask) return { kind: "vector-mask", layerId: layer.id };
6694
+ return null;
6695
+ }
6696
+ /** The components a path tool target holds (document pixels). Throws for a target that is gone. */
6697
+ getPathEditComponents(target) {
6698
+ if (target.kind === "path") return this.requirePath(target.id).components;
6699
+ if (target.kind === "shape") return requireShapeLayer(this.state.layers, target.layerId).components;
6700
+ return this.requireVectorMask(target.layerId).vm.components;
6701
+ }
6702
+ /**
6703
+ * Replaces a path tool target's components: one undo step, or none with `commitToHistory` false
6704
+ * (the later moves of a drag whose first move recorded one). A component that still has its
6705
+ * `live` shape is kept as given (drop `live` from one whose anchors changed); a shape layer keeps
6706
+ * at least one component.
6707
+ */
6708
+ setPathEditComponents(target, components, commitToHistory = true) {
6709
+ this.getPathEditComponents(target);
6710
+ const next = components.map(
6711
+ (c, i) => c.live ? { ...c } : this.componentFrom({ op: c.op, subpaths: c.subpaths.filter((sp) => sp.anchors.length) }, `components[${i}].`)
6712
+ );
6713
+ if (target.kind === "path") {
6714
+ this.commitPaths(this.getPaths().map((p) => p.id === target.id ? { ...p, components: next } : p), void 0, commitToHistory);
6715
+ } else if (target.kind === "shape") {
6716
+ if (!next.length) throw new LayerOperationError("A shape needs at least one component; delete the layer instead (delete_layer).");
6717
+ const { layers } = updateShapeLayer(this.state.layers, target.layerId, () => ({ components: next }), this.shapeEnv(), "edit its path");
6718
+ this.commitLayers(layers, void 0, commitToHistory);
6719
+ } else {
6720
+ const { vm } = this.requireVectorMask(target.layerId);
6721
+ this.commitLayers(mapLayer(this.state.layers, target.layerId, (l) => ({ ...l, vectorMask: { ...vm, components: next } })), void 0, commitToHistory);
6722
+ }
6723
+ }
6724
+ /**
6725
+ * The Pen with no path to add to: a new Work Path from `components` (replacing the last one,
6726
+ * unless it was saved), made the active path. One undo step. Returns the path.
6727
+ */
6728
+ startWorkPath(components) {
6729
+ const paths = this.getPaths();
6730
+ const old = paths.find((p) => p.work);
6731
+ const work = {
6732
+ id: old?.id ?? newPathId(),
6733
+ name: WORK_PATH_NAME,
6734
+ components: components.map((c, i) => this.componentFrom({ op: c.op, subpaths: c.subpaths }, `components[${i}].`)),
6735
+ work: true
6736
+ };
6737
+ this.commitPaths(old ? paths.map((p) => p === old ? work : p) : [...paths, work], work.id);
6738
+ return work;
6739
+ }
6612
6740
  /** Renames a path; renaming the Work Path saves it (Photoshop's Save Path). One undo step. */
6613
6741
  renamePath(id, name) {
6614
6742
  const old = this.requirePath(id);
@@ -7485,7 +7613,7 @@ var ImageEditorController = class {
7485
7613
  return this.getGlobalLight();
7486
7614
  }
7487
7615
  /** Composites `layers` (in isolation) over `rect` into a surface, loading depth maps first. */
7488
- async compositeToSurface(layers, rect) {
7616
+ async compositeToSurface(layers, rect2) {
7489
7617
  const { width, height } = this.naturalSize;
7490
7618
  if (flattenLayers(layers).some((l) => l.type === "annotation" && l.visible) && !this.renderer.canRasterize()) {
7491
7619
  throw new LayerOperationError("Merging annotation layers needs a canvas (a browser); hide or delete them first, or merge in the browser.");
@@ -7494,20 +7622,20 @@ var ImageEditorController = class {
7494
7622
  const sources = this.renderer.compositorSources(width, height);
7495
7623
  const depth = this.bitDepth;
7496
7624
  if (depth !== 8) {
7497
- const samples = workingToSamples(compositeDeep(layers, rect, sources, depth), depth);
7498
- return surfaceFromDeepPixels({ width: rect.width, height: rect.height, data: samples }, 4, rect.x, rect.y);
7625
+ const samples = workingToSamples(compositeDeep(layers, rect2, sources, depth), depth);
7626
+ return surfaceFromDeepPixels({ width: rect2.width, height: rect2.height, data: samples }, 4, rect2.x, rect2.y);
7499
7627
  }
7500
- const data = new LayerCompositor().composite(layers, rect, sources);
7501
- return surfaceFromPixels({ width: rect.width, height: rect.height, data }, 4, rect.x, rect.y);
7628
+ const data = new LayerCompositor().composite(layers, rect2, sources);
7629
+ return surfaceFromPixels({ width: rect2.width, height: rect2.height, data }, 4, rect2.x, rect2.y);
7502
7630
  }
7503
7631
  /**
7504
7632
  * The composite of the layer stack at the document's bit depth (as `compositePixels`, no
7505
7633
  * canvas): 16-bit sRGB samples, or 32-bit linear float, over `rect` (default the canvas). At 8
7506
7634
  * bits, the 8-bit composite widened to 16 bits.
7507
7635
  */
7508
- compositeDeepPixels(rect) {
7636
+ compositeDeepPixels(rect2) {
7509
7637
  const { width, height } = this.naturalSize;
7510
- const r = rect ?? { x: 0, y: 0, width, height };
7638
+ const r = rect2 ?? { x: 0, y: 0, width, height };
7511
7639
  const depth = this.bitDepth;
7512
7640
  if (depth === 8) return { width: r.width, height: r.height, depth: 16, data: to16From8(this.compositePixels(r).data) };
7513
7641
  const data = workingToSamples(compositeDeep(this.state.layers, r, this.compositorSources(), depth), depth);
@@ -7545,12 +7673,12 @@ var ImageEditorController = class {
7545
7673
  if (!upper.visible) throw new LayerOperationError(`Layer "${upper.name || id}" is hidden; show it or delete it instead of merging.`);
7546
7674
  requireUnlocked(upper, "all", "merge it");
7547
7675
  requireUnlocked(lower, "pixels", "merge into it");
7548
- let rect = unionRect(this.paintBounds(lower), upper.type === "adjustment" ? null : this.paintBounds(upper)) ?? { x: 0, y: 0, width: 1, height: 1 };
7676
+ let rect2 = unionRect(this.paintBounds(lower), upper.type === "adjustment" ? null : this.paintBounds(upper)) ?? { x: 0, y: 0, width: 1, height: 1 };
7549
7677
  const reach = Math.max(effectsMargin(lower.effects), upper.type === "adjustment" ? 0 : effectsMargin(upper.effects));
7550
- if (reach) rect = { x: rect.x - reach, y: rect.y - reach, width: rect.width + 2 * reach, height: rect.height + 2 * reach };
7678
+ if (reach) rect2 = { x: rect2.x - reach, y: rect2.y - reach, width: rect2.width + 2 * reach, height: rect2.height + 2 * reach };
7551
7679
  const base = { ...lower, blend: "normal", opacity: 1 };
7552
7680
  delete base.clipped;
7553
- const surface = await this.compositeToSurface([base, upper], rect);
7681
+ const surface = await this.compositeToSurface([base, upper], rect2);
7554
7682
  const merged = { ...lower, surface, offset: { x: 0, y: 0 } };
7555
7683
  delete merged.mask;
7556
7684
  delete merged.fillOpacity;
@@ -7632,9 +7760,9 @@ var ImageEditorController = class {
7632
7760
  if (!surface) return null;
7633
7761
  const ox = layer.type === "pixel" ? layer.offset.x : 0;
7634
7762
  const oy = layer.type === "pixel" ? layer.offset.y : 0;
7635
- const key = `${tw}x${th}:${ox},${oy}`;
7763
+ const key2 = `${tw}x${th}:${ox},${oy}`;
7636
7764
  let byKey = this.thumbCache.get(surface);
7637
- const hit = byKey?.get(key);
7765
+ const hit = byKey?.get(key2);
7638
7766
  if (hit) return hit;
7639
7767
  const out = new Uint8ClampedArray(tw * th * 4);
7640
7768
  for (let y = 0; y < th; y++) {
@@ -7654,7 +7782,7 @@ var ImageEditorController = class {
7654
7782
  byKey = /* @__PURE__ */ new Map();
7655
7783
  this.thumbCache.set(surface, byKey);
7656
7784
  }
7657
- byKey.set(key, thumb);
7785
+ byKey.set(key2, thumb);
7658
7786
  return thumb;
7659
7787
  }
7660
7788
  /**
@@ -8249,27 +8377,27 @@ var ImageEditorController = class {
8249
8377
  copyPixels(options = {}) {
8250
8378
  const { width, height } = this.naturalSize;
8251
8379
  const sel = this.state.selection?.bounds ? this.state.selection : null;
8252
- const rect = sel?.bounds ?? { x: 0, y: 0, width, height };
8380
+ const rect2 = sel?.bounds ?? { x: 0, y: 0, width, height };
8253
8381
  let data;
8254
8382
  if (options.merged) {
8255
- data = new Uint8ClampedArray(this.compositePixels(rect).data);
8383
+ data = new Uint8ClampedArray(this.compositePixels(rect2).data);
8256
8384
  } else {
8257
8385
  const id = options.layerId ?? this.state.activeLayerId;
8258
8386
  if (!id) throw new LayerOperationError("No layer given and none is active; pass layerId, or merged: true to copy the visible image.");
8259
8387
  const layer = requireLayer(this.state.layers, id).layer;
8260
8388
  if (layer.type !== "pixel") throw new LayerOperationError(`Layer "${layer.name || id}" is not a pixel layer; copy merged, or rasterize it first.`);
8261
- data = readRegion(this.surfaceOf(layer), { x: rect.x - layer.offset.x, y: rect.y - layer.offset.y, width: rect.width, height: rect.height });
8389
+ data = readRegion(this.surfaceOf(layer), { x: rect2.x - layer.offset.x, y: rect2.y - layer.offset.y, width: rect2.width, height: rect2.height });
8262
8390
  }
8263
8391
  if (sel) {
8264
- for (let y = 0; y < rect.height; y++) {
8265
- for (let x = 0; x < rect.width; x++) {
8266
- const k = sel.data[(rect.y + y) * sel.width + rect.x + x];
8267
- const p = (y * rect.width + x) * 4 + 3;
8392
+ for (let y = 0; y < rect2.height; y++) {
8393
+ for (let x = 0; x < rect2.width; x++) {
8394
+ const k = sel.data[(rect2.y + y) * sel.width + rect2.x + x];
8395
+ const p = (y * rect2.width + x) * 4 + 3;
8268
8396
  data[p] = Math.round(data[p] * k / 255);
8269
8397
  }
8270
8398
  }
8271
8399
  }
8272
- this.clipboard = { width: rect.width, height: rect.height, x: rect.x, y: rect.y, data };
8400
+ this.clipboard = { width: rect2.width, height: rect2.height, x: rect2.x, y: rect2.y, data };
8273
8401
  return this.clipboard;
8274
8402
  }
8275
8403
  /** Edit › Cut: Copy, then clear the selected pixels, as one undo step. */
@@ -8753,8 +8881,8 @@ var ImageEditorController = class {
8753
8881
  if (!Number.isFinite(position) || at < 0 || at >= max) {
8754
8882
  throw new RangeError(`${orientation === "row" ? "y" : "x"} ${position} is outside the image (0..${max - 1}).`);
8755
8883
  }
8756
- const rect = orientation === "row" ? { x: 0, y: at, width, height: 1 } : { x: at, y: 0, width: 1, height };
8757
- return this.shapeCommand(rectSelection(width, height, rect, { antiAlias: false }), options);
8884
+ const rect2 = orientation === "row" ? { x: 0, y: at, width, height: 1 } : { x: at, y: 0, width: 1, height };
8885
+ return this.shapeCommand(rectSelection(width, height, rect2, { antiAlias: false }), options);
8758
8886
  }
8759
8887
  /** Select › Grow: adds connected pixels within `tolerance` of a selected colour (one undo step). */
8760
8888
  async growSelection(options = {}) {
@@ -8810,14 +8938,14 @@ var ImageEditorController = class {
8810
8938
  return this.setSelection(shaped, options.mode ?? "replace");
8811
8939
  }
8812
8940
  /** Rectangular marquee in natural image pixels. */
8813
- selectRect(rect, options = {}) {
8941
+ selectRect(rect2, options = {}) {
8814
8942
  const { width, height } = this.naturalSize;
8815
- return this.shapeCommand(rectSelection(width, height, rect, options), options);
8943
+ return this.shapeCommand(rectSelection(width, height, rect2, options), options);
8816
8944
  }
8817
8945
  /** Elliptical marquee inscribed in `rect`. */
8818
- selectEllipse(rect, options = {}) {
8946
+ selectEllipse(rect2, options = {}) {
8819
8947
  const { width, height } = this.naturalSize;
8820
- return this.shapeCommand(ellipseSelection(width, height, rect, options), options);
8948
+ return this.shapeCommand(ellipseSelection(width, height, rect2, options), options);
8821
8949
  }
8822
8950
  /** Lasso / polygonal lasso. */
8823
8951
  selectPolygon(points, options = {}) {
@@ -10036,15 +10164,15 @@ var ImageEditorController = class {
10036
10164
  let mask;
10037
10165
  if (!b || x1 <= x0 || y1 <= y0) mask = createSelectionMask(width, height);
10038
10166
  else {
10039
- const rect = { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
10040
- const rgba = readRegion(layer.surface, rect);
10041
- const bytes = new Uint8Array(rect.width * rect.height);
10167
+ const rect2 = { x: x0, y: y0, width: x1 - x0, height: y1 - y0 };
10168
+ const rgba = readRegion(layer.surface, rect2);
10169
+ const bytes = new Uint8Array(rect2.width * rect2.height);
10042
10170
  const hard = init.antiAlias === false;
10043
10171
  for (let i = 0; i < bytes.length; i++) {
10044
10172
  const a = rgba[i * 4 + 3];
10045
10173
  bytes[i] = hard ? a >= 128 ? 255 : 0 : a;
10046
10174
  }
10047
- mask = selectionFromRegion(width, height, rect, bytes);
10175
+ mask = selectionFromRegion(width, height, rect2, bytes);
10048
10176
  }
10049
10177
  if ((init.feather ?? 0) > 0 && mask.bounds) mask = modifySelection(mask, "feather", init.feather);
10050
10178
  return this.setSelection(mask.bounds ? mask : null, init.mode ?? "replace");
@@ -10403,8 +10531,8 @@ var ImageEditorController = class {
10403
10531
  delete content.mask;
10404
10532
  delete content.clipped;
10405
10533
  delete content.fillOpacity;
10406
- const rect = spatial ? canvas : this.getLayerBounds(id) ?? canvas;
10407
- const surface = await this.compositeToSurface([content], rect);
10534
+ const rect2 = spatial ? canvas : this.getLayerBounds(id) ?? canvas;
10535
+ const surface = await this.compositeToSurface([content], rect2);
10408
10536
  next = { ...layer, surface, offset: { x: 0, y: 0 } };
10409
10537
  delete next.filters;
10410
10538
  delete next.source;
@@ -12046,7 +12174,7 @@ function layersOf(args) {
12046
12174
  add2(args.layerId);
12047
12175
  add2(args.id);
12048
12176
  add2(args.target);
12049
- for (const key of ["ids", "layerIds"]) if (Array.isArray(args[key])) args[key].forEach(add2);
12177
+ for (const key2 of ["ids", "layerIds"]) if (Array.isArray(args[key2])) args[key2].forEach(add2);
12050
12178
  return out.slice(0, 6);
12051
12179
  }
12052
12180
  function planImageToolPlayback(tool, args) {
@@ -12059,9 +12187,9 @@ function planImageToolPlayback(tool, args) {
12059
12187
  else if (box) steps.push({ type: "move", to: box.from }, { type: "press" }, { type: "drag", to: box.to }, { type: "release" });
12060
12188
  else if (num(args.x) && num(args.y)) steps.push({ type: "move", to: { x: args.x, y: args.y } }, { type: "press" }, { type: "release" });
12061
12189
  else if (tool === "set_adjustments" && args.values && typeof args.values === "object") {
12062
- for (const key of Object.keys(args.values).slice(0, 4)) {
12063
- const m = /^layers\.([^.]+)\.(.+)$/.exec(key);
12064
- steps.push({ type: "target", ref: m ? { kind: "control", id: m[2] } : { kind: "control", id: key } });
12190
+ for (const key2 of Object.keys(args.values).slice(0, 4)) {
12191
+ const m = /^layers\.([^.]+)\.(.+)$/.exec(key2);
12192
+ steps.push({ type: "target", ref: m ? { kind: "control", id: m[2] } : { kind: "control", id: key2 } });
12065
12193
  }
12066
12194
  } else for (const id of layersOf(args)) steps.push({ type: "target", ref: { kind: IMAGE_PLAYBACK_KIND, id } });
12067
12195
  if (typeof args.text === "string" && args.text.trim()) {
@@ -12237,10 +12365,10 @@ function sampleOptions(args) {
12237
12365
  return { sampleMerged: args.sampleMerged === true, sample: args.sample ?? "composite" };
12238
12366
  }
12239
12367
  function rectOf(args) {
12240
- const rect = { x: num2(args.x, NaN), y: num2(args.y, NaN), width: num2(args.width, NaN), height: num2(args.height, NaN) };
12241
- if (![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite)) throw new Error("Pass numeric x, y, width and height in natural image pixels.");
12242
- if (rect.width <= 0 || rect.height <= 0) throw new Error("width and height must be greater than 0.");
12243
- return rect;
12368
+ const rect2 = { x: num2(args.x, NaN), y: num2(args.y, NaN), width: num2(args.width, NaN), height: num2(args.height, NaN) };
12369
+ if (![rect2.x, rect2.y, rect2.width, rect2.height].every(Number.isFinite)) throw new Error("Pass numeric x, y, width and height in natural image pixels.");
12370
+ if (rect2.width <= 0 || rect2.height <= 0) throw new Error("width and height must be greater than 0.");
12371
+ return rect2;
12244
12372
  }
12245
12373
  function summary(controller) {
12246
12374
  const sel = controller.getSelection();
@@ -12315,12 +12443,12 @@ function createImageSelectionTools(controller) {
12315
12443
  break;
12316
12444
  case "rect":
12317
12445
  case "ellipse": {
12318
- const rect = { x: num2(x, NaN), y: num2(y, NaN), width: num2(width, NaN), height: num2(height, NaN) };
12319
- if (![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite)) {
12446
+ const rect2 = { x: num2(x, NaN), y: num2(y, NaN), width: num2(width, NaN), height: num2(height, NaN) };
12447
+ if (![rect2.x, rect2.y, rect2.width, rect2.height].every(Number.isFinite)) {
12320
12448
  throw new Error("rect and ellipse need numeric x, y, width and height.");
12321
12449
  }
12322
- if (shape === "rect") controller.selectRect(rect, opts);
12323
- else controller.selectEllipse(rect, opts);
12450
+ if (shape === "rect") controller.selectRect(rect2, opts);
12451
+ else controller.selectEllipse(rect2, opts);
12324
12452
  break;
12325
12453
  }
12326
12454
  case "polygon": {
@@ -12691,10 +12819,10 @@ function createImageSelectionTools(controller) {
12691
12819
  },
12692
12820
  annotations: { ...writeAnnotations("Object Selection"), openWorldHint: true },
12693
12821
  handler: async (args, context) => {
12694
- const rect = args.rect;
12822
+ const rect2 = args.rect;
12695
12823
  const point = args.point;
12696
- if (!rect && !point) throw new Error("Pass rect (a box around the object) or point (a point on it).");
12697
- if (rect && point) throw new Error("Pass rect or point, not both.");
12824
+ if (!rect2 && !point) throw new Error("Pass rect (a box around the object) or point (a point on it).");
12825
+ if (rect2 && point) throw new Error("Pass rect or point, not both.");
12698
12826
  const options = {
12699
12827
  mode: parseSelectionMode(args.mode),
12700
12828
  sampleAllLayers: args.sampleAllLayers,
@@ -12702,7 +12830,7 @@ function createImageSelectionTools(controller) {
12702
12830
  useModel: args.useModel,
12703
12831
  signal: context?.signal
12704
12832
  };
12705
- const { method } = rect ? await controller.selectObject({ rect: { x: num2(rect.x, NaN), y: num2(rect.y, NaN), width: num2(rect.width, NaN), height: num2(rect.height, NaN) } }, options) : await controller.selectObject({ point: { x: num2(point.x, NaN), y: num2(point.y, NaN) } }, options);
12833
+ const { method } = rect2 ? await controller.selectObject({ rect: { x: num2(rect2.x, NaN), y: num2(rect2.y, NaN), width: num2(rect2.width, NaN), height: num2(rect2.height, NaN) } }, options) : await controller.selectObject({ point: { x: num2(point.x, NaN), y: num2(point.y, NaN) } }, options);
12706
12834
  return { ...summary(controller), method };
12707
12835
  }
12708
12836
  };
@@ -13454,7 +13582,7 @@ async function renderImageRegion(controller, request) {
13454
13582
  out.height = request.height;
13455
13583
  const context = out.getContext("2d");
13456
13584
  if (!context) throw new Error(NO_CANVAS);
13457
- const { rect } = request;
13585
+ const { rect: rect2 } = request;
13458
13586
  context.imageSmoothingEnabled = request.scale < 1;
13459
13587
  if (request.format === "jpeg") {
13460
13588
  context.fillStyle = "#ffffff";
@@ -13462,22 +13590,22 @@ async function renderImageRegion(controller, request) {
13462
13590
  }
13463
13591
  const kx = view.width / state.imageDimensions.width;
13464
13592
  const ky = view.height / state.imageDimensions.height;
13465
- context.drawImage(view, rect.x * kx, rect.y * ky, rect.width * kx, rect.height * ky, 0, 0, out.width, out.height);
13593
+ context.drawImage(view, rect2.x * kx, rect2.y * ky, rect2.width * kx, rect2.height * ky, 0, 0, out.width, out.height);
13466
13594
  if (request.includeOverlays) {
13467
13595
  const s = request.scale;
13468
13596
  const maskView = maskViewCanvas(controller, document);
13469
- if (maskView) context.drawImage(maskView, rect.x, rect.y, rect.width, rect.height, 0, 0, out.width, out.height);
13597
+ if (maskView) context.drawImage(maskView, rect2.x, rect2.y, rect2.width, rect2.height, 0, 0, out.width, out.height);
13470
13598
  const selection = state.selection;
13471
13599
  if (selection?.bounds && selection.width === state.imageDimensions.width && selection.height === state.imageDimensions.height) {
13472
13600
  const shade = selectionShade(selection, document);
13473
- if (shade) context.drawImage(shade, rect.x, rect.y, rect.width, rect.height, 0, 0, out.width, out.height);
13601
+ if (shade) context.drawImage(shade, rect2.x, rect2.y, rect2.width, rect2.height, 0, 0, out.width, out.height);
13474
13602
  }
13475
13603
  if (state.crop) {
13476
13604
  context.save();
13477
13605
  context.strokeStyle = "#ffd400";
13478
13606
  context.lineWidth = 2;
13479
13607
  context.setLineDash([6, 4]);
13480
- context.strokeRect((state.crop.x - rect.x) * s, (state.crop.y - rect.y) * s, state.crop.width * s, state.crop.height * s);
13608
+ context.strokeRect((state.crop.x - rect2.x) * s, (state.crop.y - rect2.y) * s, state.crop.width * s, state.crop.height * s);
13481
13609
  context.restore();
13482
13610
  }
13483
13611
  }
@@ -14779,19 +14907,19 @@ var SelectionGesture = class {
14779
14907
  const { tool } = this.getOptions();
14780
14908
  if (tool === "quick") return null;
14781
14909
  if (tool === "lasso") return this.points.length > 1 ? this.points.slice() : null;
14782
- const rect = normalizeRect({
14910
+ const rect2 = normalizeRect({
14783
14911
  x: this.start.x,
14784
14912
  y: this.start.y,
14785
14913
  width: this.current.x - this.start.x,
14786
14914
  height: this.current.y - this.start.y
14787
14915
  });
14788
- if (rect.width < 1 && rect.height < 1) return null;
14789
- if (tool === "ellipse") return ellipsePoints(rect);
14916
+ if (rect2.width < 1 && rect2.height < 1) return null;
14917
+ if (tool === "ellipse") return ellipsePoints(rect2);
14790
14918
  return [
14791
- { x: rect.x, y: rect.y },
14792
- { x: rect.x + rect.width, y: rect.y },
14793
- { x: rect.x + rect.width, y: rect.y + rect.height },
14794
- { x: rect.x, y: rect.y + rect.height }
14919
+ { x: rect2.x, y: rect2.y },
14920
+ { x: rect2.x + rect2.width, y: rect2.y },
14921
+ { x: rect2.x + rect2.width, y: rect2.y + rect2.height },
14922
+ { x: rect2.x, y: rect2.y + rect2.height }
14795
14923
  ];
14796
14924
  }
14797
14925
  /** Pointer down. Resolves once a wand click has been applied. */
@@ -14947,11 +15075,11 @@ var SelectionGesture = class {
14947
15075
  const options = this.getOptions();
14948
15076
  const mode = selectionModeFromModifiers(modifiers ?? this.modifiers, options.mode);
14949
15077
  if (options.tool === "object") {
14950
- const rect2 = normalizeRect({ x: this.start.x, y: this.start.y, width: this.current.x - this.start.x, height: this.current.y - this.start.y });
15078
+ const rect3 = normalizeRect({ x: this.start.x, y: this.start.y, width: this.current.x - this.start.x, height: this.current.y - this.start.y });
14951
15079
  this.start = null;
14952
15080
  this.current = null;
14953
15081
  this.points = [];
14954
- const target = rect2.width >= 4 && rect2.height >= 4 ? { rect: rect2 } : { point: { x: rect2.x + rect2.width / 2, y: rect2.y + rect2.height / 2 } };
15082
+ const target = rect3.width >= 4 && rect3.height >= 4 ? { rect: rect3 } : { point: { x: rect3.x + rect3.width / 2, y: rect3.y + rect3.height / 2 } };
14955
15083
  this.pending = this.controller.selectObject(target, { mode, sampleAllLayers: options.objectSampleAll ?? true, enhanceEdge: options.objectEnhance ?? false }).then(
14956
15084
  () => void 0,
14957
15085
  (err) => {
@@ -14981,7 +15109,7 @@ var SelectionGesture = class {
14981
15109
  return false;
14982
15110
  }
14983
15111
  const path = this.draftPath;
14984
- const rect = normalizeRect({
15112
+ const rect2 = normalizeRect({
14985
15113
  x: this.start.x,
14986
15114
  y: this.start.y,
14987
15115
  width: this.current.x - this.start.x,
@@ -14993,7 +15121,7 @@ var SelectionGesture = class {
14993
15121
  if (!path) return mode === "replace" ? this.controller.deselect() : false;
14994
15122
  const opts = { mode, antiAlias: options.antiAlias, feather: options.feather ?? 0 };
14995
15123
  if (options.tool === "lasso") return path.length >= 3 ? this.controller.selectPolygon(path, opts) : false;
14996
- return options.tool === "ellipse" ? this.controller.selectEllipse(rect, opts) : this.controller.selectRect(rect, opts);
15124
+ return options.tool === "ellipse" ? this.controller.selectEllipse(rect2, opts) : this.controller.selectRect(rect2, opts);
14997
15125
  }
14998
15126
  /** Closes a polygonal lasso and selects it. Returns true when the selection changed. */
14999
15127
  close() {
@@ -15017,11 +15145,11 @@ var SelectionGesture = class {
15017
15145
  * Keys for a polygonal lasso being drawn: Enter closes it, Escape cancels, Backspace / Delete
15018
15146
  * removes the last corner. Returns true when the key was used (the host prevents its default).
15019
15147
  */
15020
- key(key) {
15148
+ key(key2) {
15021
15149
  if (!this.polygon && !this.magnetic) return false;
15022
- if (key === "Enter") this.close();
15023
- else if (key === "Escape") this.cancel();
15024
- else if (key === "Backspace" || key === "Delete") {
15150
+ if (key2 === "Enter") this.close();
15151
+ else if (key2 === "Escape") this.cancel();
15152
+ else if (key2 === "Backspace" || key2 === "Delete") {
15025
15153
  const m = this.magnetic;
15026
15154
  if (m) {
15027
15155
  m.anchors.pop();
@@ -15121,7 +15249,7 @@ var SelectionGesture = class {
15121
15249
  };
15122
15250
  function handleSelectionShortcut(controller, e, onError) {
15123
15251
  if (!(e.ctrlKey || e.metaKey)) return false;
15124
- const key = e.key.toLowerCase();
15252
+ const key2 = e.key.toLowerCase();
15125
15253
  const run = (fn) => {
15126
15254
  try {
15127
15255
  fn();
@@ -15130,11 +15258,11 @@ function handleSelectionShortcut(controller, e, onError) {
15130
15258
  }
15131
15259
  return true;
15132
15260
  };
15133
- if (key === "c") return run(() => controller.copyPixels({ merged: Boolean(e.shiftKey) }));
15134
- if (key === "x" && !e.shiftKey) return run(() => controller.cutPixels());
15135
- if (key === "v") return run(() => controller.pastePixels(e.shiftKey && e.altKey ? "into" : e.shiftKey ? "in-place" : "new-layer"));
15136
- if (key === "d") return run(() => e.shiftKey ? controller.reselect() : controller.deselect());
15137
- if (key === "i" && e.shiftKey) return run(() => controller.invertSelection());
15261
+ if (key2 === "c") return run(() => controller.copyPixels({ merged: Boolean(e.shiftKey) }));
15262
+ if (key2 === "x" && !e.shiftKey) return run(() => controller.cutPixels());
15263
+ if (key2 === "v") return run(() => controller.pastePixels(e.shiftKey && e.altKey ? "into" : e.shiftKey ? "in-place" : "new-layer"));
15264
+ if (key2 === "d") return run(() => e.shiftKey ? controller.reselect() : controller.deselect());
15265
+ if (key2 === "i" && e.shiftKey) return run(() => controller.invertSelection());
15138
15266
  return false;
15139
15267
  }
15140
15268
  function handleMaskViewKey(controller, e) {
@@ -15284,14 +15412,14 @@ function handlePaintShortcut(controller, e) {
15284
15412
  if (e.ctrlKey || e.metaKey || e.altKey || isTyping(e.target)) return false;
15285
15413
  const brush = controller.getState().paint.brush;
15286
15414
  const set = (b) => controller.setPaintOptions({ brush: b });
15287
- const key = e.key;
15415
+ const key2 = e.key;
15288
15416
  if (handleMaskViewKey(controller, e)) return true;
15289
- switch (key.toLowerCase()) {
15417
+ switch (key2.toLowerCase()) {
15290
15418
  case "q":
15291
15419
  controller.setQuickMaskMode(!controller.quickMaskEditing);
15292
15420
  return true;
15293
15421
  case "b":
15294
- controller.setTool(e.shiftKey || key === "B" ? "mixer" : "brush");
15422
+ controller.setTool(e.shiftKey || key2 === "B" ? "mixer" : "brush");
15295
15423
  return true;
15296
15424
  case "e":
15297
15425
  controller.setTool("eraser");
@@ -15303,11 +15431,11 @@ function handlePaintShortcut(controller, e) {
15303
15431
  controller.setTool("clone");
15304
15432
  return true;
15305
15433
  case "j":
15306
- controller.setTool(e.shiftKey || key === "J" ? controller.getState().activeTool === "heal" ? "spot-heal" : "heal" : "spot-heal");
15434
+ controller.setTool(e.shiftKey || key2 === "J" ? controller.getState().activeTool === "heal" ? "spot-heal" : "heal" : "spot-heal");
15307
15435
  return true;
15308
15436
  case "o": {
15309
15437
  const cur = controller.getState().activeTool;
15310
- const next = e.shiftKey || key === "O" ? cur === "dodge" ? "burn" : cur === "burn" ? "sponge" : "dodge" : "dodge";
15438
+ const next = e.shiftKey || key2 === "O" ? cur === "dodge" ? "burn" : cur === "burn" ? "sponge" : "dodge" : "dodge";
15311
15439
  controller.setTool(next);
15312
15440
  return true;
15313
15441
  }
@@ -15321,18 +15449,18 @@ function handlePaintShortcut(controller, e) {
15321
15449
  controller.swapPaintColors();
15322
15450
  return true;
15323
15451
  }
15324
- if (key === "[" || key === "{") {
15325
- if (e.shiftKey || key === "{") set({ hardness: Math.max(0, Math.round((brush.hardness - 0.25) * 4) / 4) });
15452
+ if (key2 === "[" || key2 === "{") {
15453
+ if (e.shiftKey || key2 === "{") set({ hardness: Math.max(0, Math.round((brush.hardness - 0.25) * 4) / 4) });
15326
15454
  else set({ size: Math.max(1, brush.size - sizeStep(brush.size - 1)) });
15327
15455
  return true;
15328
15456
  }
15329
- if (key === "]" || key === "}") {
15330
- if (e.shiftKey || key === "}") set({ hardness: Math.min(1, Math.round((brush.hardness + 0.25) * 4) / 4) });
15457
+ if (key2 === "]" || key2 === "}") {
15458
+ if (e.shiftKey || key2 === "}") set({ hardness: Math.min(1, Math.round((brush.hardness + 0.25) * 4) / 4) });
15331
15459
  else set({ size: Math.min(MAX_BRUSH_SIZE, brush.size + sizeStep(brush.size)) });
15332
15460
  return true;
15333
15461
  }
15334
- if (/^[0-9]$/.test(key) && isPaintTool(controller.getState().activeTool)) {
15335
- set({ opacity: key === "0" ? 1 : Number(key) / 10 });
15462
+ if (/^[0-9]$/.test(key2) && isPaintTool(controller.getState().activeTool)) {
15463
+ set({ opacity: key2 === "0" ? 1 : Number(key2) / 10 });
15336
15464
  return true;
15337
15465
  }
15338
15466
  return false;
@@ -18136,11 +18264,11 @@ function scalePixels(pixels, w, h) {
18136
18264
  return { width: w, height: h, data: out };
18137
18265
  }
18138
18266
  function scaleEdit(edit, s) {
18139
- const pt = (p) => ({ x: p.x * s, y: p.y * s });
18267
+ const pt2 = (p) => ({ x: p.x * s, y: p.y * s });
18140
18268
  if (edit.kind === "object") {
18141
- return { ...edit, rect: edit.rect && { x: edit.rect.x * s, y: edit.rect.y * s, width: edit.rect.width * s, height: edit.rect.height * s }, point: edit.point && pt(edit.point) };
18269
+ return { ...edit, rect: edit.rect && { x: edit.rect.x * s, y: edit.rect.y * s, width: edit.rect.width * s, height: edit.rect.height * s }, point: edit.point && pt2(edit.point) };
18142
18270
  }
18143
- const scaled = { ...edit, points: edit.points.map(pt) };
18271
+ const scaled = { ...edit, points: edit.points.map(pt2) };
18144
18272
  if ("size" in edit && edit.size !== void 0) scaled.size = Math.max(1, edit.size * s);
18145
18273
  return scaled;
18146
18274
  }
@@ -18432,8 +18560,8 @@ var SelectAndMaskSession = class {
18432
18560
  if (!live || !this.dragStart) return;
18433
18561
  if (live.kind === "object") {
18434
18562
  const a = this.dragStart;
18435
- const rect = { x: Math.min(a.x, point.x), y: Math.min(a.y, point.y), width: Math.abs(point.x - a.x), height: Math.abs(point.y - a.y) };
18436
- this.live = rect.width >= 4 && rect.height >= 4 ? { kind: "object", rect, mode: live.mode } : { kind: "object", point: a, mode: live.mode };
18563
+ const rect2 = { x: Math.min(a.x, point.x), y: Math.min(a.y, point.y), width: Math.abs(point.x - a.x), height: Math.abs(point.y - a.y) };
18564
+ this.live = rect2.width >= 4 && rect2.height >= 4 ? { kind: "object", rect: rect2, mode: live.mode } : { kind: "object", point: a, mode: live.mode };
18437
18565
  return;
18438
18566
  }
18439
18567
  const last = live.points[live.points.length - 1];
@@ -18470,8 +18598,8 @@ var SelectAndMaskSession = class {
18470
18598
  * Show Original), J (Show Edge), P (Show Original), a tool letter, [ and ] (brush size).
18471
18599
  * Returns true when the key was used.
18472
18600
  */
18473
- handleKey(key) {
18474
- const k = key.toLowerCase();
18601
+ handleKey(key2) {
18602
+ const k = key2.toLowerCase();
18475
18603
  const view = SELECT_AND_MASK_VIEWS.find((v) => v.key === k);
18476
18604
  if (view) {
18477
18605
  this.update({ view: view.id });
@@ -19148,9 +19276,9 @@ function gradientControls(prefix, g) {
19148
19276
  { key: `${prefix}.align`, label: "Align with layer", type: "checkbox", value: g.align }
19149
19277
  ];
19150
19278
  }
19151
- var px = (key, label, value, max = 250) => ({ key, label, type: "range", min: 0, max, step: 1, value, unit: "px" });
19152
- var pct = (key, label, value, min = 0, max = 1) => ({ key, label, type: "range", min, max, step: 0.01, value, percent: true, unit: "%" });
19153
- var deg = (key, label, value, min = -180, max = 180) => ({ key, label, type: "range", min, max, step: 1, value, unit: "\xB0" });
19279
+ var px = (key2, label, value, max = 250) => ({ key: key2, label, type: "range", min: 0, max, step: 1, value, unit: "px" });
19280
+ var pct = (key2, label, value, min = 0, max = 1) => ({ key: key2, label, type: "range", min, max, step: 0.01, value, percent: true, unit: "%" });
19281
+ var deg = (key2, label, value, min = -180, max = 180) => ({ key: key2, label, type: "range", min, max, step: 1, value, unit: "\xB0" });
19154
19282
  function layerEffectControls(e, patterns = []) {
19155
19283
  const blended = e.kind === "bevel-emboss" ? [] : [
19156
19284
  { key: "blend", label: "Blend mode", type: "select", value: e.blend, options: BLENDS },
@@ -19235,8 +19363,8 @@ function fillOfType(current, type, patterns) {
19235
19363
  if (type === "pattern") return { type: "pattern", patternId: patterns[0]?.id ?? "checker", scale: 1 };
19236
19364
  return { type: "color", color: current.type === "color" ? current.color : "#000000" };
19237
19365
  }
19238
- function effectControlChange(effect, key, value, patterns = []) {
19239
- const [top, ...rest] = key.split(".");
19366
+ function effectControlChange(effect, key2, value, patterns = []) {
19367
+ const [top, ...rest] = key2.split(".");
19240
19368
  if (top === "fill" && rest[0] === "type") return { fill: fillOfType(effect.fill, String(value), patterns) };
19241
19369
  if (top === "patternId" && !rest.length) {
19242
19370
  const name = patternOptions(patterns).find((o) => o.value === value)?.label;
@@ -20088,7 +20216,9 @@ var IMAGE_TOOL_GROUPS = [
20088
20216
  { id: "focus", label: "Blur & Sharpen", tools: ["blur", "sharpen", "smudge"] },
20089
20217
  { id: "tone", label: "Dodge & Burn", tools: ["dodge", "burn", "sponge"] },
20090
20218
  { id: "shape", label: "Shape", tools: ["shape"] },
20219
+ { id: "pen", label: "Pen", tools: ["path-pen"] },
20091
20220
  { id: "type", label: "Type", tools: ["type"] },
20221
+ { id: "path-select", label: "Path Selection", tools: ["path-select", "direct-select"] },
20092
20222
  { id: "annotate", label: "Annotate", tools: ["pen", "line", "arrow", "rect", "circle"] }
20093
20223
  ];
20094
20224
  function imageToolGroup(tool) {
@@ -20099,7 +20229,10 @@ var OTHER_TOOL_LABELS = {
20099
20229
  crop: "Crop",
20100
20230
  shape: "Shape",
20101
20231
  type: "Type",
20102
- pen: "Pen",
20232
+ "path-pen": "Pen",
20233
+ "path-select": "Path Selection",
20234
+ "direct-select": "Direct Selection",
20235
+ pen: "Freehand",
20103
20236
  line: "Line",
20104
20237
  arrow: "Arrow",
20105
20238
  rect: "Rectangle",
@@ -20115,10 +20248,13 @@ var IMAGE_TOOL_SHORTCUTS = {
20115
20248
  select: "M",
20116
20249
  crop: "C",
20117
20250
  shape: "U",
20118
- type: "T"
20251
+ type: "T",
20252
+ "path-pen": "P",
20253
+ "path-select": "A",
20254
+ "direct-select": "Shift+A"
20119
20255
  };
20120
- function imageToolForShortcut(key, shift = false) {
20121
- const shortcut = `${shift ? "Shift+" : ""}${key.toUpperCase()}`;
20256
+ function imageToolForShortcut(key2, shift = false) {
20257
+ const shortcut = `${shift ? "Shift+" : ""}${key2.toUpperCase()}`;
20122
20258
  for (const group of IMAGE_TOOL_GROUPS) {
20123
20259
  for (const tool of group.tools) if (IMAGE_TOOL_SHORTCUTS[tool] === shortcut) return tool;
20124
20260
  }
@@ -20175,6 +20311,422 @@ function createImageEditorDocument(controller, options = {}) {
20175
20311
  });
20176
20312
  }
20177
20313
 
20314
+ // src/image-editor/path-edit.ts
20315
+ var PATH_EDIT_TOOLS = ["path-select", "direct-select", "path-pen"];
20316
+ function isPathEditTool(tool) {
20317
+ return PATH_EDIT_TOOLS.includes(tool);
20318
+ }
20319
+ var key = (r) => `${r.component}:${r.subpath}:${r.anchor}`;
20320
+ var parseKey = (k) => {
20321
+ const [component, subpath, anchor] = k.split(":").map(Number);
20322
+ return { component, subpath, anchor };
20323
+ };
20324
+ var pt = (p) => ({ x: p.x, y: p.y });
20325
+ var cloneComponents = (cs) => cs.map((c) => ({ op: c.op, subpaths: c.subpaths.map((sp) => ({ anchors: sp.anchors.map(cloneAnchor), closed: sp.closed })), ...c.live ? { live: c.live } : {} }));
20326
+ var sameTarget = (a, b) => !!a && !!b && a.kind === b.kind && (a.kind === "path" ? a.id === b.id : a.layerId === b.layerId);
20327
+ var PathEditGesture = class {
20328
+ constructor(controller) {
20329
+ this.controller = controller;
20330
+ }
20331
+ controller;
20332
+ /** Selected anchors, as `component:subpath:anchor` keys. */
20333
+ selected = /* @__PURE__ */ new Set();
20334
+ target = null;
20335
+ drag = null;
20336
+ /** The Pen's open subpath being drawn. */
20337
+ drawing = null;
20338
+ pointer = null;
20339
+ scale = 1;
20340
+ error = null;
20341
+ /** Screen pixels per document pixel (the viewport projection's scale). */
20342
+ setView(scale) {
20343
+ if (scale > 0 && Number.isFinite(scale)) this.scale = scale;
20344
+ }
20345
+ get active() {
20346
+ return this.drag !== null;
20347
+ }
20348
+ /** The anchors selected now (on the current target). */
20349
+ get selection() {
20350
+ return [...this.selected].map(parseKey);
20351
+ }
20352
+ /** True while the Pen has an open subpath it adds to. */
20353
+ get isDrawing() {
20354
+ return this.drawing !== null;
20355
+ }
20356
+ get tolerance() {
20357
+ return 6 / this.scale;
20358
+ }
20359
+ /** The target's components, or null (and the selection dropped) when the target changed or is gone. */
20360
+ components() {
20361
+ const target = this.controller.pathEditTarget();
20362
+ if (!sameTarget(target, this.target)) {
20363
+ this.target = target;
20364
+ this.selected.clear();
20365
+ this.drawing = null;
20366
+ }
20367
+ if (!target) return null;
20368
+ let cs;
20369
+ try {
20370
+ cs = this.controller.getPathEditComponents(target);
20371
+ } catch {
20372
+ return null;
20373
+ }
20374
+ for (const k of [...this.selected]) {
20375
+ const r = parseKey(k);
20376
+ if (!cs[r.component]?.subpaths[r.subpath]?.anchors[r.anchor]) this.selected.delete(k);
20377
+ }
20378
+ const d = this.drawing;
20379
+ if (d && (!cs[d.component]?.subpaths[d.subpath] || cs[d.component].subpaths[d.subpath].closed)) this.drawing = null;
20380
+ return cs;
20381
+ }
20382
+ anchorAt(cs, p) {
20383
+ const tol = this.tolerance;
20384
+ let best = null;
20385
+ let bestD = tol;
20386
+ cs.forEach(
20387
+ (c, ci) => c.subpaths.forEach(
20388
+ (sp, si) => sp.anchors.forEach((a, ai) => {
20389
+ const d = Math.hypot(a.x - p.x, a.y - p.y);
20390
+ if (d <= bestD) {
20391
+ bestD = d;
20392
+ best = { component: ci, subpath: si, anchor: ai };
20393
+ }
20394
+ })
20395
+ )
20396
+ );
20397
+ return best;
20398
+ }
20399
+ /** A handle of a selected anchor near `p`. */
20400
+ handleAt(cs, p) {
20401
+ const tol = this.tolerance;
20402
+ for (const r of this.selection) {
20403
+ const a = cs[r.component].subpaths[r.subpath].anchors[r.anchor];
20404
+ for (const side of ["out", "in"]) {
20405
+ const h = a[side];
20406
+ if (h && Math.hypot(h.x - p.x, h.y - p.y) <= tol) return { ref: r, side };
20407
+ }
20408
+ }
20409
+ return null;
20410
+ }
20411
+ /** The segment near `p`: its first anchor (as `anchor`) and the parameter on it. */
20412
+ segmentAt(cs, p) {
20413
+ let best = null;
20414
+ let bestD = this.tolerance;
20415
+ cs.forEach(
20416
+ (c, ci) => c.subpaths.forEach((sp, si) => {
20417
+ for (let i = 0; i < subpathSegmentCount(sp); i++) {
20418
+ const { t, distance } = segmentNearest(subpathSegment(sp, i), [p.x, p.y]);
20419
+ if (distance <= bestD) {
20420
+ bestD = distance;
20421
+ best = { ref: { component: ci, subpath: si, anchor: i }, t };
20422
+ }
20423
+ }
20424
+ })
20425
+ );
20426
+ return best;
20427
+ }
20428
+ /** The component under `p`: on its outline, or inside its fill. */
20429
+ componentAt(cs, p) {
20430
+ const seg = this.segmentAt(cs, p) ?? (this.anchorAt(cs, p) ? { ref: this.anchorAt(cs, p) } : null);
20431
+ if (seg) return seg.ref.component;
20432
+ for (let i = cs.length - 1; i >= 0; i--) {
20433
+ const closed = cs[i].subpaths.filter((sp) => sp.anchors.length > 2);
20434
+ if (closed.length && pointInBezierPath(subpathsToBezier(closed), [p.x, p.y])) return i;
20435
+ }
20436
+ return null;
20437
+ }
20438
+ selectComponent(cs, ci) {
20439
+ cs[ci].subpaths.forEach((sp, si) => sp.anchors.forEach((_, ai) => this.selected.add(key({ component: ci, subpath: si, anchor: ai }))));
20440
+ }
20441
+ commit(cs, record) {
20442
+ if (!this.target) return false;
20443
+ try {
20444
+ this.controller.setPathEditComponents(this.target, cs, record);
20445
+ return true;
20446
+ } catch (e) {
20447
+ this.error = e instanceof Error ? e.message : String(e);
20448
+ return false;
20449
+ }
20450
+ }
20451
+ down(p, tool, mods = {}) {
20452
+ this.error = null;
20453
+ this.pointer = pt(p);
20454
+ const cs = this.components();
20455
+ if (tool === "path-pen") return this.penDown(cs, p, mods);
20456
+ if (!cs) {
20457
+ this.error = "There is no path to edit: pick a path in the Paths panel, a shape layer, or a layer with a vector mask (or draw one with the Pen).";
20458
+ return;
20459
+ }
20460
+ if (tool === "path-select") {
20461
+ const ci = this.componentAt(cs, p);
20462
+ if (ci === null) {
20463
+ this.drag = { kind: "marquee", start: pt(p), end: pt(p), keep: mods.shift ? new Set(this.selected) : /* @__PURE__ */ new Set() };
20464
+ if (!mods.shift) this.selected.clear();
20465
+ return;
20466
+ }
20467
+ const ref = { component: ci, subpath: 0, anchor: 0 };
20468
+ if (!mods.shift && !this.selected.has(key(ref))) this.selected.clear();
20469
+ this.selectComponent(cs, ci);
20470
+ this.drag = { kind: "anchors", start: pt(p), base: cloneComponents(cs), moved: false };
20471
+ return;
20472
+ }
20473
+ const handle = this.handleAt(cs, p);
20474
+ if (handle) {
20475
+ this.drag = { kind: "handle", ...handle, base: cloneComponents(cs), breakHandles: !!mods.alt, moved: false };
20476
+ return;
20477
+ }
20478
+ const anchor = this.anchorAt(cs, p);
20479
+ if (anchor) {
20480
+ if (mods.alt) return this.convert(cs, anchor);
20481
+ const k = key(anchor);
20482
+ if (mods.shift) {
20483
+ if (this.selected.has(k)) this.selected.delete(k);
20484
+ else this.selected.add(k);
20485
+ } else if (!this.selected.has(k)) {
20486
+ this.selected.clear();
20487
+ this.selected.add(k);
20488
+ }
20489
+ this.drag = { kind: "anchors", start: pt(p), base: cloneComponents(cs), moved: false };
20490
+ return;
20491
+ }
20492
+ const seg = this.segmentAt(cs, p);
20493
+ if (seg) {
20494
+ if (!mods.shift) this.selected.clear();
20495
+ this.drag = { kind: "segment", ...seg, base: cloneComponents(cs), moved: false };
20496
+ return;
20497
+ }
20498
+ this.drag = { kind: "marquee", start: pt(p), end: pt(p), keep: mods.shift ? new Set(this.selected) : /* @__PURE__ */ new Set() };
20499
+ if (!mods.shift) this.selected.clear();
20500
+ }
20501
+ /** Alt-click: a corner anchor becomes smooth, a smooth one a corner (one undo step). */
20502
+ convert(cs, r) {
20503
+ const next = cloneComponents(cs);
20504
+ const c = next[r.component];
20505
+ const sp = c.subpaths[r.subpath];
20506
+ const a = sp.anchors[r.anchor];
20507
+ const type = inferAnchorType(a) === "corner" && !a.in && !a.out ? "smooth" : "corner";
20508
+ c.subpaths[r.subpath] = convertAnchor(sp, r.anchor, type);
20509
+ delete c.live;
20510
+ this.selected.clear();
20511
+ this.selected.add(key(r));
20512
+ this.commit(next, true);
20513
+ }
20514
+ penDown(cs, p, mods) {
20515
+ const d = this.drawing;
20516
+ if (cs && d) {
20517
+ const next = cloneComponents(cs);
20518
+ const c = next[d.component];
20519
+ const sp = c.subpaths[d.subpath];
20520
+ const first = sp.anchors[0];
20521
+ delete c.live;
20522
+ if (sp.anchors.length > 1 && Math.hypot(first.x - p.x, first.y - p.y) <= this.tolerance) {
20523
+ sp.closed = true;
20524
+ this.drawing = null;
20525
+ const ref3 = { component: d.component, subpath: d.subpath, anchor: 0 };
20526
+ if (this.commit(next, true)) this.drag = { kind: "pen", ref: ref3, base: next };
20527
+ return;
20528
+ }
20529
+ sp.anchors.push({ x: p.x, y: p.y });
20530
+ const ref2 = { component: d.component, subpath: d.subpath, anchor: sp.anchors.length - 1 };
20531
+ this.selected = /* @__PURE__ */ new Set([key(ref2)]);
20532
+ if (this.commit(next, true)) this.drag = { kind: "pen", ref: ref2, base: next };
20533
+ return;
20534
+ }
20535
+ if (cs) {
20536
+ const anchor = this.anchorAt(cs, p);
20537
+ if (anchor && mods.alt) return this.convert(cs, anchor);
20538
+ if (anchor) {
20539
+ const next = cloneComponents(cs);
20540
+ const c = next[anchor.component];
20541
+ const sp = c.subpaths[anchor.subpath];
20542
+ delete c.live;
20543
+ c.subpaths[anchor.subpath] = sp.anchors.length > 2 ? removeAnchorsKeepingShape(sp, [anchor.anchor]) : removeAnchors(sp, [anchor.anchor]);
20544
+ this.selected.clear();
20545
+ this.commit(prune(next), true);
20546
+ return;
20547
+ }
20548
+ const seg = this.segmentAt(cs, p);
20549
+ if (seg) {
20550
+ const next = cloneComponents(cs);
20551
+ const c = next[seg.ref.component];
20552
+ const { subpath, index } = insertAnchor(c.subpaths[seg.ref.subpath], seg.ref.anchor, seg.t);
20553
+ c.subpaths[seg.ref.subpath] = subpath;
20554
+ delete c.live;
20555
+ this.selected = /* @__PURE__ */ new Set([key({ ...seg.ref, anchor: index })]);
20556
+ this.commit(next, true);
20557
+ return;
20558
+ }
20559
+ }
20560
+ const start = { anchors: [{ x: p.x, y: p.y }], closed: false };
20561
+ if (!this.target || !cs) {
20562
+ try {
20563
+ const path = this.controller.startWorkPath([{ op: "combine", subpaths: [start] }]);
20564
+ this.target = { kind: "path", id: path.id };
20565
+ } catch (e) {
20566
+ this.error = e instanceof Error ? e.message : String(e);
20567
+ return;
20568
+ }
20569
+ this.drawing = { component: 0, subpath: 0 };
20570
+ } else {
20571
+ const next = cloneComponents(cs);
20572
+ if (!next.length) next.push({ op: "combine", subpaths: [] });
20573
+ const ci = next.length - 1;
20574
+ delete next[ci].live;
20575
+ next[ci].subpaths.push(start);
20576
+ if (!this.commit(next, true)) return;
20577
+ this.drawing = { component: ci, subpath: next[ci].subpaths.length - 1 };
20578
+ }
20579
+ const ref = { component: this.drawing.component, subpath: this.drawing.subpath, anchor: 0 };
20580
+ this.selected = /* @__PURE__ */ new Set([key(ref)]);
20581
+ this.drag = { kind: "pen", ref, base: cloneComponents(this.controller.getPathEditComponents(this.target)) };
20582
+ }
20583
+ move(p, mods = {}) {
20584
+ this.pointer = pt(p);
20585
+ const g = this.drag;
20586
+ if (!g) return;
20587
+ if (g.kind === "marquee") {
20588
+ g.end = pt(p);
20589
+ const box = rect(g.start, g.end);
20590
+ const cs = this.target ? this.controller.getPathEditComponents(this.target) : [];
20591
+ this.selected = new Set(g.keep);
20592
+ cs.forEach(
20593
+ (c, ci) => c.subpaths.forEach(
20594
+ (sp, si) => sp.anchors.forEach((a, ai) => {
20595
+ if (a.x >= box.x && a.x <= box.x + box.width && a.y >= box.y && a.y <= box.y + box.height) this.selected.add(key({ component: ci, subpath: si, anchor: ai }));
20596
+ })
20597
+ )
20598
+ );
20599
+ return;
20600
+ }
20601
+ const next = cloneComponents(g.base);
20602
+ if (g.kind === "anchors") {
20603
+ let dx = p.x - g.start.x;
20604
+ let dy = p.y - g.start.y;
20605
+ if (!g.moved && Math.hypot(dx, dy) < 1 / this.scale) return;
20606
+ if (mods.shift) {
20607
+ if (Math.abs(dx) > Math.abs(dy)) dy = 0;
20608
+ else dx = 0;
20609
+ }
20610
+ for (const r of this.selection) {
20611
+ const c = next[r.component];
20612
+ c.subpaths[r.subpath].anchors[r.anchor] = translateAnchor(c.subpaths[r.subpath].anchors[r.anchor], dx, dy);
20613
+ delete c.live;
20614
+ }
20615
+ } else if (g.kind === "handle") {
20616
+ const c = next[g.ref.component];
20617
+ const a = c.subpaths[g.ref.subpath].anchors[g.ref.anchor];
20618
+ const typed = { ...a, type: a.type ?? inferAnchorType(a) };
20619
+ const moved = moveAnchorHandle(typed, g.side, p, g.breakHandles);
20620
+ delete moved.type;
20621
+ c.subpaths[g.ref.subpath].anchors[g.ref.anchor] = moved;
20622
+ delete c.live;
20623
+ } else if (g.kind === "segment") {
20624
+ const c = next[g.ref.component];
20625
+ c.subpaths[g.ref.subpath] = bendSegment(c.subpaths[g.ref.subpath], g.ref.anchor, g.t, p);
20626
+ delete c.live;
20627
+ } else {
20628
+ const c = next[g.ref.component];
20629
+ const a = c.subpaths[g.ref.subpath].anchors[g.ref.anchor];
20630
+ if (Math.hypot(p.x - a.x, p.y - a.y) < 1 / this.scale) return;
20631
+ a.out = { x: p.x, y: p.y };
20632
+ a.in = { x: 2 * a.x - p.x, y: 2 * a.y - p.y };
20633
+ delete c.live;
20634
+ this.commit(next, false);
20635
+ return;
20636
+ }
20637
+ if (this.commit(next, !g.moved)) g.moved = true;
20638
+ }
20639
+ /** The pointer moving with no button down (the Pen's preview of its next segment). */
20640
+ hover(p) {
20641
+ this.pointer = pt(p);
20642
+ }
20643
+ up() {
20644
+ this.drag = null;
20645
+ }
20646
+ /** Ends the Pen's subpath (left open). */
20647
+ finish() {
20648
+ this.drawing = null;
20649
+ this.drag = null;
20650
+ }
20651
+ cancel() {
20652
+ this.drag = null;
20653
+ this.drawing = null;
20654
+ this.selected.clear();
20655
+ }
20656
+ /**
20657
+ * Keys: Delete / Backspace remove the selected anchors (one undo step), Enter and Escape end the
20658
+ * Pen's subpath, Escape also deselects. Returns true when the key was handled.
20659
+ */
20660
+ key(k) {
20661
+ if (k === "Enter" && this.drawing) {
20662
+ this.finish();
20663
+ return true;
20664
+ }
20665
+ if (k === "Escape") {
20666
+ if (!this.drawing && !this.selected.size) return false;
20667
+ this.drawing = null;
20668
+ this.selected.clear();
20669
+ return true;
20670
+ }
20671
+ if ((k === "Delete" || k === "Backspace") && this.selected.size && !this.drawing) {
20672
+ const cs = this.components();
20673
+ if (!cs) return false;
20674
+ const next = cloneComponents(cs);
20675
+ const bySub = /* @__PURE__ */ new Map();
20676
+ for (const r of this.selection) {
20677
+ const sk = `${r.component}:${r.subpath}`;
20678
+ bySub.set(sk, [...bySub.get(sk) ?? [], r.anchor]);
20679
+ }
20680
+ for (const [sk, indices] of bySub) {
20681
+ const [ci, si] = sk.split(":").map(Number);
20682
+ next[ci].subpaths[si] = removeAnchors(next[ci].subpaths[si], indices);
20683
+ delete next[ci].live;
20684
+ }
20685
+ this.selected.clear();
20686
+ this.commit(prune(next), true);
20687
+ return true;
20688
+ }
20689
+ return false;
20690
+ }
20691
+ /** What to draw (`RenderOptions.pathEdit`) for `tool`, or null when there is no target. */
20692
+ overlay(tool) {
20693
+ const cs = this.components();
20694
+ if (!cs) return null;
20695
+ const curves = [];
20696
+ const anchors = [];
20697
+ const handles = [];
20698
+ const selectedComponents = new Set(this.selection.map((r) => r.component));
20699
+ cs.forEach(
20700
+ (c, ci) => c.subpaths.forEach((sp, si) => {
20701
+ for (let i = 0; i < subpathSegmentCount(sp); i++) curves.push(subpathSegment(sp, i).map(([x, y]) => ({ x, y })));
20702
+ sp.anchors.forEach((a, ai) => {
20703
+ const selected = this.selected.has(key({ component: ci, subpath: si, anchor: ai }));
20704
+ if (tool === "path-select" && !selectedComponents.has(ci)) return;
20705
+ anchors.push({ point: pt(a), selected });
20706
+ if (!selected || tool === "path-select") return;
20707
+ for (const side of ["in", "out"]) if (a[side]) handles.push({ point: pt(a[side]), anchor: pt(a) });
20708
+ });
20709
+ })
20710
+ );
20711
+ let preview = null;
20712
+ const d = this.drawing;
20713
+ if (tool === "path-pen" && d && this.pointer && !this.drag) {
20714
+ const sp = cs[d.component]?.subpaths[d.subpath];
20715
+ const last = sp?.anchors[sp.anchors.length - 1];
20716
+ if (last) preview = last.out ? [pt(last), pt(last.out), this.pointer, this.pointer] : [pt(last), this.pointer];
20717
+ }
20718
+ const marquee = this.drag?.kind === "marquee" ? rect(this.drag.start, this.drag.end) : null;
20719
+ return { curves, anchors, handles, marquee, preview };
20720
+ }
20721
+ };
20722
+ function rect(a, b) {
20723
+ return { x: Math.min(a.x, b.x), y: Math.min(a.y, b.y), width: Math.abs(b.x - a.x), height: Math.abs(b.y - a.y) };
20724
+ }
20725
+ function prune(cs) {
20726
+ for (const c of cs) c.subpaths = c.subpaths.filter((sp) => sp.anchors.length);
20727
+ return cs.filter((c) => c.subpaths.length);
20728
+ }
20729
+
20178
20730
  // src/image-editor/content-aware-fill-session.ts
20179
20731
  var CONTENT_AWARE_FILL_TOOLS = [
20180
20732
  { id: "sampling-brush", label: "Sampling Brush", key: "b" },
@@ -20466,8 +21018,8 @@ var ContentAwareFillSession = class _ContentAwareFillSession {
20466
21018
  /**
20467
21019
  * Keys: B, H, Z for the tools, [ and ] for the brush size. Returns true when the key was used.
20468
21020
  */
20469
- handleKey(key) {
20470
- const k = key.toLowerCase();
21021
+ handleKey(key2) {
21022
+ const k = key2.toLowerCase();
20471
21023
  const tool = CONTENT_AWARE_FILL_TOOLS.find((t) => t.key === k);
20472
21024
  if (tool) {
20473
21025
  this.update({ tool: tool.id });
@@ -20646,6 +21198,7 @@ export {
20646
21198
  PARAM,
20647
21199
  PARAM_FLOATS,
20648
21200
  PASTE_MODES,
21201
+ PATH_EDIT_TOOLS,
20649
21202
  PATTERN_BLOCK_KEYS,
20650
21203
  PIGMENT_BANDS,
20651
21204
  PIGMENT_CHANNELS,
@@ -20675,6 +21228,7 @@ export {
20675
21228
  PaintSourceError,
20676
21229
  PaintStrokeSession,
20677
21230
  PaperGrain,
21231
+ PathEditGesture,
20678
21232
  PatternPlacement,
20679
21233
  PatternTile,
20680
21234
  PsdError,
@@ -21122,6 +21676,7 @@ export {
21122
21676
  isPaintTool,
21123
21677
  isPaintableLayer,
21124
21678
  isPainterlyBlendMode,
21679
+ isPathEditTool,
21125
21680
  isPng,
21126
21681
  isQuarterTurn,
21127
21682
  isQuarterTurnSwapped,