export type CanvasLayerKind = 'image' | 'video' | 'audio' | 'text' | 'rect' | 'roundRect' | 'ellipse' | 'triangle' | 'star' | 'hexagon' | 'pentagon' | 'diamond' | 'heart' | 'line' | 'arrow' | 'path' | 'sticky' | 'canvasPanel' | 'overlay' | 'border' | 'hBox' | 'vBox' | 'sizeBox' | 'scaleBox' | 'group' | 'widget' | 'namedSlot' | 'scrollBox' | 'wrapBox' | 'uniformGrid'; export type CanvasObjectFit = 'cover' | 'contain' | 'fill'; export type CanvasTextAlign = 'left' | 'center' | 'right'; export type CanvasFontStyle = 'normal' | 'italic'; export type CanvasTextDecoration = 'none' | 'underline' | 'line-through'; export type CanvasAlign = 'left' | 'center-h' | 'right' | 'top' | 'center-v' | 'bottom' | 'center' | 'full'; /** Normalized point inside a layer rect (0–1). */ export interface CanvasPoint { x: number; y: number; } export interface CanvasLayerRect { x: number; y: number; w: number; h: number; } /** UMG-style anchors (0–1 normalized against parent). */ export interface CanvasAnchors { minX: number; minY: number; maxX: number; maxY: number; } /** UMG slot: anchors + offsets in px relative to anchored edges. */ export interface CanvasSlot { anchors: CanvasAnchors; offsets: { left: number; top: number; right: number; bottom: number; }; padding?: { left: number; top: number; right: number; bottom: number; }; sizeRule?: 'auto' | 'fill' | 'fixed'; alignment?: { x: number; y: number; }; order?: number; } export interface CanvasExposedProp { id: string; targetLayerId: string; field: string; label: string; /** true = editable on instances (eye open) */ exposed: boolean; } export interface CanvasWidgetDefinition { id: string; name: string; width: number; height: number; background?: string; layers: CanvasLayer[]; exposed: CanvasExposedProp[]; } export interface CanvasLayer { id: string; kind: CanvasLayerKind; name: string; src?: string; text?: string; fill?: string; stroke?: string; strokeWidth?: number; fontSize?: number; fontWeight?: number | string; fontFamily?: string; fontStyle?: CanvasFontStyle; textDecoration?: CanvasTextDecoration; letterSpacing?: number; lineHeight?: number; textAlign?: CanvasTextAlign; /** * When true, the box hugs the text. * Side handles set `textMaxWidth` so lines wrap; height always follows the type. */ autoSize?: boolean; /** * Wrap width in layer pixels. Unset means the box grows with the longest line, * including manual line breaks. Set by dragging the left or right handle. */ textMaxWidth?: number; /** * When true, resizing this container scales children (position + size + text) * relative to the parent — same idea as text `autoSize`. Groups default on. */ autoSizeChildren?: boolean; color?: string; /** Text box background */ textBackground?: string; objectFit?: CanvasObjectFit; borderRadius?: number; rotation?: number; flipX?: boolean; flipY?: boolean; shadowBlur?: number; shadowColor?: string; blur?: number; /** CSS filter percents. `100` is unchanged. Used by image and video layers. */ brightness?: number; contrast?: number; saturate?: number; /** * Extra zoom inside an image or video, on top of `objectFit`. * `1` is the fit itself. The layer rect does not change. */ mediaScale?: number; /** Horizontal alignment of the fitted bitmap inside the layer. `0.5` is centered. */ mediaX?: number; /** Vertical alignment of the fitted bitmap inside the layer. `0.5` is centered. */ mediaY?: number; /** For `path`: normalized points (0–1) relative to rect. */ points?: CanvasPoint[]; /** For `path`: close the shape into a filled polygon. */ closed?: boolean; /** Parent layer id; null/undefined = artboard root. */ parentId?: string | null; /** UMG slot (preferred). When missing, derived from rect + top-left anchors. */ slot?: CanvasSlot; clipChildren?: boolean; /** Gap for hBox / vBox / wrapBox / uniformGrid */ gap?: number; /** namedSlot: public slot name exposed on the User Widget */ slotName?: string; /** Child of a widget instance: which named slot to fill */ fillSlot?: string; /** uniformGrid column count */ columns?: number; /** kind === 'widget' */ definitionId?: string; /** Exposed prop overrides on widget instances */ overrides?: Record; rect: CanvasLayerRect; zIndex: number; opacity: number; visible: boolean; locked: boolean; } export type CanvasGuideOrientation = 'horizontal' | 'vertical'; export interface CanvasGuide { id: string; orientation: CanvasGuideOrientation; /** X for vertical, Y for horizontal (document px). */ position: number; locked?: boolean; } /** One artboard inside a design. Every page shares the document size. */ export interface CanvasPage { id: string; name?: string; background: string; layers: CanvasLayer[]; guides?: CanvasGuide[]; guidesLocked?: boolean; } export interface CanvasDocument { version: 1 | 2; width: number; height: number; background: string; layers: CanvasLayer[]; guides?: CanvasGuide[]; /** When true, all guides are locked. */ guidesLocked?: boolean; /** Local User Widget library (v2). */ widgets?: CanvasWidgetDefinition[]; /** * Sheets of this design. Missing or empty means one page taken from the * top-level background, layers and guides (older documents). */ pages?: CanvasPage[]; } /** Top-left point anchors; offsets encode x/y/w/h without needing parent size. */ export declare function defaultSlotFromRect(rect: CanvasLayerRect, anchors?: CanvasAnchors): CanvasSlot; /** For top-left point anchors, offsets.left/top = x/y and right/bottom = -(x+w)/-(y+h) when parent size unknown. * Prefer slotFromLocalRect from canvasHierarchy when parent size is known. */ export declare function rectFromSlot(slot: CanvasSlot, parentSize: { width: number; height: number; }): CanvasLayerRect; export declare function emptyCanvasDocument(partial?: Partial> & { layers?: CanvasLayer[]; widgets?: CanvasWidgetDefinition[]; }): CanvasDocument; export declare function blankCanvasPage(partial?: Partial>): CanvasPage; /** Pages on a document. A document without `pages` is a single sheet. */ export declare function pagesOf(doc: CanvasDocument): CanvasPage[]; /** Persist `pages` without changing a document that already has them. */ export declare function documentWithPages(doc: CanvasDocument): CanvasDocument; /** View of the document as the given sheet. Top-level fields mirror that sheet. */ export declare function projectPage(doc: CanvasDocument, pageId: string): CanvasDocument; /** Write one sheet and mirror it on the top-level fields (the active sheet). */ export declare function writePage(doc: CanvasDocument, pageId: string, patch: Partial>): CanvasDocument; /** Clone layer ids so a duplicated sheet does not share identity with the source. */ export declare function cloneCanvasLayers(layers: CanvasLayer[]): CanvasLayer[]; /** Migrate v1 (flat absolute layers) → v2 (parentId + slot). */ export declare function migrateCanvasDocument(doc: CanvasDocument | (Omit & { version?: number; })): CanvasDocument; export declare function ensureLayerSlot(layer: CanvasLayer): CanvasLayer; export declare function createCanvasGuide(orientation: CanvasGuideOrientation, position: number, partial?: Partial>): CanvasGuide; export declare function createCanvasLayer(kind: CanvasLayerKind, partial?: Partial>): CanvasLayer; /** Scalar fields that can reset to createCanvasLayer defaults (UE-style details). */ export type CanvasResettableField = 'opacity' | 'rotation' | 'visible' | 'locked' | 'flipX' | 'flipY' | 'blur' | 'brightness' | 'contrast' | 'saturate' | 'mediaScale' | 'mediaX' | 'mediaY' | 'shadowBlur' | 'shadowColor' | 'fill' | 'stroke' | 'strokeWidth' | 'fontSize' | 'fontWeight' | 'fontFamily' | 'fontStyle' | 'textDecoration' | 'letterSpacing' | 'lineHeight' | 'textAlign' | 'autoSize' | 'color' | 'textBackground' | 'objectFit' | 'borderRadius' | 'gap' | 'columns' | 'clipChildren' | 'autoSizeChildren' | 'text' | 'name'; /** Default value for a layer field (same as createCanvasLayer would use). */ export declare function canvasLayerFieldDefault(kind: CanvasLayerKind, field: CanvasResettableField): unknown; export declare function isCanvasFieldModified(layer: CanvasLayer, field: CanvasResettableField): boolean; export declare function resetCanvasField(layer: CanvasLayer, field: CanvasResettableField): Partial; /** * Scale fontSize with the box when `autoSize` is on (text / sticky). * Side handles reflow and the frame grows with the lines. Only a corner scales the type. */ export declare function applyTextAutoSize(from: CanvasLayer, to: CanvasLayer, prev: Pick, next: Pick): CanvasLayer; export declare function reorderCanvasLayers(layers: CanvasLayer[], orderedIds: string[]): CanvasLayer[]; export declare function updateCanvasLayer(doc: CanvasDocument, layerId: string, updater: (l: CanvasLayer) => CanvasLayer): CanvasDocument; export declare function updateCanvasLayers(doc: CanvasDocument, ids: string[], updater: (l: CanvasLayer) => CanvasLayer): CanvasDocument; export declare function snapLayerRect(rect: CanvasLayerRect, cellSize: number, enabled: boolean, bounds: { width: number; height: number; }, minW?: number, minH?: number, guides?: Pick[], guideThreshold?: number, clampToBounds?: boolean): CanvasLayerRect; /** Snap left / center / right (or top / middle / bottom) edges to guides. */ export declare function snapRectToGuides(rect: CanvasLayerRect, guides: Pick[], threshold?: number): CanvasLayerRect; export declare function alignLayerRect(rect: CanvasLayerRect, bounds: { width: number; height: number; }, align: CanvasAlign): CanvasLayerRect; export declare const COVER_ARTBOARD: { readonly width: 1800; readonly height: 600; }; /** Artboard with the photo fully visible. Scaling that layer is the zoom. */ export declare function coverDocumentFromImage(src: string, name?: string): CanvasDocument; /** Drop session blob URLs on the cover layer so the JSON can be stored. */ export declare function persistCoverDocument(doc: CanvasDocument): CanvasDocument; /** Point the stored cover layer at the original file URL. */ export declare function hydrateCoverDocument(doc: CanvasDocument, src: string): CanvasDocument; /** Where the bitmap sits inside the layer box, in the same units as `box`. */ export declare function mediaContentBox(box: { w: number; h: number; }, natural: { w: number; h: number; }, fit: CanvasObjectFit | undefined, zoom?: number, mediaX?: number, mediaY?: number): { x: number; y: number; w: number; h: number; }; export declare function canvasLayerFilter(layer: { blur?: number; brightness?: number; contrast?: number; saturate?: number; }): string | undefined; /** Instagram puzzle feed: 3 columns × 1080 and 3 rows × 1350 (4:5 tiles). */ export declare const INSTAGRAM_GRID_PRESET_ID = "ig-grid-3x3"; export declare const INSTAGRAM_GRID: { readonly id: "ig-grid-3x3"; readonly cols: 3; readonly rows: 3; readonly cellWidth: 1080; readonly cellHeight: 1350; readonly width: 3240; readonly height: 4050; }; export declare const CANVAS_PRESETS: readonly [{ readonly id: "cover"; readonly label: "Cover · 1800×600"; readonly width: 1800; readonly height: 600; }, { readonly id: "hd"; readonly label: "HD · 1280×720"; readonly width: 1280; readonly height: 720; }, { readonly id: "fhd"; readonly label: "Full HD · 1920×1080"; readonly width: 1920; readonly height: 1080; }, { readonly id: "square"; readonly label: "Instagram · 1080×1080"; readonly width: 1080; readonly height: 1080; }, { readonly id: "story"; readonly label: "Story · 1080×1920"; readonly width: 1080; readonly height: 1920; }, { readonly id: "ig-grid-3x3"; readonly label: "Instagram 3×3 · 3240×4050"; readonly width: 3240; readonly height: 4050; }, { readonly id: "landscape"; readonly label: "Landscape · 1600×900"; readonly width: 1600; readonly height: 900; }, { readonly id: "presentation"; readonly label: "Presentation · 1920×1080"; readonly width: 1920; readonly height: 1080; }, { readonly id: "a4"; readonly label: "A4 · 794×1123"; readonly width: 794; readonly height: 1123; }, { readonly id: "twitter"; readonly label: "X Post · 1600×900"; readonly width: 1600; readonly height: 900; }]; /** Locked cut lines between the nine Instagram tiles. */ export declare function instagramGridGuides(): CanvasGuide[]; export declare function emptyInstagramGridDocument(): CanvasDocument; export declare function presetIdForSize(width: number, height: number): string; /** Build a path layer from absolute document points. */ export declare function createPathFromDocPoints(docPoints: { x: number; y: number; }[], opts?: { closed?: boolean; fill?: string; strokeWidth?: number; name?: string; zIndex?: number; }): CanvasLayer; /** Recompute rect + normalized points after editing absolute doc points. */ export declare function rebakePathLayer(layer: CanvasLayer, docPoints: { x: number; y: number; }[]): CanvasLayer; /** Map normalized path points → document space using an absolute (or local) rect. */ export declare function pathPointsToDoc(layer: CanvasLayer, absRect?: Pick): { x: number; y: number; }[];