@pixerate/editor 0.132.0 → 0.134.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 (90) 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-oCBfrGDG.d.cts → bezier-J8i4uqSH.d.cts} +1 -1
  6. package/dist/{bezier-0IHDCvv_.d.ts → bezier-lNEOp0Jx.d.ts} +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-YRZ6NVE3.js → chunk-6CTZAGZ2.js} +235 -4
  14. package/dist/chunk-6CTZAGZ2.js.map +1 -0
  15. package/dist/{chunk-ECVI5QRE.js → chunk-7RJ6P7MI.js} +2 -2
  16. package/dist/{chunk-G5FX56KX.js → chunk-ESIWZMMK.js} +51 -11
  17. package/dist/chunk-ESIWZMMK.js.map +1 -0
  18. package/dist/{chunk-IEYXYMRQ.js → chunk-HHTCDFZC.js} +99 -5
  19. package/dist/chunk-HHTCDFZC.js.map +1 -0
  20. package/dist/{chunk-W6M7C7R3.cjs → chunk-NVTHUPJ6.cjs} +236 -5
  21. package/dist/chunk-NVTHUPJ6.cjs.map +1 -0
  22. package/dist/{chunk-OYPJGGF3.cjs → chunk-T6TV6HY6.cjs} +36 -36
  23. package/dist/{chunk-OYPJGGF3.cjs.map → chunk-T6TV6HY6.cjs.map} +1 -1
  24. package/dist/{chunk-55UEZIU4.cjs → chunk-YAFS2QFZ.cjs} +231 -191
  25. package/dist/chunk-YAFS2QFZ.cjs.map +1 -0
  26. package/dist/{chunk-YYJKIESX.cjs → chunk-Z2H7KECK.cjs} +153 -59
  27. package/dist/chunk-Z2H7KECK.cjs.map +1 -0
  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-BGdT02aL.d.cts} +10 -10
  31. package/dist/{export-CfAogLEr.d.cts → export-DMeG6E06.d.ts} +1 -1
  32. package/dist/{export-ByJvAvR7.d.ts → export-Dng22YB8.d.ts} +10 -10
  33. package/dist/{export-Crl82sbE.d.ts → export-KzubBKo7.d.cts} +1 -1
  34. package/dist/{helpers-BCd2-PAR.d.ts → helpers-Coa4e2j_.d.ts} +35 -1
  35. package/dist/{helpers-CoIeXQnW.d.cts → helpers-DHl28yLO.d.cts} +35 -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-BWR7Qv8W.d.cts} +1 -1
  43. package/dist/{opentype-BQFEvy-t.d.ts → opentype-Dfcx6wFx.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-DaVdT8jq.d.ts → pathfinder-Celbi3e6.d.ts} +1 -1
  54. package/dist/{pathfinder-01ZgAwqV.d.cts → pathfinder-D8KQ6blQ.d.cts} +1 -1
  55. package/dist/{pool-D2A3WTt9.d.ts → pool-8JZGWHvR.d.ts} +1 -1
  56. package/dist/{pool-D6a45MoE.d.cts → pool-BfuxBKTd.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-DELNVerw.d.ts → svg-CxxqG3_W.d.ts} +2 -2
  64. package/dist/{svg-CcDz7Zq2.d.cts → svg-pwXWq6s2.d.cts} +2 -2
  65. package/dist/{tools-BFvkFnRj.d.cts → tools-3EBjR91b.d.cts} +1 -1
  66. package/dist/{tools-D7ws-OKL.d.ts → tools-y1M6O3UT.d.ts} +1 -1
  67. package/dist/vector/index.cjs +1642 -1046
  68. package/dist/vector/index.cjs.map +1 -1
  69. package/dist/vector/index.d.cts +275 -93
  70. package/dist/vector/index.d.ts +275 -93
  71. package/dist/vector/index.js +607 -11
  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-2N9wkOkL.d.ts → warp-C1gwu2G_.d.ts} +1 -1
  76. package/dist/{warp-DErXW5rz.d.cts → warp-Ceqdz7kj.d.cts} +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/skills/pixerate-vector/SKILL.md +12 -53
  81. package/skills/pixerate-vector/references/pitfalls.md +62 -0
  82. package/dist/chunk-55UEZIU4.cjs.map +0 -1
  83. package/dist/chunk-AHUYMDYW.js.map +0 -1
  84. package/dist/chunk-G5FX56KX.js.map +0 -1
  85. package/dist/chunk-IEYXYMRQ.js.map +0 -1
  86. package/dist/chunk-MZXQTKUM.cjs.map +0 -1
  87. package/dist/chunk-W6M7C7R3.cjs.map +0 -1
  88. package/dist/chunk-YRZ6NVE3.js.map +0 -1
  89. package/dist/chunk-YYJKIESX.cjs.map +0 -1
  90. /package/dist/{chunk-ECVI5QRE.js.map → chunk-7RJ6P7MI.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-7RJ6P7MI.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,
@@ -206,6 +208,7 @@ import {
206
208
  isVectorMaskActive,
207
209
  isVectorMaskGroup,
208
210
  isVectorMaskType,
211
+ isVectorMasterPage,
209
212
  isVectorMeshGradient,
210
213
  isVectorShadedPaint,
211
214
  isVectorSymmetryGroup,
@@ -271,6 +274,7 @@ import {
271
274
  rerouteVectorConnectors,
272
275
  resizeWarpMesh,
273
276
  resolveVectorLenses,
277
+ resolveVectorMasterPages,
274
278
  resolveVectorSymbols,
275
279
  reverseSubpath,
276
280
  reverseVectorGradient,
@@ -317,6 +321,7 @@ import {
317
321
  splitWarpMeshV,
318
322
  starSubpath,
319
323
  stripVectorLensViews,
324
+ stripVectorMasterArt,
320
325
  stripVectorSymbolArt,
321
326
  strokePositionPoint,
322
327
  strokeWidthProfileOf,
@@ -411,6 +416,8 @@ import {
411
416
  vectorMaskedGeometry,
412
417
  vectorMaskedLocalBounds,
413
418
  vectorMaskedShapes,
419
+ vectorMasterArt,
420
+ vectorMasterPages,
414
421
  vectorMeshColorAt,
415
422
  vectorMeshFromOutline,
416
423
  vectorMeshLines,
@@ -425,6 +432,7 @@ import {
425
432
  vectorNodeToBezier,
426
433
  vectorNodesNear,
427
434
  vectorOperandOperation,
435
+ vectorPages,
428
436
  vectorPaintLayerNodes,
429
437
  vectorPaintSolid,
430
438
  vectorPaintToSvg,
@@ -493,7 +501,7 @@ import {
493
501
  withVectorStrokes,
494
502
  withVectorTargetPaint,
495
503
  zigzagPaths
496
- } from "../chunk-IEYXYMRQ.js";
504
+ } from "../chunk-HHTCDFZC.js";
497
505
  import {
498
506
  bezierPathBounds,
499
507
  bezierPathToSvg,
@@ -834,6 +842,131 @@ function releaseVectorBlend(group, newId) {
834
842
  return [...keys, group.transform ? transformVectorNode(path, m) : path];
835
843
  }
836
844
 
845
+ // src/vector/perspective.ts
846
+ function vectorPerspectivePlanes(p) {
847
+ return p.vanishingPoints.length <= 1 ? ["floor", "side", "front"] : ["floor", "left", "right"];
848
+ }
849
+ function sanitizeVectorPerspective(value) {
850
+ if (!value || typeof value !== "object") return null;
851
+ const r = value;
852
+ const isPt = (v) => !!v && typeof v === "object" && Number.isFinite(v.x) && Number.isFinite(v.y);
853
+ if (!Array.isArray(r.vanishingPoints) || r.vanishingPoints.length < 1 || r.vanishingPoints.length > 3 || !r.vanishingPoints.every(isPt) || !isPt(r.origin)) return null;
854
+ const origin = { x: r.origin.x, y: r.origin.y };
855
+ const vps = r.vanishingPoints.map((v) => ({ x: v.x, y: v.y }));
856
+ if (vps.some((v) => Math.hypot(v.x - origin.x, v.y - origin.y) < 1e-6)) return null;
857
+ const out = { vanishingPoints: vps, origin };
858
+ if (typeof r.gridSize === "number" && r.gridSize > 0 && Number.isFinite(r.gridSize)) out.gridSize = Math.min(1e3, r.gridSize);
859
+ if (typeof r.extent === "number" && Number.isFinite(r.extent)) out.extent = Math.min(100, Math.max(1, Math.round(r.extent)));
860
+ if (r.visible === false) out.visible = false;
861
+ return out;
862
+ }
863
+ function towards(o, vp, want) {
864
+ const d = Math.hypot(vp.x - o.x, vp.y - o.y);
865
+ let a = 1 / d;
866
+ if (want === "right" ? vp.x - o.x < 0 : vp.y - o.y > 0) a = -a;
867
+ return [a * vp.x, a * vp.y, a];
868
+ }
869
+ function vectorPerspectivePlaneMap(p, plane) {
870
+ if (!vectorPerspectivePlanes(p).includes(plane)) return null;
871
+ const o = p.origin;
872
+ const [v1, v2, v3] = p.vanishingPoints;
873
+ const right = [1, 0, 0];
874
+ const up = v3 ? towards(o, v3, "up") : [0, -1, 0];
875
+ let x;
876
+ let y;
877
+ if (p.vanishingPoints.length <= 1) {
878
+ if (plane === "floor") [x, y] = [right, towards(o, v1, "up")];
879
+ else if (plane === "side") [x, y] = [towards(o, v1, "right"), up];
880
+ else [x, y] = [right, up];
881
+ } else if (plane === "floor") [x, y] = [towards(o, v2, "right"), towards(o, v1, "up")];
882
+ else if (plane === "left") [x, y] = [towards(o, v1, "right"), up];
883
+ else [x, y] = [towards(o, v2, "right"), up];
884
+ return [x[0], y[0], o.x, x[1], y[1], o.y, x[2], y[2], 1];
885
+ }
886
+ function vectorPerspectiveFlatToPlane(p, plane) {
887
+ const h = vectorPerspectivePlaneMap(p, plane);
888
+ if (!h) return null;
889
+ const o = p.origin;
890
+ return (q) => {
891
+ const u = q.x - o.x;
892
+ const v = o.y - q.y;
893
+ const w = h[6] * u + h[7] * v + h[8];
894
+ if (!(w > 1e-6)) return null;
895
+ return applyHomography(h, { x: u, y: v });
896
+ };
897
+ }
898
+ function vectorPerspectivePlaneToFlat(p, plane) {
899
+ const h = vectorPerspectivePlaneMap(p, plane);
900
+ const inv = h ? invertHomography(h) : null;
901
+ if (!inv) return null;
902
+ const o = p.origin;
903
+ return (q) => {
904
+ const f = applyHomography(inv, q);
905
+ if (!f) return null;
906
+ const w = h[6] * f.x + h[7] * f.y + h[8];
907
+ if (!(w > 1e-6)) return null;
908
+ return { x: o.x + f.x, y: o.y - f.y };
909
+ };
910
+ }
911
+ function vectorPerspectiveMapPath(p, plane, path) {
912
+ const f = vectorPerspectiveFlatToPlane(p, plane);
913
+ if (!f) return null;
914
+ let behind = false;
915
+ const out = mapBezierPath(
916
+ path,
917
+ (q) => {
918
+ const r = f(q);
919
+ if (!r) {
920
+ behind = true;
921
+ return q;
922
+ }
923
+ return r;
924
+ },
925
+ 0.1
926
+ );
927
+ return behind ? null : out;
928
+ }
929
+ function vectorPerspectiveGridLines(p, plane) {
930
+ const h = vectorPerspectivePlaneMap(p, plane);
931
+ if (!h) return [];
932
+ const g = p.gridSize ?? 20;
933
+ const n2 = p.extent ?? 12;
934
+ const max = n2 * g;
935
+ const lo = (axis) => axis === "y" && plane !== "floor" ? 0 : -max;
936
+ const w = (u, v) => h[6] * u + h[7] * v + h[8];
937
+ const at = (u, v) => applyHomography(h, { x: u, y: v });
938
+ const segment = (u0, v0, u1, v1) => {
939
+ const wa = w(u0, v0);
940
+ const wb = w(u1, v1);
941
+ const min = 0.05;
942
+ if (wa < min && wb < min) return null;
943
+ let t0 = 0;
944
+ let t1 = 1;
945
+ if (wa < min) t0 = (min - wa) / (wb - wa);
946
+ if (wb < min) t1 = (min - wa) / (wb - wa);
947
+ const a = at(u0 + (u1 - u0) * t0, v0 + (v1 - v0) * t0);
948
+ const b = at(u0 + (u1 - u0) * t1, v0 + (v1 - v0) * t1);
949
+ return a && b ? { a, b } : null;
950
+ };
951
+ const out = [];
952
+ for (let k = lo("x") / g; k <= n2; k++) {
953
+ const s = segment(k * g, lo("y"), k * g, max);
954
+ if (s) out.push(s);
955
+ }
956
+ for (let k = lo("y") / g; k <= n2; k++) {
957
+ const s = segment(lo("x"), k * g, max, k * g);
958
+ if (s) out.push(s);
959
+ }
960
+ return out;
961
+ }
962
+ function vectorPerspectivePreset(points, width, height) {
963
+ const horizon = height * 0.35;
964
+ const origin = { x: width / 2, y: height * 0.8 };
965
+ const vps = points === 1 ? [{ x: width / 2, y: horizon }] : [{ x: -width * 0.3, y: horizon }, { x: width * 1.3, y: horizon }];
966
+ if (points === 3) vps.push({ x: width / 2, y: height * 3 });
967
+ return { vanishingPoints: vps, origin, gridSize: Math.max(4, Math.round(Math.min(width, height) / 20)), extent: 12 };
968
+ }
969
+
837
970
  // src/vector/constraints/lm.ts
838
971
  var MAX_ROWS = 800;
839
972
  var maxAbs = (v) => v.reduce((m, a) => Number.isFinite(a) ? Math.max(m, Math.abs(a)) : Infinity, 0);
@@ -2310,7 +2443,7 @@ var lensResolved = /* @__PURE__ */ new WeakMap();
2310
2443
  function resolvedLensNodes(doc) {
2311
2444
  const hit = lensResolved.get(doc.nodes);
2312
2445
  if (hit) return hit;
2313
- const nodes = resolveVectorLenses(doc).nodes;
2446
+ const nodes = resolveVectorLenses(resolveVectorMasterPages(doc)).nodes;
2314
2447
  lensResolved.set(doc.nodes, nodes);
2315
2448
  return nodes;
2316
2449
  }
@@ -2414,6 +2547,21 @@ function shadingOverlay(state) {
2414
2547
  }
2415
2548
  return out;
2416
2549
  }
2550
+ function perspectiveOverlay(state) {
2551
+ const grid = state.document.perspective;
2552
+ if (!grid || grid.visible === false) return null;
2553
+ const r = (n2) => Math.round(n2 * 10) / 10;
2554
+ const planes = vectorPerspectivePlanes(grid).map((plane) => ({
2555
+ plane,
2556
+ active: state.perspectivePlane === plane,
2557
+ d: vectorPerspectiveGridLines(grid, plane).map(({ a, b }) => {
2558
+ const p = toScreen(state, a);
2559
+ const q = toScreen(state, b);
2560
+ return `M${r(p.x)} ${r(p.y)}L${r(q.x)} ${r(q.y)}`;
2561
+ }).join("")
2562
+ }));
2563
+ return { planes, vanishingPoints: grid.vanishingPoints.map((v) => toScreen(state, v)), origin: toScreen(state, grid.origin) };
2564
+ }
2417
2565
  function symmetryOverlay(state) {
2418
2566
  const ids = /* @__PURE__ */ new Set([...state.selection, ...state.symmetryTarget ? [state.symmetryTarget] : []]);
2419
2567
  const out = [];
@@ -2727,6 +2875,7 @@ function getVectorOverlayRenderData(state) {
2727
2875
  connectorSites: connectorSiteOverlay(state),
2728
2876
  shading: shadingOverlay(state),
2729
2877
  symmetry: symmetryOverlay(state),
2878
+ perspective: perspectiveOverlay(state),
2730
2879
  trackingPoints: (state.trackingPoints ?? []).map((p) => toScreen(state, p)),
2731
2880
  typedEntry: state.typedEntry ? { text: state.typedEntry, ...state.pointer ? toScreen(state, state.pointer) : { x: 0, y: 0 } } : null
2732
2881
  };
@@ -3535,10 +3684,10 @@ ${offset}
3535
3684
  var pdfString = (s) => `(${s.replace(/[\\()]/g, (c) => `\\${c}`).replace(/[^\x20-\x7e]/g, "?")})`;
3536
3685
  function vectorDocumentToPdf(doc, options = {}) {
3537
3686
  const notes = /* @__PURE__ */ new Set();
3538
- doc = resolveVectorLenses(doc);
3687
+ doc = resolveVectorLenses(resolveVectorMasterPages(doc));
3539
3688
  const scale = options.scale ?? 1;
3540
3689
  const compress = options.compress !== false;
3541
- const frames = options.pages === "artboards" ? vectorArtboards(doc) : [];
3690
+ const frames = options.pages === "artboards" ? vectorPages(doc) : [];
3542
3691
  if (options.pages === "artboards" && !frames.length) notes.add("There are no artboards (top-level frames): the whole drawing is one page.");
3543
3692
  const docs = frames.length ? frames.map((f) => vectorFrameDocument(doc, f.id, findVectorNode)) : [doc];
3544
3693
  const w = new PdfWriter();
@@ -3805,7 +3954,7 @@ function vectorDocumentToLottie(doc, options = {}) {
3805
3954
  const fps = options.fps ?? 30;
3806
3955
  const op = Math.max(1, Math.round(options.frames ?? 1));
3807
3956
  const shapes = [];
3808
- if (!options.nodes) doc = resolveVectorLenses(doc);
3957
+ if (!options.nodes) doc = resolveVectorLenses(resolveVectorMasterPages(doc));
3809
3958
  for (const n2 of [...options.nodes ?? doc.nodes].reverse()) {
3810
3959
  const g = nodeGroup(n2, notes);
3811
3960
  if (g) shapes.push(g);
@@ -6987,6 +7136,7 @@ var VectorEditorController = class _VectorEditorController {
6987
7136
  typedEntry: this.typed,
6988
7137
  shadingPoint: this.getShadingSelection(),
6989
7138
  symmetryTarget: this.symmetryTarget && this.doc.nodes.some((n2) => n2.id === this.symmetryTarget) ? this.symmetryTarget : null,
7139
+ perspectivePlane: this.perspectivePlane && this.doc.perspective && vectorPerspectivePlanes(this.doc.perspective).includes(this.perspectivePlane) ? this.perspectivePlane : null,
6990
7140
  canUndo: h.canUndo,
6991
7141
  canRedo: h.canRedo,
6992
7142
  undoLabel: h.undoName,
@@ -7003,7 +7153,7 @@ var VectorEditorController = class _VectorEditorController {
7003
7153
  out = { ...out, symbols: out.symbols.map((s) => s.id === edit.symbolId ? { ...s, nodes: out.nodes } : s) };
7004
7154
  }
7005
7155
  out = resolveVectorSymbols(out);
7006
- return resolveVectorLenses(rerouteVectorConnectors(this.solved(out)));
7156
+ return resolveVectorLenses(resolveVectorMasterPages(rerouteVectorConnectors(this.solved(out))));
7007
7157
  }
7008
7158
  /**
7009
7159
  * `doc` with its constraints solved (geometry edited since the last solve moving least) and
@@ -7497,7 +7647,7 @@ var VectorEditorController = class _VectorEditorController {
7497
7647
  return vectorDocumentToSvg(this.storedDocument(), options);
7498
7648
  }
7499
7649
  toJSON() {
7500
- return cloneVectorDocument(stripVectorLensViews(stripVectorSymbolArt(this.storedDocument())));
7650
+ return cloneVectorDocument(stripVectorMasterArt(stripVectorLensViews(stripVectorSymbolArt(this.storedDocument()))));
7501
7651
  }
7502
7652
  /** Adds a node (ids are kept unless taken). Returns its id. */
7503
7653
  addNode(node, options = {}) {
@@ -9514,6 +9664,109 @@ var VectorEditorController = class _VectorEditorController {
9514
9664
  this.notify();
9515
9665
  }
9516
9666
  // -----------------------------------------------------------------------------------------
9667
+ // Perspective drawing
9668
+ perspectivePlane = null;
9669
+ /** The document's perspective grid, or null. */
9670
+ getPerspective() {
9671
+ return this.doc.perspective ?? null;
9672
+ }
9673
+ /**
9674
+ * Sets the document's perspective grid (CorelDRAW's Perspective Drawing): 1 to 3 vanishing
9675
+ * points, the origin where its planes meet, the cell size and extent, and whether it shows;
9676
+ * `null` removes it. One undo step.
9677
+ */
9678
+ setPerspective(grid) {
9679
+ let next = null;
9680
+ if (grid) {
9681
+ next = sanitizeVectorPerspective(grid);
9682
+ if (!next) return { ok: false, message: "A perspective grid needs 1 to 3 vanishing points { x, y } away from an origin { x, y }." };
9683
+ }
9684
+ this.change(next ? "Perspective Grid" : "Remove Perspective Grid", () => {
9685
+ const { perspective: _p, ...rest } = this.doc;
9686
+ this.doc = next ? { ...rest, perspective: next } : rest;
9687
+ });
9688
+ if (!next) this.perspectivePlane = null;
9689
+ return { ok: true, perspective: next };
9690
+ }
9691
+ /**
9692
+ * Draws new rectangles, ellipses, polygons and stars on a plane of the perspective grid (their
9693
+ * drag box is taken on the plane, so they recede with it), or flat again with `null`.
9694
+ */
9695
+ setPerspectivePlane(plane) {
9696
+ if (plane !== null) {
9697
+ const grid = this.doc.perspective;
9698
+ if (!grid) return { ok: false, message: "There is no perspective grid: set one with vector.setPerspective first." };
9699
+ const planes = vectorPerspectivePlanes(grid);
9700
+ if (!planes.includes(plane)) return { ok: false, message: `This grid's planes are ${planes.join(", ")}.` };
9701
+ }
9702
+ this.perspectivePlane = plane;
9703
+ this.notify();
9704
+ return { ok: true };
9705
+ }
9706
+ /**
9707
+ * Lays objects onto a plane of the perspective grid: each is read flat, relative to the grid's
9708
+ * origin (right and up), and projected onto the plane, so art near the origin keeps its size and
9709
+ * art further along recedes. Paths and groups are mapped in document space (their transforms
9710
+ * baked in); text and instances must be converted to outlines first. One undo step. Returns
9711
+ * the ids changed.
9712
+ */
9713
+ attachToPerspective(plane, options = {}) {
9714
+ const grid = this.doc.perspective;
9715
+ if (!grid) return { ok: false, message: "There is no perspective grid: set one with vector.setPerspective first." };
9716
+ const planes = vectorPerspectivePlanes(grid);
9717
+ if (!planes.includes(plane)) return { ok: false, message: `This grid's planes are ${planes.join(", ")}.` };
9718
+ const ids = [...new Set(options.ids ?? this.selectedRoots())];
9719
+ if (!ids.length) return { ok: false, message: "Select objects to put on the plane first." };
9720
+ const next = /* @__PURE__ */ new Map();
9721
+ const project = (n2, m) => {
9722
+ const own = n2.transform ? multiplyMatrices(m, n2.transform) : m;
9723
+ 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" : ""}).`;
9724
+ if (n2.type === "group") {
9725
+ const children = [];
9726
+ for (const c of n2.children) {
9727
+ const r = project(c, own);
9728
+ if (typeof r === "string") return r;
9729
+ children.push(r);
9730
+ }
9731
+ const { transform: _t2, ...rest2 } = n2;
9732
+ return { ...rest2, children };
9733
+ }
9734
+ const flat = transformBezierPath(subpathsToBezier(n2.subpaths), own);
9735
+ const mapped = vectorPerspectiveMapPath(grid, plane, flat);
9736
+ if (!mapped) return `"${n2.id}" reaches beyond the plane's horizon: move it nearer the origin, or the vanishing points further out.`;
9737
+ const { transform: _t, ...rest } = n2;
9738
+ return { ...rest, subpaths: bezierToSubpaths(mapped) };
9739
+ };
9740
+ for (const id of ids) {
9741
+ const loc = locateVectorNode(this.doc.nodes, id);
9742
+ if (!loc) return { ok: false, message: `There is no node "${id}".` };
9743
+ const node = loc.list[loc.index];
9744
+ if (node.locked) return { ok: false, message: `"${id}" is locked; unlock it first.` };
9745
+ const parentM = this.parentMatrixOf(id);
9746
+ const r = project(node, parentM);
9747
+ if (typeof r === "string") return { ok: false, message: r };
9748
+ const inv = invertMatrix(parentM);
9749
+ next.set(id, isIdentityMatrix(inv) ? r : transformVectorNode(r, inv));
9750
+ }
9751
+ this.change(`Attach to ${plane[0].toUpperCase()}${plane.slice(1)} Plane`, () => {
9752
+ this.setDoc(mapTree(this.doc.nodes, (n2) => next.get(n2.id) ?? n2));
9753
+ });
9754
+ return { ok: true, ids };
9755
+ }
9756
+ /** A shape's subpaths drawn on the active perspective plane from its flat drag box, or null to keep it flat. */
9757
+ perspectiveShape(kind, start, end) {
9758
+ const grid = this.doc.perspective;
9759
+ const plane = this.perspectivePlane;
9760
+ if (!grid || !plane || kind === "line" || !vectorPerspectivePlanes(grid).includes(plane)) return null;
9761
+ const toFlat = vectorPerspectivePlaneToFlat(grid, plane);
9762
+ const a = toFlat?.(start);
9763
+ const b = toFlat?.(end);
9764
+ if (!a || !b) return null;
9765
+ const flat = this.createShapeNode({ kind, ...boundsFromPoints(a, b) });
9766
+ const mapped = vectorPerspectiveMapPath(grid, plane, subpathsToBezier(flat.subpaths));
9767
+ return mapped ? bezierToSubpaths(mapped) : null;
9768
+ }
9769
+ // -----------------------------------------------------------------------------------------
9517
9770
  // PDF
9518
9771
  /**
9519
9772
  * The document as a vector PDF (see `vectorDocumentToPdf`): one page of the artboard, or a page
@@ -10312,6 +10565,146 @@ var VectorEditorController = class _VectorEditorController {
10312
10565
  });
10313
10566
  return { ok: true, id };
10314
10567
  }
10568
+ // -----------------------------------------------------------------------------------------
10569
+ // Pages and master pages
10570
+ /**
10571
+ * The document's pages (top-level frames that are not master pages, in order: `number` from 1)
10572
+ * with the master each uses, and its master pages.
10573
+ */
10574
+ getPages() {
10575
+ const pages = vectorPages(this.doc);
10576
+ const masters = vectorMasterPages(this.doc);
10577
+ const box = (f) => ({ x: f.frame.x, y: f.frame.y, width: f.frame.width, height: f.frame.height });
10578
+ return {
10579
+ pages: pages.map((p, i) => ({ id: p.id, name: p.name ?? `Page ${i + 1}`, number: i + 1, masterId: p.frame.masterId && masters.some((m) => m.id === p.frame.masterId) ? p.frame.masterId : null, bounds: box(p) })),
10580
+ masters: masters.map((m) => ({ id: m.id, name: m.name ?? "Master", usedBy: pages.filter((p) => p.frame.masterId === m.id).map((p) => p.id), bounds: box(m) }))
10581
+ };
10582
+ }
10583
+ /** Where a new page or master goes: right of every top-level frame, or below them for a master. */
10584
+ nextPageRect(master) {
10585
+ const frames = this.doc.nodes.filter(isVectorFrame);
10586
+ const pages = vectorPages(this.doc);
10587
+ const like = pages[pages.length - 1]?.frame ?? frames[frames.length - 1]?.frame;
10588
+ const width = like?.width ?? this.doc.width;
10589
+ const height = like?.height ?? this.doc.height;
10590
+ if (!frames.length) return { x: 0, y: 0, width, height };
10591
+ const gap = Math.max(20, Math.round(width * 0.1));
10592
+ if (master) {
10593
+ const bottom = Math.max(...frames.map((f) => f.frame.y + f.frame.height));
10594
+ const left = Math.min(...frames.map((f) => f.frame.x));
10595
+ return { x: left, y: bottom + gap * 2, width, height };
10596
+ }
10597
+ const right = Math.max(...pages.map((f) => f.frame.x + f.frame.width), ...pages.length ? [] : frames.map((f) => f.frame.x + f.frame.width));
10598
+ const top = pages.length ? Math.min(...pages.map((f) => f.frame.y)) : Math.min(...frames.map((f) => f.frame.y));
10599
+ return { x: right + gap, y: top, width, height };
10600
+ }
10601
+ /**
10602
+ * Adds a page: a frame the size of the last page (or the document), to the right of the others,
10603
+ * after page `after` in the page order (default last), using master page `masterId` (default:
10604
+ * the master the last page uses). The first page added to a document without frames covers the
10605
+ * artboard and takes the existing top-level art in. One undo step; selects it. Returns its id.
10606
+ */
10607
+ addPage(input = {}) {
10608
+ const pages = vectorPages(this.doc);
10609
+ if (input.masterId && !isVectorMasterPage(this.doc.nodes.find((n2) => n2.id === input.masterId))) return { ok: false, message: `"${input.masterId}" is not a master page (add one with vector.addMasterPage).` };
10610
+ if (input.after !== void 0 && !pages.some((p) => p.id === input.after)) return { ok: false, message: `"${input.after}" is not a page.` };
10611
+ const first = !this.doc.nodes.some(isVectorFrame);
10612
+ const rect = this.nextPageRect(false);
10613
+ const id = this.newId();
10614
+ const masterId = input.masterId === void 0 ? pages[pages.length - 1]?.frame.masterId ?? null : input.masterId;
10615
+ const adopted = first ? this.doc.nodes.filter((n2) => !n2.locked && n2.visible !== false && !n2.blendStep) : [];
10616
+ const page = {
10617
+ id,
10618
+ type: "group",
10619
+ name: input.name ?? `Page ${pages.length + 1}`,
10620
+ frame: { ...rect, fill: "#ffffff", ...masterId ? { masterId } : {} },
10621
+ children: adopted
10622
+ };
10623
+ const taken = new Set(adopted.map((n2) => n2.id));
10624
+ this.change("Add Page", () => {
10625
+ const rest = this.doc.nodes.filter((n2) => !taken.has(n2.id));
10626
+ const at = input.after ? rest.findIndex((n2) => n2.id === input.after) + 1 : rest.length;
10627
+ this.setDoc([...rest.slice(0, at), page, ...rest.slice(at)]);
10628
+ this.selection = [id];
10629
+ this.anchors = [];
10630
+ this.editingId = null;
10631
+ });
10632
+ return { ok: true, id };
10633
+ }
10634
+ /**
10635
+ * Adds an empty master page (a frame with `master` set, the size of the last page) below the
10636
+ * pages. Art drawn in it shows on every page that uses it; text can show `{page}` and `{pages}`.
10637
+ * With `assignTo` (page ids, or `all`), those pages use it. One undo step; selects it.
10638
+ */
10639
+ addMasterPage(input = {}) {
10640
+ const pages = vectorPages(this.doc);
10641
+ const assign = input.assignTo === "all" ? pages.map((p) => p.id) : input.assignTo ?? [];
10642
+ for (const pid of assign) if (!pages.some((p) => p.id === pid)) return { ok: false, message: `"${pid}" is not a page.` };
10643
+ const rect = this.nextPageRect(true);
10644
+ const id = this.newId();
10645
+ const master = { id, type: "group", name: input.name ?? `Master ${String.fromCharCode(65 + vectorMasterPages(this.doc).length % 26)}`, frame: { ...rect, master: true }, children: [] };
10646
+ const set = new Set(assign);
10647
+ this.change("Add Master Page", () => {
10648
+ this.setDoc([...this.doc.nodes.map((n2) => set.has(n2.id) && isVectorFrame(n2) ? { ...n2, frame: { ...n2.frame, masterId: id } } : n2), master]);
10649
+ this.selection = [id];
10650
+ this.anchors = [];
10651
+ this.editingId = null;
10652
+ });
10653
+ return { ok: true, id };
10654
+ }
10655
+ /** Makes pages use master page `masterId`, or none with null. One undo step. */
10656
+ setPageMaster(masterId, options = {}) {
10657
+ const pages = vectorPages(this.doc);
10658
+ const ids = options.ids ?? this.selection.filter((id) => pages.some((p) => p.id === id));
10659
+ if (!ids.length) return { ok: false, message: "Select a page (a top-level frame) first." };
10660
+ for (const id of ids) if (!pages.some((p) => p.id === id)) return { ok: false, message: `"${id}" is not a page${isVectorMasterPage(this.getNode(id)) ? " (it is a master page)" : ""}.` };
10661
+ if (masterId !== null && !isVectorMasterPage(this.doc.nodes.find((n2) => n2.id === masterId))) return { ok: false, message: `"${masterId}" is not a master page.` };
10662
+ const set = new Set(ids);
10663
+ this.change(masterId ? "Apply Master Page" : "Remove Master Page", () => {
10664
+ this.setDoc(
10665
+ this.doc.nodes.map((n2) => {
10666
+ if (!set.has(n2.id) || !isVectorFrame(n2)) return n2;
10667
+ const { masterId: _m, ...frame } = n2.frame;
10668
+ return { ...n2, frame: masterId ? { ...frame, masterId } : frame };
10669
+ })
10670
+ );
10671
+ });
10672
+ return { ok: true, ids: [...ids] };
10673
+ }
10674
+ /** Moves page `id` to position `index` (0-based) in the page order. One undo step. Returns the new order. */
10675
+ movePage(id, index) {
10676
+ const pages = vectorPages(this.doc);
10677
+ const from = pages.findIndex((p) => p.id === id);
10678
+ if (from < 0) return { ok: false, message: `"${id}" is not a page.` };
10679
+ if (!Number.isInteger(index) || index < 0 || index >= pages.length) return { ok: false, message: `index must be from 0 to ${pages.length - 1}.` };
10680
+ if (index === from) return { ok: true, order: pages.map((p) => p.id) };
10681
+ const order = pages.map((p) => p.id);
10682
+ order.splice(index, 0, ...order.splice(from, 1));
10683
+ const slots = this.doc.nodes.map((n2, i) => pages.some((p) => p.id === n2.id) ? i : -1).filter((i) => i >= 0);
10684
+ const byId = new Map(pages.map((p) => [p.id, p]));
10685
+ this.change("Move Page", () => {
10686
+ const nodes = this.doc.nodes.slice();
10687
+ slots.forEach((slot, k) => nodes[slot] = byId.get(order[k]));
10688
+ this.setDoc(nodes);
10689
+ });
10690
+ return { ok: true, order };
10691
+ }
10692
+ /**
10693
+ * Goes to a page (or master page): selects it and brings it into view, fitted to a canvas of
10694
+ * `size` screen pixels when given, otherwise at the current zoom with its corner near the
10695
+ * canvas's top-left. Not an undo step.
10696
+ */
10697
+ goToPage(id, options = {}) {
10698
+ const n2 = this.doc.nodes.find((x) => x.id === id);
10699
+ if (!isVectorFrame(n2)) return { ok: false, message: `"${id}" is not a page or master page.` };
10700
+ const b = vectorNodeBounds(n2) ?? { x: n2.frame.x, y: n2.frame.y, width: n2.frame.width, height: n2.frame.height };
10701
+ this.selection = [id];
10702
+ this.anchors = [];
10703
+ if (options.size) this.zoomToFit(options.size, { bounds: b });
10704
+ else this.setViewport({ x: 40 - b.x * this.viewport.zoom, y: 40 - b.y * this.viewport.zoom });
10705
+ this.notify();
10706
+ return { ok: true };
10707
+ }
10315
10708
  /**
10316
10709
  * Frame Selection: puts the selected (or given) objects, which must share a parent, into a new
10317
10710
  * frame fitted to their bounds plus `padding` (document units, default 0), in their place in the
@@ -12305,7 +12698,9 @@ var VectorEditorController = class _VectorEditorController {
12305
12698
  const y2 = drag.start.y + h;
12306
12699
  bounds = boundsFromPoints(start, { x: x2, y: y2 });
12307
12700
  }
12308
- const shaped = this.createShapeNode({ kind: drag.tool === "frame" ? "rect" : drag.tool, ...bounds });
12701
+ const kind = drag.tool === "frame" ? "rect" : drag.tool;
12702
+ 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;
12703
+ const shaped = onPlane ? { subpaths: onPlane } : this.createShapeNode({ kind, ...bounds });
12309
12704
  this.replaceNode(drag.nodeId, { ...node, subpaths: shaped.subpaths });
12310
12705
  this.interaction = { type: "draw", tool: drag.tool, nodeId: drag.nodeId, bounds };
12311
12706
  this.notify(true);
@@ -14634,7 +15029,8 @@ function createVectorCommands(controller, options = {}) {
14634
15029
  name: { type: "string", description: "Layer name (default the text's start)." },
14635
15030
  fill: PAINT,
14636
15031
  stroke: PAINT,
14637
- strokeWidth: { type: "number", minimum: 0, description: "Stroke width (default 1)." }
15032
+ strokeWidth: { type: "number", minimum: 0, description: "Stroke width (default 1)." },
15033
+ parentId: { type: "string", description: "A group (frame, master page, symmetry group) to add it into, on top of its children (default: the top of the document). On a master page, {page} and {pages} show each page's number and the page count." }
14638
15034
  },
14639
15035
  required: ["text"],
14640
15036
  additionalProperties: false
@@ -14654,7 +15050,7 @@ function createVectorCommands(controller, options = {}) {
14654
15050
  ...params.paragraph ? { paragraph: params.paragraph } : {},
14655
15051
  ...params.fill !== void 0 ? { fill: checkPaint(params.fill, "fill") } : {},
14656
15052
  ...params.stroke !== void 0 ? { stroke: checkPaint(params.stroke, "stroke") } : {}
14657
- });
15053
+ }, parentOf(params.parentId));
14658
15054
  } catch (e) {
14659
15055
  fail(e instanceof Error ? e.message : String(e));
14660
15056
  }
@@ -15166,6 +15562,86 @@ function createVectorCommands(controller, options = {}) {
15166
15562
  }
15167
15563
  }
15168
15564
  ];
15565
+ 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)." };
15566
+ const perspectiveResult = () => {
15567
+ const grid = controller.getPerspective();
15568
+ return { perspective: grid, planes: grid ? vectorPerspectivePlanes(grid) : [], drawingOn: controller.getState().perspectivePlane };
15569
+ };
15570
+ const perspectiveCommands = () => [
15571
+ {
15572
+ ...base,
15573
+ id: `${prefix}getPerspective`,
15574
+ label: "Perspective Grid",
15575
+ keywords: ["perspective", "grid", "vanishing point", "horizon"],
15576
+ 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) }.",
15577
+ params: { type: "object", properties: {}, additionalProperties: false },
15578
+ run: () => perspectiveResult()
15579
+ },
15580
+ {
15581
+ ...base,
15582
+ id: `${prefix}setPerspective`,
15583
+ label: "Perspective Grid\u2026",
15584
+ keywords: ["perspective", "grid", "vanishing point", "one point", "two point", "three point", "horizon", "drawing in perspective"],
15585
+ 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.",
15586
+ params: {
15587
+ type: "object",
15588
+ properties: {
15589
+ vanishingPoints: { type: "array", minItems: 1, maxItems: 3, items: { ...POINT, description: "A vanishing point (document units)." }, description: "1 to 3 vanishing points, document units." },
15590
+ origin: { ...POINT, description: "Where the planes meet (document units), not on a vanishing point." },
15591
+ gridSize: { type: "number", exclusiveMinimum: 0, description: "Grid cell size at the origin, document units (default 20)." },
15592
+ extent: { type: "integer", minimum: 1, maximum: 100, description: "Cells drawn out from the origin along each axis (default 12)." },
15593
+ visible: { type: "boolean", description: "Show the grid on the canvas (default true)." }
15594
+ },
15595
+ required: ["vanishingPoints", "origin"],
15596
+ additionalProperties: false
15597
+ },
15598
+ undoable: true,
15599
+ run: (_ctx, params) => {
15600
+ check(controller.setPerspective(params));
15601
+ return perspectiveResult();
15602
+ }
15603
+ },
15604
+ {
15605
+ ...base,
15606
+ id: `${prefix}removePerspective`,
15607
+ label: "Remove Perspective Grid",
15608
+ keywords: ["perspective", "grid", "remove"],
15609
+ description: "Removes the perspective grid (art already on its planes stays as drawn). One undo step. Returns { perspective: null }.",
15610
+ params: { type: "object", properties: {}, additionalProperties: false },
15611
+ undoable: true,
15612
+ run: () => {
15613
+ check(controller.setPerspective(null));
15614
+ return { perspective: null };
15615
+ }
15616
+ },
15617
+ {
15618
+ ...base,
15619
+ id: `${prefix}setPerspectivePlane`,
15620
+ label: "Draw on Perspective Plane",
15621
+ keywords: ["perspective", "plane", "draw", "floor", "wall"],
15622
+ 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.",
15623
+ params: { type: "object", properties: { plane: { ...PLANE, type: ["string", "null"], enum: [...VECTOR_PERSPECTIVE_PLANES, null], description: `${PLANE.description} null draws flat.` } }, required: ["plane"], additionalProperties: false },
15624
+ run: (_ctx, params) => {
15625
+ check(controller.setPerspectivePlane(params.plane));
15626
+ return perspectiveResult();
15627
+ }
15628
+ },
15629
+ {
15630
+ ...base,
15631
+ id: `${prefix}attachToPerspective`,
15632
+ label: "Attach to Perspective",
15633
+ keywords: ["perspective", "attach", "plane", "project", "wall", "floor", "sign"],
15634
+ 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 }.",
15635
+ params: { type: "object", properties: { ids: { ...IDS, description: "Objects to project (default: the selection)." }, plane: PLANE }, required: ["plane"], additionalProperties: false },
15636
+ undoable: true,
15637
+ enabled: enabledFor,
15638
+ run: (_ctx, params) => {
15639
+ const r = controller.attachToPerspective(params.plane, params.ids ? { ids: params.ids } : {});
15640
+ check(r);
15641
+ return { ids: r.ids ?? [] };
15642
+ }
15643
+ }
15644
+ ];
15169
15645
  const pdfCommands = () => [
15170
15646
  {
15171
15647
  ...base,
@@ -15646,6 +16122,109 @@ function createVectorCommands(controller, options = {}) {
15646
16122
  ];
15647
16123
  const frameResult = (id) => controller.getFrames().find((f) => f.id === id) ?? fail(`"${id}" is not a frame.`);
15648
16124
  const FRAME_PAINT = { ...PAINT, description: `The frame's background, drawn below its children. ${PAINT.description}` };
16125
+ const pagesResult = () => controller.getPages();
16126
+ const pageCommands = () => [
16127
+ {
16128
+ ...base,
16129
+ id: `${prefix}listPages`,
16130
+ label: "Pages",
16131
+ keywords: ["pages", "master page", "multipage", "page numbers", "document pages"],
16132
+ description: "Lists the document's pages (top-level frames that are not master pages, in order) and its master pages: { pages: [{ id, name, number, masterId, bounds }], masters: [{ id, name, usedBy, bounds }] }. Place master art inside the master bounds.",
16133
+ params: { type: "object", properties: {}, additionalProperties: false },
16134
+ run: () => pagesResult()
16135
+ },
16136
+ {
16137
+ ...base,
16138
+ id: `${prefix}addPage`,
16139
+ label: "Add Page",
16140
+ keywords: ["page", "add page", "new page", "insert page", "multipage"],
16141
+ description: "Adds a page: a frame the size of the last page (or the document) right of the others, after page `after` (default last), using master `masterId` (default: the last page's master; null for none). The first page of a document without frames covers the artboard and takes its art in. Add art to it with parentId. One undo step. Returns { id } and the pages.",
16142
+ params: {
16143
+ type: "object",
16144
+ properties: {
16145
+ name: { type: "string", description: 'Page name (default "Page n").' },
16146
+ masterId: { type: ["string", "null"], description: "Master page id, or null for none (default: the last page's master)." },
16147
+ after: { type: "string", description: "Page id to insert after (default: at the end)." }
16148
+ },
16149
+ additionalProperties: false
16150
+ },
16151
+ undoable: true,
16152
+ run: (_ctx, params) => {
16153
+ const r = controller.addPage(params ?? {});
16154
+ check(r);
16155
+ return { id: r.id, ...pagesResult() };
16156
+ }
16157
+ },
16158
+ {
16159
+ ...base,
16160
+ id: `${prefix}addMasterPage`,
16161
+ label: "Add Master Page",
16162
+ keywords: ["master page", "template page", "header", "footer", "page numbers"],
16163
+ description: 'Adds an empty master page (a frame below the pages, not a page itself). Art added to it (parentId: its id) shows on every page using it, laid out by each object\'s resizing constraints; text there can show {page} and {pages}. `assignTo` page ids or "all" make pages use it. One undo step. Returns { id } and the pages.',
16164
+ params: {
16165
+ type: "object",
16166
+ properties: {
16167
+ name: { type: "string", description: 'Master name (default "Master A", "Master B", \u2026).' },
16168
+ assignTo: { anyOf: [{ type: "string", enum: ["all"] }, { ...IDS }], description: 'Pages that use it: "all" or page ids (default none).' }
16169
+ },
16170
+ additionalProperties: false
16171
+ },
16172
+ undoable: true,
16173
+ run: (_ctx, params) => {
16174
+ const r = controller.addMasterPage(params ?? {});
16175
+ check(r);
16176
+ return { id: r.id, ...pagesResult() };
16177
+ }
16178
+ },
16179
+ {
16180
+ ...base,
16181
+ id: `${prefix}setPageMaster`,
16182
+ label: "Apply Master Page",
16183
+ keywords: ["master page", "apply master", "page template"],
16184
+ description: "Makes pages (`ids`, default the selected pages) use master page `masterId`, or none with null. One undo step. Returns the pages.",
16185
+ params: { type: "object", properties: { ids: { ...IDS, description: "Page ids (default: the selected pages)." }, masterId: { type: ["string", "null"], description: "Master page id, or null for none." } }, required: ["masterId"], additionalProperties: false },
16186
+ undoable: true,
16187
+ run: (_ctx, params) => {
16188
+ check(controller.setPageMaster(params.masterId, params.ids ? { ids: params.ids } : {}));
16189
+ return pagesResult();
16190
+ }
16191
+ },
16192
+ {
16193
+ ...base,
16194
+ id: `${prefix}movePage`,
16195
+ label: "Move Page",
16196
+ keywords: ["page order", "reorder pages", "move page"],
16197
+ description: "Moves page `id` to position `index` (0-based) in the page order; page numbers follow. One undo step. Returns the pages.",
16198
+ params: { type: "object", properties: { id: { type: "string", description: "The page." }, index: { type: "integer", minimum: 0, description: "New position, 0 for first." } }, required: ["id", "index"], additionalProperties: false },
16199
+ undoable: true,
16200
+ run: (_ctx, params) => {
16201
+ check(controller.movePage(params.id, params.index));
16202
+ return pagesResult();
16203
+ }
16204
+ },
16205
+ {
16206
+ ...base,
16207
+ id: `${prefix}goToPage`,
16208
+ label: "Go to Page",
16209
+ keywords: ["go to page", "navigate", "show page", "zoom to page"],
16210
+ description: "Selects a page or master page and brings it into view for the user (fitted when the canvas size is given). Not an undo step. Returns { id }.",
16211
+ params: {
16212
+ type: "object",
16213
+ properties: {
16214
+ id: { type: "string", description: "Page or master page id." },
16215
+ canvasWidth: { type: "number", exclusiveMinimum: 0, description: "Canvas width in screen pixels, to fit the page (optional)." },
16216
+ canvasHeight: { type: "number", exclusiveMinimum: 0, description: "Canvas height in screen pixels, to fit the page (optional)." }
16217
+ },
16218
+ required: ["id"],
16219
+ additionalProperties: false
16220
+ },
16221
+ run: (_ctx, params) => {
16222
+ const size = params.canvasWidth && params.canvasHeight ? { width: params.canvasWidth, height: params.canvasHeight } : void 0;
16223
+ check(controller.goToPage(params.id, size ? { size } : {}));
16224
+ return { id: params.id };
16225
+ }
16226
+ }
16227
+ ];
15649
16228
  const frameCommands = () => [
15650
16229
  {
15651
16230
  ...base,
@@ -15995,6 +16574,7 @@ function createVectorCommands(controller, options = {}) {
15995
16574
  ...symbolCommands(),
15996
16575
  ...constraintCommands(),
15997
16576
  ...frameCommands(),
16577
+ ...pageCommands(),
15998
16578
  ...connectorCommands(),
15999
16579
  ...trackingCommands(),
16000
16580
  ...shadingCommands(),
@@ -16002,6 +16582,7 @@ function createVectorCommands(controller, options = {}) {
16002
16582
  ...lottieCommands(),
16003
16583
  ...pdfCommands(),
16004
16584
  ...symmetryCommands(),
16585
+ ...perspectiveCommands(),
16005
16586
  ...lensCommands(),
16006
16587
  {
16007
16588
  ...base,
@@ -17052,6 +17633,7 @@ export {
17052
17633
  VECTOR_PATH_ACTIONS,
17053
17634
  VECTOR_PATH_TEXT_BASELINES,
17054
17635
  VECTOR_PENCIL_STRING_PX,
17636
+ VECTOR_PERSPECTIVE_PLANES,
17055
17637
  VECTOR_PRESSURE_NOMINAL,
17056
17638
  VECTOR_ROTATE_HANDLE_OFFSET,
17057
17639
  VECTOR_SPATIAL_MIN,
@@ -17248,6 +17830,7 @@ export {
17248
17830
  isVectorMaskActive,
17249
17831
  isVectorMaskGroup,
17250
17832
  isVectorMaskType,
17833
+ isVectorMasterPage,
17251
17834
  isVectorMeshGradient,
17252
17835
  isVectorShadedPaint,
17253
17836
  isVectorSymmetryGroup,
@@ -17335,6 +17918,7 @@ export {
17335
17918
  resizeWarpMesh as resizeVectorMesh,
17336
17919
  resolveVectorBlendOptions,
17337
17920
  resolveVectorLenses,
17921
+ resolveVectorMasterPages,
17338
17922
  resolveVectorSymbols,
17339
17923
  reverseMorphContour,
17340
17924
  reverseSubpath,
@@ -17358,6 +17942,7 @@ export {
17358
17942
  sanitizeVectorGradient,
17359
17943
  sanitizeVectorLens,
17360
17944
  sanitizeVectorMask,
17945
+ sanitizeVectorPerspective,
17361
17946
  sanitizeVectorResizing,
17362
17947
  sanitizeVectorShadedPaint,
17363
17948
  sanitizeVectorStrokeSettings,
@@ -17387,6 +17972,7 @@ export {
17387
17972
  splitWarpMeshV as splitVectorMeshV,
17388
17973
  starSubpath,
17389
17974
  stripVectorLensViews,
17975
+ stripVectorMasterArt,
17390
17976
  stripVectorSymbolArt,
17391
17977
  strokePositionPoint,
17392
17978
  strokeWidthProfileOf,
@@ -17519,6 +18105,8 @@ export {
17519
18105
  vectorMaskedGeometry,
17520
18106
  vectorMaskedLocalBounds,
17521
18107
  vectorMaskedShapes,
18108
+ vectorMasterArt,
18109
+ vectorMasterPages,
17522
18110
  vectorMeshColorAt,
17523
18111
  warpMeshColumns as vectorMeshColumns,
17524
18112
  warpMeshControlBounds as vectorMeshControlBounds,
@@ -17539,6 +18127,7 @@ export {
17539
18127
  vectorNodeToBezier,
17540
18128
  vectorNodesNear,
17541
18129
  vectorOperandOperation,
18130
+ vectorPages,
17542
18131
  vectorPaintLayerNodes,
17543
18132
  vectorPaintSolid,
17544
18133
  vectorPaintSwatch,
@@ -17554,6 +18143,13 @@ export {
17554
18143
  vectorPathTextPositions,
17555
18144
  vectorPathWorldBezier,
17556
18145
  vectorPathfinderShape,
18146
+ vectorPerspectiveFlatToPlane,
18147
+ vectorPerspectiveGridLines,
18148
+ vectorPerspectiveMapPath,
18149
+ vectorPerspectivePlaneMap,
18150
+ vectorPerspectivePlaneToFlat,
18151
+ vectorPerspectivePlanes,
18152
+ vectorPerspectivePreset,
17557
18153
  vectorPolarAngles,
17558
18154
  vectorPolarIncrementOf,
17559
18155
  vectorRepeaterCopies,