@pixerate/editor 0.211.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,6 +1,18 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
3
 
4
+
5
+
6
+
7
+
8
+ var _chunkQAUIZPYIcjs = require('./chunk-QAUIZPYI.cjs');
9
+
10
+
11
+ var _chunkGF2RBSDMcjs = require('./chunk-GF2RBSDM.cjs');
12
+
13
+
14
+
15
+
4
16
  var _chunkNM3G4VQTcjs = require('./chunk-NM3G4VQT.cjs');
5
17
 
6
18
  // src/vector/trace.ts
@@ -1342,6 +1354,412 @@ async function unwoff(view) {
1342
1354
  return o;
1343
1355
  }
1344
1356
 
1357
+ // src/vector/pathEdit.ts
1358
+ var EPS = 1e-9;
1359
+ var unit = (x, y) => {
1360
+ const l = Math.hypot(x, y);
1361
+ return l > 1e-12 && Number.isFinite(l) ? [x / l, y / l] : null;
1362
+ };
1363
+ function cubicOf(sp, i) {
1364
+ const s = _chunkQAUIZPYIcjs.subpathSegment.call(void 0, sp, i);
1365
+ if (s.length === 4) return s;
1366
+ const [a, b] = s;
1367
+ return [a, [a[0] + (b[0] - a[0]) / 3, a[1] + (b[1] - a[1]) / 3], [a[0] + 2 * (b[0] - a[0]) / 3, a[1] + 2 * (b[1] - a[1]) / 3], b];
1368
+ }
1369
+ var isLine = (sp, i) => _chunkQAUIZPYIcjs.subpathSegment.call(void 0, sp, i).length === 2;
1370
+ function startTangent(c) {
1371
+ for (let k = 1; k < 4; k++) {
1372
+ const u = unit(c[k][0] - c[0][0], c[k][1] - c[0][1]);
1373
+ if (u) return u;
1374
+ }
1375
+ return [1, 0];
1376
+ }
1377
+ function endTangent(c) {
1378
+ for (let k = 2; k >= 0; k--) {
1379
+ const u = unit(c[3][0] - c[k][0], c[3][1] - c[k][1]);
1380
+ if (u) return u;
1381
+ }
1382
+ return [1, 0];
1383
+ }
1384
+ function sample(c, n, out, withStart) {
1385
+ for (let i = withStart ? 0 : 1; i <= n; i++) out.push(_chunkGF2RBSDMcjs.segmentPoint.call(void 0, c, i / n));
1386
+ }
1387
+ var onAnchor = (a, h) => !h || Math.abs(h.x - a.x) < EPS && Math.abs(h.y - a.y) < EPS;
1388
+ function tidy(a, keepType = true) {
1389
+ const next = _chunkQAUIZPYIcjs.cloneAnchor.call(void 0, a);
1390
+ if (onAnchor(next, next.in)) delete next.in;
1391
+ if (onAnchor(next, next.out)) delete next.out;
1392
+ const shown = _chunkQAUIZPYIcjs.inferAnchorType.call(void 0, next);
1393
+ const type = keepType && a.type ? a.type === "symmetric" && shown !== "symmetric" ? shown === "corner" ? "corner" : "smooth" : a.type === "smooth" && shown === "corner" ? "corner" : a.type : shown;
1394
+ if (type === "corner") delete next.type;
1395
+ else next.type = type;
1396
+ return next;
1397
+ }
1398
+ function neighbours(sp, i) {
1399
+ const n = sp.anchors.length;
1400
+ const wrap = sp.closed && n > 1;
1401
+ return [i > 0 ? i - 1 : wrap ? n - 1 : -1, i < n - 1 ? i + 1 : wrap ? 0 : -1];
1402
+ }
1403
+ function goldenMin(f, a, b) {
1404
+ const R = 0.618033988749895;
1405
+ let x1 = b - R * (b - a);
1406
+ let x2 = a + R * (b - a);
1407
+ let f1 = f(x1);
1408
+ let f2 = f(x2);
1409
+ for (let i = 0; i < 40; i++) {
1410
+ if (f1.error < f2.error || Number.isNaN(f2.error)) {
1411
+ b = x2;
1412
+ x2 = x1;
1413
+ f2 = f1;
1414
+ x1 = b - R * (b - a);
1415
+ f1 = f(x1);
1416
+ } else {
1417
+ a = x1;
1418
+ x1 = x2;
1419
+ f1 = f2;
1420
+ x2 = a + R * (b - a);
1421
+ f2 = f(x2);
1422
+ }
1423
+ }
1424
+ return f1.error <= f2.error ? f1 : f2;
1425
+ }
1426
+ function fitAcross(pts, N, t0, t1) {
1427
+ const fit = (k) => {
1428
+ const u = [];
1429
+ for (let i = 0; i <= N; i++) u.push(k * i / N);
1430
+ for (let i = 1; i <= N; i++) u.push(k + (1 - k) * i / N);
1431
+ return _chunkNM3G4VQTcjs.fitCubicWithTangents.call(void 0, pts, u, t0, t1);
1432
+ };
1433
+ return goldenMin(fit, 0, 1);
1434
+ }
1435
+ function removeAnchorKeepingShape(sp, index) {
1436
+ const anchors = sp.anchors.map(_chunkQAUIZPYIcjs.cloneAnchor);
1437
+ const n = anchors.length;
1438
+ if (index < 0 || index >= n) return { anchors, closed: sp.closed };
1439
+ if ((sp.closed || index > 0 && index < n - 1) && n >= 3) {
1440
+ const pi = (index + n - 1) % n;
1441
+ const ni = (index + 1) % n;
1442
+ if (!(isLine(sp, pi) && isLine(sp, index))) {
1443
+ const left = cubicOf(sp, pi);
1444
+ const right = cubicOf(sp, index);
1445
+ const N = 16;
1446
+ const pts = [];
1447
+ sample(left, N, pts, true);
1448
+ sample(right, N, pts, false);
1449
+ const t0 = startTangent(left);
1450
+ const t1 = endTangent(right);
1451
+ const { curve } = fitAcross(pts, N, t0, t1);
1452
+ if ([curve.p1, curve.p2].every((p) => Number.isFinite(p[0]) && Number.isFinite(p[1]))) {
1453
+ anchors[pi] = tidy({ ...anchors[pi], out: { x: curve.p1[0], y: curve.p1[1] } });
1454
+ anchors[ni] = tidy({ ...anchors[ni], in: { x: curve.p2[0], y: curve.p2[1] } });
1455
+ }
1456
+ }
1457
+ }
1458
+ anchors.splice(index, 1);
1459
+ let closed = sp.closed;
1460
+ if (!closed && anchors.length) {
1461
+ anchors[0] = tidy({ ...anchors[0], in: null });
1462
+ const last = anchors.length - 1;
1463
+ anchors[last] = tidy({ ...anchors[last], out: null });
1464
+ }
1465
+ if (anchors.length < 3) closed = closed && anchors.length === 2 && anchors.some((a) => a.in || a.out);
1466
+ return { anchors, closed };
1467
+ }
1468
+ function removeAnchorsKeepingShape(sp, indices) {
1469
+ const order = [...new Set(indices)].filter((i) => i >= 0 && i < sp.anchors.length).sort((a, b) => b - a);
1470
+ return order.reduce((acc, i) => removeAnchorKeepingShape(acc, i), { anchors: sp.anchors.map(_chunkQAUIZPYIcjs.cloneAnchor), closed: sp.closed });
1471
+ }
1472
+ function bendSegment(sp, segment, t, p) {
1473
+ const anchors = sp.anchors.map(_chunkQAUIZPYIcjs.cloneAnchor);
1474
+ const n = anchors.length;
1475
+ if (segment < 0 || segment >= _chunkQAUIZPYIcjs.subpathSegmentCount.call(void 0, sp) || !Number.isFinite(t) || !Number.isFinite(p.x) || !Number.isFinite(p.y)) return { anchors, closed: sp.closed };
1476
+ const tc = Math.min(0.95, Math.max(0.05, t));
1477
+ const ia = segment;
1478
+ const ib = (segment + 1) % n;
1479
+ const a = anchors[ia];
1480
+ const b = anchors[ib];
1481
+ const k = 3 * tc * (1 - tc);
1482
+ const ox = (p.x - (a.x + (b.x - a.x) * tc)) / k;
1483
+ const oy = (p.y - (a.y + (b.y - a.y) * tc)) / k;
1484
+ const c1 = { x: a.x + (b.x - a.x) / 3 + ox, y: a.y + (b.y - a.y) / 3 + oy };
1485
+ const c2 = { x: b.x + (a.x - b.x) / 3 + ox, y: b.y + (a.y - b.y) / 3 + oy };
1486
+ anchors[ia] = _chunkQAUIZPYIcjs.moveAnchorHandle.call(void 0, a, "out", c1);
1487
+ anchors[ib] = _chunkQAUIZPYIcjs.moveAnchorHandle.call(void 0, b, "in", c2);
1488
+ return { anchors, closed: sp.closed };
1489
+ }
1490
+ function subpathPointAt(sp, segment, t) {
1491
+ const [x, y] = _chunkGF2RBSDMcjs.segmentPoint.call(void 0, _chunkQAUIZPYIcjs.subpathSegment.call(void 0, sp, segment), t);
1492
+ return { x, y };
1493
+ }
1494
+ function curvatureMoveAnchor(sp, index, q) {
1495
+ const anchors = sp.anchors.map(_chunkQAUIZPYIcjs.cloneAnchor);
1496
+ const a = sp.anchors[index];
1497
+ if (!a) return { anchors, closed: sp.closed };
1498
+ const own = [
1499
+ a.in && !onAnchor(a, a.in) ? { x: a.in.x - a.x, y: a.in.y - a.y } : null,
1500
+ a.out && !onAnchor(a, a.out) ? { x: a.out.x - a.x, y: a.out.y - a.y } : null
1501
+ ];
1502
+ const ends = [null, null];
1503
+ neighbours(sp, index).forEach((j, side) => {
1504
+ if (j < 0 || j === index) return;
1505
+ const b = anchors[j];
1506
+ const old = Math.hypot(a.x - b.x, a.y - b.y);
1507
+ const s = old > 1e-9 ? Math.hypot(q.x - b.x, q.y - b.y) / old : 1;
1508
+ const key = side === 0 ? "out" : "in";
1509
+ const h = b[key];
1510
+ if (h) b[key] = { x: b.x + (h.x - b.x) * s, y: b.y + (h.y - b.y) * s };
1511
+ const o = own[side];
1512
+ if (o) own[side] = { x: o.x * s, y: o.y * s };
1513
+ ends[side] = { x: b.x, y: b.y };
1514
+ });
1515
+ const moved = { x: q.x, y: q.y };
1516
+ if (own[0]) moved.in = { x: q.x + own[0].x, y: q.y + own[0].y };
1517
+ if (own[1]) moved.out = { x: q.x + own[1].x, y: q.y + own[1].y };
1518
+ if (a.type) moved.type = a.type;
1519
+ if (a.corner) moved.corner = { ...a.corner };
1520
+ if (a.id) moved.id = a.id;
1521
+ const [prev, next] = ends;
1522
+ if (a.type && a.type !== "corner" && prev && next) {
1523
+ const dx = next.x - prev.x;
1524
+ const dy = next.y - prev.y;
1525
+ const l = Math.hypot(dx, dy);
1526
+ if (l > 1e-9) {
1527
+ const len = (h, to) => h ? Math.hypot(h.x, h.y) : Math.hypot(q.x - to.x, q.y - to.y) / 3;
1528
+ let li = len(own[0], prev);
1529
+ let lo = len(own[1], next);
1530
+ if (a.type === "symmetric") li = lo = (li + lo) / 2;
1531
+ moved.in = { x: q.x - dx * li / l, y: q.y - dy * li / l };
1532
+ moved.out = { x: q.x + dx * lo / l, y: q.y + dy * lo / l };
1533
+ }
1534
+ }
1535
+ anchors[index] = moved;
1536
+ return { anchors, closed: sp.closed };
1537
+ }
1538
+ function cutSubpathAt(sp, at) {
1539
+ const n = sp.anchors.length;
1540
+ const cuts = [...new Set(at)].filter((i) => Number.isInteger(i) && i >= 0 && i < n && (sp.closed || i > 0 && i < n - 1)).sort((a, b) => a - b);
1541
+ if (!cuts.length || n < 2) return [{ anchors: sp.anchors.map(_chunkQAUIZPYIcjs.cloneAnchor), closed: sp.closed }];
1542
+ const first = cuts[0];
1543
+ const run = sp.closed ? Array.from({ length: n + 1 }, (_, i) => sp.anchors[(first + i) % n]) : sp.anchors;
1544
+ const cutSet = new Set(sp.closed ? [...cuts.map((c) => (c - first + n) % n), n] : cuts);
1545
+ const pieces = [];
1546
+ let cur = [];
1547
+ run.forEach((a, i) => {
1548
+ if (!cutSet.has(i)) {
1549
+ cur.push(_chunkQAUIZPYIcjs.cloneAnchor.call(void 0, a));
1550
+ return;
1551
+ }
1552
+ if (cur.length) {
1553
+ cur.push(tidy({ ...a, out: null }, false));
1554
+ pieces.push({ anchors: cur, closed: false });
1555
+ cur = [];
1556
+ }
1557
+ if (i < n) cur.push(tidy({ ...a, in: null }, false));
1558
+ });
1559
+ if (cur.length > 1) pieces.push({ anchors: cur, closed: false });
1560
+ return pieces;
1561
+ }
1562
+ function splitSubpathAt(sp, segment, t, endTolerance = 1e-6) {
1563
+ const n = sp.anchors.length;
1564
+ if (segment < 0 || segment >= _chunkQAUIZPYIcjs.subpathSegmentCount.call(void 0, sp)) return [{ anchors: sp.anchors.map(_chunkQAUIZPYIcjs.cloneAnchor), closed: sp.closed }];
1565
+ if (t <= endTolerance) return cutSubpathAt(sp, [segment]);
1566
+ if (t >= 1 - endTolerance) return cutSubpathAt(sp, [(segment + 1) % n]);
1567
+ const { subpath, index } = _chunkQAUIZPYIcjs.insertAnchor.call(void 0, sp, segment, t);
1568
+ return cutSubpathAt(subpath, [index]);
1569
+ }
1570
+ var endOf = (sp, last) => sp.anchors[last ? sp.anchors.length - 1 : 0];
1571
+ var dist = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
1572
+ function reverse(sp) {
1573
+ return {
1574
+ closed: sp.closed,
1575
+ anchors: sp.anchors.slice().reverse().map((a) => {
1576
+ const next = { x: a.x, y: a.y };
1577
+ if (a.out) next.in = { ...a.out };
1578
+ if (a.in) next.out = { ...a.in };
1579
+ if (a.type) next.type = a.type;
1580
+ if (a.corner) next.corner = { ...a.corner };
1581
+ if (a.id) next.id = a.id;
1582
+ return next;
1583
+ })
1584
+ };
1585
+ }
1586
+ function joinSubpaths(subpaths, tolerance) {
1587
+ const closed = [];
1588
+ let open = [];
1589
+ for (const sp of subpaths) {
1590
+ const copy = { anchors: sp.anchors.map(_chunkQAUIZPYIcjs.cloneAnchor), closed: sp.closed };
1591
+ if (sp.closed || sp.anchors.length < 2) {
1592
+ if (sp.anchors.length) closed.push(copy);
1593
+ } else open.push(copy);
1594
+ }
1595
+ if (open.length === 1) {
1596
+ const sp = open[0];
1597
+ const first = sp.anchors[0];
1598
+ const last = sp.anchors[sp.anchors.length - 1];
1599
+ if (sp.anchors.length > 2 && dist(first, last) <= tolerance) {
1600
+ sp.anchors.pop();
1601
+ sp.anchors[0] = tidy({ ...first, in: _nullishCoalesce(last.in, () => ( null)) }, false);
1602
+ } else {
1603
+ sp.anchors[0] = tidy({ ...first, in: null }, false);
1604
+ sp.anchors[sp.anchors.length - 1] = tidy({ ...sp.anchors[sp.anchors.length - 1], out: null }, false);
1605
+ }
1606
+ sp.closed = true;
1607
+ return [...closed, sp];
1608
+ }
1609
+ while (open.length > 1) {
1610
+ let best = { d: Infinity, i: 0, j: 1, ei: true, ej: false };
1611
+ for (let i = 0; i < open.length; i++) {
1612
+ for (let j = i + 1; j < open.length; j++) {
1613
+ for (const ei of [false, true]) {
1614
+ for (const ej of [false, true]) {
1615
+ const d = dist(endOf(open[i], ei), endOf(open[j], ej));
1616
+ if (d < best.d) best = { d, i, j, ei, ej };
1617
+ }
1618
+ }
1619
+ }
1620
+ }
1621
+ let a = open[best.i];
1622
+ let b = open[best.j];
1623
+ open = open.filter((_, k) => k !== best.j);
1624
+ if (!best.ei) a = reverse(a);
1625
+ if (best.ej) b = reverse(b);
1626
+ const anchors = a.anchors;
1627
+ if (best.d <= tolerance) {
1628
+ const [head, ...rest] = b.anchors;
1629
+ const l = anchors[anchors.length - 1];
1630
+ anchors[anchors.length - 1] = tidy({ ...l, out: _nullishCoalesce(head.out, () => ( null)) }, false);
1631
+ anchors.push(...rest);
1632
+ } else {
1633
+ const l = anchors[anchors.length - 1];
1634
+ anchors[anchors.length - 1] = tidy({ ...l, out: null }, false);
1635
+ anchors.push(tidy({ ...b.anchors[0], in: null }, false), ...b.anchors.slice(1));
1636
+ }
1637
+ open[best.i > best.j ? best.i - 1 : best.i] = { anchors, closed: false };
1638
+ }
1639
+ return [...closed, ...open];
1640
+ }
1641
+ function smoothSubpath(sp, amount = 1, only) {
1642
+ const t = Math.min(1, Math.max(0, amount));
1643
+ const orig = sp.anchors;
1644
+ const n = orig.length;
1645
+ const anchors = orig.map(_chunkQAUIZPYIcjs.cloneAnchor);
1646
+ if (n < 3 || t === 0) return { anchors, closed: sp.closed };
1647
+ for (let i = 0; i < n; i++) {
1648
+ if (!sp.closed && (i === 0 || i === n - 1)) continue;
1649
+ if (only && !only.has(i)) continue;
1650
+ const prev = orig[(i + n - 1) % n];
1651
+ const next = orig[(i + 1) % n];
1652
+ const p = orig[i];
1653
+ const dir = unit(next.x - prev.x, next.y - prev.y);
1654
+ if (!dir) continue;
1655
+ const li = dist(p, prev) / 3;
1656
+ const lo = dist(p, next) / 3;
1657
+ const hin = _nullishCoalesce(p.in, () => ( p));
1658
+ const hout = _nullishCoalesce(p.out, () => ( p));
1659
+ const target = (h, x, y) => ({ x: h.x + (x - h.x) * t, y: h.y + (y - h.y) * t });
1660
+ anchors[i] = tidy(
1661
+ {
1662
+ x: p.x,
1663
+ y: p.y,
1664
+ in: target(hin, p.x - dir[0] * li, p.y - dir[1] * li),
1665
+ out: target(hout, p.x + dir[0] * lo, p.y + dir[1] * lo)
1666
+ },
1667
+ false
1668
+ );
1669
+ }
1670
+ return { anchors, closed: sp.closed };
1671
+ }
1672
+ function segsOf(sp) {
1673
+ const out = [];
1674
+ for (let i = 0; i < _chunkQAUIZPYIcjs.subpathSegmentCount.call(void 0, sp); i++) {
1675
+ const s = _chunkQAUIZPYIcjs.subpathSegment.call(void 0, sp, i);
1676
+ out.push(s.length === 2 ? { c: s, line: true } : { c: s, line: false });
1677
+ }
1678
+ return out;
1679
+ }
1680
+ function segsToSubpath(segs, closed, types) {
1681
+ if (!segs.length) return null;
1682
+ const anchors = [];
1683
+ const at = (p) => ({ x: p[0], y: p[1] });
1684
+ anchors.push(at(segs[0].c[0]));
1685
+ for (const s of segs) {
1686
+ const end = s.c[s.c.length - 1];
1687
+ if (!s.line) anchors[anchors.length - 1].out = { x: s.c[1][0], y: s.c[1][1] };
1688
+ const next = at(end);
1689
+ if (!s.line) next.in = { x: s.c[2][0], y: s.c[2][1] };
1690
+ anchors.push(next);
1691
+ }
1692
+ if (closed) {
1693
+ const last = anchors.pop();
1694
+ if (last.in) anchors[0].in = last.in;
1695
+ }
1696
+ const typed = anchors.map((a) => {
1697
+ const type = types.get(`${a.x},${a.y}`);
1698
+ return tidy(type ? { ...a, type } : a, !!type);
1699
+ });
1700
+ return { anchors: typed, closed };
1701
+ }
1702
+ var isZeroSeg = (s) => s.c.every((p) => Math.abs(p[0] - s.c[0][0]) < 1e-9 && Math.abs(p[1] - s.c[0][1]) < 1e-9);
1703
+ function mergePair(a, b, tol) {
1704
+ const a0 = a.c[0];
1705
+ const a3 = a.c[a.c.length - 1];
1706
+ const b3 = b.c[b.c.length - 1];
1707
+ if (a.line && b.line) {
1708
+ const cx = b3[0] - a0[0];
1709
+ const cy = b3[1] - a0[1];
1710
+ const len = Math.hypot(cx, cy);
1711
+ if (len < 1e-12) return null;
1712
+ const mx = a3[0] - a0[0];
1713
+ const my = a3[1] - a0[1];
1714
+ const along = (mx * cx + my * cy) / len;
1715
+ return Math.abs((mx * cy - my * cx) / len) <= tol && along >= 0 && along <= len ? { c: [a0, b3], line: true } : null;
1716
+ }
1717
+ const ca = a.line ? [a0, a0, a3, a3] : a.c;
1718
+ const cb = b.line ? [a3, a3, b3, b3] : b.c;
1719
+ const ta = endTangent(ca);
1720
+ const tb = startTangent(cb);
1721
+ if (ta[0] * tb[0] + ta[1] * tb[1] < 0.99) return null;
1722
+ const pts = [];
1723
+ sample(ca, 16, pts, true);
1724
+ sample(cb, 16, pts, false);
1725
+ const { curve, error } = fitAcross(pts, 16, startTangent(ca), endTangent(cb));
1726
+ return error <= tol ? { c: [curve.p0, curve.p1, curve.p2, curve.p3], line: false } : null;
1727
+ }
1728
+ function removeRedundantPoints(sp, tolerance) {
1729
+ const tol = Math.max(tolerance, 1e-9);
1730
+ const types = new Map(sp.anchors.filter((a) => a.type).map((a) => [`${a.x},${a.y}`, a.type]));
1731
+ const segs = segsOf(sp).filter((s) => !isZeroSeg(s));
1732
+ if (!segs.length) return { anchors: sp.anchors.map(_chunkQAUIZPYIcjs.cloneAnchor), closed: sp.closed };
1733
+ const minSegs = sp.closed ? 2 : 1;
1734
+ let changed = true;
1735
+ while (changed && segs.length > minSegs) {
1736
+ changed = false;
1737
+ const n = segs.length;
1738
+ const joints = sp.closed ? n : n - 1;
1739
+ for (let j = 0; j < joints; j++) {
1740
+ const [i0, i1] = sp.closed ? [(j + n - 1) % n, j] : [j, j + 1];
1741
+ const m = mergePair(segs[i0], segs[i1], tol);
1742
+ if (!m) continue;
1743
+ segs[i0] = m;
1744
+ segs.splice(i1, 1);
1745
+ if (sp.closed && i1 === 0) segs.unshift(segs.pop());
1746
+ changed = true;
1747
+ break;
1748
+ }
1749
+ }
1750
+ return _nullishCoalesce(segsToSubpath(segs, sp.closed, types), () => ( { anchors: sp.anchors.map(_chunkQAUIZPYIcjs.cloneAnchor), closed: sp.closed }));
1751
+ }
1752
+
1753
+
1754
+
1755
+
1756
+
1757
+
1758
+
1759
+
1760
+
1761
+
1762
+
1345
1763
 
1346
1764
 
1347
1765
 
@@ -1350,4 +1768,4 @@ async function unwoff(view) {
1350
1768
 
1351
1769
 
1352
1770
 
1353
- exports.VECTOR_TRACE_MAX_PIXELS = VECTOR_TRACE_MAX_PIXELS; exports.vectorTraceContours = vectorTraceContours; exports.traceField = traceField; exports.quantizeVectorTraceColors = quantizeVectorTraceColors; exports.traceVectorImage = traceVectorImage; exports.parseOpenTypeFont = parseOpenTypeFont; exports.loadOpenTypeFont = loadOpenTypeFont;
1771
+ exports.VECTOR_TRACE_MAX_PIXELS = VECTOR_TRACE_MAX_PIXELS; exports.vectorTraceContours = vectorTraceContours; exports.traceField = traceField; exports.quantizeVectorTraceColors = quantizeVectorTraceColors; exports.traceVectorImage = traceVectorImage; exports.parseOpenTypeFont = parseOpenTypeFont; exports.loadOpenTypeFont = loadOpenTypeFont; exports.removeAnchorKeepingShape = removeAnchorKeepingShape; exports.removeAnchorsKeepingShape = removeAnchorsKeepingShape; exports.bendSegment = bendSegment; exports.subpathPointAt = subpathPointAt; exports.curvatureMoveAnchor = curvatureMoveAnchor; exports.cutSubpathAt = cutSubpathAt; exports.splitSubpathAt = splitSubpathAt; exports.joinSubpaths = joinSubpaths; exports.smoothSubpath = smoothSubpath; exports.removeRedundantPoints = removeRedundantPoints;
@@ -5,7 +5,7 @@
5
5
  * applies it as one undo step through `contentAwareFillAsync` (the same fill the
6
6
  * `content_aware_fill` tool makes, with a sampling area).
7
7
  */
8
- import type { ImageEditorController } from './controller.cjs';
8
+ import type { ContentAwareFillOutput, ImageEditorController } from './controller.cjs';
9
9
  import type { CropBox, ImagePoint } from './types.cjs';
10
10
  import { type SelectionMask } from './selection/mask.cjs';
11
11
  export type ContentAwareFillSampling = 'auto' | 'rectangular' | 'custom';
@@ -32,7 +32,7 @@ export interface ContentAwareFillSettings {
32
32
  brushMode: 'add' | 'subtract';
33
33
  /** The fill's random seed; another gives another fill to choose from. */
34
34
  seed: number;
35
- output: 'current' | 'new-layer';
35
+ output: ContentAwareFillOutput;
36
36
  /** Update the preview as the area changes (otherwise with Preview). */
37
37
  livePreview: boolean;
38
38
  }
@@ -61,11 +61,23 @@ export declare class ContentAwareFillSession {
61
61
  get window(): CropBox | null;
62
62
  subscribe(listener: () => void): () => void;
63
63
  private emit;
64
+ /** Images up to this many pixels show (and can be sampled) whole in the workspace. */
65
+ static readonly WHOLE_IMAGE_PIXELS = 4000000;
66
+ /** Room around the selection the window holds, in px each way. */
67
+ private margin;
64
68
  /**
65
- * Reads the layer around the selection and makes the first preview. The window holds the
66
- * selection and room around it to sample from (its larger side, at least 64 px, each way).
69
+ * Reads the layer and makes the first preview. The window is the whole image up to
70
+ * {@link WHOLE_IMAGE_PIXELS}; on a larger one it holds the selection and room around it (twice
71
+ * its larger side, at least 256 px, each way), and {@link expandView} widens it.
67
72
  */
68
73
  open(): Promise<boolean>;
74
+ /** Whether the window shows the whole image (else {@link expandView} can widen it). */
75
+ get showsWholeImage(): boolean;
76
+ /**
77
+ * Doubles the room around the selection (up to the whole image), keeping what was painted; a
78
+ * preset fills the new room as it would. Returns false when the whole image already shows.
79
+ */
80
+ expandView(): boolean;
69
81
  /** Sets the area to a preset: Auto (a band around the selection), Rectangular (the whole window). */
70
82
  private applySampling;
71
83
  /** Changes settings; a sampling preset replaces the area, and the preview follows when live. */
@@ -104,6 +116,6 @@ export declare class ContentAwareFillSession {
104
116
  }): Promise<{
105
117
  layerId: string;
106
118
  bounds: CropBox;
107
- output: 'current' | 'new-layer';
119
+ output: ContentAwareFillOutput;
108
120
  }>;
109
121
  }
@@ -5,7 +5,7 @@
5
5
  * applies it as one undo step through `contentAwareFillAsync` (the same fill the
6
6
  * `content_aware_fill` tool makes, with a sampling area).
7
7
  */
8
- import type { ImageEditorController } from './controller.js';
8
+ import type { ContentAwareFillOutput, ImageEditorController } from './controller.js';
9
9
  import type { CropBox, ImagePoint } from './types.js';
10
10
  import { type SelectionMask } from './selection/mask.js';
11
11
  export type ContentAwareFillSampling = 'auto' | 'rectangular' | 'custom';
@@ -32,7 +32,7 @@ export interface ContentAwareFillSettings {
32
32
  brushMode: 'add' | 'subtract';
33
33
  /** The fill's random seed; another gives another fill to choose from. */
34
34
  seed: number;
35
- output: 'current' | 'new-layer';
35
+ output: ContentAwareFillOutput;
36
36
  /** Update the preview as the area changes (otherwise with Preview). */
37
37
  livePreview: boolean;
38
38
  }
@@ -61,11 +61,23 @@ export declare class ContentAwareFillSession {
61
61
  get window(): CropBox | null;
62
62
  subscribe(listener: () => void): () => void;
63
63
  private emit;
64
+ /** Images up to this many pixels show (and can be sampled) whole in the workspace. */
65
+ static readonly WHOLE_IMAGE_PIXELS = 4000000;
66
+ /** Room around the selection the window holds, in px each way. */
67
+ private margin;
64
68
  /**
65
- * Reads the layer around the selection and makes the first preview. The window holds the
66
- * selection and room around it to sample from (its larger side, at least 64 px, each way).
69
+ * Reads the layer and makes the first preview. The window is the whole image up to
70
+ * {@link WHOLE_IMAGE_PIXELS}; on a larger one it holds the selection and room around it (twice
71
+ * its larger side, at least 256 px, each way), and {@link expandView} widens it.
67
72
  */
68
73
  open(): Promise<boolean>;
74
+ /** Whether the window shows the whole image (else {@link expandView} can widen it). */
75
+ get showsWholeImage(): boolean;
76
+ /**
77
+ * Doubles the room around the selection (up to the whole image), keeping what was painted; a
78
+ * preset fills the new room as it would. Returns false when the whole image already shows.
79
+ */
80
+ expandView(): boolean;
69
81
  /** Sets the area to a preset: Auto (a band around the selection), Rectangular (the whole window). */
70
82
  private applySampling;
71
83
  /** Changes settings; a sampling preset replaces the area, and the preview follows when live. */
@@ -104,6 +116,6 @@ export declare class ContentAwareFillSession {
104
116
  }): Promise<{
105
117
  layerId: string;
106
118
  bounds: CropBox;
107
- output: 'current' | 'new-layer';
119
+ output: ContentAwareFillOutput;
108
120
  }>;
109
121
  }
@@ -2,7 +2,7 @@ import { AdjustmentKind, AdjustmentLayer, AdjustmentParamsMap, Annotation, Creat
2
2
  import { type PathToSelectionOptions, type SelectionToPathOptions } from './selection/path.cjs';
3
3
  import { type MagneticEdgeOptions } from './selection/magnetic.cjs';
4
4
  import type { ObjectTarget } from './selection/segment/grabcut.cjs';
5
- import { type DocumentPath } from './paths.cjs';
5
+ import { type DocumentPath, type PathEditTarget } from './paths.cjs';
6
6
  import { type SelectionJob, type SelectionJobResult, type SelectionWorkerFactory } from './selection/worker.cjs';
7
7
  import type { SelectionProgress } from './selection/segment/work.cjs';
8
8
  import type { FocusAreaOptions } from './selection/segment/focus.cjs';
@@ -20,7 +20,7 @@ import { type RefineEdit } from './selection/refine-edits.cjs';
20
20
  import { type LayerMaskProps } from './selection/layer-mask.cjs';
21
21
  import { type AlphaChannel } from './selection/channels.cjs';
22
22
  import type { AdjustmentLayerMask } from './types.cjs';
23
- import { type LayerBlendMode, type ImageLayer, type LayerFilter, type LayerTarget, type PixelLayer, type RasterMask, type SmartContents, type SmartFilter, type SmartPlacement, type LiveShape, type ShapeFillRule, type VectorMask, type ShapeOperation, type ShapePaint, type ShapeStroke, type ParagraphStyle, type TextStyle, type TextOrientation, type TypeLayer, type ImageEffectAnimation } from './layers/model.cjs';
23
+ import { type LayerBlendMode, type ImageLayer, type LayerFilter, type LayerTarget, type PixelLayer, type RasterMask, type SmartContents, type SmartFilter, type SmartPlacement, type LiveShape, type ShapeComponent, type ShapeFillRule, type VectorMask, type ShapeOperation, type ShapePaint, type ShapeStroke, type ParagraphStyle, type TextStyle, type TextOrientation, type TypeLayer, type ImageEffectAnimation } from './layers/model.cjs';
24
24
  import { type TextLayout, type TextShaper } from './layers/text.cjs';
25
25
  import type { VectorSubpath } from '../vector/types.cjs';
26
26
  import { EffectsSlot } from '../effects/helpers.cjs';
@@ -479,12 +479,17 @@ export interface StrokeSelectionOptions extends Omit<FillOptions, 'useSelection'
479
479
  /** Where the band lies against the selection edge (default `outside`). */
480
480
  location?: 'inside' | 'center' | 'outside';
481
481
  }
482
+ /** Where Content-Aware Fill puts its result. */
483
+ export type ContentAwareFillOutput = 'current' | 'new-layer' | 'duplicate-layer';
482
484
  /** Edit › Content-Aware Fill (see `contentAwareFill`). */
483
485
  export interface ContentAwareFillOptions {
484
486
  /** The pixel layer to sample and fill (default the active layer). */
485
487
  layerId?: string;
486
- /** `current` (default) paints into the layer; `new-layer` puts the fill on a new layer above it. */
487
- output?: 'current' | 'new-layer';
488
+ /**
489
+ * `current` (default) paints into the layer; `new-layer` puts the fill on a new layer above it;
490
+ * `duplicate-layer` paints it into a copy of the layer, above it.
491
+ */
492
+ output?: ContentAwareFillOutput;
488
493
  /** How far around the selection to sample, in pixels (default its larger side, at least 32). */
489
494
  sampleRadius?: number;
490
495
  /** Random seed: the same seed gives the same fill (default 0). */
@@ -1106,6 +1111,26 @@ export declare class ImageEditorController {
1106
1111
  }): DocumentPath;
1107
1112
  /** Replaces a path's geometry (its name is kept). One undo step. */
1108
1113
  setPathGeometry(id: string, input: VectorMaskPathInput): DocumentPath;
1114
+ /**
1115
+ * What the path tools (Path Selection, Direct Selection, Pen) edit: the active path in the Paths
1116
+ * panel, else the active layer when it is a shape, else the active layer's vector mask; null
1117
+ * when there is none of these (the Pen then starts a new Work Path).
1118
+ */
1119
+ pathEditTarget(): PathEditTarget | null;
1120
+ /** The components a path tool target holds (document pixels). Throws for a target that is gone. */
1121
+ getPathEditComponents(target: PathEditTarget): ShapeComponent[];
1122
+ /**
1123
+ * Replaces a path tool target's components: one undo step, or none with `commitToHistory` false
1124
+ * (the later moves of a drag whose first move recorded one). A component that still has its
1125
+ * `live` shape is kept as given (drop `live` from one whose anchors changed); a shape layer keeps
1126
+ * at least one component.
1127
+ */
1128
+ setPathEditComponents(target: PathEditTarget, components: readonly ShapeComponent[], commitToHistory?: boolean): void;
1129
+ /**
1130
+ * The Pen with no path to add to: a new Work Path from `components` (replacing the last one,
1131
+ * unless it was saved), made the active path. One undo step. Returns the path.
1132
+ */
1133
+ startWorkPath(components: readonly ShapeComponent[]): DocumentPath;
1109
1134
  /** Renames a path; renaming the Work Path saves it (Photoshop's Save Path). One undo step. */
1110
1135
  renamePath(id: string, name: string): DocumentPath;
1111
1136
  /** Duplicates a path as a saved path. One undo step. */
@@ -1528,7 +1553,7 @@ export declare class ImageEditorController {
1528
1553
  contentAwareFill(options?: ContentAwareFillOptions): {
1529
1554
  layerId: string;
1530
1555
  bounds: CropBox;
1531
- output: 'current' | 'new-layer';
1556
+ output: ContentAwareFillOutput;
1532
1557
  };
1533
1558
  /**
1534
1559
  * `contentAwareFill` with the synthesis in the selection worker when there is one, with
@@ -1540,7 +1565,7 @@ export declare class ImageEditorController {
1540
1565
  }): Promise<{
1541
1566
  layerId: string;
1542
1567
  bounds: CropBox;
1543
- output: 'current' | 'new-layer';
1568
+ output: ContentAwareFillOutput;
1544
1569
  }>;
1545
1570
  /**
1546
1571
  * The fill without changing anything: the window, its pixels before and after (RGBA, the fill