@pixerate/editor 0.154.0 → 0.156.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 (86) hide show
  1. package/dist/animation-editor/index.cjs +60 -61
  2. package/dist/animation-editor/index.js +5 -6
  3. package/dist/{brushstrokeRender-YAZVQUXY.cjs → brushstrokeRender-5WANQIG4.cjs} +10 -10
  4. package/dist/{brushstrokeRender-EUDQTDYS.js → brushstrokeRender-Q4LI22QV.js} +3 -3
  5. package/dist/canvas/index.cjs +23 -24
  6. package/dist/canvas/index.js +3 -4
  7. package/dist/chart/index.cjs +2 -3
  8. package/dist/chart/index.js +1 -2
  9. package/dist/{chunk-AGZ47XY2.js → chunk-2N2QPO3H.js} +6 -2
  10. package/dist/{chunk-NMXPOKF3.js → chunk-45XXVHVG.js} +1 -1
  11. package/dist/{chunk-FLOGD3QB.js → chunk-6UPGM3JB.js} +2 -2
  12. package/dist/{chunk-6FUAHCVI.cjs → chunk-7NW46N7I.cjs} +16 -16
  13. package/dist/{chunk-ORDXMBD4.js → chunk-A5UCR2TO.js} +10 -3
  14. package/dist/{chunk-PI45JMP7.cjs → chunk-CD7PLU77.cjs} +81 -81
  15. package/dist/{chunk-LRRCMRSL.cjs → chunk-EBDCRTZI.cjs} +20 -16
  16. package/dist/{chunk-OLCYQLFE.js → chunk-EL5PAHNP.js} +1 -1
  17. package/dist/{chunk-PUC43JPT.cjs → chunk-EYVVIZJ3.cjs} +1 -1
  18. package/dist/{chunk-BXMIBMVG.cjs → chunk-JGKCC5OF.cjs} +65 -58
  19. package/dist/chunk-M4MIZPQI.cjs +393 -0
  20. package/dist/{chunk-LIXLD433.cjs → chunk-MHEXLG3M.cjs} +35 -35
  21. package/dist/chunk-NHLYK3AX.js +393 -0
  22. package/dist/{chunk-RHXW4TA5.cjs → chunk-QMI5HZWA.cjs} +7 -7
  23. package/dist/{chunk-XFDQEAOS.cjs → chunk-RQCMSEM5.cjs} +2 -2
  24. package/dist/{chunk-BCY22GBH.js → chunk-SDHZG5TF.js} +1 -1
  25. package/dist/{chunk-HVX35M2I.cjs → chunk-WI7QJA7A.cjs} +2 -2
  26. package/dist/{chunk-UILRJQFD.js → chunk-WNAAWLZ5.js} +1 -1
  27. package/dist/{chunk-4J4MC3F5.js → chunk-YXOW456B.js} +1 -1
  28. package/dist/{chunk-Y64YPIFT.js → chunk-ZQWLWDZI.js} +1 -1
  29. package/dist/diagram/index.cjs +2 -3
  30. package/dist/diagram/index.js +1 -2
  31. package/dist/image-editor/controller.d.cts +8 -0
  32. package/dist/image-editor/controller.d.ts +8 -0
  33. package/dist/image-editor/editorDocument.d.cts +16 -0
  34. package/dist/image-editor/editorDocument.d.ts +16 -0
  35. package/dist/image-editor/index.cjs +787 -737
  36. package/dist/image-editor/index.d.cts +1 -0
  37. package/dist/image-editor/index.d.ts +1 -0
  38. package/dist/image-editor/index.js +56 -6
  39. package/dist/index.cjs +4 -5
  40. package/dist/index.js +2 -3
  41. package/dist/paint/grain.d.cts +7 -0
  42. package/dist/paint/grain.d.ts +7 -0
  43. package/dist/paint/index.cjs +3 -3
  44. package/dist/paint/index.js +2 -2
  45. package/dist/paint-editor/controller.d.cts +38 -0
  46. package/dist/paint-editor/controller.d.ts +38 -0
  47. package/dist/paint-editor/document.d.cts +8 -0
  48. package/dist/paint-editor/document.d.ts +8 -0
  49. package/dist/paint-editor/editorDocument.d.cts +17 -0
  50. package/dist/paint-editor/editorDocument.d.ts +17 -0
  51. package/dist/paint-editor/index.cjs +476 -273
  52. package/dist/paint-editor/index.d.cts +1 -0
  53. package/dist/paint-editor/index.d.ts +1 -0
  54. package/dist/paint-editor/index.js +216 -13
  55. package/dist/paint-editor/paper.d.cts +8 -0
  56. package/dist/paint-editor/paper.d.ts +8 -0
  57. package/dist/paint-editor/stencils.d.cts +2 -0
  58. package/dist/paint-editor/stencils.d.ts +2 -0
  59. package/dist/paint-editor/wet.d.cts +1 -0
  60. package/dist/paint-editor/wet.d.ts +1 -0
  61. package/dist/parameters/index.cjs +2 -3
  62. package/dist/parameters/index.js +1 -2
  63. package/dist/persistence/assets.d.cts +51 -0
  64. package/dist/persistence/assets.d.ts +51 -0
  65. package/dist/persistence/editorDocument.d.cts +12 -0
  66. package/dist/persistence/editorDocument.d.ts +12 -0
  67. package/dist/persistence/index.cjs +32 -8
  68. package/dist/persistence/index.d.cts +1 -0
  69. package/dist/persistence/index.d.ts +1 -0
  70. package/dist/persistence/index.js +34 -10
  71. package/dist/spreadsheet/index.cjs +3 -4
  72. package/dist/spreadsheet/index.js +2 -3
  73. package/dist/tokens/index.cjs +3 -4
  74. package/dist/tokens/index.js +2 -3
  75. package/dist/vector/index.cjs +952 -953
  76. package/dist/vector/index.js +5 -6
  77. package/dist/video-editor/index.cjs +2 -3
  78. package/dist/video-editor/index.js +1 -2
  79. package/package.json +1 -1
  80. package/skills/pixerate-image-editor/SKILL.md +16 -24
  81. package/skills/pixerate-image-editor/references/smart-objects.md +28 -0
  82. package/skills/pixerate-paint-editor/SKILL.md +26 -6
  83. package/dist/chunk-5H2C3QSF.cjs +0 -134
  84. package/dist/chunk-GNV54DLO.js +0 -134
  85. package/dist/chunk-L5OWG2NA.cjs +0 -135
  86. package/dist/chunk-WHKNYFM2.js +0 -135
@@ -46,3 +46,4 @@ export { wetSupport, type WetSupport } from '../paint/wet/wasm.cjs';
46
46
  export type { WetBackendInfo } from '../paint/wet/world.cjs';
47
47
  export * from '../paint/gradient-paint.cjs';
48
48
  export * from './playback.cjs';
49
+ export * from './editorDocument.cjs';
@@ -46,3 +46,4 @@ export { wetSupport, type WetSupport } from '../paint/wet/wasm.js';
46
46
  export type { WetBackendInfo } from '../paint/wet/world.js';
47
47
  export * from '../paint/gradient-paint.js';
48
48
  export * from './playback.js';
49
+ export * from './editorDocument.js';
@@ -7,7 +7,7 @@ import {
7
7
  resolveExternalAssets,
8
8
  serializeLayers,
9
9
  serializeLayersExternal
10
- } from "../chunk-Y64YPIFT.js";
10
+ } from "../chunk-ZQWLWDZI.js";
11
11
  import "../chunk-3CI7FRMK.js";
12
12
  import {
13
13
  EffectRendererPool
@@ -54,7 +54,7 @@ import {
54
54
  realPresenceClock,
55
55
  tweenPresenceCursor
56
56
  } from "../chunk-O2ANJFUF.js";
57
- import "../chunk-FLOGD3QB.js";
57
+ import "../chunk-6UPGM3JB.js";
58
58
  import {
59
59
  BRUSH_ID,
60
60
  BRUSH_ROTATIONS,
@@ -119,7 +119,7 @@ import {
119
119
  tiltToOrientation,
120
120
  toStrokePoint,
121
121
  usesPaintExchange
122
- } from "../chunk-ORDXMBD4.js";
122
+ } from "../chunk-A5UCR2TO.js";
123
123
  import "../chunk-M6DNZNVY.js";
124
124
  import "../chunk-WEIK3UYB.js";
125
125
  import "../chunk-PWBZH3FK.js";
@@ -189,7 +189,7 @@ import {
189
189
  seedFromString,
190
190
  solidPaint,
191
191
  updatePaintGradientStop
192
- } from "../chunk-AGZ47XY2.js";
192
+ } from "../chunk-2N2QPO3H.js";
193
193
  import "../chunk-ODBT55Q7.js";
194
194
  import {
195
195
  BLEND_MODES,
@@ -224,6 +224,11 @@ import "../chunk-QGHEDC7B.js";
224
224
  import "../chunk-SIFAIGB3.js";
225
225
  import "../chunk-CIQIM2IP.js";
226
226
  import "../chunk-VDGBHYC7.js";
227
+ import {
228
+ createEditorAssetRegistry,
229
+ defineEditorDocument
230
+ } from "../chunk-NHLYK3AX.js";
231
+ import "../chunk-HF5QZS7Z.js";
227
232
  import "../chunk-KY3FYKP3.js";
228
233
  import "../chunk-2WPNAGHI.js";
229
234
  import {
@@ -1352,6 +1357,17 @@ function stencilMask(stencils, width, height) {
1352
1357
  }
1353
1358
  return { width, height, data };
1354
1359
  }
1360
+ function translateStencil(s, dx, dy) {
1361
+ const p = (q) => q ? { x: q.x + dx, y: q.y + dy } : void 0;
1362
+ return {
1363
+ ...s,
1364
+ ...s.from ? { from: p(s.from) } : {},
1365
+ ...s.to ? { to: p(s.to) } : {},
1366
+ ...s.center ? { center: p(s.center) } : {},
1367
+ ...s.rect ? { rect: { ...s.rect, x: s.rect.x + dx, y: s.rect.y + dy } } : {},
1368
+ ...s.points ? { points: s.points.map((q) => p(q)) } : {}
1369
+ };
1370
+ }
1355
1371
  function scaleStencil(s, k) {
1356
1372
  const p = (q) => q ? { x: q.x * k, y: q.y * k } : void 0;
1357
1373
  return {
@@ -1502,7 +1518,7 @@ var TRACING_NODE_ID = "__tracing";
1502
1518
  var PAPER_EFFECT_NODE_ID = "__paper_effect";
1503
1519
  function paperKey(p, light) {
1504
1520
  const metal = p.metallic ?? 0;
1505
- return JSON.stringify([p.pattern, p.seed, p.color, p.scale, p.tiling, p.shading, p.dry, metal, metal > 0 && light ? [light.azimuth, light.elevation] : null]);
1521
+ return JSON.stringify([p.pattern, p.seed, p.color, p.scale, p.tiling, p.shading, p.dry, p.offset ?? null, metal, metal > 0 && light ? [light.azimuth, light.elevation] : null]);
1506
1522
  }
1507
1523
  var paperNodes = /* @__PURE__ */ new WeakMap();
1508
1524
  function paperNode(paper, light) {
@@ -1517,7 +1533,7 @@ function paperNode(paper, light) {
1517
1533
  var grainCache = /* @__PURE__ */ new Map();
1518
1534
  var tileCache = /* @__PURE__ */ new Map();
1519
1535
  function paperGrain(paper) {
1520
- const key = JSON.stringify([paper.pattern, paper.seed, paper.scale, paper.tiling, paper.dry]);
1536
+ const key = JSON.stringify([paper.pattern, paper.seed, paper.scale, paper.tiling, paper.dry, paper.offset ?? null]);
1521
1537
  let g = grainCache.get(key);
1522
1538
  if (!g) {
1523
1539
  g = new PaperGrain(paper, "dry");
@@ -1607,7 +1623,7 @@ function downsampleRgba(data, width, height, tw, th) {
1607
1623
  // src/paint-editor/serialize.ts
1608
1624
  var PAINT_FORMAT = "pixerate-paint";
1609
1625
  var PAINT_SCHEMA_VERSION = 1;
1610
- var KNOWN = /* @__PURE__ */ new Set(["format", "version", "width", "height", "dpi", "paper", "layers", "assets", "activeLayerId", "strokes", "guides", "swatches", "mixingPad", "wet", "wetEvents", "paintEvents"]);
1626
+ var KNOWN = /* @__PURE__ */ new Set(["format", "version", "width", "height", "dpi", "paper", "layers", "assets", "activeLayerId", "strokes", "guides", "swatches", "mixingPad", "wet", "wetEvents", "paintEvents", "origin"]);
1611
1627
  function documentExtensions(raw) {
1612
1628
  const out = {};
1613
1629
  for (const [k, v] of Object.entries(raw)) if (!KNOWN.has(k)) out[k] = v;
@@ -1631,6 +1647,7 @@ function header(state, options) {
1631
1647
  if (options.wet !== void 0) out.wet = JSON.parse(JSON.stringify(options.wet));
1632
1648
  if (state.wetEvents?.length) out.wetEvents = JSON.parse(JSON.stringify(state.wetEvents));
1633
1649
  if (state.paintEvents?.length) out.paintEvents = JSON.parse(JSON.stringify(state.paintEvents));
1650
+ if (state.origin && (state.origin.x || state.origin.y)) out.origin = { ...state.origin };
1634
1651
  return out;
1635
1652
  }
1636
1653
  function serializePaintDocument(state, options = {}) {
@@ -1683,6 +1700,8 @@ function deserializePaintDocument(raw) {
1683
1700
  if (wetEvents.length) state.wetEvents = wetEvents;
1684
1701
  const paintEvents = Array.isArray(r.paintEvents) ? r.paintEvents.filter((e) => e && typeof e === "object" && Number.isInteger(e.at) && typeof e.layerId === "string" && ["fill", "clear", "flatten", "stickers"].includes(e.kind)) : [];
1685
1702
  if (paintEvents.length) state.paintEvents = paintEvents;
1703
+ const o = r.origin;
1704
+ if (o && typeof o === "object" && Number.isFinite(o.x) && Number.isFinite(o.y) && (o.x || o.y)) state.origin = { x: Math.round(o.x), y: Math.round(o.y) };
1686
1705
  return { state, extensions: documentExtensions(r), guides: r.guides, swatches: r.swatches, mixingPad: r.mixingPad, wet: r.wet, problems };
1687
1706
  }
1688
1707
  async function deserializePaintDocumentAsync(raw, store) {
@@ -2133,7 +2152,10 @@ var PaintWetLayers = class {
2133
2152
  /** The world (made on first use, at the canvas size). */
2134
2153
  ensureWorld() {
2135
2154
  const { width, height } = this.host.canvas();
2136
- if (this.world && (this.world.width !== width || this.world.height !== height)) throw new Error("The wet world is the wrong size; dry the paint before resizing the canvas.");
2155
+ if (this.world && (this.world.width !== width || this.world.height !== height)) {
2156
+ if (this.world.laneIds().length) throw new Error("The wet world is the wrong size; dry the paint before resizing the canvas.");
2157
+ this.dropWorld();
2158
+ }
2137
2159
  if (!this.world) {
2138
2160
  this.world = new WetWorld(width, height, wetPaperInput(this.host.paper()), { kernel: this.options.kernel, threads: this.options.threads });
2139
2161
  this.world.setTilt(this.tilt);
@@ -2396,9 +2418,16 @@ var PaintWetLayers = class {
2396
2418
  if (this.world) for (const id of this.world.laneIds()) this.world.removeLane(id);
2397
2419
  return;
2398
2420
  }
2421
+ const { width, height } = this.host.canvas();
2422
+ if (this.world && (this.world.width !== width || this.world.height !== height)) this.dropWorld();
2399
2423
  this.ensureWorld().restore(snap);
2400
2424
  this.syncDisplays();
2401
2425
  }
2426
+ dropWorld() {
2427
+ this.world?.destroy();
2428
+ this.world = null;
2429
+ this.driver = null;
2430
+ }
2402
2431
  /** Forgets everything wet (a new canvas, a resize, opening a document). */
2403
2432
  reset() {
2404
2433
  this.world?.destroy();
@@ -3481,6 +3510,20 @@ var PaintEditorController = class _PaintEditorController {
3481
3510
  return d;
3482
3511
  }
3483
3512
  // --- History -------------------------------------------------------------------
3513
+ docRevision = 0;
3514
+ /**
3515
+ * Bumped on every change to the painting (a committed or amended step, undo, redo, opening a
3516
+ * document), not on wet-paint frames or view changes. `createPaintEditorDocument` uses it.
3517
+ */
3518
+ get documentRevision() {
3519
+ return this.docRevision;
3520
+ }
3521
+ /** Why the document cannot be replaced or stepped through right now, or `null`. */
3522
+ busyReason() {
3523
+ if (this.session) return "A stroke is in progress; end or cancel it first.";
3524
+ if (this.wet.busy) return "Wet paint is mid-step; wait a moment (wetIdle()) first.";
3525
+ return null;
3526
+ }
3484
3527
  requireIdle(what) {
3485
3528
  if (this.session) throw new PaintEditorError(`A stroke is in progress; end or cancel it before ${what}.`);
3486
3529
  }
@@ -3494,6 +3537,7 @@ var PaintEditorController = class _PaintEditorController {
3494
3537
  if (this.undoStack.length > this.historyLimit) this.undoStack.shift();
3495
3538
  this.redoStack = [];
3496
3539
  this.doc = next;
3540
+ this.docRevision++;
3497
3541
  this.trimWetHistory();
3498
3542
  this.emit();
3499
3543
  }
@@ -3510,6 +3554,7 @@ var PaintEditorController = class _PaintEditorController {
3510
3554
  last.after = next;
3511
3555
  this.redoStack = [];
3512
3556
  this.doc = next;
3557
+ this.docRevision++;
3513
3558
  this.emit();
3514
3559
  }
3515
3560
  /** Keeps the wet snapshots in history within the wet undo memory, dropping the oldest. */
@@ -3572,6 +3617,8 @@ var PaintEditorController = class _PaintEditorController {
3572
3617
  this.redoStack.push(e);
3573
3618
  const paper = this.doc.paper;
3574
3619
  this.doc = e.before;
3620
+ this.docRevision++;
3621
+ if (e.guides) this.restoreGuides(e.guides.before);
3575
3622
  if (e.wet) {
3576
3623
  if (paper !== this.doc.paper) this.wet.paperChanged();
3577
3624
  this.wet.restore(e.wet.lost ? null : e.wet.before);
@@ -3589,6 +3636,8 @@ var PaintEditorController = class _PaintEditorController {
3589
3636
  this.undoStack.push(e);
3590
3637
  const paper = this.doc.paper;
3591
3638
  this.doc = e.after;
3639
+ this.docRevision++;
3640
+ if (e.guides) this.restoreGuides(e.guides.after);
3592
3641
  if (e.wet) {
3593
3642
  this.wet.restore(e.wet.lost ? null : e.wet.after);
3594
3643
  if (paper !== this.doc.paper) this.wet.paperChanged();
@@ -3596,6 +3645,10 @@ var PaintEditorController = class _PaintEditorController {
3596
3645
  this.emit();
3597
3646
  return e.label;
3598
3647
  }
3648
+ restoreGuides(guides) {
3649
+ if (guides.tracing !== this.guides.tracing) this.tracingVersion++;
3650
+ this.guides = guides;
3651
+ }
3599
3652
  /** Labels of the undo steps, oldest first. */
3600
3653
  historyLabels() {
3601
3654
  return { undo: this.undoStack.map((e) => e.label), redo: this.redoStack.map((e) => e.label).reverse() };
@@ -3616,7 +3669,7 @@ var PaintEditorController = class _PaintEditorController {
3616
3669
  width: options.width ?? this.doc.canvas.width,
3617
3670
  height: options.height ?? this.doc.canvas.height,
3618
3671
  dpi: options.dpi ?? this.doc.canvas.dpi,
3619
- paper: options.paper === void 0 ? this.doc.paper : this.resolvePaper(options.paper)
3672
+ paper: options.paper === void 0 ? { ...this.doc.paper, offset: void 0 } : this.resolvePaper(options.paper)
3620
3673
  });
3621
3674
  this.extensions = {};
3622
3675
  this.guides = { ...this.guides, symmetry: { ...this.guides.symmetry, center: { x: next.canvas.width / 2, y: next.canvas.height / 2 } }, tracing: null };
@@ -3643,6 +3696,54 @@ var PaintEditorController = class _PaintEditorController {
3643
3696
  this.commit("Canvas size", this.withWetEvents({ ...this.doc, canvas }));
3644
3697
  return canvas;
3645
3698
  }
3699
+ /**
3700
+ * Grows the canvas by `left`, `top`, `right` and `bottom` pixels without moving anything
3701
+ * relative to anything else: the layers (with their masks, relief and stickers), the paper grain,
3702
+ * the effect paper, guides, stencils, the tracing image and the recorded strokes and events all
3703
+ * move by (left, top) in document pixels, `document.origin` moves by (−left, −top), and the view
3704
+ * pans so the picture stays still on screen.
3705
+ *
3706
+ * Wet paint dries first (the wet simulation is canvas-sized), as with `setCanvas`; one undo step
3707
+ * (undo brings back the old size, origin and guides, and the wet paint). Returns the new canvas,
3708
+ * where it sits in the previous document's pixels, and the origin.
3709
+ */
3710
+ extendCanvas(by) {
3711
+ this.requireIdle("extending the canvas");
3712
+ const side = (k) => {
3713
+ const v = by[k] ?? 0;
3714
+ if (!Number.isInteger(v) || v < 0) throw new PaintEditorError(`${k} is ${JSON.stringify(by[k])}; use a whole number of pixels to add, 0 or more.`);
3715
+ return v;
3716
+ };
3717
+ const left = side("left");
3718
+ const top = side("top");
3719
+ const right = side("right");
3720
+ const bottom = side("bottom");
3721
+ if (!(left || top || right || bottom)) throw new PaintEditorError("Nothing to extend: give left, top, right or bottom (pixels to add on that side) above 0.");
3722
+ const was = this.doc.canvas;
3723
+ const width = was.width + left + right;
3724
+ const height = was.height + top + bottom;
3725
+ if (width > MAX_CANVAS_SIDE || height > MAX_CANVAS_SIDE) {
3726
+ throw new PaintEditorError(`The canvas would be ${width}\xD7${height}; the limit is ${MAX_CANVAS_SIDE} px per side. Extend by less (it is ${was.width}\xD7${was.height} now).`);
3727
+ }
3728
+ if (this.wet.world) {
3729
+ this.requireWetIdle("extending the canvas");
3730
+ this.dryLayersFirst(this.wet.wetLayerIds());
3731
+ const settings = [...this.wet.settings];
3732
+ this.wet.reset();
3733
+ for (const [id, s] of settings) this.wet.settings.set(id, s.flow.center ? { ...s, flow: { ...s.flow, center: { x: s.flow.center.x + left, y: s.flow.center.y + top } } } : s);
3734
+ }
3735
+ const next = { ...shiftDocument(this.withWetEvents(this.doc), left, top), canvas: { ...was, width, height } };
3736
+ const m = viewMatrix(this.view, was, { width: 0, height: 0 });
3737
+ const dx = (right - left) / 2;
3738
+ const dy = (bottom - top) / 2;
3739
+ this.view = { ...this.view, pan: { x: this.view.pan.x + m[0] * dx + m[1] * dy, y: this.view.pan.y + m[2] * dx + m[3] * dy } };
3740
+ const guidesBefore = this.guides;
3741
+ if (left || top) this.restoreGuides(shiftGuides(this.guides, left, top));
3742
+ this.commit("Extend canvas", next);
3743
+ if (this.guides !== guidesBefore) this.undoStack[this.undoStack.length - 1].guides = { before: guidesBefore, after: this.guides };
3744
+ const origin = { ...next.origin ?? { x: 0, y: 0 } };
3745
+ return { canvas: { ...next.canvas }, rect: { x: 0 - left, y: 0 - top, width, height }, origin };
3746
+ }
3646
3747
  /** Changes the paper: a library preset (`preset`), and / or any of its settings (one undo step). */
3647
3748
  setPaper(update) {
3648
3749
  this.requireIdle("changing the paper");
@@ -3654,7 +3755,7 @@ var PaintEditorController = class _PaintEditorController {
3654
3755
  }
3655
3756
  const { preset: _p, ...rest } = update;
3656
3757
  if (rest.color !== void 0) parseColor(rest.color, "paper color");
3657
- const paper = normalizePaper({ ...rest, preset: base.preset }, base);
3758
+ const paper = normalizePaper({ ...rest, preset: base.preset, offset: rest.offset ?? this.doc.paper.offset }, base);
3658
3759
  const changedKeys = Object.keys(rest).filter((k) => k !== "name");
3659
3760
  if (changedKeys.length && update.preset === void 0) paper.preset = base.preset;
3660
3761
  if (this.wet.world) this.beginWetOp();
@@ -5220,6 +5321,7 @@ var PaintEditorController = class _PaintEditorController {
5220
5321
  this.undoStack = [];
5221
5322
  this.redoStack = [];
5222
5323
  this.doc = this.readThick(loaded.state, loaded.extensions, loaded.problems);
5324
+ this.docRevision++;
5223
5325
  delete this.extensions.layerChannels;
5224
5326
  delete this.extensions.lighting;
5225
5327
  delete this.extensions.stickers;
@@ -5815,6 +5917,60 @@ var PaintEditorController = class _PaintEditorController {
5815
5917
  }
5816
5918
  };
5817
5919
  var MAX_IMAGE_SIDE = 8192;
5920
+ var movePoint = (p, dx, dy) => ({ x: p.x + dx, y: p.y + dy });
5921
+ var moveRect = (r, dx, dy) => ({ ...r, x: r.x + dx, y: r.y + dy });
5922
+ function shiftDocument(doc, dx, dy) {
5923
+ if (!dx && !dy) return doc;
5924
+ let layers = doc.layers;
5925
+ for (const l of flattenLayers(doc.layers)) if (l.type === "pixel") layers = mapLayer(layers, l.id, () => ({ ...l, offset: movePoint(l.offset, dx, dy) }));
5926
+ const m = [1, 0, 0, 1, dx, dy];
5927
+ const moveStickers = (list) => list.map((s) => ({ ...s, x: Math.round((s.x + dx) * 100) / 100, y: Math.round((s.y + dy) * 100) / 100 }));
5928
+ const out = {
5929
+ ...doc,
5930
+ layers,
5931
+ paper: { ...doc.paper, offset: movePoint(doc.paper.offset ?? { x: 0, y: 0 }, dx, dy) },
5932
+ origin: movePoint(doc.origin ?? { x: 0, y: 0 }, -dx, -dy),
5933
+ strokes: doc.strokes.map((s) => {
5934
+ const moved = varyStroke(s, m, {});
5935
+ return s.stencils ? { ...moved, stencils: s.stencils.map((x) => translateStencil(x, dx, dy)) } : moved;
5936
+ })
5937
+ };
5938
+ if (doc.stickers) out.stickers = Object.fromEntries(Object.entries(doc.stickers).map(([id, list]) => [id, moveStickers(list)]));
5939
+ if (doc.paintEvents) {
5940
+ out.paintEvents = doc.paintEvents.map((e) => {
5941
+ const list = e.kind === "stickers" ? e.data?.stickers : void 0;
5942
+ return list ? { ...e, data: { ...e.data, stickers: moveStickers(list) } } : e;
5943
+ });
5944
+ }
5945
+ if (doc.wetEvents) {
5946
+ out.wetEvents = doc.wetEvents.map((e) => {
5947
+ const d = e.data;
5948
+ if (!d) return e;
5949
+ if (e.kind === "dry" && typeof d.x === "number") return { ...e, data: moveRect(d, dx, dy) };
5950
+ if (e.kind === "wet-paper" && d.rect) return { ...e, data: { ...d, rect: moveRect(d.rect, dx, dy) } };
5951
+ if (e.kind === "flow" && d.center) return { ...e, data: { ...d, center: movePoint(d.center, dx, dy) } };
5952
+ return e;
5953
+ });
5954
+ }
5955
+ if (doc.paperEffect) {
5956
+ const src = doc.paperEffect.image;
5957
+ const width = src.width + dx;
5958
+ const height = src.height + dy;
5959
+ const data = new Uint8ClampedArray(width * height * 4);
5960
+ for (let y = 0; y < src.height; y++) data.set(src.data.subarray(y * src.width * 4, (y + 1) * src.width * 4), ((y + dy) * width + dx) * 4);
5961
+ out.paperEffect = { ...doc.paperEffect, image: { width, height, data }, version: nextPaperEffectVersion() };
5962
+ }
5963
+ return out;
5964
+ }
5965
+ function shiftGuides(g, dx, dy) {
5966
+ return {
5967
+ ...g,
5968
+ symmetry: { ...g.symmetry, center: movePoint(g.symmetry.center, dx, dy) },
5969
+ perspective: { ...g.perspective, points: g.perspective.points.map((p) => movePoint(p, dx, dy)) },
5970
+ tracing: g.tracing ? { ...g.tracing, rect: moveRect(g.tracing.rect, dx, dy) } : null,
5971
+ stencils: g.stencils.map((s) => translateStencil(s, dx, dy))
5972
+ };
5973
+ }
5818
5974
  function resampleInto(src, rect, width, height) {
5819
5975
  const out = new Uint8ClampedArray(width * height * 4);
5820
5976
  const x0 = Math.max(0, Math.floor(rect.x));
@@ -7143,7 +7299,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
7143
7299
  add({
7144
7300
  name: "get_canvas_info",
7145
7301
  title: "Get the canvas",
7146
- description: "Reads the canvas: size in pixels and dpi, the paper (preset, colour, grain depth), the active layer, the active brush id, the paint colour, the view (zoom, rotation, mirror), the hand, how many strokes are recorded, the undo / redo labels, and the blend modes. Call it first.",
7302
+ description: "Reads the canvas: size in pixels and dpi, its origin (where (0, 0) is relative to the canvas it was extended from), the paper (preset, colour, grain depth), the active layer, the active brush id, the paint colour, the view (zoom, rotation, mirror), the hand, how many strokes are recorded, the undo / redo labels, and the blend modes. Call it first.",
7147
7303
  inputSchema: EMPTY3,
7148
7304
  annotations: { title: "Get the canvas", ...READ6 },
7149
7305
  handler: () => {
@@ -7153,6 +7309,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
7153
7309
  width: d.canvas.width,
7154
7310
  height: d.canvas.height,
7155
7311
  dpi: d.canvas.dpi,
7312
+ origin: { ...d.origin ?? { x: 0, y: 0 } },
7156
7313
  paper: { preset: d.paper.preset, name: d.paper.name, color: d.paper.color, depth: d.paper.depth },
7157
7314
  activeLayerId: d.activeLayerId,
7158
7315
  layerCount: c.listLayers().length,
@@ -7206,6 +7363,19 @@ function createPaintEditorToolProvider(controller, options = {}) {
7206
7363
  annotations: { title: "Resize the canvas", ...SET4 },
7207
7364
  handler: (a) => c.setCanvas({ width: a.width, height: a.height, dpi: a.dpi })
7208
7365
  });
7366
+ const EXTEND_SIDE = (side) => ({ type: "integer", minimum: 0, maximum: MAX_CANVAS_SIDE - 1, description: `Pixels to add on the ${side} (default 0).` });
7367
+ add({
7368
+ name: "extend_canvas",
7369
+ title: "Extend the canvas",
7370
+ description: "Grows the canvas by adding pixels on any side, keeping everything painted. Adding on the left or top moves the whole picture (layers, paper grain, guides, stencils, recorded strokes) right or down by that much in document pixels, so coordinates you read before change by (+left, +top). Wet paint dries first. One undo step. Returns the canvas, rect (the new canvas in the previous coordinates: x -left, y -top) and origin.",
7371
+ inputSchema: {
7372
+ type: "object",
7373
+ properties: { left: EXTEND_SIDE("left"), top: EXTEND_SIDE("top"), right: EXTEND_SIDE("right"), bottom: EXTEND_SIDE("bottom") },
7374
+ additionalProperties: false
7375
+ },
7376
+ annotations: { title: "Extend the canvas", ...WRITE5 },
7377
+ handler: (a) => c.extendCanvas({ left: a.left, top: a.top, right: a.right, bottom: a.bottom })
7378
+ });
7209
7379
  add({
7210
7380
  name: "list_papers",
7211
7381
  title: "List papers",
@@ -7810,7 +7980,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
7810
7980
  add({
7811
7981
  name: "save_document",
7812
7982
  title: "Save the document",
7813
- description: "Returns the document as JSON (format pixerate-paint, version 1): canvas, paper, layers with PNG data URLs, and the recorded strokes unless strokes is false. Changes nothing.",
7983
+ description: "Deprecated (kept for one minor version): use get_paint_document, which returns the painting with images as asset references, or save_paint_document to store it. Returns the document as JSON (format pixerate-paint, version 1): canvas, paper, layers with PNG data URLs, and the recorded strokes unless strokes is false. Changes nothing.",
7814
7984
  inputSchema: { type: "object", properties: { strokes: { type: "boolean", description: "Include the recorded strokes (default true)." } }, additionalProperties: false },
7815
7985
  annotations: { title: "Save the document", ...READ6 },
7816
7986
  handler: ({ strokes }) => c.toJSON({ strokes })
@@ -7818,7 +7988,7 @@ function createPaintEditorToolProvider(controller, options = {}) {
7818
7988
  add({
7819
7989
  name: "open_document",
7820
7990
  title: "Open a document",
7821
- description: "Opens a saved paint document (as save_document returns), replacing the painting and clearing the undo history. Returns the canvas, the layers and anything that could not be read.",
7991
+ description: "Deprecated (kept for one minor version): use load_paint_document or open_saved_paint_document. Opens a saved paint document (as save_document returns), replacing the painting and clearing the undo history. Returns the canvas, the layers and anything that could not be read.",
7822
7992
  inputSchema: { type: "object", properties: { document: { type: "object", description: "The saved document JSON." } }, required: ["document"], additionalProperties: false },
7823
7993
  annotations: { title: "Open a document", ...DESTRUCTIVE3 },
7824
7994
  handler: ({ document }) => {
@@ -8560,6 +8730,37 @@ function createPaintEffectsToolProvider(controller, options = {}) {
8560
8730
  ]
8561
8731
  }), { director: false, surface: options.playbackSurface });
8562
8732
  }
8733
+
8734
+ // src/paint-editor/editorDocument.ts
8735
+ function createPaintEditorDocument(controller, options = {}) {
8736
+ return defineEditorDocument({
8737
+ kind: "paint",
8738
+ recordsLoads: false,
8739
+ assets: options.assets ?? createEditorAssetRegistry(),
8740
+ get: () => controller.toJSON({ strokes: options.strokes ?? true }),
8741
+ load: (document) => {
8742
+ const problems = controller.loadJSON(document);
8743
+ return { problems: problems.map((message) => ({ section: "painting", message })) };
8744
+ },
8745
+ subscribe: (listener) => {
8746
+ let revision = controller.documentRevision;
8747
+ return controller.subscribe(() => {
8748
+ if (controller.documentRevision === revision) return;
8749
+ revision = controller.documentRevision;
8750
+ listener();
8751
+ });
8752
+ },
8753
+ history: {
8754
+ undo: () => controller.undo() !== null,
8755
+ redo: () => controller.redo() !== null,
8756
+ getState: () => {
8757
+ const s = controller.getState();
8758
+ return { canUndo: s.canUndo, canRedo: s.canRedo, ...s.undoLabel ? { undoName: s.undoLabel } : {}, ...s.redoLabel ? { redoName: s.redoLabel } : {} };
8759
+ }
8760
+ },
8761
+ readOnly: () => controller.busyReason() ?? false
8762
+ });
8763
+ }
8563
8764
  export {
8564
8765
  BLEND_MENU_GROUPS,
8565
8766
  BLEND_MODES,
@@ -8699,6 +8900,7 @@ export {
8699
8900
  contactSheet,
8700
8901
  createPaintCompositor,
8701
8902
  createPaintDocument,
8903
+ createPaintEditorDocument,
8702
8904
  createPaintEditorToolProvider,
8703
8905
  createPaintEffectsToolProvider,
8704
8906
  createPaintLayer,
@@ -8848,6 +9050,7 @@ export {
8848
9050
  timeLapseZip,
8849
9051
  toStrokePoint,
8850
9052
  toggleStamp,
9053
+ translateStencil,
8851
9054
  updatePaintGradientStop,
8852
9055
  usesPaintExchange,
8853
9056
  variationAffine,
@@ -40,6 +40,14 @@ export interface PaperSettings {
40
40
  roughness: number;
41
41
  /** Metallic paper (gold or silver leaf, pearlescent card): the grain catches the light, 0..1 (default 0). */
42
42
  metallic?: number;
43
+ /**
44
+ * Where the grain's (0, 0) sits in document pixels (missing = the top-left). `extendCanvas` moves
45
+ * it with the paint, so the texture stays under what was painted on it.
46
+ */
47
+ offset?: {
48
+ x: number;
49
+ y: number;
50
+ };
43
51
  }
44
52
  type PaperPreset = PaperSettings;
45
53
  /** The built-in paper library: generated papers in families. */
@@ -40,6 +40,14 @@ export interface PaperSettings {
40
40
  roughness: number;
41
41
  /** Metallic paper (gold or silver leaf, pearlescent card): the grain catches the light, 0..1 (default 0). */
42
42
  metallic?: number;
43
+ /**
44
+ * Where the grain's (0, 0) sits in document pixels (missing = the top-left). `extendCanvas` moves
45
+ * it with the paint, so the texture stays under what was painted on it.
46
+ */
47
+ offset?: {
48
+ x: number;
49
+ y: number;
50
+ };
43
51
  }
44
52
  type PaperPreset = PaperSettings;
45
53
  /** The built-in paper library: generated papers in families. */
@@ -76,5 +76,7 @@ export declare function stencilMask(stencils: readonly Stencil[], width: number,
76
76
  height: number;
77
77
  data: Uint8Array;
78
78
  } | null;
79
+ /** A stencil moved by (dx, dy) document pixels (for a canvas extended to the left or top). */
80
+ export declare function translateStencil(s: Stencil, dx: number, dy: number): Stencil;
79
81
  /** A stencil scaled about the origin by `k` (for repainting at a larger size). */
80
82
  export declare function scaleStencil(s: Stencil, k: number): Stencil;
@@ -76,5 +76,7 @@ export declare function stencilMask(stencils: readonly Stencil[], width: number,
76
76
  height: number;
77
77
  data: Uint8Array;
78
78
  } | null;
79
+ /** A stencil moved by (dx, dy) document pixels (for a canvas extended to the left or top). */
80
+ export declare function translateStencil(s: Stencil, dx: number, dy: number): Stencil;
79
81
  /** A stencil scaled about the origin by `k` (for repainting at a larger size). */
80
82
  export declare function scaleStencil(s: Stencil, k: number): Stencil;
@@ -266,6 +266,7 @@ export declare class PaintWetLayers {
266
266
  snapshot(): WetSnapshot | null;
267
267
  /** Puts a snapshot back (`null`: no wet layers). Returns the layer surfaces it implies. */
268
268
  restore(snap: WetSnapshot | null): void;
269
+ private dropWorld;
269
270
  /** Forgets everything wet (a new canvas, a resize, opening a document). */
270
271
  reset(): void;
271
272
  /** Water overlay tiles (RGBA) over a document rectangle, for the view. */
@@ -266,6 +266,7 @@ export declare class PaintWetLayers {
266
266
  snapshot(): WetSnapshot | null;
267
267
  /** Puts a snapshot back (`null`: no wet layers). Returns the layer surfaces it implies. */
268
268
  restore(snap: WetSnapshot | null): void;
269
+ private dropWorld;
269
270
  /** Forgets everything wet (a new canvas, a resize, opening a document). */
270
271
  reset(): void;
271
272
  /** Water overlay tiles (RGBA) over a document rectangle, for the view. */
@@ -1,7 +1,6 @@
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; } var _class;
2
2
 
3
- var _chunk5H2C3QSFcjs = require('../chunk-5H2C3QSF.cjs');
4
- require('../chunk-L5OWG2NA.cjs');
3
+ var _chunkM4MIZPQIcjs = require('../chunk-M4MIZPQI.cjs');
5
4
  require('../chunk-2BSBFBOA.cjs');
6
5
 
7
6
 
@@ -1328,7 +1327,7 @@ function mapRange(value, from, to, options) {
1328
1327
  // src/parameters/editorDocument.ts
1329
1328
  var isRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value);
1330
1329
  function createParametersEditorDocument(controller) {
1331
- return _chunk5H2C3QSFcjs.defineEditorDocument.call(void 0, {
1330
+ return _chunkM4MIZPQIcjs.defineEditorDocument.call(void 0, {
1332
1331
  kind: "parameters",
1333
1332
  get: () => controller.toJSON(),
1334
1333
  load: (document, { history }) => {
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  defineEditorDocument
3
- } from "../chunk-GNV54DLO.js";
4
- import "../chunk-WHKNYFM2.js";
3
+ } from "../chunk-NHLYK3AX.js";
5
4
  import "../chunk-HF5QZS7Z.js";
6
5
  import {
7
6
  HistoryManager
@@ -0,0 +1,51 @@
1
+ import type { StorageAdapter } from "./storage.cjs";
2
+ /** Prefix of an asset reference in a document (`pixerate-asset:<length>-<hash>`). */
3
+ export declare const EDITOR_ASSET_PREFIX = "pixerate-asset:";
4
+ /**
5
+ * Inline binary data kept out of an editor document: PNG data URLs and long base64 strings
6
+ * (layer pixels, masks, thick-paint channels, patterns…) are replaced by short, content-addressed
7
+ * references, and the bytes live here. `put` and `get` are synchronous, so an `EditorDocument`
8
+ * can stay synchronous; `persistEditorAssets` / `preloadEditorAssets` move the bytes to and from
9
+ * storage around a save or an open.
10
+ */
11
+ export interface EditorAssetRegistry {
12
+ /** Stores `data` (once per content) and returns its reference. */
13
+ put(data: string): string;
14
+ /** The data for `ref`, or `undefined` when it is not loaded. */
15
+ get(ref: string): string | undefined;
16
+ has(ref: string): boolean;
17
+ /** Puts data read back from storage under its reference. */
18
+ set(ref: string, data: string): void;
19
+ readonly size: number;
20
+ }
21
+ /** Whether a string is inline binary data worth keeping out of the document. */
22
+ export declare function isInlineAsset(value: string): boolean;
23
+ export declare function createEditorAssetRegistry(): EditorAssetRegistry;
24
+ /** A deep copy of `value` with every inline asset string replaced by its reference. */
25
+ export declare function externalizeAssets<T>(value: T, registry: EditorAssetRegistry): T;
26
+ /** Every asset reference in a document (or its JSON text). */
27
+ export declare function collectAssetRefs(value: unknown): string[];
28
+ /**
29
+ * A deep copy of `value` with every asset reference replaced by its data. Throws an error naming the references that are not loaded (and changes nothing).
30
+ */
31
+ export declare function internalizeAssets<T>(value: T, registry: EditorAssetRegistry): T;
32
+ export interface EditorAssetStorageOptions {
33
+ /** Key prefix of stored assets (default `pixerate.assets:`); one shared pool, as assets are content-addressed. */
34
+ prefix?: string;
35
+ }
36
+ /**
37
+ * Writes the assets `document` (or its JSON text) refers to into `storage`, skipping those
38
+ * this storage already has. Call before writing the document, so it never points at missing data.
39
+ */
40
+ export declare function persistEditorAssets(storage: StorageAdapter, registry: EditorAssetRegistry, document: unknown, options?: EditorAssetStorageOptions): Promise<void>;
41
+ /**
42
+ * Reads the assets `document` (or its JSON text) refers to from `storage` into the registry.
43
+ * Returns the references that could not be found.
44
+ */
45
+ export declare function preloadEditorAssets(storage: StorageAdapter, registry: EditorAssetRegistry, document: unknown, options?: EditorAssetStorageOptions): Promise<string[]>;
46
+ /**
47
+ * `storage` with assets handled: every `set` first writes the assets the value refers to, and
48
+ * every `get` reads them into the registry before returning. Wrap the storage an autosave or a
49
+ * document store uses for an editor whose document has `assets`.
50
+ */
51
+ export declare function withEditorAssets(storage: StorageAdapter, registry: EditorAssetRegistry, options?: EditorAssetStorageOptions): StorageAdapter;
@@ -0,0 +1,51 @@
1
+ import type { StorageAdapter } from "./storage.js";
2
+ /** Prefix of an asset reference in a document (`pixerate-asset:<length>-<hash>`). */
3
+ export declare const EDITOR_ASSET_PREFIX = "pixerate-asset:";
4
+ /**
5
+ * Inline binary data kept out of an editor document: PNG data URLs and long base64 strings
6
+ * (layer pixels, masks, thick-paint channels, patterns…) are replaced by short, content-addressed
7
+ * references, and the bytes live here. `put` and `get` are synchronous, so an `EditorDocument`
8
+ * can stay synchronous; `persistEditorAssets` / `preloadEditorAssets` move the bytes to and from
9
+ * storage around a save or an open.
10
+ */
11
+ export interface EditorAssetRegistry {
12
+ /** Stores `data` (once per content) and returns its reference. */
13
+ put(data: string): string;
14
+ /** The data for `ref`, or `undefined` when it is not loaded. */
15
+ get(ref: string): string | undefined;
16
+ has(ref: string): boolean;
17
+ /** Puts data read back from storage under its reference. */
18
+ set(ref: string, data: string): void;
19
+ readonly size: number;
20
+ }
21
+ /** Whether a string is inline binary data worth keeping out of the document. */
22
+ export declare function isInlineAsset(value: string): boolean;
23
+ export declare function createEditorAssetRegistry(): EditorAssetRegistry;
24
+ /** A deep copy of `value` with every inline asset string replaced by its reference. */
25
+ export declare function externalizeAssets<T>(value: T, registry: EditorAssetRegistry): T;
26
+ /** Every asset reference in a document (or its JSON text). */
27
+ export declare function collectAssetRefs(value: unknown): string[];
28
+ /**
29
+ * A deep copy of `value` with every asset reference replaced by its data. Throws an error naming the references that are not loaded (and changes nothing).
30
+ */
31
+ export declare function internalizeAssets<T>(value: T, registry: EditorAssetRegistry): T;
32
+ export interface EditorAssetStorageOptions {
33
+ /** Key prefix of stored assets (default `pixerate.assets:`); one shared pool, as assets are content-addressed. */
34
+ prefix?: string;
35
+ }
36
+ /**
37
+ * Writes the assets `document` (or its JSON text) refers to into `storage`, skipping those
38
+ * this storage already has. Call before writing the document, so it never points at missing data.
39
+ */
40
+ export declare function persistEditorAssets(storage: StorageAdapter, registry: EditorAssetRegistry, document: unknown, options?: EditorAssetStorageOptions): Promise<void>;
41
+ /**
42
+ * Reads the assets `document` (or its JSON text) refers to from `storage` into the registry.
43
+ * Returns the references that could not be found.
44
+ */
45
+ export declare function preloadEditorAssets(storage: StorageAdapter, registry: EditorAssetRegistry, document: unknown, options?: EditorAssetStorageOptions): Promise<string[]>;
46
+ /**
47
+ * `storage` with assets handled: every `set` first writes the assets the value refers to, and
48
+ * every `get` reads them into the registry before returning. Wrap the storage an autosave or a
49
+ * document store uses for an editor whose document has `assets`.
50
+ */
51
+ export declare function withEditorAssets(storage: StorageAdapter, registry: EditorAssetRegistry, options?: EditorAssetStorageOptions): StorageAdapter;