@overtone-art/canvas-editor-core 0.2.1 → 0.2.3

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.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/editor.ts","../src/events.ts","../src/utils/id.ts","../src/layer.ts","../src/history.ts","../src/snapping.ts","../src/crop.ts","../src/utils/clamp.ts","../src/pattern.ts","../src/utils/units.ts","../src/serialization.ts","../src/export.ts","../src/types.ts"],"sourcesContent":["export { CanvasEditor } from './editor';\nexport { Layer, LayerManager } from './layer';\nexport { HistoryManager } from './history';\nexport { SnapManager } from './snapping';\nexport { CropController } from './crop';\nexport {\n PatternManager,\n buildPatternDataURL,\n drawTiles,\n computeTilePositions,\n clearPatternImageCache,\n} from './pattern';\nexport type { TilePlacement } from './pattern';\nexport { EventEmitter } from './events';\nexport { UnitConverter } from './utils/units';\nexport { generateId } from './utils/id';\nexport { clamp, round2 } from './utils/clamp';\nexport { serializeEditor, deserializeEditor } from './serialization';\nexport { exportPNG, exportSVG, exportDataURL } from './export';\nexport { DEFAULT_PATTERN_CONFIG } from './types';\n\nexport type {\n LayerType,\n LayerData,\n LayerMeta,\n SerializedLayer,\n EditorState,\n EditorConfig,\n FileAdapter,\n ImageProvider,\n ImageProviderResult,\n ExportFormat,\n ShapePlugin,\n TemplateDefinition,\n TemplateParameter,\n PrintifyPositioning,\n TileMode,\n PatternConfig,\n PatternState,\n MockupConfig,\n MockupPrintArea,\n EditorEvents,\n Unit,\n} from './types';\n\nexport type { PngExportOptions } from './export';\n","import { Canvas, FabricImage, Textbox } from 'fabric';\nimport type { FabricObject, ImageFormat } from 'fabric';\nimport type {\n EditorConfig,\n EditorEvents,\n EditorState,\n FileAdapter,\n ImageProvider,\n ImageProviderResult,\n MockupConfig,\n PatternConfig,\n PrintifyPositioning,\n ShapePlugin,\n TemplateDefinition,\n} from './types';\nimport { EventEmitter } from './events';\nimport { Layer, LayerManager } from './layer';\nimport { HistoryManager } from './history';\nimport { SnapManager } from './snapping';\nimport { CropController } from './crop';\nimport { PatternManager } from './pattern';\nimport { UnitConverter } from './utils/units';\nimport { serializeEditor, deserializeEditor } from './serialization';\nimport { exportPNG, exportSVG, exportDataURL } from './export';\nimport type { PngExportOptions } from './export';\nimport { clamp, round2 } from './utils/clamp';\n\nconst MIN_ZOOM = 0.1;\nconst MAX_ZOOM = 8;\n\nexport class CanvasEditor {\n readonly canvas: Canvas;\n readonly layers: LayerManager;\n readonly history: HistoryManager;\n readonly events: EventEmitter<EditorEvents>;\n readonly units: UnitConverter;\n readonly snapping: SnapManager;\n readonly crop: CropController;\n readonly patterns: PatternManager;\n\n private fileAdapter?: FileAdapter;\n private imageProvider?: ImageProvider;\n private zoomLevel = 1;\n private mockup: MockupConfig | null = null;\n // The design's configured background. The live canvas background is forced\n // transparent while a mockup preview is shown, so this is the source of truth\n // for serialization and export — not the (possibly transient) canvas value.\n private designBackground: string;\n\n constructor(canvasElement: HTMLCanvasElement, config: EditorConfig) {\n this.events = new EventEmitter<EditorEvents>();\n this.units = new UnitConverter(config.unit ?? 'px', config.dpi ?? 72);\n\n const widthPx = this.units.toPixels(config.width);\n const heightPx = this.units.toPixels(config.height);\n this.designBackground = config.backgroundColor ?? '#ffffff';\n\n this.canvas = new Canvas(canvasElement, {\n width: widthPx,\n height: heightPx,\n backgroundColor: this.designBackground,\n preserveObjectStacking: config.preserveObjectStacking ?? true,\n selection: true,\n });\n\n this.layers = new LayerManager(this.canvas, this.events);\n this.fileAdapter = config.fileAdapter;\n this.imageProvider = config.imageProvider;\n\n this.history = new HistoryManager({\n getState: () => JSON.stringify(this.toJSON()),\n restoreState: async (state: string) => {\n const parsed = JSON.parse(state) as EditorState;\n await this.fromJSON(parsed);\n },\n events: this.events,\n });\n\n this.snapping = new SnapManager(this.canvas, this.events);\n this.crop = new CropController(this.canvas, this.history, this.events);\n this.patterns = new PatternManager(this.canvas, this.layers, this.history);\n\n this.setupCanvasEvents();\n this.history.saveImmediate();\n }\n\n // ─── Layer Operations ────────────────────────────────\n\n async addImage(url: string, options?: Partial<Record<string, unknown>>): Promise<Layer> {\n // Don't force crossOrigin here: a remote image whose host sends no CORS\n // header would then fail to load at all. The pattern engine requests its own\n // crossOrigin copy when it needs an untainted canvas (see pattern.ts).\n // Fabric v7 defaults originX/originY to 'center'; pin top-left so images\n // land at their left/top like the rest of the editor's coordinate model.\n const img = await FabricImage.fromURL(url, {}, { originX: 'left', originY: 'top', ...options });\n const layer = this.layers.add('image', img);\n this.history.save();\n return layer;\n }\n\n addText(text: string, options?: Partial<Record<string, unknown>>): Layer {\n const textbox = new Textbox(text, {\n fontSize: 32,\n fontFamily: 'Arial',\n fill: '#000000',\n width: 200,\n // v7 defaults origin to center; keep top-left placement.\n originX: 'left',\n originY: 'top',\n ...options,\n });\n const layer = this.layers.add('text', textbox as unknown as FabricObject);\n this.history.save();\n return layer;\n }\n\n addShape(plugin: ShapePlugin, options?: Record<string, unknown>): Layer {\n const obj = plugin.create(options);\n const layer = this.layers.add('shape', obj, plugin.name);\n this.history.save();\n return layer;\n }\n\n async addTemplate(\n template: TemplateDefinition,\n _params: Record<string, string | number>,\n ): Promise<Layer> {\n // Template loading is handled by the templates package\n // This is a placeholder for direct fabric object addition\n const { Textbox: TextboxClass } = await import('fabric');\n const placeholder = new TextboxClass(`[Template: ${template.name}]`, {\n fontSize: 24,\n fontFamily: 'Arial',\n fill: '#666666',\n width: 300,\n originX: 'left',\n originY: 'top',\n });\n const layer = this.layers.add(\n 'template',\n placeholder as unknown as FabricObject,\n template.name,\n );\n this.history.save();\n return layer;\n }\n\n removeLayer(id: string): void {\n this.layers.remove(id);\n this.history.save();\n }\n\n selectLayer(id: string | null): void {\n this.layers.select(id);\n }\n\n getSelectedLayer(): Layer | null {\n const active = this.canvas.getActiveObject();\n if (!active) return null;\n return this.layers.findByObject(active) ?? null;\n }\n\n /** Mirror a layer along the given axis. */\n flipLayer(id: string, axis: 'horizontal' | 'vertical'): void {\n const layer = this.layers.get(id);\n if (!layer) return;\n const obj = layer.fabricObject;\n if (axis === 'horizontal') {\n obj.set('flipX', !obj.flipX);\n } else {\n obj.set('flipY', !obj.flipY);\n }\n obj.setCoords();\n this.canvas.requestRenderAll();\n this.history.save();\n }\n\n /** Clone a layer (offset slightly) and select the copy. */\n async duplicateLayer(id: string): Promise<Layer | null> {\n const layer = this.layers.get(id);\n if (!layer) return null;\n\n const clone = await layer.fabricObject.clone();\n // A pattern layer's image is rasterised to cover the canvas at 0,0; offsetting\n // it would leave a gap at the edges, so only nudge non-pattern duplicates.\n if (!layer.meta.pattern) {\n clone.set({ left: (clone.left ?? 0) + 15, top: (clone.top ?? 0) + 15 });\n }\n clone.setCoords();\n\n const copy = this.layers.add(layer.type, clone, `${layer.name} copy`);\n // Carry non-fabric metadata (e.g. pattern config) onto the copy.\n copy.meta = structuredClone(layer.meta);\n\n this.canvas.setActiveObject(clone);\n this.canvas.requestRenderAll();\n this.history.save();\n return copy;\n }\n\n // ─── Serialization ──────────────────────────────────\n\n toJSON(): EditorState {\n return serializeEditor(this);\n }\n\n async fromJSON(state: EditorState): Promise<void> {\n await deserializeEditor(this, state);\n }\n\n // ─── Export ──────────────────────────────────────────\n\n async toPNG(options?: PngExportOptions): Promise<Blob> {\n this.events.emit('export:start', { format: 'png' });\n const blob = await this.withDesignBackground(() => exportPNG(this.canvas, options));\n this.events.emit('export:complete', { format: 'png' });\n return blob;\n }\n\n toSVG(): string {\n this.events.emit('export:start', { format: 'svg' });\n const svg = this.withDesignBackground(() => exportSVG(this.canvas));\n this.events.emit('export:complete', { format: 'svg' });\n return svg;\n }\n\n toDataURL(format: ImageFormat = 'png', multiplier = 1): string {\n return this.withDesignBackground(() => exportDataURL(this.canvas, format, multiplier));\n }\n\n /**\n * Run an export with the configured design background applied, even when a\n * mockup preview has forced the live canvas transparent — so exports reflect\n * the design, not the preview. Restores the preview state afterwards.\n */\n private withDesignBackground<T>(fn: () => T): T {\n if (!this.mockup) return fn();\n const previewBg = this.canvas.backgroundColor;\n this.canvas.backgroundColor = this.designBackground;\n try {\n return fn();\n } finally {\n this.canvas.backgroundColor = previewBg;\n }\n }\n\n toPrintifyPositioning(): Record<string, PrintifyPositioning> {\n const result: Record<string, PrintifyPositioning> = {};\n const canvasW = this.canvas.getWidth();\n const canvasH = this.canvas.getHeight();\n\n const imageLayers = this.layers.getAll().filter((l) => l.type === 'image');\n\n if (imageLayers.length === 0) {\n result['front'] = { x: 0.5, y: 0.5, scale: 1, angle: 0 };\n return result;\n }\n\n for (const layer of imageLayers) {\n const obj = layer.fabricObject;\n const center = obj.getCenterPoint();\n result[layer.name] = {\n x: round2(center.x / canvasW),\n y: round2(center.y / canvasH),\n scale: round2(obj.scaleX ?? 1),\n angle: round2(obj.angle ?? 0),\n };\n }\n\n return result;\n }\n\n // ─── File Operations ────────────────────────────────\n\n async save(filename: string, format: 'png' | 'svg' | 'json'): Promise<string | undefined> {\n if (!this.fileAdapter) return undefined;\n\n let data: Blob | string;\n if (format === 'png') {\n data = await this.toPNG();\n } else if (format === 'svg') {\n data = this.toSVG();\n } else {\n data = JSON.stringify(this.toJSON(), null, 2);\n }\n\n return this.fileAdapter.save(data, filename, format);\n }\n\n async uploadImage(file: File): Promise<ImageProviderResult | undefined> {\n if (!this.imageProvider) return undefined;\n return this.imageProvider.upload(file);\n }\n\n async browseImages(): Promise<ImageProviderResult | null> {\n if (!this.imageProvider?.browse) return null;\n return this.imageProvider.browse();\n }\n\n setFileAdapter(adapter: FileAdapter): void {\n this.fileAdapter = adapter;\n }\n\n setImageProvider(provider: ImageProvider): void {\n this.imageProvider = provider;\n }\n\n // ─── Canvas Operations ──────────────────────────────\n\n setBackground(color: string): void {\n this.designBackground = color;\n // Don't override the mockup's transparent preview; it will pick up the new\n // colour when the mockup is cleared.\n if (!this.mockup) {\n this.canvas.backgroundColor = color;\n this.canvas.requestRenderAll();\n }\n this.history.save();\n }\n\n getDesignBackground(): string {\n return this.designBackground;\n }\n\n resize(width: number, height: number): void {\n const widthPx = this.units.toPixels(width);\n const heightPx = this.units.toPixels(height);\n this.canvas.setDimensions({ width: widthPx, height: heightPx });\n this.canvas.requestRenderAll();\n }\n\n // ─── Zoom ───────────────────────────────────────────\n //\n // Zoom is a *display* transform (the view layer CSS-scales the canvas), not a\n // fabric viewport zoom — fabric's setZoom bakes into toDataURL/toSVG and would\n // corrupt exports. The editor only tracks the level and emits an event.\n\n setZoom(level: number): void {\n const next = clamp(round2(level), MIN_ZOOM, MAX_ZOOM);\n if (next === this.zoomLevel) return;\n this.zoomLevel = next;\n this.events.emit('zoom:changed', { zoom: next });\n }\n\n /** @deprecated use setZoom — kept for backward compatibility. */\n zoom(level: number): void {\n this.setZoom(level);\n }\n\n getZoom(): number {\n return this.zoomLevel;\n }\n\n zoomIn(step = 0.1): void {\n this.setZoom(this.zoomLevel + step);\n }\n\n zoomOut(step = 0.1): void {\n this.setZoom(this.zoomLevel - step);\n }\n\n resetZoom(): void {\n this.setZoom(1);\n }\n\n /** Fit the canvas inside a viewport (in px), accounting for padding. */\n zoomToFit(viewportWidth: number, viewportHeight: number, padding = 0): void {\n const w = this.canvas.getWidth();\n const h = this.canvas.getHeight();\n if (w <= 0 || h <= 0) return;\n const sx = (viewportWidth - padding * 2) / w;\n const sy = (viewportHeight - padding * 2) / h;\n this.setZoom(Math.min(sx, sy));\n }\n\n // ─── Patterns ───────────────────────────────────────\n\n applyPattern(layerId: string, config: PatternConfig): Promise<void> {\n return this.patterns.apply(layerId, config);\n }\n\n clearPattern(layerId: string): Promise<void> {\n return this.patterns.disable(layerId);\n }\n\n // ─── Mockup (preview-only) ──────────────────────────\n\n setMockup(mockup: MockupConfig | null): void {\n this.mockup = mockup;\n // The mockup renders behind the design, so the canvas must be transparent\n // while it is shown; restore the configured background when it is cleared.\n this.canvas.backgroundColor = mockup ? '' : this.designBackground;\n this.canvas.requestRenderAll();\n this.events.emit('mockup:changed', { mockup });\n }\n\n clearMockup(): void {\n this.setMockup(null);\n }\n\n getMockup(): MockupConfig | null {\n return this.mockup;\n }\n\n // ─── Cleanup ────────────────────────────────────────\n\n dispose(): void {\n this.snapping.dispose();\n this.crop.dispose();\n this.history.dispose();\n this.events.removeAllListeners();\n this.canvas.dispose();\n }\n\n // ─── Private ────────────────────────────────────────\n\n private setupCanvasEvents(): void {\n this.canvas.on('object:modified', (e) => {\n if (!e.target) return;\n const layer = this.layers.findByObject(e.target);\n if (layer) {\n this.events.emit('layer:modified', { layerId: layer.id });\n this.history.save();\n }\n });\n\n this.canvas.on('selection:created', (e) => {\n const selected = (e.selected ?? [])\n .map((obj) => this.layers.findByObject(obj)?.id)\n .filter((id): id is string => id !== undefined);\n this.events.emit('selection:changed', { selected });\n });\n\n this.canvas.on('selection:updated', (e) => {\n const selected = (e.selected ?? [])\n .map((obj) => this.layers.findByObject(obj)?.id)\n .filter((id): id is string => id !== undefined);\n this.events.emit('selection:changed', { selected });\n });\n\n this.canvas.on('selection:cleared', () => {\n this.events.emit('selection:changed', { selected: [] });\n });\n }\n}\n","type EventHandler<T> = (data: T) => void;\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport class EventEmitter<TEvents extends {} = Record<string, unknown>> {\n private listeners = new Map<keyof TEvents, Set<EventHandler<unknown>>>();\n\n on<K extends keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(handler as EventHandler<unknown>);\n }\n\n off<K extends keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): void {\n this.listeners.get(event)?.delete(handler as EventHandler<unknown>);\n }\n\n once<K extends keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): void {\n const wrapped: EventHandler<TEvents[K]> = (data) => {\n this.off(event, wrapped);\n handler(data);\n };\n this.on(event, wrapped);\n }\n\n emit<K extends keyof TEvents>(event: K, data: TEvents[K]): void {\n const handlers = this.listeners.get(event);\n if (!handlers) return;\n for (const handler of handlers) {\n handler(data);\n }\n }\n\n removeAllListeners(event?: keyof TEvents): void {\n if (event) {\n this.listeners.delete(event);\n } else {\n this.listeners.clear();\n }\n }\n}\n","import { nanoid } from 'nanoid';\n\nexport const generateId = (): string => nanoid(12);\n","import type { Canvas, FabricObject } from 'fabric';\nimport type { LayerData, LayerMeta, LayerType, SerializedLayer } from './types';\nimport type { EditorEvents } from './types';\nimport type { EventEmitter } from './events';\nimport { generateId } from './utils/id';\n\nexport class Layer {\n readonly id: string;\n readonly type: LayerType;\n name: string;\n visible: boolean;\n locked: boolean;\n opacity: number;\n /** Non-fabric data (e.g. pattern config) that must persist with the layer. */\n meta: LayerMeta;\n readonly fabricObject: FabricObject;\n\n constructor(type: LayerType, fabricObject: FabricObject, name?: string, id?: string) {\n this.id = id ?? generateId();\n this.type = type;\n this.name = name ?? `${type}-${this.id.slice(0, 4)}`;\n this.visible = true;\n this.locked = false;\n this.opacity = 1;\n this.meta = {};\n this.fabricObject = fabricObject;\n\n (this.fabricObject as FabricObject & { _layerId: string })._layerId = this.id;\n }\n\n private hasMeta(): boolean {\n return Object.keys(this.meta).length > 0;\n }\n\n toData(): LayerData {\n return {\n id: this.id,\n type: this.type,\n name: this.name,\n visible: this.visible,\n locked: this.locked,\n opacity: this.opacity,\n ...(this.hasMeta() ? { meta: this.meta } : {}),\n };\n }\n\n serialize(): SerializedLayer {\n return {\n ...this.toData(),\n fabricObject: this.fabricObject.toObject() as Record<string, unknown>,\n };\n }\n}\n\nexport class LayerManager {\n private layers: Layer[] = [];\n\n constructor(\n private canvas: Canvas,\n private events: EventEmitter<EditorEvents>,\n ) {}\n\n add(type: LayerType, fabricObject: FabricObject, name?: string, id?: string): Layer {\n const layer = new Layer(type, fabricObject, name, id);\n this.layers.push(layer);\n this.canvas.add(fabricObject);\n this.events.emit('layer:added', { layer: layer.toData() });\n this.emitChanged();\n return layer;\n }\n\n remove(id: string): boolean {\n const index = this.layers.findIndex((l) => l.id === id);\n if (index === -1) return false;\n const layer = this.layers[index];\n this.canvas.remove(layer.fabricObject);\n this.layers.splice(index, 1);\n this.events.emit('layer:removed', { layerId: id });\n this.emitChanged();\n return true;\n }\n\n reorder(id: string, newIndex: number): boolean {\n const oldIndex = this.layers.findIndex((l) => l.id === id);\n if (oldIndex === -1) return false;\n const clamped = Math.max(0, Math.min(this.layers.length - 1, newIndex));\n const [layer] = this.layers.splice(oldIndex, 1);\n this.layers.splice(clamped, 0, layer);\n // Sync fabric z-order\n this.layers.forEach((l, i) => {\n this.canvas.moveObjectTo(l.fabricObject, i);\n });\n this.events.emit('layer:reordered', { layerIds: this.layers.map((l) => l.id) });\n this.emitChanged();\n return true;\n }\n\n select(id: string | null): void {\n if (id === null) {\n this.canvas.discardActiveObject();\n } else {\n const layer = this.get(id);\n if (layer) {\n this.canvas.setActiveObject(layer.fabricObject);\n }\n }\n this.canvas.requestRenderAll();\n this.events.emit('layer:selected', { layerId: id });\n // Fabric only fires selection:* on user interaction; mirror programmatic\n // selection so React selection state (useSelection) stays in sync.\n const selected = id && this.get(id) ? [id] : [];\n this.events.emit('selection:changed', { selected });\n }\n\n get(id: string): Layer | undefined {\n return this.layers.find((l) => l.id === id);\n }\n\n getAll(): Layer[] {\n return [...this.layers];\n }\n\n findByObject(obj: FabricObject): Layer | undefined {\n const layerId = (obj as FabricObject & { _layerId?: string })._layerId;\n if (layerId) return this.get(layerId);\n return this.layers.find((l) => l.fabricObject === obj);\n }\n\n setVisibility(id: string, visible: boolean): void {\n const layer = this.get(id);\n if (!layer) return;\n layer.visible = visible;\n layer.fabricObject.visible = visible;\n this.canvas.requestRenderAll();\n this.emitChanged();\n }\n\n setLocked(id: string, locked: boolean): void {\n const layer = this.get(id);\n if (!layer) return;\n layer.locked = locked;\n layer.fabricObject.selectable = !locked;\n layer.fabricObject.evented = !locked;\n this.canvas.requestRenderAll();\n this.emitChanged();\n }\n\n setOpacity(id: string, opacity: number): void {\n const layer = this.get(id);\n if (!layer) return;\n layer.opacity = opacity;\n layer.fabricObject.opacity = opacity;\n this.canvas.requestRenderAll();\n this.emitChanged();\n }\n\n setName(id: string, name: string): void {\n const layer = this.get(id);\n if (!layer) return;\n layer.name = name;\n this.emitChanged();\n }\n\n clear(): void {\n for (const layer of this.layers) {\n this.canvas.remove(layer.fabricObject);\n }\n this.layers = [];\n this.emitChanged();\n }\n\n count(): number {\n return this.layers.length;\n }\n\n private emitChanged(): void {\n this.events.emit('layers:changed', {\n layers: this.layers.map((l) => l.toData()),\n });\n }\n}\n","import type { EditorEvents } from './types';\nimport type { EventEmitter } from './events';\n\nexport class HistoryManager {\n private undoStack: string[] = [];\n private redoStack: string[] = [];\n private maxSize: number;\n private paused = false;\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private debounceMs: number;\n private getState: () => string;\n private restoreState: (state: string) => Promise<void>;\n private events: EventEmitter<EditorEvents>;\n\n constructor(opts: {\n getState: () => string;\n restoreState: (state: string) => Promise<void>;\n events: EventEmitter<EditorEvents>;\n maxSize?: number;\n debounceMs?: number;\n }) {\n this.getState = opts.getState;\n this.restoreState = opts.restoreState;\n this.events = opts.events;\n this.maxSize = opts.maxSize ?? 50;\n this.debounceMs = opts.debounceMs ?? 300;\n }\n\n save(): void {\n if (this.paused) return;\n\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n }\n\n this.debounceTimer = setTimeout(() => {\n this.saveImmediate();\n }, this.debounceMs);\n }\n\n saveImmediate(): void {\n if (this.paused) return;\n const state = this.getState();\n this.undoStack.push(state);\n if (this.undoStack.length > this.maxSize) {\n this.undoStack.shift();\n }\n this.redoStack = [];\n this.emitChanged();\n }\n\n async undo(): Promise<void> {\n // Drop any debounced save still pending from an in-progress edit, otherwise\n // its timer would fire after restore completes and clobber the redo stack.\n this.cancelPending();\n const state = this.undoStack.pop();\n if (!state) return;\n\n this.redoStack.push(this.getState());\n this.paused = true;\n await this.restoreState(state);\n this.paused = false;\n this.emitChanged();\n }\n\n async redo(): Promise<void> {\n this.cancelPending();\n const state = this.redoStack.pop();\n if (!state) return;\n\n this.undoStack.push(this.getState());\n this.paused = true;\n await this.restoreState(state);\n this.paused = false;\n this.emitChanged();\n }\n\n /** True while a restore (undo/redo/deserialize) is in flight. Managers that\n * react to canvas events should skip mutating history during this window. */\n isRestoring(): boolean {\n return this.paused;\n }\n\n pause(): void {\n this.paused = true;\n }\n\n resume(): void {\n this.paused = false;\n }\n\n canUndo(): boolean {\n return this.undoStack.length > 0;\n }\n\n canRedo(): boolean {\n return this.redoStack.length > 0;\n }\n\n clear(): void {\n this.undoStack = [];\n this.redoStack = [];\n this.emitChanged();\n }\n\n private cancelPending(): void {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n }\n\n dispose(): void {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n }\n }\n\n private emitChanged(): void {\n this.events.emit('history:changed', {\n canUndo: this.canUndo(),\n canRedo: this.canRedo(),\n });\n }\n}\n","import type { Canvas, FabricObject, TPointerEvent } from 'fabric';\nimport type { EventEmitter } from './events';\nimport type { EditorEvents } from './types';\n\ninterface SnapGuide {\n /** Vertical guide at this x (canvas px), or undefined for a horizontal one. */\n x?: number;\n y?: number;\n}\n\nconst STROKE = '#22c55e';\n\n/**\n * Snaps a dragged object's edges and centre to the canvas edges/centre and to\n * other objects, drawing transient guide lines while it does. Switchable via\n * {@link setEnabled}; inert (zero overhead beyond a flag check) when disabled.\n */\nexport class SnapManager {\n private enabled = false;\n private threshold = 8;\n private guides: SnapGuide[] = [];\n\n private readonly onMoving: (e: { target?: FabricObject; e: TPointerEvent }) => void;\n private readonly onAfterRender: () => void;\n private readonly onModified: () => void;\n\n constructor(\n private canvas: Canvas,\n private events: EventEmitter<EditorEvents>,\n ) {\n this.onMoving = (e) => this.handleMoving(e.target);\n this.onAfterRender = () => this.drawGuides();\n this.onModified = () => this.clearGuides();\n\n this.canvas.on('object:moving', this.onMoving);\n this.canvas.on('after:render', this.onAfterRender);\n this.canvas.on('mouse:up', this.onModified);\n this.canvas.on('selection:cleared', this.onModified);\n }\n\n setEnabled(enabled: boolean): void {\n if (this.enabled === enabled) return;\n this.enabled = enabled;\n if (!enabled) this.clearGuides();\n this.events.emit('snap:changed', { enabled });\n }\n\n isEnabled(): boolean {\n return this.enabled;\n }\n\n setThreshold(px: number): void {\n this.threshold = Math.max(0, px);\n }\n\n dispose(): void {\n this.canvas.off('object:moving', this.onMoving);\n this.canvas.off('after:render', this.onAfterRender);\n this.canvas.off('mouse:up', this.onModified);\n this.canvas.off('selection:cleared', this.onModified);\n this.guides = [];\n }\n\n private handleMoving(target?: FabricObject): void {\n if (!this.enabled || !target) return;\n\n target.setCoords();\n const r = target.getBoundingRect();\n const cw = this.canvas.getWidth();\n const ch = this.canvas.getHeight();\n\n // Candidate lines for both axes, collected in a single pass over the objects.\n const { xs, ys } = collectCandidates(this.canvas, target, cw, ch);\n\n this.guides = [];\n\n const dx = bestSnap([r.left, r.left + r.width / 2, r.left + r.width], xs, this.threshold);\n if (dx !== null) {\n target.set('left', (target.left ?? 0) + dx.delta);\n this.guides.push({ x: dx.line });\n }\n\n const dy = bestSnap([r.top, r.top + r.height / 2, r.top + r.height], ys, this.threshold);\n if (dy !== null) {\n target.set('top', (target.top ?? 0) + dy.delta);\n this.guides.push({ y: dy.line });\n }\n\n target.setCoords();\n }\n\n private drawGuides(): void {\n if (!this.enabled || this.guides.length === 0) return;\n const ctx = this.canvas.contextTop;\n if (!ctx) return;\n\n const retina = this.canvas.getRetinaScaling();\n const cw = this.canvas.getWidth();\n const ch = this.canvas.getHeight();\n\n ctx.save();\n ctx.setTransform(retina, 0, 0, retina, 0, 0);\n ctx.lineWidth = 1;\n ctx.strokeStyle = STROKE;\n ctx.setLineDash([4, 4]);\n ctx.beginPath();\n for (const g of this.guides) {\n if (g.x !== undefined) {\n ctx.moveTo(g.x, 0);\n ctx.lineTo(g.x, ch);\n }\n if (g.y !== undefined) {\n ctx.moveTo(0, g.y);\n ctx.lineTo(cw, g.y);\n }\n }\n ctx.stroke();\n ctx.restore();\n }\n\n private clearGuides(): void {\n if (this.guides.length === 0) return;\n this.guides = [];\n this.canvas.clearContext(this.canvas.contextTop);\n }\n}\n\n/**\n * Snap candidate lines (edges + centre) for the canvas and every other object,\n * for both axes, computed in one pass so each object's bounds are read once.\n */\nfunction collectCandidates(\n canvas: Canvas,\n moving: FabricObject,\n cw: number,\n ch: number,\n): { xs: number[]; ys: number[] } {\n const xs = [0, cw / 2, cw];\n const ys = [0, ch / 2, ch];\n for (const obj of canvas.getObjects()) {\n if (obj === moving || !obj.visible) continue;\n obj.setCoords();\n const r = obj.getBoundingRect();\n xs.push(r.left, r.left + r.width / 2, r.left + r.width);\n ys.push(r.top, r.top + r.height / 2, r.top + r.height);\n }\n return { xs, ys };\n}\n\n/**\n * Find the closest (edge, candidate) pair within threshold. Returns the delta\n * to apply and the guide line to draw, or null if nothing is close enough.\n */\nfunction bestSnap(\n edges: number[],\n candidates: number[],\n threshold: number,\n): { delta: number; line: number } | null {\n let best: { delta: number; line: number; dist: number } | null = null;\n for (const edge of edges) {\n for (const c of candidates) {\n const dist = Math.abs(edge - c);\n if (dist <= threshold && (best === null || dist < best.dist)) {\n best = { delta: c - edge, line: c, dist };\n }\n }\n }\n return best ? { delta: best.delta, line: best.line } : null;\n}\n\nexport { bestSnap as _bestSnap };\n","import { Rect } from 'fabric';\nimport type { Canvas, FabricImage } from 'fabric';\nimport type { Layer } from './layer';\nimport type { HistoryManager } from './history';\nimport type { EventEmitter } from './events';\nimport type { EditorEvents } from './types';\nimport { clamp } from './utils/clamp';\n\ninterface CropSession {\n layerId: string;\n image: FabricImage;\n rect: Rect;\n prevSelectable: boolean;\n prevEvented: boolean;\n /** Angle before crop normalised it to 0; restored if the crop is cancelled. */\n prevAngle: number;\n}\n\n/**\n * Interactive crop for image layers. {@link start} drops a draggable/resizable\n * rectangle over the image; {@link apply} converts it into fabric crop props\n * (cropX/cropY/width/height in element pixels) and keeps the displayed size in\n * sync with the rectangle.\n *\n * Rotation is unsupported: an angled image is normalised to 0° on start.\n */\nexport class CropController {\n private session: CropSession | null = null;\n private readonly onSelectionCleared: () => void;\n\n constructor(\n private canvas: Canvas,\n private history: HistoryManager,\n private events: EventEmitter<EditorEvents>,\n ) {\n this.onSelectionCleared = () => {\n // Clicking away from the crop rect abandons the crop.\n if (this.session) this.cancel();\n };\n this.canvas.on('selection:cleared', this.onSelectionCleared);\n }\n\n isActive(): boolean {\n return this.session !== null;\n }\n\n activeLayerId(): string | null {\n return this.session?.layerId ?? null;\n }\n\n start(layer: Layer): void {\n if (layer.type !== 'image') return;\n if (this.session) this.cancel();\n\n const image = layer.fabricObject as FabricImage;\n const prevAngle = image.angle ?? 0;\n if (prevAngle) image.rotate(0);\n image.setCoords();\n const b = image.getBoundingRect();\n\n const rect = new Rect({\n left: b.left,\n top: b.top,\n width: b.width,\n height: b.height,\n originX: 'left',\n originY: 'top',\n fill: 'rgba(34,197,94,0.12)',\n stroke: STROKE,\n strokeWidth: 1,\n strokeDashArray: [4, 4],\n strokeUniform: true,\n lockRotation: true,\n cornerColor: STROKE,\n transparentCorners: false,\n });\n rect.setControlsVisibility({ mtr: false });\n\n const prevSelectable = image.selectable ?? true;\n const prevEvented = image.evented ?? true;\n image.selectable = false;\n image.evented = false;\n\n this.canvas.add(rect);\n this.canvas.setActiveObject(rect);\n this.canvas.requestRenderAll();\n\n this.session = { layerId: layer.id, image, rect, prevSelectable, prevEvented, prevAngle };\n this.events.emit('crop:changed', { active: true, layerId: layer.id });\n }\n\n apply(): void {\n const s = this.session;\n if (!s) return;\n const { image, rect } = s;\n\n rect.setCoords();\n image.setCoords();\n const scaleX = image.scaleX ?? 1;\n const scaleY = image.scaleY ?? 1;\n const imgLeft = image.left ?? 0;\n const imgTop = image.top ?? 0;\n\n const el = image.getElement() as HTMLImageElement;\n const naturalW = el.naturalWidth || image.width || 0;\n const naturalH = el.naturalHeight || image.height || 0;\n\n // Map the rect (canvas px) into the image's element-pixel space.\n let cropX = (image.cropX ?? 0) + (rect.left! - imgLeft) / scaleX;\n let cropY = (image.cropY ?? 0) + (rect.top! - imgTop) / scaleY;\n let cropW = rect.getScaledWidth() / scaleX;\n let cropH = rect.getScaledHeight() / scaleY;\n\n // Keep the crop window inside the source element.\n cropX = clamp(cropX, 0, Math.max(0, naturalW - 1));\n cropY = clamp(cropY, 0, Math.max(0, naturalH - 1));\n cropW = clamp(cropW, 1, naturalW - cropX);\n cropH = clamp(cropH, 1, naturalH - cropY);\n\n image.set({\n cropX,\n cropY,\n width: cropW,\n height: cropH,\n // Anchor the displayed result where the rect sits.\n left: imgLeft + (cropX - (image.cropX ?? 0)) * scaleX,\n top: imgTop + (cropY - (image.cropY ?? 0)) * scaleY,\n });\n image.setCoords();\n\n this.finish();\n this.history.save();\n }\n\n cancel(): void {\n const s = this.session;\n if (!s) return;\n // Cancelling discards the crop, so restore the rotation start() zeroed out.\n if (s.prevAngle) {\n s.image.rotate(s.prevAngle);\n s.image.setCoords();\n }\n this.finish();\n }\n\n dispose(): void {\n this.cancel();\n this.canvas.off('selection:cleared', this.onSelectionCleared);\n }\n\n private finish(): void {\n const s = this.session;\n if (!s) return;\n // Null the session first so the selection:cleared fired by removing the\n // rect / re-selecting does not re-enter cancel().\n this.session = null;\n\n this.canvas.remove(s.rect);\n s.image.selectable = s.prevSelectable;\n s.image.evented = s.prevEvented;\n this.canvas.setActiveObject(s.image);\n this.canvas.requestRenderAll();\n\n this.events.emit('crop:changed', { active: false, layerId: null });\n }\n}\n\nconst STROKE = '#22c55e';\n","export function clamp(v: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, v));\n}\n\nexport function round2(v: number): number {\n return Math.round(v * 100) / 100;\n}\n","import { FabricImage } from 'fabric';\nimport type { Canvas } from 'fabric';\nimport type { Layer, LayerManager } from './layer';\nimport type { HistoryManager } from './history';\nimport type { PatternConfig } from './types';\n\n/**\n * Renders a single image layer as a repeating pattern that fills the canvas.\n *\n * The tiled result is rasterised onto an offscreen canvas and swapped in as the\n * layer's image source, so it survives export, serialization and undo/redo with\n * no extra machinery. The original source + transform are kept in `layer.meta`\n * so the pattern can be turned back off.\n */\nexport class PatternManager {\n // Per-layer task chain. apply()/disable() both await an async setSrc on the\n // same fabric image; running two concurrently lets their setSrc resolutions\n // interleave (wrong image installed, original lost). Serialising per layer\n // guarantees the last-requested operation wins and state stays consistent.\n private chains = new Map<string, Promise<unknown>>();\n\n constructor(\n private canvas: Canvas,\n private layers: LayerManager,\n private history: HistoryManager,\n ) {}\n\n isPattern(layerId: string): boolean {\n return !!this.layers.get(layerId)?.meta.pattern;\n }\n\n getConfig(layerId: string): PatternConfig | null {\n return this.layers.get(layerId)?.meta.pattern?.config ?? null;\n }\n\n /** Turn a plain image layer into a pattern, or update an existing one. */\n apply(layerId: string, config: PatternConfig): Promise<void> {\n return this.enqueue(layerId, async () => {\n const layer = this.layers.get(layerId);\n if (!layer || layer.type !== 'image') return;\n const image = layer.fabricObject as FabricImage;\n\n const firstEnable = !layer.meta.pattern;\n // Check the field directly (not `firstEnable`) so TS narrows the else.\n if (!layer.meta.pattern) {\n // Captured at run time (not enqueue time) so a queued apply always reads\n // the genuine pre-pattern image, never a half-applied one.\n layer.meta.pattern = {\n config,\n originalSrc: elementToDataURL(image) ?? image.getSrc(),\n original: {\n left: image.left ?? 0,\n top: image.top ?? 0,\n scaleX: image.scaleX ?? 1,\n scaleY: image.scaleY ?? 1,\n width: image.width ?? 0,\n height: image.height ?? 0,\n angle: image.angle ?? 0,\n cropX: image.cropX ?? 0,\n cropY: image.cropY ?? 0,\n },\n };\n } else {\n layer.meta.pattern.config = config;\n }\n\n try {\n await this.renderLayer(layer);\n } catch (err) {\n // Roll back a freshly-captured state so a failed first render doesn't\n // leave the layer flagged as a pattern with the original still showing.\n if (firstEnable) delete layer.meta.pattern;\n throw err;\n }\n this.history.save();\n });\n }\n\n /** Restore the original image and drop the pattern. */\n disable(layerId: string): Promise<void> {\n return this.enqueue(layerId, async () => {\n const layer = this.layers.get(layerId);\n const state = layer?.meta.pattern;\n if (!layer || !state) return;\n const image = layer.fabricObject as FabricImage;\n\n await image.setSrc(state.originalSrc);\n // setSrc resets width/height to the full element size, so restore the\n // captured crop window too — otherwise an image that was cropped before\n // being turned into a pattern loses its crop on disable.\n image.set({\n left: state.original.left,\n top: state.original.top,\n scaleX: state.original.scaleX,\n scaleY: state.original.scaleY,\n width: state.original.width,\n height: state.original.height,\n cropX: state.original.cropX,\n cropY: state.original.cropY,\n angle: state.original.angle,\n });\n image.setCoords();\n delete layer.meta.pattern;\n this.canvas.requestRenderAll();\n this.history.save();\n });\n }\n\n /** Run `task` after any in-flight work for this layer, regardless of outcome. */\n private enqueue(layerId: string, task: () => Promise<void>): Promise<void> {\n const prev = this.chains.get(layerId) ?? Promise.resolve();\n const next = prev.then(task, task);\n // Keep the chain alive on rejection so later ops still run.\n this.chains.set(\n layerId,\n next.catch(() => undefined),\n );\n return next;\n }\n\n private async renderLayer(layer: Layer): Promise<void> {\n const state = layer.meta.pattern;\n if (!state) return;\n const image = layer.fabricObject as FabricImage;\n\n const cw = this.canvas.getWidth();\n const ch = this.canvas.getHeight();\n const tileW = state.original.width * state.original.scaleX;\n const tileH = state.original.height * state.original.scaleY;\n\n const dataUrl = await buildPatternDataURL(\n state.originalSrc,\n state.config,\n cw,\n ch,\n tileW,\n tileH,\n );\n\n await image.setSrc(dataUrl);\n // setSrc leaves cropX/cropY untouched — reset them, then position the tiled\n // image to cover the whole canvas at 1:1.\n image.set({ left: 0, top: 0, scaleX: 1, scaleY: 1, cropX: 0, cropY: 0, angle: 0 });\n image.setCoords();\n this.canvas.requestRenderAll();\n }\n}\n\n/** Snapshot an already-loaded image element to a PNG data URL. */\nfunction elementToDataURL(image: FabricImage): string | null {\n try {\n const el = image.getElement() as HTMLImageElement | HTMLCanvasElement;\n const w = (el as HTMLImageElement).naturalWidth || el.width;\n const h = (el as HTMLImageElement).naturalHeight || el.height;\n if (!w || !h) return null;\n const off = document.createElement('canvas');\n off.width = w;\n off.height = h;\n const ctx = off.getContext('2d');\n if (!ctx) return null;\n ctx.drawImage(el, 0, 0);\n return off.toDataURL('image/png');\n } catch {\n // Tainted canvas (remote image without CORS) — fall back to the live src.\n return null;\n }\n}\n\n// Decoding a source (often a large data URL) is the dominant cost of a regen,\n// so cache decoded images. Bounded with simple LRU eviction so a stream of\n// distinct sources can't grow the cache without limit.\nconst IMAGE_CACHE_MAX = 16;\nconst imageCache = new Map<string, Promise<HTMLImageElement>>();\n\nfunction loadImage(src: string): Promise<HTMLImageElement> {\n const cached = imageCache.get(src);\n if (cached) {\n // Refresh recency (Map preserves insertion order → re-insert = most recent).\n imageCache.delete(src);\n imageCache.set(src, cached);\n return cached;\n }\n\n const promise = decodeImage(src);\n // Don't poison the cache with a failed decode.\n promise.catch(() => {\n if (imageCache.get(src) === promise) imageCache.delete(src);\n });\n\n imageCache.set(src, promise);\n if (imageCache.size > IMAGE_CACHE_MAX) {\n const oldest = imageCache.keys().next().value;\n if (oldest !== undefined) imageCache.delete(oldest);\n }\n return promise;\n}\n\nfunction decodeImage(src: string): Promise<HTMLImageElement> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => resolve(img);\n img.onerror = () => reject(new Error(`Failed to load pattern source: ${src}`));\n img.src = src;\n });\n}\n\n/** Clear the decoded-image cache (e.g. on teardown). Exported for completeness. */\nexport function clearPatternImageCache(): void {\n imageCache.clear();\n}\n\n/**\n * Draw the source image repeatedly across a `targetW × targetH` offscreen\n * canvas and return the result as a PNG data URL.\n *\n * Exported for unit testing of the tile geometry.\n */\nexport async function buildPatternDataURL(\n src: string,\n config: PatternConfig,\n targetW: number,\n targetH: number,\n baseW: number,\n baseH: number,\n): Promise<string> {\n const img = await loadImage(src);\n const off = document.createElement('canvas');\n off.width = Math.max(1, Math.round(targetW));\n off.height = Math.max(1, Math.round(targetH));\n const ctx = off.getContext('2d');\n if (!ctx) return off.toDataURL('image/png');\n\n drawTiles(ctx, img, config, targetW, targetH, baseW, baseH);\n return off.toDataURL('image/png');\n}\n\n/** Placement of one tile, relative to the (already rotated) pattern centre. */\nexport interface TilePlacement {\n /** Tile-centre offset from the pattern centre, in px. */\n x: number;\n y: number;\n /** Per-tile rotation, in radians. */\n rotation: number;\n}\n\n/**\n * Pure tile geometry: where each tile centre lands and how much it is rotated,\n * for a config + target/base size. Covers the rotated bounding diagonal so\n * corners stay filled at any pattern angle. Extracted for unit testing.\n */\nexport function computeTilePositions(\n config: PatternConfig,\n targetW: number,\n targetH: number,\n baseW: number,\n baseH: number,\n): TilePlacement[] {\n const tileW = Math.max(1, baseW * (1 + config.horizontalSpacing / 100));\n const tileH = Math.max(1, baseH * (1 + config.verticalSpacing / 100));\n\n const diag = Math.sqrt(targetW * targetW + targetH * targetH);\n const cols = Math.ceil(diag / tileW) + 2;\n const rows = Math.ceil(diag / tileH) + 2;\n const halfCols = Math.ceil(cols / 2);\n const halfRows = Math.ceil(rows / 2);\n\n const placements: TilePlacement[] = [];\n for (let j = -halfRows; j <= halfRows; j++) {\n for (let i = -halfCols; i <= halfCols; i++) {\n let x = i * tileW;\n let y = j * tileH;\n\n if (config.mode === 'brick-horizontal' && mod2(j) === 1) {\n x += tileW * (config.horizontalOffset / 100);\n } else if (config.mode === 'brick-vertical' && mod2(i) === 1) {\n y += tileH * (config.horizontalOffset / 100);\n }\n\n const rotation = ((i * config.rotationStepH + j * config.rotationStepV) * Math.PI) / 180;\n placements.push({ x, y, rotation });\n }\n }\n return placements;\n}\n\n/**\n * Draw the tiled pattern onto a 2D context. The geometry lives in\n * {@link computeTilePositions}; this just plays it out as canvas calls.\n */\nexport function drawTiles(\n ctx: CanvasRenderingProtocol,\n img: CanvasImageSource,\n config: PatternConfig,\n targetW: number,\n targetH: number,\n baseW: number,\n baseH: number,\n): void {\n ctx.save();\n // Rotate the whole pattern around the canvas centre.\n ctx.translate(targetW / 2, targetH / 2);\n ctx.rotate((config.angle * Math.PI) / 180);\n\n for (const tile of computeTilePositions(config, targetW, targetH, baseW, baseH)) {\n ctx.save();\n ctx.translate(tile.x, tile.y);\n ctx.rotate(tile.rotation);\n ctx.drawImage(img, -baseW / 2, -baseH / 2, baseW, baseH);\n ctx.restore();\n }\n\n ctx.restore();\n}\n\n/** Non-negative parity (so negative row indices brick-shift consistently). */\nfunction mod2(n: number): number {\n return ((n % 2) + 2) % 2;\n}\n\n/** Minimal 2D-context surface used by {@link drawTiles}; lets tests inject a spy. */\nexport interface CanvasRenderingProtocol {\n save(): void;\n restore(): void;\n translate(x: number, y: number): void;\n rotate(angle: number): void;\n drawImage(image: CanvasImageSource, dx: number, dy: number, dw: number, dh: number): void;\n}\n","import type { Unit } from '../types';\n\nconst MM_PER_INCH = 25.4;\n\nexport class UnitConverter {\n constructor(\n private unit: Unit = 'px',\n private dpi: number = 72,\n ) {}\n\n setUnit(unit: Unit): void {\n this.unit = unit;\n }\n\n setDpi(dpi: number): void {\n this.dpi = dpi;\n }\n\n getUnit(): Unit {\n return this.unit;\n }\n\n getDpi(): number {\n return this.dpi;\n }\n\n toPixels(value: number, fromUnit?: Unit): number {\n const u = fromUnit ?? this.unit;\n switch (u) {\n case 'px':\n return value;\n case 'in':\n return value * this.dpi;\n case 'mm':\n return (value / MM_PER_INCH) * this.dpi;\n }\n }\n\n fromPixels(px: number, toUnit?: Unit): number {\n const u = toUnit ?? this.unit;\n switch (u) {\n case 'px':\n return px;\n case 'in':\n return px / this.dpi;\n case 'mm':\n return (px / this.dpi) * MM_PER_INCH;\n }\n }\n}\n","import { util } from 'fabric';\nimport type { FabricObject } from 'fabric';\nimport type { EditorState, SerializedLayer } from './types';\nimport type { CanvasEditor } from './editor';\nimport { Layer } from './layer';\n\nconst VERSION = '1.0.0';\n\nexport function serializeEditor(editor: CanvasEditor): EditorState {\n return {\n version: VERSION,\n canvas: {\n width: editor.canvas.getWidth(),\n height: editor.canvas.getHeight(),\n },\n layers: editor.layers.getAll().map((layer) => layer.serialize()),\n // The configured design background, not the live canvas value (which is\n // forced transparent while a mockup preview is active).\n background: editor.getDesignBackground(),\n mockup: editor.getMockup(),\n };\n}\n\nexport async function deserializeEditor(editor: CanvasEditor, state: EditorState): Promise<void> {\n editor.layers.clear();\n editor.canvas.setDimensions({ width: state.canvas.width, height: state.canvas.height });\n\n // setBackground records the design background; history is paused during\n // restore so this does not push a spurious entry. setMockup (below) then\n // applies the correct live canvas background.\n if (state.background) {\n editor.setBackground(state.background);\n }\n\n // mockup is preview-only (rendered behind the canvas), so it is restored\n // separately from the design layers. `undefined` means \"leave as-is\".\n if (state.mockup !== undefined) {\n editor.setMockup(state.mockup);\n }\n\n for (const serializedLayer of state.layers) {\n await restoreLayer(editor, serializedLayer);\n }\n\n editor.canvas.requestRenderAll();\n}\n\nasync function restoreLayer(editor: CanvasEditor, serialized: SerializedLayer): Promise<Layer> {\n const objects = await util.enlivenObjects([serialized.fabricObject]);\n const fabricObject = objects[0] as FabricObject;\n\n const layer = editor.layers.add(serialized.type, fabricObject, serialized.name, serialized.id);\n\n if (serialized.meta) {\n layer.meta = serialized.meta;\n }\n\n if (!serialized.visible) {\n editor.layers.setVisibility(layer.id, false);\n }\n if (serialized.locked) {\n editor.layers.setLocked(layer.id, true);\n }\n if (serialized.opacity !== 1) {\n editor.layers.setOpacity(layer.id, serialized.opacity);\n }\n\n return layer;\n}\n","import type { Canvas, ImageFormat } from 'fabric';\n\nexport interface PngExportOptions {\n multiplier?: number;\n format?: ImageFormat;\n quality?: number;\n}\n\nexport async function exportPNG(\n canvas: Canvas,\n options: PngExportOptions = {},\n): Promise<Blob> {\n const { multiplier = 1, format = 'png' as ImageFormat, quality = 1 } = options;\n const dataUrl = canvas.toDataURL({\n format,\n multiplier,\n quality,\n });\n const response = await fetch(dataUrl);\n return response.blob();\n}\n\nexport function exportSVG(canvas: Canvas): string {\n return canvas.toSVG();\n}\n\nexport function exportDataURL(\n canvas: Canvas,\n format: ImageFormat = 'png',\n multiplier = 1,\n): string {\n return canvas.toDataURL({ format, multiplier });\n}\n","import type { FabricObject } from 'fabric';\n\n// ─── Units ───────────────────────────────────────────────\n\nexport type Unit = 'px' | 'mm' | 'in';\n\n// ─── Layers ──────────────────────────────────────────────\n\nexport type LayerType = 'image' | 'text' | 'shape' | 'template' | 'group';\n\nexport interface LayerData {\n id: string;\n type: LayerType;\n name: string;\n visible: boolean;\n locked: boolean;\n opacity: number;\n /** Non-fabric metadata persisted with the layer (pattern config, etc). */\n meta?: LayerMeta;\n}\n\nexport interface LayerMeta {\n pattern?: PatternState;\n [key: string]: unknown;\n}\n\nexport interface SerializedLayer extends LayerData {\n fabricObject: Record<string, unknown>;\n}\n\n// ─── Editor ──────────────────────────────────────────────\n\nexport interface EditorState {\n version: string;\n canvas: { width: number; height: number };\n layers: SerializedLayer[];\n background?: string;\n mockup?: MockupConfig | null;\n}\n\nexport interface EditorConfig {\n width: number;\n height: number;\n backgroundColor?: string;\n fileAdapter?: FileAdapter;\n imageProvider?: ImageProvider;\n preserveObjectStacking?: boolean;\n unit?: Unit;\n dpi?: number;\n}\n\n// ─── Adapters ────────────────────────────────────────────\n\nexport type ExportFormat = 'png' | 'svg' | 'json';\n\nexport interface FileAdapter {\n save(data: Blob | string, filename: string, format: ExportFormat): Promise<string>;\n}\n\nexport interface ImageProviderResult {\n url: string;\n width: number;\n height: number;\n}\n\nexport interface ImageProvider {\n upload(file: File): Promise<ImageProviderResult>;\n browse?(): Promise<ImageProviderResult | null>;\n}\n\n// ─── Plugins ─────────────────────────────────────────────\n\nexport interface ShapePlugin {\n name: string;\n icon: string;\n category?: string;\n create(options?: Record<string, unknown>): FabricObject;\n}\n\nexport interface TemplateParameter {\n key: string;\n label: string;\n type: 'text' | 'color' | 'number' | 'font';\n default: string | number;\n}\n\nexport interface TemplateDefinition {\n id: string;\n name: string;\n category: string;\n svg: string;\n parameters: TemplateParameter[];\n preview?: string;\n}\n\n// ─── Printify ────────────────────────────────────────────\n\nexport interface PrintifyPositioning {\n x: number;\n y: number;\n scale: number;\n angle: number;\n}\n\n// ─── Patterns ────────────────────────────────────────────\n\nexport type TileMode = 'grid' | 'brick-horizontal' | 'brick-vertical';\n\nexport interface PatternConfig {\n mode: TileMode;\n /** Extra horizontal gap between tiles, as % of tile width. */\n horizontalSpacing: number;\n /** Extra vertical gap between tiles, as % of tile height. */\n verticalSpacing: number;\n /** Rotation of the whole pattern, in degrees. */\n angle: number;\n /** Brick row/column shift, as % of tile size. */\n horizontalOffset: number;\n /** Added rotation per horizontal step, in degrees. */\n rotationStepH: number;\n /** Added rotation per vertical step, in degrees. */\n rotationStepV: number;\n}\n\n/** Persisted on a layer's meta while it is rendered as a repeating pattern. */\nexport interface PatternState {\n config: PatternConfig;\n /** Original (pre-pattern) image source, as a durable data URL when possible. */\n originalSrc: string;\n /** Original fabric transform, restored when the pattern is turned off. */\n original: {\n left: number;\n top: number;\n scaleX: number;\n scaleY: number;\n width: number;\n height: number;\n angle: number;\n cropX: number;\n cropY: number;\n };\n}\n\nexport const DEFAULT_PATTERN_CONFIG: PatternConfig = {\n mode: 'grid',\n horizontalSpacing: 0,\n verticalSpacing: 0,\n angle: 0,\n horizontalOffset: 0,\n rotationStepH: 0,\n rotationStepV: 0,\n};\n\n// ─── Mockup ──────────────────────────────────────────────\n\nexport interface MockupPrintArea {\n left: number;\n top: number;\n width: number;\n height: number;\n}\n\nexport interface MockupConfig {\n /** Garment/product image rendered behind the design (preview only). */\n image: string;\n /** Print-area rectangle, in canvas pixels. Drawn as a guide. */\n printArea?: MockupPrintArea;\n}\n\n// ─── Events ──────────────────────────────────────────────\n\nexport interface EditorEvents {\n 'layer:added': { layer: LayerData };\n 'layer:removed': { layerId: string };\n 'layer:selected': { layerId: string | null };\n 'layer:modified': { layerId: string };\n 'layer:reordered': { layerIds: string[] };\n 'layers:changed': { layers: LayerData[] };\n 'selection:changed': { selected: string[] };\n 'history:changed': { canUndo: boolean; canRedo: boolean };\n 'zoom:changed': { zoom: number };\n 'snap:changed': { enabled: boolean };\n 'crop:changed': { active: boolean; layerId: string | null };\n 'mockup:changed': { mockup: MockupConfig | null };\n 'canvas:modified': Record<string, never>;\n 'export:start': { format: string };\n 'export:complete': { format: string; url?: string };\n error: { message: string; error?: unknown };\n}\n"],"mappings":"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","names":["import_fabric","STROKE","import_fabric"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/editor.ts","../src/events.ts","../src/utils/id.ts","../src/layer.ts","../src/history.ts","../src/snapping.ts","../src/crop.ts","../src/utils/clamp.ts","../src/pattern.ts","../src/utils/units.ts","../src/serialization.ts","../src/export.ts","../src/types.ts"],"sourcesContent":["export { CanvasEditor } from './editor';\nexport { Layer, LayerManager } from './layer';\nexport { HistoryManager } from './history';\nexport { SnapManager } from './snapping';\nexport { CropController } from './crop';\nexport {\n PatternManager,\n buildPatternDataURL,\n drawTiles,\n computeTilePositions,\n clearPatternImageCache,\n resolvePatternPlacement,\n} from './pattern';\nexport type { TilePlacement } from './pattern';\nexport { EventEmitter } from './events';\nexport { UnitConverter } from './utils/units';\nexport { generateId } from './utils/id';\nexport { clamp, round2 } from './utils/clamp';\nexport { serializeEditor, deserializeEditor } from './serialization';\nexport { exportPNG, exportSVG, exportDataURL } from './export';\nexport { DEFAULT_PATTERN_CONFIG } from './types';\n\nexport type {\n LayerType,\n LayerData,\n LayerMeta,\n SerializedLayer,\n EditorState,\n EditorConfig,\n FileAdapter,\n ImageProvider,\n ImageProviderResult,\n ExportFormat,\n ShapePlugin,\n TemplateDefinition,\n TemplateParameter,\n PrintifyPositioning,\n TileMode,\n PatternConfig,\n PatternState,\n MockupConfig,\n MockupPrintArea,\n EditorEvents,\n Unit,\n} from './types';\n\nexport type { PngExportOptions } from './export';\n","import { Canvas, FabricImage, Textbox } from 'fabric';\nimport type { FabricObject, ImageFormat } from 'fabric';\nimport type {\n EditorConfig,\n EditorEvents,\n EditorState,\n FileAdapter,\n ImageProvider,\n ImageProviderResult,\n MockupConfig,\n PatternConfig,\n PrintifyPositioning,\n ShapePlugin,\n TemplateDefinition,\n} from './types';\nimport { EventEmitter } from './events';\nimport { Layer, LayerManager } from './layer';\nimport { HistoryManager } from './history';\nimport { SnapManager } from './snapping';\nimport { CropController } from './crop';\nimport { PatternManager } from './pattern';\nimport { UnitConverter } from './utils/units';\nimport { serializeEditor, deserializeEditor } from './serialization';\nimport { exportPNG, exportSVG, exportDataURL } from './export';\nimport type { PngExportOptions } from './export';\nimport { clamp, round2 } from './utils/clamp';\n\nconst MIN_ZOOM = 0.1;\nconst MAX_ZOOM = 8;\n\nexport class CanvasEditor {\n readonly canvas: Canvas;\n readonly layers: LayerManager;\n readonly history: HistoryManager;\n readonly events: EventEmitter<EditorEvents>;\n readonly units: UnitConverter;\n readonly snapping: SnapManager;\n readonly crop: CropController;\n readonly patterns: PatternManager;\n\n private fileAdapter?: FileAdapter;\n private imageProvider?: ImageProvider;\n private zoomLevel = 1;\n private mockup: MockupConfig | null = null;\n // The design's configured background. The live canvas background is forced\n // transparent while a mockup preview is shown, so this is the source of truth\n // for serialization and export — not the (possibly transient) canvas value.\n private designBackground: string;\n\n constructor(canvasElement: HTMLCanvasElement, config: EditorConfig) {\n this.events = new EventEmitter<EditorEvents>();\n this.units = new UnitConverter(config.unit ?? 'px', config.dpi ?? 72);\n\n const widthPx = this.units.toPixels(config.width);\n const heightPx = this.units.toPixels(config.height);\n this.designBackground = config.backgroundColor ?? '#ffffff';\n\n this.canvas = new Canvas(canvasElement, {\n width: widthPx,\n height: heightPx,\n backgroundColor: this.designBackground,\n preserveObjectStacking: config.preserveObjectStacking ?? true,\n selection: true,\n });\n\n this.layers = new LayerManager(this.canvas, this.events);\n this.fileAdapter = config.fileAdapter;\n this.imageProvider = config.imageProvider;\n\n this.history = new HistoryManager({\n getState: () => JSON.stringify(this.toJSON()),\n restoreState: async (state: string) => {\n const parsed = JSON.parse(state) as EditorState;\n await this.fromJSON(parsed);\n },\n events: this.events,\n });\n\n this.snapping = new SnapManager(this.canvas, this.events);\n this.crop = new CropController(this.canvas, this.history, this.events);\n this.patterns = new PatternManager(this.canvas, this.layers, this.history);\n\n this.setupCanvasEvents();\n this.history.saveImmediate();\n }\n\n // ─── Layer Operations ────────────────────────────────\n\n async addImage(url: string, options?: Partial<Record<string, unknown>>): Promise<Layer> {\n // Don't force crossOrigin here: a remote image whose host sends no CORS\n // header would then fail to load at all. The pattern engine requests its own\n // crossOrigin copy when it needs an untainted canvas (see pattern.ts).\n // Fabric v7 defaults originX/originY to 'center'; pin top-left so images\n // land at their left/top like the rest of the editor's coordinate model.\n const img = await FabricImage.fromURL(url, {}, { originX: 'left', originY: 'top', ...options });\n const layer = this.layers.add('image', img);\n this.history.save();\n return layer;\n }\n\n addText(text: string, options?: Partial<Record<string, unknown>>): Layer {\n const textbox = new Textbox(text, {\n fontSize: 32,\n fontFamily: 'Arial',\n fill: '#000000',\n width: 200,\n // v7 defaults origin to center; keep top-left placement.\n originX: 'left',\n originY: 'top',\n ...options,\n });\n const layer = this.layers.add('text', textbox as unknown as FabricObject);\n this.history.save();\n return layer;\n }\n\n addShape(plugin: ShapePlugin, options?: Record<string, unknown>): Layer {\n const obj = plugin.create(options);\n const layer = this.layers.add('shape', obj, plugin.name);\n this.history.save();\n return layer;\n }\n\n async addTemplate(\n template: TemplateDefinition,\n _params: Record<string, string | number>,\n ): Promise<Layer> {\n // Template loading is handled by the templates package\n // This is a placeholder for direct fabric object addition\n const { Textbox: TextboxClass } = await import('fabric');\n const placeholder = new TextboxClass(`[Template: ${template.name}]`, {\n fontSize: 24,\n fontFamily: 'Arial',\n fill: '#666666',\n width: 300,\n originX: 'left',\n originY: 'top',\n });\n const layer = this.layers.add(\n 'template',\n placeholder as unknown as FabricObject,\n template.name,\n );\n this.history.save();\n return layer;\n }\n\n removeLayer(id: string): void {\n this.layers.remove(id);\n this.history.save();\n }\n\n selectLayer(id: string | null): void {\n this.layers.select(id);\n }\n\n getSelectedLayer(): Layer | null {\n const active = this.canvas.getActiveObject();\n if (!active) return null;\n return this.layers.findByObject(active) ?? null;\n }\n\n /** Mirror a layer along the given axis. */\n flipLayer(id: string, axis: 'horizontal' | 'vertical'): void {\n const layer = this.layers.get(id);\n if (!layer) return;\n const obj = layer.fabricObject;\n if (axis === 'horizontal') {\n obj.set('flipX', !obj.flipX);\n } else {\n obj.set('flipY', !obj.flipY);\n }\n obj.setCoords();\n this.canvas.requestRenderAll();\n this.history.save();\n }\n\n /** Clone a layer (offset slightly) and select the copy. */\n async duplicateLayer(id: string): Promise<Layer | null> {\n const layer = this.layers.get(id);\n if (!layer) return null;\n\n const clone = await layer.fabricObject.clone();\n // A pattern layer's image is rasterised to cover the canvas at 0,0; offsetting\n // it would leave a gap at the edges, so only nudge non-pattern duplicates.\n if (!layer.meta.pattern) {\n clone.set({ left: (clone.left ?? 0) + 15, top: (clone.top ?? 0) + 15 });\n }\n clone.setCoords();\n\n const copy = this.layers.add(layer.type, clone, `${layer.name} copy`);\n // Carry non-fabric metadata (e.g. pattern config) onto the copy.\n copy.meta = structuredClone(layer.meta);\n\n this.canvas.setActiveObject(clone);\n this.canvas.requestRenderAll();\n this.history.save();\n return copy;\n }\n\n // ─── Serialization ──────────────────────────────────\n\n toJSON(): EditorState {\n return serializeEditor(this);\n }\n\n async fromJSON(state: EditorState): Promise<void> {\n await deserializeEditor(this, state);\n }\n\n // ─── Export ──────────────────────────────────────────\n\n async toPNG(options?: PngExportOptions): Promise<Blob> {\n this.events.emit('export:start', { format: 'png' });\n const blob = await this.withDesignBackground(() => exportPNG(this.canvas, options));\n this.events.emit('export:complete', { format: 'png' });\n return blob;\n }\n\n toSVG(): string {\n this.events.emit('export:start', { format: 'svg' });\n const svg = this.withDesignBackground(() => exportSVG(this.canvas));\n this.events.emit('export:complete', { format: 'svg' });\n return svg;\n }\n\n toDataURL(format: ImageFormat = 'png', multiplier = 1): string {\n return this.withDesignBackground(() => exportDataURL(this.canvas, format, multiplier));\n }\n\n /**\n * Run an export with the configured design background applied, even when a\n * mockup preview has forced the live canvas transparent — so exports reflect\n * the design, not the preview. Restores the preview state afterwards.\n */\n private withDesignBackground<T>(fn: () => T): T {\n if (!this.mockup) return fn();\n const previewBg = this.canvas.backgroundColor;\n this.canvas.backgroundColor = this.designBackground;\n try {\n return fn();\n } finally {\n this.canvas.backgroundColor = previewBg;\n }\n }\n\n toPrintifyPositioning(): Record<string, PrintifyPositioning> {\n const result: Record<string, PrintifyPositioning> = {};\n const canvasW = this.canvas.getWidth();\n const canvasH = this.canvas.getHeight();\n\n const imageLayers = this.layers.getAll().filter((l) => l.type === 'image');\n\n if (imageLayers.length === 0) {\n result['front'] = { x: 0.5, y: 0.5, scale: 1, angle: 0 };\n return result;\n }\n\n for (const layer of imageLayers) {\n const obj = layer.fabricObject;\n const center = obj.getCenterPoint();\n result[layer.name] = {\n x: round2(center.x / canvasW),\n y: round2(center.y / canvasH),\n scale: round2(obj.scaleX ?? 1),\n angle: round2(obj.angle ?? 0),\n };\n }\n\n return result;\n }\n\n // ─── File Operations ────────────────────────────────\n\n async save(filename: string, format: 'png' | 'svg' | 'json'): Promise<string | undefined> {\n if (!this.fileAdapter) return undefined;\n\n let data: Blob | string;\n if (format === 'png') {\n data = await this.toPNG();\n } else if (format === 'svg') {\n data = this.toSVG();\n } else {\n data = JSON.stringify(this.toJSON(), null, 2);\n }\n\n return this.fileAdapter.save(data, filename, format);\n }\n\n async uploadImage(file: File): Promise<ImageProviderResult | undefined> {\n if (!this.imageProvider) return undefined;\n return this.imageProvider.upload(file);\n }\n\n async browseImages(): Promise<ImageProviderResult | null> {\n if (!this.imageProvider?.browse) return null;\n return this.imageProvider.browse();\n }\n\n setFileAdapter(adapter: FileAdapter): void {\n this.fileAdapter = adapter;\n }\n\n setImageProvider(provider: ImageProvider): void {\n this.imageProvider = provider;\n }\n\n // ─── Canvas Operations ──────────────────────────────\n\n setBackground(color: string): void {\n this.designBackground = color;\n // Don't override the mockup's transparent preview; it will pick up the new\n // colour when the mockup is cleared.\n if (!this.mockup) {\n this.canvas.backgroundColor = color;\n this.canvas.requestRenderAll();\n }\n this.history.save();\n }\n\n getDesignBackground(): string {\n return this.designBackground;\n }\n\n resize(width: number, height: number): void {\n const widthPx = this.units.toPixels(width);\n const heightPx = this.units.toPixels(height);\n this.canvas.setDimensions({ width: widthPx, height: heightPx });\n this.canvas.requestRenderAll();\n }\n\n // ─── Zoom ───────────────────────────────────────────\n //\n // Zoom is a *display* transform (the view layer CSS-scales the canvas), not a\n // fabric viewport zoom — fabric's setZoom bakes into toDataURL/toSVG and would\n // corrupt exports. The editor only tracks the level and emits an event.\n\n setZoom(level: number): void {\n const next = clamp(round2(level), MIN_ZOOM, MAX_ZOOM);\n if (next === this.zoomLevel) return;\n this.zoomLevel = next;\n this.events.emit('zoom:changed', { zoom: next });\n }\n\n /** @deprecated use setZoom — kept for backward compatibility. */\n zoom(level: number): void {\n this.setZoom(level);\n }\n\n getZoom(): number {\n return this.zoomLevel;\n }\n\n zoomIn(step = 0.1): void {\n this.setZoom(this.zoomLevel + step);\n }\n\n zoomOut(step = 0.1): void {\n this.setZoom(this.zoomLevel - step);\n }\n\n resetZoom(): void {\n this.setZoom(1);\n }\n\n /** Fit the canvas inside a viewport (in px), accounting for padding. */\n zoomToFit(viewportWidth: number, viewportHeight: number, padding = 0): void {\n const w = this.canvas.getWidth();\n const h = this.canvas.getHeight();\n if (w <= 0 || h <= 0) return;\n const sx = (viewportWidth - padding * 2) / w;\n const sy = (viewportHeight - padding * 2) / h;\n this.setZoom(Math.min(sx, sy));\n }\n\n // ─── Patterns ───────────────────────────────────────\n\n applyPattern(layerId: string, config: PatternConfig): Promise<void> {\n return this.patterns.apply(layerId, config);\n }\n\n clearPattern(layerId: string): Promise<void> {\n return this.patterns.disable(layerId);\n }\n\n // ─── Mockup (preview-only) ──────────────────────────\n\n setMockup(mockup: MockupConfig | null): void {\n this.mockup = mockup;\n // The mockup renders behind the design, so the canvas must be transparent\n // while it is shown; restore the configured background when it is cleared.\n this.canvas.backgroundColor = mockup ? '' : this.designBackground;\n this.canvas.requestRenderAll();\n this.events.emit('mockup:changed', { mockup });\n }\n\n clearMockup(): void {\n this.setMockup(null);\n }\n\n getMockup(): MockupConfig | null {\n return this.mockup;\n }\n\n // ─── Cleanup ────────────────────────────────────────\n\n dispose(): void {\n this.snapping.dispose();\n this.crop.dispose();\n this.history.dispose();\n this.events.removeAllListeners();\n this.canvas.dispose();\n }\n\n // ─── Private ────────────────────────────────────────\n\n private setupCanvasEvents(): void {\n this.canvas.on('object:modified', (e) => {\n if (!e.target) return;\n const layer = this.layers.findByObject(e.target);\n if (layer) {\n this.events.emit('layer:modified', { layerId: layer.id });\n this.history.save();\n }\n });\n\n this.canvas.on('selection:created', (e) => {\n const selected = (e.selected ?? [])\n .map((obj) => this.layers.findByObject(obj)?.id)\n .filter((id): id is string => id !== undefined);\n this.events.emit('selection:changed', { selected });\n });\n\n this.canvas.on('selection:updated', (e) => {\n const selected = (e.selected ?? [])\n .map((obj) => this.layers.findByObject(obj)?.id)\n .filter((id): id is string => id !== undefined);\n this.events.emit('selection:changed', { selected });\n });\n\n this.canvas.on('selection:cleared', () => {\n this.events.emit('selection:changed', { selected: [] });\n });\n }\n}\n","type EventHandler<T> = (data: T) => void;\n\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport class EventEmitter<TEvents extends {} = Record<string, unknown>> {\n private listeners = new Map<keyof TEvents, Set<EventHandler<unknown>>>();\n\n on<K extends keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(handler as EventHandler<unknown>);\n }\n\n off<K extends keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): void {\n this.listeners.get(event)?.delete(handler as EventHandler<unknown>);\n }\n\n once<K extends keyof TEvents>(event: K, handler: EventHandler<TEvents[K]>): void {\n const wrapped: EventHandler<TEvents[K]> = (data) => {\n this.off(event, wrapped);\n handler(data);\n };\n this.on(event, wrapped);\n }\n\n emit<K extends keyof TEvents>(event: K, data: TEvents[K]): void {\n const handlers = this.listeners.get(event);\n if (!handlers) return;\n for (const handler of handlers) {\n handler(data);\n }\n }\n\n removeAllListeners(event?: keyof TEvents): void {\n if (event) {\n this.listeners.delete(event);\n } else {\n this.listeners.clear();\n }\n }\n}\n","import { nanoid } from 'nanoid';\n\nexport const generateId = (): string => nanoid(12);\n","import type { Canvas, FabricObject } from 'fabric';\nimport type { LayerData, LayerMeta, LayerType, SerializedLayer } from './types';\nimport type { EditorEvents } from './types';\nimport type { EventEmitter } from './events';\nimport { generateId } from './utils/id';\n\nexport class Layer {\n readonly id: string;\n readonly type: LayerType;\n name: string;\n visible: boolean;\n locked: boolean;\n opacity: number;\n /** Non-fabric data (e.g. pattern config) that must persist with the layer. */\n meta: LayerMeta;\n readonly fabricObject: FabricObject;\n\n constructor(type: LayerType, fabricObject: FabricObject, name?: string, id?: string) {\n this.id = id ?? generateId();\n this.type = type;\n this.name = name ?? `${type}-${this.id.slice(0, 4)}`;\n this.visible = true;\n this.locked = false;\n this.opacity = 1;\n this.meta = {};\n this.fabricObject = fabricObject;\n\n (this.fabricObject as FabricObject & { _layerId: string })._layerId = this.id;\n }\n\n private hasMeta(): boolean {\n return Object.keys(this.meta).length > 0;\n }\n\n toData(): LayerData {\n return {\n id: this.id,\n type: this.type,\n name: this.name,\n visible: this.visible,\n locked: this.locked,\n opacity: this.opacity,\n ...(this.hasMeta() ? { meta: this.meta } : {}),\n };\n }\n\n serialize(): SerializedLayer {\n return {\n ...this.toData(),\n fabricObject: this.fabricObject.toObject() as Record<string, unknown>,\n };\n }\n}\n\nexport class LayerManager {\n private layers: Layer[] = [];\n\n constructor(\n private canvas: Canvas,\n private events: EventEmitter<EditorEvents>,\n ) {}\n\n add(type: LayerType, fabricObject: FabricObject, name?: string, id?: string): Layer {\n const layer = new Layer(type, fabricObject, name, id);\n this.layers.push(layer);\n this.canvas.add(fabricObject);\n this.events.emit('layer:added', { layer: layer.toData() });\n this.emitChanged();\n return layer;\n }\n\n remove(id: string): boolean {\n const index = this.layers.findIndex((l) => l.id === id);\n if (index === -1) return false;\n const layer = this.layers[index];\n this.canvas.remove(layer.fabricObject);\n this.layers.splice(index, 1);\n this.events.emit('layer:removed', { layerId: id });\n this.emitChanged();\n return true;\n }\n\n reorder(id: string, newIndex: number): boolean {\n const oldIndex = this.layers.findIndex((l) => l.id === id);\n if (oldIndex === -1) return false;\n const clamped = Math.max(0, Math.min(this.layers.length - 1, newIndex));\n const [layer] = this.layers.splice(oldIndex, 1);\n this.layers.splice(clamped, 0, layer);\n // Sync fabric z-order\n this.layers.forEach((l, i) => {\n this.canvas.moveObjectTo(l.fabricObject, i);\n });\n this.events.emit('layer:reordered', { layerIds: this.layers.map((l) => l.id) });\n this.emitChanged();\n return true;\n }\n\n select(id: string | null): void {\n if (id === null) {\n this.canvas.discardActiveObject();\n } else {\n const layer = this.get(id);\n if (layer) {\n this.canvas.setActiveObject(layer.fabricObject);\n }\n }\n this.canvas.requestRenderAll();\n this.events.emit('layer:selected', { layerId: id });\n // Fabric only fires selection:* on user interaction; mirror programmatic\n // selection so React selection state (useSelection) stays in sync.\n const selected = id && this.get(id) ? [id] : [];\n this.events.emit('selection:changed', { selected });\n }\n\n get(id: string): Layer | undefined {\n return this.layers.find((l) => l.id === id);\n }\n\n getAll(): Layer[] {\n return [...this.layers];\n }\n\n findByObject(obj: FabricObject): Layer | undefined {\n const layerId = (obj as FabricObject & { _layerId?: string })._layerId;\n if (layerId) return this.get(layerId);\n return this.layers.find((l) => l.fabricObject === obj);\n }\n\n setVisibility(id: string, visible: boolean): void {\n const layer = this.get(id);\n if (!layer) return;\n layer.visible = visible;\n layer.fabricObject.visible = visible;\n this.canvas.requestRenderAll();\n this.emitChanged();\n }\n\n setLocked(id: string, locked: boolean): void {\n const layer = this.get(id);\n if (!layer) return;\n layer.locked = locked;\n layer.fabricObject.selectable = !locked;\n layer.fabricObject.evented = !locked;\n this.canvas.requestRenderAll();\n this.emitChanged();\n }\n\n setOpacity(id: string, opacity: number): void {\n const layer = this.get(id);\n if (!layer) return;\n layer.opacity = opacity;\n layer.fabricObject.opacity = opacity;\n this.canvas.requestRenderAll();\n this.emitChanged();\n }\n\n setName(id: string, name: string): void {\n const layer = this.get(id);\n if (!layer) return;\n layer.name = name;\n this.emitChanged();\n }\n\n clear(): void {\n for (const layer of this.layers) {\n this.canvas.remove(layer.fabricObject);\n }\n this.layers = [];\n this.emitChanged();\n }\n\n count(): number {\n return this.layers.length;\n }\n\n private emitChanged(): void {\n this.events.emit('layers:changed', {\n layers: this.layers.map((l) => l.toData()),\n });\n }\n}\n","import type { EditorEvents } from './types';\nimport type { EventEmitter } from './events';\n\nexport class HistoryManager {\n private undoStack: string[] = [];\n private redoStack: string[] = [];\n private maxSize: number;\n private paused = false;\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private debounceMs: number;\n private getState: () => string;\n private restoreState: (state: string) => Promise<void>;\n private events: EventEmitter<EditorEvents>;\n\n constructor(opts: {\n getState: () => string;\n restoreState: (state: string) => Promise<void>;\n events: EventEmitter<EditorEvents>;\n maxSize?: number;\n debounceMs?: number;\n }) {\n this.getState = opts.getState;\n this.restoreState = opts.restoreState;\n this.events = opts.events;\n this.maxSize = opts.maxSize ?? 50;\n this.debounceMs = opts.debounceMs ?? 300;\n }\n\n save(): void {\n if (this.paused) return;\n\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n }\n\n this.debounceTimer = setTimeout(() => {\n this.saveImmediate();\n }, this.debounceMs);\n }\n\n saveImmediate(): void {\n if (this.paused) return;\n const state = this.getState();\n this.undoStack.push(state);\n if (this.undoStack.length > this.maxSize) {\n this.undoStack.shift();\n }\n this.redoStack = [];\n this.emitChanged();\n }\n\n async undo(): Promise<void> {\n // Drop any debounced save still pending from an in-progress edit, otherwise\n // its timer would fire after restore completes and clobber the redo stack.\n this.cancelPending();\n const state = this.undoStack.pop();\n if (!state) return;\n\n this.redoStack.push(this.getState());\n this.paused = true;\n await this.restoreState(state);\n this.paused = false;\n this.emitChanged();\n }\n\n async redo(): Promise<void> {\n this.cancelPending();\n const state = this.redoStack.pop();\n if (!state) return;\n\n this.undoStack.push(this.getState());\n this.paused = true;\n await this.restoreState(state);\n this.paused = false;\n this.emitChanged();\n }\n\n /** True while a restore (undo/redo/deserialize) is in flight. Managers that\n * react to canvas events should skip mutating history during this window. */\n isRestoring(): boolean {\n return this.paused;\n }\n\n pause(): void {\n this.paused = true;\n }\n\n resume(): void {\n this.paused = false;\n }\n\n canUndo(): boolean {\n return this.undoStack.length > 0;\n }\n\n canRedo(): boolean {\n return this.redoStack.length > 0;\n }\n\n clear(): void {\n this.undoStack = [];\n this.redoStack = [];\n this.emitChanged();\n }\n\n private cancelPending(): void {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n this.debounceTimer = null;\n }\n }\n\n dispose(): void {\n if (this.debounceTimer) {\n clearTimeout(this.debounceTimer);\n }\n }\n\n private emitChanged(): void {\n this.events.emit('history:changed', {\n canUndo: this.canUndo(),\n canRedo: this.canRedo(),\n });\n }\n}\n","import type { Canvas, FabricObject, TPointerEvent } from 'fabric';\nimport type { EventEmitter } from './events';\nimport type { EditorEvents } from './types';\n\ninterface SnapGuide {\n /** Vertical guide at this x (canvas px), or undefined for a horizontal one. */\n x?: number;\n y?: number;\n}\n\nconst STROKE = '#22c55e';\n\n/**\n * Snaps a dragged object's edges and centre to the canvas edges/centre and to\n * other objects, drawing transient guide lines while it does. Switchable via\n * {@link setEnabled}; inert (zero overhead beyond a flag check) when disabled.\n */\nexport class SnapManager {\n private enabled = false;\n private threshold = 8;\n private guides: SnapGuide[] = [];\n\n private readonly onMoving: (e: { target?: FabricObject; e: TPointerEvent }) => void;\n private readonly onAfterRender: () => void;\n private readonly onModified: () => void;\n\n constructor(\n private canvas: Canvas,\n private events: EventEmitter<EditorEvents>,\n ) {\n this.onMoving = (e) => this.handleMoving(e.target);\n this.onAfterRender = () => this.drawGuides();\n this.onModified = () => this.clearGuides();\n\n this.canvas.on('object:moving', this.onMoving);\n this.canvas.on('after:render', this.onAfterRender);\n this.canvas.on('mouse:up', this.onModified);\n this.canvas.on('selection:cleared', this.onModified);\n }\n\n setEnabled(enabled: boolean): void {\n if (this.enabled === enabled) return;\n this.enabled = enabled;\n if (!enabled) this.clearGuides();\n this.events.emit('snap:changed', { enabled });\n }\n\n isEnabled(): boolean {\n return this.enabled;\n }\n\n setThreshold(px: number): void {\n this.threshold = Math.max(0, px);\n }\n\n dispose(): void {\n this.canvas.off('object:moving', this.onMoving);\n this.canvas.off('after:render', this.onAfterRender);\n this.canvas.off('mouse:up', this.onModified);\n this.canvas.off('selection:cleared', this.onModified);\n this.guides = [];\n }\n\n private handleMoving(target?: FabricObject): void {\n if (!this.enabled || !target) return;\n\n target.setCoords();\n const r = target.getBoundingRect();\n const cw = this.canvas.getWidth();\n const ch = this.canvas.getHeight();\n\n // Candidate lines for both axes, collected in a single pass over the objects.\n const { xs, ys } = collectCandidates(this.canvas, target, cw, ch);\n\n this.guides = [];\n\n const dx = bestSnap([r.left, r.left + r.width / 2, r.left + r.width], xs, this.threshold);\n if (dx !== null) {\n target.set('left', (target.left ?? 0) + dx.delta);\n this.guides.push({ x: dx.line });\n }\n\n const dy = bestSnap([r.top, r.top + r.height / 2, r.top + r.height], ys, this.threshold);\n if (dy !== null) {\n target.set('top', (target.top ?? 0) + dy.delta);\n this.guides.push({ y: dy.line });\n }\n\n target.setCoords();\n }\n\n private drawGuides(): void {\n if (!this.enabled || this.guides.length === 0) return;\n const ctx = this.canvas.contextTop;\n if (!ctx) return;\n\n const retina = this.canvas.getRetinaScaling();\n const cw = this.canvas.getWidth();\n const ch = this.canvas.getHeight();\n\n ctx.save();\n ctx.setTransform(retina, 0, 0, retina, 0, 0);\n ctx.lineWidth = 1;\n ctx.strokeStyle = STROKE;\n ctx.setLineDash([4, 4]);\n ctx.beginPath();\n for (const g of this.guides) {\n if (g.x !== undefined) {\n ctx.moveTo(g.x, 0);\n ctx.lineTo(g.x, ch);\n }\n if (g.y !== undefined) {\n ctx.moveTo(0, g.y);\n ctx.lineTo(cw, g.y);\n }\n }\n ctx.stroke();\n ctx.restore();\n }\n\n private clearGuides(): void {\n if (this.guides.length === 0) return;\n this.guides = [];\n this.canvas.clearContext(this.canvas.contextTop);\n }\n}\n\n/**\n * Snap candidate lines (edges + centre) for the canvas and every other object,\n * for both axes, computed in one pass so each object's bounds are read once.\n */\nfunction collectCandidates(\n canvas: Canvas,\n moving: FabricObject,\n cw: number,\n ch: number,\n): { xs: number[]; ys: number[] } {\n const xs = [0, cw / 2, cw];\n const ys = [0, ch / 2, ch];\n for (const obj of canvas.getObjects()) {\n if (obj === moving || !obj.visible) continue;\n obj.setCoords();\n const r = obj.getBoundingRect();\n xs.push(r.left, r.left + r.width / 2, r.left + r.width);\n ys.push(r.top, r.top + r.height / 2, r.top + r.height);\n }\n return { xs, ys };\n}\n\n/**\n * Find the closest (edge, candidate) pair within threshold. Returns the delta\n * to apply and the guide line to draw, or null if nothing is close enough.\n */\nfunction bestSnap(\n edges: number[],\n candidates: number[],\n threshold: number,\n): { delta: number; line: number } | null {\n let best: { delta: number; line: number; dist: number } | null = null;\n for (const edge of edges) {\n for (const c of candidates) {\n const dist = Math.abs(edge - c);\n if (dist <= threshold && (best === null || dist < best.dist)) {\n best = { delta: c - edge, line: c, dist };\n }\n }\n }\n return best ? { delta: best.delta, line: best.line } : null;\n}\n\nexport { bestSnap as _bestSnap };\n","import { Rect } from 'fabric';\nimport type { Canvas, FabricImage } from 'fabric';\nimport type { Layer } from './layer';\nimport type { HistoryManager } from './history';\nimport type { EventEmitter } from './events';\nimport type { EditorEvents } from './types';\nimport { clamp } from './utils/clamp';\n\ninterface CropSession {\n layerId: string;\n image: FabricImage;\n rect: Rect;\n prevSelectable: boolean;\n prevEvented: boolean;\n /** Angle before crop normalised it to 0; restored if the crop is cancelled. */\n prevAngle: number;\n}\n\n/**\n * Interactive crop for image layers. {@link start} drops a draggable/resizable\n * rectangle over the image; {@link apply} converts it into fabric crop props\n * (cropX/cropY/width/height in element pixels) and keeps the displayed size in\n * sync with the rectangle.\n *\n * Rotation is unsupported: an angled image is normalised to 0° on start.\n */\nexport class CropController {\n private session: CropSession | null = null;\n private readonly onSelectionCleared: () => void;\n\n constructor(\n private canvas: Canvas,\n private history: HistoryManager,\n private events: EventEmitter<EditorEvents>,\n ) {\n this.onSelectionCleared = () => {\n // Clicking away from the crop rect abandons the crop.\n if (this.session) this.cancel();\n };\n this.canvas.on('selection:cleared', this.onSelectionCleared);\n }\n\n isActive(): boolean {\n return this.session !== null;\n }\n\n activeLayerId(): string | null {\n return this.session?.layerId ?? null;\n }\n\n start(layer: Layer): void {\n if (layer.type !== 'image') return;\n if (this.session) this.cancel();\n\n const image = layer.fabricObject as FabricImage;\n const prevAngle = image.angle ?? 0;\n if (prevAngle) image.rotate(0);\n image.setCoords();\n const b = image.getBoundingRect();\n\n const rect = new Rect({\n left: b.left,\n top: b.top,\n width: b.width,\n height: b.height,\n originX: 'left',\n originY: 'top',\n fill: 'rgba(34,197,94,0.12)',\n stroke: STROKE,\n strokeWidth: 1,\n strokeDashArray: [4, 4],\n strokeUniform: true,\n lockRotation: true,\n cornerColor: STROKE,\n transparentCorners: false,\n });\n rect.setControlsVisibility({ mtr: false });\n\n const prevSelectable = image.selectable ?? true;\n const prevEvented = image.evented ?? true;\n image.selectable = false;\n image.evented = false;\n\n this.canvas.add(rect);\n this.canvas.setActiveObject(rect);\n this.canvas.requestRenderAll();\n\n this.session = { layerId: layer.id, image, rect, prevSelectable, prevEvented, prevAngle };\n this.events.emit('crop:changed', { active: true, layerId: layer.id });\n }\n\n apply(): void {\n const s = this.session;\n if (!s) return;\n const { image, rect } = s;\n\n rect.setCoords();\n image.setCoords();\n const scaleX = image.scaleX ?? 1;\n const scaleY = image.scaleY ?? 1;\n const imgLeft = image.left ?? 0;\n const imgTop = image.top ?? 0;\n\n const el = image.getElement() as HTMLImageElement;\n const naturalW = el.naturalWidth || image.width || 0;\n const naturalH = el.naturalHeight || image.height || 0;\n\n // Map the rect (canvas px) into the image's element-pixel space.\n let cropX = (image.cropX ?? 0) + (rect.left! - imgLeft) / scaleX;\n let cropY = (image.cropY ?? 0) + (rect.top! - imgTop) / scaleY;\n let cropW = rect.getScaledWidth() / scaleX;\n let cropH = rect.getScaledHeight() / scaleY;\n\n // Keep the crop window inside the source element.\n cropX = clamp(cropX, 0, Math.max(0, naturalW - 1));\n cropY = clamp(cropY, 0, Math.max(0, naturalH - 1));\n cropW = clamp(cropW, 1, naturalW - cropX);\n cropH = clamp(cropH, 1, naturalH - cropY);\n\n image.set({\n cropX,\n cropY,\n width: cropW,\n height: cropH,\n // Anchor the displayed result where the rect sits.\n left: imgLeft + (cropX - (image.cropX ?? 0)) * scaleX,\n top: imgTop + (cropY - (image.cropY ?? 0)) * scaleY,\n });\n image.setCoords();\n\n this.finish();\n this.history.save();\n }\n\n cancel(): void {\n const s = this.session;\n if (!s) return;\n // Cancelling discards the crop, so restore the rotation start() zeroed out.\n if (s.prevAngle) {\n s.image.rotate(s.prevAngle);\n s.image.setCoords();\n }\n this.finish();\n }\n\n dispose(): void {\n this.cancel();\n this.canvas.off('selection:cleared', this.onSelectionCleared);\n }\n\n private finish(): void {\n const s = this.session;\n if (!s) return;\n // Null the session first so the selection:cleared fired by removing the\n // rect / re-selecting does not re-enter cancel().\n this.session = null;\n\n this.canvas.remove(s.rect);\n s.image.selectable = s.prevSelectable;\n s.image.evented = s.prevEvented;\n this.canvas.setActiveObject(s.image);\n this.canvas.requestRenderAll();\n\n this.events.emit('crop:changed', { active: false, layerId: null });\n }\n}\n\nconst STROKE = '#22c55e';\n","export function clamp(v: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, v));\n}\n\nexport function round2(v: number): number {\n return Math.round(v * 100) / 100;\n}\n","import { FabricImage, util } from 'fabric';\nimport type { Canvas, FabricObject } from 'fabric';\nimport type { Layer, LayerManager } from './layer';\nimport type { HistoryManager } from './history';\nimport type { PatternConfig } from './types';\n\n/**\n * Upper bound on tiles per axis. Floors the effective tile size so an extreme\n * `scale` (or a tiny source) can't make the draw loop cover the diagonal with a\n * runaway number of tiles.\n */\nconst MAX_TILES_PER_AXIS = 200;\n\n/**\n * Renders a single image layer as a repeating pattern that fills the canvas.\n *\n * The tiled result is rasterised onto an offscreen canvas and swapped in as the\n * layer's image source, so it survives export, serialization and undo/redo with\n * no extra machinery. The original source + transform are kept in `layer.meta`\n * so the pattern can be turned back off.\n */\nexport class PatternManager {\n // Per-layer task chain. apply()/disable() both await an async setSrc on the\n // same fabric image; running two concurrently lets their setSrc resolutions\n // interleave (wrong image installed, original lost). Serialising per layer\n // guarantees the last-requested operation wins and state stays consistent.\n private chains = new Map<string, Promise<unknown>>();\n\n constructor(\n private canvas: Canvas,\n private layers: LayerManager,\n private history: HistoryManager,\n ) {}\n\n isPattern(layerId: string): boolean {\n return !!this.layers.get(layerId)?.meta.pattern;\n }\n\n getConfig(layerId: string): PatternConfig | null {\n return this.layers.get(layerId)?.meta.pattern?.config ?? null;\n }\n\n /** Turn a plain image layer into a pattern, or update an existing one. */\n apply(layerId: string, config: PatternConfig): Promise<void> {\n return this.enqueue(layerId, async () => {\n const layer = this.layers.get(layerId);\n if (!layer || layer.type !== 'image') return;\n const image = layer.fabricObject as FabricImage;\n\n const firstEnable = !layer.meta.pattern;\n // Check the field directly (not `firstEnable`) so TS narrows the else.\n if (!layer.meta.pattern) {\n // Captured at run time (not enqueue time) so a queued apply always reads\n // the genuine pre-pattern image, never a half-applied one.\n const clip = image.clipPath as FabricObject | undefined;\n layer.meta.pattern = {\n config,\n originalSrc: elementToDataURL(image) ?? image.getSrc(),\n originalClip: clip ? (clip.toObject() as Record<string, unknown>) : null,\n original: {\n left: image.left ?? 0,\n top: image.top ?? 0,\n scaleX: image.scaleX ?? 1,\n scaleY: image.scaleY ?? 1,\n width: image.width ?? 0,\n height: image.height ?? 0,\n angle: image.angle ?? 0,\n cropX: image.cropX ?? 0,\n cropY: image.cropY ?? 0,\n },\n };\n } else {\n layer.meta.pattern.config = config;\n }\n\n try {\n // Only the first enable pins the pattern to the full print area; later\n // config updates preserve wherever the user has moved/rotated it.\n await this.renderLayer(layer, firstEnable);\n } catch (err) {\n // Roll back a freshly-captured state so a failed first render doesn't\n // leave the layer flagged as a pattern with the original still showing.\n if (firstEnable) delete layer.meta.pattern;\n throw err;\n }\n this.history.save();\n });\n }\n\n /** Restore the original image and drop the pattern. */\n disable(layerId: string): Promise<void> {\n return this.enqueue(layerId, async () => {\n const layer = this.layers.get(layerId);\n const state = layer?.meta.pattern;\n if (!layer || !state) return;\n const image = layer.fabricObject as FabricImage;\n\n await image.setSrc(state.originalSrc);\n // setSrc resets width/height to the full element size, so restore the\n // captured crop window too — otherwise an image that was cropped before\n // being turned into a pattern loses its crop on disable.\n image.set({\n left: state.original.left,\n top: state.original.top,\n scaleX: state.original.scaleX,\n scaleY: state.original.scaleY,\n width: state.original.width,\n height: state.original.height,\n cropX: state.original.cropX,\n cropY: state.original.cropY,\n angle: state.original.angle,\n });\n // Re-apply the clipPath the image had before it became a pattern (stripped\n // in renderLayer so the pattern could fill the whole print area).\n image.clipPath = state.originalClip\n ? ((await util.enlivenObjects([state.originalClip]))[0] as FabricObject)\n : undefined;\n image.setCoords();\n delete layer.meta.pattern;\n this.canvas.requestRenderAll();\n this.history.save();\n });\n }\n\n /** Run `task` after any in-flight work for this layer, regardless of outcome. */\n private enqueue(layerId: string, task: () => Promise<void>): Promise<void> {\n const prev = this.chains.get(layerId) ?? Promise.resolve();\n const next = prev.then(task, task);\n // Keep the chain alive on rejection so later ops still run.\n this.chains.set(\n layerId,\n next.catch(() => undefined),\n );\n return next;\n }\n\n private async renderLayer(layer: Layer, pin: boolean): Promise<void> {\n const state = layer.meta.pattern;\n if (!state) return;\n const image = layer.fabricObject as FabricImage;\n\n const cw = this.canvas.getWidth();\n const ch = this.canvas.getHeight();\n\n const { left, top, angle } = resolvePatternPlacement(pin, image);\n\n // Base tile = source's on-canvas size × the user's scale. A guard floors the\n // tile so an extreme scale can't spawn a pathological tile count (the draw\n // loop is O(tiles); computeTilePositions covers the diagonal).\n const scale = Math.max(1, state.config.scale ?? 100) / 100;\n const diag = Math.sqrt(cw * cw + ch * ch);\n const minTile = Math.max(2, diag / MAX_TILES_PER_AXIS);\n const tileW = Math.max(minTile, state.original.width * state.original.scaleX * scale);\n const tileH = Math.max(minTile, state.original.height * state.original.scaleY * scale);\n\n const dataUrl = await buildPatternDataURL(\n state.originalSrc,\n state.config,\n cw,\n ch,\n tileW,\n tileH,\n );\n\n await image.setSrc(dataUrl);\n // The tiled source is exactly cw×ch and drawn at 1:1 (scaleX/scaleY=1), so on\n // first enable (pin) it fills the whole print area from the origin.\n // `width`/`height` are set explicitly (not just via setSrc) and any\n // pre-pattern clipPath is stripped so a leftover crop sized to the original\n // image box can't confine the pattern to that box. cropX/cropY are reset for\n // the same reason. `left`/`top`/`angle` are preserved on re-render (see above).\n image.set({\n left,\n top,\n scaleX: 1,\n scaleY: 1,\n width: cw,\n height: ch,\n cropX: 0,\n cropY: 0,\n angle,\n });\n image.clipPath = undefined;\n image.setCoords();\n this.canvas.requestRenderAll();\n }\n}\n\n/**\n * Object placement for a pattern re-render.\n *\n * The FIRST enable pins the pattern to the print-area origin (0,0, unrotated) so\n * it fills the whole area. A later config update (scale/angle/spacing slider)\n * preserves the object's current placement — otherwise tweaking a slider would\n * snap a pattern the user had dragged/rotated back to the top-left corner.\n *\n * Exported for unit testing.\n */\nexport function resolvePatternPlacement(\n pin: boolean,\n current: { left?: number; top?: number; angle?: number },\n): { left: number; top: number; angle: number } {\n if (pin) return { left: 0, top: 0, angle: 0 };\n return {\n left: current.left ?? 0,\n top: current.top ?? 0,\n angle: current.angle ?? 0,\n };\n}\n\n/** Snapshot an already-loaded image element to a PNG data URL. */\nfunction elementToDataURL(image: FabricImage): string | null {\n try {\n const el = image.getElement() as HTMLImageElement | HTMLCanvasElement;\n const w = (el as HTMLImageElement).naturalWidth || el.width;\n const h = (el as HTMLImageElement).naturalHeight || el.height;\n if (!w || !h) return null;\n const off = document.createElement('canvas');\n off.width = w;\n off.height = h;\n const ctx = off.getContext('2d');\n if (!ctx) return null;\n ctx.drawImage(el, 0, 0);\n return off.toDataURL('image/png');\n } catch {\n // Tainted canvas (remote image without CORS) — fall back to the live src.\n return null;\n }\n}\n\n// Decoding a source (often a large data URL) is the dominant cost of a regen,\n// so cache decoded images. Bounded with simple LRU eviction so a stream of\n// distinct sources can't grow the cache without limit.\nconst IMAGE_CACHE_MAX = 16;\nconst imageCache = new Map<string, Promise<HTMLImageElement>>();\n\nfunction loadImage(src: string): Promise<HTMLImageElement> {\n const cached = imageCache.get(src);\n if (cached) {\n // Refresh recency (Map preserves insertion order → re-insert = most recent).\n imageCache.delete(src);\n imageCache.set(src, cached);\n return cached;\n }\n\n const promise = decodeImage(src);\n // Don't poison the cache with a failed decode.\n promise.catch(() => {\n if (imageCache.get(src) === promise) imageCache.delete(src);\n });\n\n imageCache.set(src, promise);\n if (imageCache.size > IMAGE_CACHE_MAX) {\n const oldest = imageCache.keys().next().value;\n if (oldest !== undefined) imageCache.delete(oldest);\n }\n return promise;\n}\n\nfunction decodeImage(src: string): Promise<HTMLImageElement> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => resolve(img);\n img.onerror = () => reject(new Error(`Failed to load pattern source: ${src}`));\n img.src = src;\n });\n}\n\n/** Clear the decoded-image cache (e.g. on teardown). Exported for completeness. */\nexport function clearPatternImageCache(): void {\n imageCache.clear();\n}\n\n/**\n * Draw the source image repeatedly across a `targetW × targetH` offscreen\n * canvas and return the result as a PNG data URL.\n *\n * Exported for unit testing of the tile geometry.\n */\nexport async function buildPatternDataURL(\n src: string,\n config: PatternConfig,\n targetW: number,\n targetH: number,\n baseW: number,\n baseH: number,\n): Promise<string> {\n const img = await loadImage(src);\n const off = document.createElement('canvas');\n off.width = Math.max(1, Math.round(targetW));\n off.height = Math.max(1, Math.round(targetH));\n const ctx = off.getContext('2d');\n if (!ctx) return off.toDataURL('image/png');\n\n drawTiles(ctx, img, config, targetW, targetH, baseW, baseH);\n return off.toDataURL('image/png');\n}\n\n/** Placement of one tile, relative to the (already rotated) pattern centre. */\nexport interface TilePlacement {\n /** Tile-centre offset from the pattern centre, in px. */\n x: number;\n y: number;\n /** Per-tile rotation, in radians. */\n rotation: number;\n}\n\n/**\n * Pure tile geometry: where each tile centre lands and how much it is rotated,\n * for a config + target/base size. Covers the rotated bounding diagonal so\n * corners stay filled at any pattern angle. Extracted for unit testing.\n */\nexport function computeTilePositions(\n config: PatternConfig,\n targetW: number,\n targetH: number,\n baseW: number,\n baseH: number,\n): TilePlacement[] {\n const diag = Math.sqrt(targetW * targetW + targetH * targetH);\n // Floor the spacing-adjusted tile too (not just the base in renderLayer): a\n // negative spacing from a hand-crafted config would otherwise collapse the\n // loop tile to ~1px and spawn ~diag² draw calls (client-side DoS).\n const minTile = Math.max(1, diag / MAX_TILES_PER_AXIS);\n const tileW = Math.max(minTile, baseW * (1 + config.horizontalSpacing / 100));\n const tileH = Math.max(minTile, baseH * (1 + config.verticalSpacing / 100));\n\n const cols = Math.ceil(diag / tileW) + 2;\n const rows = Math.ceil(diag / tileH) + 2;\n const halfCols = Math.ceil(cols / 2);\n const halfRows = Math.ceil(rows / 2);\n\n const placements: TilePlacement[] = [];\n for (let j = -halfRows; j <= halfRows; j++) {\n for (let i = -halfCols; i <= halfCols; i++) {\n let x = i * tileW;\n let y = j * tileH;\n\n if (config.mode === 'brick-horizontal' && mod2(j) === 1) {\n x += tileW * (config.horizontalOffset / 100);\n } else if (config.mode === 'brick-vertical' && mod2(i) === 1) {\n y += tileH * (config.horizontalOffset / 100);\n }\n\n const rotation = ((i * config.rotationStepH + j * config.rotationStepV) * Math.PI) / 180;\n placements.push({ x, y, rotation });\n }\n }\n return placements;\n}\n\n/**\n * Draw the tiled pattern onto a 2D context. The geometry lives in\n * {@link computeTilePositions}; this just plays it out as canvas calls.\n */\nexport function drawTiles(\n ctx: CanvasRenderingProtocol,\n img: CanvasImageSource,\n config: PatternConfig,\n targetW: number,\n targetH: number,\n baseW: number,\n baseH: number,\n): void {\n ctx.save();\n // Rotate the whole pattern around the canvas centre.\n ctx.translate(targetW / 2, targetH / 2);\n ctx.rotate((config.angle * Math.PI) / 180);\n\n for (const tile of computeTilePositions(config, targetW, targetH, baseW, baseH)) {\n ctx.save();\n ctx.translate(tile.x, tile.y);\n ctx.rotate(tile.rotation);\n ctx.drawImage(img, -baseW / 2, -baseH / 2, baseW, baseH);\n ctx.restore();\n }\n\n ctx.restore();\n}\n\n/** Non-negative parity (so negative row indices brick-shift consistently). */\nfunction mod2(n: number): number {\n return ((n % 2) + 2) % 2;\n}\n\n/** Minimal 2D-context surface used by {@link drawTiles}; lets tests inject a spy. */\nexport interface CanvasRenderingProtocol {\n save(): void;\n restore(): void;\n translate(x: number, y: number): void;\n rotate(angle: number): void;\n drawImage(image: CanvasImageSource, dx: number, dy: number, dw: number, dh: number): void;\n}\n","import type { Unit } from '../types';\n\nconst MM_PER_INCH = 25.4;\n\nexport class UnitConverter {\n constructor(\n private unit: Unit = 'px',\n private dpi: number = 72,\n ) {}\n\n setUnit(unit: Unit): void {\n this.unit = unit;\n }\n\n setDpi(dpi: number): void {\n this.dpi = dpi;\n }\n\n getUnit(): Unit {\n return this.unit;\n }\n\n getDpi(): number {\n return this.dpi;\n }\n\n toPixels(value: number, fromUnit?: Unit): number {\n const u = fromUnit ?? this.unit;\n switch (u) {\n case 'px':\n return value;\n case 'in':\n return value * this.dpi;\n case 'mm':\n return (value / MM_PER_INCH) * this.dpi;\n }\n }\n\n fromPixels(px: number, toUnit?: Unit): number {\n const u = toUnit ?? this.unit;\n switch (u) {\n case 'px':\n return px;\n case 'in':\n return px / this.dpi;\n case 'mm':\n return (px / this.dpi) * MM_PER_INCH;\n }\n }\n}\n","import { util } from 'fabric';\nimport type { FabricObject } from 'fabric';\nimport type { EditorState, SerializedLayer } from './types';\nimport type { CanvasEditor } from './editor';\nimport { Layer } from './layer';\n\nconst VERSION = '1.0.0';\n\nexport function serializeEditor(editor: CanvasEditor): EditorState {\n return {\n version: VERSION,\n canvas: {\n width: editor.canvas.getWidth(),\n height: editor.canvas.getHeight(),\n },\n layers: editor.layers.getAll().map((layer) => layer.serialize()),\n // The configured design background, not the live canvas value (which is\n // forced transparent while a mockup preview is active).\n background: editor.getDesignBackground(),\n mockup: editor.getMockup(),\n };\n}\n\nexport async function deserializeEditor(editor: CanvasEditor, state: EditorState): Promise<void> {\n editor.layers.clear();\n editor.canvas.setDimensions({ width: state.canvas.width, height: state.canvas.height });\n\n // setBackground records the design background; history is paused during\n // restore so this does not push a spurious entry. setMockup (below) then\n // applies the correct live canvas background.\n if (state.background) {\n editor.setBackground(state.background);\n }\n\n // mockup is preview-only (rendered behind the canvas), so it is restored\n // separately from the design layers. `undefined` means \"leave as-is\".\n if (state.mockup !== undefined) {\n editor.setMockup(state.mockup);\n }\n\n for (const serializedLayer of state.layers) {\n await restoreLayer(editor, serializedLayer);\n }\n\n editor.canvas.requestRenderAll();\n}\n\nasync function restoreLayer(editor: CanvasEditor, serialized: SerializedLayer): Promise<Layer> {\n const objects = await util.enlivenObjects([serialized.fabricObject]);\n const fabricObject = objects[0] as FabricObject;\n\n const layer = editor.layers.add(serialized.type, fabricObject, serialized.name, serialized.id);\n\n if (serialized.meta) {\n layer.meta = serialized.meta;\n }\n\n if (!serialized.visible) {\n editor.layers.setVisibility(layer.id, false);\n }\n if (serialized.locked) {\n editor.layers.setLocked(layer.id, true);\n }\n if (serialized.opacity !== 1) {\n editor.layers.setOpacity(layer.id, serialized.opacity);\n }\n\n return layer;\n}\n","import type { Canvas, ImageFormat } from 'fabric';\n\nexport interface PngExportOptions {\n multiplier?: number;\n format?: ImageFormat;\n quality?: number;\n}\n\nexport async function exportPNG(\n canvas: Canvas,\n options: PngExportOptions = {},\n): Promise<Blob> {\n const { multiplier = 1, format = 'png' as ImageFormat, quality = 1 } = options;\n const dataUrl = canvas.toDataURL({\n format,\n multiplier,\n quality,\n });\n const response = await fetch(dataUrl);\n return response.blob();\n}\n\nexport function exportSVG(canvas: Canvas): string {\n return canvas.toSVG();\n}\n\nexport function exportDataURL(\n canvas: Canvas,\n format: ImageFormat = 'png',\n multiplier = 1,\n): string {\n return canvas.toDataURL({ format, multiplier });\n}\n","import type { FabricObject } from 'fabric';\n\n// ─── Units ───────────────────────────────────────────────\n\nexport type Unit = 'px' | 'mm' | 'in';\n\n// ─── Layers ──────────────────────────────────────────────\n\nexport type LayerType = 'image' | 'text' | 'shape' | 'template' | 'group';\n\nexport interface LayerData {\n id: string;\n type: LayerType;\n name: string;\n visible: boolean;\n locked: boolean;\n opacity: number;\n /** Non-fabric metadata persisted with the layer (pattern config, etc). */\n meta?: LayerMeta;\n}\n\nexport interface LayerMeta {\n pattern?: PatternState;\n [key: string]: unknown;\n}\n\nexport interface SerializedLayer extends LayerData {\n fabricObject: Record<string, unknown>;\n}\n\n// ─── Editor ──────────────────────────────────────────────\n\nexport interface EditorState {\n version: string;\n canvas: { width: number; height: number };\n layers: SerializedLayer[];\n background?: string;\n mockup?: MockupConfig | null;\n}\n\nexport interface EditorConfig {\n width: number;\n height: number;\n backgroundColor?: string;\n fileAdapter?: FileAdapter;\n imageProvider?: ImageProvider;\n preserveObjectStacking?: boolean;\n unit?: Unit;\n dpi?: number;\n}\n\n// ─── Adapters ────────────────────────────────────────────\n\nexport type ExportFormat = 'png' | 'svg' | 'json';\n\nexport interface FileAdapter {\n save(data: Blob | string, filename: string, format: ExportFormat): Promise<string>;\n}\n\nexport interface ImageProviderResult {\n url: string;\n width: number;\n height: number;\n}\n\nexport interface ImageProvider {\n upload(file: File): Promise<ImageProviderResult>;\n browse?(): Promise<ImageProviderResult | null>;\n}\n\n// ─── Plugins ─────────────────────────────────────────────\n\nexport interface ShapePlugin {\n name: string;\n icon: string;\n category?: string;\n create(options?: Record<string, unknown>): FabricObject;\n}\n\nexport interface TemplateParameter {\n key: string;\n label: string;\n type: 'text' | 'color' | 'number' | 'font';\n default: string | number;\n}\n\nexport interface TemplateDefinition {\n id: string;\n name: string;\n category: string;\n svg: string;\n parameters: TemplateParameter[];\n preview?: string;\n}\n\n// ─── Printify ────────────────────────────────────────────\n\nexport interface PrintifyPositioning {\n x: number;\n y: number;\n scale: number;\n angle: number;\n}\n\n// ─── Patterns ────────────────────────────────────────────\n\nexport type TileMode = 'grid' | 'brick-horizontal' | 'brick-vertical';\n\nexport interface PatternConfig {\n mode: TileMode;\n /**\n * Tile size as a % of the source image's on-canvas size (100 = natural).\n * Smaller values pack more repeats across the print area; larger values make\n * fewer, bigger tiles. Independent of how the source layer was placed.\n */\n scale: number;\n /** Extra horizontal gap between tiles, as % of tile width. */\n horizontalSpacing: number;\n /** Extra vertical gap between tiles, as % of tile height. */\n verticalSpacing: number;\n /** Rotation of the whole pattern, in degrees. */\n angle: number;\n /** Brick row/column shift, as % of tile size. */\n horizontalOffset: number;\n /** Added rotation per horizontal step, in degrees. */\n rotationStepH: number;\n /** Added rotation per vertical step, in degrees. */\n rotationStepV: number;\n}\n\n/** Persisted on a layer's meta while it is rendered as a repeating pattern. */\nexport interface PatternState {\n config: PatternConfig;\n /** Original (pre-pattern) image source, as a durable data URL when possible. */\n originalSrc: string;\n /**\n * Serialized `clipPath` the image carried before it became a pattern, or null\n * if it had none. A pattern must fill the whole print area, so any pre-pattern\n * clip (e.g. a template crop sized to the original box) is stripped while the\n * pattern is on and re-applied when it is turned off. Persisted so it survives\n * serialization / reload / undo.\n */\n originalClip?: Record<string, unknown> | null;\n /** Original fabric transform, restored when the pattern is turned off. */\n original: {\n left: number;\n top: number;\n scaleX: number;\n scaleY: number;\n width: number;\n height: number;\n angle: number;\n cropX: number;\n cropY: number;\n };\n}\n\nexport const DEFAULT_PATTERN_CONFIG: PatternConfig = {\n mode: 'grid',\n scale: 100,\n horizontalSpacing: 0,\n verticalSpacing: 0,\n angle: 0,\n horizontalOffset: 0,\n rotationStepH: 0,\n rotationStepV: 0,\n};\n\n// ─── Mockup ──────────────────────────────────────────────\n\nexport interface MockupPrintArea {\n left: number;\n top: number;\n width: number;\n height: number;\n}\n\nexport interface MockupConfig {\n /** Garment/product image rendered behind the design (preview only). */\n image: string;\n /** Print-area rectangle, in canvas pixels. Drawn as a guide. */\n printArea?: MockupPrintArea;\n}\n\n// ─── Events ──────────────────────────────────────────────\n\nexport interface EditorEvents {\n 'layer:added': { layer: LayerData };\n 'layer:removed': { layerId: string };\n 'layer:selected': { layerId: string | null };\n 'layer:modified': { layerId: string };\n 'layer:reordered': { layerIds: string[] };\n 'layers:changed': { layers: LayerData[] };\n 'selection:changed': { selected: string[] };\n 'history:changed': { canUndo: boolean; canRedo: boolean };\n 'zoom:changed': { zoom: number };\n 'snap:changed': { enabled: boolean };\n 'crop:changed': { active: boolean; layerId: string | null };\n 'mockup:changed': { mockup: MockupConfig | null };\n 'canvas:modified': Record<string, never>;\n 'export:start': { format: string };\n 'export:complete': { format: string; url?: string };\n error: { message: string; error?: unknown };\n}\n"],"mappings":"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","names":["import_fabric","STROKE","import_fabric","import_fabric"]}
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/editor.ts
2
- import { Canvas, FabricImage, Textbox } from "fabric";
2
+ import { Canvas, FabricImage as FabricImage2, Textbox } from "fabric";
3
3
 
4
4
  // src/events.ts
5
5
  var EventEmitter = class {
@@ -541,6 +541,8 @@ var CropController = class {
541
541
  var STROKE2 = "#22c55e";
542
542
 
543
543
  // src/pattern.ts
544
+ import { util } from "fabric";
545
+ var MAX_TILES_PER_AXIS = 200;
544
546
  var PatternManager = class {
545
547
  constructor(canvas, layers, history) {
546
548
  this.canvas = canvas;
@@ -569,9 +571,11 @@ var PatternManager = class {
569
571
  const image = layer.fabricObject;
570
572
  const firstEnable = !layer.meta.pattern;
571
573
  if (!layer.meta.pattern) {
574
+ const clip = image.clipPath;
572
575
  layer.meta.pattern = {
573
576
  config,
574
577
  originalSrc: elementToDataURL(image) ?? image.getSrc(),
578
+ originalClip: clip ? clip.toObject() : null,
575
579
  original: {
576
580
  left: image.left ?? 0,
577
581
  top: image.top ?? 0,
@@ -588,7 +592,7 @@ var PatternManager = class {
588
592
  layer.meta.pattern.config = config;
589
593
  }
590
594
  try {
591
- await this.renderLayer(layer);
595
+ await this.renderLayer(layer, firstEnable);
592
596
  } catch (err) {
593
597
  if (firstEnable) delete layer.meta.pattern;
594
598
  throw err;
@@ -615,6 +619,7 @@ var PatternManager = class {
615
619
  cropY: state.original.cropY,
616
620
  angle: state.original.angle
617
621
  });
622
+ image.clipPath = state.originalClip ? (await util.enlivenObjects([state.originalClip]))[0] : void 0;
618
623
  image.setCoords();
619
624
  delete layer.meta.pattern;
620
625
  this.canvas.requestRenderAll();
@@ -631,14 +636,18 @@ var PatternManager = class {
631
636
  );
632
637
  return next;
633
638
  }
634
- async renderLayer(layer) {
639
+ async renderLayer(layer, pin) {
635
640
  const state = layer.meta.pattern;
636
641
  if (!state) return;
637
642
  const image = layer.fabricObject;
638
643
  const cw = this.canvas.getWidth();
639
644
  const ch = this.canvas.getHeight();
640
- const tileW = state.original.width * state.original.scaleX;
641
- const tileH = state.original.height * state.original.scaleY;
645
+ const { left, top, angle } = resolvePatternPlacement(pin, image);
646
+ const scale = Math.max(1, state.config.scale ?? 100) / 100;
647
+ const diag = Math.sqrt(cw * cw + ch * ch);
648
+ const minTile = Math.max(2, diag / MAX_TILES_PER_AXIS);
649
+ const tileW = Math.max(minTile, state.original.width * state.original.scaleX * scale);
650
+ const tileH = Math.max(minTile, state.original.height * state.original.scaleY * scale);
642
651
  const dataUrl = await buildPatternDataURL(
643
652
  state.originalSrc,
644
653
  state.config,
@@ -648,11 +657,30 @@ var PatternManager = class {
648
657
  tileH
649
658
  );
650
659
  await image.setSrc(dataUrl);
651
- image.set({ left: 0, top: 0, scaleX: 1, scaleY: 1, cropX: 0, cropY: 0, angle: 0 });
660
+ image.set({
661
+ left,
662
+ top,
663
+ scaleX: 1,
664
+ scaleY: 1,
665
+ width: cw,
666
+ height: ch,
667
+ cropX: 0,
668
+ cropY: 0,
669
+ angle
670
+ });
671
+ image.clipPath = void 0;
652
672
  image.setCoords();
653
673
  this.canvas.requestRenderAll();
654
674
  }
655
675
  };
676
+ function resolvePatternPlacement(pin, current) {
677
+ if (pin) return { left: 0, top: 0, angle: 0 };
678
+ return {
679
+ left: current.left ?? 0,
680
+ top: current.top ?? 0,
681
+ angle: current.angle ?? 0
682
+ };
683
+ }
656
684
  function elementToDataURL(image) {
657
685
  try {
658
686
  const el = image.getElement();
@@ -713,9 +741,10 @@ async function buildPatternDataURL(src, config, targetW, targetH, baseW, baseH)
713
741
  return off.toDataURL("image/png");
714
742
  }
715
743
  function computeTilePositions(config, targetW, targetH, baseW, baseH) {
716
- const tileW = Math.max(1, baseW * (1 + config.horizontalSpacing / 100));
717
- const tileH = Math.max(1, baseH * (1 + config.verticalSpacing / 100));
718
744
  const diag = Math.sqrt(targetW * targetW + targetH * targetH);
745
+ const minTile = Math.max(1, diag / MAX_TILES_PER_AXIS);
746
+ const tileW = Math.max(minTile, baseW * (1 + config.horizontalSpacing / 100));
747
+ const tileH = Math.max(minTile, baseH * (1 + config.verticalSpacing / 100));
719
748
  const cols = Math.ceil(diag / tileW) + 2;
720
749
  const rows = Math.ceil(diag / tileH) + 2;
721
750
  const halfCols = Math.ceil(cols / 2);
@@ -799,7 +828,7 @@ var UnitConverter = class {
799
828
  };
800
829
 
801
830
  // src/serialization.ts
802
- import { util } from "fabric";
831
+ import { util as util2 } from "fabric";
803
832
  var VERSION = "1.0.0";
804
833
  function serializeEditor(editor) {
805
834
  return {
@@ -830,7 +859,7 @@ async function deserializeEditor(editor, state) {
830
859
  editor.canvas.requestRenderAll();
831
860
  }
832
861
  async function restoreLayer(editor, serialized) {
833
- const objects = await util.enlivenObjects([serialized.fabricObject]);
862
+ const objects = await util2.enlivenObjects([serialized.fabricObject]);
834
863
  const fabricObject = objects[0];
835
864
  const layer = editor.layers.add(serialized.type, fabricObject, serialized.name, serialized.id);
836
865
  if (serialized.meta) {
@@ -918,7 +947,7 @@ var CanvasEditor = class {
918
947
  }
919
948
  // ─── Layer Operations ────────────────────────────────
920
949
  async addImage(url, options) {
921
- const img = await FabricImage.fromURL(url, {}, { originX: "left", originY: "top", ...options });
950
+ const img = await FabricImage2.fromURL(url, {}, { originX: "left", originY: "top", ...options });
922
951
  const layer = this.layers.add("image", img);
923
952
  this.history.save();
924
953
  return layer;
@@ -1199,6 +1228,7 @@ var CanvasEditor = class {
1199
1228
  // src/types.ts
1200
1229
  var DEFAULT_PATTERN_CONFIG = {
1201
1230
  mode: "grid",
1231
+ scale: 100,
1202
1232
  horizontalSpacing: 0,
1203
1233
  verticalSpacing: 0,
1204
1234
  angle: 0,
@@ -1227,6 +1257,7 @@ export {
1227
1257
  exportPNG,
1228
1258
  exportSVG,
1229
1259
  generateId,
1260
+ resolvePatternPlacement,
1230
1261
  round2,
1231
1262
  serializeEditor
1232
1263
  };