@overtone-art/canvas-editor-core 0.3.2 → 0.5.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.
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import { Canvas, FabricObject, ImageFormat } from 'fabric';
2
- import { b as EditorEvents, m as LayerType, k as LayerMeta, L as LayerData, W as SerializedLayer, J as PatternSourceResolver, P as PatternConfig, H as PatternLocks, a0 as TextCurveConfig, r as MaskPresetId, X as ShapeMaskPresetId, a1 as TextureMaskPresetId, a3 as Unit, G as MockupPrintArea, x as MockupConfig, e as FontDefinition, n as LicenseConfig, p as LicenseStatus, S as ProjectState, M as MaskBrushOptions, q as MaskPoint, t as MaskRefinementProvider, s as MaskRefinementPrompt, v as MaskRefinementResult, E as EditorConfig, Y as ShapePlugin, _ as TemplateDefinition, I as ImageAdjustments, c as EditorState, U as SemanticExportOptions, Z as SvgExportOptions, Q as PrintifyPositioning, N as NormalizedLayerPosition, O as PositioningAdapter, h as ImageProviderResult, i as ImageSearchOptions, j as ImageSearchResult, F as FileAdapter, g as ImageProvider, V as SerializedBackgroundImageOptions, B as BackgroundImageOptions, T as ResizeOptions, a as DpiIssue, l as LayerShadowConfig, C as CanvasSizePreset, y as MockupDisplacement } from './types-Dh1unrzT.mjs';
3
- export { D as DEFAULT_PATTERN_CONFIG, d as ExportFormat, f as ImageAttribution, o as LicensePayload, u as MaskRefinementRequest, w as MockupBlendMode, z as MockupDisplacementChannel, A as MockupOverlay, K as PatternState, R as ProjectPage, $ as TemplateParameter, a2 as TileMode } from './types-Dh1unrzT.mjs';
1
+ import { Canvas, FabricObject, Point, ImageFormat, Group } from 'fabric';
2
+ import { b as EditorEvents, o as LayerType, m as LayerMeta, L as LayerData, X as SerializedLayer, P as PatternConfig, K as PatternLocks, a1 as TextCurveConfig, t as MaskPresetId, Y as ShapeMaskPresetId, a2 as TextureMaskPresetId, a4 as Unit, J as MockupPrintArea, z as MockupConfig, e as FontDefinition, p as LicenseConfig, r as LicenseStatus, T as ProjectState, M as MaskBrushOptions, s as MaskPoint, v as MaskRefinementProvider, u as MaskRefinementPrompt, x as MaskRefinementResult, k as LayerMaskEntry, l as LayerMaskMode, E as EditorConfig, Z as ShapePlugin, $ as TemplateDefinition, I as ImageAdjustments, c as EditorState, V as SemanticExportOptions, _ as SvgExportOptions, R as PrintifyPositioning, N as NormalizedLayerPosition, Q as PositioningAdapter, h as ImageProviderResult, i as ImageSearchOptions, j as ImageSearchResult, F as FileAdapter, g as ImageProvider, W as SerializedBackgroundImageOptions, B as BackgroundImageOptions, U as ResizeOptions, a as DpiIssue, n as LayerShadowConfig, C as CanvasSizePreset, A as MockupDisplacement } from './types-JP8MZsIs.mjs';
3
+ export { D as DEFAULT_PATTERN_CONFIG, d as ExportFormat, f as ImageAttribution, q as LicensePayload, w as MaskRefinementRequest, y as MockupBlendMode, G as MockupDisplacementChannel, H as MockupOverlay, O as PatternState, S as ProjectPage, a0 as TemplateParameter, a3 as TileMode } from './types-JP8MZsIs.mjs';
4
4
 
5
5
  type EventHandler<T> = (data: T) => void;
6
6
  declare class EventEmitter<TEvents extends {} = Record<string, unknown>> {
@@ -22,6 +22,12 @@ declare class Layer {
22
22
  /** Non-fabric data (e.g. pattern config) that must persist with the layer. */
23
23
  meta: LayerMeta;
24
24
  fabricObject: FabricObject;
25
+ /**
26
+ * Stand-in object drawn in place of `fabricObject` (the tiled pattern). It is
27
+ * part of the canvas but never part of the document: it is not serialized, and
28
+ * the layer's real object stays the one every editor API talks to.
29
+ */
30
+ renderProxy: FabricObject | null;
25
31
  constructor(type: LayerType, fabricObject: FabricObject, name?: string, id?: string);
26
32
  private hasMeta;
27
33
  toData(): LayerData;
@@ -38,6 +44,19 @@ declare class LayerManager {
38
44
  remove(id: string): boolean;
39
45
  /** Replace a layer's render object while preserving its immutable ID and panel state. */
40
46
  replaceObject(id: string, fabricObject: FabricObject): boolean;
47
+ /**
48
+ * Attach (or clear) the object drawn in place of a layer's own object. The
49
+ * proxy tracks the layer's stacking position, visibility and opacity, and is
50
+ * removed with the layer — it must never outlive or drift from its source.
51
+ */
52
+ setRenderProxy(id: string, proxy: FabricObject | null): void;
53
+ /**
54
+ * Re-stack every canvas object to match layer order, keeping each proxy
55
+ * directly above the source it stands in for. Canvas indices can't be derived
56
+ * from layer indices once proxies are in the array, so the order is rebuilt
57
+ * front-to-back instead of computed.
58
+ */
59
+ private syncZOrder;
41
60
  reorder(id: string, newIndex: number): boolean;
42
61
  select(id: string | null): void;
43
62
  get(id: string): Layer | undefined;
@@ -176,79 +195,182 @@ declare class CropController {
176
195
  }
177
196
 
178
197
  /**
179
- * Renders a single image layer as a repeating pattern that fills the canvas.
198
+ * Repeats a layer's own object across the print area.
180
199
  *
181
- * The tiled result is rasterised onto an offscreen canvas and swapped in as the
182
- * layer's image source, so it survives export, serialization and undo/redo with
183
- * no extra machinery. The original source + transform are kept in `layer.meta`
184
- * so the pattern can be turned back off.
200
+ * The layer keeps its real fabric object as the tile *source*: it stays in the
201
+ * document, stays selectable and stays editable, and is simply parked at
202
+ * `opacity: 0` while a {@link TiledPatternObject} render proxy draws the tiling
203
+ * over the print area. Nothing is baked into the layer, so a source edit (text,
204
+ * font, colour, drag, scale) shows up on the very next render, and the saved
205
+ * state stores a `Textbox` rather than a screenshot of one.
185
206
  */
186
207
  declare class PatternManager {
187
208
  private canvas;
188
209
  private layers;
189
210
  private history;
190
211
  private events;
191
- private sourceResolver?;
192
- private chains;
193
- constructor(canvas: Canvas, layers: LayerManager, history: HistoryManager, events: EventEmitter<EditorEvents>, sourceResolver?: PatternSourceResolver | undefined);
212
+ private proxies;
213
+ private readonly onObjectModified;
214
+ private readonly onSourceModified;
215
+ private readonly onLayerRemoved;
216
+ constructor(canvas: Canvas, layers: LayerManager, history: HistoryManager, events: EventEmitter<EditorEvents>);
194
217
  isPattern(layerId: string): boolean;
195
218
  getConfig(layerId: string): PatternConfig | null;
196
- /** Turn a plain image layer into a pattern, or update an existing one. */
219
+ /** Turn a layer into a repeating pattern, or update an existing one. */
197
220
  apply(layerId: string, config: PatternConfig): Promise<void>;
221
+ /** Drop the tiling and show the source again. */
222
+ disable(layerId: string): Promise<void>;
198
223
  /**
199
- * Stretch every restored pattern layer back over the full print area and
200
- * re-freeze it, without re-rasterising: the baked bitmap keeps whatever size
201
- * it was saved at, so it is scaled (not re-tiled) to the current canvas. Used
202
- * after a state restore, where the canvas may be a different display size
203
- * than when the pattern was baked — and to repair states saved while a
204
- * pattern could still be dragged out of the print area.
224
+ * Rebuild every proxy after a state restore, migrating any layer that was
225
+ * saved by the old bake-into-the-layer engine.
205
226
  */
206
- repinAll(): void;
207
- /** Restore the original image and drop the pattern. */
208
- disable(layerId: string): Promise<void>;
209
- /** Run `task` after any in-flight work for this layer, regardless of outcome. */
210
- private enqueue;
211
- private renderLayer;
212
- }
213
- /** Interaction flags to restore when a pattern is turned off. */
214
- declare function captureLocks(obj: FabricObject): PatternLocks;
215
- /** Pin a pattern object: no move, scale or rotate while the pattern is on. */
216
- declare function applyPatternLocks(obj: FabricObject): void;
227
+ rehydrateAll(): Promise<void>;
228
+ /** Re-fit every proxy to the print area (canvas resize). */
229
+ syncArea(): void;
230
+ /** Drop a layer's cached source snapshot (its content changed). */
231
+ invalidate(layerId: string): void;
232
+ /** Give a freshly cloned layer its own proxy (duplicating a pattern layer). */
233
+ attachTo(layer: Layer): void;
234
+ dispose(): void;
235
+ /** Release every proxy and the offscreen snapshot it holds. */
236
+ private clearProxies;
237
+ private attach;
238
+ /**
239
+ * Fold a finished drag / scale / rotate on the proxy back into the pattern:
240
+ * position becomes the grid origin (carried by the source), scale becomes the
241
+ * tile scale, rotation becomes the pattern angle. Read the live transform
242
+ * first — writing the config re-fits the box and destroys it.
243
+ */
244
+ private commitGesture;
245
+ private area;
246
+ private detach;
247
+ private invalidateFor;
248
+ }
217
249
  /**
218
- * Restore pre-pattern interaction flags. States saved before locking existed
219
- * carry no snapshot fall back to a fully interactive object rather than
220
- * leaving it frozen forever.
250
+ * Restore pre-pattern interaction flags. Only legacy states carry a snapshot —
251
+ * the live-source engine never freezes anything so a missing snapshot means
252
+ * "fully interactive", not "leave it frozen".
221
253
  */
222
254
  declare function restoreLocks(obj: FabricObject, locks: PatternLocks | undefined): void;
223
- declare function loadPatternImage(src: string, resolver?: PatternSourceResolver): Promise<HTMLImageElement>;
224
- /** Clear the decoded-image cache (e.g. on teardown). Exported for completeness. */
225
- declare function clearPatternImageCache(): void;
255
+
256
+ interface PatternArea {
257
+ width: number;
258
+ height: number;
259
+ }
226
260
  /**
227
- * Draw the source image repeatedly across a `targetW × targetH` offscreen
228
- * canvas and return the result as a PNG data URL.
261
+ * Renders a layer's source object repeated across the print area.
262
+ *
263
+ * This is a *render proxy*, not a layer: it is attached to the layer that owns
264
+ * the source (see `LayerManager.setRenderProxy`) and never serialized. The
265
+ * document keeps the real object — a `Textbox` stays a `Textbox` — so tiling is
266
+ * non-destructive and every source edit shows up on the next render.
229
267
  *
230
- * Exported for unit testing of the tile geometry.
268
+ * **Its box is one tile, its drawing is the whole area.** The object's own
269
+ * width/height/angle/position track the tile drawn at the grid origin (the
270
+ * "anchor" tile), so the selection frame the user grabs sits exactly on a tile
271
+ * they can see, while `_render` paints the full print area outside those bounds.
272
+ * That also makes the transform handles meaningful: dragging moves the grid,
273
+ * scaling is the tile scale, rotating is the pattern angle. The live transform
274
+ * is the truth during a gesture; `PatternManager` folds it back into the config
275
+ * when the gesture ends.
231
276
  */
232
- declare function buildPatternDataURL(src: string, config: PatternConfig, targetW: number, targetH: number, baseW: number, baseH: number, sourceResolver?: PatternSourceResolver): Promise<string>;
233
- /** Placement of one tile, relative to the (already rotated) pattern centre. */
277
+ declare class TiledPatternObject extends FabricObject {
278
+ static type: string;
279
+ /** The layer's real object. Never rendered directly (it is kept at opacity 0). */
280
+ source: FabricObject;
281
+ config: PatternConfig;
282
+ /** The print area this pattern fills, in canvas units. */
283
+ area: PatternArea;
284
+ private snapshotEl;
285
+ private snapshotScale;
286
+ constructor(source: FabricObject, config: PatternConfig, area: PatternArea);
287
+ /** Swap in a new config and re-fit the box to the tile it now describes. */
288
+ setConfig(config: PatternConfig): void;
289
+ /** Re-fit to a new print area (canvas resize). */
290
+ setArea(area: PatternArea): void;
291
+ /** Drop the cached source snapshot (the source was edited). */
292
+ invalidate(): void;
293
+ /** Free the offscreen snapshot. */
294
+ dispose(): void;
295
+ /**
296
+ * Put the box back on the anchor tile: the tile's size, the pattern angle, and
297
+ * the grid origin displaced by the configured phase shift. Resets any live
298
+ * gesture scale, so it must run only once that gesture has been folded in.
299
+ */
300
+ syncBox(): void;
301
+ /** Grid rotation in play right now — the live handle during a rotate gesture. */
302
+ liveAngle(): number;
303
+ /** Tile scale in play right now, as a config percentage. */
304
+ liveScale(): number;
305
+ /**
306
+ * Where the source's centre would have to sit for the box to stay put — i.e.
307
+ * the grid origin implied by a drag. `PatternManager` moves the source there.
308
+ */
309
+ liveOrigin(): Point;
310
+ _render(ctx: CanvasRenderingContext2D): void;
311
+ /** Raster fallback for SVG export — one `<image>` covering the print area. */
312
+ _toSVG(): string[];
313
+ /**
314
+ * The proxy holds a live reference to its source, which would make a
315
+ * serialized canvas circular. Nothing persists this object (only layers are
316
+ * serialized) — this keeps an accidental `canvas.toObject()` from throwing.
317
+ */
318
+ toObject(): Record<string, unknown>;
319
+ /**
320
+ * Fabric's generic clone round-trips through `toObject()` + the class
321
+ * registry, which cannot carry a live source reference. Export paths clone
322
+ * every canvas object, so without this a print export would silently lose the
323
+ * tiling. The copy shares the source (it only ever reads from it).
324
+ */
325
+ clone(): Promise<this>;
326
+ /** The source's on-canvas width, before the tile scale. */
327
+ private baseW;
328
+ /** Tile size from the config alone, ignoring any in-flight gesture. */
329
+ private baseTile;
330
+ /** Tile size as drawn right now, including a live scale gesture. */
331
+ private liveTile;
332
+ /** Smallest tile the draw loop may use, so a tiny tile can't flood the area. */
333
+ private tileFloor;
334
+ /** Phase shift (`offsetX`/`offsetY`) as a canvas-space vector. */
335
+ private shiftVector;
336
+ private anchorFromOrigin;
337
+ /**
338
+ * Snapshot the source at (at least) `scale`, reusing the cached one while it
339
+ * is still sharp enough and the source has not changed.
340
+ */
341
+ private ensureSnapshot;
342
+ /** Bound the snapshot by both a linear scale and a total pixel budget. */
343
+ private clampScale;
344
+ }
345
+
346
+ /** Placement of one tile, relative to the (already rotated) pattern origin. */
234
347
  interface TilePlacement {
235
- /** Tile-centre offset from the pattern centre, in px. */
348
+ /** Tile-centre offset from the pattern origin, in px. */
236
349
  x: number;
237
350
  y: number;
238
351
  /** Per-tile rotation, in radians. */
239
352
  rotation: number;
240
353
  }
354
+ /** Where the tile grid is anchored inside the target, in target px. */
355
+ interface PatternOrigin {
356
+ x: number;
357
+ y: number;
358
+ }
241
359
  /**
242
360
  * Pure tile geometry: where each tile centre lands and how much it is rotated,
243
- * for a config + target/base size. Covers the rotated bounding diagonal so
244
- * corners stay filled at any pattern angle. Extracted for unit testing.
361
+ * for a config + target/base size. Covers every corner of the target from the
362
+ * grid origin, so no rotation or origin can uncover an edge. Extracted for unit
363
+ * testing.
364
+ *
365
+ * `origin` defaults to the centre of the target.
245
366
  */
246
- declare function computeTilePositions(config: PatternConfig, targetW: number, targetH: number, baseW: number, baseH: number): TilePlacement[];
367
+ declare function computeTilePositions(config: PatternConfig, targetW: number, targetH: number, baseW: number, baseH: number, origin?: PatternOrigin): TilePlacement[];
247
368
  /**
248
- * Draw the tiled pattern onto a 2D context. The geometry lives in
249
- * {@link computeTilePositions}; this just plays it out as canvas calls.
369
+ * Draw the tiled pattern onto a 2D context, in target coordinates (0,0 = the
370
+ * target's top-left corner). The geometry lives in {@link computeTilePositions};
371
+ * this just plays it out as canvas calls.
250
372
  */
251
- declare function drawTiles(ctx: CanvasRenderingProtocol, img: CanvasImageSource, config: PatternConfig, targetW: number, targetH: number, baseW: number, baseH: number): void;
373
+ declare function drawTiles(ctx: CanvasRenderingProtocol, img: CanvasImageSource, config: PatternConfig, targetW: number, targetH: number, baseW: number, baseH: number, origin?: PatternOrigin): void;
252
374
  /** Minimal 2D-context surface used by {@link drawTiles}; lets tests inject a spy. */
253
375
  interface CanvasRenderingProtocol {
254
376
  save(): void;
@@ -543,6 +665,237 @@ declare class MaskController {
543
665
  private assertActive;
544
666
  }
545
667
 
668
+ interface MaskEditTarget {
669
+ target: string;
670
+ maskId: string;
671
+ }
672
+
673
+ interface MaskBox {
674
+ left: number;
675
+ top: number;
676
+ width: number;
677
+ height: number;
678
+ }
679
+
680
+ /** Target id for the mask stack that clips the whole design rather than a layer. */
681
+ declare const CANVAS_MASK_TARGET = "canvas";
682
+ /**
683
+ * Storage half of the mask stacks: where a stack lives, how it is read back out
684
+ * of the composed clip, and how a new one is installed. `LayerMaskManager` adds
685
+ * the editing operations on top.
686
+ *
687
+ * Two things are persisted, and each has a job:
688
+ *
689
+ * - the composed `clipPath` on the host's fabric object, which is what actually
690
+ * renders — in the browser, in an export, and in a server-side replay that
691
+ * knows nothing about this class;
692
+ * - `layer.meta.maskStack`, the per-entry list (name, mode, link, visibility)
693
+ * that the UI edits.
694
+ *
695
+ * Geometry is NOT duplicated into the meta: the group's children are the single
696
+ * source of truth for it, unwrapped and recomposed on every mutation, so the two
697
+ * can never drift into disagreeing about where a mask sits. The order of
698
+ * `entries` matches the order of the group's children, minus the implicit base
699
+ * rect a stack that opens with subtract/intersect needs.
700
+ */
701
+ declare abstract class MaskStackStore {
702
+ protected canvas: Canvas;
703
+ protected layers: LayerManager;
704
+ protected history: HistoryManager;
705
+ protected events: EventEmitter<EditorEvents>;
706
+ protected selected: {
707
+ target: string;
708
+ maskId: string;
709
+ } | null;
710
+ private pinning;
711
+ constructor(canvas: Canvas, layers: LayerManager, history: HistoryManager, events: EventEmitter<EditorEvents>);
712
+ list(target: string): LayerMaskEntry[];
713
+ get(target: string, maskId: string): LayerMaskEntry | undefined;
714
+ /** Every target that currently carries at least one mask. */
715
+ targets(): string[];
716
+ /** The box a mask is fitted to, in the space the stack is composed in. */
717
+ hostBox(target: string, absolute?: boolean): MaskBox;
718
+ /** The host layer of a target, optionally creating the design overlay. */
719
+ protected hostLayer(target: string, create?: boolean): Layer | undefined;
720
+ protected host(target: string, create?: boolean): FabricObject | null;
721
+ /** Re-pin the design overlay, guarding the reorder that re-triggers this. */
722
+ protected pin(): void;
723
+ /**
724
+ * Take the current geometry back out of the composed clip, entry-aligned.
725
+ *
726
+ * The entry list is the authority on how to read the clip: with no entries the
727
+ * clip predates the stack (a mask preset, or a single `clipPath` an older host
728
+ * installed) and is one mask whole — including when it happens to be a group,
729
+ * which is why this cannot just unwrap anything group-shaped.
730
+ */
731
+ protected unwrap(target: string, host: FabricObject): FabricObject[];
732
+ /**
733
+ * Entries for a stack, adopting a pre-stack clip as the first one. Without
734
+ * this, the first `add()` on an already-masked layer would compose a clip it
735
+ * has no entry for and silently throw that mask away.
736
+ */
737
+ protected entriesFor(target: string, sources: FabricObject[]): LayerMaskEntry[];
738
+ /**
739
+ * Install a stack: convert geometry into the space the stack needs, compose the
740
+ * clip, store the entries, and commit one history checkpoint for the lot.
741
+ */
742
+ protected commit(target: string, host: FabricObject, entries: LayerMaskEntry[], sources: FabricObject[], save?: boolean,
743
+ /** Compose in canvas space regardless of the entries — see `beginEdit`. */
744
+ forceAbsolute?: boolean): void;
745
+ }
746
+
747
+ interface AddMaskOptions {
748
+ name?: string;
749
+ mode?: LayerMaskMode;
750
+ linked?: boolean;
751
+ /** Host data describing where the geometry came from (preset id, generator…). */
752
+ meta?: Record<string, unknown>;
753
+ /** Scale the geometry to the host's box (a number zooms it); default 1. */
754
+ fit?: number | false;
755
+ /**
756
+ * Fit to this box instead of the host's own, in the stack's coordinate space.
757
+ * The whole-design overlay spans the canvas, but a mask on it usually wants to
758
+ * frame the artwork, which occupies part of it.
759
+ */
760
+ box?: {
761
+ left: number;
762
+ top: number;
763
+ width: number;
764
+ height: number;
765
+ };
766
+ }
767
+ /**
768
+ * Stacked, boolean-composed masks — one ordered list per host, where a host is
769
+ * either a layer or the design as a whole (`CANVAS_MASK_TARGET`). Storage and
770
+ * clip composition live in `MaskStackStore`; this class is what a UI drives.
771
+ */
772
+ declare class LayerMaskManager extends MaskStackStore {
773
+ private readonly edits;
774
+ private readonly onLayersChanged;
775
+ private readonly onLayerSelected;
776
+ private readonly onObjectModified;
777
+ constructor(canvas: Canvas, layers: LayerManager, history: HistoryManager, events: EventEmitter<EditorEvents>);
778
+ dispose(): void;
779
+ /** The mask currently being dragged on the canvas, if any. */
780
+ editing(): MaskEditTarget | null;
781
+ /**
782
+ * Put drag handles on one mask. The stack is composed in canvas space for the
783
+ * duration — a linked mask would otherwise sit in the host's space, where the
784
+ * handle's own canvas coordinates mean something else entirely. `endEdit`
785
+ * returns it to whichever space its entries call for.
786
+ */
787
+ beginEdit(target: string, maskId: string): Promise<boolean>;
788
+ /** Take the handles down and settle the stack back into its own space. */
789
+ endEdit(save?: boolean): void;
790
+ getSelected(): {
791
+ target: string;
792
+ maskId: string;
793
+ } | null;
794
+ select(target: string | null, maskId: string | null): void;
795
+ add(target: string, object: FabricObject, options?: AddMaskOptions): LayerMaskEntry | null;
796
+ /** Swap one mask's geometry, keeping its identity, mode and position in the stack. */
797
+ replaceGeometry(target: string, maskId: string, object: FabricObject, options?: AddMaskOptions): boolean;
798
+ /**
799
+ * Re-fit one mask to its host's box (or `box`) at `fit` scale, keeping the
800
+ * geometry it already has. This is what a zoom control drives: re-picking the
801
+ * mask would cost another render of a generated alpha map just to resize it.
802
+ */
803
+ refit(target: string, maskId: string, options?: {
804
+ fit?: number;
805
+ box?: MaskBox;
806
+ save?: boolean;
807
+ }): boolean;
808
+ remove(target: string, maskId: string): boolean;
809
+ clear(target: string): boolean;
810
+ reorder(target: string, maskId: string, index: number): boolean;
811
+ /** Replace a mask's host metadata (which preset or generator produced it). */
812
+ setMeta(target: string, maskId: string, meta: Record<string, unknown>): boolean;
813
+ setMode(target: string, maskId: string, mode: LayerMaskMode): boolean;
814
+ setVisible(target: string, maskId: string, visible: boolean): boolean;
815
+ setOpacity(target: string, maskId: string, opacity: number): boolean;
816
+ setName(target: string, maskId: string, name: string): boolean;
817
+ /**
818
+ * Link or unlink one mask. Unlinking pins it where it currently appears;
819
+ * re-linking records its position relative to the host so later host moves
820
+ * carry it along.
821
+ */
822
+ setLinked(target: string, maskId: string, linked: boolean): boolean;
823
+ /**
824
+ * Consume a layer, turning its artwork into a mask. Without an explicit target
825
+ * it masks the layer directly beneath it, and the bottom layer masks the whole
826
+ * design — there is nothing under it to clip.
827
+ */
828
+ convertLayer(layerId: string, target?: string): {
829
+ target: string;
830
+ entry: LayerMaskEntry;
831
+ } | null;
832
+ /** Recompose one stack's clip from the geometry it already holds. */
833
+ rebuild(target: string, save?: boolean): void;
834
+ /** Re-derive linked masks after the host moved (canvas-space stacks only). */
835
+ private reflow;
836
+ /**
837
+ * Adopt a clip this manager did not install — a host's own single `clipPath` —
838
+ * as a one-entry stack, so it shows up in the UI as a mask row before anything
839
+ * is added to it. A mask preset is left alone unless asked for by name: it is
840
+ * still owned by `MaskPresetManager` until a stack operation takes it over.
841
+ */
842
+ adopt(target: string, name?: string): LayerMaskEntry | null;
843
+ /** After a state restore: re-pin the design overlay and recompose every stack. */
844
+ refreshAll(): void;
845
+ private patch;
846
+ }
847
+
848
+ interface ComposeOptions {
849
+ /** Host box in the coordinate space the children use, for the implicit base. */
850
+ box: {
851
+ left: number;
852
+ top: number;
853
+ width: number;
854
+ height: number;
855
+ };
856
+ /**
857
+ * Canvas-space children (an unlinked mask is pinned to the canvas, so the
858
+ * whole group has to leave the host's local space with it).
859
+ */
860
+ absolute: boolean;
861
+ }
862
+ /**
863
+ * Build the `clipPath` for one mask stack: one child per entry, in order, each
864
+ * carrying its mode's compositing operation.
865
+ *
866
+ * `children` are consumed — a Group takes ownership of what it is given and
867
+ * rewrites the coordinates, so callers pass clones of their sources rather than
868
+ * the sources themselves.
869
+ */
870
+ declare function composeMaskGroup(children: FabricObject[], entries: LayerMaskEntry[], options: ComposeOptions): Group | undefined;
871
+ /** True when the stack has to be composed in canvas space rather than host space. */
872
+ declare function needsAbsoluteSpace(entries: LayerMaskEntry[]): boolean;
873
+
874
+ /** Host-space geometry → canvas-space, for the same on-screen result. */
875
+ declare function toCanvasSpace(object: FabricObject, host: FabricObject): void;
876
+ /** Canvas-space geometry → host-space, for the same on-screen result. */
877
+ declare function toHostSpace(object: FabricObject, host: FabricObject): void;
878
+ /** Scale an object so its unrotated box covers `box`, centred on it. */
879
+ declare function fitToBox(object: FabricObject, box: {
880
+ left: number;
881
+ top: number;
882
+ width: number;
883
+ height: number;
884
+ }, zoom?: number): void;
885
+ /**
886
+ * Pull a composed clip group back apart into standalone objects in the space the
887
+ * group itself was in.
888
+ *
889
+ * `removeAll` already restores each child's own transform on the way out — the
890
+ * group rebases children when it takes them in, and reverses that when it lets
891
+ * them go. Folding the group's matrix back in on top (as one would with a v5-era
892
+ * group) double-counts it, which stays invisible while a mask sits at the origin
893
+ * and moves it twice as far as it should the moment one does not.
894
+ *
895
+ * The group passed in is emptied.
896
+ */
897
+ declare function unwrapGroup(group: Group): FabricObject[];
898
+
546
899
  declare class CanvasEditor {
547
900
  readonly canvas: Canvas;
548
901
  readonly layers: LayerManager;
@@ -554,6 +907,8 @@ declare class CanvasEditor {
554
907
  readonly patterns: PatternManager;
555
908
  readonly curves: TextCurveManager;
556
909
  readonly maskPresets: MaskPresetManager;
910
+ /** Stacked boolean masks, per layer and for the design as a whole. */
911
+ readonly layerMasks: LayerMaskManager;
557
912
  readonly fonts: FontRegistry;
558
913
  readonly licensing: LicenseManager;
559
914
  readonly pages: ProjectManager;
@@ -820,4 +1175,4 @@ declare class AnnotationOverlay {
820
1175
  */
821
1176
  declare function displaceRgba(source: Uint8ClampedArray, map: Uint8ClampedArray, width: number, height: number, options: Omit<MockupDisplacement, 'image'>): Uint8ClampedArray;
822
1177
 
823
- export { AnnotationOverlay, type AnnotationPrimitive, BackgroundImageOptions, CANVAS_SIZE_PRESETS, CanvasEditor, CanvasSizePreset, type CoverPlacement, CropController, DEFAULT_LAYER_SHADOW, DEFAULT_SELECTION_STYLE, DEFAULT_TEXT_CURVE, DpiIssue, EditorConfig, EditorEvents, EditorState, EventEmitter, FileAdapter, FontDefinition, FontRegistry, HistoryManager, ImageAdjustments, ImageProvider, ImageProviderResult, ImageSearchOptions, ImageSearchResult, Layer, LayerData, LayerManager, LayerMeta, LayerShadowConfig, LayerType, LicenseConfig, LicenseManager, LicenseStatus, MaskBrushOptions, MaskController, type MaskPerformanceSample, MaskPoint, MaskPresetId, MaskPresetManager, MaskRefinementError, type MaskRefinementErrorCode, MaskRefinementPrompt, MaskRefinementProvider, MaskRefinementResult, MockupConfig, MockupDisplacement, MockupPrintArea, NormalizedLayerPosition, PatternConfig, PatternLocks, PatternManager, PatternSourceResolver, type PngExportOptions, PositioningAdapter, PrintifyPositioning, ProjectManager, ProjectState, ResizeOptions, SHAPE_MASK_BOX, SHAPE_MASK_IDS, type SelectionStyle, SemanticExportOptions, SerializedBackgroundImageOptions, SerializedLayer, ShapeMaskPresetId, ShapePlugin, SnapManager, SvgExportOptions, TEXTURE_MASK_IDS, TEXTURE_MASK_SIZE, TemplateDefinition, TextCurveConfig, TextCurveManager, TextureMaskPresetId, type TilePlacement, Unit, UnitConverter, type ViewportTransform, applyAspectLock, applyLayerShadow, applyObjectSelectionStyle, applyPatternLocks, applySelectionStyle, buildCurvePathData, buildPatternDataURL, captureLocks, clamp, clearPatternImageCache, clearTextureMaskCache, computeCoverPlacement, computePrintAreaClip, computeTilePositions, deserializeEditor, displaceRgba, drawTiles, escapeXml, exportDataURL, exportMockup, exportPNG, exportPrintArea, exportSVG, generateId, isCssColor, isMaskPresetId, isShapeMaskId, isTextureMaskId, loadPatternImage, readLayerShadow, renderTextureMask, resetTransform, restoreLocks, round2, sanitizeSvg, serializeEditor, shapeMaskPathData };
1178
+ export { type AddMaskOptions, AnnotationOverlay, type AnnotationPrimitive, BackgroundImageOptions, CANVAS_MASK_TARGET, CANVAS_SIZE_PRESETS, CanvasEditor, type CanvasRenderingProtocol, CanvasSizePreset, type CoverPlacement, CropController, DEFAULT_LAYER_SHADOW, DEFAULT_SELECTION_STYLE, DEFAULT_TEXT_CURVE, DpiIssue, EditorConfig, EditorEvents, EditorState, EventEmitter, FileAdapter, FontDefinition, FontRegistry, HistoryManager, ImageAdjustments, ImageProvider, ImageProviderResult, ImageSearchOptions, ImageSearchResult, Layer, LayerData, LayerManager, LayerMaskEntry, LayerMaskManager, LayerMaskMode, LayerMeta, LayerShadowConfig, LayerType, LicenseConfig, LicenseManager, LicenseStatus, MaskBrushOptions, MaskController, type MaskPerformanceSample, MaskPoint, MaskPresetId, MaskPresetManager, MaskRefinementError, type MaskRefinementErrorCode, MaskRefinementPrompt, MaskRefinementProvider, MaskRefinementResult, MockupConfig, MockupDisplacement, MockupPrintArea, NormalizedLayerPosition, PatternConfig, PatternLocks, PatternManager, type PatternOrigin, type PngExportOptions, PositioningAdapter, PrintifyPositioning, ProjectManager, ProjectState, ResizeOptions, SHAPE_MASK_BOX, SHAPE_MASK_IDS, type SelectionStyle, SemanticExportOptions, SerializedBackgroundImageOptions, SerializedLayer, ShapeMaskPresetId, ShapePlugin, SnapManager, SvgExportOptions, TEXTURE_MASK_IDS, TEXTURE_MASK_SIZE, TemplateDefinition, TextCurveConfig, TextCurveManager, TextureMaskPresetId, type TilePlacement, TiledPatternObject, Unit, UnitConverter, type ViewportTransform, applyAspectLock, applyLayerShadow, applyObjectSelectionStyle, applySelectionStyle, buildCurvePathData, clamp, clearTextureMaskCache, composeMaskGroup, computeCoverPlacement, computePrintAreaClip, computeTilePositions, deserializeEditor, displaceRgba, drawTiles, escapeXml, exportDataURL, exportMockup, exportPNG, exportPrintArea, exportSVG, fitToBox, generateId, isCssColor, isMaskPresetId, isShapeMaskId, isTextureMaskId, needsAbsoluteSpace, readLayerShadow, renderTextureMask, resetTransform, restoreLocks, round2, sanitizeSvg, serializeEditor, shapeMaskPathData, toCanvasSpace, toHostSpace, unwrapGroup };