@pixerate/editor 0.132.0 → 0.133.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/animation-editor/index.cjs +55 -55
  2. package/dist/animation-editor/index.d.cts +4 -4
  3. package/dist/animation-editor/index.d.ts +4 -4
  4. package/dist/animation-editor/index.js +2 -2
  5. package/dist/{bezier-0IHDCvv_.d.ts → bezier-BAGdL89w.d.ts} +1 -1
  6. package/dist/{bezier-oCBfrGDG.d.cts → bezier-DL2wPsFf.d.cts} +1 -1
  7. package/dist/canvas/index.d.cts +7 -7
  8. package/dist/canvas/index.d.ts +7 -7
  9. package/dist/{chunk-MZXQTKUM.cjs → chunk-24ATT2PE.cjs} +10 -6
  10. package/dist/chunk-24ATT2PE.cjs.map +1 -0
  11. package/dist/{chunk-AHUYMDYW.js → chunk-2HC4DRF4.js} +10 -6
  12. package/dist/chunk-2HC4DRF4.js.map +1 -0
  13. package/dist/{chunk-OYPJGGF3.cjs → chunk-2QKUD3E2.cjs} +36 -36
  14. package/dist/{chunk-OYPJGGF3.cjs.map → chunk-2QKUD3E2.cjs.map} +1 -1
  15. package/dist/{chunk-55UEZIU4.cjs → chunk-65GNHOD4.cjs} +231 -191
  16. package/dist/chunk-65GNHOD4.cjs.map +1 -0
  17. package/dist/{chunk-YRZ6NVE3.js → chunk-6CTZAGZ2.js} +235 -4
  18. package/dist/chunk-6CTZAGZ2.js.map +1 -0
  19. package/dist/{chunk-G5FX56KX.js → chunk-AOCFL7L7.js} +51 -11
  20. package/dist/chunk-AOCFL7L7.js.map +1 -0
  21. package/dist/{chunk-YYJKIESX.cjs → chunk-F7GGEAO2.cjs} +4 -2
  22. package/dist/chunk-F7GGEAO2.cjs.map +1 -0
  23. package/dist/{chunk-IEYXYMRQ.js → chunk-FOBB6AIO.js} +3 -1
  24. package/dist/chunk-FOBB6AIO.js.map +1 -0
  25. package/dist/{chunk-W6M7C7R3.cjs → chunk-NVTHUPJ6.cjs} +236 -5
  26. package/dist/chunk-NVTHUPJ6.cjs.map +1 -0
  27. package/dist/{chunk-ECVI5QRE.js → chunk-VGZSIXX3.js} +2 -2
  28. package/dist/effects/index.d.cts +4 -4
  29. package/dist/effects/index.d.ts +4 -4
  30. package/dist/{export-D-gkjBkB.d.cts → export-BICOEM9d.d.cts} +10 -10
  31. package/dist/{export-CfAogLEr.d.cts → export-CeFplzAW.d.cts} +1 -1
  32. package/dist/{export-ByJvAvR7.d.ts → export-XnuX8FZ2.d.ts} +10 -10
  33. package/dist/{export-Crl82sbE.d.ts → export-zB4HITwP.d.ts} +1 -1
  34. package/dist/{helpers-CoIeXQnW.d.cts → helpers-BuKdqUlM.d.cts} +23 -1
  35. package/dist/{helpers-BCd2-PAR.d.ts → helpers-cssRNUzH.d.ts} +23 -1
  36. package/dist/image-editor/index.cjs +983 -874
  37. package/dist/image-editor/index.cjs.map +1 -1
  38. package/dist/image-editor/index.d.cts +32 -13
  39. package/dist/image-editor/index.d.ts +32 -13
  40. package/dist/image-editor/index.js +124 -15
  41. package/dist/image-editor/index.js.map +1 -1
  42. package/dist/{opentype-BDahNRTe.d.cts → opentype-C3iyIFRx.d.cts} +1 -1
  43. package/dist/{opentype-BQFEvy-t.d.ts → opentype-DG5Lkwul.d.ts} +1 -1
  44. package/dist/paint/index.cjs +8 -2
  45. package/dist/paint/index.cjs.map +1 -1
  46. package/dist/paint/index.d.cts +14 -3
  47. package/dist/paint/index.d.ts +14 -3
  48. package/dist/paint/index.js +7 -1
  49. package/dist/paint-editor/index.cjs +78 -78
  50. package/dist/paint-editor/index.d.cts +7 -7
  51. package/dist/paint-editor/index.d.ts +7 -7
  52. package/dist/paint-editor/index.js +3 -3
  53. package/dist/{pathfinder-01ZgAwqV.d.cts → pathfinder-8vXik95z.d.cts} +1 -1
  54. package/dist/{pathfinder-DaVdT8jq.d.ts → pathfinder-DKLt4UDG.d.ts} +1 -1
  55. package/dist/{pool-D2A3WTt9.d.ts → pool-C69pkWRz.d.ts} +1 -1
  56. package/dist/{pool-D6a45MoE.d.cts → pool-VAWcYUbs.d.cts} +1 -1
  57. package/dist/{rand-pQQ2sJTk.d.ts → rand-BaqEOs1Y.d.ts} +17 -4
  58. package/dist/{rand-DTXAssW1.d.cts → rand-DdpFzCo6.d.cts} +17 -4
  59. package/dist/raster/index.cjs +2 -2
  60. package/dist/raster/index.d.cts +4 -4
  61. package/dist/raster/index.d.ts +4 -4
  62. package/dist/raster/index.js +1 -1
  63. package/dist/{svg-CcDz7Zq2.d.cts → svg-BThlZmja.d.cts} +2 -2
  64. package/dist/{svg-DELNVerw.d.ts → svg-CEYpvsgH.d.ts} +2 -2
  65. package/dist/{tools-BFvkFnRj.d.cts → tools-CRnZJXXr.d.cts} +1 -1
  66. package/dist/{tools-D7ws-OKL.d.ts → tools-CVGqEpO8.d.ts} +1 -1
  67. package/dist/vector/index.cjs +1383 -1044
  68. package/dist/vector/index.cjs.map +1 -1
  69. package/dist/vector/index.d.cts +102 -12
  70. package/dist/vector/index.d.ts +102 -12
  71. package/dist/vector/index.js +342 -3
  72. package/dist/vector/index.js.map +1 -1
  73. package/dist/video-editor/index.d.cts +4 -4
  74. package/dist/video-editor/index.d.ts +4 -4
  75. package/dist/{warp-DErXW5rz.d.cts → warp-ChcFDdlT.d.cts} +1 -1
  76. package/dist/{warp-2N9wkOkL.d.ts → warp-mKylbLiF.d.ts} +1 -1
  77. package/package.json +1 -1
  78. package/skills/pixerate-image-editor/SKILL.md +1 -1
  79. package/skills/pixerate-image-editor/references/retouching.md +3 -2
  80. package/dist/chunk-55UEZIU4.cjs.map +0 -1
  81. package/dist/chunk-AHUYMDYW.js.map +0 -1
  82. package/dist/chunk-G5FX56KX.js.map +0 -1
  83. package/dist/chunk-IEYXYMRQ.js.map +0 -1
  84. package/dist/chunk-MZXQTKUM.cjs.map +0 -1
  85. package/dist/chunk-W6M7C7R3.cjs.map +0 -1
  86. package/dist/chunk-YRZ6NVE3.js.map +0 -1
  87. package/dist/chunk-YYJKIESX.cjs.map +0 -1
  88. /package/dist/{chunk-ECVI5QRE.js.map → chunk-VGZSIXX3.js.map} +0 -0
@@ -12,6 +12,7 @@ import {
12
12
  applyHomography,
13
13
  homographyFromPoints,
14
14
  homographyKeepsQuad,
15
+ invertHomography,
15
16
  orderQuad,
16
17
  solve8
17
18
  } from "../chunk-GX332W2C.js";
@@ -42,7 +43,7 @@ import {
42
43
  reverseMorphContour,
43
44
  subpathToMorphContour,
44
45
  turnMorphContour
45
- } from "../chunk-ECVI5QRE.js";
46
+ } from "../chunk-VGZSIXX3.js";
46
47
  import {
47
48
  DEFAULT_VECTOR_FONT,
48
49
  IDENTITY_MATRIX,
@@ -83,6 +84,7 @@ import {
83
84
  VECTOR_PARAGRAPH_STYLE_KEYS,
84
85
  VECTOR_PATHFINDER_OPERATIONS,
85
86
  VECTOR_PATH_TEXT_BASELINES,
87
+ VECTOR_PERSPECTIVE_PLANES,
86
88
  VECTOR_PRESSURE_NOMINAL,
87
89
  VECTOR_SPATIAL_MIN,
88
90
  VECTOR_STROKE_GRADIENT_MODES,
@@ -493,7 +495,7 @@ import {
493
495
  withVectorStrokes,
494
496
  withVectorTargetPaint,
495
497
  zigzagPaths
496
- } from "../chunk-IEYXYMRQ.js";
498
+ } from "../chunk-FOBB6AIO.js";
497
499
  import {
498
500
  bezierPathBounds,
499
501
  bezierPathToSvg,
@@ -834,6 +836,131 @@ function releaseVectorBlend(group, newId) {
834
836
  return [...keys, group.transform ? transformVectorNode(path, m) : path];
835
837
  }
836
838
 
839
+ // src/vector/perspective.ts
840
+ function vectorPerspectivePlanes(p) {
841
+ return p.vanishingPoints.length <= 1 ? ["floor", "side", "front"] : ["floor", "left", "right"];
842
+ }
843
+ function sanitizeVectorPerspective(value) {
844
+ if (!value || typeof value !== "object") return null;
845
+ const r = value;
846
+ const isPt = (v) => !!v && typeof v === "object" && Number.isFinite(v.x) && Number.isFinite(v.y);
847
+ if (!Array.isArray(r.vanishingPoints) || r.vanishingPoints.length < 1 || r.vanishingPoints.length > 3 || !r.vanishingPoints.every(isPt) || !isPt(r.origin)) return null;
848
+ const origin = { x: r.origin.x, y: r.origin.y };
849
+ const vps = r.vanishingPoints.map((v) => ({ x: v.x, y: v.y }));
850
+ if (vps.some((v) => Math.hypot(v.x - origin.x, v.y - origin.y) < 1e-6)) return null;
851
+ const out = { vanishingPoints: vps, origin };
852
+ if (typeof r.gridSize === "number" && r.gridSize > 0 && Number.isFinite(r.gridSize)) out.gridSize = Math.min(1e3, r.gridSize);
853
+ if (typeof r.extent === "number" && Number.isFinite(r.extent)) out.extent = Math.min(100, Math.max(1, Math.round(r.extent)));
854
+ if (r.visible === false) out.visible = false;
855
+ return out;
856
+ }
857
+ function towards(o, vp, want) {
858
+ const d = Math.hypot(vp.x - o.x, vp.y - o.y);
859
+ let a = 1 / d;
860
+ if (want === "right" ? vp.x - o.x < 0 : vp.y - o.y > 0) a = -a;
861
+ return [a * vp.x, a * vp.y, a];
862
+ }
863
+ function vectorPerspectivePlaneMap(p, plane) {
864
+ if (!vectorPerspectivePlanes(p).includes(plane)) return null;
865
+ const o = p.origin;
866
+ const [v1, v2, v3] = p.vanishingPoints;
867
+ const right = [1, 0, 0];
868
+ const up = v3 ? towards(o, v3, "up") : [0, -1, 0];
869
+ let x;
870
+ let y;
871
+ if (p.vanishingPoints.length <= 1) {
872
+ if (plane === "floor") [x, y] = [right, towards(o, v1, "up")];
873
+ else if (plane === "side") [x, y] = [towards(o, v1, "right"), up];
874
+ else [x, y] = [right, up];
875
+ } else if (plane === "floor") [x, y] = [towards(o, v2, "right"), towards(o, v1, "up")];
876
+ else if (plane === "left") [x, y] = [towards(o, v1, "right"), up];
877
+ else [x, y] = [towards(o, v2, "right"), up];
878
+ return [x[0], y[0], o.x, x[1], y[1], o.y, x[2], y[2], 1];
879
+ }
880
+ function vectorPerspectiveFlatToPlane(p, plane) {
881
+ const h = vectorPerspectivePlaneMap(p, plane);
882
+ if (!h) return null;
883
+ const o = p.origin;
884
+ return (q) => {
885
+ const u = q.x - o.x;
886
+ const v = o.y - q.y;
887
+ const w = h[6] * u + h[7] * v + h[8];
888
+ if (!(w > 1e-6)) return null;
889
+ return applyHomography(h, { x: u, y: v });
890
+ };
891
+ }
892
+ function vectorPerspectivePlaneToFlat(p, plane) {
893
+ const h = vectorPerspectivePlaneMap(p, plane);
894
+ const inv = h ? invertHomography(h) : null;
895
+ if (!inv) return null;
896
+ const o = p.origin;
897
+ return (q) => {
898
+ const f = applyHomography(inv, q);
899
+ if (!f) return null;
900
+ const w = h[6] * f.x + h[7] * f.y + h[8];
901
+ if (!(w > 1e-6)) return null;
902
+ return { x: o.x + f.x, y: o.y - f.y };
903
+ };
904
+ }
905
+ function vectorPerspectiveMapPath(p, plane, path) {
906
+ const f = vectorPerspectiveFlatToPlane(p, plane);
907
+ if (!f) return null;
908
+ let behind = false;
909
+ const out = mapBezierPath(
910
+ path,
911
+ (q) => {
912
+ const r = f(q);
913
+ if (!r) {
914
+ behind = true;
915
+ return q;
916
+ }
917
+ return r;
918
+ },
919
+ 0.1
920
+ );
921
+ return behind ? null : out;
922
+ }
923
+ function vectorPerspectiveGridLines(p, plane) {
924
+ const h = vectorPerspectivePlaneMap(p, plane);
925
+ if (!h) return [];
926
+ const g = p.gridSize ?? 20;
927
+ const n2 = p.extent ?? 12;
928
+ const max = n2 * g;
929
+ const lo = (axis) => axis === "y" && plane !== "floor" ? 0 : -max;
930
+ const w = (u, v) => h[6] * u + h[7] * v + h[8];
931
+ const at = (u, v) => applyHomography(h, { x: u, y: v });
932
+ const segment = (u0, v0, u1, v1) => {
933
+ const wa = w(u0, v0);
934
+ const wb = w(u1, v1);
935
+ const min = 0.05;
936
+ if (wa < min && wb < min) return null;
937
+ let t0 = 0;
938
+ let t1 = 1;
939
+ if (wa < min) t0 = (min - wa) / (wb - wa);
940
+ if (wb < min) t1 = (min - wa) / (wb - wa);
941
+ const a = at(u0 + (u1 - u0) * t0, v0 + (v1 - v0) * t0);
942
+ const b = at(u0 + (u1 - u0) * t1, v0 + (v1 - v0) * t1);
943
+ return a && b ? { a, b } : null;
944
+ };
945
+ const out = [];
946
+ for (let k = lo("x") / g; k <= n2; k++) {
947
+ const s = segment(k * g, lo("y"), k * g, max);
948
+ if (s) out.push(s);
949
+ }
950
+ for (let k = lo("y") / g; k <= n2; k++) {
951
+ const s = segment(lo("x"), k * g, max, k * g);
952
+ if (s) out.push(s);
953
+ }
954
+ return out;
955
+ }
956
+ function vectorPerspectivePreset(points, width, height) {
957
+ const horizon = height * 0.35;
958
+ const origin = { x: width / 2, y: height * 0.8 };
959
+ const vps = points === 1 ? [{ x: width / 2, y: horizon }] : [{ x: -width * 0.3, y: horizon }, { x: width * 1.3, y: horizon }];
960
+ if (points === 3) vps.push({ x: width / 2, y: height * 3 });
961
+ return { vanishingPoints: vps, origin, gridSize: Math.max(4, Math.round(Math.min(width, height) / 20)), extent: 12 };
962
+ }
963
+
837
964
  // src/vector/constraints/lm.ts
838
965
  var MAX_ROWS = 800;
839
966
  var maxAbs = (v) => v.reduce((m, a) => Number.isFinite(a) ? Math.max(m, Math.abs(a)) : Infinity, 0);
@@ -2414,6 +2541,21 @@ function shadingOverlay(state) {
2414
2541
  }
2415
2542
  return out;
2416
2543
  }
2544
+ function perspectiveOverlay(state) {
2545
+ const grid = state.document.perspective;
2546
+ if (!grid || grid.visible === false) return null;
2547
+ const r = (n2) => Math.round(n2 * 10) / 10;
2548
+ const planes = vectorPerspectivePlanes(grid).map((plane) => ({
2549
+ plane,
2550
+ active: state.perspectivePlane === plane,
2551
+ d: vectorPerspectiveGridLines(grid, plane).map(({ a, b }) => {
2552
+ const p = toScreen(state, a);
2553
+ const q = toScreen(state, b);
2554
+ return `M${r(p.x)} ${r(p.y)}L${r(q.x)} ${r(q.y)}`;
2555
+ }).join("")
2556
+ }));
2557
+ return { planes, vanishingPoints: grid.vanishingPoints.map((v) => toScreen(state, v)), origin: toScreen(state, grid.origin) };
2558
+ }
2417
2559
  function symmetryOverlay(state) {
2418
2560
  const ids = /* @__PURE__ */ new Set([...state.selection, ...state.symmetryTarget ? [state.symmetryTarget] : []]);
2419
2561
  const out = [];
@@ -2727,6 +2869,7 @@ function getVectorOverlayRenderData(state) {
2727
2869
  connectorSites: connectorSiteOverlay(state),
2728
2870
  shading: shadingOverlay(state),
2729
2871
  symmetry: symmetryOverlay(state),
2872
+ perspective: perspectiveOverlay(state),
2730
2873
  trackingPoints: (state.trackingPoints ?? []).map((p) => toScreen(state, p)),
2731
2874
  typedEntry: state.typedEntry ? { text: state.typedEntry, ...state.pointer ? toScreen(state, state.pointer) : { x: 0, y: 0 } } : null
2732
2875
  };
@@ -6987,6 +7130,7 @@ var VectorEditorController = class _VectorEditorController {
6987
7130
  typedEntry: this.typed,
6988
7131
  shadingPoint: this.getShadingSelection(),
6989
7132
  symmetryTarget: this.symmetryTarget && this.doc.nodes.some((n2) => n2.id === this.symmetryTarget) ? this.symmetryTarget : null,
7133
+ perspectivePlane: this.perspectivePlane && this.doc.perspective && vectorPerspectivePlanes(this.doc.perspective).includes(this.perspectivePlane) ? this.perspectivePlane : null,
6990
7134
  canUndo: h.canUndo,
6991
7135
  canRedo: h.canRedo,
6992
7136
  undoLabel: h.undoName,
@@ -9514,6 +9658,109 @@ var VectorEditorController = class _VectorEditorController {
9514
9658
  this.notify();
9515
9659
  }
9516
9660
  // -----------------------------------------------------------------------------------------
9661
+ // Perspective drawing
9662
+ perspectivePlane = null;
9663
+ /** The document's perspective grid, or null. */
9664
+ getPerspective() {
9665
+ return this.doc.perspective ?? null;
9666
+ }
9667
+ /**
9668
+ * Sets the document's perspective grid (CorelDRAW's Perspective Drawing): 1 to 3 vanishing
9669
+ * points, the origin where its planes meet, the cell size and extent, and whether it shows;
9670
+ * `null` removes it. One undo step.
9671
+ */
9672
+ setPerspective(grid) {
9673
+ let next = null;
9674
+ if (grid) {
9675
+ next = sanitizeVectorPerspective(grid);
9676
+ if (!next) return { ok: false, message: "A perspective grid needs 1 to 3 vanishing points { x, y } away from an origin { x, y }." };
9677
+ }
9678
+ this.change(next ? "Perspective Grid" : "Remove Perspective Grid", () => {
9679
+ const { perspective: _p, ...rest } = this.doc;
9680
+ this.doc = next ? { ...rest, perspective: next } : rest;
9681
+ });
9682
+ if (!next) this.perspectivePlane = null;
9683
+ return { ok: true, perspective: next };
9684
+ }
9685
+ /**
9686
+ * Draws new rectangles, ellipses, polygons and stars on a plane of the perspective grid (their
9687
+ * drag box is taken on the plane, so they recede with it), or flat again with `null`.
9688
+ */
9689
+ setPerspectivePlane(plane) {
9690
+ if (plane !== null) {
9691
+ const grid = this.doc.perspective;
9692
+ if (!grid) return { ok: false, message: "There is no perspective grid: set one with vector.setPerspective first." };
9693
+ const planes = vectorPerspectivePlanes(grid);
9694
+ if (!planes.includes(plane)) return { ok: false, message: `This grid's planes are ${planes.join(", ")}.` };
9695
+ }
9696
+ this.perspectivePlane = plane;
9697
+ this.notify();
9698
+ return { ok: true };
9699
+ }
9700
+ /**
9701
+ * Lays objects onto a plane of the perspective grid: each is read flat, relative to the grid's
9702
+ * origin (right and up), and projected onto the plane, so art near the origin keeps its size and
9703
+ * art further along recedes. Paths and groups are mapped in document space (their transforms
9704
+ * baked in); text and instances must be converted to outlines first. One undo step. Returns
9705
+ * the ids changed.
9706
+ */
9707
+ attachToPerspective(plane, options = {}) {
9708
+ const grid = this.doc.perspective;
9709
+ if (!grid) return { ok: false, message: "There is no perspective grid: set one with vector.setPerspective first." };
9710
+ const planes = vectorPerspectivePlanes(grid);
9711
+ if (!planes.includes(plane)) return { ok: false, message: `This grid's planes are ${planes.join(", ")}.` };
9712
+ const ids = [...new Set(options.ids ?? this.selectedRoots())];
9713
+ if (!ids.length) return { ok: false, message: "Select objects to put on the plane first." };
9714
+ const next = /* @__PURE__ */ new Map();
9715
+ const project = (n2, m) => {
9716
+ const own = n2.transform ? multiplyMatrices(m, n2.transform) : m;
9717
+ if (n2.type === "text" || n2.type === "instance") return `"${n2.id}" is ${n2.type === "text" ? "text" : "a symbol instance"}: convert it to outlines first (vector.createOutlines${n2.type === "instance" ? " after vector.detachInstance" : ""}).`;
9718
+ if (n2.type === "group") {
9719
+ const children = [];
9720
+ for (const c of n2.children) {
9721
+ const r = project(c, own);
9722
+ if (typeof r === "string") return r;
9723
+ children.push(r);
9724
+ }
9725
+ const { transform: _t2, ...rest2 } = n2;
9726
+ return { ...rest2, children };
9727
+ }
9728
+ const flat = transformBezierPath(subpathsToBezier(n2.subpaths), own);
9729
+ const mapped = vectorPerspectiveMapPath(grid, plane, flat);
9730
+ if (!mapped) return `"${n2.id}" reaches beyond the plane's horizon: move it nearer the origin, or the vanishing points further out.`;
9731
+ const { transform: _t, ...rest } = n2;
9732
+ return { ...rest, subpaths: bezierToSubpaths(mapped) };
9733
+ };
9734
+ for (const id of ids) {
9735
+ const loc = locateVectorNode(this.doc.nodes, id);
9736
+ if (!loc) return { ok: false, message: `There is no node "${id}".` };
9737
+ const node = loc.list[loc.index];
9738
+ if (node.locked) return { ok: false, message: `"${id}" is locked; unlock it first.` };
9739
+ const parentM = this.parentMatrixOf(id);
9740
+ const r = project(node, parentM);
9741
+ if (typeof r === "string") return { ok: false, message: r };
9742
+ const inv = invertMatrix(parentM);
9743
+ next.set(id, isIdentityMatrix(inv) ? r : transformVectorNode(r, inv));
9744
+ }
9745
+ this.change(`Attach to ${plane[0].toUpperCase()}${plane.slice(1)} Plane`, () => {
9746
+ this.setDoc(mapTree(this.doc.nodes, (n2) => next.get(n2.id) ?? n2));
9747
+ });
9748
+ return { ok: true, ids };
9749
+ }
9750
+ /** A shape's subpaths drawn on the active perspective plane from its flat drag box, or null to keep it flat. */
9751
+ perspectiveShape(kind, start, end) {
9752
+ const grid = this.doc.perspective;
9753
+ const plane = this.perspectivePlane;
9754
+ if (!grid || !plane || kind === "line" || !vectorPerspectivePlanes(grid).includes(plane)) return null;
9755
+ const toFlat = vectorPerspectivePlaneToFlat(grid, plane);
9756
+ const a = toFlat?.(start);
9757
+ const b = toFlat?.(end);
9758
+ if (!a || !b) return null;
9759
+ const flat = this.createShapeNode({ kind, ...boundsFromPoints(a, b) });
9760
+ const mapped = vectorPerspectiveMapPath(grid, plane, subpathsToBezier(flat.subpaths));
9761
+ return mapped ? bezierToSubpaths(mapped) : null;
9762
+ }
9763
+ // -----------------------------------------------------------------------------------------
9517
9764
  // PDF
9518
9765
  /**
9519
9766
  * The document as a vector PDF (see `vectorDocumentToPdf`): one page of the artboard, or a page
@@ -12305,7 +12552,9 @@ var VectorEditorController = class _VectorEditorController {
12305
12552
  const y2 = drag.start.y + h;
12306
12553
  bounds = boundsFromPoints(start, { x: x2, y: y2 });
12307
12554
  }
12308
- const shaped = this.createShapeNode({ kind: drag.tool === "frame" ? "rect" : drag.tool, ...bounds });
12555
+ const kind = drag.tool === "frame" ? "rect" : drag.tool;
12556
+ const onPlane = drag.tool !== "frame" ? this.perspectiveShape(kind, start, { x: bounds.x === start.x ? bounds.x + bounds.width : bounds.x, y: bounds.y === start.y ? bounds.y + bounds.height : bounds.y }) : null;
12557
+ const shaped = onPlane ? { subpaths: onPlane } : this.createShapeNode({ kind, ...bounds });
12309
12558
  this.replaceNode(drag.nodeId, { ...node, subpaths: shaped.subpaths });
12310
12559
  this.interaction = { type: "draw", tool: drag.tool, nodeId: drag.nodeId, bounds };
12311
12560
  this.notify(true);
@@ -15166,6 +15415,86 @@ function createVectorCommands(controller, options = {}) {
15166
15415
  }
15167
15416
  }
15168
15417
  ];
15418
+ const PLANE = { type: "string", enum: [...VECTOR_PERSPECTIVE_PLANES], description: "A plane of the grid: floor, left and right (2 or 3 vanishing points), or floor, side and front (1 point)." };
15419
+ const perspectiveResult = () => {
15420
+ const grid = controller.getPerspective();
15421
+ return { perspective: grid, planes: grid ? vectorPerspectivePlanes(grid) : [], drawingOn: controller.getState().perspectivePlane };
15422
+ };
15423
+ const perspectiveCommands = () => [
15424
+ {
15425
+ ...base,
15426
+ id: `${prefix}getPerspective`,
15427
+ label: "Perspective Grid",
15428
+ keywords: ["perspective", "grid", "vanishing point", "horizon"],
15429
+ description: "Reads the document's perspective grid: { perspective: { vanishingPoints, origin, gridSize, extent, visible } or null, planes (the planes it has), drawingOn (the plane new shapes are drawn on, or null) }.",
15430
+ params: { type: "object", properties: {}, additionalProperties: false },
15431
+ run: () => perspectiveResult()
15432
+ },
15433
+ {
15434
+ ...base,
15435
+ id: `${prefix}setPerspective`,
15436
+ label: "Perspective Grid\u2026",
15437
+ keywords: ["perspective", "grid", "vanishing point", "one point", "two point", "three point", "horizon", "drawing in perspective"],
15438
+ description: "Sets the document's perspective grid (CorelDRAW's Perspective Drawing): 1 vanishing point (planes floor, side, front), 2 (floor, left, right; walls recede to the first and second) or 3 (the third is where the walls' verticals meet). Put the vanishing points on the horizon line and the origin below it, where the planes meet. One undo step. Returns the grid as vector.getPerspective does.",
15439
+ params: {
15440
+ type: "object",
15441
+ properties: {
15442
+ vanishingPoints: { type: "array", minItems: 1, maxItems: 3, items: { ...POINT, description: "A vanishing point (document units)." }, description: "1 to 3 vanishing points, document units." },
15443
+ origin: { ...POINT, description: "Where the planes meet (document units), not on a vanishing point." },
15444
+ gridSize: { type: "number", exclusiveMinimum: 0, description: "Grid cell size at the origin, document units (default 20)." },
15445
+ extent: { type: "integer", minimum: 1, maximum: 100, description: "Cells drawn out from the origin along each axis (default 12)." },
15446
+ visible: { type: "boolean", description: "Show the grid on the canvas (default true)." }
15447
+ },
15448
+ required: ["vanishingPoints", "origin"],
15449
+ additionalProperties: false
15450
+ },
15451
+ undoable: true,
15452
+ run: (_ctx, params) => {
15453
+ check(controller.setPerspective(params));
15454
+ return perspectiveResult();
15455
+ }
15456
+ },
15457
+ {
15458
+ ...base,
15459
+ id: `${prefix}removePerspective`,
15460
+ label: "Remove Perspective Grid",
15461
+ keywords: ["perspective", "grid", "remove"],
15462
+ description: "Removes the perspective grid (art already on its planes stays as drawn). One undo step. Returns { perspective: null }.",
15463
+ params: { type: "object", properties: {}, additionalProperties: false },
15464
+ undoable: true,
15465
+ run: () => {
15466
+ check(controller.setPerspective(null));
15467
+ return { perspective: null };
15468
+ }
15469
+ },
15470
+ {
15471
+ ...base,
15472
+ id: `${prefix}setPerspectivePlane`,
15473
+ label: "Draw on Perspective Plane",
15474
+ keywords: ["perspective", "plane", "draw", "floor", "wall"],
15475
+ description: "Makes the Rectangle, Ellipse, Polygon and Star tools draw on a plane of the grid (their drag box is taken on the plane, so shapes recede with it), or flat again with plane null. Not an undo step. Returns the grid as vector.getPerspective does.",
15476
+ params: { type: "object", properties: { plane: { ...PLANE, type: ["string", "null"], enum: [...VECTOR_PERSPECTIVE_PLANES, null], description: `${PLANE.description} null draws flat.` } }, required: ["plane"], additionalProperties: false },
15477
+ run: (_ctx, params) => {
15478
+ check(controller.setPerspectivePlane(params.plane));
15479
+ return perspectiveResult();
15480
+ }
15481
+ },
15482
+ {
15483
+ ...base,
15484
+ id: `${prefix}attachToPerspective`,
15485
+ label: "Attach to Perspective",
15486
+ keywords: ["perspective", "attach", "plane", "project", "wall", "floor", "sign"],
15487
+ description: "Lays objects (`ids`, default the selection) onto a plane of the grid: each is read flat relative to the grid origin (right and up) and projected, so art near the origin keeps its size and further art recedes. Draw or place the flat art beside the origin first. Text and instances must be outlines (vector.createOutlines). One undo step. Returns { ids }.",
15488
+ params: { type: "object", properties: { ids: { ...IDS, description: "Objects to project (default: the selection)." }, plane: PLANE }, required: ["plane"], additionalProperties: false },
15489
+ undoable: true,
15490
+ enabled: enabledFor,
15491
+ run: (_ctx, params) => {
15492
+ const r = controller.attachToPerspective(params.plane, params.ids ? { ids: params.ids } : {});
15493
+ check(r);
15494
+ return { ids: r.ids ?? [] };
15495
+ }
15496
+ }
15497
+ ];
15169
15498
  const pdfCommands = () => [
15170
15499
  {
15171
15500
  ...base,
@@ -16002,6 +16331,7 @@ function createVectorCommands(controller, options = {}) {
16002
16331
  ...lottieCommands(),
16003
16332
  ...pdfCommands(),
16004
16333
  ...symmetryCommands(),
16334
+ ...perspectiveCommands(),
16005
16335
  ...lensCommands(),
16006
16336
  {
16007
16337
  ...base,
@@ -17052,6 +17382,7 @@ export {
17052
17382
  VECTOR_PATH_ACTIONS,
17053
17383
  VECTOR_PATH_TEXT_BASELINES,
17054
17384
  VECTOR_PENCIL_STRING_PX,
17385
+ VECTOR_PERSPECTIVE_PLANES,
17055
17386
  VECTOR_PRESSURE_NOMINAL,
17056
17387
  VECTOR_ROTATE_HANDLE_OFFSET,
17057
17388
  VECTOR_SPATIAL_MIN,
@@ -17358,6 +17689,7 @@ export {
17358
17689
  sanitizeVectorGradient,
17359
17690
  sanitizeVectorLens,
17360
17691
  sanitizeVectorMask,
17692
+ sanitizeVectorPerspective,
17361
17693
  sanitizeVectorResizing,
17362
17694
  sanitizeVectorShadedPaint,
17363
17695
  sanitizeVectorStrokeSettings,
@@ -17554,6 +17886,13 @@ export {
17554
17886
  vectorPathTextPositions,
17555
17887
  vectorPathWorldBezier,
17556
17888
  vectorPathfinderShape,
17889
+ vectorPerspectiveFlatToPlane,
17890
+ vectorPerspectiveGridLines,
17891
+ vectorPerspectiveMapPath,
17892
+ vectorPerspectivePlaneMap,
17893
+ vectorPerspectivePlaneToFlat,
17894
+ vectorPerspectivePlanes,
17895
+ vectorPerspectivePreset,
17557
17896
  vectorPolarAngles,
17558
17897
  vectorPolarIncrementOf,
17559
17898
  vectorRepeaterCopies,