@snowcone-app/canvas 0.1.9 → 0.1.12
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/{CanvasStateV1-D5GzvmnY.cjs → CanvasStateV1-C4hC1MCe.cjs} +5 -5
- package/dist/{CanvasStateV1-D5GzvmnY.cjs.map → CanvasStateV1-C4hC1MCe.cjs.map} +1 -1
- package/dist/{CanvasStateV1-ejb4d_LM.js → CanvasStateV1-CJU_xYW5.js} +3 -3
- package/dist/{CanvasStateV1-ejb4d_LM.js.map → CanvasStateV1-CJU_xYW5.js.map} +1 -1
- package/dist/{HybridHistoryManager-BV6XV0nD.js → HybridHistoryManager-jBBnVim8.js} +54 -54
- package/dist/{HybridHistoryManager-BV6XV0nD.js.map → HybridHistoryManager-jBBnVim8.js.map} +1 -1
- package/dist/{ElementFactory-uJTXU-nP.js → ImportManager-Oqu2yB54.js} +916 -697
- package/dist/ImportManager-Oqu2yB54.js.map +1 -0
- package/dist/{ElementFactory-B7UOaJSD.cjs → ImportManager-W1eWhfyM.cjs} +5 -5
- package/dist/ImportManager-W1eWhfyM.cjs.map +1 -0
- package/dist/ThemeContext-BMNQKl1c.cjs +2 -0
- package/dist/{ThemeContext-4mJ_y0Me.cjs.map → ThemeContext-BMNQKl1c.cjs.map} +1 -1
- package/dist/ThemeContext-wj-wSO7J.js +1158 -0
- package/dist/{ThemeContext-H0Z-MqqR.js.map → ThemeContext-wj-wSO7J.js.map} +1 -1
- package/dist/advanced.js +5 -32
- package/dist/advanced.js.map +1 -1
- package/dist/advanced.mjs +593 -15081
- package/dist/advanced.mjs.map +1 -1
- package/dist/components/embed/KitLayout.d.ts +22 -0
- package/dist/components/embed/UndoRedoControls.d.ts +3 -0
- package/dist/compose-Dqh2f8tS.js +22222 -0
- package/dist/compose-Dqh2f8tS.js.map +1 -0
- package/dist/compose-HDJp4Z_d.cjs +60 -0
- package/dist/compose-HDJp4Z_d.cjs.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +589 -508
- package/dist/index.mjs.map +1 -1
- package/dist/internals.js +1 -1
- package/dist/internals.js.map +1 -1
- package/dist/internals.mjs +101 -102
- package/dist/internals.mjs.map +1 -1
- package/dist/style.css.d.ts +4 -0
- package/dist/testing.js +1 -1
- package/dist/testing.mjs +11 -11
- package/package.json +9 -5
- package/dist/ElementFactory-B7UOaJSD.cjs.map +0 -1
- package/dist/ElementFactory-uJTXU-nP.js.map +0 -1
- package/dist/ImportManager-BYwuK6n4.cjs +0 -2
- package/dist/ImportManager-BYwuK6n4.cjs.map +0 -1
- package/dist/ImportManager-CxiaRg1N.js +0 -222
- package/dist/ImportManager-CxiaRg1N.js.map +0 -1
- package/dist/ThemeContext-4mJ_y0Me.cjs +0 -2
- package/dist/ThemeContext-H0Z-MqqR.js +0 -1077
- package/dist/components/stories/utils/MockEditorProvider.d.ts +0 -32
- package/dist/components/stories/utils/QACanvasCard.d.ts +0 -41
- package/dist/components/stories/utils/VisualQACard.d.ts +0 -24
- package/dist/components/stories/utils/element-factories.d.ts +0 -188
- package/dist/components/stories/utils/spec-to-elements.d.ts +0 -74
- package/dist/components/stories/utils/themeDecorator.d.ts +0 -45
- package/dist/components/stories/utils/unified-test-cases.d.ts +0 -27
- package/dist/compose-Bo108juW.cjs +0 -33
- package/dist/compose-Bo108juW.cjs.map +0 -1
- package/dist/compose-DQ1FZS3O.js +0 -7690
- package/dist/compose-DQ1FZS3O.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HybridHistoryManager-BV6XV0nD.js","sources":["../../../node_modules/.pnpm/vite-plugin-node-polyfills@0.24.0_rollup@4.60.3_vite@5.4.21_@types+node@25.7.0_lightningcss@1.32.0_terser@5.47.1_/node_modules/vite-plugin-node-polyfills/shims/process/dist/index.js","../src/core/ArtboardElement.ts","../src/core/ArtboardManager.ts","../src/core/RotationUtils.ts","../src/core/BaseElement.ts","../src/core/Transform.ts","../src/fonts/google-fonts.ts","../src/constants.ts","../src/utils/logger.ts","../src/core/ImageLoadEvents.ts","../src/core/ImageCache.ts","../src/core/ImageElement.ts","../src/types/index.ts","../src/core/TextElement.ts","../src/core/TextMetrics.ts","../src/rendering/transform-renderer.ts","../src/rendering/stroke-utils.ts","../src/rendering/StrokeRenderer.ts","../src/utils/FontAnalyzer.ts","../src/utils/GlyphRenderer.ts","../src/rendering/rich-text-renderer.ts","../src/rendering/text-renderer.ts","../src/core/ResizeUtils.ts","../src/transforms/CustomTransform.ts","../src/core/GeometryUtils.ts","../src/transforms/CircleTransform.ts","../src/transforms/ArchTransform.ts","../src/transforms/defaults.ts","../src/transforms/WaveTransform.ts","../src/transforms/FlagTransform.ts","../src/transforms/LeanTransform.ts","../src/transforms/AscendTransform.ts","../src/core/ShapeElement.ts","../src/core/PathElement.ts","../src/transforms/registry.ts","../src/core/GroupElement.ts","../src/core/ElementStore.ts","../src/core/CommandHistory.ts","../src/core/HybridHistoryManager.ts"],"sourcesContent":["function getDefaultExportFromCjs (x) {\n\treturn x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;\n}\n\nvar browser = {exports: {}};\n\n// shim for using process in browser\nvar process = browser.exports = {};\n\n// cached from whatever global is present so that test runners that stub it\n// don't break things. But we need to wrap it in a try catch in case it is\n// wrapped in strict mode code which doesn't define any globals. It's inside a\n// function because try/catches deoptimize in certain engines.\n\nvar cachedSetTimeout;\nvar cachedClearTimeout;\n\nfunction defaultSetTimout() {\n throw new Error('setTimeout has not been defined');\n}\nfunction defaultClearTimeout () {\n throw new Error('clearTimeout has not been defined');\n}\n(function () {\n try {\n if (typeof setTimeout === 'function') {\n cachedSetTimeout = setTimeout;\n } else {\n cachedSetTimeout = defaultSetTimout;\n }\n } catch (e) {\n cachedSetTimeout = defaultSetTimout;\n }\n try {\n if (typeof clearTimeout === 'function') {\n cachedClearTimeout = clearTimeout;\n } else {\n cachedClearTimeout = defaultClearTimeout;\n }\n } catch (e) {\n cachedClearTimeout = defaultClearTimeout;\n }\n} ());\nfunction runTimeout(fun) {\n if (cachedSetTimeout === setTimeout) {\n //normal enviroments in sane situations\n return setTimeout(fun, 0);\n }\n // if setTimeout wasn't available but was latter defined\n if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {\n cachedSetTimeout = setTimeout;\n return setTimeout(fun, 0);\n }\n try {\n // when when somebody has screwed with setTimeout but no I.E. maddness\n return cachedSetTimeout(fun, 0);\n } catch(e){\n try {\n // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally\n return cachedSetTimeout.call(null, fun, 0);\n } catch(e){\n // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error\n return cachedSetTimeout.call(this, fun, 0);\n }\n }\n\n\n}\nfunction runClearTimeout(marker) {\n if (cachedClearTimeout === clearTimeout) {\n //normal enviroments in sane situations\n return clearTimeout(marker);\n }\n // if clearTimeout wasn't available but was latter defined\n if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {\n cachedClearTimeout = clearTimeout;\n return clearTimeout(marker);\n }\n try {\n // when when somebody has screwed with setTimeout but no I.E. maddness\n return cachedClearTimeout(marker);\n } catch (e){\n try {\n // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally\n return cachedClearTimeout.call(null, marker);\n } catch (e){\n // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.\n // Some versions of I.E. have different rules for clearTimeout vs setTimeout\n return cachedClearTimeout.call(this, marker);\n }\n }\n\n\n\n}\nvar queue = [];\nvar draining = false;\nvar currentQueue;\nvar queueIndex = -1;\n\nfunction cleanUpNextTick() {\n if (!draining || !currentQueue) {\n return;\n }\n draining = false;\n if (currentQueue.length) {\n queue = currentQueue.concat(queue);\n } else {\n queueIndex = -1;\n }\n if (queue.length) {\n drainQueue();\n }\n}\n\nfunction drainQueue() {\n if (draining) {\n return;\n }\n var timeout = runTimeout(cleanUpNextTick);\n draining = true;\n\n var len = queue.length;\n while(len) {\n currentQueue = queue;\n queue = [];\n while (++queueIndex < len) {\n if (currentQueue) {\n currentQueue[queueIndex].run();\n }\n }\n queueIndex = -1;\n len = queue.length;\n }\n currentQueue = null;\n draining = false;\n runClearTimeout(timeout);\n}\n\nprocess.nextTick = function (fun) {\n var args = new Array(arguments.length - 1);\n if (arguments.length > 1) {\n for (var i = 1; i < arguments.length; i++) {\n args[i - 1] = arguments[i];\n }\n }\n queue.push(new Item(fun, args));\n if (queue.length === 1 && !draining) {\n runTimeout(drainQueue);\n }\n};\n\n// v8 likes predictible objects\nfunction Item(fun, array) {\n this.fun = fun;\n this.array = array;\n}\nItem.prototype.run = function () {\n this.fun.apply(null, this.array);\n};\nprocess.title = 'browser';\nprocess.browser = true;\nprocess.env = {};\nprocess.argv = [];\nprocess.version = ''; // empty string to avoid regexp issues\nprocess.versions = {};\n\nfunction noop() {}\n\nprocess.on = noop;\nprocess.addListener = noop;\nprocess.once = noop;\nprocess.off = noop;\nprocess.removeListener = noop;\nprocess.removeAllListeners = noop;\nprocess.emit = noop;\nprocess.prependListener = noop;\nprocess.prependOnceListener = noop;\n\nprocess.listeners = function (name) { return [] };\n\nprocess.binding = function (name) {\n throw new Error('process.binding is not supported');\n};\n\nprocess.cwd = function () { return '/' };\nprocess.chdir = function (dir) {\n throw new Error('process.chdir is not supported');\n};\nprocess.umask = function() { return 0; };\n\nvar browserExports = browser.exports;\nconst process$1 = /*@__PURE__*/getDefaultExportFromCjs(browserExports);\n\nexport { process$1 as default, process$1 as process };\n//# sourceMappingURL=index.js.map\n","/**\n * ArtboardElement - Container for elements with fixed bounds (like Figma frames)\n * Unlike GroupElement, artboards have:\n * - Fixed dimensions (width/height)\n * - Background color\n * - Export boundaries\n * - Named for organization\n */\n\nimport type { TextElement } from './TextElement.js';\nimport type { ImageElement } from './ImageElement.js';\nimport type { GroupElement } from './GroupElement.js';\nimport type { BoundingBox, Point, ArtboardConfig, ArtboardBackgroundType, ArtboardDistressTexture, ArtboardImageMask, ClipShape } from '../types/index.js';\n\nlet nextArtboardId = 1;\n\nexport class ArtboardElement {\n id: string;\n name: string;\n x: number;\n y: number;\n width: number;\n height: number;\n backgroundColor: string;\n backgroundType: ArtboardBackgroundType;\n backgroundTexture?: string;\n exportBackground: boolean;\n transformType: 'artboard' = 'artboard';\n\n // Clip shape for artboard content (affects both rendering and export)\n clipShape?: ClipShape;\n\n // Preview only - NOT exported to PNG (for t-shirt color preview)\n previewBackgroundColor?: string;\n\n // Artboard-level distress texture\n distressTexture?: ArtboardDistressTexture;\n\n // Artboard-level image mask\n imageMask?: ArtboardImageMask;\n\n // Children elements (stored by ID for serialization, actual elements managed externally)\n private elementIds: Set<string>;\n\n constructor(config: Partial<ArtboardConfig> = {}) {\n this.id = config.id || `artboard-${nextArtboardId++}`;\n this.name = config.name || `Artboard ${nextArtboardId}`;\n this.x = config.x !== undefined ? config.x : 0;\n this.y = config.y !== undefined ? config.y : 0;\n this.width = config.width || 1920;\n this.height = config.height || 1080;\n this.backgroundColor = config.backgroundColor || '#ffffff';\n\n // Determine background type from config or backgroundColor\n if (config.backgroundType) {\n this.backgroundType = config.backgroundType;\n } else if (config.backgroundColor === 'transparent') {\n this.backgroundType = 'transparent';\n } else if (config.backgroundTexture) {\n this.backgroundType = 'texture';\n } else {\n this.backgroundType = 'color';\n }\n\n this.backgroundTexture = config.backgroundTexture;\n this.exportBackground = config.exportBackground ?? false; // Defaults to false\n this.clipShape = config.clipShape; // undefined means no clipping (same as 'rectangle')\n this.previewBackgroundColor = config.previewBackgroundColor;\n this.distressTexture = config.distressTexture ? { ...config.distressTexture } : undefined;\n this.imageMask = config.imageMask ? { ...config.imageMask } : undefined;\n this.elementIds = new Set(config.elementIds || []);\n }\n\n /**\n * Get bounding box of the artboard\n */\n getBoundingBox(): BoundingBox {\n return {\n x: this.x,\n y: this.y,\n width: this.width,\n height: this.height,\n };\n }\n\n /**\n * Get center point of the artboard\n */\n getCenter(): Point {\n return {\n x: this.x + this.width / 2,\n y: this.y + this.height / 2,\n };\n }\n\n /**\n * Check if a point is within artboard bounds\n */\n containsPoint(px: number, py: number): boolean {\n return px >= this.x && px <= this.x + this.width && py >= this.y && py <= this.y + this.height;\n }\n\n /**\n * Check if an element's bounding box is within artboard bounds\n */\n containsElement(element: TextElement | ImageElement | GroupElement): boolean {\n const bbox = element.getBoundingBox();\n return (\n bbox.x >= this.x &&\n bbox.y >= this.y &&\n bbox.x + bbox.width <= this.x + this.width &&\n bbox.y + bbox.height <= this.y + this.height\n );\n }\n\n /**\n * Add an element ID to this artboard\n */\n addElementId(elementId: string): void {\n this.elementIds.add(elementId);\n }\n\n /**\n * Remove an element ID from this artboard\n */\n removeElementId(elementId: string): void {\n this.elementIds.delete(elementId);\n }\n\n /**\n * Check if artboard contains an element ID\n */\n hasElementId(elementId: string): boolean {\n return this.elementIds.has(elementId);\n }\n\n /**\n * Get all element IDs in this artboard\n */\n getElementIds(): string[] {\n return Array.from(this.elementIds);\n }\n\n /**\n * Get number of elements in artboard\n */\n getElementCount(): number {\n return this.elementIds.size;\n }\n\n /**\n * Clear all element IDs\n */\n clearElementIds(): void {\n this.elementIds.clear();\n }\n\n /**\n * Test if a point hits the artboard border (for selection)\n * Returns true if point is near the border (within tolerance)\n */\n hitTestBorder(px: number, py: number, tolerance: number = 5): boolean {\n const inXRange = px >= this.x - tolerance && px <= this.x + this.width + tolerance;\n const inYRange = py >= this.y - tolerance && py <= this.y + this.height + tolerance;\n\n // Check if point is near any edge\n const nearLeft = Math.abs(px - this.x) <= tolerance && inYRange;\n const nearRight = Math.abs(px - (this.x + this.width)) <= tolerance && inYRange;\n const nearTop = Math.abs(py - this.y) <= tolerance && inXRange;\n const nearBottom = Math.abs(py - (this.y + this.height)) <= tolerance && inXRange;\n\n return nearLeft || nearRight || nearTop || nearBottom;\n }\n\n /**\n * Clone the artboard\n */\n clone(): ArtboardElement {\n return new ArtboardElement({\n id: this.id,\n name: this.name,\n x: this.x,\n y: this.y,\n width: this.width,\n height: this.height,\n backgroundColor: this.backgroundColor,\n backgroundType: this.backgroundType,\n backgroundTexture: this.backgroundTexture,\n exportBackground: this.exportBackground,\n clipShape: this.clipShape,\n previewBackgroundColor: this.previewBackgroundColor,\n distressTexture: this.distressTexture ? { ...this.distressTexture } : undefined,\n imageMask: this.imageMask ? { ...this.imageMask } : undefined,\n elementIds: Array.from(this.elementIds),\n });\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): ArtboardConfig {\n return {\n id: this.id,\n name: this.name,\n x: this.x,\n y: this.y,\n width: this.width,\n height: this.height,\n backgroundColor: this.backgroundColor,\n backgroundType: this.backgroundType,\n ...(this.backgroundTexture && { backgroundTexture: this.backgroundTexture }),\n exportBackground: this.exportBackground,\n elementIds: Array.from(this.elementIds),\n transformType: 'artboard' as const,\n // Clip shape for content clipping\n ...(this.clipShape && { clipShape: this.clipShape }),\n // Preview only - saved to project file, but NOT exported to PNG\n ...(this.previewBackgroundColor && { previewBackgroundColor: this.previewBackgroundColor }),\n // Artboard-level distress texture\n ...(this.distressTexture && { distressTexture: { ...this.distressTexture } }),\n // Artboard-level image mask\n ...(this.imageMask && { imageMask: { ...this.imageMask } }),\n };\n }\n\n /**\n * Create from JSON\n */\n static fromJSON(config: ArtboardConfig): ArtboardElement {\n return new ArtboardElement(config);\n }\n\n /**\n * Update artboard properties\n */\n updateProperties(\n updates: Partial<{\n name: string;\n x: number;\n y: number;\n width: number;\n height: number;\n backgroundColor: string;\n backgroundType: ArtboardBackgroundType;\n backgroundTexture: string;\n exportBackground: boolean;\n clipShape: ClipShape;\n distressTexture: ArtboardDistressTexture | undefined;\n imageMask: ArtboardImageMask | undefined;\n }>\n ): void {\n if (updates.name !== undefined) this.name = updates.name;\n if (updates.x !== undefined) this.x = updates.x;\n if (updates.y !== undefined) this.y = updates.y;\n if (updates.width !== undefined) this.width = Math.max(100, updates.width); // Min width\n if (updates.height !== undefined) this.height = Math.max(100, updates.height); // Min height\n if (updates.backgroundColor !== undefined) this.backgroundColor = updates.backgroundColor;\n if (updates.backgroundType !== undefined) this.backgroundType = updates.backgroundType;\n if (updates.backgroundTexture !== undefined) this.backgroundTexture = updates.backgroundTexture;\n if (updates.exportBackground !== undefined) this.exportBackground = updates.exportBackground;\n if (updates.clipShape !== undefined) this.clipShape = updates.clipShape;\n if ('distressTexture' in updates) this.distressTexture = updates.distressTexture ? { ...updates.distressTexture } : undefined;\n if ('imageMask' in updates) this.imageMask = updates.imageMask ? { ...updates.imageMask } : undefined;\n }\n\n /**\n * Resize artboard (with constraints)\n */\n resize(newWidth: number, newHeight: number): void {\n this.width = Math.max(100, newWidth);\n this.height = Math.max(100, newHeight);\n }\n\n /**\n * Move artboard\n */\n move(newX: number, newY: number): void {\n this.x = newX;\n this.y = newY;\n }\n}\n\nexport default ArtboardElement;\n","/**\n * ArtboardManager - Manages artboard lifecycle and element-to-artboard mapping\n * Handles creation, deletion, and tracking of artboards\n */\n\nimport { ArtboardElement } from './ArtboardElement.js';\nimport type { TextElement } from './TextElement.js';\nimport type { ImageElement } from './ImageElement.js';\nimport type { GroupElement } from './GroupElement.js';\nimport type { ArtboardConfig } from '../types/index.js';\n\nexport class ArtboardManager {\n private artboards: Map<string, ArtboardElement>;\n private elementToArtboard: Map<string, string>; // elementId -> artboardId\n private activeArtboardId: string | null;\n\n constructor() {\n this.artboards = new Map();\n this.elementToArtboard = new Map();\n this.activeArtboardId = null;\n }\n\n /**\n * Create a new artboard\n *\n * NOTE: This method sets the newly created artboard as active. When creating\n * multiple artboards in a loop (e.g., during initialization), the LAST artboard\n * will end up as active. If you need a specific artboard to be active after\n * batch creation, call setActiveArtboard() explicitly afterward.\n *\n * SnowconeCanvas handles this via hasInitialSyncRef - it suppresses onArtboardChange\n * callbacks until the controlled activeArtboard prop has been synced.\n */\n createArtboard(config: Partial<ArtboardConfig> = {}): ArtboardElement {\n const artboard = new ArtboardElement(config);\n this.artboards.set(artboard.id, artboard);\n\n // Always set newly created artboard as active\n // See note above about implications for batch creation\n this.activeArtboardId = artboard.id;\n\n return artboard;\n }\n\n /**\n * Delete an artboard\n * Returns the IDs of elements that were on the artboard\n */\n deleteArtboard(artboardId: string): string[] {\n const artboard = this.artboards.get(artboardId);\n if (!artboard) {\n return [];\n }\n\n // Get all element IDs from the artboard\n const elementIds = artboard.getElementIds();\n\n // Remove element-to-artboard mappings\n elementIds.forEach((elementId) => {\n this.elementToArtboard.delete(elementId);\n });\n\n // Remove artboard\n this.artboards.delete(artboardId);\n\n // Update active artboard if deleted\n if (this.activeArtboardId === artboardId) {\n // Set to first available artboard or null\n const remainingArtboards = Array.from(this.artboards.keys());\n this.activeArtboardId = remainingArtboards.length > 0 ? remainingArtboards[0] : null;\n }\n\n return elementIds;\n }\n\n /**\n * Get an artboard by ID\n */\n getArtboard(artboardId: string): ArtboardElement | undefined {\n return this.artboards.get(artboardId);\n }\n\n /**\n * Get all artboards\n */\n getAllArtboards(): ArtboardElement[] {\n return Array.from(this.artboards.values());\n }\n\n /**\n * Get artboard IDs\n */\n getArtboardIds(): string[] {\n return Array.from(this.artboards.keys());\n }\n\n /**\n * Set active artboard\n */\n setActiveArtboard(artboardId: string | null): void {\n if (artboardId === null || this.artboards.has(artboardId)) {\n this.activeArtboardId = artboardId;\n }\n }\n\n /**\n * Get active artboard\n */\n getActiveArtboard(): ArtboardElement | null {\n return this.activeArtboardId ? this.artboards.get(this.activeArtboardId) || null : null;\n }\n\n /**\n * Get active artboard ID\n */\n getActiveArtboardId(): string | null {\n return this.activeArtboardId;\n }\n\n /**\n * Add an element to an artboard\n */\n addElementToArtboard(elementId: string, artboardId: string): boolean {\n const artboard = this.artboards.get(artboardId);\n if (!artboard) {\n return false;\n }\n\n // Remove from previous artboard if exists\n const previousArtboardId = this.elementToArtboard.get(elementId);\n if (previousArtboardId) {\n const previousArtboard = this.artboards.get(previousArtboardId);\n previousArtboard?.removeElementId(elementId);\n }\n\n // Add to new artboard\n artboard.addElementId(elementId);\n this.elementToArtboard.set(elementId, artboardId);\n return true;\n }\n\n /**\n * Remove an element from its artboard\n */\n removeElementFromArtboard(elementId: string): void {\n const artboardId = this.elementToArtboard.get(elementId);\n if (artboardId) {\n const artboard = this.artboards.get(artboardId);\n artboard?.removeElementId(elementId);\n this.elementToArtboard.delete(elementId);\n }\n }\n\n /**\n * Get the artboard that contains an element\n */\n getArtboardForElement(elementId: string): ArtboardElement | null {\n const artboardId = this.elementToArtboard.get(elementId);\n return artboardId ? this.artboards.get(artboardId) || null : null;\n }\n\n /**\n * Get artboard ID for an element\n */\n getArtboardIdForElement(elementId: string): string | null {\n return this.elementToArtboard.get(elementId) || null;\n }\n\n /**\n * Get all elements on an artboard\n * Note: Returns element IDs, not actual element objects\n */\n getElementsOnArtboard(artboardId: string): string[] {\n const artboard = this.artboards.get(artboardId);\n return artboard ? artboard.getElementIds() : [];\n }\n\n /**\n * Find artboard at a point (for selection)\n */\n findArtboardAtPoint(x: number, y: number): ArtboardElement | null {\n // Check in reverse order (top artboard first)\n const artboards = Array.from(this.artboards.values()).reverse();\n\n for (const artboard of artboards) {\n if (artboard.containsPoint(x, y)) {\n return artboard;\n }\n }\n\n return null;\n }\n\n /**\n * Find artboard border at point (for resize handles)\n */\n findArtboardBorderAtPoint(x: number, y: number, tolerance: number = 5): ArtboardElement | null {\n const artboards = Array.from(this.artboards.values()).reverse();\n\n for (const artboard of artboards) {\n if (artboard.hitTestBorder(x, y, tolerance)) {\n return artboard;\n }\n }\n\n return null;\n }\n\n /**\n * Determine which artboard an element should belong to based on position\n */\n findArtboardForPosition(x: number, y: number): ArtboardElement | null {\n // Find first artboard that contains this point\n return this.findArtboardAtPoint(x, y);\n }\n\n /**\n * Update artboard properties\n */\n updateArtboard(artboardId: string, updates: Partial<ArtboardConfig>): boolean {\n const artboard = this.artboards.get(artboardId);\n if (!artboard) {\n return false;\n }\n\n artboard.updateProperties(updates);\n return true;\n }\n\n /**\n * Rename an artboard\n */\n renameArtboard(artboardId: string, name: string): boolean {\n return this.updateArtboard(artboardId, { name });\n }\n\n /**\n * Check if an artboard exists\n */\n hasArtboard(artboardId: string): boolean {\n return this.artboards.has(artboardId);\n }\n\n /**\n * Get artboard count\n */\n getArtboardCount(): number {\n return this.artboards.size;\n }\n\n /**\n * Reorder artboards by moving the artboard at fromIndex to toIndex.\n * Preserves Map insertion order by rebuilding the internal Map.\n */\n reorderArtboards(fromIndex: number, toIndex: number): void {\n const entries = Array.from(this.artboards.entries());\n if (fromIndex < 0 || fromIndex >= entries.length) return;\n if (toIndex < 0 || toIndex >= entries.length) return;\n if (fromIndex === toIndex) return;\n\n const [moved] = entries.splice(fromIndex, 1);\n entries.splice(toIndex, 0, moved);\n\n this.artboards = new Map(entries);\n }\n\n /**\n * Clear all artboards\n */\n clear(): void {\n this.artboards.clear();\n this.elementToArtboard.clear();\n this.activeArtboardId = null;\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): {\n artboards: ArtboardConfig[];\n activeArtboardId: string | null;\n } {\n return {\n artboards: Array.from(this.artboards.values()).map((a) => a.toJSON()),\n activeArtboardId: this.activeArtboardId,\n };\n }\n\n /**\n * Load from JSON\n */\n fromJSON(data: { artboards: ArtboardConfig[]; activeArtboardId: string | null }): void {\n this.clear();\n\n // Restore artboards\n data.artboards.forEach((config) => {\n const artboard = ArtboardElement.fromJSON(config);\n this.artboards.set(artboard.id, artboard);\n\n // Restore element mappings\n artboard.getElementIds().forEach((elementId) => {\n this.elementToArtboard.set(elementId, artboard.id);\n });\n });\n\n // Restore active artboard\n this.activeArtboardId = data.activeArtboardId;\n }\n\n /**\n * Validate element-to-artboard mappings\n * Ensures all elements have valid artboard assignments\n */\n validateMappings(elements: (TextElement | ImageElement | GroupElement)[]): void {\n const elementIds = new Set(elements.map((e) => e.id));\n\n // Remove mappings for elements that no longer exist\n for (const [elementId, _artboardId] of this.elementToArtboard.entries()) {\n if (!elementIds.has(elementId)) {\n this.removeElementFromArtboard(elementId);\n }\n }\n\n // Ensure all artboards have valid element IDs\n for (const artboard of this.artboards.values()) {\n const validElementIds = artboard.getElementIds().filter((id) => elementIds.has(id));\n artboard.clearElementIds();\n validElementIds.forEach((id) => artboard.addElementId(id));\n }\n }\n}\n\nexport default ArtboardManager;\n","/**\n * RotationUtils - Centralized rotation convention utilities\n *\n * Convention: Clockwise rotations in UI are represented as positive degrees,\n * but canvas rendering uses counter-clockwise rotation (hence the negative sign).\n */\nexport const RotationUtils = {\n /**\n * Forward transform (local → world, for rendering)\n * Converts degrees to radians with the canvas convention (negative = clockwise)\n * @param {number} degrees - Rotation in degrees (positive = clockwise in UI)\n * @returns {number} Rotation in radians for canvas context\n */\n toRadians(degrees: number): number {\n return (-degrees * Math.PI) / 180;\n },\n\n /**\n * Inverse transform (world → local, for hit testing)\n * Converts degrees to radians without negation\n * @param {number} degrees - Rotation in degrees\n * @returns {number} Rotation in radians\n */\n toRadiansInverse(degrees: number): number {\n return (degrees * Math.PI) / 180;\n },\n\n /**\n * Normalize angle to -180 to +180 range\n * @param {number} degrees - Angle in degrees\n * @returns {number} Normalized angle in range [-180, 180]\n */\n normalize(degrees: number): number {\n let normalized = ((degrees % 360) + 360) % 360;\n if (normalized > 180) {\n normalized -= 360;\n }\n return normalized;\n },\n};\n","/**\n * BaseElement - Abstract base class for all canvas elements\n * Defines common interface and shared behavior for all element types\n */\n\nimport { RotationUtils } from './RotationUtils.js';\nimport type {\n TransformType,\n AnyTransformData,\n BoundingBox,\n Point,\n ResizeAnchor,\n BlendMode,\n KnockoutConfig,\n StrokeConfig,\n MaskDefinition,\n DistressEffect,\n BaseElementConfig,\n TransformStartData,\n} from '../types/index.js';\n\nlet nextId = 1;\n\n/**\n * Reset the ID counter (useful for testing)\n */\nexport function resetElementIdCounter(): void {\n nextId = 1;\n}\n\nexport abstract class BaseElement {\n id: string;\n x: number;\n y: number;\n rotation: number;\n opacity: number;\n transformType: TransformType;\n transformData: AnyTransformData;\n\n // Effects properties\n blendMode?: BlendMode;\n knockoutParts?: KnockoutConfig;\n stroke?: StrokeConfig;\n masks?: MaskDefinition[];\n distressEffect?: DistressEffect;\n\n // Layer properties\n name?: string;\n visible?: boolean;\n locked?: boolean;\n\n /** Whether this element clips content below it (derived from blendMode === 'clip') */\n get isClipping(): boolean {\n return this.blendMode === 'clip';\n }\n\n set isClipping(value: boolean | undefined) {\n if (value) {\n this.blendMode = 'clip';\n if (!this.knockoutParts) {\n this.knockoutParts = { fill: true, scope: 'group' };\n }\n } else if (this.blendMode === 'clip') {\n this.blendMode = 'normal';\n }\n }\n\n constructor(config: Partial<BaseElementConfig> = {}) {\n this.id = config.id || `element-${nextId++}`;\n this.x = config.x !== undefined ? config.x : 100;\n this.y = config.y !== undefined ? config.y : 100;\n this.rotation = config.rotation !== undefined ? config.rotation : 0;\n this.opacity = config.opacity !== undefined ? config.opacity : 1;\n\n // Transform-specific data (overridden by subclasses)\n this.transformType = config.transformType || 'custom';\n this.transformData = (config.transformData as AnyTransformData) || ({} as AnyTransformData);\n\n // Effects properties\n this.blendMode = config.blendMode;\n this.knockoutParts = config.knockoutParts;\n this.stroke = config.stroke;\n this.masks = config.masks;\n this.distressEffect = config.distressEffect;\n\n // Legacy migration: isClipping without blendMode → set blendMode = 'clip'\n if (config.isClipping && !config.blendMode) {\n this.blendMode = 'clip';\n if (!this.knockoutParts) {\n this.knockoutParts = { fill: true, scope: 'group' };\n }\n }\n\n // Layer properties\n this.name = config.name;\n this.visible = config.visible !== undefined ? config.visible : true;\n this.locked = config.locked !== undefined ? config.locked : false;\n }\n\n /**\n * Get bounding box in world coordinates\n * Used for transform math (resize, rotation, handles)\n * Must be implemented by subclasses\n */\n abstract getBoundingBox(): BoundingBox;\n\n /**\n * Get visual bounding box (tight fit around actual rendered content)\n * Used for selection display - should match what user sees\n * Default: same as getBoundingBox(), override for tighter fit\n */\n getVisualBoundingBox(): BoundingBox {\n return this.getBoundingBox();\n }\n\n /**\n * Get rotation anchor point (the point around which element rotates)\n * Default: returns element's position (x, y)\n * Override if rotation should be around a different point\n */\n getRotationAnchor(): Point {\n return { x: this.x, y: this.y };\n }\n\n /**\n * Test if a point hits this element\n * Uses visual bounding box for better user experience\n */\n hitTest(px: number, py: number): boolean {\n const visualBbox = this.getVisualBoundingBox();\n const rotationAnchor = this.getRotationAnchor();\n\n // Transform point to local coordinates (undo rotation)\n const rotationRad = RotationUtils.toRadiansInverse(this.rotation);\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Translate to rotation anchor\n const dx = px - rotationAnchor.x;\n const dy = py - rotationAnchor.y;\n\n // Rotate\n const localX = dx * cos - dy * sin;\n const localY = dx * sin + dy * cos;\n\n // Translate back\n const transformedX = rotationAnchor.x + localX;\n const transformedY = rotationAnchor.y + localY;\n\n // Test against axis-aligned visual bbox\n return (\n transformedX >= visualBbox.x &&\n transformedX <= visualBbox.x + visualBbox.width &&\n transformedY >= visualBbox.y &&\n transformedY <= visualBbox.y + visualBbox.height\n );\n }\n\n /**\n * Render the element\n * Must be implemented by subclasses\n */\n abstract render(ctx: CanvasRenderingContext2D, isSelected?: boolean, isHovered?: boolean): void;\n\n /**\n * Apply canvas transform (translate, rotate)\n * Common implementation for all elements\n */\n applyCanvasTransform(ctx: CanvasRenderingContext2D): void {\n ctx.translate(this.x, this.y);\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n }\n\n /**\n * Serialize to JSON\n * Returns base element properties - subclasses should extend this\n */\n toJSON(): BaseElementConfig {\n return {\n id: this.id,\n x: this.x,\n y: this.y,\n rotation: this.rotation,\n opacity: this.opacity,\n transformType: this.transformType,\n transformData: { ...this.transformData },\n // Effects properties (only include if defined)\n ...(this.blendMode && { blendMode: this.blendMode }),\n ...(this.knockoutParts && { knockoutParts: { ...this.knockoutParts } }),\n ...(this.stroke && { stroke: { ...this.stroke } }),\n ...(this.masks && { masks: this.masks.map((m) => ({ ...m })) }),\n ...(this.distressEffect && { distressEffect: { ...this.distressEffect } }),\n // Layer properties (only include if defined)\n ...(this.name && { name: this.name }),\n ...(this.visible !== undefined && { visible: this.visible }),\n ...(this.locked !== undefined && { locked: this.locked }),\n ...(this.isClipping && { isClipping: true }),\n };\n }\n\n /**\n * Clone this element\n * Uses toJSON() to serialize and reconstruct\n */\n abstract clone(): BaseElement;\n\n /**\n * Update position during drag\n */\n move(dx: number, dy: number): void {\n this.x += dx;\n this.y += dy;\n }\n\n /**\n * Update rotation\n */\n setRotation(newRotation: number): void {\n this.rotation = newRotation;\n }\n\n /**\n * Get enabled resize anchors for this element type\n * Default: all 8 anchors\n * Override in subclasses if needed\n */\n getEnabledAnchors(): ResizeAnchor[] {\n return [\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n 'middle-left',\n 'middle-right',\n 'middle-top',\n 'middle-bottom',\n ];\n }\n\n /**\n * Handle resize transform\n * Must be implemented by subclasses\n */\n abstract resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): void | boolean;\n\n /**\n * Called when transform starts\n * Returns data to be passed to resize()\n */\n getTransformStartData(): TransformStartData {\n const bbox = this.getBoundingBox();\n return {\n id: this.id,\n x: this.x,\n y: this.y,\n width: bbox.width,\n height: bbox.height,\n rotation: this.rotation,\n transformData: { ...this.transformData },\n // Capture stroke width for proportional scaling during resize\n strokeWidth: this.stroke?.width,\n };\n }\n}\n\nexport default BaseElement;\n","/**\n * Transform - Centralized coordinate transformation utilities\n *\n * ROTATION CONVENTION:\n * -------------------\n * - UI: Positive rotation = CLOCKWISE (e.g., +45° rotates 45° clockwise)\n * - Canvas rendering: Uses ctx.rotate() with NEGATIVE angle (negative = clockwise in canvas)\n * - Mathematical convention: Standard rotation matrices\n *\n * COORDINATE SPACES:\n * -----------------\n * - WORLD space: The canvas coordinate system (absolute positions)\n * - LOCAL space: The element's own coordinate system (relative to element center, unrotated)\n *\n * TRANSFORMS:\n * ----------\n * - FORWARD transform (local → world): Used for rendering, uses NEGATIVE angle\n * - INVERSE transform (world → local): Used for hit testing and input, uses POSITIVE angle\n *\n * WHY THE SIGN DIFFERENCE?\n * -----------------------\n * Canvas rotation is counter-clockwise by default. To make positive rotations appear\n * clockwise in the UI, we negate the angle when rendering. For coordinate transforms:\n * - To go from local→world (rendering), we use the RENDERING angle (negative)\n * - To go from world→local (input), we UNDO the rendering rotation (positive)\n *\n * EXAMPLE:\n * -------\n * An element at (100, 100) rotated 45° clockwise:\n * - Rendering: ctx.rotate((-45 * Math.PI) / 180) // negative = clockwise\n * - Mouse at (150, 120) in world coords\n * - Convert to local: use +45° to undo the -45° rendering rotation\n * - Result: local coordinates relative to element's center\n */\n\n/** Minimal interface for objects that can be used with Transform */\nexport interface Transformable {\n x: number;\n y: number;\n rotation: number;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- extensible interface for elements with varying properties\n [key: string]: any;\n}\n\nexport class Transform {\n element: Transformable;\n\n /**\n * Create a transform helper for an element\n * @param element - Element with x, y, rotation properties\n */\n constructor(element: Transformable) {\n this.element = element;\n }\n\n /**\n * Convert a point from world coordinates to local coordinates\n * This is an INVERSE transform (world → local)\n * Uses positive angle with standard rotation matrix formula\n *\n * @param worldX - X coordinate in world space\n * @param worldY - Y coordinate in world space\n * @returns Point in local coordinates\n */\n worldToLocal(worldX: number, worldY: number) {\n // Get positive angle for inverse transform\n const rad = (this.element.rotation * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n\n // Translate to element's origin\n const dx = worldX - this.element.x;\n const dy = worldY - this.element.y;\n\n // Apply rotation (same formula as forward, but this represents viewing from rotated frame)\n return {\n x: dx * cos - dy * sin,\n y: dx * sin + dy * cos,\n };\n }\n\n /**\n * Convert a point from local coordinates to world coordinates\n * This is a FORWARD transform (local → world)\n * Uses NEGATIVE angle matching the rendering convention\n *\n * @param localX - X coordinate in local space\n * @param localY - Y coordinate in local space\n * @returns Point in world coordinates\n */\n localToWorld(localX: number, localY: number) {\n // Get negative angle for forward transform (matches rendering)\n const rad = (-this.element.rotation * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n\n // Apply forward rotation\n const rotX = localX * cos - localY * sin;\n const rotY = localX * sin + localY * cos;\n\n // Translate to world position\n return {\n x: this.element.x + rotX,\n y: this.element.y + rotY,\n };\n }\n\n /**\n * Convert a delta (movement/offset) from world space to local space\n * This is useful for drag operations - no translation, just rotation\n * Uses positive angle with standard rotation matrix formula\n *\n * @param dx - Delta X in world space\n * @param dy - Delta Y in world space\n * @returns Delta in local space\n */\n worldDeltaToLocal(dx: number, dy: number) {\n const rad = (this.element.rotation * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n\n return {\n dx: dx * cos - dy * sin,\n dy: dx * sin + dy * cos,\n };\n }\n\n /**\n * Convert a delta (movement/offset) from local space to world space\n * Uses NEGATIVE angle (forward transform, matches rendering)\n *\n * @param dx - Delta X in local space\n * @param dy - Delta Y in local space\n * @returns Delta in world space\n */\n localDeltaToWorld(dx: number, dy: number) {\n const rad = (-this.element.rotation * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n\n return {\n dx: dx * cos - dy * sin,\n dy: dx * sin + dy * cos,\n };\n }\n\n /**\n * Rotate a point around a custom anchor point\n * Useful for rotation handles and pivot-based operations\n *\n * @param pointX - Point X in world space\n * @param pointY - Point Y in world space\n * @param anchorX - Anchor X in world space\n * @param anchorY - Anchor Y in world space\n * @param angleDegrees - Rotation angle in degrees (positive = clockwise in UI)\n * @returns Rotated point in world space\n */\n static rotatePointAroundAnchor(pointX: number, pointY: number, anchorX: number, anchorY: number, angleDegrees: number) {\n // Use negative angle for forward transform (matches rendering convention)\n const rad = (-angleDegrees * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n\n // Translate to anchor origin\n const dx = pointX - anchorX;\n const dy = pointY - anchorY;\n\n // Rotate\n const rotX = dx * cos - dy * sin;\n const rotY = dx * sin + dy * cos;\n\n // Translate back\n return {\n x: anchorX + rotX,\n y: anchorY + rotY,\n };\n }\n\n /**\n * Get rotation angle in radians for rendering (forward transform)\n * Returns NEGATIVE angle (positive UI rotation → clockwise canvas rotation)\n *\n * @returns Rotation in radians for use with ctx.rotate()\n */\n getRenderingAngle() {\n return (-this.element.rotation * Math.PI) / 180;\n }\n\n /**\n * Get rotation angle in radians for inverse transforms\n * Returns POSITIVE angle (for converting world → local)\n *\n * @returns Rotation in radians for inverse transforms\n */\n getInverseAngle() {\n return (this.element.rotation * Math.PI) / 180;\n }\n\n /**\n * Get precomputed cos/sin for rendering (forward transform)\n * Useful when you need to do multiple transformations with the same angle\n *\n * @returns Cosine and sine of rendering angle\n */\n getRenderingCosSin() {\n const rad = this.getRenderingAngle();\n return {\n cos: Math.cos(rad),\n sin: Math.sin(rad),\n };\n }\n\n /**\n * Get precomputed cos/sin for inverse transforms\n * Useful when you need to do multiple transformations with the same angle\n *\n * @returns Cosine and sine of inverse angle\n */\n getInverseCosSin() {\n const rad = this.getInverseAngle();\n return {\n cos: Math.cos(rad),\n sin: Math.sin(rad),\n };\n }\n\n /**\n * Create a transform for an element snapshot (from startData)\n * Useful during drag operations when you need to use the original rotation\n *\n * @param elementSnapshot - Object with x, y, rotation properties\n * @returns Transform instance for the snapshot\n */\n static fromSnapshot(elementSnapshot: Transformable) {\n return new Transform(elementSnapshot);\n }\n}\n\nexport default Transform;\n","/**\n * Google Fonts configuration for Snowcone Canvas\n * Extensive list of fonts optimized for t-shirt and merchandise design\n */\n\nexport type FontCategory =\n | 'sans-serif'\n | 'serif'\n | 'slab-serif' // NEW - Bold, chunky serifs (popular for t-shirts!)\n | 'display'\n | 'script'\n | 'vintage'\n | 'decorative'\n | 'monospace'\n | 'system';\n\nexport type FontSource = 'google' | 'monotype' | 'system';\n\nexport interface FontDefinition {\n name: string;\n category: FontCategory;\n weights?: number[]; // Available font weights (e.g., 400, 700)\n\n // Google Fonts (existing)\n googleFont: boolean; // Whether this needs to be loaded from Google Fonts\n\n // Monotype Fonts (NEW)\n monotypeFont?: boolean;\n monotypeId?: string; // For WebFont kit generation\n foundry?: string; // \"Monotype\", \"Linotype\", etc.\n tags?: string[]; // Descriptive tags from Monotype\n preview?: string; // Preview image URL\n\n // Metadata\n source?: FontSource;\n description?: string;\n}\n\n/**\n * Extensive font list for t-shirt design (~212 fonts)\n * Organized by category for easy browsing.\n *\n * Note: We deliberately DO NOT include \"system\" fonts (Arial, Impact,\n * Times New Roman, Helvetica, etc.). Those aren't on Google Fonts and\n * aren't installed on every platform we ship to — Android in\n * particular ships only Roboto + Noto + a handful of others, so any\n * design referencing Impact / Verdana / Tahoma renders as a default\n * sans there. Sticking to Google Fonts means whatever a user picks\n * looks the same on iOS, macOS, Windows, Android, and Chrome OS.\n */\nexport const TSHIRT_FONTS: FontDefinition[] = [\n // Bold/Display Fonts - Perfect for statements and headlines (49 fonts)\n {\n name: 'Bebas Neue',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Modern condensed display',\n },\n {\n name: 'Bevan',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold slab serif display',\n },\n {\n name: 'Bigshot One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold elegant display',\n },\n {\n name: 'Anton',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Super bold display font',\n },\n {\n name: 'Oswald',\n category: 'display',\n weights: [200, 300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Condensed sans-serif',\n },\n {\n name: 'Archivo Black',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Heavy weight display',\n },\n {\n name: 'Alfa Slab One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold slab serif, varsity style',\n },\n {\n name: 'Bowlby One SC',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Blocky display, sports style',\n },\n {\n name: 'Black Ops One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Military/stencil style',\n },\n {\n name: 'Abril Fatface',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold high-contrast display',\n },\n {\n name: 'Bangers',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Comic book style',\n },\n {\n name: 'Titan One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Heavy impactful display',\n },\n {\n name: 'Lilita One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Rounded display font',\n },\n {\n name: 'Fugaz One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold geometric display',\n },\n {\n name: 'Russo One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold sans display',\n },\n {\n name: 'Passion One',\n category: 'display',\n weights: [400, 700, 900],\n googleFont: true,\n description: 'Condensed bold display',\n },\n {\n name: 'Righteous',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: '1980s-style display',\n },\n {\n name: 'Bungee',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold urban/street style',\n },\n {\n name: 'Barlow Condensed',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Condensed sans display',\n },\n {\n name: 'Staatliches',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold condensed display',\n },\n {\n name: 'Rubik Mono One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Blocky monoline display',\n },\n {\n name: 'Ultra',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Ultra bold serif',\n },\n {\n name: 'Audiowide',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Tech/futuristic display',\n },\n {\n name: 'Changa One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Rounded bold display',\n },\n {\n name: 'Lobster',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold script display',\n },\n {\n name: 'Coda',\n category: 'display',\n weights: [400, 800],\n googleFont: true,\n description: 'Bold humanist sans',\n },\n {\n name: 'Teko',\n category: 'display',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Narrow sans display',\n },\n {\n name: 'Fredoka One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Rounded bold display',\n },\n {\n name: 'Caprasimo',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Retro rounded display',\n },\n {\n name: 'Fjalla One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold condensed sans',\n },\n {\n name: 'Secular One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Hebrew-inspired display',\n },\n {\n name: 'Saira',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Semi-condensed sans display',\n },\n {\n name: 'Barlow Semi Condensed',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Semi-condensed low-contrast',\n },\n {\n name: 'Exo',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Geometric technological display',\n },\n {\n name: 'Saira Condensed',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Condensed display font',\n },\n {\n name: 'Fredoka',\n category: 'display',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Rounded friendly display',\n },\n {\n name: 'Comfortaa',\n category: 'display',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Rounded geometric display',\n },\n {\n name: 'Questrial',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Simple sans-serif display',\n },\n {\n name: 'Lexend',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Variable sans for readability',\n },\n {\n name: 'Darker Grotesque',\n category: 'display',\n weights: [300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Contemporary grotesque sans',\n },\n {\n name: 'Bakbak One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Heavy condensed display',\n },\n {\n name: 'Turret Road',\n category: 'display',\n weights: [200, 300, 400, 500, 700, 800],\n googleFont: true,\n description: 'Geometric display font',\n },\n {\n name: 'Zilla Slab Highlight',\n category: 'display',\n weights: [400, 700],\n googleFont: true,\n description: 'Highlighted slab serif',\n },\n {\n name: 'Bree Serif',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Upright italic slab',\n },\n {\n name: 'Overpass',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Highway-inspired sans',\n },\n {\n name: 'Archivo',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Grotesque sans-serif',\n },\n {\n name: 'Chakra Petch',\n category: 'display',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Futuristic Thai-inspired',\n },\n {\n name: 'Concert One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Rounded grotesque display',\n },\n\n // Sans Serif - Modern and clean (44 fonts)\n {\n name: 'Roboto',\n category: 'sans-serif',\n weights: [100, 300, 400, 500, 700, 900],\n googleFont: true,\n description: 'Clean, modern sans-serif',\n },\n {\n name: 'Montserrat',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Geometric sans-serif',\n },\n {\n name: 'Open Sans',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Highly readable',\n },\n {\n name: 'Poppins',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Geometric with personality',\n },\n {\n name: 'Work Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Contemporary sans-serif',\n },\n {\n name: 'Raleway',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Elegant sans-serif',\n },\n {\n name: 'Lato',\n category: 'sans-serif',\n weights: [100, 300, 400, 700, 900],\n googleFont: true,\n description: 'Warm sans-serif',\n },\n {\n name: 'Nunito',\n category: 'sans-serif',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Rounded sans-serif',\n },\n {\n name: 'Outfit',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Modern geometric sans',\n },\n {\n name: 'Inter',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Clean interface font',\n },\n {\n name: 'Quicksand',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Friendly rounded sans',\n },\n {\n name: 'Rubik',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Slightly rounded sans',\n },\n {\n name: 'Barlow',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Low-contrast sans',\n },\n {\n name: 'Josefin Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Geometric vintage sans',\n },\n {\n name: 'Exo 2',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Contemporary geometric',\n },\n {\n name: 'Kanit',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Modern loopless Thai',\n },\n {\n name: 'Hind',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Clean humanist sans',\n },\n {\n name: 'Alata',\n category: 'sans-serif',\n weights: [400],\n googleFont: true,\n description: 'Geometric sans',\n },\n {\n name: 'Urbanist',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Geometric low-contrast',\n },\n {\n name: 'Manrope',\n category: 'sans-serif',\n weights: [200, 300, 400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Modern geometric sans',\n },\n {\n name: 'Be Vietnam Pro',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Modern Vietnamese-optimized sans',\n },\n {\n name: 'Belanosima',\n category: 'sans-serif',\n weights: [400, 600, 700],\n googleFont: true,\n description: 'Contemporary sans-serif',\n },\n {\n name: 'DM Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Low-contrast geometric sans',\n },\n {\n name: 'Source Sans 3',\n category: 'sans-serif',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Adobe sans-serif family',\n },\n {\n name: 'Source Sans Pro',\n category: 'sans-serif',\n weights: [200, 300, 400, 600, 700, 900],\n googleFont: true,\n description: 'Adobe sans-serif (legacy)',\n },\n {\n name: 'IBM Plex Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700],\n googleFont: true,\n description: 'IBM corporate sans',\n },\n {\n name: 'Karla',\n category: 'sans-serif',\n weights: [200, 300, 400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Grotesque sans-serif',\n },\n {\n name: 'PT Sans',\n category: 'sans-serif',\n weights: [400, 700],\n googleFont: true,\n description: 'Russian universal sans',\n },\n {\n name: 'Fira Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Mozilla humanist sans',\n },\n {\n name: 'Space Grotesk',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Proportional space age sans',\n },\n {\n name: 'Syne',\n category: 'sans-serif',\n weights: [400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Geometric variable sans',\n },\n {\n name: 'Libre Franklin',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Interpretation of Franklin Gothic',\n },\n {\n name: 'Alegreya Sans',\n category: 'sans-serif',\n weights: [100, 300, 400, 500, 700, 800, 900],\n googleFont: true,\n description: 'Humanist sans companion',\n },\n {\n name: 'Archivo Narrow',\n category: 'sans-serif',\n weights: [400, 500, 600, 700],\n googleFont: true,\n description: 'Narrow grotesque sans',\n },\n {\n name: 'Chivo',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Grotesque sans-serif',\n },\n {\n name: 'Neuton',\n category: 'sans-serif',\n weights: [200, 300, 400, 700, 800],\n googleFont: true,\n description: 'Serif-adjacent sans',\n },\n {\n name: 'Prompt',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Loopless Thai sans',\n },\n {\n name: 'Noto Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Google universal sans',\n },\n {\n name: 'Nunito Sans',\n category: 'sans-serif',\n weights: [200, 300, 400, 600, 700, 800, 900],\n googleFont: true,\n description: 'Rounded sans-serif',\n },\n {\n name: 'Proza Libre',\n category: 'sans-serif',\n weights: [400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Humanist sans-serif',\n },\n {\n name: 'Cabin',\n category: 'sans-serif',\n weights: [400, 500, 600, 700],\n googleFont: true,\n description: 'Humanist sans inspired by Edward Johnston',\n },\n {\n name: 'Signika',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Sans-serif for signage',\n },\n {\n name: 'Public Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Strong neutral sans',\n },\n {\n name: 'Plus Jakarta Sans',\n category: 'sans-serif',\n weights: [200, 300, 400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Geometric neo-grotesque',\n },\n {\n name: 'Red Hat Display',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Display variant of Red Hat sans',\n },\n\n // Script/Handwritten - Personal and creative (26 fonts)\n {\n name: 'Pacifico',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Retro surf script',\n },\n {\n name: 'Dancing Script',\n category: 'script',\n weights: [400, 500, 600, 700],\n googleFont: true,\n description: 'Elegant handwriting',\n },\n {\n name: 'Satisfy',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual handwritten',\n },\n {\n name: 'Permanent Marker',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Bold marker style',\n },\n {\n name: 'Caveat',\n category: 'script',\n weights: [400, 500, 600, 700],\n googleFont: true,\n description: 'Modern handwritten',\n },\n {\n name: 'Shadows Into Light',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Friendly handwriting',\n },\n {\n name: 'Kaushan Script',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Bold script',\n },\n {\n name: 'Amatic SC',\n category: 'script',\n weights: [400, 700],\n googleFont: true,\n description: 'Hand-drawn style',\n },\n {\n name: 'Indie Flower',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual handwriting',\n },\n {\n name: 'Cookie',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Brush script',\n },\n {\n name: 'Sacramento',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Elegant script',\n },\n {\n name: 'Allura',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Formal script',\n },\n {\n name: 'Great Vibes',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Elegant calligraphy',\n },\n {\n name: 'Yellowtail',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Flat-nib script',\n },\n {\n name: 'Courgette',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual upright script',\n },\n {\n name: 'Beth Ellen',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Handwritten marker style',\n },\n {\n name: 'Covered By Your Grace',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual handwritten',\n },\n {\n name: 'Nothing You Could Do',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Hand-drawn lettering',\n },\n {\n name: 'Homemade Apple',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Thin marker handwriting',\n },\n {\n name: 'Reenie Beanie',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual handwritten note style',\n },\n {\n name: 'Patrick Hand',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Handwriting font',\n },\n {\n name: 'Architects Daughter',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Architect-style handwriting',\n },\n {\n name: 'Handlee',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual handwriting',\n },\n {\n name: 'Zeyada',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Bouncy handwritten script',\n },\n {\n name: 'Gloria Hallelujah',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Comic-style handwriting',\n },\n {\n name: 'Bad Script',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual Cyrillic script',\n },\n\n // Serif - Classic and elegant (28 fonts)\n {\n name: 'Playfair Display',\n category: 'serif',\n weights: [400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'High-contrast elegant',\n },\n {\n name: 'Merriweather',\n category: 'serif',\n weights: [300, 400, 700, 900],\n googleFont: true,\n description: 'Readable serif',\n },\n {\n name: 'Lora',\n category: 'serif',\n weights: [400, 500, 600, 700],\n googleFont: true,\n description: 'Contemporary serif',\n },\n {\n name: 'Crimson Text',\n category: 'serif',\n weights: [400, 600, 700],\n googleFont: true,\n description: 'Classic book typography',\n },\n {\n name: 'PT Serif',\n category: 'serif',\n weights: [400, 700],\n googleFont: true,\n description: 'Transitional serif',\n },\n {\n name: 'Libre Baskerville',\n category: 'serif',\n weights: [400, 700],\n googleFont: true,\n description: 'Classic serif',\n },\n {\n name: 'Bitter',\n category: 'serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Contemporary slab serif',\n },\n {\n name: 'Cormorant Garamond',\n category: 'serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Display serif',\n },\n {\n name: 'Cinzel',\n category: 'serif',\n weights: [400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Classical serif capitals',\n },\n {\n name: 'Zilla Slab',\n category: 'serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Contemporary slab serif',\n },\n {\n name: 'Cardo',\n category: 'serif',\n weights: [400, 700],\n googleFont: true,\n description: 'Large text serif',\n },\n {\n name: 'Spectral',\n category: 'serif',\n weights: [200, 300, 400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Efficient serif',\n },\n {\n name: 'Literata',\n category: 'serif',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Modern serif',\n },\n {\n name: 'Cantata One',\n category: 'serif',\n weights: [400],\n googleFont: true,\n description: 'Humanist slab serif',\n },\n {\n name: 'Bellefair',\n category: 'serif',\n weights: [400],\n googleFont: true,\n description: 'Elegant high-contrast serif',\n },\n {\n name: 'Source Serif Pro',\n category: 'serif',\n weights: [200, 300, 400, 600, 700, 900],\n googleFont: true,\n description: 'Adobe serif family',\n },\n {\n name: 'Cormorant',\n category: 'serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Display serif typeface',\n },\n {\n name: 'Eczar',\n category: 'serif',\n weights: [400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Hybrid serif for screen',\n },\n {\n name: 'Alegreya',\n category: 'serif',\n weights: [400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Humanist serif',\n },\n {\n name: 'Fraunces',\n category: 'serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Variable display serif',\n },\n {\n name: 'Inknut Antiqua',\n category: 'serif',\n weights: [300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Display serif',\n },\n {\n name: 'BioRhyme',\n category: 'serif',\n weights: [200, 300, 400, 700, 800],\n googleFont: true,\n description: 'Rounded slab serif',\n },\n {\n name: 'Slabo 27px',\n category: 'serif',\n weights: [400],\n googleFont: true,\n description: 'Display serif at 27px',\n },\n {\n name: 'Slabo 13px',\n category: 'serif',\n weights: [400],\n googleFont: true,\n description: 'Text serif at 13px',\n },\n {\n name: 'Crimson Pro',\n category: 'serif',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Modern oldstyle serif',\n },\n {\n name: 'Noto Serif',\n category: 'serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Google universal serif',\n },\n {\n name: 'EB Garamond',\n category: 'serif',\n weights: [400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Classical Garamond revival',\n },\n {\n name: 'Vollkorn',\n category: 'serif',\n weights: [400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Quiet, moderate serif',\n },\n {\n name: 'Young Serif',\n category: 'serif',\n weights: [400, 500, 600, 700],\n googleFont: true,\n description: 'Modern retro serif',\n },\n\n // Vintage/Retro - Nostalgic and stylish (49 fonts)\n // Art Deco & 1920s-30s Style\n {\n name: 'Monoton',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Art deco display',\n },\n {\n name: 'Limelight',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Art deco geometric sans',\n },\n {\n name: 'Poiret One',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Art deco constructivist',\n },\n {\n name: 'Italiana',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Art deco calligraphic',\n },\n {\n name: 'Federo',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Art nouveau geometric',\n },\n {\n name: 'Dorsa',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Art deco display',\n },\n\n // Groovy/Psychedelic 60s-70s Style\n {\n name: 'Boogaloo',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Groovy rounded display',\n },\n {\n name: 'Gorditas',\n category: 'vintage',\n weights: [400, 700],\n googleFont: true,\n description: 'Bubble slab with hearts',\n },\n {\n name: 'Shrikhand',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Retro curved display',\n },\n {\n name: 'Kumar One',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Rounded bubble display',\n },\n {\n name: 'Chicle',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Bubble gum style',\n },\n {\n name: 'Flavors',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Retro bubble font',\n },\n {\n name: 'Chango',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Groovy rounded bold',\n },\n {\n name: 'Purple Purse',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Hippie funky casual',\n },\n {\n name: 'Super Dream',\n category: 'vintage',\n weights: [400],\n googleFont: false,\n description: '70s groovy bubble font',\n },\n\n // Retro Script & Sign Painting\n {\n name: 'Yesteryear',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Vintage cursive script',\n },\n {\n name: 'Mrs Sheppards',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Old American script',\n },\n {\n name: 'Condiment',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Vintage sign design',\n },\n\n // Western/Cowboy Style\n {\n name: 'Rye',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Western Victorian',\n },\n {\n name: 'Sancreek',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Western slab serif',\n },\n {\n name: 'Smokum',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Western playful slab',\n },\n {\n name: 'Diplomata',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Bold western display',\n },\n {\n name: 'Ewert',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Ornamental wood type',\n },\n {\n name: 'Macondo',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Western carnival style',\n },\n {\n name: 'Wellfleet',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Vintage slab serif',\n },\n {\n name: 'Ranchers',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Western slab serif',\n },\n\n // Circus/Carnival/Theater\n {\n name: 'Bungee Shade',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Layered circus display',\n },\n {\n name: 'Peralta',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Rounded circus style',\n },\n {\n name: 'Kelly Slab',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Retro slab serif',\n },\n {\n name: 'Snippet',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Vintage condensed',\n },\n\n // Retro Gaming & Tech\n {\n name: 'Press Start 2P',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: '8-bit retro gaming',\n },\n {\n name: 'VT323',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Terminal monospace',\n },\n\n // Victorian/Gothic/Medieval\n {\n name: 'UnifrakturCook',\n category: 'vintage',\n weights: [700],\n googleFont: true,\n description: 'Gothic blackletter',\n },\n {\n name: 'Sevillana',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Victorian ornate',\n },\n {\n name: 'Uncial Antiqua',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Medieval uncial',\n },\n {\n name: 'Piedra',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Stone carved display',\n },\n\n // Horror/Heavy Metal\n {\n name: 'Creepster',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Horror Halloween style',\n },\n {\n name: 'Nosifer',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Horror dripping',\n },\n {\n name: 'Eater',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Zombie horror',\n },\n {\n name: 'Metal Mania',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Heavy metal style',\n },\n {\n name: 'Butcherman',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Zombified horror',\n },\n\n // Typewriter & Classic\n {\n name: 'Special Elite',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Typewriter style',\n },\n\n // Soviet/Constructivist\n {\n name: 'Stalinist One',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Soviet constructivist',\n },\n {\n name: 'Ruslan Display',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Russian display style',\n },\n\n // Additional Retro Display\n {\n name: 'Corben',\n category: 'vintage',\n weights: [400, 700],\n googleFont: true,\n description: 'Mid-century rounded',\n },\n {\n name: 'Kranky',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Quirky handwritten retro',\n },\n\n // Decorative/Unique (10+ fonts)\n {\n name: 'Orbitron',\n category: 'decorative',\n weights: [400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Futuristic geometric',\n },\n {\n name: 'Playball',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Baseball script',\n },\n {\n name: 'Sedgwick Ave Display',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Graffiti style',\n },\n {\n name: 'Modak',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Rounded heavy display',\n },\n {\n name: 'Fascinate',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Broadway/theater',\n },\n {\n name: 'Bungee Inline',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Inline display',\n },\n {\n name: 'Emblema One',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Decorative caps',\n },\n {\n name: 'Fascinate Inline',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Inline Broadway',\n },\n {\n name: 'New Rocker',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Rock and roll',\n },\n {\n name: 'Bigelow Rules',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Playful cursive display',\n },\n\n // Monospace (useful for tech designs)\n {\n name: 'Roboto Mono',\n category: 'monospace',\n weights: [100, 200, 300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Modern monospace',\n },\n {\n name: 'Courier Prime',\n category: 'monospace',\n weights: [400, 700],\n googleFont: true,\n description: 'Typewriter monospace',\n },\n {\n name: 'Space Mono',\n category: 'monospace',\n weights: [400, 700],\n googleFont: true,\n description: 'Retro-futuristic mono',\n },\n {\n name: 'Inconsolata',\n category: 'monospace',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Humanist monospace',\n },\n];\n\n/**\n * Generate Google Fonts CSS URL for all fonts\n */\nexport function getGoogleFontsUrl(): string {\n const googleFonts = TSHIRT_FONTS.filter((f) => f.googleFont);\n\n const fontSpecs = googleFonts.map((font) => {\n const weights = font.weights || [400];\n const weightStr = weights.join(';');\n return `${font.name.replace(/ /g, '+')}:wght@${weightStr}`;\n });\n\n return `https://fonts.googleapis.com/css2?${fontSpecs.join('&')}&display=swap`;\n}\n\n/**\n * Get font names only (for dropdown)\n */\nexport function getFontNames(): string[] {\n return TSHIRT_FONTS.map((f) => f.name);\n}\n\n/**\n * Get fonts grouped by category\n */\nexport function getFontsByCategory(): Record<FontCategory, FontDefinition[]> {\n const grouped: Record<string, FontDefinition[]> = {};\n\n TSHIRT_FONTS.forEach((font) => {\n if (!grouped[font.category]) {\n grouped[font.category] = [];\n }\n grouped[font.category].push(font);\n });\n\n return grouped as Record<FontCategory, FontDefinition[]>;\n}\n\n/**\n * Category display names\n */\nexport const CATEGORY_LABELS: Record<FontCategory, string> = {\n system: 'System Fonts',\n display: 'Bold & Display',\n 'sans-serif': 'Sans Serif',\n serif: 'Serif',\n 'slab-serif': 'Slab Serif', // NEW - Chunky serifs for bold designs\n script: 'Script & Handwritten',\n vintage: 'Vintage & Retro',\n decorative: 'Decorative & Unique',\n monospace: 'Monospace',\n};\n","/**\n * Central constants file for theme values, colors, and magic numbers\n * Eliminates duplication across the codebase\n */\n\nimport { getFontNames } from './fonts/google-fonts.js';\n\n/**\n * Get the current theme's accent color from HeroUI CSS variables\n * Returns oklch() color from --accent variable\n */\nexport function getThemeAccentColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0% 0 0)'; // Fallback for SSR (black for default light theme)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--accent').trim();\n return color || 'oklch(0% 0 0)'; // Fallback if variable not found\n}\n\n/**\n * Get the current theme's accent foreground color from HeroUI CSS variables\n * This is the contrasting color for text/icons on accent backgrounds\n */\nexport function getThemeAccentForegroundColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(100% 0 0)'; // Fallback for SSR (white)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--accent-foreground').trim();\n return color || 'oklch(100% 0 0)'; // Fallback if variable not found\n}\n\n/**\n * Get the current theme name from the data-theme attribute\n */\nexport function getCurrentTheme(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'light';\n }\n \n if (element) {\n const themeAttr = element.getAttribute('data-theme');\n if (themeAttr) return themeAttr;\n \n const closestTheme = element.closest('[data-theme]');\n if (closestTheme) {\n return closestTheme.getAttribute('data-theme') || 'light';\n }\n }\n \n return document.documentElement.getAttribute('data-theme') || 'light';\n}\n\n/**\n * Get the default shape fill color based on the current theme\n * Returns colors that complement each theme's aesthetic\n */\nexport function getThemeShapeFillColor(): string {\n const theme = getCurrentTheme();\n\n // Use theme-appropriate blue shades for shapes\n if (theme.includes('dark')) {\n return '#60a5fa'; // Lighter blue for better visibility on dark backgrounds\n } else {\n return '#3b82f6'; // Classic blue for light theme\n }\n}\n\n/**\n * Get the spacing indicator color from the current theme\n */\nexport function getThemeSpacingColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return '#FF0000'; // Fallback for SSR\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--color-spacing-indicator').trim();\n return color || '#FF0000'; // Fallback if variable not found\n}\n\n/**\n * Get the current theme's accent color with alpha transparency\n * Handles both oklch() colors (from HeroUI theme) and hex colors (legacy)\n */\nexport function getThemeAccentColorWithAlpha(alpha: number = 0.1, element?: HTMLElement): string {\n const color = getThemeAccentColor(element);\n\n // If it's an oklch color, add alpha channel\n if (color.startsWith('oklch(')) {\n // oklch(L C H) → oklch(L C H / alpha)\n const withoutClosing = color.slice(0, -1); // Remove closing )\n return `${withoutClosing} / ${alpha})`;\n }\n\n // Legacy hex color support\n if (color.startsWith('#')) {\n const hex = color.replace('#', '');\n const r = parseInt(hex.substring(0, 2), 16);\n const g = parseInt(hex.substring(2, 4), 16);\n const b = parseInt(hex.substring(4, 6), 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n\n // Fallback - return color as-is\n return color;\n}\n\n/**\n * Get the current theme's text selection color from CSS variables\n * Returns the theme-specific selection highlight color (already includes opacity)\n */\nexport function getThemeTextSelectionColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0% 0 0 / 0.45)'; // Fallback for SSR (default light theme - black at 45%)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--color-text-selection').trim();\n return color || 'oklch(0% 0 0 / 0.45)'; // Fallback if variable not found\n}\n\n/**\n * Get the current theme's hover accent color\n */\nexport function getThemeAccentHoverColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return '#b5f747'; // Fallback for SSR (light theme default)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--color-accent-hover').trim();\n return color || '#b5f747'; // Fallback if variable not found\n}\n\n/**\n * Get the current theme's hover accent color with alpha channel\n */\nexport function getThemeAccentHoverColorWithAlpha(alpha: number = 0.1, element?: HTMLElement): string {\n const color = getThemeAccentHoverColor(element);\n\n // If it's an oklch color, add alpha channel\n if (color.startsWith('oklch(')) {\n // oklch(L C H) → oklch(L C H / alpha)\n const withoutClosing = color.slice(0, -1); // Remove closing )\n return `${withoutClosing} / ${alpha})`;\n }\n\n // Legacy hex color support\n if (color.startsWith('#')) {\n const hex = color.replace('#', '');\n const r = parseInt(hex.substring(0, 2), 16);\n const g = parseInt(hex.substring(2, 4), 16);\n const b = parseInt(hex.substring(4, 6), 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n\n // Fallback - return color as-is\n return color;\n}\n\n/**\n * Get the background color for canvas-rendered tooltips.\n * Uses --foreground so the tooltip is dark in light mode and light in dark mode,\n * ensuring contrast against the typically light artboard canvas.\n */\nexport function getThemeTooltipBackground(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0.2103 0.0059 285.89)'; // Fallback for SSR (dark for light theme)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--foreground').trim();\n return color || 'oklch(0.2103 0.0059 285.89)';\n}\n\n/**\n * Get the text color for canvas-rendered tooltips.\n * Uses --background so text contrasts with getThemeTooltipBackground().\n */\nexport function getThemeTooltipForeground(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0.9607 0 0)'; // Fallback for SSR (light for light theme)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--background').trim();\n return color || 'oklch(0.9607 0 0)';\n}\n\n/**\n * Get the shadow color for canvas-rendered tooltips.\n * Derives from the tooltip background at 0.2 opacity.\n */\nexport function getThemeTooltipShadowColor(element?: HTMLElement): string {\n const bg = getThemeTooltipBackground(element);\n if (bg.startsWith('oklch(')) {\n return `${bg.slice(0, -1)} / 0.2)`;\n }\n return 'rgba(0,0,0,0.2)';\n}\n\n/**\n * Get the border color for artboard outlines.\n * Uses --divider for a theme-appropriate muted border that adapts to light/dark mode.\n */\nexport function getThemeArtboardBorderColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0% 0 0 / 0.2)'; // Fallback for SSR\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--divider').trim();\n return color || 'oklch(0% 0 0 / 0.2)';\n}\n\n/**\n * Get the label text color for artboard names.\n * Uses --foreground at 50% opacity for a muted appearance that adapts to light/dark mode.\n */\nexport function getThemeArtboardLabelColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0% 0 0 / 0.5)'; // Fallback for SSR\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--foreground').trim();\n if (color.startsWith('oklch(')) {\n return `${color.slice(0, -1)} / 0.5)`;\n }\n return color || 'oklch(0% 0 0 / 0.5)';\n}\n\n/**\n * Get the current theme's hover border color (uses hover accent color)\n */\nexport function getThemeHoverBorderColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return '#b5f747'; // Fallback for SSR (uses hover accent color)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--color-accent-hover-border').trim();\n return color || '#b5f747'; // Fallback if variable not found\n}\n\n/**\n * Get the pen tool path stroke color.\n * Uses --foreground so the path line is dark in light mode and light in dark mode,\n * ensuring visibility against both white and dark artboard backgrounds.\n */\nexport function getThemePenPathColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0.2103 0.0059 285.89)'; // Fallback for SSR (dark for light theme)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--foreground').trim();\n return color || 'oklch(0.2103 0.0059 285.89)';\n}\n\n/**\n * Get the fill color for pen tool anchor points.\n * Uses --background so anchor circles are visible against the accent-color stroke on both light and dark themes.\n */\nexport function getThemePenAnchorFillColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(1 0 0)'; // Fallback for SSR (white, suitable for light theme)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--background').trim();\n return color || 'oklch(1 0 0)';\n}\n\n/**\n * Get the pen tool bezier handle color (control lines and dots).\n * Uses --foreground at 50% opacity for a muted appearance that adapts to light/dark mode.\n */\nexport function getThemePenHandleColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0.2103 0.0059 285.89 / 0.5)'; // Fallback for SSR (muted dark)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--foreground').trim();\n if (color.startsWith('oklch(')) {\n return `${color.slice(0, -1)} / 0.5)`;\n }\n return color || 'oklch(0.2103 0.0059 285.89 / 0.5)';\n}\n\n// Selection and UI colors - dynamic based on current theme\n// Call these functions at render time (not at import time) to get the current theme color.\nexport function getSelectionColor(): string {\n return getThemeAccentColor();\n}\nexport function getSelectionFillColor(): string {\n return getThemeAccentColorWithAlpha(0.1);\n}\nexport const HOVER_COLOR = '#fbbf24'; // Amber\n\n// Preview rendering settings (UI only, never exported)\nexport const PREVIEW_ELEMENT_OPACITY = 1.0; // Full opacity\n\n// Default element colors\nexport const DEFAULT_ARTBOARD_COLOR = '#ffffff'; // White\n// Note: DEFAULT_SHAPE_FILL_COLOR is deprecated - use getThemeShapeFillColor() instead\nexport const DEFAULT_SHAPE_FILL_COLOR = '#3b82f6'; // Blue (fallback only)\n\n// Handle dimensions\nexport const HANDLE_SIZE = 12;\nexport const HANDLE_RADIUS = 6;\nexport const ROTATION_HANDLE_DISTANCE = 20; // Distance from bottom edge to rotation handle\nexport const HANDLE_HIT_TOLERANCE = 8; // Hit test tolerance for handles\n\n// Crop handle dimensions - VISUAL sizes (what users see)\nexport const CORNER_HANDLE_VISUAL_RADIUS = 6; // Visual radius of circular corner handles\nexport const EDGE_HANDLE_VISUAL_LENGTH = 24; // Visual length of edge rectangles\n\n// Crop handle dimensions - HIT ZONE sizes (larger for comfortable touch/mouse interaction)\nexport const CORNER_HANDLE_HIT_RADIUS = 16; // Hit zone radius for circular corner handles\nexport const EDGE_HANDLE_LENGTH = 36; // Hit zone length for edge handles\nexport const EDGE_HANDLE_HIT_WIDTH = 24; // Hit zone WIDTH for edge handles (perpendicular to edge)\n\n// Snap system\nexport const ALIGNMENT_SNAP_THRESHOLD = 2; // Distance threshold for edge/center alignment snapping in pixels\nexport const SPACING_SNAP_THRESHOLD = 4; // Distance threshold for spacing pattern snapping in pixels\n\n// Spacing indicator colors\n// Note: Use getThemeSpacingColor() for dynamic theme-based colors\nexport const SPACING_LABEL_TEXT_COLOR = '#FFFFFF'; // White text for spacing labels\nexport const SPACING_LABEL_BORDER_RADIUS = 2; // Border radius for spacing labels in pixels\n\n// Crop constraints\nexport const MIN_CROP_SIZE = 0.1; // Minimum crop size (10% of image)\n\n// Font options - imported from Google Fonts configuration\nexport const FONT_FAMILIES = getFontNames();\n\nexport const FONT_SIZES = [8, 10, 12, 14, 16, 18, 20, 24, 28, 32, 36, 48, 64, 72, 96, 128];\n\n// Text layout\nexport const HORIZONTAL_PADDING = 0;\nexport const LINE_HEIGHT_MULTIPLIER = 1.2;\n\n// Transform constraints\nexport const MIN_WIDTH = 50;\nexport const MIN_FONT_SIZE = 8;\nexport const MAX_FONT_SIZE = 999;\n\n// Names that, even though they're allowed CSS values, we do NOT\n// want to load from Google Fonts. Used by the export worker and\n// FontAnalyzer as a deny-list. Kept for safety when legacy designs\n// reference these family names — we don't want to chase a 404 on\n// Google's CDN trying to download \"Impact\" from there. The font\n// browser doesn't expose any of these for selection (see\n// `TSHIRT_FONTS` in `fonts/google-fonts.ts`).\nexport const SYSTEM_FONTS = new Set([\n 'Arial', 'Arial Black', 'Verdana', 'Tahoma', 'Trebuchet MS',\n 'Impact', 'Times New Roman', 'Georgia', 'Garamond', 'Courier New',\n 'Brush Script MT', 'Palatino', 'Baskerville', 'Helvetica',\n 'Helvetica Neue', 'Comic Sans MS', 'Lucida Console', 'Monaco',\n 'Consolas', 'Courier',\n]);\n\n// Wave transform\nexport const WAVE_SKEW_FACTOR = 0.3;\nexport const WAVE_PATH_STEPS = 100;\n","/**\n * Centralized debug logger utility\n * Replaces scattered console.log statements with a consistent, filterable logging system\n */\n\nexport enum LogLevel {\n DEBUG = 0,\n INFO = 1,\n WARN = 2,\n ERROR = 3,\n NONE = 4,\n}\n\nclass Logger {\n private level: LogLevel = LogLevel.DEBUG;\n private enabled = typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production'; // Only enabled in development by default\n\n /**\n * Set the minimum log level to display\n */\n setLevel(level: LogLevel): void {\n this.level = level;\n }\n\n /**\n * Enable or disable logging globally\n */\n setEnabled(enabled: boolean): void {\n this.enabled = enabled;\n }\n\n /**\n * Get current log level\n */\n getLevel(): LogLevel {\n return this.level;\n }\n\n /**\n * Check if logging is enabled\n */\n isEnabled(): boolean {\n return this.enabled;\n }\n\n /**\n * Log debug message (verbose, development only)\n */\n debug(_message: string, ..._args: unknown[]): void {\n // Debug logging intentionally no-ops in production\n }\n\n /**\n * Log info message (general information)\n */\n info(message: string, ...args: unknown[]): void {\n if (this.enabled && this.level <= LogLevel.INFO) {\n console.info(`[INFO] ${message}`, ...args);\n }\n }\n\n /**\n * Log warning message (potential issues)\n */\n warn(message: string, ...args: unknown[]): void {\n if (this.enabled && this.level <= LogLevel.WARN) {\n console.warn(`[WARN] ${message}`, ...args);\n }\n }\n\n /**\n * Log error message (critical issues)\n */\n error(message: string, ...args: unknown[]): void {\n if (this.enabled && this.level <= LogLevel.ERROR) {\n console.error(`[ERROR] ${message}`, ...args);\n }\n }\n\n /**\n * Create a scoped logger with a prefix for a specific module\n */\n scope(scopeName: string): ScopedLogger {\n return new ScopedLogger(this, scopeName);\n }\n}\n\n/**\n * Scoped logger for specific modules/components\n */\nclass ScopedLogger {\n constructor(private logger: Logger, private scopeName: string) {}\n\n debug(message: string, ...args: unknown[]): void {\n this.logger.debug(`[${this.scopeName}] ${message}`, ...args);\n }\n\n info(message: string, ...args: unknown[]): void {\n this.logger.info(`[${this.scopeName}] ${message}`, ...args);\n }\n\n warn(message: string, ...args: unknown[]): void {\n this.logger.warn(`[${this.scopeName}] ${message}`, ...args);\n }\n\n error(message: string, ...args: unknown[]): void {\n this.logger.error(`[${this.scopeName}] ${message}`, ...args);\n }\n}\n\n// Export singleton instance\nexport const logger = new Logger();\n\n// Export scoped logger factory\nexport const createLogger = (scopeName: string): ScopedLogger => {\n return logger.scope(scopeName);\n};\n\n// Usage examples:\n// import { logger } from '@/utils/logger';\n// logger.debug('Canvas rendered', { width, height });\n// logger.info('Element added', element);\n// logger.warn('Performance issue detected', duration);\n// logger.error('Failed to export', error);\n\n// Or create a scoped logger:\n// import { createLogger } from '@/utils/logger';\n// const log = createLogger('CanvasEditor');\n// log.debug('Resize started');\n","/**\n * ImageLoadEvents - Simple event system for notifying when images finish loading\n *\n * This solves the issue where the canvas shows \"Loading...\" even after an image\n * has loaded, because React doesn't know the element's internal state changed.\n *\n * When ImageElement finishes loading an image, it emits an event.\n * CanvasEditor subscribes to this event and triggers a re-render.\n */\n\nimport { createLogger } from '../utils/logger.js';\n\nconst logger = createLogger('ImageLoadEvents');\n\ntype ImageLoadListener = (elementId: string) => void;\n\nconst listeners = new Set<ImageLoadListener>();\n\n/**\n * Subscribe to image load events\n * @param listener - Callback called when any ImageElement finishes loading\n * @returns Unsubscribe function\n */\nexport function subscribeToImageLoads(listener: ImageLoadListener): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n}\n\n/**\n * Emit an image load event\n * Called by ImageElement when an image finishes loading\n * @param elementId - The ID of the element that finished loading\n */\nexport function emitImageLoaded(elementId: string): void {\n listeners.forEach(listener => {\n try {\n listener(elementId);\n } catch (error) {\n logger.error('Listener error:', error);\n }\n });\n}\n","/**\n * Shared image cache for HTMLImageElement instances.\n * Prevents duplicate loading of the same URL across elements.\n * Reference-counted -- images are evicted when no elements reference them.\n *\n * Usage:\n * const cache = ImageCache.getInstance();\n * const img = cache.acquire('https://example.com/photo.jpg');\n * // ... use img ...\n * cache.release('https://example.com/photo.jpg');\n */\n\ninterface CacheEntry {\n image: HTMLImageElement;\n refCount: number;\n loaded: boolean;\n}\n\nexport class ImageCache {\n private static instance: ImageCache;\n private cache: Map<string, CacheEntry>;\n\n private constructor() {\n this.cache = new Map();\n }\n\n /**\n * Get the singleton ImageCache instance.\n */\n static getInstance(): ImageCache {\n if (!ImageCache.instance) {\n ImageCache.instance = new ImageCache();\n }\n return ImageCache.instance;\n }\n\n /**\n * Acquire an HTMLImageElement for the given URL.\n * If the URL is already cached, increments the reference count and returns the existing element.\n * If not cached, creates a new HTMLImageElement, starts loading, and caches it.\n *\n * @param url - The image URL to load\n * @returns The cached or newly created HTMLImageElement\n */\n acquire(url: string): HTMLImageElement {\n const existing = this.cache.get(url);\n if (existing) {\n existing.refCount++;\n return existing.image;\n }\n\n // Create new entry\n const img = new Image();\n img.crossOrigin = 'anonymous';\n\n const entry: CacheEntry = {\n image: img,\n refCount: 1,\n loaded: false,\n };\n\n img.onload = () => {\n entry.loaded = true;\n };\n\n img.onerror = () => {\n // Mark as loaded (with error) so callers can detect failure\n // The image element itself will have naturalWidth === 0\n entry.loaded = true;\n };\n\n this.cache.set(url, entry);\n\n // Don't attempt to load synthetic URIs (e.g. builtin-mask:splatter).\n // The caller is responsible for setting img.src to a real URL or data URL.\n if (!url.includes(':') || /^https?:\\/\\/|^data:|^blob:/.test(url)) {\n img.src = url;\n }\n\n return img;\n }\n\n /**\n * Release a reference to a cached image.\n * When the reference count reaches zero, the entry is evicted from the cache.\n *\n * @param url - The image URL to release\n */\n release(url: string): void {\n const entry = this.cache.get(url);\n if (!entry) return;\n\n entry.refCount--;\n if (entry.refCount <= 0) {\n this.cache.delete(url);\n }\n }\n\n /**\n * Get a cached HTMLImageElement without incrementing the reference count.\n * Returns undefined if the URL is not in the cache.\n *\n * @param url - The image URL to look up\n */\n get(url: string): HTMLImageElement | undefined {\n return this.cache.get(url)?.image;\n }\n\n /**\n * Check if a URL is in the cache.\n *\n * @param url - The image URL to check\n */\n has(url: string): boolean {\n return this.cache.has(url);\n }\n\n /**\n * Get cache statistics.\n *\n * @returns Object with entry count and total reference count across all entries\n */\n getStats(): { entries: number; totalRefs: number } {\n let totalRefs = 0;\n for (const entry of this.cache.values()) {\n totalRefs += entry.refCount;\n }\n return { entries: this.cache.size, totalRefs };\n }\n\n /**\n * Clear the entire cache.\n * Primarily for testing -- removes all entries regardless of reference count.\n */\n clear(): void {\n this.cache.clear();\n }\n}\n","/**\n * ImageElement - Element for displaying images\n * Images maintain aspect ratio and can be dragged, scaled, and rotated\n */\n\nimport { BaseElement } from './BaseElement.js';\nimport { RotationUtils } from './RotationUtils.js';\nimport { Transform } from './Transform.js';\nimport { CORNER_HANDLE_HIT_RADIUS, EDGE_HANDLE_LENGTH, EDGE_HANDLE_HIT_WIDTH } from '../constants.js';\nimport { emitImageLoaded } from './ImageLoadEvents.js';\nimport { ImageCache } from './ImageCache.js';\nimport { createLogger } from '../utils/logger.js';\nimport type { ImageTransformData, ImageElementConfig, ResizeAnchor, BoundingBox, Point, TransformStartData } from '../types/index.js';\n\nconst logger = createLogger('ImageElement');\n\n/** Image loading state for retry logic and UI feedback */\nexport type ImageLoadState = 'loading' | 'loaded' | 'error' | 'retrying';\n\n/** Default timeout for image loading in milliseconds */\nconst IMAGE_LOAD_TIMEOUT_MS = 30000;\n\n/** Maximum number of retry attempts */\nconst IMAGE_LOAD_MAX_RETRIES = 3;\n\n/** Base delay for exponential backoff in milliseconds */\nconst IMAGE_LOAD_BASE_DELAY_MS = 1000;\n\n/** Helper to access transformData as ImageTransformData from TransformStartData */\nfunction imageTransformData(startData: TransformStartData): ImageTransformData {\n return startData.transformData as unknown as ImageTransformData;\n}\n\nexport class ImageElement extends BaseElement {\n declare transformType: 'image';\n declare transformData: ImageTransformData;\n imageUrl: string;\n imageLoaded: boolean;\n imageElement: HTMLImageElement | null;\n imageAspectRatio: number;\n isCropping: boolean;\n onLoadCallback: ((element: ImageElement) => void) | null;\n isSvg: boolean; // Track if this is an SVG image\n preserveDimensions: boolean; // If true, don't recalculate dimensions on image load\n imageLoadState: ImageLoadState; // Current image loading state\n imageLoadError: Error | null; // Error from last failed load attempt\n private svgBlobUrl: string | null = null; // Track blob URL for SVG high-DPI rendering cleanup\n private loadTimeoutId: ReturnType<typeof setTimeout> | null = null; // Timeout for current load attempt\n private retryAttempt: number = 0; // Current retry attempt number\n private cachedImageUrl: string | null = null; // Track URL acquired from ImageCache for release on destroy\n\n constructor(config: Partial<ImageElementConfig> = {}) {\n super(config);\n this.transformType = 'image';\n\n // Initialize transformData with proper ImageTransformData type\n this.transformData = {\n type: 'image',\n width: config.transformData?.width || 200,\n height: config.transformData?.height || 200,\n cropX: config.transformData?.cropX ?? 0,\n cropY: config.transformData?.cropY ?? 0,\n cropWidth: config.transformData?.cropWidth ?? 1,\n cropHeight: config.transformData?.cropHeight ?? 1,\n flipHorizontal: config.transformData?.flipHorizontal ?? false,\n flipVertical: config.transformData?.flipVertical ?? false,\n borderRadius: config.transformData?.borderRadius ?? 0,\n };\n\n // Image-specific properties\n this.imageUrl = config.imageUrl || '';\n this.imageLoaded = false;\n this.imageElement = null;\n this.imageAspectRatio = config.imageAspectRatio || 1; // width / height\n this.isSvg = config.imageUrl?.toLowerCase().endsWith('.svg') || false;\n this.preserveDimensions = config.preserveDimensions ?? false;\n this.imageLoadState = config.imageUrl ? 'loading' : 'loaded';\n this.imageLoadError = null;\n\n // Crop mode state\n this.isCropping = false;\n\n // Callback for when image loads and dimensions change\n this.onLoadCallback = config.onLoadCallback || null;\n\n // Load the image\n if (this.imageUrl) {\n this.loadImage(this.imageUrl);\n }\n }\n\n /**\n * Load image from URL\n * For SVG files, we render to a high-DPI canvas to maintain quality\n */\n loadImage(url: string): void {\n const isSvg = url.toLowerCase().endsWith('.svg');\n this.isSvg = isSvg;\n\n if (isSvg) {\n // For SVG: Load at high resolution and convert to canvas\n this.loadSvgAsHighDpi(url);\n } else {\n // For regular images: Load normally\n this.loadRegularImage(url);\n }\n }\n\n /**\n * Load regular raster image (PNG, JPG, etc.) with timeout and retry logic.\n * Uses exponential backoff: 1s, 2s, 4s between retries.\n */\n private loadRegularImage(url: string): void {\n this.retryAttempt = 0;\n this.imageLoadError = null;\n this.imageLoadState = 'loading';\n this.attemptLoadRegularImage(url);\n }\n\n /**\n * Single attempt to load a regular image with timeout.\n * Called by loadRegularImage and retry logic.\n *\n * Uses the shared ImageCache to avoid duplicate loading of the same URL\n * across multiple ImageElement instances.\n */\n private attemptLoadRegularImage(url: string): void {\n // Clean up any previous timeout\n this.clearLoadTimeout();\n\n // Release previous cache entry if switching URLs\n this.releaseImageCache();\n\n const imageCache = ImageCache.getInstance();\n const img = imageCache.acquire(url);\n this.cachedImageUrl = url;\n\n let settled = false;\n\n const settle = () => {\n settled = true;\n this.clearLoadTimeout();\n };\n\n // If the image is already loaded (from cache), handle immediately\n if (img.complete && img.naturalWidth > 0) {\n settle();\n this.imageElement = img;\n this.imageLoaded = true;\n this.imageLoadState = 'loaded';\n this.imageLoadError = null;\n this.imageAspectRatio = img.naturalWidth / img.naturalHeight;\n\n if (!this.preserveDimensions) {\n this.transformData.height = this.transformData.width / this.imageAspectRatio;\n }\n\n if (this.onLoadCallback) {\n this.onLoadCallback(this.clone());\n }\n\n emitImageLoaded(this.id);\n return;\n }\n\n // Store original handlers in case the image already has them from the cache\n const originalOnload = img.onload;\n const originalOnerror = img.onerror;\n\n img.onload = (ev) => {\n if (settled) return;\n settle();\n\n this.imageElement = img;\n this.imageLoaded = true;\n this.imageLoadState = 'loaded';\n this.imageLoadError = null;\n this.imageAspectRatio = img.naturalWidth / img.naturalHeight;\n\n // Only recalculate dimensions if not explicitly provided (preserveDimensions flag)\n // This allows callers to provide explicit \"cover\" sizing that won't be overwritten\n if (!this.preserveDimensions) {\n // Update display height to maintain aspect ratio based on current width\n this.transformData.height = this.transformData.width / this.imageAspectRatio;\n }\n\n // Notify parent that dimensions have changed\n if (this.onLoadCallback) {\n this.onLoadCallback(this.clone());\n }\n\n // Emit global event to trigger canvas re-render\n // This ensures the \"Loading...\" placeholder is replaced with the actual image\n emitImageLoaded(this.id);\n\n // Call original handler if present (for cache bookkeeping)\n if (originalOnload && originalOnload !== img.onload) {\n (originalOnload as EventListener)(ev);\n }\n };\n\n img.onerror = (ev) => {\n if (settled) return;\n settle();\n\n const error = new Error(`Failed to load image: ${url}`);\n this.handleLoadFailure(url, error);\n\n // Call original handler if present\n if (originalOnerror && originalOnerror !== img.onerror) {\n (originalOnerror as EventListener)(ev as Event);\n }\n };\n\n // Set timeout for this attempt\n this.loadTimeoutId = setTimeout(() => {\n if (settled) return;\n settle();\n\n // Abort the in-progress load by clearing the src\n img.src = '';\n const error = new Error(`Image load timed out after ${IMAGE_LOAD_TIMEOUT_MS}ms: ${url}`);\n this.handleLoadFailure(url, error);\n }, IMAGE_LOAD_TIMEOUT_MS);\n\n // If the image is newly created by the cache, src is already set.\n // If it's a retry on a previously failed image, we need to re-set src.\n if (!img.src || img.src !== url) {\n img.src = url;\n }\n\n // Re-check complete after attaching handlers — the image may have loaded\n // between the first check (line 146) and the handler attachment above.\n // This race is common with data URLs and cached images where the browser\n // fires onload synchronously during img.src assignment.\n if (!settled && img.complete && img.naturalWidth > 0) {\n img.onload?.(new Event('load'));\n }\n }\n\n /**\n * Handle a load failure - either retry with backoff or set error state.\n */\n private handleLoadFailure(url: string, error: Error): void {\n this.retryAttempt++;\n\n if (this.retryAttempt < IMAGE_LOAD_MAX_RETRIES) {\n // Exponential backoff: 1s, 2s, 4s, ...\n const delay = IMAGE_LOAD_BASE_DELAY_MS * Math.pow(2, this.retryAttempt - 1);\n logger.warn(`Image load attempt ${this.retryAttempt} failed, retrying in ${delay}ms:`, url);\n this.imageLoadState = 'retrying';\n this.imageLoadError = error;\n\n // Emit event so placeholder updates to show \"Retrying...\"\n emitImageLoaded(this.id);\n\n this.loadTimeoutId = setTimeout(() => {\n this.attemptLoadRegularImage(url);\n }, delay);\n } else {\n // All retries exhausted\n logger.error(`Image load failed after ${IMAGE_LOAD_MAX_RETRIES} attempts:`, url);\n this.imageLoaded = false;\n this.imageLoadState = 'error';\n this.imageLoadError = error;\n\n // Emit event so placeholder updates to show error state\n emitImageLoaded(this.id);\n }\n }\n\n /**\n * Clear any pending load timeout\n */\n private clearLoadTimeout(): void {\n if (this.loadTimeoutId !== null) {\n clearTimeout(this.loadTimeoutId);\n this.loadTimeoutId = null;\n }\n }\n\n /**\n * Release the current image from the shared cache.\n * Called when switching URLs or destroying the element.\n */\n private releaseImageCache(): void {\n if (this.cachedImageUrl) {\n ImageCache.getInstance().release(this.cachedImageUrl);\n this.cachedImageUrl = null;\n }\n }\n\n /**\n * Manually retry loading the image after a failure.\n * Resets retry count and starts fresh.\n */\n retryImageLoad(): void {\n if (!this.imageUrl) return;\n\n logger.info('Manual retry triggered for image:', this.imageUrl);\n this.clearLoadTimeout();\n this.imageLoaded = false;\n this.imageElement = null;\n\n if (this.isSvg) {\n this.imageLoadState = 'loading';\n this.imageLoadError = null;\n this.loadSvgAsHighDpi(this.imageUrl);\n } else {\n this.loadRegularImage(this.imageUrl);\n }\n }\n\n /**\n * Revoke any existing SVG blob URL to prevent memory leaks\n * iOS Safari has strict blob URL limits; this prevents accumulation\n */\n private revokeSvgBlobUrl(): void {\n if (this.svgBlobUrl) {\n URL.revokeObjectURL(this.svgBlobUrl);\n this.svgBlobUrl = null;\n }\n }\n\n /**\n * Load SVG and convert to high-DPI canvas for crisp rendering\n * SVG is rendered at 4x resolution to maintain quality when scaled\n */\n private loadSvgAsHighDpi(url: string): void {\n // Revoke any existing SVG blob URL before creating a new one\n this.revokeSvgBlobUrl();\n const tempImg = new Image();\n tempImg.crossOrigin = 'anonymous';\n\n tempImg.onload = () => {\n // Render SVG to a high-DPI canvas (4x resolution)\n const scale = 4; // 4x resolution for crisp quality\n const canvas = document.createElement('canvas');\n canvas.width = tempImg.width * scale;\n canvas.height = tempImg.height * scale;\n\n const ctx = canvas.getContext('2d')!;\n ctx.scale(scale, scale);\n ctx.drawImage(tempImg, 0, 0);\n\n // Convert canvas to blob and create object URL\n canvas.toBlob((blob) => {\n if (!blob) {\n logger.error('Failed to convert SVG to blob');\n this.imageLoaded = false;\n return;\n }\n\n // Store blob URL for later cleanup (iOS Safari memory management)\n this.svgBlobUrl = URL.createObjectURL(blob);\n\n // Now load the high-DPI version\n this.imageElement = new Image();\n this.imageElement.crossOrigin = 'anonymous';\n this.imageElement.onload = () => {\n this.imageLoaded = true;\n this.imageAspectRatio = this.imageElement!.width / this.imageElement!.height;\n\n // Only recalculate dimensions if not explicitly provided (preserveDimensions flag)\n if (!this.preserveDimensions) {\n // Update display height to maintain aspect ratio\n this.transformData.height = this.transformData.width / this.imageAspectRatio;\n }\n\n // Notify parent\n if (this.onLoadCallback) {\n this.onLoadCallback(this.clone());\n }\n\n // Emit global event to trigger canvas re-render\n // This ensures the \"Loading...\" placeholder is replaced with the actual image\n emitImageLoaded(this.id);\n\n // Note: We no longer revoke the blob URL here via setTimeout\n // Instead, it's tracked in this.svgBlobUrl and cleaned up via destroy()\n };\n\n this.imageElement.src = this.svgBlobUrl;\n }, 'image/png');\n };\n\n tempImg.onerror = () => {\n logger.error('Failed to load SVG:', url);\n this.imageLoaded = false;\n };\n\n tempImg.src = url;\n }\n\n /**\n * Get bounding box in world coordinates\n * Returns the unrotated dimensions - rotation is handled by the renderer\n * When not in crop mode, returns only the cropped area dimensions\n */\n getBoundingBox(): BoundingBox {\n if (this.isCropping) {\n // In crop mode: Need to show the full image frame\n // this.x, this.y is at the center of the visible crop area\n // We need to calculate where the full frame would be, accounting for rotation and flips\n\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n // Offset from crop center to frame center in local space (before rotation and flip)\n let localOffsetX =\n this.transformData.width / 2 - (this.transformData.cropX * this.transformData.width + cropWidth / 2);\n let localOffsetY =\n this.transformData.height / 2 - (this.transformData.cropY * this.transformData.height + cropHeight / 2);\n\n // Account for flips: when flipped, the offset direction is reversed\n const flipH = this.transformData.flipHorizontal ? -1 : 1;\n const flipV = this.transformData.flipVertical ? -1 : 1;\n localOffsetX *= flipH;\n localOffsetY *= flipV;\n\n // Rotate the offset to world space\n const cos = Math.cos(RotationUtils.toRadians(this.rotation));\n const sin = Math.sin(RotationUtils.toRadians(this.rotation));\n const worldOffsetX = localOffsetX * cos - localOffsetY * sin;\n const worldOffsetY = localOffsetX * sin + localOffsetY * cos;\n\n // Calculate frame center in world space\n const frameCenterX = this.x + worldOffsetX;\n const frameCenterY = this.y + worldOffsetY;\n\n return {\n x: frameCenterX - this.transformData.width / 2,\n y: frameCenterY - this.transformData.height / 2,\n width: this.transformData.width,\n height: this.transformData.height,\n };\n } else {\n // Not in crop mode: only the cropped area\n // this.x, this.y is already at the center of the cropped area\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n return {\n x: this.x - cropWidth / 2,\n y: this.y - cropHeight / 2,\n width: cropWidth,\n height: cropHeight,\n };\n }\n }\n\n /**\n * Get visual bounding box - returns the cropped area bounds\n * This is used for group bounds calculation and should represent the visible area\n */\n getVisualBoundingBox(): BoundingBox {\n // Always return the cropped area, even during crop mode\n // This ensures group bounds update correctly while cropping\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n return {\n x: this.x - cropWidth / 2,\n y: this.y - cropHeight / 2,\n width: cropWidth,\n height: cropHeight,\n };\n }\n\n /**\n * Get rotation anchor (center of the visible image area)\n * In crop mode: returns the center of the full frame\n * Not in crop mode: returns this.x, this.y (center of cropped area)\n */\n getRotationAnchor(): Point {\n if (this.isCropping) {\n // In crop mode: return the center of the full frame\n // this.x, this.y is at the crop center, so we need to calculate the frame center\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n // Offset from crop center to frame center in local space (before rotation and flip)\n let localOffsetX =\n this.transformData.width / 2 - (this.transformData.cropX * this.transformData.width + cropWidth / 2);\n let localOffsetY =\n this.transformData.height / 2 - (this.transformData.cropY * this.transformData.height + cropHeight / 2);\n\n // Account for flips: when flipped, the offset direction is reversed\n // This is because flip inverts the coordinate system\n const flipH = this.transformData.flipHorizontal ? -1 : 1;\n const flipV = this.transformData.flipVertical ? -1 : 1;\n localOffsetX *= flipH;\n localOffsetY *= flipV;\n\n // Rotate the offset to world space\n const cos = Math.cos(RotationUtils.toRadians(this.rotation));\n const sin = Math.sin(RotationUtils.toRadians(this.rotation));\n const worldOffsetX = localOffsetX * cos - localOffsetY * sin;\n const worldOffsetY = localOffsetX * sin + localOffsetY * cos;\n\n // Calculate frame center in world space\n return {\n x: this.x + worldOffsetX,\n y: this.y + worldOffsetY,\n };\n } else {\n // Not in crop mode: this.x, this.y is already at the center of the visible area\n return {\n x: this.x,\n y: this.y,\n };\n }\n }\n\n /**\n * Hit test for image - checks if point is inside the visible (cropped) area\n */\n hitTest(worldX: number, worldY: number): boolean {\n // Convert to local coordinates (relative to this.x, this.y)\n const local = this.worldToLocal(worldX, worldY);\n\n if (this.isCropping) {\n // In crop mode: hit test against full image frame\n // Calculate where the full frame is relative to the crop center\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n const localOffsetX =\n this.transformData.width / 2 - (this.transformData.cropX * this.transformData.width + cropWidth / 2);\n const localOffsetY =\n this.transformData.height / 2 - (this.transformData.cropY * this.transformData.height + cropHeight / 2);\n\n const halfWidth = this.transformData.width / 2;\n const halfHeight = this.transformData.height / 2;\n\n return (\n local.x >= localOffsetX - halfWidth &&\n local.x <= localOffsetX + halfWidth &&\n local.y >= localOffsetY - halfHeight &&\n local.y <= localOffsetY + halfHeight\n );\n } else {\n // Not in crop mode: hit test against cropped area only\n // The crop is centered at this.x, this.y, so just check the crop size\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n const halfCropWidth = cropWidth / 2;\n const halfCropHeight = cropHeight / 2;\n\n return (\n local.x >= -halfCropWidth && local.x <= halfCropWidth && local.y >= -halfCropHeight && local.y <= halfCropHeight\n );\n }\n }\n\n /**\n * Render the image\n */\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n if (!this.imageLoaded || !this.imageElement) {\n // Show placeholder while loading\n this.renderPlaceholder(ctx);\n return;\n }\n\n\n ctx.save();\n\n // Apply element opacity\n if (this.opacity !== undefined && this.opacity !== 1) {\n ctx.globalAlpha = this.opacity;\n }\n\n // Translate to the visible area center and rotate\n ctx.translate(this.x, this.y);\n // Negate rotation for clockwise-negative convention\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n\n // Apply flip transformations\n const flipH = this.transformData.flipHorizontal ? -1 : 1;\n const flipV = this.transformData.flipVertical ? -1 : 1;\n ctx.scale(flipH, flipV);\n\n if (this.isCropping) {\n // In crop mode: render the full image frame\n // Calculate where the frame is relative to the crop center\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n const localOffsetX =\n this.transformData.width / 2 - (this.transformData.cropX * this.transformData.width + cropWidth / 2);\n const localOffsetY =\n this.transformData.height / 2 - (this.transformData.cropY * this.transformData.height + cropHeight / 2);\n\n const x = localOffsetX - this.transformData.width / 2;\n const y = localOffsetY - this.transformData.height / 2;\n\n // Apply border radius if set\n const borderRadius = this.transformData.borderRadius || 0;\n if (borderRadius > 0) {\n ctx.save();\n const radius = Math.min(\n (borderRadius / 100) * Math.min(this.transformData.width, this.transformData.height),\n this.transformData.width / 2,\n this.transformData.height / 2\n );\n ctx.beginPath();\n ctx.roundRect(x, y, this.transformData.width, this.transformData.height, radius);\n ctx.clip();\n }\n\n // Draw the full image\n ctx.drawImage(\n this.imageElement,\n 0,\n 0,\n this.imageElement.width,\n this.imageElement.height,\n x,\n y,\n this.transformData.width,\n this.transformData.height\n );\n\n if (borderRadius > 0) {\n ctx.restore();\n }\n } else if (this.masks && this.masks.length > 0) {\n // Has masks: render the full image without crop clipping.\n // The mask shape defines the visible area instead of the crop rectangle.\n // Crop values (cropX/Y) still offset the image within the mask.\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n const x = -cropWidth / 2;\n const y = -cropHeight / 2;\n\n // Calculate where the full image should be drawn so the crop offset\n // positions the image content within the mask shape\n const fullImageX = x - this.transformData.cropX * this.transformData.width;\n const fullImageY = y - this.transformData.cropY * this.transformData.height;\n\n ctx.drawImage(\n this.imageElement,\n 0,\n 0,\n this.imageElement.width,\n this.imageElement.height,\n fullImageX,\n fullImageY,\n this.transformData.width,\n this.transformData.height\n );\n } else {\n // Not in crop mode: render only the cropped area\n // The visible crop is centered at the origin\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n const x = -cropWidth / 2;\n const y = -cropHeight / 2;\n\n // Apply border radius clipping\n const borderRadius = this.transformData.borderRadius || 0;\n ctx.save();\n const radius = Math.min((borderRadius / 100) * Math.min(cropWidth, cropHeight), cropWidth / 2, cropHeight / 2);\n ctx.beginPath();\n ctx.roundRect(x, y, cropWidth, cropHeight, radius);\n ctx.clip();\n\n // Draw the full image, but only the cropped area is visible due to clipping\n // Calculate where the full image should be drawn so the crop appears at the origin\n const fullImageX = x - this.transformData.cropX * this.transformData.width;\n const fullImageY = y - this.transformData.cropY * this.transformData.height;\n\n ctx.drawImage(\n this.imageElement,\n 0,\n 0,\n this.imageElement.width,\n this.imageElement.height,\n fullImageX,\n fullImageY,\n this.transformData.width,\n this.transformData.height\n );\n\n ctx.restore();\n }\n\n ctx.restore();\n }\n\n /**\n * Render placeholder based on current image load state.\n * Shows different visuals for loading, retrying, error, and no-image states.\n */\n renderPlaceholder(ctx: CanvasRenderingContext2D): void {\n ctx.save();\n\n // Translate to the visible area center and rotate\n ctx.translate(this.x, this.y);\n // Negate rotation for clockwise-negative convention\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n\n // For placeholder, just show the crop size centered\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n const x = -cropWidth / 2;\n const y = -cropHeight / 2;\n\n // Choose background and message based on load state\n let bgColor: string;\n let textColor: string;\n let message: string;\n\n if (this.imageLoadState === 'error') {\n bgColor = '#f0e0e0'; // Light red/pink tint for error\n textColor = '#993333';\n message = 'Image failed to load';\n } else if (this.imageLoadState === 'retrying') {\n bgColor = '#e0e0e0';\n textColor = '#666';\n message = 'Retrying...';\n } else if (this.imageLoadState === 'loading') {\n bgColor = '#e0e0e0';\n textColor = '#666';\n message = 'Loading...';\n } else {\n // No URL set or other state\n bgColor = '#e0e0e0';\n textColor = '#666';\n message = this.imageUrl ? 'Loading...' : 'No image';\n }\n\n // Draw background rectangle\n ctx.fillStyle = bgColor;\n ctx.fillRect(x, y, cropWidth, cropHeight);\n\n // Draw placeholder text at center\n ctx.fillStyle = textColor;\n ctx.font = '14px system-ui, -apple-system, sans-serif';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(message, 0, 0);\n\n ctx.restore();\n }\n\n /**\n * Handle resize - maintain aspect ratio\n * @returns {boolean} true if resize was applied\n */\n override resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): boolean {\n // Calculate the proposed scale from startData\n // In crop mode: scale relative to frame dimensions\n // Not in crop mode: scale relative to visible (cropped) dimensions\n let widthScale, heightScale;\n if (this.isCropping) {\n // In crop mode: newWidth/newHeight are based on frame bounding box\n widthScale = newWidth / imageTransformData(startData).width;\n heightScale = newHeight / imageTransformData(startData).height;\n } else {\n // Not in crop mode: newWidth/newHeight are based on cropped area bounding box\n // We need to scale relative to the visible crop dimensions\n widthScale = newWidth / (startData.width ?? 1);\n heightScale = newHeight / (startData.height ?? 1);\n }\n const proposedScale = (widthScale + heightScale) / 2;\n\n // Remap anchor when flipped\n let effectiveAnchor = anchor;\n if (this.isCropping) {\n effectiveAnchor = this.remapAnchorForFlips(anchor, startData);\n }\n\n // Apply crop constraints if in crop mode\n let finalScale = proposedScale;\n if (this.isCropping) {\n const currentScaleRelativeToStart = this.transformData.width / imageTransformData(startData).width;\n\n // Only apply constraint when trying to shrink\n if (proposedScale < currentScaleRelativeToStart) {\n finalScale = this.calculateMinScaleForCropBounds(\n effectiveAnchor,\n startData,\n proposedScale,\n currentScaleRelativeToStart\n );\n }\n }\n\n // Apply scale to both dimensions while maintaining aspect ratio\n this.transformData.width = imageTransformData(startData).width * finalScale;\n this.transformData.height = this.transformData.width / this.imageAspectRatio;\n\n // Ensure rotation stays the same\n this.rotation = startData.rotation;\n\n // For crop mode, we need to get the actual frame bounds, not the crop bounds\n // startData.x, startData.y is the crop center in crop mode, frame center otherwise\n // We need to calculate the frame center and corners\n\n let frameCenterX, frameCenterY;\n\n if (this.isCropping) {\n // Calculate where the frame center was in world coordinates\n // Crop center in frame-local coordinates (frame center is at 0,0 in local coords)\n // Frame spans from (-width/2, -height/2) to (width/2, height/2)\n // Crop spans from (cropX * width - width/2, cropY * height - height/2) to (...)\n const oldCropCenterXLocal =\n (imageTransformData(startData).cropX + imageTransformData(startData).cropWidth / 2) * imageTransformData(startData).width -\n imageTransformData(startData).width / 2;\n const oldCropCenterYLocal =\n (imageTransformData(startData).cropY + imageTransformData(startData).cropHeight / 2) * imageTransformData(startData).height -\n imageTransformData(startData).height / 2;\n\n // This is the offset from frame center to crop center in local coords\n const localOffsetX = oldCropCenterXLocal;\n const localOffsetY = oldCropCenterYLocal;\n\n // Rotate to world coords (forward transform: local → world)\n // NOTE: No flip transformation needed here!\n // The local offsets are stored in unflipped coordinate space,\n // and this.x/this.y are also in unflipped world space.\n // The flip only affects rendering, not the coordinate calculations.\n const startTransform = Transform.fromSnapshot(startData);\n const worldOffset = startTransform.localDeltaToWorld(localOffsetX, localOffsetY);\n const worldOffsetX = worldOffset.dx;\n const worldOffsetY = worldOffset.dy;\n\n // startData.x, startData.y is crop center, so frame center is:\n frameCenterX = startData.x - worldOffsetX;\n frameCenterY = startData.y - worldOffsetY;\n } else {\n // Not in crop mode: startData.x, startData.y is the crop center (visual center)\n // We need to calculate the frame center from the crop center\n const oldCropCenterXLocal =\n (imageTransformData(startData).cropX + imageTransformData(startData).cropWidth / 2) * imageTransformData(startData).width -\n imageTransformData(startData).width / 2;\n const oldCropCenterYLocal =\n (imageTransformData(startData).cropY + imageTransformData(startData).cropHeight / 2) * imageTransformData(startData).height -\n imageTransformData(startData).height / 2;\n\n // This is the offset from frame center to crop center in local coords\n const localOffsetX = oldCropCenterXLocal;\n const localOffsetY = oldCropCenterYLocal;\n\n // Rotate to world coords (forward transform: local → world)\n const startTransform = Transform.fromSnapshot(startData);\n const worldOffset = startTransform.localDeltaToWorld(localOffsetX, localOffsetY);\n const worldOffsetX = worldOffset.dx;\n const worldOffsetY = worldOffset.dy;\n\n // startData.x, startData.y is crop center, so frame center is:\n frameCenterX = startData.x - worldOffsetX;\n frameCenterY = startData.y - worldOffsetY;\n }\n\n // Calculate the FIXED corner point in world coordinates at start\n // In crop mode, the fixed corner is the OPPOSITE of the dragged anchor\n // Use effectiveAnchor (which accounts for flips) instead of anchor\n let fixedCornerWorldX, fixedCornerWorldY;\n\n // Fixed corner positions in local frame coordinates (unrotated, centered at frame center)\n // These are the OPPOSITE corners from the anchor being dragged\n let fixedCornerLocalX = 0;\n let fixedCornerLocalY = 0;\n\n if (effectiveAnchor === 'top-left') {\n // Dragging top-left, so fix bottom-right\n fixedCornerLocalX = imageTransformData(startData).width / 2;\n fixedCornerLocalY = imageTransformData(startData).height / 2;\n } else if (effectiveAnchor === 'top-right') {\n // Dragging top-right, so fix bottom-left\n fixedCornerLocalX = -imageTransformData(startData).width / 2;\n fixedCornerLocalY = imageTransformData(startData).height / 2;\n } else if (effectiveAnchor === 'bottom-left') {\n // Dragging bottom-left, so fix top-right\n fixedCornerLocalX = imageTransformData(startData).width / 2;\n fixedCornerLocalY = -imageTransformData(startData).height / 2;\n } else if (effectiveAnchor === 'bottom-right') {\n // Dragging bottom-right, so fix top-left\n fixedCornerLocalX = -imageTransformData(startData).width / 2;\n fixedCornerLocalY = -imageTransformData(startData).height / 2;\n } else if (effectiveAnchor === 'middle-left') {\n // Dragging left edge, so fix right edge\n fixedCornerLocalX = imageTransformData(startData).width / 2;\n fixedCornerLocalY = 0;\n } else if (effectiveAnchor === 'middle-right') {\n // Dragging right edge, so fix left edge\n fixedCornerLocalX = -imageTransformData(startData).width / 2;\n fixedCornerLocalY = 0;\n } else if (effectiveAnchor === 'middle-top') {\n // Dragging top edge, so fix bottom edge\n fixedCornerLocalX = 0;\n fixedCornerLocalY = imageTransformData(startData).height / 2;\n } else if (effectiveAnchor === 'middle-bottom') {\n // Dragging bottom edge, so fix top edge\n fixedCornerLocalX = 0;\n fixedCornerLocalY = -imageTransformData(startData).height / 2;\n }\n\n // Rotate fixed corner to world coordinates (forward transform: local → world)\n // NOTE: No flip transformation needed!\n // The fixed corner positions are in unflipped local space,\n // and we convert them directly to unflipped world space.\n const startTransformForCorner = Transform.fromSnapshot(startData);\n const fixedCornerWorldOffset = startTransformForCorner.localDeltaToWorld(fixedCornerLocalX, fixedCornerLocalY);\n const fixedCornerWorldOffsetX = fixedCornerWorldOffset.dx;\n const fixedCornerWorldOffsetY = fixedCornerWorldOffset.dy;\n\n fixedCornerWorldX = frameCenterX + fixedCornerWorldOffsetX;\n fixedCornerWorldY = frameCenterY + fixedCornerWorldOffsetY;\n\n // Calculate new frame center position\n // The fixed corner (opposite of the dragged anchor) should stay at fixedCornerWorldX/Y\n // We need to find where the frame center should be so that the fixed corner ends up there\n\n // Calculate where the fixed corner will be in the NEW frame's local coordinates\n // (same relative position as before, but with new dimensions)\n let newFixedCornerLocalX = 0;\n let newFixedCornerLocalY = 0;\n\n if (effectiveAnchor === 'top-left') {\n // Fixed corner is bottom-right\n newFixedCornerLocalX = this.transformData.width / 2;\n newFixedCornerLocalY = this.transformData.height / 2;\n } else if (effectiveAnchor === 'top-right') {\n // Fixed corner is bottom-left\n newFixedCornerLocalX = -this.transformData.width / 2;\n newFixedCornerLocalY = this.transformData.height / 2;\n } else if (effectiveAnchor === 'bottom-left') {\n // Fixed corner is top-right\n newFixedCornerLocalX = this.transformData.width / 2;\n newFixedCornerLocalY = -this.transformData.height / 2;\n } else if (effectiveAnchor === 'bottom-right') {\n // Fixed corner is top-left\n newFixedCornerLocalX = -this.transformData.width / 2;\n newFixedCornerLocalY = -this.transformData.height / 2;\n } else if (effectiveAnchor === 'middle-left') {\n // Fixed edge is right\n newFixedCornerLocalX = this.transformData.width / 2;\n newFixedCornerLocalY = 0;\n } else if (effectiveAnchor === 'middle-right') {\n // Fixed edge is left\n newFixedCornerLocalX = -this.transformData.width / 2;\n newFixedCornerLocalY = 0;\n } else if (effectiveAnchor === 'middle-top') {\n // Fixed edge is bottom\n newFixedCornerLocalX = 0;\n newFixedCornerLocalY = this.transformData.height / 2;\n } else if (effectiveAnchor === 'middle-bottom') {\n // Fixed edge is top\n newFixedCornerLocalX = 0;\n newFixedCornerLocalY = -this.transformData.height / 2;\n }\n\n // Rotate the new fixed corner position to world coordinates (forward transform: local → world)\n // NOTE: No flip transformation needed!\n const newTransform = new Transform(this);\n const newFixedCornerWorldOffset = newTransform.localDeltaToWorld(newFixedCornerLocalX, newFixedCornerLocalY);\n const newFixedCornerWorldOffsetX = newFixedCornerWorldOffset.dx;\n const newFixedCornerWorldOffsetY = newFixedCornerWorldOffset.dy;\n\n // Calculate new frame center: the fixed corner stays at the same world position\n // fixedCornerWorld = frameCenter + fixedCornerWorldOffset\n // Therefore: frameCenter = fixedCornerWorld - fixedCornerWorldOffset\n const newFrameCenterX = fixedCornerWorldX - newFixedCornerWorldOffsetX;\n const newFrameCenterY = fixedCornerWorldY - newFixedCornerWorldOffsetY;\n\n // In crop mode, we need to keep the crop zone at the same world size and position\n if (this.isCropping) {\n // The crop zone should stay the same size AND position in world coordinates\n // Calculate the old crop center in world coordinates\n const oldCropCenterWorldX = startData.x;\n const oldCropCenterWorldY = startData.y;\n\n // Calculate the old crop size in world coordinates\n const oldCropWorldWidth = imageTransformData(startData).cropWidth * imageTransformData(startData).width;\n const oldCropWorldHeight = imageTransformData(startData).cropHeight * imageTransformData(startData).height;\n\n // The crop center stays at the same world position\n this.x = oldCropCenterWorldX;\n this.y = oldCropCenterWorldY;\n\n // Calculate new normalized crop dimensions (fixed world size / new frame size)\n const newCropWidthNorm = oldCropWorldWidth / this.transformData.width;\n const newCropHeightNorm = oldCropWorldHeight / this.transformData.height;\n\n // Now figure out where the crop is positioned within the new frame\n // Get the offset from new frame center to crop center in world coords\n const worldOffsetX = oldCropCenterWorldX - newFrameCenterX;\n const worldOffsetY = oldCropCenterWorldY - newFrameCenterY;\n\n // Convert to local coords (relative to the new frame) - inverse transform: world → local\n const newFrameTransform = new Transform(this);\n const localOffset = newFrameTransform.worldDeltaToLocal(worldOffsetX, worldOffsetY);\n const localOffsetX = localOffset.dx;\n const localOffsetY = localOffset.dy;\n\n // The crop center in local frame coordinates (from frame top-left)\n const cropCenterXLocal = this.transformData.width / 2 + localOffsetX;\n const cropCenterYLocal = this.transformData.height / 2 + localOffsetY;\n\n // Convert to normalized coordinates (0-1) and get top-left corner\n const newCropX = cropCenterXLocal / this.transformData.width - newCropWidthNorm / 2;\n const newCropY = cropCenterYLocal / this.transformData.height - newCropHeightNorm / 2;\n\n // Update crop parameters\n // No clamping needed - we already checked bounds and reverted if out of bounds\n this.transformData.cropX = newCropX;\n this.transformData.cropY = newCropY;\n this.transformData.cropWidth = newCropWidthNorm;\n this.transformData.cropHeight = newCropHeightNorm;\n } else {\n // Not in crop mode: this.x, this.y should be at the crop center (visual center)\n // Calculate the new crop center from the new frame center\n\n // Calculate crop center position in the NEW frame's local coordinates\n const newCropCenterXLocal =\n (this.transformData.cropX + this.transformData.cropWidth / 2) * this.transformData.width -\n this.transformData.width / 2;\n const newCropCenterYLocal =\n (this.transformData.cropY + this.transformData.cropHeight / 2) * this.transformData.height -\n this.transformData.height / 2;\n\n // This is the offset from frame center to crop center in local coords\n const newLocalOffsetX = newCropCenterXLocal;\n const newLocalOffsetY = newCropCenterYLocal;\n\n // Rotate to world coords (forward transform: local → world)\n const newTransform = new Transform(this);\n const newWorldOffset = newTransform.localDeltaToWorld(newLocalOffsetX, newLocalOffsetY);\n const newWorldOffsetX = newWorldOffset.dx;\n const newWorldOffsetY = newWorldOffset.dy;\n\n // Crop center = frame center + offset\n this.x = newFrameCenterX + newWorldOffsetX;\n this.y = newFrameCenterY + newWorldOffsetY;\n }\n\n return true; // Resize was applied successfully\n }\n\n /**\n * Get enabled anchors - only corner handles for images\n */\n getEnabledAnchors(): ResizeAnchor[] {\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n /**\n * Enter crop mode\n * Just sets the flag - position stays at the cropped area center\n */\n enterCropMode(): void {\n this.isCropping = true;\n }\n\n /**\n * Exit crop mode\n * Just clears the flag - position stays the same\n */\n exitCropMode(): void {\n this.isCropping = false;\n }\n\n /**\n * Get crop box bounds in local coordinates (before rotation)\n * Returns the rectangle where the crop box should be rendered\n * Local coordinates are relative to this.x, this.y (the center of the cropped area)\n */\n getCropBoxBounds(): BoundingBox | null {\n if (!this.isCropping) return null;\n\n // In crop mode, this.x/this.y is at the center of the cropped area (what's visible)\n // The crop box represents this visible area, so it's always centered at the origin\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n // Since this.x, this.y is the crop center, the crop box is centered at origin in local coords\n return {\n x: -cropWidth / 2,\n y: -cropHeight / 2,\n width: cropWidth,\n height: cropHeight,\n };\n }\n\n /**\n * Get crop box corners in world coordinates (accounting for rotation)\n * Returns array of {x, y} points for each corner\n */\n getCropBoxWorldCorners(): Point[] | null {\n const cropBox = this.getCropBoxBounds();\n if (!cropBox) return null;\n\n const cos = Math.cos(RotationUtils.toRadians(this.rotation));\n const sin = Math.sin(RotationUtils.toRadians(this.rotation));\n\n // Define corners in local space\n const corners = [\n { x: cropBox.x, y: cropBox.y }, // top-left\n { x: cropBox.x + cropBox.width, y: cropBox.y }, // top-right\n { x: cropBox.x, y: cropBox.y + cropBox.height }, // bottom-left\n { x: cropBox.x + cropBox.width, y: cropBox.y + cropBox.height }, // bottom-right\n ];\n\n // Transform to world coordinates\n return corners.map((corner) => ({\n x: this.x + corner.x * cos - corner.y * sin,\n y: this.y + corner.x * sin + corner.y * cos,\n }));\n }\n\n /**\n * Convert world coordinates to local coordinates (accounting for rotation and flips)\n * Local coordinates are relative to this.x, this.y (the center of the visible area)\n */\n worldToLocal(worldX: number, worldY: number): Point {\n // Translate relative to the visible area center\n const dx = worldX - this.x;\n const dy = worldY - this.y;\n\n // Rotate back (inverse rotation) - positive to undo the negative forward rotation\n const cos = Math.cos(RotationUtils.toRadiansInverse(this.rotation));\n const sin = Math.sin(RotationUtils.toRadiansInverse(this.rotation));\n\n let localX = dx * cos - dy * sin;\n let localY = dx * sin + dy * cos;\n\n // Apply inverse flip transformation\n // Since flip is a scale by ±1, the inverse is the same operation\n const flipH = this.transformData.flipHorizontal ? -1 : 1;\n const flipV = this.transformData.flipVertical ? -1 : 1;\n localX *= flipH;\n localY *= flipV;\n\n return {\n x: localX,\n y: localY,\n };\n }\n\n /**\n * Convert local coordinates to world coordinates (accounting for rotation and flips)\n * Inverse of worldToLocal\n * Canvas transformations are applied in reverse: scale -> rotate -> translate\n */\n localToWorld(localX: number, localY: number): Point {\n // Apply flip transformation first (ctx.scale is applied first to points)\n const flipH = this.transformData.flipHorizontal ? -1 : 1;\n const flipV = this.transformData.flipVertical ? -1 : 1;\n let flippedX = localX * flipH;\n let flippedY = localY * flipV;\n\n // Then rotate (ctx.rotate is applied second)\n const cos = Math.cos(RotationUtils.toRadians(this.rotation));\n const sin = Math.sin(RotationUtils.toRadians(this.rotation));\n\n const rotatedX = flippedX * cos - flippedY * sin;\n const rotatedY = flippedX * sin + flippedY * cos;\n\n // Finally translate to world position (ctx.translate is applied last)\n return {\n x: this.x + rotatedX,\n y: this.y + rotatedY,\n };\n }\n\n /**\n * Hit test for crop box handles in world coordinates\n * Returns {type: 'corner'|'edge', anchor: string} if hit, null otherwise\n * @param worldX - X coordinate in world space\n * @param worldY - Y coordinate in world space\n * @param zoom - Current zoom level (used to scale hit areas for consistent interaction)\n */\n hitTestCropHandle(\n worldX: number,\n worldY: number,\n zoom: number = 1.0\n ): { type: 'corner' | 'edge'; anchor: string; worldX: number; worldY: number } | null {\n if (!this.isCropping) return null;\n\n const cropBox = this.getCropBoxBounds();\n if (!cropBox) return null;\n\n // Convert world coordinates to local\n const local = this.worldToLocal(worldX, worldY);\n\n // Scale hit areas by 1/zoom to maintain consistent screen-space hit targets\n // When zoomed out (zoom < 1), hit areas need to be larger in world space\n const hitScale = 1 / zoom;\n\n // Crop handle constants - scaled for zoom\n const CORNER_HIT_RADIUS_SCALED = CORNER_HANDLE_HIT_RADIUS * hitScale;\n const EDGE_LENGTH_SCALED = EDGE_HANDLE_LENGTH * hitScale;\n const EDGE_WIDTH_SCALED = EDGE_HANDLE_HIT_WIDTH * hitScale; // Width perpendicular to edge\n\n // Test corner handles first (higher priority)\n // Circular hit zones for corners\n const corners = [\n { x: cropBox.x, y: cropBox.y, anchor: 'top-left' },\n { x: cropBox.x + cropBox.width, y: cropBox.y, anchor: 'top-right' },\n { x: cropBox.x, y: cropBox.y + cropBox.height, anchor: 'bottom-left' },\n { x: cropBox.x + cropBox.width, y: cropBox.y + cropBox.height, anchor: 'bottom-right' },\n ];\n\n for (const corner of corners) {\n const dx = local.x - corner.x;\n const dy = local.y - corner.y;\n const distance = Math.sqrt(dx * dx + dy * dy);\n\n // Circular hit zone\n if (distance <= CORNER_HIT_RADIUS_SCALED) {\n const worldCorner = this.localToWorld(corner.x, corner.y);\n return { type: 'corner', anchor: corner.anchor, worldX: worldCorner.x, worldY: worldCorner.y };\n }\n }\n\n // Test edge handles (rectangles - wide for top/bottom, tall for left/right)\n const edges = [\n { x: cropBox.x + cropBox.width / 2, y: cropBox.y, anchor: 'top', orientation: 'horizontal' },\n { x: cropBox.x + cropBox.width / 2, y: cropBox.y + cropBox.height, anchor: 'bottom', orientation: 'horizontal' },\n { x: cropBox.x, y: cropBox.y + cropBox.height / 2, anchor: 'left', orientation: 'vertical' },\n { x: cropBox.x + cropBox.width, y: cropBox.y + cropBox.height / 2, anchor: 'right', orientation: 'vertical' },\n ];\n\n for (const edge of edges) {\n const dx = local.x - edge.x;\n const dy = local.y - edge.y;\n\n // Check if within the rectangular bounds\n // Use large hit zones for comfortable touch/mouse interaction\n let halfWidth, halfHeight;\n if (edge.orientation === 'horizontal') {\n // Top/bottom: wide along edge, uses EDGE_WIDTH for perpendicular hit zone\n halfWidth = EDGE_LENGTH_SCALED / 2;\n halfHeight = EDGE_WIDTH_SCALED / 2;\n } else {\n // Left/right: tall along edge, uses EDGE_WIDTH for perpendicular hit zone\n halfWidth = EDGE_WIDTH_SCALED / 2;\n halfHeight = EDGE_LENGTH_SCALED / 2;\n }\n\n if (Math.abs(dx) <= halfWidth && Math.abs(dy) <= halfHeight) {\n // Convert edge position from local to world coordinates to determine screen position\n const worldEdge = this.localToWorld(edge.x, edge.y);\n return { type: 'edge', anchor: edge.anchor, worldX: worldEdge.x, worldY: worldEdge.y };\n }\n }\n\n return null;\n }\n\n /**\n * Hit test for inside crop box in world coordinates\n * Used for dragging the image within the crop area\n */\n hitTestCropBox(worldX: number, worldY: number): boolean {\n if (!this.isCropping) return false;\n\n const cropBox = this.getCropBoxBounds();\n if (!cropBox) return false;\n\n // Convert world coordinates to local\n const local = this.worldToLocal(worldX, worldY);\n\n return (\n local.x >= cropBox.x &&\n local.x <= cropBox.x + cropBox.width &&\n local.y >= cropBox.y &&\n local.y <= cropBox.y + cropBox.height\n );\n }\n\n /**\n * Update crop box (normalized 0-1 coordinates)\n * @param {number} cropX - Normalized X position (0-1)\n * @param {number} cropY - Normalized Y position (0-1)\n * @param {number} cropWidth - Normalized width (0-1)\n * @param {number} cropHeight - Normalized height (0-1)\n * @param {boolean} adjustPosition - Whether to adjust this.x, this.y to keep visual position stable (default: false)\n */\n updateCrop(\n cropX: number,\n cropY: number,\n cropWidth: number,\n cropHeight: number,\n adjustPosition: boolean = false\n ): void {\n // Ensure minimum crop size\n const minSize = 0.1;\n\n // Clamp width and height first (ensure they're valid sizes)\n let clampedWidth = Math.max(minSize, Math.min(1, cropWidth));\n let clampedHeight = Math.max(minSize, Math.min(1, cropHeight));\n\n // Then clamp position so crop box stays within 0-1 bounds\n // If position + size > 1, adjust position to keep right/bottom edge at 1\n let clampedX = Math.max(0, Math.min(1 - clampedWidth, cropX));\n let clampedY = Math.max(0, Math.min(1 - clampedHeight, cropY));\n\n // Final safety check: if the box is still outside bounds, reduce size\n if (clampedX + clampedWidth > 1) {\n clampedWidth = 1 - clampedX;\n }\n if (clampedY + clampedHeight > 1) {\n clampedHeight = 1 - clampedY;\n }\n\n // If adjustPosition is true, calculate offset to keep visual position stable\n let worldOffsetX = 0;\n let worldOffsetY = 0;\n\n if (adjustPosition) {\n // Calculate the old crop center in local coordinates\n const oldCropCenterX =\n this.transformData.cropX * this.transformData.width +\n (this.transformData.cropWidth * this.transformData.width) / 2;\n const oldCropCenterY =\n this.transformData.cropY * this.transformData.height +\n (this.transformData.cropHeight * this.transformData.height) / 2;\n\n // Calculate the new crop center in local coordinates\n const newCropCenterX = clampedX * this.transformData.width + (clampedWidth * this.transformData.width) / 2;\n const newCropCenterY = clampedY * this.transformData.height + (clampedHeight * this.transformData.height) / 2;\n\n // Calculate the offset between old and new crop centers in local space\n const localOffsetX = newCropCenterX - oldCropCenterX;\n const localOffsetY = newCropCenterY - oldCropCenterY;\n\n // Rotate the offset to world space\n const cos = Math.cos(RotationUtils.toRadians(this.rotation));\n const sin = Math.sin(RotationUtils.toRadians(this.rotation));\n worldOffsetX = localOffsetX * cos - localOffsetY * sin;\n worldOffsetY = localOffsetX * sin + localOffsetY * cos;\n }\n\n // Update the crop parameters\n this.transformData.cropX = clampedX;\n this.transformData.cropY = clampedY;\n this.transformData.cropWidth = clampedWidth;\n this.transformData.cropHeight = clampedHeight;\n\n // Adjust this.x, this.y if requested\n if (adjustPosition) {\n this.x += worldOffsetX;\n this.y += worldOffsetY;\n }\n }\n\n /**\n * Get the local position of the corner opposite to the given anchor\n * Corners are relative to frame center\n */\n private getOppositeCornerLocalPosition(\n anchor: ResizeAnchor,\n width: number,\n height: number\n ): { x: number; y: number } {\n const halfW = width / 2;\n const halfH = height / 2;\n\n const oppositeCorners: Record<ResizeAnchor, { x: number; y: number }> = {\n 'top-left': { x: halfW, y: halfH }, // Opposite is bottom-right\n 'top-right': { x: -halfW, y: halfH }, // Opposite is bottom-left\n 'bottom-left': { x: halfW, y: -halfH }, // Opposite is top-right\n 'bottom-right': { x: -halfW, y: -halfH }, // Opposite is top-left\n top: { x: 0, y: halfH }, // Opposite is bottom edge\n bottom: { x: 0, y: -halfH }, // Opposite is top edge\n left: { x: halfW, y: 0 }, // Opposite is right edge\n right: { x: -halfW, y: 0 }, // Opposite is left edge\n 'middle-top': { x: 0, y: halfH }, // Opposite is bottom edge\n 'middle-bottom': { x: 0, y: -halfH }, // Opposite is top edge\n 'middle-left': { x: halfW, y: 0 }, // Opposite is right edge\n 'middle-right': { x: -halfW, y: 0 }, // Opposite is left edge\n };\n\n return oppositeCorners[anchor];\n }\n\n /**\n * Calculate frame center position from crop center position\n * In crop mode, this.x/y is the crop center, not the frame center\n */\n private calculateFrameCenterFromCropCenter(startData: TransformStartData): { x: number; y: number } {\n const cropWidth = imageTransformData(startData).width * imageTransformData(startData).cropWidth;\n const cropHeight = imageTransformData(startData).height * imageTransformData(startData).cropHeight;\n\n // Offset from crop center to frame center in local space\n // NOTE: No flip transformation needed here!\n // The local offsets are stored in unflipped coordinate space,\n // and this.x/this.y are also in unflipped world space.\n // The flip only affects rendering, not the coordinate calculations.\n const localOffsetX =\n imageTransformData(startData).width / 2 -\n (imageTransformData(startData).cropX * imageTransformData(startData).width + cropWidth / 2);\n const localOffsetY =\n imageTransformData(startData).height / 2 -\n (imageTransformData(startData).cropY * imageTransformData(startData).height + cropHeight / 2);\n\n // Rotate to world space\n const transform = new Transform(startData);\n const worldOffset = transform.localDeltaToWorld(localOffsetX, localOffsetY);\n\n // Frame center = crop center + offset\n return {\n x: startData.x + worldOffset.dx,\n y: startData.y + worldOffset.dy,\n };\n }\n\n /**\n * Get the position of the fixed corner (opposite of anchor) in world coordinates\n */\n private getFixedCornerWorldPosition(anchor: ResizeAnchor, startData: TransformStartData): { x: number; y: number } {\n // Determine fixed corner position in start frame's local coords\n const fixedCornerLocal = this.getOppositeCornerLocalPosition(\n anchor,\n imageTransformData(startData).width,\n imageTransformData(startData).height\n );\n\n // Calculate start frame center in world coords\n const startFrameCenter = this.calculateFrameCenterFromCropCenter(startData);\n\n // Transform to world coords\n const startTransform = new Transform(startData);\n const fixedCornerWorldOffset = startTransform.localDeltaToWorld(fixedCornerLocal.x, fixedCornerLocal.y);\n\n return {\n x: startFrameCenter.x + fixedCornerWorldOffset.dx,\n y: startFrameCenter.y + fixedCornerWorldOffset.dy,\n };\n }\n\n /**\n * Calculate where the frame center would be after resize\n * The opposite corner from the anchor stays fixed in world space\n */\n private calculateFrameCenterAfterResize(\n scale: number,\n anchor: ResizeAnchor,\n startData: TransformStartData\n ): { x: number; y: number } {\n // Get the fixed corner position in world coords (opposite of dragged anchor)\n const fixedCornerWorld = this.getFixedCornerWorldPosition(anchor, startData);\n\n // Calculate new frame dimensions\n const newFrameWidth = imageTransformData(startData).width * scale;\n const newFrameHeight = newFrameWidth / this.imageAspectRatio;\n\n // Get fixed corner position in new frame's local coords\n const fixedCornerLocal = this.getOppositeCornerLocalPosition(anchor, newFrameWidth, newFrameHeight);\n\n // Transform fixed corner local position to world coords\n const transform = new Transform({\n x: 0, // Will calculate frame center\n y: 0,\n rotation: startData.rotation,\n });\n\n const fixedCornerWorldOffset = transform.localDeltaToWorld(fixedCornerLocal.x, fixedCornerLocal.y);\n\n // Frame center = fixed corner world position - fixed corner offset in world coords\n return {\n x: fixedCornerWorld.x - fixedCornerWorldOffset.dx,\n y: fixedCornerWorld.y - fixedCornerWorldOffset.dy,\n };\n }\n\n /**\n * Calculate crop state (position and size in normalized coords) at a given scale\n * This is the core geometric calculation that determines where the crop zone\n * would be positioned within the frame after resize.\n */\n private calculateCropStateAtScale(\n scale: number,\n anchor: ResizeAnchor,\n startData: TransformStartData\n ): { cropX: number; cropY: number; cropWidth: number; cropHeight: number } {\n // Calculate new frame dimensions\n const newFrameWidth = imageTransformData(startData).width * scale;\n const newFrameHeight = newFrameWidth / this.imageAspectRatio;\n\n // Crop zone world size (constant)\n const cropWorldWidth = imageTransformData(startData).cropWidth * imageTransformData(startData).width;\n const cropWorldHeight = imageTransformData(startData).cropHeight * imageTransformData(startData).height;\n\n // Crop zone normalized size in new frame\n const cropNormWidth = cropWorldWidth / newFrameWidth;\n const cropNormHeight = cropWorldHeight / newFrameHeight;\n\n // Calculate new frame center position\n // The frame resizes with the opposite corner staying fixed\n const newFrameCenter = this.calculateFrameCenterAfterResize(scale, anchor, startData);\n\n // Calculate crop center position (stays at same world position)\n const cropCenterWorld = {\n x: startData.x, // In crop mode, this.x/y is the crop center\n y: startData.y,\n };\n\n // Calculate offset from new frame center to crop center (in world coords)\n const worldOffsetX = cropCenterWorld.x - newFrameCenter.x;\n const worldOffsetY = cropCenterWorld.y - newFrameCenter.y;\n\n // Convert offset to local frame coordinates\n const transform = new Transform({\n x: newFrameCenter.x,\n y: newFrameCenter.y,\n rotation: startData.rotation,\n });\n\n const localOffset = transform.worldDeltaToLocal(worldOffsetX, worldOffsetY);\n\n // Calculate crop center in frame-local coordinates\n const cropCenterLocalX = newFrameWidth / 2 + localOffset.dx;\n const cropCenterLocalY = newFrameHeight / 2 + localOffset.dy;\n\n // Convert to normalized coordinates (0-1)\n const cropX = cropCenterLocalX / newFrameWidth - cropNormWidth / 2;\n const cropY = cropCenterLocalY / newFrameHeight - cropNormHeight / 2;\n\n return {\n cropX,\n cropY,\n cropWidth: cropNormWidth,\n cropHeight: cropNormHeight,\n };\n }\n\n /**\n * Check if a given scale keeps crop within frame bounds\n */\n private isScaleValidForCropBounds(scale: number, anchor: ResizeAnchor, startData: TransformStartData): boolean {\n const EPSILON = 0.0001; // Tolerance for floating point comparison\n\n // Calculate what the crop state would be at this scale\n const cropState = this.calculateCropStateAtScale(scale, anchor, startData);\n\n // Check if crop width/height exceed 1.0 (impossible - crop larger than frame)\n if (cropState.cropWidth > 1.0 + EPSILON || cropState.cropHeight > 1.0 + EPSILON) {\n return false;\n }\n\n // Check if crop is within bounds [0, 1] in both dimensions\n const cropLeft = cropState.cropX;\n const cropRight = cropState.cropX + cropState.cropWidth;\n const cropTop = cropState.cropY;\n const cropBottom = cropState.cropY + cropState.cropHeight;\n\n // Crop must be fully within frame: 0 <= crop edges <= 1\n const withinBounds =\n cropLeft >= -EPSILON && cropTop >= -EPSILON && cropRight <= 1.0 + EPSILON && cropBottom <= 1.0 + EPSILON;\n\n return withinBounds;\n }\n\n /**\n * Calculate minimum scale that keeps crop within frame bounds\n * Uses binary search to find the largest scale >= proposedScale that satisfies constraints\n */\n private calculateMinScaleForCropBounds(\n anchor: ResizeAnchor,\n startData: TransformStartData,\n proposedScale: number,\n currentScale: number\n ): number {\n const EPSILON = 0.0001; // Tolerance for floating point comparison\n const MAX_ITERATIONS = 20; // More than enough for precision\n\n // If proposed scale is already valid, use it\n if (this.isScaleValidForCropBounds(proposedScale, anchor, startData)) {\n return proposedScale;\n }\n\n // Binary search for the minimum valid scale\n // We're looking for the smallest scale that still satisfies constraints\n let lo = proposedScale; // Known invalid (too small)\n let hi = currentScale; // Known valid (current state)\n\n for (let i = 0; i < MAX_ITERATIONS; i++) {\n const testScale = (lo + hi) / 2;\n\n if (this.isScaleValidForCropBounds(testScale, anchor, startData)) {\n // This scale works - try going smaller (closer to proposed)\n hi = testScale;\n } else {\n // This scale fails - need to go larger\n lo = testScale;\n }\n\n // Early exit if we've converged\n if (Math.abs(hi - lo) < EPSILON) {\n break;\n }\n }\n\n return hi; // Return the smallest valid scale we found\n }\n\n /**\n * Remap anchor to account for flips\n */\n private remapAnchorForFlips(anchor: ResizeAnchor, startData: TransformStartData): ResizeAnchor {\n let effectiveAnchor = anchor;\n const flipH = imageTransformData(startData).flipHorizontal;\n const flipV = imageTransformData(startData).flipVertical;\n\n if (flipH) {\n if (effectiveAnchor.includes('left')) {\n effectiveAnchor = effectiveAnchor.replace('left', 'right') as ResizeAnchor;\n } else if (effectiveAnchor.includes('right')) {\n effectiveAnchor = effectiveAnchor.replace('right', 'left') as ResizeAnchor;\n }\n }\n\n if (flipV) {\n if (effectiveAnchor.includes('top')) {\n effectiveAnchor = effectiveAnchor.replace('top', 'bottom') as ResizeAnchor;\n } else if (effectiveAnchor.includes('bottom')) {\n effectiveAnchor = effectiveAnchor.replace('bottom', 'top') as ResizeAnchor;\n }\n }\n\n return effectiveAnchor;\n }\n\n /**\n * Clone this element\n */\n clone(): ImageElement {\n const json = this.toJSON();\n // CRITICAL: Remove imageUrl to prevent constructor from loading image again\n // We'll manually assign the already-loaded image element instead\n const { imageUrl, ...configWithoutUrl } = json;\n\n const cloned = new ImageElement(configWithoutUrl);\n\n // Manually set image properties from the original element\n cloned.imageUrl = this.imageUrl;\n cloned.imageElement = this.imageElement;\n cloned.imageLoaded = this.imageLoaded;\n cloned.imageAspectRatio = this.imageAspectRatio;\n cloned.isCropping = this.isCropping; // Preserve crop mode state\n cloned.onLoadCallback = this.onLoadCallback; // Preserve load callback\n cloned.isSvg = this.isSvg; // Preserve SVG flag\n cloned.imageLoadState = this.imageLoadState; // Preserve load state\n cloned.imageLoadError = this.imageLoadError; // Preserve load error\n cloned.preserveDimensions = this.preserveDimensions; // Preserve dimension lock for cover crop\n\n return cloned;\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): ImageElementConfig & { id: string; type: 'image'; x: number; y: number; rotation: number } {\n const baseJSON = super.toJSON();\n return {\n ...baseJSON,\n id: this.id, // Explicitly include to ensure string type\n type: 'image' as const,\n transformType: 'image' as const,\n x: this.x, // Explicitly include to ensure number type\n y: this.y, // Explicitly include to ensure number type\n rotation: this.rotation, // Explicitly include to ensure number type\n imageUrl: this.imageUrl,\n imageAspectRatio: this.imageAspectRatio,\n transformData: { ...this.transformData },\n };\n }\n\n /**\n * Clean up resources held by this element\n * Call this when the element is being removed from the canvas to prevent memory leaks\n * Particularly important for iOS Safari which has strict memory limits\n */\n destroy(): void {\n // Clear any pending load timeout or retry\n this.clearLoadTimeout();\n\n // Release image from shared cache (decrements refCount, evicts at 0)\n this.releaseImageCache();\n\n // Revoke SVG blob URL if present\n this.revokeSvgBlobUrl();\n\n // Clear references to allow garbage collection\n this.imageElement = null;\n this.onLoadCallback = null;\n this.imageLoadError = null;\n }\n}\n\nexport default ImageElement;\n","/**\n * Comprehensive TypeScript type definitions for custom-canvas\n * Uses discriminated unions for type-safe transform handling\n */\n\n// ============================================================================\n// Core Element Types\n// ============================================================================\n\nexport type TransformType =\n | 'custom'\n | 'distort'\n | 'circle'\n | 'lean'\n | 'arch'\n | 'ascend'\n | 'wave'\n | 'flag'\n | 'image'\n | 'group'\n | 'artboard'\n | 'shape'\n | 'path';\n\nexport interface BoundingBox {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport type ResizeAnchor =\n | 'top-left'\n | 'top-right'\n | 'bottom-left'\n | 'bottom-right'\n | 'top'\n | 'bottom'\n | 'left'\n | 'right'\n | 'middle-left'\n | 'middle-right'\n | 'middle-top'\n | 'middle-bottom';\n\nexport type TextAlign = 'left' | 'center' | 'right';\n\n// ============================================================================\n// Transform Data Types (Discriminated Unions)\n// ============================================================================\n\nexport interface CustomTransformData {\n type: 'custom';\n controlPoints: Point[];\n /** Container width for text wrapping */\n width: number;\n}\n\nexport interface DistortTransformData {\n type: 'distort';\n [key: string]: unknown; // Placeholder until DistortTransform is implemented\n}\n\nexport interface CircleTransformData {\n type: 'circle';\n radius: number;\n scale: number;\n reverse: boolean;\n}\n\nexport interface LeanTransformData {\n type: 'lean';\n leanAmount: number; // -0.5 to 0.5\n /** Container width for text layout */\n width: number;\n}\n\nexport interface ArchTransformData {\n type: 'arch';\n archHeight: number; // -1 to 1\n /** Container width for text layout */\n width: number;\n}\n\nexport interface AscendTransformData {\n type: 'ascend';\n ascendAngle: number; // -30 to 30 degrees\n /** Container width for text layout */\n width: number;\n}\n\nexport interface WaveTransformData {\n type: 'wave';\n amplitude: number; // 0 to 1\n frequency: number; // 1 to 5\n /** Container width for text layout */\n width: number;\n}\n\nexport interface FlagTransformData {\n type: 'flag';\n amplitude: number; // 0 to 1\n frequency: number; // 1 to 5\n /** Container width for text layout */\n width: number;\n}\n\nexport interface ImageTransformData {\n type: 'image';\n width: number;\n height: number;\n // Crop properties (normalized 0-1)\n cropX: number;\n cropY: number;\n cropWidth: number;\n cropHeight: number;\n // Flip properties\n flipHorizontal: boolean;\n flipVertical: boolean;\n // Border radius (0-50)\n borderRadius: number;\n}\n\nexport interface GroupTransformData {\n type: 'group';\n}\n\nexport interface ArtboardTransformData {\n type: 'artboard';\n}\n\n// Shape types and data\nexport type ShapeType = 'rectangle' | 'circle' | 'ellipse' | 'triangle' | 'polygon' | 'star' | 'line';\n\nexport interface ShapeTransformData {\n type: 'shape';\n shapeType: ShapeType;\n\n // Common properties\n width: number;\n height: number;\n\n // Rectangle-specific\n borderRadius?: number; // 0-50 (percentage)\n\n // Circle/Ellipse-specific\n radiusX?: number;\n radiusY?: number;\n\n // Polygon-specific\n sides?: number; // 3-20\n\n // Star-specific\n points?: number; // 3-20\n innerRadius?: number; // 0-1 (relative to outer radius)\n\n // Fill properties\n fillColor?: string;\n fillOpacity?: number; // 0-1\n}\n\n// ============================================================================\n// Path Types (Custom Bezier Paths)\n// ============================================================================\n\n/**\n * Path point types define how curve handles behave\n */\nexport type PathPointType =\n | 'corner' // Sharp corner, no curve handles\n | 'smooth' // Smooth curve, handles are mirrored and equal length\n | 'bezier'; // Asymmetric curve, independent handle control\n\n/**\n * A single point in a path with optional bezier control handles\n * All coordinates are relative to the path element's position\n */\nexport interface PathPoint {\n id: string;\n x: number;\n y: number;\n type: PathPointType;\n\n // Control handles for bezier curves (relative to point position)\n // handleIn: control point for curve coming INTO this point\n // handleOut: control point for curve going OUT from this point\n handleIn?: Point;\n handleOut?: Point;\n}\n\n/**\n * Transform data for custom path elements\n */\nexport interface PathTransformData {\n type: 'path';\n\n // Array of points defining the path\n points: PathPoint[];\n\n // Whether the path is closed (connects last point to first)\n closed: boolean;\n\n // Bounding box dimensions (cached for performance)\n width: number;\n height: number;\n\n // Fill properties (only apply if path is closed)\n fillEnabled?: boolean;\n fillColor?: string;\n fillOpacity?: number;\n\n // Stroke properties (always apply)\n strokeEnabled?: boolean;\n strokeColor?: string;\n strokeWidth?: number;\n}\n\n// Discriminated union of all transform data types\nexport type AnyTransformData =\n | CustomTransformData\n | DistortTransformData\n | CircleTransformData\n | LeanTransformData\n | ArchTransformData\n | AscendTransformData\n | WaveTransformData\n | FlagTransformData\n | ImageTransformData\n | GroupTransformData\n | ArtboardTransformData\n | ShapeTransformData\n | PathTransformData;\n\n// ============================================================================\n// Transform Start Data (for interaction state)\n// ============================================================================\n\nexport interface TransformStartData {\n id: string;\n x: number;\n y: number;\n rotation: number;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- transformData is intentionally flexible; consumers cast to specific types\n transformData: Record<string, any>;\n // For bounding box operations\n width?: number;\n height?: number;\n // For text elements\n fontSize?: number;\n richText?: unknown;\n lineCount?: number;\n // For visual bbox (added by InteractionStateMachine)\n visualX?: number;\n visualY?: number;\n visualWidth?: number;\n visualHeight?: number;\n bboxX?: number;\n bboxY?: number;\n // For image crop mode\n cropX?: number;\n cropY?: number;\n cropWidth?: number;\n cropHeight?: number;\n // For stroke scaling during resize\n strokeWidth?: number;\n // For group operations\n childrenStartData?: TransformStartData[];\n // Allow additional properties for transform-specific start data\n [key: string]: unknown;\n}\n\n// ============================================================================\n// Blend Modes & Compositing\n// ============================================================================\n\nexport type BlendMode =\n | 'normal' // Standard blending\n | 'knockout' // Cuts through to artboard (destination-out)\n | 'clip'; // Clips content to shape (destination-in)\n// Future: 'multiply' | 'screen' | 'overlay' | etc.\n\nexport type CompositingScope = 'group' | 'artboard';\nexport type KnockoutScope = CompositingScope; // backward compat alias\n\nexport interface CompositingConfig {\n fill?: boolean; // Element fill participates in compositing\n stroke?: boolean; // Element stroke participates in compositing\n scope?: CompositingScope; // 'group' (default) or 'artboard'\n}\nexport type KnockoutConfig = CompositingConfig; // backward compat alias\n\n// ============================================================================\n// Stroke System\n// ============================================================================\n\nexport interface StrokeConfig {\n enabled: boolean;\n color: string;\n width: number;\n\n // Style options\n dashArray?: number[]; // [5, 5] for dashed lines\n lineCap?: 'butt' | 'round' | 'square';\n lineJoin?: 'miter' | 'round' | 'bevel';\n miterLimit?: number;\n\n // Visual effects\n opacity?: number; // 0-1\n feather?: number; // 0-20 blur radius\n}\n\n/**\n * Rendering context metadata passed to renderers\n * Used to customize rendering behavior for specific contexts (e.g., knockout, export, preview)\n */\nexport interface RenderingContext {\n /** True when rendering for knockout compositing (affects stroke color/opacity) */\n isKnockout?: boolean;\n /** True when rendering for export (may skip certain preview-only features) */\n isExport?: boolean;\n /** True when rendering for mask application */\n isMask?: boolean;\n /** True when the caller has already applied element positioning (translate/rotate) */\n positionApplied?: boolean;\n}\n\n// ============================================================================\n// Mask System\n// ============================================================================\n\nexport type MaskType =\n | 'clip' // Hard edge clipping (text, shape clipping)\n | 'alpha' // Alpha/transparency mask\n | 'luma' // Luminosity mask\n | 'distress'; // Texture/grunge mask (HIGH PRIORITY for t-shirt design)\n\nexport interface MaskDefinition {\n id: string;\n type: MaskType;\n\n // The element that defines the mask\n // Positioned relative to the masked element\n maskElement: AnyElementConfig;\n\n // Mask properties\n inverted?: boolean; // Invert the mask\n feather?: number; // Blur edges (0-100)\n opacity?: number; // Mask strength (0-1)\n blendMode?: string; // How this mask combines with other masks\n}\n\n// ============================================================================\n// Distress Effects (HIGH PRIORITY for t-shirt design)\n// ============================================================================\n\nexport type DistressStyle = 'worn' | 'cracked' | 'grunge' | 'retro' | 'custom';\n\nexport interface DistressEffect {\n enabled: boolean;\n style: DistressStyle;\n intensity: number; // 0-100 (overall effect strength)\n\n // Effect parameters\n fadeAmount?: number; // 0-100 (color fading)\n grainAmount?: number; // 0-100 (film grain/noise)\n scratchAmount?: number; // 0-100 (scratch marks)\n edgeWear?: number; // 0-100 (worn edges)\n\n // Custom texture (for 'custom' style)\n textureUrl?: string; // URL to distress texture image\n textureOpacity?: number; // 0-1 (how strong the texture is)\n textureBlendMode?: 'multiply' | 'screen' | 'overlay';\n\n // Random seed (for consistent results)\n seed?: number;\n}\n\n// ============================================================================\n// Glyph & OpenType Feature Types\n// ============================================================================\n\n/**\n * Per-character glyph override\n * Allows substituting specific characters with alternate glyphs from the font\n */\nexport interface GlyphOverride {\n /** Index of the character in the text string */\n charIndex: number;\n /** Glyph index in the font file */\n glyphIndex: number;\n /** Unicode codepoint of the original character */\n unicode: string;\n /** Human-readable name of the alternate (e.g., \"Swash A\", \"Fancy G\") */\n alternateName?: string;\n}\n\n/**\n * OpenType feature settings for advanced typography\n * Features are applied globally to the entire text element\n */\nexport interface OpenTypeFeatures {\n /** Standard ligatures (fi, fl, etc.) */\n liga?: boolean;\n /** Discretionary ligatures (ct, st, etc.) */\n dlig?: boolean;\n /** Contextual alternates */\n calt?: boolean;\n /** Swash alternates */\n swsh?: boolean;\n /** Small capitals */\n smcp?: boolean;\n /** All small capitals */\n c2sc?: boolean;\n /** Oldstyle figures (0-9) */\n onum?: boolean;\n /** Lining figures (0-9) */\n lnum?: boolean;\n /** Tabular figures */\n tnum?: boolean;\n /** Proportional figures */\n pnum?: boolean;\n /** Stylistic sets (ss01-ss20) */\n ss01?: boolean;\n ss02?: boolean;\n ss03?: boolean;\n ss04?: boolean;\n ss05?: boolean;\n ss06?: boolean;\n ss07?: boolean;\n ss08?: boolean;\n ss09?: boolean;\n ss10?: boolean;\n ss11?: boolean;\n ss12?: boolean;\n ss13?: boolean;\n ss14?: boolean;\n ss15?: boolean;\n ss16?: boolean;\n ss17?: boolean;\n ss18?: boolean;\n ss19?: boolean;\n ss20?: boolean;\n /** Character variants (cv01-cv99) - common ones */\n cv01?: boolean;\n cv02?: boolean;\n cv03?: boolean;\n cv04?: boolean;\n cv05?: boolean;\n /** Glyph composition/decomposition */\n ccmp?: boolean;\n /** Localized forms */\n locl?: boolean;\n /** Kerning */\n kern?: boolean;\n /** Mark positioning */\n mark?: boolean;\n /** Mark-to-mark positioning */\n mkmk?: boolean;\n}\n\n/**\n * Metadata about available glyphs for a specific character\n */\nexport interface GlyphAlternate {\n /** Glyph index in the font file */\n glyphIndex: number;\n /** Unicode codepoint this glyph represents */\n unicode: string;\n /** Human-readable name (e.g., \"A.swash\", \"g.alt01\") */\n name: string;\n /** Category of alternate (swash, stylistic, etc.) */\n category?: 'default' | 'swash' | 'stylistic' | 'contextual' | 'ligature';\n /** Preview data URL (base64-encoded image) */\n previewDataUrl?: string;\n}\n\n// ============================================================================\n// Rich Text System (Character-Level Formatting)\n// ============================================================================\n\n/**\n * Character-level styling properties\n * Each property is optional - undefined means inherit from element defaults\n */\nexport interface CharacterStyle {\n /** Text color (CSS color string) */\n color?: string;\n /** Font family name */\n fontFamily?: string;\n /** Font size in pixels */\n fontSize?: number;\n /** Bold weight */\n bold?: boolean;\n /** Italic style */\n italic?: boolean;\n /** Underline decoration */\n underline?: boolean;\n /** Strikethrough decoration */\n strikethrough?: boolean;\n}\n\n/**\n * A span of text with uniform styling\n * Used for efficient storage of rich text (avoids per-character arrays)\n */\nexport interface TextSpan {\n /** The text content of this span */\n text: string;\n /** Style properties for this span (optional properties inherit from element defaults) */\n style: CharacterStyle;\n}\n\n/**\n * Rich text data structure with helper methods\n * Stores text as an array of styled spans for efficient rendering\n */\nexport class RichText {\n spans: TextSpan[];\n\n constructor(spans: TextSpan[] = []) {\n this.spans = spans.length > 0 ? spans : [{ text: '', style: {} }];\n this.normalize();\n }\n\n /**\n * Create RichText from plain text string (no special formatting)\n */\n static fromPlainText(text: string, style: CharacterStyle = {}): RichText {\n return new RichText([{ text, style }]);\n }\n\n /**\n * Get the full text content (all spans concatenated)\n */\n getText(): string {\n return this.spans.map((s) => s.text).join('');\n }\n\n /**\n * Get the total character count\n */\n getLength(): number {\n return this.getText().length;\n }\n\n /**\n * Get the style at a specific character index\n * @param charIndex Character index (0-based)\n * @returns The style object for the span containing this character\n */\n getStyleAt(charIndex: number): CharacterStyle {\n // Handle empty RichText\n if (this.spans.length === 0) return {};\n\n // Handle negative index - return first span style\n if (charIndex < 0) return this.spans[0].style;\n\n let currentIndex = 0;\n for (const span of this.spans) {\n if (charIndex < currentIndex + span.text.length) {\n return span.style;\n }\n currentIndex += span.text.length;\n }\n\n // At or past end - return LAST span's style (not empty object)\n // This ensures new characters typed at end inherit the previous style\n return this.spans[this.spans.length - 1].style;\n }\n\n /**\n * Apply a style to a character range\n * @param start Start index (inclusive)\n * @param end End index (exclusive)\n * @param style Style to apply (merged with existing styles)\n */\n applyStyle(start: number, end: number, style: CharacterStyle): void {\n if (start >= end || start < 0 || end > this.getLength()) {\n return;\n }\n\n const newSpans: TextSpan[] = [];\n let currentIndex = 0;\n\n for (const span of this.spans) {\n const spanStart = currentIndex;\n const spanEnd = currentIndex + span.text.length;\n\n // Case 1: Span is completely before the range\n if (spanEnd <= start) {\n newSpans.push(span);\n currentIndex = spanEnd;\n continue;\n }\n\n // Case 2: Span is completely after the range\n if (spanStart >= end) {\n newSpans.push(span);\n currentIndex = spanEnd;\n continue;\n }\n\n // Case 3: Span overlaps with range - split it\n const overlapStart = Math.max(start, spanStart);\n const overlapEnd = Math.min(end, spanEnd);\n\n // Before overlap\n if (overlapStart > spanStart) {\n newSpans.push({\n text: span.text.substring(0, overlapStart - spanStart),\n style: span.style,\n });\n }\n\n // Overlap region - apply new style\n newSpans.push({\n text: span.text.substring(overlapStart - spanStart, overlapEnd - spanStart),\n style: { ...span.style, ...style },\n });\n\n // After overlap\n if (overlapEnd < spanEnd) {\n newSpans.push({\n text: span.text.substring(overlapEnd - spanStart),\n style: span.style,\n });\n }\n\n currentIndex = spanEnd;\n }\n\n this.spans = newSpans;\n this.normalize();\n }\n\n /**\n * Insert text at a specific position with a given style\n * @param index Character index to insert at\n * @param text Text to insert\n * @param style Style for the inserted text\n */\n insert(index: number, text: string, style: CharacterStyle): void {\n if (text.length === 0) return;\n\n const newSpans: TextSpan[] = [];\n let currentIndex = 0;\n\n for (const span of this.spans) {\n const spanStart = currentIndex;\n const spanEnd = currentIndex + span.text.length;\n\n // Insert point is before this span\n if (index <= spanStart && newSpans.length === 0) {\n newSpans.push({ text, style });\n }\n\n // Insert point is within this span\n if (index > spanStart && index <= spanEnd) {\n const offset = index - spanStart;\n // Split the span\n newSpans.push({\n text: span.text.substring(0, offset),\n style: span.style,\n });\n newSpans.push({ text, style });\n newSpans.push({\n text: span.text.substring(offset),\n style: span.style,\n });\n currentIndex = spanEnd;\n continue;\n }\n\n newSpans.push(span);\n currentIndex = spanEnd;\n }\n\n // Insert at end if we haven't inserted yet\n if (index >= this.getLength() && !newSpans.some((s) => s.text === text)) {\n newSpans.push({ text, style });\n }\n\n this.spans = newSpans;\n this.normalize();\n }\n\n /**\n * Delete text in a character range\n * @param start Start index (inclusive)\n * @param end End index (exclusive)\n */\n delete(start: number, end: number): void {\n // Clamp to valid range instead of silently failing\n const length = this.getLength();\n start = Math.max(0, Math.min(start, length));\n end = Math.max(0, Math.min(end, length));\n\n // Nothing to delete if range is empty after clamping\n if (start >= end) {\n return;\n }\n\n const newSpans: TextSpan[] = [];\n let currentIndex = 0;\n\n for (const span of this.spans) {\n const spanStart = currentIndex;\n const spanEnd = currentIndex + span.text.length;\n\n // Span is completely before deletion range\n if (spanEnd <= start) {\n newSpans.push(span);\n currentIndex = spanEnd;\n continue;\n }\n\n // Span is completely after deletion range\n if (spanStart >= end) {\n newSpans.push(span);\n currentIndex = spanEnd;\n continue;\n }\n\n // Span overlaps with deletion range\n const deleteStart = Math.max(start, spanStart);\n const deleteEnd = Math.min(end, spanEnd);\n\n // Keep text before deletion\n if (deleteStart > spanStart) {\n newSpans.push({\n text: span.text.substring(0, deleteStart - spanStart),\n style: span.style,\n });\n }\n\n // Keep text after deletion\n if (deleteEnd < spanEnd) {\n newSpans.push({\n text: span.text.substring(deleteEnd - spanStart),\n style: span.style,\n });\n }\n\n currentIndex = spanEnd;\n }\n\n this.spans = newSpans.length > 0 ? newSpans : [{ text: '', style: {} }];\n this.normalize();\n }\n\n /**\n * Normalize spans by:\n * 1. Removing empty spans\n * 2. Merging adjacent spans with identical styles\n * This prevents fragmentation and improves performance\n */\n private normalize(): void {\n // Step 1: Remove empty spans\n this.spans = this.spans.filter((s) => s.text !== '');\n\n // Step 2: Ensure at least one span exists (empty span for empty RichText)\n if (this.spans.length === 0) {\n this.spans = [{ text: '', style: {} }];\n return;\n }\n\n // Step 3: Merge adjacent spans with identical styles\n if (this.spans.length <= 1) return;\n\n const merged: TextSpan[] = [];\n let current = this.spans[0];\n\n for (let i = 1; i < this.spans.length; i++) {\n const next = this.spans[i];\n\n // Check if styles are identical\n if (this.stylesEqual(current.style, next.style)) {\n // Merge spans\n current = {\n text: current.text + next.text,\n style: current.style,\n };\n } else {\n merged.push(current);\n current = next;\n }\n }\n\n merged.push(current);\n this.spans = merged;\n }\n\n /**\n * Check if two styles are equal\n */\n private stylesEqual(a: CharacterStyle, b: CharacterStyle): boolean {\n return (\n a.color === b.color &&\n a.fontFamily === b.fontFamily &&\n a.fontSize === b.fontSize &&\n a.bold === b.bold &&\n a.italic === b.italic &&\n a.underline === b.underline &&\n a.strikethrough === b.strikethrough\n );\n }\n\n /**\n * Clone this RichText instance\n */\n clone(): RichText {\n return new RichText(\n this.spans.map((span) => ({\n text: span.text,\n style: { ...span.style },\n }))\n );\n }\n\n /**\n * Clear a specific style property from all spans.\n * Used when element-level property changes should override character-level.\n * After clearing, spans will inherit the property from element defaults.\n * @param property The style property to clear (e.g., 'fontFamily', 'bold')\n */\n clearStyleProperty(property: keyof CharacterStyle): void {\n for (const span of this.spans) {\n if (span.style[property] !== undefined) {\n delete span.style[property];\n }\n }\n this.normalize();\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): { spans: TextSpan[] } {\n return { spans: this.spans };\n }\n\n /**\n * Deserialize from JSON\n */\n static fromJSON(json: { spans: TextSpan[] }): RichText {\n return new RichText(json.spans);\n }\n}\n\n// ============================================================================\n// Element Configuration Types (Discriminated Unions)\n// ============================================================================\n\nexport interface BaseElementConfig {\n id?: string;\n x?: number;\n y?: number;\n rotation?: number;\n opacity?: number;\n\n // Transform type and data (specific to each element type)\n transformType?: TransformType;\n transformData?: Partial<AnyTransformData>;\n\n // Compositing\n blendMode?: BlendMode;\n knockoutParts?: KnockoutConfig;\n\n // Effects\n stroke?: StrokeConfig;\n masks?: MaskDefinition[];\n distressEffect?: DistressEffect;\n\n // Layer properties (for layers panel)\n name?: string;\n visible?: boolean;\n locked?: boolean;\n\n // Clipping mask - when true, this element clips all elements below it in the same group\n isClipping?: boolean;\n}\n\nexport interface BaseTextElementConfig extends BaseElementConfig {\n // Rich text support (new)\n richText?: RichText | { spans: TextSpan[] };\n // Legacy plain text (kept for backward compatibility)\n text?: string;\n // Element-level style defaults (used when richText spans don't specify)\n fontSize?: number;\n fontFamily?: string;\n color?: string;\n textAlign?: TextAlign;\n bold?: boolean;\n italic?: boolean;\n underline?: boolean;\n strikethrough?: boolean;\n\n // Glyph & OpenType features\n glyphOverrides?: GlyphOverride[];\n openTypeFeatures?: OpenTypeFeatures;\n}\n\n// Text element configs with specific transform types\nexport interface CustomElementConfig extends BaseTextElementConfig {\n transformType: 'custom';\n transformData?: Partial<CustomTransformData>;\n}\n\nexport interface DistortElementConfig extends BaseTextElementConfig {\n transformType: 'distort';\n transformData?: Partial<DistortTransformData>;\n}\n\nexport interface CircleElementConfig extends BaseTextElementConfig {\n transformType: 'circle';\n transformData?: Partial<CircleTransformData>;\n}\n\nexport interface LeanElementConfig extends BaseTextElementConfig {\n transformType: 'lean';\n transformData?: Partial<LeanTransformData>;\n}\n\nexport interface ArchElementConfig extends BaseTextElementConfig {\n transformType: 'arch';\n transformData?: Partial<ArchTransformData>;\n}\n\nexport interface AscendElementConfig extends BaseTextElementConfig {\n transformType: 'ascend';\n transformData?: Partial<AscendTransformData>;\n}\n\nexport interface WaveElementConfig extends BaseTextElementConfig {\n transformType: 'wave';\n transformData?: Partial<WaveTransformData>;\n}\n\nexport interface FlagElementConfig extends BaseTextElementConfig {\n transformType: 'flag';\n transformData?: Partial<FlagTransformData>;\n}\n\nexport interface ImageElementConfig extends BaseElementConfig {\n transformType: 'image';\n transformData?: Partial<ImageTransformData>;\n imageUrl?: string;\n imageAspectRatio?: number;\n /**\n * Callback invoked when the image finishes loading.\n *\n * The parameter is an `ImageElement` instance (clone of the loaded element).\n * Import `ImageElement` from `@snowcone-app/canvas/advanced` to narrow the type.\n *\n * @example\n * ```ts\n * import type { ImageElement } from '@snowcone-app/canvas/advanced';\n * const config: ImageElementConfig = {\n * transformType: 'image',\n * onLoadCallback(el) {\n * const img = el as ImageElement;\n * console.log(img.id, img.transformData.width);\n * },\n * };\n * ```\n */\n // Method syntax provides bivariant parameter checking, allowing callers to\n // type the parameter as ImageElement without importing it in this file.\n onLoadCallback?(element: BaseElementConfig & { readonly id: string; readonly transformType: 'image' }): void;\n /**\n * If true, dimensions (width/height) won't be recalculated when the image loads.\n * Use this when you've explicitly calculated dimensions (e.g., for \"cover\" sizing).\n */\n preserveDimensions?: boolean;\n}\n\nexport interface GroupElementConfig extends BaseElementConfig {\n transformType: 'group';\n transformData?: Partial<GroupTransformData>;\n children?: AnyElementConfig[];\n}\n\nexport interface ShapeElementConfig extends BaseElementConfig {\n transformType: 'shape';\n transformData?: Partial<ShapeTransformData>;\n}\n\nexport interface PathElementConfig extends BaseElementConfig {\n transformType: 'path';\n transformData?: Partial<PathTransformData>;\n}\n\nexport type ArtboardBackgroundType = 'color' | 'transparent' | 'texture';\n\n// Artboard-level distress texture (applied after all elements render)\nexport interface ArtboardDistressTexture {\n enabled: boolean;\n textureUrl: string; // URL to grayscale JPEG texture\n intensity: number; // 0-100\n}\n\n// Artboard-level image mask (applied after distress texture)\nexport interface ArtboardImageMask {\n enabled: boolean;\n imageUrl: string;\n maskType: 'clip' | 'alpha' | 'luma';\n opacity: number; // 0-100\n inverted?: boolean;\n}\n\n// Clip shape types for artboard content clipping\nexport type ClipShape =\n | 'rectangle' // Full artboard bounds (default, no clipping)\n | 'circle' // Circle inscribed in artboard\n | { type: 'rounded'; radius: number } // Rounded rectangle with specified corner radius\n | { type: 'path'; d: string } // Custom SVG path data\n // Union of multiple piece-local SVG paths, each translated to its\n // (x, y) inside the artboard frame and optionally rotated by 0/90/180/270\n // around the piece center. Used for ADR-0054 multi-piece spreads\n // (e.g. AFOOES hoodie) so artwork visually clips to the union of piece\n // silhouettes instead of the bounding rectangle. `baseWidth`/`baseHeight`\n // are the path's pre-rotation extents (the piece's unrotated bbox);\n // needed only when `rotation` is non-zero so we can rotate around the\n // piece's geometric center.\n | {\n type: 'composite-path';\n pieces: Array<{\n d: string;\n x: number;\n y: number;\n rotation?: 0 | 90 | 180 | 270;\n baseWidth?: number;\n baseHeight?: number;\n }>;\n };\n\nexport interface ArtboardConfig {\n id?: string;\n name?: string;\n x?: number;\n y?: number;\n width?: number;\n height?: number;\n backgroundColor?: string;\n backgroundType?: ArtboardBackgroundType;\n backgroundTexture?: string; // Texture ID or URL\n exportBackground?: boolean; // Whether to export background (defaults to false)\n elementIds?: string[];\n transformType?: 'artboard';\n\n // Clip shape for artboard content\n // - 'rectangle' or undefined: No clipping (content renders to artboard edges)\n // - 'circle': Content clipped to circle inscribed in artboard\n // - { type: 'rounded', radius: number }: Rounded rectangle corners\n // - { type: 'path', d: string }: Custom SVG path\n clipShape?: ClipShape;\n\n // Preview only - NOT exported to PNG (for t-shirt color preview)\n previewBackgroundColor?: string;\n\n // Artboard-level distress texture (applied after all elements, before UI layers)\n distressTexture?: ArtboardDistressTexture;\n\n // Artboard-level image mask (applied after distress texture)\n imageMask?: ArtboardImageMask;\n}\n\n// Discriminated union of all element configs\nexport type AnyElementConfig =\n | CustomElementConfig\n | DistortElementConfig\n | CircleElementConfig\n | LeanElementConfig\n | ArchElementConfig\n | AscendElementConfig\n | WaveElementConfig\n | FlagElementConfig\n | ImageElementConfig\n | GroupElementConfig\n | ShapeElementConfig\n | PathElementConfig;\n\n// Helper type for text-only elements (excludes image, shape, and path)\nexport type TextOnlyElementConfig = Exclude<\n AnyElementConfig,\n ImageElementConfig | ShapeElementConfig | PathElementConfig\n>;\n\n// ============================================================================\n// Interaction Types\n// ============================================================================\n\nexport type InteractionMode = 'idle' | 'drag' | 'resize' | 'rotate' | 'crop' | 'pen' | 'edit-path' | 'pinch';\n\nexport interface InteractionContext<T = unknown> {\n mode: InteractionMode;\n startX: number;\n startY: number;\n startData: TransformStartData;\n activeHandle?: ResizeAnchor;\n element: T; // Will be TextElement or its subclasses\n}\n\n// ============================================================================\n// Command History Types\n// ============================================================================\n\nexport interface Command {\n execute(): void;\n undo(): void;\n}\n\nexport interface ElementUpdate<T = unknown> {\n oldElement: T;\n newElement: T;\n}\n\nexport interface MultiElementUpdate<T = unknown> {\n updates: Map<string, ElementUpdate<T>>;\n}\n\n// ============================================================================\n// Selection Types\n// ============================================================================\n\nexport type SelectionState =\n | { type: 'none' }\n | { type: 'single'; elementId: string }\n | { type: 'multiple'; elementIds: Set<string> };\n\n// ============================================================================\n// Snap System Types\n// ============================================================================\n\nexport interface SnapGuide {\n type: 'vertical' | 'horizontal';\n sourceId?: string;\n targetId?: string;\n // For vertical guides\n x?: number;\n y1?: number;\n y2?: number;\n // For horizontal guides\n y?: number;\n x1?: number;\n x2?: number;\n}\n\nexport interface SnapResult {\n x: number;\n y: number;\n snappedX: boolean;\n snappedY: boolean;\n guides: SnapGuide[];\n}\n\n// ============================================================================\n// Spacing Indicator Types\n// ============================================================================\n\nexport interface SpacingIndicator {\n type: 'horizontal' | 'vertical';\n distance: number;\n isSnapTarget?: boolean; // If true, render in green to show snap will happen\n isAltHover?: boolean; // If true, render in purple to show Alt+hover measurements\n // For horizontal indicators\n x1?: number;\n x2?: number;\n y?: number;\n // For vertical indicators\n x?: number;\n y1?: number;\n y2?: number;\n // Label position\n labelX: number;\n labelY: number;\n}\n\n// ============================================================================\n// Resize Types\n// ============================================================================\n\nexport interface ResizeResult {\n newWidth: number;\n newHeight: number;\n newX: number;\n newY: number;\n newRotation: number;\n}\n\nexport interface ResizeParams<T = unknown> {\n element: T;\n anchor: ResizeAnchor;\n dx: number;\n dy: number;\n startData: TransformStartData;\n maintainAspectRatio?: boolean;\n}\n\n// ============================================================================\n// Render Types\n// ============================================================================\n\nexport interface RenderContext {\n ctx: CanvasRenderingContext2D;\n isSelected: boolean;\n isHovered?: boolean;\n scale?: number;\n}\n\n// ============================================================================\n// Transform Handle Types\n// ============================================================================\n\nexport interface HandleInfo {\n anchor: ResizeAnchor;\n x: number;\n y: number;\n cursor: string;\n}\n\n// ============================================================================\n// Transform Control Types (for UI)\n// ============================================================================\n\nexport interface TransformControl {\n key: string;\n label: string;\n type?: 'checkbox' | 'slider' | 'select';\n defaultValue?: number | boolean | string;\n defaultInternalValue?: number;\n step?: number;\n options?: Array<{ value: string; label: string }>;\n toSlider?: (internal: number) => number;\n fromSlider?: (slider: number) => number;\n toDisplay?: (internal: number) => string;\n visibleWhen?: (data: Record<string, unknown>) => boolean;\n}\n\nexport interface TransformTypeDefinition {\n id: TransformType;\n label: string;\n Component: new (config?: Partial<BaseTextElementConfig>) => unknown; // Element class constructor\n}\n\n// ============================================================================\n// Utility Types\n// ============================================================================\n\n// Extract element type by transform type\nexport type ExtractElementByType<T extends TransformType> = Extract<AnyElementConfig, { transformType: T }>;\n\n// Extract transform data by type\nexport type ExtractTransformData<T extends TransformType> = Extract<AnyTransformData, { type: T }>;\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\n// Transform data type guards\nexport function isCustomTransform(data: AnyTransformData): data is CustomTransformData {\n return data.type === 'custom';\n}\n\nexport function isDistortTransform(data: AnyTransformData): data is DistortTransformData {\n return data.type === 'distort';\n}\n\nexport function isCircleTransform(data: AnyTransformData): data is CircleTransformData {\n return data.type === 'circle';\n}\n\nexport function isLeanTransform(data: AnyTransformData): data is LeanTransformData {\n return data.type === 'lean';\n}\n\nexport function isArchTransform(data: AnyTransformData): data is ArchTransformData {\n return data.type === 'arch';\n}\n\nexport function isAscendTransform(data: AnyTransformData): data is AscendTransformData {\n return data.type === 'ascend';\n}\n\nexport function isWaveTransform(data: AnyTransformData): data is WaveTransformData {\n return data.type === 'wave';\n}\n\nexport function isFlagTransform(data: AnyTransformData): data is FlagTransformData {\n return data.type === 'flag';\n}\n\nexport function isImageTransform(data: AnyTransformData): data is ImageTransformData {\n return data.type === 'image';\n}\n\nexport function isGroupTransform(data: AnyTransformData): data is GroupTransformData {\n return data.type === 'group';\n}\n\nexport function isArtboardTransform(data: AnyTransformData): data is ArtboardTransformData {\n return data.type === 'artboard';\n}\n\n// Element config type guards\nexport function isTextElementConfig(config: AnyElementConfig): config is TextOnlyElementConfig {\n return config.transformType !== 'image' && config.transformType !== 'group';\n}\n\nexport function isImageElementConfig(config: AnyElementConfig): config is ImageElementConfig {\n return config.transformType === 'image';\n}\n\nexport function isCustomElementConfig(config: AnyElementConfig): config is CustomElementConfig {\n return config.transformType === 'custom';\n}\n\nexport function isCircleElementConfig(config: AnyElementConfig): config is CircleElementConfig {\n return config.transformType === 'circle';\n}\n\nexport function isLeanElementConfig(config: AnyElementConfig): config is LeanElementConfig {\n return config.transformType === 'lean';\n}\n\nexport function isArchElementConfig(config: AnyElementConfig): config is ArchElementConfig {\n return config.transformType === 'arch';\n}\n\nexport function isAscendElementConfig(config: AnyElementConfig): config is AscendElementConfig {\n return config.transformType === 'ascend';\n}\n\nexport function isWaveElementConfig(config: AnyElementConfig): config is WaveElementConfig {\n return config.transformType === 'wave';\n}\n\nexport function isFlagElementConfig(config: AnyElementConfig): config is FlagElementConfig {\n return config.transformType === 'flag';\n}\n\nexport function isGroupElementConfig(config: AnyElementConfig): config is GroupElementConfig {\n return config.transformType === 'group';\n}\n\nexport function isShapeElementConfig(config: AnyElementConfig): config is ShapeElementConfig {\n return config.transformType === 'shape';\n}\n\nexport function isPathElementConfig(config: AnyElementConfig): config is PathElementConfig {\n return config.transformType === 'path';\n}\n\n// ============================================================================\n// Transform Data Type Guards\n// ============================================================================\n\nexport function isShapeTransform(data: AnyTransformData): data is ShapeTransformData {\n return data.type === 'shape';\n}\n\nexport function isPathTransform(data: AnyTransformData): data is PathTransformData {\n return data.type === 'path';\n}\n\n// ============================================================================\n// New Feature Type Guards\n// ============================================================================\n\n// Stroke type guard\nexport function hasStroke(config: AnyElementConfig): boolean {\n return config.stroke?.enabled === true;\n}\n\n// Masks type guard\nexport function hasMasks(config: AnyElementConfig): boolean {\n return Array.isArray(config.masks) && config.masks.length > 0;\n}\n\n// Knockout type guard (also matches clip mode)\nexport function isKnockout(config: AnyElementConfig): boolean {\n return config.blendMode === 'knockout' || config.blendMode === 'clip';\n}\n\n// Distress effect type guard\nexport function hasDistressEffect(config: AnyElementConfig): boolean {\n return config.distressEffect?.enabled === true;\n}\n\n// ============================================================================\n// Canvas Error Types\n// ============================================================================\n\n/**\n * Categories of errors that can occur within the canvas.\n *\n * - 'render': Errors during canvas rendering (element draw, compositing)\n * - 'export': Errors during artboard export (PNG generation, worker failures)\n * - 'import': Errors loading or deserializing saved state / initial elements\n * - 'image': Errors loading images (network failures, CORS, decode errors)\n * - 'worker': Errors in the export web worker\n * - 'state': Errors in state management (artboard creation, element updates)\n * - 'unknown': Uncategorized errors caught by the ErrorBoundary\n */\nexport type CanvasErrorCategory = 'render' | 'export' | 'import' | 'image' | 'worker' | 'state' | 'unknown';\n\n/**\n * Structured error type for all canvas errors.\n * Provides consistent error reporting with category, context, and recoverability info.\n *\n * @example\n * ```tsx\n * <SnowconeCanvas\n * onError={(error) => {\n * if (error.category === 'image' && error.recoverable) {\n * showToast('Image failed to load, please try another');\n * } else if (!error.recoverable) {\n * reportCrash(error);\n * }\n * }}\n * />\n * ```\n */\nexport interface CanvasError {\n /** The category of this error */\n category: CanvasErrorCategory;\n /** Human-readable error message */\n message: string;\n /** The original Error object, if available */\n originalError?: Error;\n /** The element ID involved, if applicable */\n elementId?: string;\n /** The artboard ID involved, if applicable */\n artboardId?: string;\n /** Whether the canvas can continue operating after this error */\n recoverable: boolean;\n}\n","/**\n * TextElement - Base class for all text-based elements\n * Extends BaseElement with text-specific properties and behavior\n */\n\nimport { BaseElement } from './BaseElement.js';\nimport { MIN_FONT_SIZE, MAX_FONT_SIZE } from '../constants.js';\nimport type {\n TextAlign,\n BoundingBox,\n BaseTextElementConfig,\n ResizeAnchor,\n GlyphOverride,\n OpenTypeFeatures,\n CharacterStyle,\n TransformStartData,\n} from '../types/index.js';\nimport { RichText } from '../types/index.js';\n\nexport class TextElement extends BaseElement {\n // Rich text support\n richText?: RichText;\n // Legacy plain text (kept for backward compatibility)\n text: string;\n // Element-level style defaults (used when richText spans don't specify)\n fontSize: number;\n fontFamily: string;\n color: string;\n textAlign: TextAlign;\n bold: boolean;\n italic: boolean;\n underline: boolean;\n strikethrough: boolean;\n\n // Glyph and OpenType features\n glyphOverrides?: GlyphOverride[];\n openTypeFeatures?: OpenTypeFeatures;\n\n constructor(config: Partial<BaseTextElementConfig> = {}) {\n super(config);\n\n // Set default transform type for text elements\n this.transformType = config.transformType || 'custom';\n\n // Element-level defaults\n this.fontSize = config.fontSize || 72;\n this.fontFamily = config.fontFamily || 'Arial';\n this.color = config.color || '#333333';\n this.bold = config.bold !== undefined ? config.bold : false;\n this.italic = config.italic !== undefined ? config.italic : false;\n this.underline = config.underline !== undefined ? config.underline : false;\n this.strikethrough = config.strikethrough !== undefined ? config.strikethrough : false;\n this.textAlign = config.textAlign || 'center';\n\n // Rich text initialization with backward compatibility\n if (config.richText) {\n // New format: use richText\n if (config.richText instanceof RichText) {\n this.richText = config.richText;\n } else {\n // Deserialize from JSON\n this.richText = RichText.fromJSON(config.richText);\n }\n this.text = this.richText.getText();\n } else if (config.text !== undefined) {\n // Legacy format: migrate to richText\n // Create richText from plain text with EMPTY style so it inherits from element defaults\n this.text = config.text;\n this.richText = RichText.fromPlainText(config.text, {});\n } else {\n // No text provided: default\n this.text = 'Text';\n this.richText = RichText.fromPlainText('Text', {});\n }\n\n // Glyph and OpenType features\n this.glyphOverrides = config.glyphOverrides;\n this.openTypeFeatures = config.openTypeFeatures;\n }\n\n /**\n * Get bounding box in world coordinates\n * Must be implemented by subclasses\n */\n getBoundingBox(): BoundingBox {\n throw new Error('getBoundingBox() must be implemented by subclass');\n }\n\n /**\n * Render the element\n * Must be implemented by subclasses\n */\n render(_ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n throw new Error('render() must be implemented by subclass');\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): BaseTextElementConfig {\n const baseJSON = super.toJSON();\n return {\n ...baseJSON,\n // Serialize rich text\n ...(this.richText && { richText: this.richText.toJSON() }),\n // Keep legacy text for backward compatibility\n text: this.text,\n fontSize: this.fontSize,\n fontFamily: this.fontFamily,\n color: this.color,\n textAlign: this.textAlign,\n bold: this.bold,\n italic: this.italic,\n underline: this.underline,\n strikethrough: this.strikethrough,\n // Typography properties (only include if defined)\n ...(this.openTypeFeatures && { openTypeFeatures: { ...this.openTypeFeatures } }),\n ...(this.glyphOverrides && { glyphOverrides: this.glyphOverrides.map((g) => ({ ...g })) }),\n };\n }\n\n /**\n * Clone this element\n */\n clone(): TextElement {\n const Constructor = this.constructor as new (config: Partial<BaseTextElementConfig>) => TextElement;\n return new Constructor(this.toJSON());\n }\n\n /**\n * Update text content\n * @param newText - Plain text to set (resets to element defaults)\n */\n setText(newText: string): void {\n this.text = newText;\n // Update richText as well - use empty style to inherit from element defaults\n this.richText = RichText.fromPlainText(newText, {});\n }\n\n /**\n * Get the plain text content\n */\n getText(): string {\n return this.richText?.getText() || this.text;\n }\n\n /**\n * Get the rich text object\n */\n getRichText(): RichText {\n if (!this.richText) {\n // Migrate on-demand if needed - use empty style to inherit from element defaults\n this.richText = RichText.fromPlainText(this.text, {});\n }\n return this.richText;\n }\n\n /**\n * Set the rich text object\n */\n setRichText(newRichText: RichText): void {\n this.richText = newRichText;\n this.text = newRichText.getText();\n }\n\n /**\n * Get the element-level default style\n */\n getDefaultStyle(): CharacterStyle {\n return {\n color: this.color,\n fontFamily: this.fontFamily,\n fontSize: this.fontSize,\n bold: this.bold,\n italic: this.italic,\n underline: this.underline,\n strikethrough: this.strikethrough,\n };\n }\n\n /**\n * Apply character-level formatting to a text range\n */\n applyFormatting(start: number, end: number, style: CharacterStyle): void {\n const richText = this.getRichText();\n richText.applyStyle(start, end, style);\n this.text = richText.getText();\n }\n\n /**\n * Update font size\n */\n setFontSize(newFontSize: number): void {\n this.fontSize = Math.max(MIN_FONT_SIZE, Math.min(MAX_FONT_SIZE, newFontSize));\n }\n\n /**\n * Update color\n * Updates both element-level default and all rich text spans that currently match the old color\n */\n setColor(newColor: string): void {\n const oldColor = this.color;\n this.color = newColor;\n\n // Also update all rich text spans that have the old color or inherit from element default\n if (this.richText) {\n for (let i = 0; i < this.richText.spans.length; i++) {\n const span = this.richText.spans[i];\n if (span.style.color === undefined || span.style.color === oldColor) {\n span.style.color = newColor;\n }\n }\n }\n }\n\n /**\n * Handle resize transform\n * Must be implemented by subclasses\n */\n resize(_anchor: ResizeAnchor, _newWidth: number, _newHeight: number, _startData: TransformStartData): void | boolean {\n throw new Error('resize() must be implemented by subclass');\n }\n\n /**\n * Called when transform starts\n * Returns data to be passed to resize()\n */\n override getTransformStartData(): TransformStartData {\n const baseData = super.getTransformStartData();\n return {\n ...baseData,\n fontSize: this.fontSize,\n // Capture rich text state for proportional scaling during resize\n richText: this.richText ? this.richText.clone() : null,\n };\n }\n}\n\nexport default TextElement;\n","/**\n * TextMetrics - Shared utilities for text measurement and layout\n * Reduces duplication across transform types\n */\n\nimport { LINE_HEIGHT_MULTIPLIER } from '../constants.js';\n\n// Reusable canvas for text measurements (works in both main thread and worker)\nlet _measureCanvas: HTMLCanvasElement | OffscreenCanvas | null = null;\n\n/**\n * Get or create a canvas for text measurements.\n * Uses OffscreenCanvas in worker contexts where document is unavailable.\n * @private\n */\nfunction getMeasureCanvas(): HTMLCanvasElement | OffscreenCanvas {\n if (!_measureCanvas) {\n if (typeof document !== 'undefined') {\n _measureCanvas = document.createElement('canvas');\n } else if (typeof OffscreenCanvas !== 'undefined') {\n _measureCanvas = new OffscreenCanvas(1, 1);\n } else {\n throw new Error('No canvas available for text measurement');\n }\n }\n return _measureCanvas;\n}\n\n/**\n * Build font string with optional bold and italic\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {boolean} bold - Whether to use bold weight\n * @param {boolean} italic - Whether to use italic style\n * @returns {string} Font string for ctx.font\n */\nexport function buildFontString(\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false\n): string {\n // Build font string parts, only including non-default values\n const parts: string[] = [];\n\n // Only add italic if true (omit 'normal' as it's the default)\n if (italic) {\n parts.push('italic');\n }\n\n // Only add bold if true (omit 'normal' as it's the default)\n if (bold) {\n parts.push('bold');\n }\n\n // Always include font size and family\n parts.push(`${fontSize}px`);\n parts.push(fontFamily);\n\n return parts.join(' ');\n}\n\n/**\n * Word wrap text to fit within a given width\n * @param {string} text - Text to wrap\n * @param {number} maxWidth - Maximum width in pixels\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {boolean} bold - Whether text is bold\n * @param {boolean} italic - Whether text is italic\n * @param {number} lockedLineCount - Optional locked line count (forces exact number of lines during corner resize)\n * @param {boolean} strict - If true, disable tolerance (wrap exactly at maxWidth, matching richText behavior)\n * @returns {string[]} Array of lines\n */\nexport function wrapText(\n text: string,\n maxWidth: number,\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false,\n lockedLineCount?: number,\n strict: boolean = false\n): string[] {\n const canvas = getMeasureCanvas();\n const ctx = canvas.getContext('2d')!;\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n\n // If text is all whitespace, return as-is (no wrapping needed)\n if (text.trim().length === 0) {\n return [text];\n }\n\n // Handle explicit newlines: split by \\n first, wrap each line separately\n if (text.includes('\\n')) {\n const explicitLines = text.split('\\n');\n const allWrappedLines: string[] = [];\n\n for (let i = 0; i < explicitLines.length; i++) {\n const line = explicitLines[i];\n const wrappedLine = wrapText(line, maxWidth, fontSize, fontFamily, bold, italic, undefined, strict);\n allWrappedLines.push(...wrappedLine);\n }\n\n return allWrappedLines;\n }\n\n // Preserve leading and trailing spaces\n const leadingSpacesMatch = text.match(/^\\s*/);\n const trailingSpacesMatch = text.match(/\\s*$/);\n const leadingSpaces = leadingSpacesMatch ? leadingSpacesMatch[0] : '';\n const trailingSpaces = trailingSpacesMatch ? trailingSpacesMatch[0] : '';\n const trimmedText = text.substring(leadingSpaces.length, text.length - trailingSpaces.length);\n\n const words = trimmedText.split(' ');\n\n // If line count is locked (during corner resize), force that exact number of lines\n if (lockedLineCount !== undefined && lockedLineCount > 0) {\n if (lockedLineCount === 1) {\n return [leadingSpaces + words.join(' ') + trailingSpaces];\n }\n\n // Distribute words as evenly as possible across locked line count\n const lines: string[] = [];\n const wordsPerLine = Math.ceil(words.length / lockedLineCount);\n\n for (let i = 0; i < words.length; i += wordsPerLine) {\n const lineWords = words.slice(i, i + wordsPerLine);\n lines.push(lineWords.join(' '));\n }\n\n // Add leading spaces to first line, trailing spaces to last line\n if (lines.length > 0) {\n lines[0] = leadingSpaces + lines[0];\n lines[lines.length - 1] = lines[lines.length - 1] + trailingSpaces;\n }\n\n return lines;\n }\n\n // Normal wrapping logic (no lock)\n // First pass: check if all text fits on one line with generous tolerance\n const allText = words.join(' ');\n // CRITICAL FIX: Measure WITH leading/trailing spaces to avoid \"fits but doesn't fit\" bug\n // where we measure \"Foo\" but return \" Foo\" which is wider\n const allTextWithSpaces = leadingSpaces + allText + trailingSpaces;\n const allTextWidth = ctx.measureText(allTextWithSpaces).width;\n\n // Use a percentage-based tolerance that scales with maxWidth\n // This provides consistent behavior regardless of text size during resize\n // At smaller sizes, use higher percentages to provide more stability\n // In strict mode, use zero tolerance to match richText wrapping behavior\n const isSmallSize = maxWidth < 200;\n const wrapTolerancePercent = strict ? 0 : (isSmallSize ? 0.08 : 0.05);\n const wrapTolerance = strict ? 0 : Math.max(20, maxWidth * wrapTolerancePercent);\n\n // If text fits on one line (with tolerance), keep it that way\n if (allTextWidth <= maxWidth + wrapTolerance) {\n return [allTextWithSpaces];\n }\n\n // Otherwise, perform word wrapping with smaller percentage-based tolerance\n const lines: string[] = [];\n let currentLine = '';\n const lineTolerancePercent = strict ? 0 : (isSmallSize ? 0.05 : 0.03);\n const lineTolerance = strict ? 0 : Math.max(15, maxWidth * lineTolerancePercent);\n\n for (let i = 0; i < words.length; i++) {\n const word = words[i];\n const wordWidth = ctx.measureText(word).width;\n\n // Check if word itself is too wide (needs character-level breaking)\n if (wordWidth > maxWidth + lineTolerance) {\n // Finish current line if it has content\n if (currentLine.length > 0) {\n lines.push(currentLine);\n currentLine = '';\n }\n\n // Break word at character level\n const chars = Array.from(word);\n let charBuffer = '';\n\n for (const char of chars) {\n const testBuffer = charBuffer + char;\n const bufferWidth = ctx.measureText(testBuffer).width;\n\n if (bufferWidth > maxWidth + lineTolerance && charBuffer.length > 0) {\n // Finish current line with buffer\n lines.push(charBuffer);\n charBuffer = char;\n } else {\n charBuffer = testBuffer;\n }\n }\n\n // Set remaining buffer as current line\n currentLine = charBuffer;\n } else {\n // Word fits on a line - check if we need to wrap\n const testLine = currentLine.length > 0 ? currentLine + ' ' + word : word;\n const metrics = ctx.measureText(testLine);\n\n if (metrics.width > maxWidth + lineTolerance && currentLine.length > 0) {\n lines.push(currentLine);\n currentLine = word;\n } else {\n currentLine = testLine;\n }\n }\n }\n\n if (currentLine.length > 0) {\n lines.push(currentLine);\n }\n\n // Add leading spaces to first line, trailing spaces to last line\n // CRITICAL FIX: Check if adding spaces makes lines too wide\n if (lines.length > 0) {\n // Check if adding leading space makes first line too wide\n if (leadingSpaces.length > 0) {\n const firstLineWithSpace = leadingSpaces + lines[0];\n const firstLineWidth = ctx.measureText(firstLineWithSpace).width;\n\n if (firstLineWidth > maxWidth + lineTolerance) {\n // Leading space doesn't fit on same line as content - put it on its own line\n // This matches rich text behavior where \" Foo\" wraps to [\" \", \"Foo\"]\n lines.unshift(leadingSpaces);\n } else {\n // Fits - add to first line\n lines[0] = firstLineWithSpace;\n }\n }\n\n // Check if adding trailing space makes last line too wide\n if (trailingSpaces.length > 0) {\n const lastIdx = lines.length - 1;\n const lastLineWithSpace = lines[lastIdx] + trailingSpaces;\n const lastLineWidth = ctx.measureText(lastLineWithSpace).width;\n\n if (lastLineWidth > maxWidth + lineTolerance) {\n // Trailing space doesn't fit - put it on its own line\n lines.push(trailingSpaces);\n } else {\n // Fits - add to last line\n lines[lastIdx] = lastLineWithSpace;\n }\n }\n }\n\n return lines;\n}\n\n/**\n * Measure width of text\n * @param {string} text - Text to measure\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {boolean} bold - Whether text is bold\n * @param {boolean} italic - Whether text is italic\n * @returns {number} Width in pixels\n */\nexport function measureTextWidth(\n text: string,\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false\n): number {\n const canvas = getMeasureCanvas();\n const ctx = canvas.getContext('2d')!;\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n return ctx.measureText(text).width;\n}\n\n/**\n * Get font metrics for accurate text positioning\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {boolean} bold - Whether text is bold\n * @param {boolean} italic - Whether text is italic\n * @returns {Object} {ascent, descent, height}\n */\nexport function getFontMetrics(\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false\n): { ascent: number; descent: number; height: number } {\n const canvas = getMeasureCanvas();\n const ctx = canvas.getContext('2d')!;\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n\n // Measure sample text with tall ascenders and deep descenders\n const sampleText = 'ÁÉÍgjpqy';\n const metrics = ctx.measureText(sampleText);\n\n const ascent = metrics.actualBoundingBoxAscent || fontSize * 0.8;\n const descent = metrics.actualBoundingBoxDescent || fontSize * 0.2;\n const height = ascent + descent;\n\n return { ascent, descent, height };\n}\n\n/**\n * Calculate visual bounds for multi-line text\n * @param {string[]} lines - Array of text lines\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {number} lineHeight - Line height multiplier (e.g., 1.2)\n * @returns {Object} {width, height}\n */\nexport function calculateTextBounds(\n lines: string[],\n fontSize: number,\n fontFamily: string,\n lineHeight: number = 1.2\n): { width: number; height: number } {\n const fontMetrics = getFontMetrics(fontSize, fontFamily);\n\n // Find max width of all lines\n let maxWidth = 0;\n for (const line of lines) {\n const width = measureTextWidth(line, fontSize, fontFamily);\n maxWidth = Math.max(maxWidth, width);\n }\n\n // Calculate height\n let height;\n if (lines.length === 1) {\n height = fontMetrics.height;\n } else {\n // All lines except last use full lineHeight, last line uses measured height\n const lineSpacing = fontSize * lineHeight;\n height = (lines.length - 1) * lineSpacing + fontMetrics.height;\n }\n\n return { width: maxWidth, height };\n}\n\n/**\n * Render multi-line text with alignment and text decorations\n * @param {CanvasRenderingContext2D} ctx - Canvas context\n * @param {string[]} lines - Array of text lines\n * @param {number} x - X position (meaning depends on alignment)\n * @param {number} y - Y position (top of first line baseline)\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {string} alignment - 'left', 'center', or 'right'\n * @param {number} containerWidth - Width of text container (for alignment)\n * @param {number} horizontalPadding - Padding on sides\n * @param {number} lineHeight - Line height multiplier\n * @param {boolean} bold - Whether text is bold\n * @param {boolean} italic - Whether text is italic\n * @param {boolean} underline - Whether text is underlined\n * @param {boolean} strikethrough - Whether text has strikethrough\n */\nexport function renderMultilineText(\n ctx: CanvasRenderingContext2D,\n lines: string[],\n x: number,\n y: number,\n fontSize: number,\n fontFamily: string,\n alignment: CanvasTextAlign = 'left',\n containerWidth: number = 0,\n horizontalPadding: number = 0,\n lineHeight: number = 1.2,\n bold: boolean = false,\n italic: boolean = false,\n underline: boolean = false,\n strikethrough: boolean = false\n): void {\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n ctx.textBaseline = 'alphabetic';\n ctx.textAlign = alignment;\n\n const fontMetrics = getFontMetrics(fontSize, fontFamily, bold, italic);\n const lineSpacing = fontSize * lineHeight;\n\n lines.forEach((line: string, index: number) => {\n let xPos = x + horizontalPadding;\n\n if (alignment === 'center') {\n xPos = x + containerWidth / 2;\n } else if (alignment === 'right') {\n xPos = x + containerWidth - horizontalPadding;\n }\n\n const yPos = y + fontMetrics.ascent + index * lineSpacing;\n ctx.fillText(line, xPos, yPos);\n\n // Draw underline if enabled\n if (underline) {\n const lineWidth = measureTextWidth(line, fontSize, fontFamily, bold, italic);\n let underlineX = xPos;\n\n if (alignment === 'center') {\n underlineX = xPos - lineWidth / 2;\n } else if (alignment === 'right') {\n underlineX = xPos - lineWidth;\n }\n\n const underlineY = yPos + fontSize * 0.1; // Slightly below baseline\n ctx.beginPath();\n ctx.moveTo(underlineX, underlineY);\n ctx.lineTo(underlineX + lineWidth, underlineY);\n ctx.lineWidth = Math.max(1, fontSize * 0.05);\n ctx.strokeStyle = ctx.fillStyle as string;\n ctx.stroke();\n }\n\n // Draw strikethrough if enabled\n if (strikethrough) {\n const lineWidth = measureTextWidth(line, fontSize, fontFamily, bold, italic);\n let strikeX = xPos;\n\n if (alignment === 'center') {\n strikeX = xPos - lineWidth / 2;\n } else if (alignment === 'right') {\n strikeX = xPos - lineWidth;\n }\n\n const strikeY = yPos - fontMetrics.ascent * 0.4; // Middle of text\n ctx.beginPath();\n ctx.moveTo(strikeX, strikeY);\n ctx.lineTo(strikeX + lineWidth, strikeY);\n ctx.lineWidth = Math.max(1, fontSize * 0.05);\n ctx.strokeStyle = ctx.fillStyle as string;\n ctx.stroke();\n }\n });\n}\n\n/**\n * Calculate container height for text with given line count\n * @param {number} lineCount - Number of lines\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {number} lineHeight - Line height multiplier\n * @returns {number} Container height in pixels\n */\nexport function calculateContainerHeight(\n lineCount: number,\n fontSize: number,\n fontFamily: string,\n lineHeight: number = 1.2\n): number {\n if (lineCount === 1) {\n return fontSize * lineHeight;\n }\n\n const fontMetrics = getFontMetrics(fontSize, fontFamily);\n const lineSpacing = fontSize * lineHeight;\n return (lineCount - 1) * lineSpacing + fontMetrics.height;\n}\n\n/**\n * Calculate visual bounds for wrapped text with space collapsing applied\n * This matches the actual rendering behavior where trailing spaces on wrapped lines are hidden\n *\n * @param {string} text - Text to measure\n * @param {number} maxWidth - Maximum width for wrapping (or Infinity for no wrapping)\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {boolean} bold - Whether text is bold\n * @param {boolean} italic - Whether text is italic\n * @param {number} lockedLineCount - Optional locked line count (forces exact number of lines during corner resize)\n * @param {boolean} strict - If true, disable tolerance (wrap exactly at maxWidth)\n * @returns {Object} {width, height, lines} - Adjusted dimensions and line array\n */\nexport function calculateVisualBoundsWithSpaceCollapsing(\n text: string,\n maxWidth: number,\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false,\n lockedLineCount?: number,\n strict: boolean = false\n): { width: number; height: number; lines: string[] } {\n // Get wrapped lines (which preserve trailing spaces)\n const lines = wrapText(text, maxWidth, fontSize, fontFamily, bold, italic, lockedLineCount, strict);\n\n // Determine paragraph boundaries (lines separated by explicit \\n)\n // In wrapText, all lines from word-wrapping are from the same paragraph\n // Only explicit \\n characters create paragraph boundaries\n const hasExplicitNewlines = text.includes('\\n');\n\n // If no explicit newlines, all wrapped lines are from one paragraph\n // First line is paragraph start, last line is paragraph end\n // Middle lines are neither (so their trailing spaces get hidden)\n const paragraphMetadata = lines.map((_, index) => ({\n isParagraphStart: !hasExplicitNewlines && index === 0,\n isParagraphEnd: !hasExplicitNewlines && index === lines.length - 1,\n }));\n\n // Calculate metrics for each line with space collapsing applied\n const canvas = getMeasureCanvas();\n const ctx = canvas.getContext('2d')!;\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n\n let maxLineWidth = 0;\n const fontMetrics = getFontMetrics(fontSize, fontFamily, bold, italic);\n\n // Apply space collapsing and collect visible lines\n // CRITICAL: We track total line count (including empty lines after collapsing)\n // to match cursor positions and selection bounds\n const visibleLines: string[] = [];\n let totalLineCount = 0;\n\n lines.forEach((line, index) => {\n const isParagraphStart = paragraphMetadata[index].isParagraphStart;\n const isParagraphEnd = paragraphMetadata[index].isParagraphEnd;\n\n // Apply space layout rules (matching renderRichTextFillOnly logic)\n let visibleText = line;\n\n // Hide leading spaces when NOT at paragraph start\n if (!isParagraphStart) {\n const leadingSpacesMatch = line.match(/^ +/);\n const leadingSpacesCount = leadingSpacesMatch ? leadingSpacesMatch[0].length : 0;\n if (leadingSpacesCount > 0) {\n visibleText = visibleText.substring(leadingSpacesCount);\n }\n }\n\n // Hide trailing spaces when NOT at paragraph end\n if (!isParagraphEnd) {\n const trailingSpacesMatch = visibleText.match(/ +$/);\n const trailingSpacesCount = trailingSpacesMatch ? trailingSpacesMatch[0].length : 0;\n if (trailingSpacesCount > 0) {\n visibleText = visibleText.substring(0, visibleText.length - trailingSpacesCount);\n }\n }\n\n // Count this line regardless of whether it's empty\n totalLineCount++;\n\n // Only measure width for non-empty lines\n if (visibleText.length > 0) {\n visibleLines.push(visibleText);\n\n // Measure the visible text width\n const lineWidth = ctx.measureText(visibleText).width;\n maxLineWidth = Math.max(maxLineWidth, lineWidth);\n }\n });\n\n // Calculate height based on TOTAL line count (including empty lines)\n // This ensures selection bounds and cursor positions align correctly\n // Example: \" Foo\" wrapping to [\" \", \"Foo\"] has 2 lines even though first is empty\n let height;\n // Use shared constant for line height to ensure consistency with renderers\n const lineHeight = LINE_HEIGHT_MULTIPLIER; \n if (totalLineCount === 1) {\n height = fontMetrics.height;\n } else if (totalLineCount === 0) {\n // No lines at all - return zero height\n height = 0;\n } else {\n const lineSpacing = fontSize * lineHeight;\n height = (totalLineCount - 1) * lineSpacing + fontMetrics.height;\n }\n\n return {\n width: maxLineWidth,\n height: height,\n lines: lines,\n };\n}\n\nexport default {\n wrapText,\n measureTextWidth,\n getFontMetrics,\n calculateTextBounds,\n renderMultilineText,\n calculateContainerHeight,\n calculateVisualBoundsWithSpaceCollapsing,\n};\n","/**\n * Transform Renderer - Pure rendering functions for all text transform types\n * Works with both CanvasRenderingContext2D and OffscreenCanvasRenderingContext2D\n */\n\nimport { buildFontString } from './canvas-renderer.js';\nimport { WAVE_SKEW_FACTOR } from '../constants.js';\nimport type {\n AnyTransformData,\n CircleTransformData,\n WaveTransformData,\n ArchTransformData,\n AscendTransformData,\n LeanTransformData,\n FlagTransformData,\n} from '../types/index.js';\n\ntype RenderContext = CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n\nexport interface SerializedTextTransformElement {\n id: string;\n type: string;\n text: string;\n x: number;\n y: number;\n rotation: number;\n fontSize: number;\n fontFamily: string;\n color: string;\n bold: boolean;\n italic: boolean;\n transformData: AnyTransformData;\n stroke?: {\n enabled: boolean;\n color: string;\n width: number;\n opacity?: number;\n lineCap?: CanvasLineCap;\n lineJoin?: CanvasLineJoin;\n miterLimit?: number;\n feather?: number;\n };\n}\n\n/**\n * Create an offscreen canvas compatible with both main thread and Web Worker.\n * Uses OffscreenCanvas when available, falls back to document.createElement.\n */\nfunction createOffscreenCanvas(\n width: number,\n height: number\n): HTMLCanvasElement | OffscreenCanvas {\n if (typeof OffscreenCanvas !== 'undefined') {\n return new OffscreenCanvas(width, height);\n }\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n return canvas;\n}\n\n/**\n * Apply stroke style to context and call strokeText before fillText.\n * This creates an outer-stroke effect: fill drawn on top covers the inner half.\n *\n * When called via the offscreen approach (stroke opacity < 1), opacity has\n * already been stripped so this renders at full alpha per character.\n */\nfunction applyStrokeToChar(ctx: RenderContext, char: string, stroke: SerializedTextTransformElement['stroke']): void {\n if (!stroke?.enabled) return;\n ctx.strokeStyle = stroke.color;\n ctx.lineWidth = stroke.width;\n ctx.lineCap = stroke.lineCap || 'round';\n ctx.lineJoin = stroke.lineJoin || 'round';\n if (stroke.miterLimit !== undefined) ctx.miterLimit = stroke.miterLimit;\n if (stroke.opacity !== undefined) {\n const prevAlpha = ctx.globalAlpha;\n ctx.globalAlpha = stroke.opacity;\n ctx.strokeText(char, 0, 0);\n ctx.globalAlpha = prevAlpha;\n } else {\n ctx.strokeText(char, 0, 0);\n }\n}\n\n/**\n * Wrap a transform render function with offscreen compositing when stroke\n * opacity < 1. Renders all strokes at full opacity on a temp canvas, then\n * composites the temp canvas onto the main canvas at the desired opacity.\n * This prevents overlap darkening where adjacent characters' strokes overlap.\n *\n * The renderFn is called twice when offscreen is needed:\n * 1. On the offscreen canvas with stroke.opacity = 1 (stroke-only pass)\n * 2. On the main canvas with stroke disabled (fill-only pass)\n * When offscreen is NOT needed, renderFn is called once normally.\n */\nfunction renderTransformWithOffscreenStroke(\n ctx: RenderContext,\n elementData: SerializedTextTransformElement,\n renderFn: (ctx: RenderContext, data: SerializedTextTransformElement) => void\n): void {\n const stroke = elementData.stroke;\n const strokeOpacity = stroke?.opacity ?? 1;\n const elementAlpha = ctx.globalAlpha;\n const hasStrokeOpacity = stroke?.enabled && strokeOpacity < 1;\n const hasElementOpacity = stroke?.enabled && elementAlpha < 1;\n const needsOffscreen = hasStrokeOpacity || hasElementOpacity;\n\n if (!needsOffscreen) {\n // No offscreen needed — render normally\n renderFn(ctx, elementData);\n return;\n }\n\n // Estimate bounds for the offscreen canvas — be generous to avoid clipping.\n // Characters can extend well beyond the transform width/radius, especially\n // for arch/circle transforms where text measured width exceeds the container.\n const fontSize = elementData.fontSize || 24;\n const strokeWidth = stroke!.width || 0;\n const td = elementData.transformData as { width?: number; radius?: number; archHeight?: number };\n const textEstimate = fontSize * elementData.text.length * 0.7;\n const containerSpan = td.width ?? (td.radius ? (td.radius * 2) : 0);\n // Use the larger of container span and text estimate — text can overflow container\n const estimatedSpan = Math.max(containerSpan, textEstimate);\n const featherPad = (stroke!.feather || 0) * 2;\n const padding = strokeWidth * 4 + featherPad + fontSize + 40;\n const estWidth = estimatedSpan + padding;\n // Arch/circle transforms need extra height for curvature\n const archExtra = td.archHeight ? Math.abs(td.archHeight) * fontSize * 2 : 0;\n const radiusExtra = td.radius ? td.radius : 0;\n const estHeight = fontSize * 3 + padding + archExtra + radiusExtra;\n\n // Account for current canvas scale\n const transform = ctx.getTransform();\n const scale = Math.max(Math.abs(transform.a), Math.abs(transform.d), 1);\n\n // Use even dimensions to avoid fractional half-offsets\n const offW = Math.ceil(estWidth * scale / 2) * 2;\n const offH = Math.ceil(estHeight * scale / 2) * 2;\n\n if (offW <= 0 || offH <= 0) {\n renderFn(ctx, elementData);\n return;\n }\n\n const offCanvas = createOffscreenCanvas(offW, offH);\n const offCtx = offCanvas.getContext('2d') as RenderContext | null;\n\n if (!offCtx) {\n renderFn(ctx, elementData);\n return;\n }\n\n // Replicate the main canvas transform, shifted so the element center\n // maps to the center of the offscreen canvas.\n const elemX = elementData.x || 0;\n const elemY = elementData.y || 0;\n const centerPixelX = transform.a * elemX + transform.c * elemY + transform.e;\n const centerPixelY = transform.b * elemX + transform.d * elemY + transform.f;\n\n // Use rounded draw-back coordinates for pixel-perfect compositing\n const drawBackX = Math.round(centerPixelX - offW / 2);\n const drawBackY = Math.round(centerPixelY - offH / 2);\n\n offCtx.setTransform(\n transform.a, transform.b,\n transform.c, transform.d,\n offW / 2 - centerPixelX + transform.e,\n offH / 2 - centerPixelY + transform.f\n );\n\n // When element opacity is involved, render the entire element (stroke + fill)\n // at full opacity on the offscreen canvas, then composite everything at once.\n // This prevents overlap darkening where adjacent characters' strokes overlap.\n if (hasElementOpacity) {\n // Render at full alpha on offscreen canvas\n offCtx.globalAlpha = 1.0;\n const savedStrokeOpa = stroke!.opacity;\n if (hasStrokeOpacity) stroke!.opacity = 1.0;\n renderFn(offCtx, elementData);\n if (hasStrokeOpacity) stroke!.opacity = savedStrokeOpa;\n\n // Composite entire offscreen result at element opacity\n ctx.save();\n ctx.resetTransform();\n ctx.globalAlpha = elementAlpha;\n ctx.drawImage(offCanvas as CanvasImageSource, drawBackX, drawBackY);\n ctx.restore();\n return;\n }\n\n // Stroke opacity only (no element opacity) — original behavior:\n // Render stroke at full opacity on offscreen, fill-only on main canvas.\n const savedOpacity = stroke!.opacity;\n stroke!.opacity = 1.0;\n renderFn(offCtx, elementData);\n stroke!.opacity = savedOpacity;\n\n // Now render fill-only on the main canvas (disable stroke temporarily).\n const savedEnabled = stroke!.enabled;\n stroke!.enabled = false;\n renderFn(ctx, elementData);\n stroke!.enabled = savedEnabled;\n\n // Composite the offscreen stroke layer onto the main canvas at desired opacity.\n ctx.save();\n ctx.resetTransform();\n ctx.globalAlpha = strokeOpacity;\n ctx.drawImage(\n offCanvas as CanvasImageSource,\n drawBackX,\n drawBackY\n );\n ctx.restore();\n}\n\n// ============================================================================\n// Circle Transform\n// ============================================================================\n\nexport function renderCircleTransform(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n renderTransformWithOffscreenStroke(ctx, elementData, renderCircleTransformDirect);\n}\n\nfunction renderCircleTransformDirect(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n const transformData = elementData.transformData as CircleTransformData;\n\n ctx.save();\n\n // Move to center\n ctx.translate(elementData.x, elementData.y);\n // Use negative angle for clockwise rotation\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n\n // Now apply scale for rendering\n ctx.scale(transformData.scale, transformData.scale);\n\n // Set up rendering at base font size in scaled coordinate space\n const weight = elementData.bold ? 'bold' : 'normal';\n const style = elementData.italic ? 'italic' : 'normal';\n ctx.font = `${style} ${weight} ${elementData.fontSize}px ${elementData.fontFamily}`;\n ctx.fillStyle = elementData.color;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n\n // Measure text widths in the current (scaled) coordinate space\n // This gives us measurements that are already in scaled units\n const chars = elementData.text.split('');\n const charWidths = chars.map((char) => ctx.measureText(char).width);\n const totalWidth = charWidths.reduce((sum, w) => sum + w, 0);\n\n if (transformData.reverse) {\n // Bottom text: start from right side and go left\n let currentAngle = Math.PI / 2 + totalWidth / (2 * transformData.radius);\n\n chars.forEach((char, i) => {\n const charWidth = charWidths[i];\n const angleStep = charWidth / transformData.radius;\n\n const x = Math.cos(currentAngle - angleStep / 2) * transformData.radius;\n const y = Math.sin(currentAngle - angleStep / 2) * transformData.radius;\n\n ctx.save();\n ctx.translate(x, y);\n ctx.rotate(currentAngle - angleStep / 2 - Math.PI / 2);\n applyStrokeToChar(ctx, char, elementData.stroke);\n ctx.fillText(char, 0, 0);\n ctx.restore();\n\n currentAngle -= angleStep;\n });\n } else {\n // Top text: start from left side and go right\n let currentAngle = -Math.PI / 2 - totalWidth / (2 * transformData.radius);\n\n chars.forEach((char, i) => {\n const charWidth = charWidths[i];\n const angleStep = charWidth / transformData.radius;\n\n const x = Math.cos(currentAngle + angleStep / 2) * transformData.radius;\n const y = Math.sin(currentAngle + angleStep / 2) * transformData.radius;\n\n ctx.save();\n ctx.translate(x, y);\n ctx.rotate(currentAngle + angleStep / 2 + Math.PI / 2);\n applyStrokeToChar(ctx, char, elementData.stroke);\n ctx.fillText(char, 0, 0);\n ctx.restore();\n\n currentAngle += angleStep;\n });\n }\n\n ctx.restore();\n}\n\n// ============================================================================\n// Wave Transform\n// ============================================================================\n\nexport function renderWaveTransform(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n renderTransformWithOffscreenStroke(ctx, elementData, renderWaveTransformDirect);\n}\n\nfunction renderWaveTransformDirect(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n const transformData = elementData.transformData as WaveTransformData;\n\n ctx.save();\n\n // Draw text along wave path\n ctx.translate(elementData.x, elementData.y);\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n\n // Set font to element's fontSize (canvas transform will handle zoom)\n ctx.font = buildFontString(elementData.fontSize, elementData.fontFamily, elementData.bold, elementData.italic);\n ctx.fillStyle = elementData.color;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n\n const chars = elementData.text.split('');\n const totalWidth = chars.reduce((sum, char) => sum + ctx.measureText(char).width, 0);\n\n // Draw each character along the wave with proper perspective/skew\n let currentX = -totalWidth / 2;\n chars.forEach((char) => {\n const charWidth = ctx.measureText(char).width;\n const x = currentX + charWidth / 2;\n const normalizedX = x / (transformData.width / 2);\n\n // Calculate y position on sine wave\n const y = transformData.amplitude * elementData.fontSize * Math.sin(transformData.frequency * Math.PI * normalizedX);\n\n // Calculate the slope of the sine wave at this point for skew\n const slope =\n transformData.amplitude *\n transformData.frequency *\n Math.PI *\n Math.cos(transformData.frequency * Math.PI * normalizedX);\n\n ctx.save();\n ctx.translate(x, y);\n\n // Apply vertical skew based on the slope\n ctx.transform(1, slope * WAVE_SKEW_FACTOR, 0, 1, 0, 0);\n\n applyStrokeToChar(ctx, char, elementData.stroke);\n ctx.fillText(char, 0, 0);\n ctx.restore();\n\n currentX += charWidth;\n });\n\n ctx.restore();\n}\n\n// ============================================================================\n// Arch Transform\n// ============================================================================\n\nexport function renderArchTransform(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n renderTransformWithOffscreenStroke(ctx, elementData, renderArchTransformDirect);\n}\n\nfunction renderArchTransformDirect(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n const transformData = elementData.transformData as ArchTransformData;\n\n ctx.save();\n\n // Draw text along arch path\n ctx.translate(elementData.x, elementData.y);\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n\n // Set font to element's fontSize (canvas transform will handle zoom)\n ctx.font = buildFontString(elementData.fontSize, elementData.fontFamily, elementData.bold, elementData.italic);\n ctx.fillStyle = elementData.color;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n\n const chars = elementData.text.split('');\n const totalWidth = chars.reduce((sum, char) => sum + ctx.measureText(char).width, 0);\n const radius = transformData.width / 2;\n\n // Draw each character along the arch with proper perspective/skew\n let currentX = -totalWidth / 2;\n chars.forEach((char) => {\n const charWidth = ctx.measureText(char).width;\n const x = currentX + charWidth / 2;\n const normalizedX = x / radius;\n\n // Calculate y position on parabola\n const y = (Math.pow(normalizedX, 2) - 1) * transformData.archHeight * elementData.fontSize;\n\n // Calculate the slope of the curve at this point for skew\n const slope = 2 * normalizedX * transformData.archHeight;\n\n // Apply skew transformation to create perspective effect\n ctx.save();\n ctx.translate(x, y);\n\n // Apply vertical skew based on the slope\n const skewFactor = 0.3;\n ctx.transform(1, slope * skewFactor, 0, 1, 0, 0);\n\n applyStrokeToChar(ctx, char, elementData.stroke);\n ctx.fillText(char, 0, 0);\n ctx.restore();\n\n currentX += charWidth;\n });\n\n ctx.restore();\n}\n\n// ============================================================================\n// Ascend Transform\n// ============================================================================\n\nexport function renderAscendTransform(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n renderTransformWithOffscreenStroke(ctx, elementData, renderAscendTransformDirect);\n}\n\nfunction renderAscendTransformDirect(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n const transformData = elementData.transformData as AscendTransformData;\n\n ctx.save();\n\n // Draw text along diagonal path\n ctx.translate(elementData.x, elementData.y);\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n\n // Set font to element's fontSize (canvas transform will handle zoom)\n ctx.font = buildFontString(elementData.fontSize, elementData.fontFamily, elementData.bold, elementData.italic);\n ctx.fillStyle = elementData.color;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n\n const chars = elementData.text.split('');\n const totalWidth = chars.reduce((sum, char) => sum + ctx.measureText(char).width, 0);\n const angleRad = (transformData.ascendAngle * Math.PI) / 180;\n\n // Draw each character along the diagonal line with skew perspective\n let currentX = -totalWidth / 2;\n chars.forEach((char) => {\n const charWidth = ctx.measureText(char).width;\n const x = currentX + charWidth / 2;\n\n // Calculate y position on straight diagonal line\n const y = x * Math.tan(angleRad);\n\n // Calculate the slope of the diagonal line for skew\n const slope = Math.tan(angleRad);\n\n // Apply skew transformation to create perspective effect\n ctx.save();\n ctx.translate(x, y);\n\n // Apply vertical skew based on the slope\n const skewFactor = 1.0;\n ctx.transform(1, slope * skewFactor, 0, 1, 0, 0);\n\n applyStrokeToChar(ctx, char, elementData.stroke);\n ctx.fillText(char, 0, 0);\n ctx.restore();\n\n currentX += charWidth;\n });\n\n ctx.restore();\n}\n\n// ============================================================================\n// Lean Transform\n// ============================================================================\n\nexport function renderLeanTransform(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n renderTransformWithOffscreenStroke(ctx, elementData, renderLeanTransformDirect);\n}\n\nfunction renderLeanTransformDirect(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n const transformData = elementData.transformData as LeanTransformData;\n\n ctx.save();\n\n // Draw text with skew/slant\n ctx.translate(elementData.x, elementData.y);\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n\n // Apply skew transform based on leanAmount\n const skewAngle = (-transformData.leanAmount * Math.PI) / 4;\n ctx.transform(1, 0, Math.tan(skewAngle), 1, 0, 0);\n\n ctx.font = buildFontString(elementData.fontSize, elementData.fontFamily, elementData.bold, elementData.italic);\n ctx.fillStyle = elementData.color;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n\n // Draw text normally - skew transform handles the slant\n // Stroke before fill for outer-stroke effect\n if (elementData.stroke?.enabled) {\n ctx.save();\n ctx.strokeStyle = elementData.stroke.color;\n ctx.lineWidth = elementData.stroke.width;\n ctx.lineCap = elementData.stroke.lineCap || 'round';\n ctx.lineJoin = elementData.stroke.lineJoin || 'round';\n if (elementData.stroke.opacity !== undefined) ctx.globalAlpha = elementData.stroke.opacity;\n ctx.strokeText(elementData.text, 0, 0);\n ctx.restore();\n }\n ctx.fillText(elementData.text, 0, 0);\n\n ctx.restore();\n}\n\n// ============================================================================\n// Flag Transform\n// ============================================================================\n\nexport function renderFlagTransform(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n renderTransformWithOffscreenStroke(ctx, elementData, renderFlagTransformDirect);\n}\n\nfunction renderFlagTransformDirect(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n const transformData = elementData.transformData as FlagTransformData;\n\n ctx.save();\n\n // Draw text along flag wave path\n ctx.translate(elementData.x, elementData.y);\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n\n // Set font to element's fontSize (canvas transform will handle zoom)\n ctx.font = buildFontString(elementData.fontSize, elementData.fontFamily, elementData.bold, elementData.italic);\n ctx.fillStyle = elementData.color;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n\n const chars = elementData.text.split('');\n const totalWidth = chars.reduce((sum, char) => sum + ctx.measureText(char).width, 0);\n\n // Draw each character along the flag wave with proper perspective/skew\n let currentX = -totalWidth / 2;\n chars.forEach((char) => {\n const charWidth = ctx.measureText(char).width;\n const x = currentX + charWidth / 2;\n const normalizedX = x / (transformData.width / 2);\n\n // Flag effect: amplitude increases from center to edges\n const distanceFromCenter = Math.abs(normalizedX);\n const flagAmplitude = transformData.amplitude * distanceFromCenter;\n\n // Calculate y position on flag wave\n const y = flagAmplitude * elementData.fontSize * Math.sin(transformData.frequency * Math.PI * normalizedX);\n\n // Calculate the slope for skew\n const cosComponent =\n flagAmplitude * transformData.frequency * Math.PI * Math.cos(transformData.frequency * Math.PI * normalizedX);\n const sinComponent =\n transformData.amplitude * Math.sign(normalizedX) * Math.sin(transformData.frequency * Math.PI * normalizedX);\n const slope = cosComponent + sinComponent;\n\n ctx.save();\n ctx.translate(x, y);\n\n // Apply vertical skew based on the slope\n const skewFactor = 0.3;\n ctx.transform(1, slope * skewFactor, 0, 1, 0, 0);\n\n applyStrokeToChar(ctx, char, elementData.stroke);\n ctx.fillText(char, 0, 0);\n ctx.restore();\n\n currentX += charWidth;\n });\n\n ctx.restore();\n}\n","/**\n * Stroke utility functions for path creation\n */\n\nimport type { AnyElementConfig, BaseTextElementConfig, ImageElementConfig, CustomElementConfig, RenderingContext } from '../types/index.js';\nimport { buildFontString, measureTextWidth } from '../core/TextMetrics.js';\n\n/**\n * Create text path for stroking\n * Handles all text transform types\n */\nexport function createTextPath(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n element: AnyElementConfig,\n _renderingContext?: RenderingContext\n): void {\n // Position and rotate\n ctx.translate(element.x || 0, element.y || 0);\n ctx.rotate((-(element.rotation || 0) * Math.PI) / 180);\n\n // Narrow to text element config to access text properties\n const textElement = element as BaseTextElementConfig;\n\n // Set font for text measurement\n const fontSize = textElement.fontSize || 24;\n const fontFamily = textElement.fontFamily || 'Arial';\n const bold = textElement.bold || false;\n const italic = textElement.italic || false;\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n\n const text = textElement.text || '';\n\n // For straight text (custom transform), stroke the bounding box\n if (element.transformType === 'custom') {\n const customElement = element as CustomElementConfig;\n const width = customElement.transformData?.width || 200;\n\n // Create rectangle path around text bounds\n const textWidth = measureTextWidth(text, fontSize, fontFamily, bold, italic);\n const actualWidth = Math.min(textWidth, width);\n const height = fontSize * 1.2;\n\n ctx.rect(-actualWidth / 2, -height / 2, actualWidth, height);\n }\n // For text on path transforms, stroke the text outline\n else {\n // For curved/transformed text, we'll stroke along the text baseline\n // This is a simplified approach - full text outline requires more complex path generation\n\n // Approximate by drawing along baseline\n const textWidth = ctx.measureText(text).width;\n\n ctx.beginPath();\n ctx.moveTo(-textWidth / 2, 0);\n ctx.lineTo(textWidth / 2, 0);\n ctx.moveTo(-textWidth / 2, -fontSize);\n ctx.lineTo(textWidth / 2, -fontSize);\n ctx.moveTo(-textWidth / 2, fontSize * 0.2);\n ctx.lineTo(textWidth / 2, fontSize * 0.2);\n }\n}\n\n/**\n * Create image boundary path for stroking\n */\nexport function createImagePath(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n element: ImageElementConfig\n): void {\n if (!element.transformData) return;\n\n const { borderRadius = 0 } = element.transformData;\n const width = element.transformData.width ?? 100;\n const height = element.transformData.height ?? 100;\n\n // Position and rotate\n ctx.translate(element.x || 0, element.y || 0);\n ctx.rotate((-(element.rotation || 0) * Math.PI) / 180);\n\n // Create path with border radius\n ctx.beginPath();\n\n if (borderRadius > 0) {\n // Rounded rectangle\n const maxRadius = Math.min(width, height) / 2;\n const radius = Math.min(borderRadius, maxRadius);\n\n const x = -width / 2;\n const y = -height / 2;\n\n ctx.moveTo(x + radius, y);\n ctx.lineTo(x + width - radius, y);\n ctx.arcTo(x + width, y, x + width, y + radius, radius);\n ctx.lineTo(x + width, y + height - radius);\n ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius);\n ctx.lineTo(x + radius, y + height);\n ctx.arcTo(x, y + height, x, y + height - radius, radius);\n ctx.lineTo(x, y + radius);\n ctx.arcTo(x, y, x + radius, y, radius);\n ctx.closePath();\n } else {\n // Simple rectangle\n ctx.rect(-width / 2, -height / 2, width, height);\n }\n}\n\n/**\n * Create circular path (for circle transform or shapes)\n */\nexport function createCirclePath(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n x: number,\n y: number,\n radius: number\n): void {\n ctx.beginPath();\n ctx.arc(x, y, radius, 0, Math.PI * 2);\n ctx.closePath();\n}\n\n/**\n * Create rectangular path\n */\nexport function createRectPath(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n x: number,\n y: number,\n width: number,\n height: number\n): void {\n ctx.beginPath();\n ctx.rect(x, y, width, height);\n ctx.closePath();\n}\n","/**\n * StrokeRenderer - Universal stroke rendering for all element types\n *\n * IMPORTANT: This renders the ELEMENT STROKE EFFECT (element.stroke property)\n * This is NOT the hover ring effect (see CanvasRenderer.ts for hover rings)\n *\n * This renderer is ONLY used when:\n * - User explicitly enables stroke in the effects panel\n * - element.stroke.enabled = true\n *\n * Provides stroke rendering capabilities for:\n * - Text elements (all transform types)\n * - Image elements\n * - Shape elements\n * - Groups\n */\n\nimport type {\n StrokeConfig,\n AnyElementConfig,\n ImageElementConfig,\n RenderingContext,\n CustomElementConfig,\n BaseTextElementConfig,\n} from '../types/index.js';\nimport { createImagePath } from './stroke-utils.js';\nimport { getFontMetrics, calculateVisualBoundsWithSpaceCollapsing } from '../core/TextMetrics.js';\nimport { HORIZONTAL_PADDING, LINE_HEIGHT_MULTIPLIER } from '../constants.js';\n\n/**\n * Apply stroke style to canvas context\n */\nexport function applyStrokeStyle(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n stroke: StrokeConfig\n): void {\n ctx.strokeStyle = stroke.color;\n ctx.lineWidth = stroke.width;\n ctx.lineCap = stroke.lineCap || 'butt';\n ctx.lineJoin = stroke.lineJoin || 'miter';\n\n if (stroke.miterLimit !== undefined) {\n ctx.miterLimit = stroke.miterLimit;\n }\n\n if (stroke.dashArray && stroke.dashArray.length > 0) {\n ctx.setLineDash(stroke.dashArray);\n } else {\n ctx.setLineDash([]);\n }\n\n if (stroke.opacity !== undefined) {\n ctx.globalAlpha = stroke.opacity;\n }\n}\n\n/**\n * Create an offscreen canvas compatible with both main thread and Web Worker.\n * Uses OffscreenCanvas when available, falls back to document.createElement.\n */\nfunction createOffscreenCanvas(\n width: number,\n height: number\n): HTMLCanvasElement | OffscreenCanvas {\n if (typeof OffscreenCanvas !== 'undefined') {\n return new OffscreenCanvas(width, height);\n }\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n return canvas;\n}\n\n/**\n * Render stroke for text element\n * Uses ctx.strokeText() for proper text outline stroking\n * Now supports text wrapping for CustomTransform elements\n *\n * When stroke.opacity < 1, renders the entire stroke to a temporary canvas at\n * full opacity, then composites that layer onto the main canvas at the desired\n * opacity. This prevents darkening where adjacent characters' strokes overlap.\n */\nexport function renderTextStroke(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n element: AnyElementConfig,\n renderingContext?: RenderingContext\n): void {\n if (!element.stroke?.enabled) return;\n\n // This function is only called for text elements; narrow to access text properties\n const textElement = element as BaseTextElementConfig;\n const text = textElement.text || '';\n if (!text) return;\n\n // Check if this is for a knockout operation (rendering to offscreen canvas)\n const isKnockoutRender = renderingContext?.isKnockout === true;\n\n // When stroke has sub-1 opacity (and this isn't a knockout render), render the\n // entire stroke to a temp canvas at full opacity, then composite at the target\n // opacity. This avoids overlap darkening between adjacent characters' strokes.\n const strokeOpacity = element.stroke.opacity ?? 1;\n const needsOffscreen = !isKnockoutRender && strokeOpacity < 1;\n\n if (needsOffscreen) {\n try {\n renderTextStrokeViaOffscreen(ctx, element, textElement, renderingContext, strokeOpacity);\n } catch {\n // Fallback: render directly if offscreen approach fails\n // (e.g., getTransform() not available in test mocks)\n renderTextStrokeDirect(ctx, element, textElement, renderingContext, false);\n }\n } else {\n renderTextStrokeDirect(ctx, element, textElement, renderingContext, isKnockoutRender);\n }\n}\n\n/**\n * Render stroke via an offscreen canvas to apply opacity uniformly.\n * Draws all stroke paths at full opacity on a temp canvas, then composites\n * that canvas onto the main canvas at the desired opacity.\n */\nfunction renderTextStrokeViaOffscreen(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n element: AnyElementConfig,\n textElement: BaseTextElementConfig,\n renderingContext: RenderingContext | undefined,\n strokeOpacity: number\n): void {\n const text = textElement.text || '';\n\n // Estimate the bounds we need for the offscreen canvas\n const fontSize = textElement.fontSize || 24;\n const strokeWidth = element.stroke!.width;\n const feather = element.stroke!.feather || 0;\n const isCustomTransform = element.transformType === 'custom' &&\n (element as CustomElementConfig).transformData?.width;\n const customWidth = isCustomTransform\n ? ((element as CustomElementConfig).transformData?.width ?? 200)\n : 0;\n\n // Estimate element dimensions in world space\n const estWidth = isCustomTransform\n ? customWidth + strokeWidth * 2 + feather * 2 + 40\n : fontSize * text.length * 0.7 + strokeWidth * 2 + feather * 2 + 40;\n const estHeight = fontSize * 3 + strokeWidth * 2 + feather * 2 + 40;\n\n // Account for current canvas scale to ensure crisp rendering\n const transform = ctx.getTransform();\n const scale = Math.max(Math.abs(transform.a), Math.abs(transform.d), 1);\n\n // Use even dimensions so that offW/2 and offH/2 are integers, avoiding\n // fractional pixel offsets that cause sub-pixel rendering differences\n // between main thread and worker (the 0.21% export parity diff).\n const offW = Math.ceil(estWidth * scale / 2) * 2;\n const offH = Math.ceil(estHeight * scale / 2) * 2;\n\n // Guard against invalid canvas dimensions\n if (offW <= 0 || offH <= 0) {\n // Fallback: render directly with per-character opacity\n renderTextStrokeDirect(ctx, element, textElement, renderingContext, false);\n return;\n }\n\n const offCanvas = createOffscreenCanvas(offW, offH);\n const offCtx = offCanvas.getContext('2d') as\n | CanvasRenderingContext2D\n | OffscreenCanvasRenderingContext2D\n | null;\n\n if (!offCtx) {\n // Fallback: render directly with per-character opacity\n renderTextStrokeDirect(ctx, element, textElement, renderingContext, false);\n return;\n }\n\n // Set up the offscreen canvas transform so the element center maps to the\n // center of the offscreen canvas. We replicate the main canvas transform\n // but shift the translation so the element is centered in the temp canvas.\n const elemX = element.x || 0;\n const elemY = element.y || 0;\n\n // Where the element center would be in pixel space on the main canvas\n const centerPixelX = transform.a * elemX + transform.c * elemY + transform.e;\n const centerPixelY = transform.b * elemX + transform.d * elemY + transform.f;\n\n // Shift so element center maps to offscreen center\n offCtx.setTransform(\n transform.a, transform.b,\n transform.c, transform.d,\n offW / 2 - centerPixelX + transform.e,\n offH / 2 - centerPixelY + transform.f\n );\n\n // Render the stroke at full opacity onto the offscreen canvas.\n // Override stroke opacity to 1.0 for this render.\n const originalOpacity = element.stroke!.opacity;\n element.stroke!.opacity = 1.0;\n renderTextStrokeDirect(offCtx, element, textElement, renderingContext, false);\n element.stroke!.opacity = originalOpacity;\n\n // Composite the offscreen canvas onto the main canvas at the desired opacity.\n // Round the draw-back coordinates to avoid sub-pixel positioning differences\n // between main thread and worker environments.\n ctx.save();\n ctx.resetTransform();\n ctx.globalAlpha = strokeOpacity;\n ctx.drawImage(\n offCanvas as CanvasImageSource,\n Math.round(centerPixelX - offW / 2),\n Math.round(centerPixelY - offH / 2)\n );\n ctx.restore();\n}\n\n/**\n * Render stroke directly onto the provided canvas context.\n * This is the original rendering logic, used both for full-opacity strokes\n * and as the inner renderer for the offscreen opacity approach.\n */\nfunction renderTextStrokeDirect(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n element: AnyElementConfig,\n textElement: BaseTextElementConfig,\n renderingContext: RenderingContext | undefined,\n isKnockoutRender: boolean\n): void {\n const text = textElement.text || '';\n\n ctx.save();\n\n // Position and rotate - match main text rendering\n // Skip if the caller (e.g. ElementRenderUtils.renderElement) already applied positioning\n if (!renderingContext?.positionApplied) {\n ctx.translate(element.x || 0, element.y || 0);\n ctx.rotate((-(element.rotation || 0) * Math.PI) / 180);\n }\n\n // Set font - match exactly how main text is rendered\n const fontSize = textElement.fontSize || 24;\n const fontFamily = textElement.fontFamily || 'Arial';\n const bold = textElement.bold || false;\n const italic = textElement.italic || false;\n const textAlign = textElement.textAlign || 'center';\n\n ctx.font = `${italic ? 'italic ' : ''}${bold ? 'bold ' : ''}${fontSize}px ${fontFamily}`;\n ctx.textAlign = textAlign as CanvasTextAlign;\n ctx.textBaseline = 'alphabetic'; // MATCH main text rendering baseline\n\n // Apply stroke style\n if (isKnockoutRender) {\n // For knockout, render with full opacity black - color doesn't matter for destination-out\n ctx.strokeStyle = '#000000';\n ctx.globalAlpha = 1.0;\n } else {\n ctx.strokeStyle = element.stroke!.color;\n if (element.stroke!.opacity !== undefined) {\n ctx.globalAlpha = element.stroke!.opacity;\n }\n }\n\n ctx.lineWidth = element.stroke!.width;\n ctx.lineCap = element.stroke!.lineCap || 'round';\n ctx.lineJoin = element.stroke!.lineJoin || 'round';\n\n if (element.stroke!.miterLimit !== undefined) {\n ctx.miterLimit = element.stroke!.miterLimit;\n }\n\n // Feathering (blur) - only supported in regular canvas context\n // Don't apply feather for knockout renders\n if (!isKnockoutRender && element.stroke!.feather && element.stroke!.feather > 0) {\n if ('filter' in ctx) {\n (ctx as CanvasRenderingContext2D).filter = `blur(${element.stroke!.feather}px)`;\n }\n }\n\n // Calculate font metrics\n const fontMetrics = getFontMetrics(fontSize, fontFamily, bold, italic);\n\n // Check if this is a CustomTransform with width (needs text wrapping)\n const isCustomTransform = element.transformType === 'custom' &&\n (element as CustomElementConfig).transformData?.width;\n\n if (isCustomTransform) {\n // CustomTransform: Handle text wrapping\n const transformData = (element as CustomElementConfig).transformData;\n const width = transformData?.width ?? 200;\n const availableWidth = width - HORIZONTAL_PADDING * 2;\n\n // CRITICAL FIX: Use calculateVisualBoundsWithSpaceCollapsing to match selection box exactly\n // This ensures stroke wraps the same way as the bounding box and main rendering\n const { lines, height: visualHeight } = calculateVisualBoundsWithSpaceCollapsing(\n text,\n availableWidth,\n fontSize,\n fontFamily,\n bold,\n italic,\n undefined, // No locked line count for stroke\n true // strict: wrap exactly at maxWidth\n );\n\n // Calculate top-left position (same as fill rendering)\n const topLeftX = -width / 2;\n const topLeftY = -visualHeight / 2;\n\n // Stroke each line (matching renderMultilineText positioning)\n // CRITICAL: We must stroke at the correct Y position even when lines are empty\n // Example: [\" \", \"Foo\", \"Bar\"] - line 0 is empty but occupies space, so \"Foo\" renders at line 0 position\n const lineSpacing = fontSize * LINE_HEIGHT_MULTIPLIER;\n lines.forEach((line: string, index: number) => {\n // Apply space layout rules (matching renderRichTextFillOnly logic)\n // For wrapped text (not explicit newlines), first line is paragraph start, last is paragraph end\n const isParagraphStart = index === 0;\n const isParagraphEnd = index === lines.length - 1;\n\n // Apply space collapsing to match visual rendering\n let visibleText = line;\n\n // Hide leading spaces when NOT at paragraph start\n if (!isParagraphStart) {\n const leadingSpacesMatch = line.match(/^ +/);\n const leadingSpacesCount = leadingSpacesMatch ? leadingSpacesMatch[0].length : 0;\n if (leadingSpacesCount > 0) {\n visibleText = visibleText.substring(leadingSpacesCount);\n }\n }\n\n // Hide trailing spaces when NOT at paragraph end\n if (!isParagraphEnd) {\n const trailingSpacesMatch = visibleText.match(/ +$/);\n const trailingSpacesCount = trailingSpacesMatch ? trailingSpacesMatch[0].length : 0;\n if (trailingSpacesCount > 0) {\n visibleText = visibleText.substring(0, visibleText.length - trailingSpacesCount);\n }\n }\n\n // CRITICAL: Stroke ALL lines, even empty ones!\n // Even though strokeText(\" \") won't draw anything visible, we MUST call it\n // to maintain correct layout positions. Otherwise the 2 visible lines get\n // vertically centered within the 3-line height container.\n let xPos = topLeftX + HORIZONTAL_PADDING;\n\n if (textAlign === 'center') {\n xPos = topLeftX + width / 2;\n } else if (textAlign === 'right') {\n xPos = topLeftX + width - HORIZONTAL_PADDING;\n }\n\n // Use index (not a visual line counter) to match the actual line position\n const yPos = topLeftY + fontMetrics.ascent + index * lineSpacing;\n ctx.strokeText(visibleText, xPos, yPos); // Stroke even if empty for correct positioning\n });\n } else {\n // Other transforms: Single line rendering (original behavior)\n const visualHeight = fontMetrics.height;\n const topLeftY = -visualHeight / 2;\n const yPos = topLeftY + fontMetrics.ascent;\n ctx.strokeText(text, 0, yPos);\n }\n\n ctx.restore();\n}\n\n/**\n * Render stroke for image element\n */\nexport function renderImageStroke(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n element: ImageElementConfig,\n renderingContext?: RenderingContext\n): void {\n if (!element.stroke?.enabled) return;\n if (!element.transformData) return;\n\n const isKnockoutRender = renderingContext?.isKnockout === true;\n\n ctx.save();\n\n // Apply stroke style\n if (isKnockoutRender) {\n // For knockout, render with full opacity\n ctx.strokeStyle = '#000000';\n ctx.lineWidth = element.stroke.width;\n ctx.lineCap = element.stroke.lineCap || 'butt';\n ctx.lineJoin = element.stroke.lineJoin || 'miter';\n ctx.globalAlpha = 1.0;\n if (element.stroke.miterLimit !== undefined) {\n ctx.miterLimit = element.stroke.miterLimit;\n }\n } else {\n applyStrokeStyle(ctx, element.stroke);\n }\n\n // Feathering (blur) - only supported in regular canvas context\n // Don't apply feather for knockout renders\n if (!isKnockoutRender && element.stroke.feather && element.stroke.feather > 0) {\n if ('filter' in ctx) {\n (ctx as CanvasRenderingContext2D).filter = `blur(${element.stroke.feather}px)`;\n }\n }\n\n // Create image boundary path and stroke it\n createImagePath(ctx, element);\n ctx.stroke();\n\n ctx.restore();\n}\n\n/**\n * Render stroke for arbitrary path\n * Used for custom shapes and transforms\n */\nexport function renderPathStroke(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n stroke: StrokeConfig,\n pathFn: () => void\n): void {\n if (!stroke.enabled) return;\n\n ctx.save();\n\n // Apply stroke style\n applyStrokeStyle(ctx, stroke);\n\n // Feathering (blur) - only supported in regular canvas context\n if (stroke.feather && stroke.feather > 0) {\n if ('filter' in ctx) {\n (ctx as CanvasRenderingContext2D).filter = `blur(${stroke.feather}px)`;\n }\n }\n\n // Execute path function and stroke it\n ctx.beginPath();\n pathFn();\n ctx.stroke();\n\n ctx.restore();\n}\n","/**\n * FontAnalyzer - Utility for analyzing fonts and extracting glyph alternates\n *\n * Features:\n * - Fetch and parse font files using fontkit v2.0+ (supports WOFF2 natively in browser)\n * - Extract glyph alternates from GSUB tables\n * - Generate preview images for glyphs\n * - Cache parsed fonts for performance\n */\n\nimport type { GlyphAlternate } from '../types';\nimport { SYSTEM_FONTS } from '../constants.js';\nimport { createLogger } from './logger.js';\n\n/**\n * Minimal type for fontkit Font objects.\n * The fontkit library doesn't export clean TS types for browser usage,\n * so we define the subset we use here.\n */\nexport interface FontkitGlyph {\n id: number;\n name: string;\n advanceWidth: number;\n codePoints?: number[];\n bbox?: { minX: number; minY: number; maxX: number; maxY: number };\n path: {\n toSVG: () => string | null;\n toPath?: () => string | null;\n };\n}\n\nexport interface FontkitGlyphRun {\n glyphs: FontkitGlyph[];\n positions: Array<{ xOffset: number; yOffset: number }>;\n}\n\nexport interface FontkitFont {\n unitsPerEm: number;\n numGlyphs: number;\n GSUB?: {\n featureList?: Array<{\n tag: string;\n feature?: { lookupListIndexes?: number[] };\n }>;\n lookupList?: { lookups?: Array<{ lookupType: number; subtables?: Record<string, unknown>[] }> };\n };\n GPOS?: {\n featureList?: Array<{ tag: string }>;\n };\n layout(text: string, features: Record<string, boolean>, script?: string): FontkitGlyphRun;\n getGlyph(glyphId: number): FontkitGlyph | null;\n glyphForCodePoint(codePoint: number): FontkitGlyph | null;\n}\n\nconst log = createLogger('FontAnalyzer');\n\n// Fontkit is lazy-loaded to reduce initial bundle size (~1MB)\n// It's only needed for OpenType features, glyph alternates, and PUA characters\nlet fontkitModule: typeof import('fontkit') | null = null;\n\nasync function getFontkit() {\n if (!fontkitModule) {\n fontkitModule = await import('fontkit');\n }\n return fontkitModule;\n}\n\ninterface FontCacheEntry {\n font: FontkitFont;\n url: string;\n timestamp: number;\n}\n\nclass FontAnalyzerService {\n private fontCache: Map<string, FontCacheEntry> = new Map();\n private failedFonts: Set<string> = new Set(); // Track fonts that failed to load (CORS, network errors)\n private readonly CACHE_DURATION = 1000 * 60 * 30; // 30 minutes\n private readonly MAX_CACHE_SIZE = 5; // Limit cache to prevent iOS Safari memory crashes\n\n /**\n * Evict the oldest font from cache if over the limit\n * Uses LRU (Least Recently Used) strategy based on timestamp\n */\n private evictOldestFont(): void {\n if (this.fontCache.size <= this.MAX_CACHE_SIZE) return;\n\n // Find oldest entry by timestamp\n let oldestKey: string | null = null;\n let oldestTime = Date.now();\n\n this.fontCache.forEach((entry, key) => {\n if (entry.timestamp < oldestTime) {\n oldestTime = entry.timestamp;\n oldestKey = key;\n }\n });\n\n if (oldestKey) {\n this.fontCache.delete(oldestKey);\n }\n }\n\n /**\n * Check if a font is a system font\n */\n private isSystemFont(fontFamily: string): boolean {\n return SYSTEM_FONTS.has(fontFamily);\n }\n\n /**\n * Get Google Fonts API URL for a specific font family\n */\n private getGoogleFontUrl(fontFamily: string, weight: number = 400): string {\n // Google Fonts API v2 URL format\n const formattedFamily = fontFamily.replace(/\\s+/g, '+');\n return `https://fonts.googleapis.com/css2?family=${formattedFamily}:wght@${weight}&display=swap`;\n }\n\n /**\n * Extract actual font file URL from Google Fonts CSS\n */\n private async extractFontFileUrl(fontFamily: string, weight: number = 400): Promise<string | null> {\n try {\n const cssUrl = this.getGoogleFontUrl(fontFamily, weight);\n\n const response = await fetch(cssUrl);\n const css = await response.text();\n\n\n // Extract all @font-face blocks\n const fontFaceRegex = /@font-face\\s*{([^}]*)}/g;\n const fontFaces = Array.from(css.matchAll(fontFaceRegex));\n\n if (fontFaces.length === 0) {\n log.warn('No @font-face found in CSS');\n return null;\n }\n\n\n // Look for the @font-face with the most complete character coverage\n // Priority: 1) No unicode-range (complete), 2) \"latin\" subset, 3) Largest unicode-range\n let bestUrl: string | null = null;\n let bestScore = -1;\n\n for (let i = 0; i < fontFaces.length; i++) {\n const fontFaceContent = fontFaces[i][1];\n const urlMatch = fontFaceContent.match(/url\\((https:\\/\\/fonts\\.gstatic\\.com\\/[^)]+)\\)/);\n const hasUnicodeRange = fontFaceContent.includes('unicode-range');\n const unicodeRangeMatch = fontFaceContent.match(/unicode-range:\\s*([^;]+);/);\n\n if (!urlMatch) continue;\n\n const url = urlMatch[1];\n let score = 0;\n\n // No unicode-range = complete font (best)\n if (!hasUnicodeRange) {\n score = 1000;\n }\n // Count unicode ranges (more = better coverage)\n else if (unicodeRangeMatch) {\n const rangeContent = unicodeRangeMatch[1];\n score = (rangeContent.match(/U\\+/g) || []).length;\n }\n\n\n if (score > bestScore) {\n bestScore = score;\n bestUrl = url;\n }\n }\n\n if (bestUrl) {\n return bestUrl;\n }\n\n log.warn('No font URL found in CSS for', fontFamily);\n return null;\n } catch (error) {\n log.error('Error extracting font file URL:', error);\n return null;\n }\n }\n\n /**\n * Load and parse a font file\n */\n async loadFont(fontFamily: string, weight: number = 400): Promise<FontkitFont | null> {\n // System fonts are already available in the browser, no need to load\n if (this.isSystemFont(fontFamily)) {\n return null;\n }\n\n const cacheKey = `${fontFamily}-${weight}`;\n\n // Check if font previously failed (avoids repeated CORS/network errors)\n if (this.failedFonts.has(cacheKey)) {\n return null;\n }\n\n // Check cache\n const cached = this.fontCache.get(cacheKey);\n if (cached && Date.now() - cached.timestamp < this.CACHE_DURATION) {\n // Update timestamp on access (LRU behavior)\n cached.timestamp = Date.now();\n return cached.font;\n }\n\n try {\n\n // Get font file URL\n const fontUrl = await this.extractFontFileUrl(fontFamily, weight);\n if (!fontUrl) {\n // Mark as failed to avoid repeated attempts (especially for CORS errors)\n this.failedFonts.add(cacheKey);\n return null;\n }\n\n\n // Fetch font file as ArrayBuffer\n const response = await fetch(fontUrl);\n if (!response.ok) {\n log.error(`Font fetch failed: ${response.status} ${response.statusText}`);\n this.failedFonts.add(cacheKey);\n return null;\n }\n\n const arrayBuffer = await response.arrayBuffer();\n\n // Parse with fontkit (supports WOFF2, WOFF, TTF, OTF natively)\n const buffer = new Uint8Array(arrayBuffer);\n const fontkit = await getFontkit();\n // fontkit types expect Buffer but Uint8Array is accepted at runtime in browser environments\n const fontOrCollection = fontkit.create(buffer as unknown as Buffer);\n\n // Handle FontCollection (TTC files) vs single Font\n const font =\n 'fonts' in fontOrCollection\n ? fontOrCollection.fonts[0] // Get first font from collection\n : fontOrCollection; // Single font\n\n\n // Cache the result\n this.fontCache.set(cacheKey, {\n font,\n url: fontUrl,\n timestamp: Date.now(),\n });\n\n // Evict oldest font if cache is full (prevents iOS Safari memory crashes)\n this.evictOldestFont();\n\n return font;\n } catch (error) {\n log.error(`Error loading font ${fontFamily}:`, error);\n // Mark as failed to avoid repeated network/CORS errors\n this.failedFonts.add(cacheKey);\n return null;\n }\n }\n\n /**\n * Get font from cache synchronously (for rendering)\n * Returns null if font is not cached - font should be pre-loaded before rendering\n */\n getFontSync(fontFamily: string, weight: number = 400): FontkitFont | null {\n const cacheKey = `${fontFamily}-${weight}`;\n const cached = this.fontCache.get(cacheKey);\n\n if (cached && Date.now() - cached.timestamp < this.CACHE_DURATION) {\n // Update timestamp on access (LRU behavior - recently used fonts stay in cache)\n cached.timestamp = Date.now();\n return cached.font;\n }\n\n return null;\n }\n\n /**\n * Clear font cache (useful for debugging or forcing reload)\n * Also clears failed fonts to allow retry\n */\n clearCache(fontFamily?: string, weight?: number): void {\n if (fontFamily) {\n const cacheKey = `${fontFamily}-${weight || 400}`;\n this.fontCache.delete(cacheKey);\n this.failedFonts.delete(cacheKey);\n } else {\n this.fontCache.clear();\n this.failedFonts.clear();\n }\n }\n\n /**\n * Get all glyphs in a font (for browsing)\n */\n async getAllGlyphs(\n fontFamily: string,\n weight: number = 400,\n limit: number = 1500 // Increased limit to catch more alternates\n ): Promise<GlyphAlternate[]> {\n // System fonts don't have accessible glyph data via Google Fonts\n if (this.isSystemFont(fontFamily)) {\n return [];\n }\n\n const font = await this.loadFont(fontFamily, weight);\n if (!font) {\n return [];\n }\n\n const glyphs: GlyphAlternate[] = [];\n\n\n // Iterate through all glyphs in the font\n const maxGlyphs = Math.min(font.numGlyphs, limit);\n for (let glyphId = 0; glyphId < maxGlyphs; glyphId++) {\n try {\n const glyph = font.getGlyph(glyphId);\n if (!glyph || !glyph.name) continue;\n\n // Skip .notdef and other special glyphs\n if (glyph.name === '.notdef' || glyph.name.startsWith('.null')) {\n continue;\n }\n\n // Get unicode value if available\n const codePoints = glyph.codePoints || [];\n let unicode = codePoints.length > 0 ? String.fromCodePoint(codePoints[0]) : '';\n\n // If no unicode, try to extract the base character from the glyph name\n // For glyphs like \"T.alt\", \"W.swash\", extract \"T\" or \"W\"\n if (!unicode || unicode.length === 0) {\n const baseCharMatch = glyph.name.match(/^([A-Za-z0-9])[._]/);\n if (baseCharMatch) {\n unicode = baseCharMatch[1];\n }\n }\n\n // Generate a friendly name\n let friendlyName = glyph.name;\n if (unicode && unicode.length > 0 && /\\S/.test(unicode)) {\n // Try to create a more readable name\n if (glyph.name.includes('.swash')) {\n friendlyName = `${unicode} Swash`;\n } else if (glyph.name.match(/\\.alt(\\d+)?/)) {\n const num = glyph.name.match(/\\.alt(\\d+)/)?.[1] || '';\n friendlyName = `${unicode} Alternate${num ? ' ' + num : ''}`;\n } else if (glyph.name.match(/\\.ss(\\d+)/)) {\n const num = glyph.name.match(/\\.ss(\\d+)/)?.[1];\n friendlyName = `${unicode} Stylistic Set ${num}`;\n } else {\n // Use the unicode character as a prefix\n friendlyName = `${unicode} - ${glyph.name}`;\n }\n }\n\n // Categorize the glyph - check for various alternate patterns\n let category: GlyphAlternate['category'] = 'default';\n const nameLower = glyph.name.toLowerCase();\n\n if (nameLower.includes('swash') || nameLower.includes('cswh')) {\n category = 'swash';\n } else if (glyph.name.match(/\\.(alt|ss\\d+|cv\\d+|salt|ornament)/)) {\n category = 'stylistic';\n } else if (glyph.name.match(/_\\d+$/)) {\n // Pattern like T_001, T_002\n category = 'stylistic';\n } else if (nameLower.match(/liga|dlig/)) {\n category = 'ligature';\n }\n\n // Only show alternates, swashes, and ligatures\n // Skip anything categorized as \"default\" - those aren't alternate glyphs\n if (category === 'default') {\n continue;\n }\n\n // Only add glyphs that have a valid unicode character (skip if we couldn't determine one)\n if (unicode && unicode.length > 0) {\n glyphs.push({\n glyphIndex: glyph.id,\n unicode: unicode,\n name: friendlyName,\n category,\n previewDataUrl: this.generateGlyphPreview(font, glyph.id, 80),\n });\n }\n } catch (e) {\n // Skip glyphs that can't be loaded\n }\n }\n\n return glyphs;\n }\n\n /**\n * Get glyph alternates for a specific character\n */\n async getGlyphAlternates(fontFamily: string, character: string, weight: number = 400): Promise<GlyphAlternate[]> {\n const font = await this.loadFont(fontFamily, weight);\n if (!font) {\n return [];\n }\n\n const alternates: GlyphAlternate[] = [];\n\n // Get default glyph\n const codePoint = character.codePointAt(0);\n if (codePoint === undefined) {\n return [];\n }\n\n const defaultGlyph = font.glyphForCodePoint(codePoint);\n if (defaultGlyph) {\n alternates.push({\n glyphIndex: defaultGlyph.id,\n unicode: character,\n name: defaultGlyph.name || 'Default',\n category: 'default',\n previewDataUrl: this.generateGlyphPreview(font, defaultGlyph.id, 80),\n });\n }\n\n // Search for glyphs by name pattern\n // Many fonts have alternates as separate glyphs (e.g., \"W.swash\", \"W.alt01\", \"W_001\")\n const baseName = defaultGlyph?.name;\n\n // Try to iterate through all glyphs\n if (baseName) {\n let foundCount = 0;\n\n // Fontkit doesn't expose _glyphs directly, but we can iterate by glyph ID\n for (let glyphId = 0; glyphId < font.numGlyphs; glyphId++) {\n try {\n const glyph = font.getGlyph(glyphId);\n if (!glyph || !glyph.name) continue;\n\n // Check if this glyph name is a variant of the base character\n // Patterns: W.swash, W.alt01, W_001, W.ss01, etc.\n const namePatterns = [\n new RegExp(`^${baseName}\\\\.(swash|alt|ss\\\\d+|salt|ornament)`, 'i'),\n new RegExp(`^${baseName}_\\\\d+$`, 'i'),\n new RegExp(`^${baseName}\\\\.\\\\d+$`, 'i'),\n ];\n\n const isVariant = namePatterns.some((pattern) => pattern.test(glyph.name));\n\n if (isVariant && glyph.id !== defaultGlyph.id) {\n foundCount++;\n\n // Extract a friendly name from the glyph name\n let friendlyName = glyph.name;\n if (glyph.name.includes('.swash')) {\n friendlyName = `${character} Swash`;\n } else if (glyph.name.match(/\\.alt(\\d+)?/)) {\n const num = glyph.name.match(/\\.alt(\\d+)/)?.[1] || '';\n friendlyName = `${character} Alternate${num ? ' ' + num : ''}`;\n } else if (glyph.name.match(/\\.ss(\\d+)/)) {\n const num = glyph.name.match(/\\.ss(\\d+)/)?.[1];\n friendlyName = `${character} Stylistic Set ${num}`;\n }\n\n alternates.push({\n glyphIndex: glyph.id,\n unicode: character,\n name: friendlyName,\n category: 'stylistic',\n previewDataUrl: this.generateGlyphPreview(font, glyph.id, 80),\n });\n }\n } catch (e) {\n // Skip glyphs that can't be loaded\n }\n }\n\n }\n\n // Check GSUB table for alternates\n if (font.GSUB) {\n const gsub = font.GSUB;\n\n // Look for single substitution features (e.g., swsh, ss01, etc.)\n if (gsub.featureList) {\n for (const feature of gsub.featureList) {\n const tag = feature.tag;\n\n // Skip if not a relevant feature\n if (!this.isRelevantFeature(tag)) {\n continue;\n }\n\n // Look through lookups\n if (feature.feature && feature.feature.lookupListIndexes) {\n for (const lookupIndex of feature.feature.lookupListIndexes) {\n const lookup = gsub.lookupList?.lookups?.[lookupIndex];\n if (!lookup) continue;\n\n // Single substitution (Type 1)\n if (lookup.lookupType === 1) {\n for (const subtable of lookup.subtables || []) {\n const coverage = subtable.coverage as Record<string, unknown> | undefined;\n if (!coverage) continue;\n\n // Check if our character is in the coverage\n const coverageIndex = this.getCoverageIndex(coverage, defaultGlyph!.id);\n if (coverageIndex !== -1) {\n const substituteGlyphIndex = this.getSubstituteGlyph(subtable as Record<string, unknown>, defaultGlyph!.id);\n if (substituteGlyphIndex !== null) {\n alternates.push({\n glyphIndex: substituteGlyphIndex,\n unicode: character,\n name: `${character} (${tag})`,\n category: this.categorizeFeature(tag) as GlyphAlternate['category'],\n previewDataUrl: this.generateGlyphPreview(font, substituteGlyphIndex, 80),\n });\n }\n }\n }\n }\n\n // Alternate substitution (Type 3)\n else if (lookup.lookupType === 3) {\n for (const subtable of lookup.subtables || []) {\n const coverage = subtable.coverage as Record<string, unknown> | undefined;\n if (!coverage) continue;\n\n const coverageIndex = this.getCoverageIndex(coverage, defaultGlyph!.id);\n const altSubtable = subtable as Record<string, unknown> & { alternateSets?: number[][] };\n if (coverageIndex !== -1 && altSubtable.alternateSets) {\n const alternateSet = altSubtable.alternateSets[coverageIndex];\n if (alternateSet) {\n for (const altGlyphIndex of alternateSet) {\n alternates.push({\n glyphIndex: altGlyphIndex,\n unicode: character,\n name: `${character} (${tag} alt)`,\n category: this.categorizeFeature(tag),\n previewDataUrl: this.generateGlyphPreview(font, altGlyphIndex, 80),\n });\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n\n return alternates;\n }\n\n /**\n * Get available OpenType features for a font\n */\n async getAvailableFeatures(fontFamily: string, weight: number = 400): Promise<string[]> {\n\n const font = await this.loadFont(fontFamily, weight);\n if (!font) {\n log.warn(`Font ${fontFamily} failed to load`);\n return [];\n }\n\n try {\n const features: string[] = [];\n\n // Extract features from GSUB table\n if (font.GSUB && font.GSUB.featureList) {\n const uniqueFeatures = new Set<string>();\n for (const feature of font.GSUB.featureList) {\n if (feature.tag) {\n uniqueFeatures.add(feature.tag);\n }\n }\n features.push(...Array.from(uniqueFeatures));\n }\n\n // Extract features from GPOS table (positioning features like kern)\n if (font.GPOS && font.GPOS.featureList) {\n const uniqueFeatures = new Set<string>();\n for (const feature of font.GPOS.featureList) {\n if (feature.tag) {\n uniqueFeatures.add(feature.tag);\n }\n }\n features.push(...Array.from(uniqueFeatures));\n }\n\n return features;\n } catch (error) {\n log.error(`Error getting features for ${fontFamily}:`, error);\n return [];\n }\n }\n\n /**\n * Generate a preview image for a glyph\n */\n private generateGlyphPreview(font: FontkitFont, glyphIndex: number, size: number = 80): string {\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d');\n\n if (!ctx) {\n return '';\n }\n\n try {\n const glyph = font.getGlyph(glyphIndex);\n if (!glyph) {\n return '';\n }\n\n // Clear canvas\n ctx.clearRect(0, 0, size, size);\n\n // Get glyph bounding box\n const bbox = glyph.bbox;\n if (!bbox) {\n return '';\n }\n\n const glyphWidth = bbox.maxX - bbox.minX;\n const glyphHeight = bbox.maxY - bbox.minY;\n\n if (glyphWidth === 0 || glyphHeight === 0) {\n return '';\n }\n\n // Use more conservative padding to prevent clipping (60% instead of 80%)\n const padding = 0.6;\n const scale = Math.min(size / glyphWidth, size / glyphHeight) * padding;\n\n // Center the glyph more carefully\n // Calculate the center of the glyph in its own coordinate space\n const glyphCenterX = (bbox.minX + bbox.maxX) / 2;\n const glyphCenterY = (bbox.minY + bbox.maxY) / 2;\n\n // Canvas center\n const canvasCenterX = size / 2;\n const canvasCenterY = size / 2;\n\n // Get SVG path and render it\n const svgPath = glyph.path.toSVG();\n if (!svgPath) {\n return '';\n }\n\n // Create Path2D from SVG path\n const path2D = new Path2D(svgPath);\n\n // Apply transformations\n ctx.save();\n\n // Move to canvas center\n ctx.translate(canvasCenterX, canvasCenterY);\n\n // Scale (flip Y for font coordinates)\n ctx.scale(scale, -scale);\n\n // Move glyph center to origin\n ctx.translate(-glyphCenterX, -glyphCenterY);\n\n ctx.fillStyle = '#000000';\n ctx.fill(path2D);\n ctx.restore();\n\n // Convert to data URL\n return canvas.toDataURL('image/png');\n } catch (error) {\n log.error('Error generating glyph preview:', error);\n return '';\n }\n }\n\n /**\n * Check if a feature tag is relevant for glyph alternates\n */\n private isRelevantFeature(tag: string): boolean {\n const relevantFeatures = [\n 'swsh',\n 'cswh',\n 'salt',\n 'ss01',\n 'ss02',\n 'ss03',\n 'ss04',\n 'ss05',\n 'ss06',\n 'ss07',\n 'ss08',\n 'ss09',\n 'ss10',\n 'ss11',\n 'ss12',\n 'ss13',\n 'ss14',\n 'ss15',\n 'ss16',\n 'ss17',\n 'ss18',\n 'ss19',\n 'ss20',\n 'cv01',\n 'cv02',\n 'cv03',\n 'cv04',\n 'cv05',\n 'cv06',\n 'cv07',\n 'cv08',\n 'cv09',\n 'cv10',\n 'aalt',\n 'dlig',\n 'liga',\n ];\n return relevantFeatures.includes(tag);\n }\n\n /**\n * Categorize a feature tag\n */\n private categorizeFeature(tag: string): GlyphAlternate['category'] {\n if (tag.startsWith('swsh') || tag === 'cswh') return 'swash';\n if (tag.startsWith('ss') || tag.startsWith('cv') || tag === 'salt') return 'stylistic';\n if (tag === 'liga' || tag === 'dlig') return 'ligature';\n if (tag === 'calt') return 'contextual';\n return 'stylistic';\n }\n\n /**\n * Get coverage index for a glyph\n */\n private getCoverageIndex(coverage: Record<string, unknown>, glyphIndex: number): number {\n if (!coverage) return -1;\n\n // Format 1: List of glyph IDs\n if (coverage.format === 1 && coverage.glyphs) {\n return (coverage.glyphs as number[]).indexOf(glyphIndex);\n }\n\n // Format 2: Ranges\n if (coverage.format === 2 && coverage.ranges) {\n const ranges = coverage.ranges as Array<{ start: number; end: number; index: number }>;\n for (let i = 0; i < ranges.length; i++) {\n const range = ranges[i];\n if (glyphIndex >= range.start && glyphIndex <= range.end) {\n return range.index + (glyphIndex - range.start);\n }\n }\n }\n\n return -1;\n }\n\n /**\n * Get substitute glyph from a single substitution subtable\n */\n private getSubstituteGlyph(subtable: Record<string, unknown>, glyphIndex: number): number | null {\n if (!subtable) return null;\n\n // Format 1: Delta\n if (subtable.substFormat === 1 && typeof subtable.deltaGlyphId === 'number') {\n return glyphIndex + subtable.deltaGlyphId;\n }\n\n // Format 2: Mapping\n if (subtable.substFormat === 2 && subtable.substitute) {\n const coverageIndex = this.getCoverageIndex(subtable.coverage as Record<string, unknown>, glyphIndex);\n const substitute = subtable.substitute as number[];\n if (coverageIndex !== -1 && substitute[coverageIndex] !== undefined) {\n return substitute[coverageIndex];\n }\n }\n\n return null;\n }\n\n /**\n * Get the count of alternate glyphs in a font (fast - no preview generation)\n */\n async getAlternateGlyphCount(fontFamily: string, weight: number = 400): Promise<number> {\n const font = await this.loadFont(fontFamily, weight);\n if (!font) {\n return 0;\n }\n\n let count = 0;\n\n // Iterate through all glyphs in the font\n const maxGlyphs = Math.min(font.numGlyphs, 1500);\n for (let glyphId = 0; glyphId < maxGlyphs; glyphId++) {\n try {\n const glyph = font.getGlyph(glyphId);\n if (!glyph || !glyph.name) continue;\n\n // Skip .notdef and other special glyphs\n if (glyph.name === '.notdef' || glyph.name.startsWith('.null')) {\n continue;\n }\n\n // Get unicode value if available\n const codePoints = glyph.codePoints || [];\n let unicode = codePoints.length > 0 ? String.fromCodePoint(codePoints[0]) : '';\n\n // If no unicode, try to extract the base character from the glyph name\n if (!unicode || unicode.length === 0) {\n const baseCharMatch = glyph.name.match(/^([A-Za-z0-9])[._]/);\n if (baseCharMatch) {\n unicode = baseCharMatch[1];\n }\n }\n\n // Categorize the glyph - check for various alternate patterns\n let category = 'default';\n const nameLower = glyph.name.toLowerCase();\n\n if (nameLower.includes('swash') || nameLower.includes('cswh')) {\n category = 'swash';\n } else if (glyph.name.match(/\\.(alt|ss\\d+|cv\\d+|salt|ornament)/)) {\n category = 'stylistic';\n } else if (glyph.name.match(/_\\d+$/)) {\n category = 'stylistic';\n } else if (nameLower.match(/liga|dlig/)) {\n category = 'ligature';\n }\n\n // Only count alternates, swashes, and ligatures\n if (category !== 'default' && unicode && unicode.length > 0) {\n count++;\n }\n } catch (e) {\n // Skip glyphs that can't be loaded\n }\n }\n\n return count;\n }\n\n /**\n * Get cache statistics\n */\n getCacheStats(): { size: number; entries: string[] } {\n return {\n size: this.fontCache.size,\n entries: Array.from(this.fontCache.keys()),\n };\n }\n}\n\n// Export singleton instance\nexport const FontAnalyzer = new FontAnalyzerService();\n","/**\n * GlyphRenderer - Utility for rendering text with glyph overrides and OpenType features\n *\n * This provides enhanced text rendering that supports:\n * - Per-character glyph substitution\n * - OpenType feature application\n */\n\nimport type { GlyphOverride, OpenTypeFeatures } from '../types';\nimport type { FontkitFont } from './FontAnalyzer';\nimport { FontAnalyzer } from './FontAnalyzer';\nimport { createLogger } from './logger';\n\nconst logger = createLogger('GlyphRenderer');\n\n/**\n * Build CSS font-feature-settings string from OpenTypeFeatures\n */\nexport function buildFontFeatureSettings(features?: OpenTypeFeatures): string {\n if (!features) return '';\n\n const settings: string[] = [];\n\n Object.entries(features).forEach(([tag, enabled]) => {\n if (enabled) {\n settings.push(`\"${tag}\" 1`);\n }\n });\n\n return settings.join(', ');\n}\n\n/**\n * Apply OpenType features to canvas context via font string\n * Note: Browser support for font-feature-settings in canvas is limited\n * This works in Chrome 99+, Firefox 105+, Safari 16.4+\n */\nexport function applyOpenTypeFeaturestoContext(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n features?: OpenTypeFeatures\n): void {\n if (!features || Object.keys(features).length === 0) return;\n\n // Use fontVariantCaps for small caps (better browser support)\n if (features.smcp || features.c2sc) {\n (ctx as CanvasRenderingContext2D & { fontVariantCaps?: string }).fontVariantCaps = 'small-caps';\n }\n\n // Note: Direct font-feature-settings support in canvas is limited\n // For full support, text would need to be rendered to DOM first, then drawn to canvas\n}\n\n/**\n * Render text with OpenType features using fontkit (synchronous version)\n * This method renders glyphs as paths with features applied\n * Requires font to be pre-loaded and passed as parameter\n */\nexport function renderTextWithOpenTypeFeaturesSync(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n font: FontkitFont,\n text: string,\n x: number,\n y: number,\n fontSize: number,\n features: OpenTypeFeatures,\n options: {\n color?: string;\n align?: 'left' | 'center' | 'right';\n } = {}\n): void {\n try {\n // Build feature array for fontkit\n const featureArray: string[] = [];\n Object.entries(features).forEach(([tag, enabled]) => {\n if (enabled) {\n featureArray.push(tag);\n }\n });\n\n\n // Check available GSUB features\n const availableFeatures: string[] = [];\n if (font.GSUB && font.GSUB.featureList) {\n font.GSUB.featureList.forEach((feat: { tag: string }) => {\n availableFeatures.push(feat.tag);\n });\n }\n\n\n\n // Use fontkit's layout engine to apply OpenType features\n // fontkit.layout(text, features, script, language, direction)\n\n // Build features object (fontkit prefers object format)\n const featuresObject: Record<string, boolean> = {};\n\n // Always enable required features for proper text shaping\n // These are the \"default\" features that should always be on\n featuresObject.ccmp = true; // Glyph composition/decomposition (required)\n featuresObject.locl = true; // Localized forms\n featuresObject.kern = true; // Kerning\n\n // Add user-selected features on top of defaults\n featureArray.forEach((tag) => {\n featuresObject[tag] = true;\n });\n\n // For script/cursive fonts, if calt is enabled, also enable related features\n // that work together to create proper connections\n if (featuresObject.calt) {\n featuresObject.liga = true; // Standard ligatures (needed for connections)\n featuresObject.init = true; // Initial forms\n featuresObject.fina = true; // Final forms\n // Note: These features work TOGETHER - calt triggers contextual substitutions,\n // while init/fina/liga provide the alternate glyphs\n }\n\n\n\n // Try layout with features and script tag\n // Script tag 'latn' is required for proper Latin font shaping\n const run = font.layout(text, featuresObject, 'latn');\n\n\n // Calculate scale from font units to pixels\n const scale = fontSize / font.unitsPerEm;\n\n // Calculate total width for alignment\n let totalWidth = 0;\n run.glyphs.forEach((glyph) => {\n totalWidth += glyph.advanceWidth * scale;\n });\n\n // Adjust starting x position based on alignment\n let startX = x;\n if (options.align === 'center') {\n startX = x - totalWidth / 2;\n } else if (options.align === 'right') {\n startX = x - totalWidth;\n }\n\n // Render each glyph\n ctx.save();\n ctx.fillStyle = options.color || '#000000';\n\n let currentX = startX;\n run.glyphs.forEach((glyph, index: number) => {\n const position = run.positions[index];\n\n // Get the path data - try multiple approaches\n let pathData: string | null = null;\n\n try {\n // Method 1: Try toPath() for d attribute (standard fontkit)\n if (glyph.path && typeof glyph.path.toPath === 'function') {\n pathData = glyph.path.toPath();\n }\n // Method 2: Try toSVG() and extract or use directly\n else if (glyph.path && typeof glyph.path.toSVG === 'function') {\n const svgPath = glyph.path.toSVG();\n\n // Check if it's already path data (starts with M, L, C, etc.)\n if (svgPath && /^[MmLlHhVvCcSsQqTtAaZz]/.test(svgPath)) {\n pathData = svgPath;\n } else {\n // Try to extract d attribute\n const dMatch = svgPath?.match(/\\bd=[\"']([^\"']+)[\"']/);\n pathData = dMatch ? dMatch[1] : null;\n }\n }\n } catch (e) {\n logger.warn('Error getting path for glyph:', glyph.name, e);\n }\n\n if (index === 0) {\n }\n\n if (pathData) {\n // Create Path2D from path data\n const path2D = new Path2D(pathData);\n\n // Apply transformations for position and scale\n ctx.save();\n ctx.translate(currentX + position.xOffset * scale, y + position.yOffset * scale);\n ctx.scale(scale, -scale); // Flip Y axis for proper rendering\n ctx.fill(path2D);\n ctx.restore();\n }\n\n currentX += glyph.advanceWidth * scale;\n });\n\n ctx.restore();\n } catch (error) {\n logger.error('Error rendering with fontkit:', error);\n // Fallback to standard rendering\n ctx.fillStyle = options.color || '#000000';\n ctx.textAlign = options.align || 'left';\n ctx.fillText(text, x, y);\n }\n}\n\n/**\n * Render text with OpenType features using fontkit (async version)\n * This method renders glyphs as paths with features applied\n */\nexport async function renderTextWithOpenTypeFeatures(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n text: string,\n x: number,\n y: number,\n fontSize: number,\n fontFamily: string,\n features: OpenTypeFeatures,\n options: {\n color?: string;\n bold?: boolean;\n italic?: boolean;\n align?: 'left' | 'center' | 'right';\n } = {}\n): Promise<void> {\n // Load font using FontAnalyzer\n const font = await FontAnalyzer.loadFont(fontFamily, options.bold ? 700 : 400);\n if (!font) {\n // Fallback to standard rendering\n ctx.fillStyle = options.color || '#000000';\n ctx.textAlign = options.align || 'left';\n ctx.fillText(text, x, y);\n return;\n }\n\n // Use the synchronous version with the loaded font\n renderTextWithOpenTypeFeaturesSync(ctx, font, text, x, y, fontSize, features, options);\n}\n\n/**\n * Render text with glyph overrides using fontkit (synchronous version)\n * Requires font to be pre-loaded and passed as parameter\n */\nexport function renderTextWithGlyphOverridesSync(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n font: FontkitFont,\n text: string,\n x: number,\n y: number,\n fontSize: number,\n glyphOverrides?: GlyphOverride[],\n options: {\n color?: string;\n align?: 'left' | 'center' | 'right';\n } = {}\n): void {\n // Helper to detect PUA characters\n const containsPUA = (str: string): boolean => {\n for (const char of str) {\n const cp = char.codePointAt(0);\n if (cp && cp >= 0xf0000 && cp <= 0xffffd) return true;\n }\n return false;\n };\n\n const hasPUA = containsPUA(text);\n\n // If no glyph overrides AND no PUA characters, use standard canvas text rendering\n if ((!glyphOverrides || glyphOverrides.length === 0) && !hasPUA) {\n ctx.fillStyle = options.color || '#000000';\n ctx.textAlign = options.align || 'left';\n ctx.fillText(text, x, y);\n return;\n }\n\n\n // Create a map of character indices to glyph overrides\n const overrideMap = new Map<number, number>();\n if (glyphOverrides) {\n glyphOverrides.forEach((override) => {\n overrideMap.set(override.charIndex, override.glyphIndex);\n });\n }\n\n // Calculate scale from font units to pixels\n const scale = fontSize / font.unitsPerEm;\n\n const chars = Array.from(text); // Use Array.from to handle multi-byte chars\n\n // Calculate total width for alignment\n let totalWidth = 0;\n chars.forEach((char, index) => {\n const codePoint = char.codePointAt(0) || 0;\n let glyphId: number | undefined;\n\n // Check if character is in our PUA range (U+F0000 to U+FFFFD)\n if (codePoint >= 0xf0000 && codePoint <= 0xffffd) {\n // Extract glyph ID from PUA codepoint\n glyphId = codePoint - 0xf0000;\n } else {\n // Check for override (legacy system)\n glyphId = overrideMap.get(index);\n\n if (glyphId === undefined) {\n const defaultGlyph = font.glyphForCodePoint(codePoint);\n glyphId = defaultGlyph ? defaultGlyph.id : 0;\n }\n }\n\n const glyph = font.getGlyph(glyphId);\n if (glyph) {\n totalWidth += glyph.advanceWidth * scale;\n }\n });\n\n // Adjust starting x position based on alignment\n let startX = x;\n if (options.align === 'center') {\n startX = x - totalWidth / 2;\n } else if (options.align === 'right') {\n startX = x - totalWidth;\n }\n\n // Render each character\n ctx.save();\n ctx.fillStyle = options.color || '#000000';\n\n let currentX = startX;\n\n chars.forEach((char, index) => {\n // Get the glyph ID\n const codePoint = char.codePointAt(0) || 0;\n let glyphId: number | undefined;\n\n // Check if character is in our PUA range (U+F0000 to U+FFFFD)\n // These are our mapped alternate glyphs\n if (codePoint >= 0xf0000 && codePoint <= 0xffffd) {\n // Extract glyph ID from PUA codepoint\n glyphId = codePoint - 0xf0000;\n } else {\n // Check for override (legacy system)\n glyphId = overrideMap.get(index);\n\n if (glyphId === undefined) {\n // Use default glyph\n const defaultGlyph = font.glyphForCodePoint(codePoint);\n glyphId = defaultGlyph ? defaultGlyph.id : 0;\n }\n }\n\n // Get the glyph\n const glyph = font.getGlyph(glyphId);\n\n if (codePoint >= 0xf0000 && codePoint <= 0xffffd) {\n }\n\n if (glyph) {\n // Get SVG path\n const svgPath = glyph.path.toSVG();\n\n if (svgPath) {\n const path2D = new Path2D(svgPath);\n\n ctx.save();\n ctx.translate(currentX, y);\n ctx.scale(scale, -scale);\n ctx.fill(path2D);\n ctx.restore();\n }\n\n currentX += glyph.advanceWidth * scale;\n }\n });\n\n ctx.restore();\n}\n\n/**\n * Render text with glyph overrides using fontkit (async version)\n * Loads the font and calls the synchronous version\n */\nexport async function renderTextWithGlyphOverrides(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n text: string,\n x: number,\n y: number,\n fontSize: number,\n fontFamily: string,\n glyphOverrides?: GlyphOverride[],\n options: {\n color?: string;\n bold?: boolean;\n italic?: boolean;\n align?: 'left' | 'center' | 'right';\n } = {}\n): Promise<void> {\n // Load font using FontAnalyzer\n const font = await FontAnalyzer.loadFont(fontFamily, options.bold ? 700 : 400);\n if (!font) {\n // Fallback to standard rendering\n ctx.fillStyle = options.color || '#000000';\n ctx.textAlign = options.align || 'left';\n ctx.fillText(text, x, y);\n return;\n }\n\n // Use the synchronous version with the loaded font\n renderTextWithGlyphOverridesSync(ctx, font, text, x, y, fontSize, glyphOverrides, options);\n}\n\n/**\n * Calculate text width with glyph overrides using fontkit API\n */\nfunction calculateTextWidthWithOverrides(\n font: FontkitFont,\n text: string,\n overrideMap: Map<number, number>,\n scale: number\n): number {\n const chars = Array.from(text);\n let totalWidth = 0;\n\n chars.forEach((char, index) => {\n const codePoint = char.codePointAt(0) || 0;\n let glyphId: number | undefined;\n\n // Check if character is in the PUA range (U+F0000 to U+FFFFD)\n if (codePoint >= 0xf0000 && codePoint <= 0xffffd) {\n glyphId = codePoint - 0xf0000;\n } else {\n glyphId = overrideMap.get(index);\n if (glyphId === undefined) {\n const defaultGlyph = font.glyphForCodePoint(codePoint);\n glyphId = defaultGlyph ? defaultGlyph.id : 0;\n }\n }\n\n const glyph = font.getGlyph(glyphId);\n if (glyph) {\n totalWidth += glyph.advanceWidth * scale;\n }\n });\n\n return totalWidth;\n}\n\n/**\n * Measure text width with glyph overrides\n */\nexport async function measureTextWithGlyphOverrides(\n text: string,\n fontSize: number,\n fontFamily: string,\n glyphOverrides?: GlyphOverride[],\n options: {\n bold?: boolean;\n } = {}\n): Promise<number> {\n // If no glyph overrides, use standard measurement\n if (!glyphOverrides || glyphOverrides.length === 0) {\n // Create a temporary canvas context for measurement\n const canvas =\n typeof OffscreenCanvas !== 'undefined' ? new OffscreenCanvas(1, 1) : document.createElement('canvas');\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return 0;\n\n const weight = options.bold ? 'bold' : 'normal';\n ctx.font = `${weight} ${fontSize}px ${fontFamily}`;\n return ctx.measureText(text).width;\n }\n\n // Load font and calculate with overrides\n const font = await FontAnalyzer.loadFont(fontFamily, options.bold ? 700 : 400);\n if (!font) {\n // Fallback\n const canvas =\n typeof OffscreenCanvas !== 'undefined' ? new OffscreenCanvas(1, 1) : document.createElement('canvas');\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return 0;\n\n const weight = options.bold ? 'bold' : 'normal';\n ctx.font = `${weight} ${fontSize}px ${fontFamily}`;\n return ctx.measureText(text).width;\n }\n\n const overrideMap = new Map<number, number>();\n glyphOverrides.forEach((override) => {\n overrideMap.set(override.charIndex, override.glyphIndex);\n });\n\n const scale = fontSize / font.unitsPerEm;\n return calculateTextWidthWithOverrides(font, text, overrideMap, scale);\n}\n","/**\n * Rich Text Renderer - Multi-line rich text rendering with per-span styling.\n *\n * Handles word wrapping, line splitting, and rendering of styled text spans\n * with support for mixed fonts, sizes, colors, and text decorations.\n */\n\nimport { LINE_HEIGHT_MULTIPLIER } from '../constants.js';\nimport type { TextAlign, CharacterStyle } from '../types/index.js';\nimport { RichText } from '../types/index.js';\nimport type { RenderContext } from './renderer-types.js';\nimport { buildFontString, getFontMetrics } from './text-renderer.js';\n\n/**\n * Wrap rich text spans to fit within a given width, preserving character-level formatting\n *\n * This function handles character-level formatting correctly by:\n * 1. First merging consecutive spans with the same style\n * 2. Finding word boundaries (spaces) across spans\n * 3. Wrapping at word boundaries, not at span boundaries\n */\nexport function wrapRichTextSpans(\n spans: Array<{ text: string; style: CharacterStyle }>,\n maxWidth: number,\n element: {\n fontSize: number;\n fontFamily: string;\n bold?: boolean;\n italic?: boolean;\n }\n): Array<{ text: string; style: CharacterStyle }[]> {\n const ctx = getMeasureContext();\n const lines: Array<{ text: string; style: CharacterStyle }[]> = [];\n\n // Helper to measure a text segment with specific styling\n const measureText = (text: string, style: CharacterStyle): number => {\n const fontSize = style.fontSize || element.fontSize;\n const fontFamily = style.fontFamily || element.fontFamily;\n const bold = style.bold !== undefined ? style.bold : element.bold || false;\n const italic = style.italic !== undefined ? style.italic : element.italic || false;\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n return ctx.measureText(text).width;\n };\n\n // Helper to check if two styles match\n const stylesMatch = (s1: CharacterStyle, s2: CharacterStyle): boolean => {\n return (\n s1.fontSize === s2.fontSize &&\n s1.fontFamily === s2.fontFamily &&\n s1.color === s2.color &&\n s1.bold === s2.bold &&\n s1.italic === s2.italic &&\n s1.underline === s2.underline &&\n s1.strikethrough === s2.strikethrough\n );\n };\n\n // Step 1: Merge consecutive spans with identical styles\n const mergedSpans: Array<{ text: string; style: CharacterStyle }> = [];\n spans.forEach((span) => {\n if (mergedSpans.length > 0 && stylesMatch(mergedSpans[mergedSpans.length - 1].style, span.style)) {\n mergedSpans[mergedSpans.length - 1].text += span.text;\n } else {\n mergedSpans.push({ text: span.text, style: span.style });\n }\n });\n\n // Step 2: Reconstruct text with character positions tracking which span each character belongs to\n let fullText = '';\n const charToSpan: number[] = []; // Maps character index to span index\n mergedSpans.forEach((span, spanIndex) => {\n for (let i = 0; i < span.text.length; i++) {\n fullText += span.text[i];\n charToSpan.push(spanIndex);\n }\n });\n\n // Step 3: Split text into words (by spaces)\n const words: Array<{ text: string; startIdx: number; endIdx: number }> = [];\n let currentWord = '';\n let wordStart = 0;\n\n for (let i = 0; i < fullText.length; i++) {\n const char = fullText[i];\n if (char === ' ') {\n if (currentWord.length > 0) {\n words.push({ text: currentWord, startIdx: wordStart, endIdx: i - 1 });\n currentWord = '';\n }\n // Add space as a separate \"word\"\n words.push({ text: ' ', startIdx: i, endIdx: i });\n } else {\n if (currentWord.length === 0) {\n wordStart = i;\n }\n currentWord += char;\n }\n }\n if (currentWord.length > 0) {\n words.push({ text: currentWord, startIdx: wordStart, endIdx: fullText.length - 1 });\n }\n\n // Step 4: Wrap words into lines\n let currentLine: { text: string; style: CharacterStyle }[] = [];\n let currentLineWidth = 0;\n\n const finishLine = () => {\n if (currentLine.length > 0) {\n lines.push(currentLine);\n currentLine = [];\n currentLineWidth = 0;\n }\n };\n\n words.forEach((word, _wordIdx) => {\n // Build spans for this word from the original merged spans\n const wordSpans: Array<{ text: string; style: CharacterStyle }> = [];\n let currentSpanIdx = charToSpan[word.startIdx];\n let currentText = '';\n\n for (let charIdx = word.startIdx; charIdx <= word.endIdx; charIdx++) {\n const spanIdx = charToSpan[charIdx];\n if (spanIdx !== currentSpanIdx) {\n // Style changed, finish current span\n wordSpans.push({ text: currentText, style: mergedSpans[currentSpanIdx].style });\n currentText = fullText[charIdx];\n currentSpanIdx = spanIdx;\n } else {\n currentText += fullText[charIdx];\n }\n }\n if (currentText.length > 0) {\n wordSpans.push({ text: currentText, style: mergedSpans[currentSpanIdx].style });\n }\n\n // Measure word width\n const wordWidth = wordSpans.reduce((sum, span) => sum + measureText(span.text, span.style), 0);\n\n // Check if we need to wrap\n if (word.text === ' ') {\n // Space character - always add to current line (will be trimmed later based on paragraph boundaries)\n if (currentLine.length > 0) {\n const lastSpan = currentLine[currentLine.length - 1];\n if (stylesMatch(lastSpan.style, wordSpans[0].style)) {\n lastSpan.text += ' ';\n } else {\n currentLine.push({ text: ' ', style: wordSpans[0].style });\n }\n } else {\n // Leading space - preserve it (width calc will trim if not paragraph start)\n currentLine.push({ text: ' ', style: wordSpans[0].style });\n }\n currentLineWidth += wordWidth;\n } else {\n // Regular word\n // Check if word itself is too wide (needs character-level breaking)\n if (wordWidth > maxWidth) {\n // Word is too wide - break it character by character\n // First, finish current line if it has content\n if (currentLine.length > 0) {\n finishLine();\n }\n\n // Break word at character level\n for (const span of wordSpans) {\n const chars = Array.from(span.text);\n let charBuffer = '';\n\n for (const char of chars) {\n const charWidth = measureText(char, span.style);\n\n // Check if adding this character would exceed max width\n if (currentLineWidth + charWidth > maxWidth && currentLineWidth > 0) {\n // Finish current line with buffer\n if (charBuffer.length > 0) {\n if (currentLine.length > 0) {\n const lastSpan = currentLine[currentLine.length - 1];\n if (stylesMatch(lastSpan.style, span.style)) {\n lastSpan.text += charBuffer;\n } else {\n currentLine.push({ text: charBuffer, style: span.style });\n }\n } else {\n currentLine.push({ text: charBuffer, style: span.style });\n }\n charBuffer = '';\n }\n finishLine();\n }\n\n charBuffer += char;\n currentLineWidth += charWidth;\n }\n\n // Add remaining buffer to current line\n if (charBuffer.length > 0) {\n if (currentLine.length > 0) {\n const lastSpan = currentLine[currentLine.length - 1];\n if (stylesMatch(lastSpan.style, span.style)) {\n lastSpan.text += charBuffer;\n } else {\n currentLine.push({ text: charBuffer, style: span.style });\n }\n } else {\n currentLine.push({ text: charBuffer, style: span.style });\n }\n }\n }\n } else {\n // Word fits on a line - check if we need to wrap to fit it\n if (currentLineWidth + wordWidth > maxWidth && currentLine.length > 0) {\n // Don't remove trailing space - it will be hidden during rendering based on paragraph rules\n // Removing it here breaks cursor position mapping\n finishLine();\n }\n\n // Add word spans to current line\n wordSpans.forEach((span) => {\n if (currentLine.length > 0) {\n const lastSpan = currentLine[currentLine.length - 1];\n if (stylesMatch(lastSpan.style, span.style)) {\n lastSpan.text += span.text;\n } else {\n currentLine.push({ ...span });\n }\n } else {\n currentLine.push({ ...span });\n }\n });\n currentLineWidth += wordWidth;\n }\n }\n });\n\n finishLine();\n\n // If no lines were created, return at least one empty line\n if (lines.length === 0) {\n lines.push([]);\n }\n\n return lines;\n}\n\n/**\n * Split rich text into lines by explicit newlines, preserving span formatting\n */\nexport function splitRichTextIntoLines(richText: RichText): Array<{ text: string; style: CharacterStyle }[]> {\n const lines: Array<{ text: string; style: CharacterStyle }[]> = [];\n let currentLine: { text: string; style: CharacterStyle }[] = [];\n\n richText.spans.forEach((span) => {\n // Split span text by newlines\n const parts = span.text.split('\\n');\n\n parts.forEach((part, i) => {\n if (part.length > 0) {\n // Add non-empty text to current line\n currentLine.push({ text: part, style: span.style });\n }\n\n // If this isn't the last part, we hit a newline\n if (i < parts.length - 1) {\n lines.push(currentLine);\n currentLine = [];\n }\n });\n });\n\n // Add the last line if it has content\n if (currentLine.length > 0) {\n lines.push(currentLine);\n }\n\n // If no lines were created, return at least one empty line\n if (lines.length === 0) {\n lines.push([]);\n }\n\n return lines;\n}\n\n/**\n * Render rich text with per-span styling (fill only, no effects)\n * Supports multi-line text with line breaks and word wrapping\n */\nexport function renderRichTextFillOnly(\n ctx: RenderContext,\n richText: RichText,\n element: {\n fontSize: number;\n fontFamily: string;\n color: string;\n bold?: boolean;\n italic?: boolean;\n underline?: boolean;\n strikethrough?: boolean;\n textAlign?: TextAlign;\n },\n maxWidth?: number,\n lockedLineCount?: number\n): void {\n // FIX: Set default fill color at start to prevent inheritance issues\n // This ensures text is rendered in the correct color even if first spans are empty\n ctx.fillStyle = element.color;\n\n const textAlign = element.textAlign || 'center';\n\n // Split into lines by explicit newlines first\n const explicitLines = splitRichTextIntoLines(richText);\n\n // Then apply word wrapping to each line if maxWidth is specified\n // Track paragraph boundaries (which lines are paragraph starts/ends)\n let lines: Array<{ text: string; style: CharacterStyle }[]>;\n let paragraphMetadata: Array<{ isParagraphStart: boolean; isParagraphEnd: boolean }>;\n\n if (maxWidth !== undefined && maxWidth > 0 && !lockedLineCount) {\n // Only wrap if no locked line count (during corner resize, bypass wrapping)\n lines = [];\n paragraphMetadata = [];\n explicitLines.forEach((lineSpans) => {\n const wrappedLines = wrapRichTextSpans(lineSpans, maxWidth, element);\n // Mark first wrapped line as paragraph start, last as paragraph end\n wrappedLines.forEach((wrappedLine, idx) => {\n lines.push(wrappedLine);\n paragraphMetadata.push({\n isParagraphStart: idx === 0,\n isParagraphEnd: idx === wrappedLines.length - 1,\n });\n });\n });\n } else {\n // No wrapping if locked line count is set or maxWidth not specified\n lines = explicitLines;\n // Each explicit line is both a paragraph start and end\n paragraphMetadata = explicitLines.map(() => ({\n isParagraphStart: true,\n isParagraphEnd: true,\n }));\n }\n\n // Calculate metrics for each line\n const lineMetrics: Array<{\n spans: { text: string; style: CharacterStyle }[];\n width: number;\n height: number;\n ascent: number;\n spanWidths: number[];\n isParagraphStart: boolean;\n isParagraphEnd: boolean;\n }> = [];\n\n let maxLineHeight = 0;\n\n lines.forEach((lineSpans, lineIndex) => {\n let lineWidth = 0;\n const spanWidths: number[] = [];\n let lineHeight = 0;\n let lineAscent = 0;\n\n // Get paragraph boundary metadata for space layout rules\n const isParagraphStart = paragraphMetadata[lineIndex].isParagraphStart;\n const isParagraphEnd = paragraphMetadata[lineIndex].isParagraphEnd;\n\n lineSpans.forEach((span) => {\n const fontSize = span.style.fontSize !== undefined ? span.style.fontSize : element.fontSize;\n const fontFamily = span.style.fontFamily !== undefined ? span.style.fontFamily : element.fontFamily;\n const bold = span.style.bold !== undefined ? span.style.bold : element.bold || false;\n const italic = span.style.italic !== undefined ? span.style.italic : element.italic || false;\n\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n const width = ctx.measureText(span.text).width;\n spanWidths.push(width);\n lineWidth += width;\n\n // Get metrics for this span\n const metrics = getFontMetrics(fontSize, fontFamily, bold, italic);\n lineHeight = Math.max(lineHeight, metrics.height);\n lineAscent = Math.max(lineAscent, metrics.ascent);\n });\n\n // Apply space layout rules to line width for alignment calculation\n // Calculate actual rendered width by summing visible characters in each span\n const fullLineText = lineSpans.map((span) => span.text).join('');\n const leadingSpacesMatch = fullLineText.match(/^ +/);\n const trailingSpacesMatch = fullLineText.match(/ +$/);\n const leadingSpacesCount = leadingSpacesMatch ? leadingSpacesMatch[0].length : 0;\n const trailingSpacesCount = trailingSpacesMatch ? trailingSpacesMatch[0].length : 0;\n\n // Calculate adjusted width by measuring only visible characters\n let adjustedLineWidth = 0;\n let charIndexInLine = 0;\n\n lineSpans.forEach((span) => {\n const fontSize = span.style.fontSize !== undefined ? span.style.fontSize : element.fontSize;\n const fontFamily = span.style.fontFamily !== undefined ? span.style.fontFamily : element.fontFamily;\n const bold = span.style.bold !== undefined ? span.style.bold : element.bold || false;\n const italic = span.style.italic !== undefined ? span.style.italic : element.italic || false;\n\n // Determine which part of this span is visible\n let visibleText = span.text;\n const spanStartIndex = charIndexInLine;\n const spanEndIndex = charIndexInLine + span.text.length;\n\n // Check if this span contains leading spaces that should be hidden\n // Leading spaces are hidden when NOT at paragraph start\n if (!isParagraphStart && leadingSpacesCount > 0 && spanStartIndex < leadingSpacesCount) {\n const hiddenCharsInSpan = Math.min(leadingSpacesCount - spanStartIndex, span.text.length);\n visibleText = visibleText.substring(hiddenCharsInSpan);\n }\n\n // Check if this span contains trailing spaces that should be hidden\n // Trailing spaces are hidden when NOT at paragraph end\n if (!isParagraphEnd && trailingSpacesCount > 0) {\n const trailingSpaceStartIndex = fullLineText.length - trailingSpacesCount;\n if (spanEndIndex > trailingSpaceStartIndex) {\n // Calculate how many characters from the START of the already-trimmed visibleText we should keep\n const leadingTrimAmount =\n !isParagraphStart && leadingSpacesCount > 0 && spanStartIndex < leadingSpacesCount\n ? Math.min(leadingSpacesCount - spanStartIndex, span.text.length)\n : 0;\n const visibleCharsInSpan = Math.max(0, trailingSpaceStartIndex - spanStartIndex - leadingTrimAmount);\n visibleText = visibleText.substring(0, visibleCharsInSpan);\n }\n }\n\n charIndexInLine += span.text.length;\n\n // Measure the visible text with this span's font\n if (visibleText.length > 0) {\n ctx.save();\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n adjustedLineWidth += ctx.measureText(visibleText).width;\n ctx.restore();\n }\n });\n\n // CRITICAL FIX: Don't skip whitespace-only lines\n // We need to count them for cursor positions and selection bounds to align\n // Example: \" Foo\" wrapping to [\" \", \"Foo\"] has 2 lines, cursor can be on line 0 or 1\n // Even though line 0 renders as empty (trailing space hidden), it still occupies vertical space\n lineMetrics.push({\n spans: lineSpans,\n width: adjustedLineWidth, // Use adjusted width for alignment (may be 0 for empty lines)\n height: lineHeight,\n ascent: lineAscent,\n spanWidths,\n isParagraphStart, // Store metadata with line\n isParagraphEnd,\n });\n\n maxLineHeight = Math.max(maxLineHeight, lineHeight);\n });\n\n // CRITICAL FIX: Use consistent height calculation with TextMetrics to ensure Fill matches Stroke\n // TextMetrics uses element.fontSize * LINE_HEIGHT_MULTIPLIER for spacing\n // and element font metrics for the height component.\n // We must use the SAME model here to avoid ghosting/misalignment.\n const elementFontMetrics = getFontMetrics(element.fontSize, element.fontFamily, element.bold, element.italic);\n const lineSpacing = element.fontSize * LINE_HEIGHT_MULTIPLIER;\n\n let totalHeight;\n if (lineMetrics.length === 0) {\n totalHeight = 0;\n } else if (lineMetrics.length === 1) {\n // For single line, use element metrics to match stroke\n totalHeight = elementFontMetrics.height;\n } else {\n // For multi-line, match TextMetrics formula\n totalHeight = (lineMetrics.length - 1) * lineSpacing + elementFontMetrics.height;\n }\n\n // Start from top of text block (vertically centered)\n const topLeftY = -totalHeight / 2;\n\n // Render each line\n lineMetrics.forEach((lineData, lineIndex) => {\n const { spans, width: lineWidth, isParagraphStart, isParagraphEnd } = lineData;\n\n // Calculate starting X position based on alignment.\n //\n // The element box is centered at (0, 0), so it spans [-w/2, +w/2]\n // where `w` is the available inner width (`maxWidth`). Aligning\n // text to the *bounding box edges* — not to x=0 — is what the\n // stroke renderer does (StrokeRenderer.ts ~line 342) and what\n // users expect: \"left aligned\" means flush with the left edge of\n // the box, \"right aligned\" means flush with the right edge.\n //\n // Earlier this anchored every alignment at x=0 (center), so for\n // left/right alignment the fill text stayed near center while the\n // stroke moved to the box edges — they diverged visibly. With\n // `maxWidth` undefined (legacy paths without an explicit box) we\n // fall back to the old anchor-at-zero behaviour, since there's no\n // box edge to align to.\n let currentX: number;\n if (textAlign === 'center') {\n currentX = -lineWidth / 2;\n } else if (textAlign === 'right') {\n currentX = maxWidth !== undefined ? maxWidth / 2 - lineWidth : -lineWidth;\n } else {\n // left\n currentX = maxWidth !== undefined ? -maxWidth / 2 : 0;\n }\n\n // Baseline Y position for this line\n // Match TextMetrics positioning: topLeftY + ascent + index * spacing\n const baselineY = topLeftY + elementFontMetrics.ascent + lineIndex * lineSpacing;\n\n // Note: We use elementFontMetrics.ascent for the line position,\n // but individual spans might have different ascents.\n // We align them to this common baseline.\n\n // Apply space layout rules: determine which spaces to hide\n const fullLineText = spans.map((s) => s.text).join('');\n const leadingSpacesMatch = fullLineText.match(/^ +/);\n const trailingSpacesMatch = fullLineText.match(/ +$/);\n const leadingSpacesCount = leadingSpacesMatch ? leadingSpacesMatch[0].length : 0;\n const trailingSpacesCount = trailingSpacesMatch ? trailingSpacesMatch[0].length : 0;\n\n // Track character position in full line to identify spaces\n let charIndexInLine = 0;\n\n // Render each span in this line\n spans.forEach((span) => {\n const fontSize = span.style.fontSize !== undefined ? span.style.fontSize : element.fontSize;\n const fontFamily = span.style.fontFamily !== undefined ? span.style.fontFamily : element.fontFamily;\n // Use explicit undefined check for color (consistent with bold/italic pattern)\n // Falsy check would incorrectly fall back if color is empty string\n const color = span.style.color !== undefined ? span.style.color : element.color;\n const bold = span.style.bold !== undefined ? span.style.bold : element.bold || false;\n const italic = span.style.italic !== undefined ? span.style.italic : element.italic || false;\n const underline = span.style.underline !== undefined ? span.style.underline : element.underline || false;\n const strikethrough = span.style.strikethrough !== undefined ? span.style.strikethrough : element.strikethrough || false;\n\n // Apply space layout rules to this span's text\n let renderText = span.text;\n let spanStartIndex = charIndexInLine;\n let spanEndIndex = charIndexInLine + span.text.length;\n\n // Check if this span contains leading spaces that should be hidden\n // Leading spaces are hidden when NOT at paragraph start\n if (!isParagraphStart && leadingSpacesCount > 0 && spanStartIndex < leadingSpacesCount) {\n // This span overlaps with hidden leading spaces\n const hiddenCharsInSpan = Math.min(leadingSpacesCount - spanStartIndex, span.text.length);\n renderText = renderText.substring(hiddenCharsInSpan);\n }\n\n // Check if this span contains trailing spaces that should be hidden\n // Trailing spaces are hidden when NOT at paragraph end\n if (!isParagraphEnd && trailingSpacesCount > 0) {\n const trailingSpaceStartIndex = fullLineText.length - trailingSpacesCount;\n if (spanEndIndex > trailingSpaceStartIndex) {\n // Calculate how many characters from the START of the already-trimmed renderText we should keep\n const leadingTrimAmount =\n !isParagraphStart && leadingSpacesCount > 0 && spanStartIndex < leadingSpacesCount\n ? Math.min(leadingSpacesCount - spanStartIndex, span.text.length)\n : 0;\n const visibleCharsInSpan = Math.max(0, trailingSpaceStartIndex - spanStartIndex - leadingTrimAmount);\n renderText = renderText.substring(0, visibleCharsInSpan);\n }\n }\n\n charIndexInLine += span.text.length;\n\n // Only render if there's visible text\n if (renderText.length > 0) {\n // Set font and color for this span\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n ctx.fillStyle = color;\n ctx.textBaseline = 'alphabetic';\n ctx.textAlign = 'left'; // We handle alignment manually\n\n // Render this span at the common baseline\n // With textBaseline='alphabetic', canvas automatically aligns text to the baseline\n // All spans should use the SAME Y coordinate for proper baseline alignment\n ctx.fillText(renderText, currentX, baselineY);\n\n // Measure the rendered text width for positioning\n const renderedWidth = ctx.measureText(renderText).width;\n\n // Draw underline if enabled\n if (underline) {\n const underlineY = baselineY + fontSize * 0.1;\n ctx.beginPath();\n ctx.moveTo(currentX, underlineY);\n ctx.lineTo(currentX + renderedWidth, underlineY);\n ctx.lineWidth = Math.max(1, fontSize * 0.05);\n ctx.strokeStyle = color;\n ctx.stroke();\n }\n\n // Draw strikethrough if enabled\n if (strikethrough) {\n const strikethroughY = baselineY - fontSize * 0.25;\n ctx.beginPath();\n ctx.moveTo(currentX, strikethroughY);\n ctx.lineTo(currentX + renderedWidth, strikethroughY);\n ctx.lineWidth = Math.max(1, fontSize * 0.05);\n ctx.strokeStyle = color;\n ctx.stroke();\n }\n\n // Move X position for next span (using rendered width, not original span width)\n currentX += renderedWidth;\n }\n });\n\n // Move Y position for next line\n // currentY += lineHeight; // No longer needed with absolute positioning\n /*\n if (lineIndex < lineMetrics.length - 1) {\n currentY += maxLineHeight * (lineSpacing - 1);\n }\n */\n });\n}\n\n// ============================================================================\n// Private helpers\n// ============================================================================\n\n/** Worker-compatible measurement context (mirrors text-renderer.ts) */\nlet _measureCtx: RenderContext | null = null;\n\nfunction getMeasureContext(): RenderContext {\n if (!_measureCtx) {\n if (typeof OffscreenCanvas !== 'undefined') {\n const canvas = new OffscreenCanvas(1, 1);\n _measureCtx = canvas.getContext('2d') as OffscreenCanvasRenderingContext2D;\n } else if (typeof document !== 'undefined') {\n const canvas = document.createElement('canvas');\n _measureCtx = canvas.getContext('2d')!;\n } else {\n throw new Error('No canvas context available');\n }\n }\n return _measureCtx;\n}\n","/**\n * Text Renderer - Text measurement utilities and plain text rendering functions.\n *\n * Worker-compatible: uses OffscreenCanvas instead of DOM APIs.\n * These functions are NOT duplicates of src/core/TextMetrics.ts — see the note\n * in canvas-renderer.ts for the distinction.\n */\n\nimport { LINE_HEIGHT_MULTIPLIER, HORIZONTAL_PADDING } from '../constants.js';\nimport type {\n AnyElementConfig,\n CustomElementConfig,\n TextAlign,\n OpenTypeFeatures,\n} from '../types/index.js';\nimport { RichText } from '../types/index.js';\nimport {\n renderCircleTransform,\n renderWaveTransform,\n renderArchTransform,\n renderAscendTransform,\n renderLeanTransform,\n renderFlagTransform,\n} from './transform-renderer.js';\nimport { renderTextStroke } from './StrokeRenderer.js';\nimport { calculateVisualBoundsWithSpaceCollapsing } from '../core/TextMetrics.js';\nimport { FontAnalyzer } from '../utils/FontAnalyzer.js';\nimport {\n renderTextWithGlyphOverridesSync,\n renderTextWithOpenTypeFeaturesSync,\n} from '../utils/GlyphRenderer.js';\nimport { createLogger } from '../utils/logger.js';\nimport type { RenderContext, SerializedTextElement } from './renderer-types.js';\nimport { renderRichTextFillOnly } from './rich-text-renderer.js';\n\nconst logger = createLogger('canvas-renderer');\n\n// ============================================================================\n// Text Measurement Utilities (Worker-Compatible)\n// ============================================================================\n//\n// IMPORTANT: These functions are NOT duplicates of src/core/TextMetrics.ts!\n//\n// This file is used in Web Workers (see src/workers/export-worker.ts) which\n// don't have access to the DOM (no `document.createElement`).\n//\n// - **TextMetrics.ts** - Main thread only (uses HTMLCanvasElement via document.createElement)\n// - **text-renderer.ts** - Worker-compatible (uses OffscreenCanvas)\n//\n// Files that run ONLY in main thread (CanvasRenderer.ts, StrokeRenderer.ts, etc.)\n// should import from TextMetrics.ts to avoid confusion.\n//\n// Files that run in WORKERS (this file, transform-renderer.ts) must use these\n// OffscreenCanvas-compatible versions.\n// ============================================================================\n\n// Use a global measurement context that works in both environments\nlet _measureCtx: RenderContext | null = null;\n\n/**\n * Get or create a measurement context\n * Works in both main thread (HTMLCanvasElement) and worker (OffscreenCanvas)\n */\nfunction getMeasureContext(): RenderContext {\n if (!_measureCtx) {\n if (typeof OffscreenCanvas !== 'undefined') {\n // In worker or modern browser\n const canvas = new OffscreenCanvas(1, 1);\n _measureCtx = canvas.getContext('2d') as OffscreenCanvasRenderingContext2D;\n } else if (typeof document !== 'undefined') {\n // In main thread, old browser\n const canvas = document.createElement('canvas');\n _measureCtx = canvas.getContext('2d')!;\n } else {\n throw new Error('No canvas context available');\n }\n }\n return _measureCtx;\n}\n\n/**\n * Build font string with optional bold and italic\n */\nexport function buildFontString(\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false\n): string {\n const parts: string[] = [];\n\n if (italic) {\n parts.push('italic');\n }\n\n if (bold) {\n parts.push('bold');\n }\n\n parts.push(`${fontSize}px`);\n parts.push(fontFamily);\n\n return parts.join(' ');\n}\n\n/**\n * Measure text width\n */\nexport function measureTextWidth(\n text: string,\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false\n): number {\n const ctx = getMeasureContext();\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n return ctx.measureText(text).width;\n}\n\n/**\n * Get font metrics for accurate text positioning\n */\nexport function getFontMetrics(\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false\n): { ascent: number; descent: number; height: number } {\n const ctx = getMeasureContext();\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n\n const sampleText = 'ÁÉÍgjpqy';\n const metrics = ctx.measureText(sampleText);\n\n const ascent = metrics.actualBoundingBoxAscent || fontSize * 0.8;\n const descent = metrics.actualBoundingBoxDescent || fontSize * 0.2;\n const height = ascent + descent;\n\n return { ascent, descent, height };\n}\n\n/**\n * Apply space layout rules to text lines for rendering\n * Hides leading/trailing spaces based on paragraph boundaries to match HTML/CSS text rendering\n *\n * Rules (matching browser text rendering behavior):\n * - Leading spaces are hidden when NOT at paragraph start\n * - Trailing spaces are hidden when NOT at paragraph end\n * - Paragraph = text separated by explicit \\n characters\n *\n * Example:\n * Input: [\"Foo \", \"Bar\"] (from wrapping \"Foo Bar\", no explicit \\n)\n * Output: [\"Foo\", \"Bar\"] (trailing space on line 1 hidden)\n *\n * @param lines - Array of text lines (from wrapText or split by \\n)\n * @param hasExplicitNewlines - Whether original text contained \\n characters\n * @returns Lines with space collapsing applied for rendering\n */\nexport function applySpaceLayoutRules(lines: string[], hasExplicitNewlines: boolean): string[] {\n // If no explicit newlines, all lines are from one paragraph\n // First line = paragraph start, last line = paragraph end\n const paragraphMetadata = lines.map((_, index) => ({\n isParagraphStart: !hasExplicitNewlines && index === 0,\n isParagraphEnd: !hasExplicitNewlines && index === lines.length - 1,\n }));\n\n const collapsedLines = lines.map((line, index) => {\n // Edge case: If line is ALL whitespace, collapse to empty regardless of paragraph position\n // This handles cases like \" Foo Bar\" wrapping to [\" \", \"Foo\", \"Bar\"] where line 1 should be hidden\n if (line.trim().length === 0) {\n return '';\n }\n\n const isParagraphStart = paragraphMetadata[index].isParagraphStart;\n const isParagraphEnd = paragraphMetadata[index].isParagraphEnd;\n\n let visibleText = line;\n\n // Hide leading spaces when NOT at paragraph start\n if (!isParagraphStart) {\n visibleText = visibleText.replace(/^ +/, '');\n }\n\n // Hide trailing spaces when NOT at paragraph end\n if (!isParagraphEnd) {\n visibleText = visibleText.replace(/ +$/, '');\n }\n\n return visibleText;\n });\n\n // CRITICAL: Filter out empty lines to prevent gaps in layout\n // Without this, height calculations and Y positioning include the empty line\n return collapsedLines.filter((line) => line.length > 0);\n}\n\n/**\n * Render text fill only (without transforms or effects)\n * Shared utility used by main rendering and knockout compositing\n *\n * IMPORTANT: This renders ONLY the fill, positioned and transformed correctly\n * It does NOT render strokes, masks, or other effects\n */\nexport function renderTextFillOnly(\n ctx: RenderContext,\n element: {\n text: string;\n fontSize: number;\n fontFamily: string;\n bold?: boolean;\n italic?: boolean;\n textAlign?: TextAlign;\n color: string;\n }\n): void {\n const fontSize = element.fontSize;\n const fontFamily = element.fontFamily;\n const bold = element.bold || false;\n const italic = element.italic || false;\n const textAlign = element.textAlign || 'center';\n const text = element.text;\n\n // Set up font\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n ctx.textAlign = textAlign as CanvasTextAlign;\n ctx.textBaseline = 'alphabetic';\n ctx.fillStyle = element.color;\n\n // Calculate Y position using font metrics for perfect alignment\n const fontMetrics = getFontMetrics(fontSize, fontFamily, bold, italic);\n const visualHeight = fontMetrics.height;\n const topLeftY = -visualHeight / 2;\n const yPos = topLeftY + fontMetrics.ascent;\n\n // Render fill only\n ctx.fillText(text, 0, yPos);\n}\n\n/**\n * Word wrap text to fit within a given width\n */\nexport function wrapText(\n text: string,\n maxWidth: number,\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false,\n lockedLineCount?: number\n): string[] {\n const ctx = getMeasureContext();\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n\n // If text is all whitespace, return as-is (no wrapping needed)\n if (text.trim().length === 0) {\n return [text];\n }\n\n // Handle explicit newlines: split by \\n first, wrap each line separately, then rejoin\n // This preserves multi-line structure like \" Foo\\nBar\"\n if (text.includes('\\n')) {\n const explicitLines = text.split('\\n');\n const allWrappedLines: string[] = [];\n\n for (let i = 0; i < explicitLines.length; i++) {\n const line = explicitLines[i];\n // Recursively wrap each line (without locked line count, as that applies to the whole text)\n const wrappedLine = wrapText(line, maxWidth, fontSize, fontFamily, bold, italic);\n allWrappedLines.push(...wrappedLine);\n }\n\n return allWrappedLines;\n }\n\n // Preserve leading and trailing spaces to prevent character loss\n // Example: \" Hello World \" -> leadingSpaces=\" \", trimmedText=\"Hello World\", trailingSpaces=\" \"\n const leadingSpacesMatch = text.match(/^\\s*/);\n const trailingSpacesMatch = text.match(/\\s*$/);\n const leadingSpaces = leadingSpacesMatch ? leadingSpacesMatch[0] : '';\n const trailingSpaces = trailingSpacesMatch ? trailingSpacesMatch[0] : '';\n const trimmedText = text.substring(leadingSpaces.length, text.length - trailingSpaces.length);\n\n const words = trimmedText.split(' ');\n\n // If line count is locked (during corner resize), force that exact number of lines\n if (lockedLineCount !== undefined && lockedLineCount > 0) {\n if (lockedLineCount === 1) {\n return [leadingSpaces + words.join(' ') + trailingSpaces];\n }\n\n // Distribute words as evenly as possible across locked line count\n const lines: string[] = [];\n const wordsPerLine = Math.ceil(words.length / lockedLineCount);\n\n for (let i = 0; i < words.length; i += wordsPerLine) {\n const lineWords = words.slice(i, i + wordsPerLine);\n const lineText = lineWords.join(' ');\n\n // Add leading spaces to first line, trailing spaces to last line\n if (i === 0 && i + wordsPerLine >= words.length) {\n // Single line result\n lines.push(leadingSpaces + lineText + trailingSpaces);\n } else if (i === 0) {\n // First line\n lines.push(leadingSpaces + lineText);\n } else if (i + wordsPerLine >= words.length) {\n // Last line\n lines.push(lineText + trailingSpaces);\n } else {\n // Middle lines\n lines.push(lineText);\n }\n }\n\n return lines;\n }\n\n // Normal wrapping logic (no lock)\n // First pass: check if all text fits on one line with generous tolerance\n const allText = words.join(' ');\n const allTextWithSpaces = leadingSpaces + allText + trailingSpaces;\n const allTextWidth = ctx.measureText(allTextWithSpaces).width;\n\n const isSmallSize = maxWidth < 200;\n const wrapTolerancePercent = isSmallSize ? 0.08 : 0.05;\n const wrapTolerance = Math.max(20, maxWidth * wrapTolerancePercent);\n\n if (allTextWidth <= maxWidth + wrapTolerance) {\n return [allTextWithSpaces];\n }\n\n // Otherwise, perform word wrapping\n const lines: string[] = [];\n let currentLine = words[0];\n const lineTolerancePercent = isSmallSize ? 0.05 : 0.03;\n const lineTolerance = Math.max(15, maxWidth * lineTolerancePercent);\n\n for (let i = 1; i < words.length; i++) {\n const word = words[i];\n const testLine = currentLine + ' ' + word;\n const metrics = ctx.measureText(testLine);\n\n if (metrics.width > maxWidth + lineTolerance) {\n lines.push(currentLine);\n currentLine = word;\n } else {\n currentLine = testLine;\n }\n }\n lines.push(currentLine);\n\n // Add leading spaces to first line and trailing spaces to last line\n if (lines.length > 0) {\n lines[0] = leadingSpaces + lines[0];\n lines[lines.length - 1] = lines[lines.length - 1] + trailingSpaces;\n }\n\n return lines;\n}\n\n/**\n * Render multi-line text with alignment and text decorations\n * Works with both CanvasRenderingContext2D and OffscreenCanvasRenderingContext2D\n */\nexport function renderMultilineText(\n ctx: RenderContext,\n lines: string[],\n x: number,\n y: number,\n fontSize: number,\n fontFamily: string,\n alignment: CanvasTextAlign = 'left',\n containerWidth: number = 0,\n horizontalPadding: number = 0,\n lineHeight: number = 1.2,\n bold: boolean = false,\n italic: boolean = false,\n underline: boolean = false,\n strikethrough: boolean = false,\n openTypeFeatures?: OpenTypeFeatures,\n glyphOverrides?: import('../types/index.js').GlyphOverride[]\n): void {\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n ctx.textBaseline = 'alphabetic';\n ctx.textAlign = alignment;\n\n // Helper function to detect PUA characters (U+F0000 to U+FFFFD)\n const containsPUACharacters = (text: string): boolean => {\n for (const char of text) {\n const codePoint = char.codePointAt(0);\n if (codePoint && codePoint >= 0xf0000 && codePoint <= 0xffffd) {\n return true;\n }\n }\n return false;\n };\n\n // Check if we should use fontkit rendering for OpenType features, glyph overrides, OR PUA characters\n const hasOpenTypeFeatures = openTypeFeatures && Object.values(openTypeFeatures).some((enabled) => enabled);\n const hasGlyphOverrides = glyphOverrides && glyphOverrides.length > 0;\n const allText = lines.join(' ');\n const hasPUACharacters = containsPUACharacters(allText);\n const needsFontkitRendering = hasOpenTypeFeatures || hasGlyphOverrides || hasPUACharacters;\n\n const fontkitFont = needsFontkitRendering ? FontAnalyzer.getFontSync(fontFamily, bold ? 700 : 400) : null;\n\n // If we need fontkit rendering but font isn't loaded, try to load it\n // (This shouldn't happen if fonts are pre-loaded properly, but provides a fallback)\n if (needsFontkitRendering && !fontkitFont) {\n logger.warn(\n `[renderMultilineText] Font ${fontFamily} not loaded for fontkit rendering. Text may not render correctly. Has PUA: ${hasPUACharacters}, Text: \"${allText}\"`\n );\n // Try to load it asynchronously for next render\n FontAnalyzer.loadFont(fontFamily, bold ? 700 : 400).catch((err) => {\n logger.error('[renderMultilineText] Error loading font:', err);\n });\n }\n\n const fontMetrics = getFontMetrics(fontSize, fontFamily, bold, italic);\n const lineSpacing = fontSize * lineHeight;\n\n // Calculate character offset for each line (for glyph overrides)\n let charOffset = 0;\n\n lines.forEach((line: string, lineIndex: number) => {\n let xPos = x + horizontalPadding;\n\n if (alignment === 'center') {\n xPos = x + containerWidth / 2;\n } else if (alignment === 'right') {\n xPos = x + containerWidth - horizontalPadding;\n }\n\n const yPos = y + fontMetrics.ascent + lineIndex * lineSpacing;\n\n // Filter glyph overrides for this line\n const lineLength = Array.from(line).length; // Handle multi-byte characters\n const lineGlyphOverrides = hasGlyphOverrides\n ? glyphOverrides!\n .filter((override) => {\n const relativeIndex = override.charIndex - charOffset;\n return relativeIndex >= 0 && relativeIndex < lineLength;\n })\n .map((override) => ({\n ...override,\n charIndex: override.charIndex - charOffset, // Adjust to line-relative index\n }))\n : [];\n\n // Use fontkit rendering if needed and font is loaded\n if (fontkitFont && (hasOpenTypeFeatures || lineGlyphOverrides.length > 0 || hasPUACharacters)) {\n // If we have glyph overrides OR PUA characters for this line, use glyph override rendering\n // (The renderTextWithGlyphOverridesSync function handles both)\n if (lineGlyphOverrides.length > 0 || containsPUACharacters(line)) {\n renderTextWithGlyphOverridesSync(ctx, fontkitFont, line, xPos, yPos, fontSize, lineGlyphOverrides, {\n color: ctx.fillStyle as string,\n align: (alignment === 'start' || alignment === 'end' ? 'left' : alignment) as 'left' | 'right' | 'center',\n });\n }\n // Otherwise use OpenType features rendering\n else if (openTypeFeatures) {\n renderTextWithOpenTypeFeaturesSync(ctx, fontkitFont, line, xPos, yPos, fontSize, openTypeFeatures, {\n color: ctx.fillStyle as string,\n align: (alignment === 'start' || alignment === 'end' ? 'left' : alignment) as 'left' | 'right' | 'center',\n });\n }\n } else {\n ctx.fillText(line, xPos, yPos);\n }\n\n // Update character offset for next line (include line break)\n charOffset += lineLength + 1; // +1 for the line break/space\n\n // Draw underline if enabled\n if (underline) {\n const lineWidth = measureTextWidth(line, fontSize, fontFamily, bold, italic);\n let underlineX = xPos;\n\n if (alignment === 'center') {\n underlineX = xPos - lineWidth / 2;\n } else if (alignment === 'right') {\n underlineX = xPos - lineWidth;\n }\n\n const underlineY = yPos + fontSize * 0.1;\n ctx.beginPath();\n ctx.moveTo(underlineX, underlineY);\n ctx.lineTo(underlineX + lineWidth, underlineY);\n ctx.lineWidth = Math.max(1, fontSize * 0.05);\n ctx.strokeStyle = ctx.fillStyle as string;\n ctx.stroke();\n }\n\n // Draw strikethrough if enabled\n if (strikethrough) {\n const lineWidth = measureTextWidth(line, fontSize, fontFamily, bold, italic);\n let strikethroughX = xPos;\n\n if (alignment === 'center') {\n strikethroughX = xPos - lineWidth / 2;\n } else if (alignment === 'right') {\n strikethroughX = xPos - lineWidth;\n }\n\n const strikethroughY = yPos - fontSize * 0.25;\n ctx.beginPath();\n ctx.moveTo(strikethroughX, strikethroughY);\n ctx.lineTo(strikethroughX + lineWidth, strikethroughY);\n ctx.lineWidth = Math.max(1, fontSize * 0.05);\n ctx.strokeStyle = ctx.fillStyle as string;\n ctx.stroke();\n }\n });\n}\n\n// ============================================================================\n// Element Rendering Functions\n// ============================================================================\n\n/**\n * Render a CustomTransform (straight text) element\n * Pure function that works in both main thread and worker\n */\nexport function renderCustomTransform(ctx: RenderContext, elementData: SerializedTextElement): void {\n // Knockout compositing is handled in CompositingRenderer.ts\n renderCustomTransformNormal(ctx, elementData);\n}\n\nfunction renderCustomTransformNormal(ctx: RenderContext, elementData: SerializedTextElement): void {\n // Render stroke BEFORE fill for outer-stroke effect.\n // Canvas strokeText draws centered on the glyph outline; drawing fill on top\n // covers the inner half, leaving only the outer half visible as an outline.\n if (elementData.stroke?.enabled) {\n renderTextStroke(ctx, elementData as unknown as AnyElementConfig);\n }\n\n ctx.save();\n\n // Translate to center (x, y), rotate\n ctx.translate(elementData.x, elementData.y);\n // Use negative angle for clockwise rotation (matching UI convention)\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n\n const transformData = elementData.transformData as CustomElementConfig['transformData'];\n const availableWidth = (transformData?.width ?? 100) - HORIZONTAL_PADDING * 2;\n\n // Check if we have rich text\n if (elementData.richText) {\n // Rich text rendering with word wrapping support\n const richText = RichText.fromJSON(elementData.richText);\n\n ctx.save();\n // Position at center\n ctx.translate(0, 0);\n\n // Check for locked line count in element data (set during corner resize)\n const lockedLineCount = elementData._lockedLineCount;\n\n // Render with word wrapping based on available width\n renderRichTextFillOnly(\n ctx,\n richText,\n {\n fontSize: elementData.fontSize,\n fontFamily: elementData.fontFamily,\n color: elementData.color,\n bold: elementData.bold,\n italic: elementData.italic,\n underline: elementData.underline,\n strikethrough: elementData.strikethrough,\n textAlign: elementData.textAlign,\n },\n availableWidth,\n lockedLineCount\n );\n\n ctx.restore();\n } else {\n // Plain text rendering - fall back to old behavior\n if (elementData.text) {\n ctx.fillStyle = elementData.color;\n\n const lockedLineCount = elementData._lockedLineCount;\n\n // CRITICAL: Use calculateVisualBoundsWithSpaceCollapsing with strict: true to match\n // the selection box (getBoundingBox) and stroke rendering (renderTextStroke).\n // This ensures the fill text aligns perfectly with the stroke/highlight.\n const { lines, height: visualHeight } = calculateVisualBoundsWithSpaceCollapsing(\n elementData.text,\n availableWidth,\n elementData.fontSize,\n elementData.fontFamily,\n elementData.bold,\n elementData.italic,\n lockedLineCount,\n true // strict: true\n );\n\n const fontMetrics = getFontMetrics(\n elementData.fontSize,\n elementData.fontFamily,\n elementData.bold,\n elementData.italic\n );\n\n const topLeftX = -(transformData?.width ?? 100) / 2;\n const topLeftY = -visualHeight / 2;\n const lineSpacing = elementData.fontSize * LINE_HEIGHT_MULTIPLIER;\n\n // Render lines with space collapsing logic matching renderTextStroke\n ctx.font = `${elementData.italic ? 'italic ' : ''}${elementData.bold ? 'bold ' : ''}${elementData.fontSize}px ${elementData.fontFamily}`;\n ctx.textBaseline = 'alphabetic';\n ctx.textAlign = elementData.textAlign as CanvasTextAlign;\n\n lines.forEach((line, index) => {\n // Match space collapsing logic from renderTextStroke\n const isParagraphStart = index === 0;\n const isParagraphEnd = index === lines.length - 1;\n\n let visibleText = line;\n\n // Hide leading spaces when NOT at paragraph start\n if (!isParagraphStart) {\n const leadingSpacesMatch = line.match(/^ +/);\n const leadingSpacesCount = leadingSpacesMatch ? leadingSpacesMatch[0].length : 0;\n if (leadingSpacesCount > 0) {\n visibleText = visibleText.substring(leadingSpacesCount);\n }\n }\n\n // Hide trailing spaces when NOT at paragraph end\n if (!isParagraphEnd) {\n const trailingSpacesMatch = visibleText.match(/ +$/);\n const trailingSpacesCount = trailingSpacesMatch ? trailingSpacesMatch[0].length : 0;\n if (trailingSpacesCount > 0) {\n visibleText = visibleText.substring(0, visibleText.length - trailingSpacesCount);\n }\n }\n\n let xPos = topLeftX + HORIZONTAL_PADDING;\n if (elementData.textAlign === 'center') {\n xPos = topLeftX + (transformData?.width ?? 100) / 2;\n } else if (elementData.textAlign === 'right') {\n xPos = topLeftX + (transformData?.width ?? 100) - HORIZONTAL_PADDING;\n }\n\n const yPos = topLeftY + fontMetrics.ascent + index * lineSpacing;\n ctx.fillText(visibleText, xPos, yPos);\n\n // Draw underline/strikethrough if needed (simplified, only if visible text)\n if (visibleText.length > 0) {\n const lineWidth = ctx.measureText(visibleText).width;\n let decorationX = xPos;\n if (elementData.textAlign === 'center') decorationX -= lineWidth / 2;\n else if (elementData.textAlign === 'right') decorationX -= lineWidth;\n\n if (elementData.underline) {\n const underlineY = yPos + elementData.fontSize * 0.1;\n ctx.fillRect(decorationX, underlineY, lineWidth, Math.max(1, elementData.fontSize * 0.05));\n }\n if (elementData.strikethrough) {\n const strikeY = yPos - fontMetrics.ascent * 0.4;\n ctx.fillRect(decorationX, strikeY, lineWidth, Math.max(1, elementData.fontSize * 0.05));\n }\n }\n });\n }\n }\n\n ctx.restore();\n}\n\n/**\n * Render a text element based on its type\n * This is the main entry point for rendering text elements in the worker\n */\nexport function renderTextElement(ctx: RenderContext, elementData: SerializedTextElement): void {\n const elementOpacity = elementData.opacity ?? 1;\n const hasStroke = !!elementData.stroke?.enabled;\n\n // When element has opacity < 1 AND a stroke, render to an offscreen canvas\n // at full opacity, then composite at the element's opacity. This prevents\n // the inner half of the stroke from showing through the semi-transparent fill\n // (canvas strokeText draws centered on the glyph outline).\n //\n // ONLY for 'custom' transforms: non-custom transforms (arch, circle, wave,\n // etc.) render stroke per-character inside the transform renderer, and their\n // main-thread render() method just sets globalAlpha directly without offscreen\n // compositing. Using offscreen here for those types produces a different\n // result (uniform composite vs per-draw globalAlpha), causing export diffs.\n const isCustomTransform = elementData.type === 'custom';\n if (elementOpacity < 1 && hasStroke && isCustomTransform) {\n renderTextElementWithOffscreen(ctx, elementData, elementOpacity);\n return;\n }\n\n // Apply opacity if specified\n const previousAlpha = ctx.globalAlpha;\n if (elementOpacity !== 1) {\n ctx.globalAlpha = elementOpacity;\n }\n\n renderTextElementInner(ctx, elementData);\n\n // Restore previous alpha\n ctx.globalAlpha = previousAlpha;\n}\n\n/**\n * Render text via offscreen canvas to avoid stroke/fill opacity compounding.\n */\nfunction renderTextElementWithOffscreen(\n ctx: RenderContext,\n elementData: SerializedTextElement,\n elementOpacity: number\n): void {\n // Estimate bounds for the offscreen canvas\n const td = elementData.transformData as { width?: number } | undefined;\n const fontSize = elementData.fontSize || 24;\n const strokeWidth = elementData.stroke?.width || 0;\n const estWidth = (td?.width ?? fontSize * elementData.text.length * 0.7) + strokeWidth * 2 + 40;\n const estHeight = fontSize * 3 + strokeWidth * 2 + 40; // generous for multi-line\n\n const transform = ctx.getTransform();\n const scale = Math.max(Math.abs(transform.a), Math.abs(transform.d), 1);\n\n const offW = Math.ceil(estWidth * scale);\n const offH = Math.ceil(estHeight * scale);\n\n let offCanvas: HTMLCanvasElement | OffscreenCanvas;\n if (typeof OffscreenCanvas !== 'undefined') {\n offCanvas = new OffscreenCanvas(offW, offH);\n } else {\n offCanvas = document.createElement('canvas');\n offCanvas.width = offW;\n offCanvas.height = offH;\n }\n\n const offCtx = offCanvas.getContext('2d') as RenderContext;\n if (!offCtx) {\n // Fallback: render directly with compounding\n const prev = ctx.globalAlpha;\n ctx.globalAlpha = elementOpacity;\n renderTextElementInner(ctx, elementData);\n ctx.globalAlpha = prev;\n return;\n }\n\n // Replicate the main canvas transform, shifted so the element center maps\n // to the center of the offscreen canvas\n const centerPixelX = transform.a * elementData.x + transform.e;\n const centerPixelY = transform.d * elementData.y + transform.f;\n\n offCtx.setTransform(\n transform.a, transform.b,\n transform.c, transform.d,\n transform.e - centerPixelX + offW / 2,\n transform.f - centerPixelY + offH / 2,\n );\n\n // Render at full opacity on offscreen\n renderTextElementInner(offCtx, elementData);\n\n // Composite onto main canvas at element opacity\n ctx.save();\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.globalAlpha = elementOpacity;\n ctx.drawImage(offCanvas, centerPixelX - offW / 2, centerPixelY - offH / 2);\n ctx.restore();\n}\n\n/**\n * Inner render dispatch — renders at whatever globalAlpha is currently set.\n */\nfunction renderTextElementInner(ctx: RenderContext, elementData: SerializedTextElement): void {\n // Non-custom transforms handle stroke internally (per-character along the path).\n // Custom transform handles stroke in renderCustomTransformNormal.\n switch (elementData.type) {\n case 'custom':\n renderCustomTransform(ctx, elementData);\n break;\n case 'circle':\n renderCircleTransform(ctx, elementData);\n break;\n case 'wave':\n renderWaveTransform(ctx, elementData);\n break;\n case 'arch':\n renderArchTransform(ctx, elementData);\n break;\n case 'ascend':\n renderAscendTransform(ctx, elementData);\n break;\n case 'lean':\n renderLeanTransform(ctx, elementData);\n break;\n case 'flag':\n renderFlagTransform(ctx, elementData);\n break;\n default:\n logger.warn(`[canvas-renderer] Unsupported transform type: ${elementData.type}`);\n // Fallback: render as CustomTransform\n renderCustomTransform(ctx, elementData);\n }\n}\n","/**\n * ResizeUtils - Common resize logic shared across transforms\n * Eliminates duplication in transform resize() methods\n */\n\nimport { RotationUtils } from './RotationUtils.js';\nimport { MIN_WIDTH } from '../constants.js';\nimport type { TextElement } from './TextElement.js';\nimport type { TransformStartData } from '../types/index.js';\nimport { RichText } from '../types/index.js';\n\n/** Helper to get width from transform data (which has index signature) */\nfunction getTransformWidth(data: Record<string, unknown>): number | undefined {\n return typeof data.width === 'number' ? data.width : undefined;\n}\n\n/** Helper to set width on transform data */\nfunction setTransformWidth(data: Record<string, unknown>, value: number): void {\n data.width = value;\n}\n\n/**\n * Handle corner resize (uniform scaling)\n * Corner handles scale both fontSize and width proportionally\n */\nexport function handleCornerResize(element: TextElement, newWidth: number, startData: TransformStartData) {\n // Use width change only (X-axis in local coordinates) for scale factor\n // This provides intuitive image-like scaling behavior\n // For text elements with transformData.width, use that instead of bbox width to avoid\n // feedback loops from text wrapping changing the bbox dimensions\n const startTransformData = startData.transformData as Record<string, unknown>;\n const startTransformWidth = getTransformWidth(startTransformData);\n const startWidth = startTransformWidth !== undefined ? startTransformWidth : (startData.width ?? 0);\n const scale = newWidth / startWidth;\n\n // Apply uniform scale to font size\n // Always update font size to match the scale - this is critical to prevent\n // text wrapping flicker where width changes but fontSize lags behind\n // At smaller sizes, round to whole pixels for more stability\n const calculatedFontSize = (startData.fontSize ?? element.fontSize) * scale;\n let newFontSize;\n if (calculatedFontSize < 16) {\n // At very small sizes, round to whole pixels\n newFontSize = Math.round(calculatedFontSize);\n } else {\n // At larger sizes, round to 0.5px increments\n newFontSize = Math.round(calculatedFontSize * 2) / 2;\n }\n element.setFontSize(newFontSize);\n\n // ALSO scale all character-level font sizes in rich text\n // Scale from the ORIGINAL startData.richText, not current state\n if (startData.richText && startData.richText instanceof RichText && typeof element.setRichText === 'function') {\n const originalRichText = startData.richText;\n const scaledRichText = originalRichText.clone();\n\n // Scale each span's fontSize by the same scale factor\n for (const span of scaledRichText.spans) {\n if (span.style.fontSize !== undefined) {\n const scaledCharSize = span.style.fontSize * scale;\n // Apply same rounding logic as element-level fontSize\n if (scaledCharSize < 16) {\n span.style.fontSize = Math.round(scaledCharSize);\n } else {\n span.style.fontSize = Math.round(scaledCharSize * 2) / 2;\n }\n }\n }\n // Update the element with scaled rich text\n element.setRichText(scaledRichText);\n }\n\n // Apply uniform scale to width\n const elemTransformData = element.transformData as Record<string, unknown>;\n const currentTransformWidth = getTransformWidth(elemTransformData);\n if (currentTransformWidth !== undefined && startTransformWidth !== undefined) {\n const scaledWidth = startTransformWidth * scale;\n // Round to nearest pixel to avoid flickering during resize (prevents sub-pixel\n // width changes from causing text to wrap/unwrap repeatedly)\n // Use a very small minimum (20px) to allow proportional scaling at small sizes\n const minWidth = 20;\n setTransformWidth(elemTransformData, Math.max(minWidth, Math.round(scaledWidth)));\n }\n\n // Position adjustment is handled by ResizeHandler's fixed corner logic\n // Don't adjust position here to avoid double adjustment\n}\n\n/**\n * Handle side resize (width only, no fontSize change)\n * Side handles only change width while keeping fontSize constant\n */\nexport function handleSideResize(element: TextElement, anchor: string, newWidth: number, startData: TransformStartData) {\n // Only handle left/right side anchors\n if (anchor !== 'middle-left' && anchor !== 'middle-right') {\n return;\n }\n\n // CRITICAL: Do NOT change fontSize for side handles\n element.fontSize = startData.fontSize ?? element.fontSize;\n\n // Update width only\n const elemTransformData = element.transformData as Record<string, unknown>;\n const currentTransformWidth = getTransformWidth(elemTransformData);\n if (currentTransformWidth !== undefined) {\n setTransformWidth(elemTransformData, Math.max(MIN_WIDTH, newWidth));\n\n // For center-based transforms, adjust position to keep opposite edge fixed\n const rotationRad = RotationUtils.toRadiansInverse(element.rotation);\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Calculate the change in width\n const startTransformData = startData.transformData as Record<string, unknown>;\n const startTransformWidth = getTransformWidth(startTransformData);\n const oldWidth = startTransformWidth ?? startData.width ?? 0;\n const newTransformWidth = getTransformWidth(elemTransformData) ?? 0;\n const widthChange = newTransformWidth - oldWidth;\n\n // The center needs to shift by half the width change\n if (anchor === 'middle-left') {\n // Left edge moving, right edge fixed\n element.x = startData.x - (widthChange / 2) * cos;\n element.y = startData.y - (widthChange / 2) * sin;\n } else if (anchor === 'middle-right') {\n // Right edge moving, left edge fixed\n element.x = startData.x + (widthChange / 2) * cos;\n element.y = startData.y + (widthChange / 2) * sin;\n }\n }\n}\n\n/**\n * Standard resize implementation for transforms with width-based containers\n * Handles both corner (uniform scale) and side (width only) handles\n */\nexport function standardResize(element: TextElement, anchor: string, newWidth: number, _newHeight: number, startData: TransformStartData) {\n const isCornerHandle = anchor.includes('top') || anchor.includes('bottom');\n const isSideHandle = anchor.includes('middle');\n\n if (isCornerHandle) {\n handleCornerResize(element, newWidth, startData);\n } else if (isSideHandle) {\n handleSideResize(element, anchor, newWidth, startData);\n }\n}\n","/**\n * CustomTransform - Straight text (simplest transform)\n * This is the baseline implementation that others build upon\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { measureTextWidth, wrapText, calculateVisualBoundsWithSpaceCollapsing, getFontMetrics } from '../core/TextMetrics.js';\nimport { renderCustomTransform, splitRichTextIntoLines, wrapRichTextSpans, type SerializedTextElement } from '../rendering/canvas-renderer.js';\nimport { standardResize } from '../core/ResizeUtils.js';\nimport { MIN_WIDTH, HORIZONTAL_PADDING, LINE_HEIGHT_MULTIPLIER } from '../constants.js';\nimport type { CustomElementConfig, CustomTransformData, BoundingBox, ResizeAnchor, CharacterStyle, TransformStartData } from '../types/index.js';\n\nexport class CustomTransform extends TextElement {\n declare transformData: CustomTransformData;\n _lockedLineCount?: number; // Temporary property used during resize operations only\n\n constructor(config: Partial<CustomElementConfig> = {}) {\n super(config);\n this.transformType = 'custom';\n // _lockedLineCount is not restored from config - it's temporary and not serialized\n\n // Custom transform data: controlPoints and width\n // If no width is provided, auto-fit to text content\n const data = config.transformData;\n const width =\n data?.width !== undefined\n ? data.width\n : Math.max(\n MIN_WIDTH,\n measureTextWidth(this.text, this.fontSize, this.fontFamily, this.bold, this.italic) + HORIZONTAL_PADDING * 2\n );\n\n this.transformData = {\n type: 'custom',\n controlPoints: data?.controlPoints ?? [],\n width: width,\n };\n }\n\n /**\n * Calculate height for rich text considering per-character fonts\n * Returns the total height based on the tallest font in each line\n */\n private calculateRichTextHeight(): number {\n const richText = this.getRichText();\n const availableWidth = this.transformData.width - HORIZONTAL_PADDING * 2;\n\n // Split into lines and wrap\n const explicitLines = splitRichTextIntoLines(richText);\n\n // Build wrapped lines\n const wrappedLines: Array<{ text: string; style: CharacterStyle }[]> = [];\n\n explicitLines.forEach((lineSpans) => {\n const wrapped = wrapRichTextSpans(lineSpans, availableWidth, {\n fontSize: this.fontSize,\n fontFamily: this.fontFamily,\n bold: this.bold,\n italic: this.italic,\n });\n wrapped.forEach((wLine) => {\n wrappedLines.push(wLine);\n });\n });\n\n if (wrappedLines.length === 0) {\n return 0;\n }\n\n // Calculate max font height across all spans to get proper total height\n // This ensures the bounding box encompasses all characters regardless of font size\n let maxFontHeight = 0;\n let maxAscent = 0;\n\n wrappedLines.forEach((lineSpans) => {\n lineSpans.forEach((span) => {\n const spanFontSize = span.style.fontSize !== undefined ? span.style.fontSize : this.fontSize;\n const spanFontFamily = span.style.fontFamily !== undefined ? span.style.fontFamily : this.fontFamily;\n const spanBold = span.style.bold !== undefined ? span.style.bold : this.bold;\n const spanItalic = span.style.italic !== undefined ? span.style.italic : this.italic;\n\n const metrics = getFontMetrics(spanFontSize, spanFontFamily, spanBold, spanItalic);\n maxFontHeight = Math.max(maxFontHeight, metrics.height);\n maxAscent = Math.max(maxAscent, metrics.ascent);\n });\n });\n\n // Use element-level line spacing for consistency with rendering\n const lineSpacing = this.fontSize * LINE_HEIGHT_MULTIPLIER;\n\n // Calculate total height\n // The rendering positions lines at lineSpacing intervals, using element-level font metrics\n // But individual characters can extend beyond this if they have larger fonts\n // We need to account for:\n // 1. The space taken by line positioning: (numLines - 1) * lineSpacing\n // 2. The height of the tallest font (to ensure all characters fit)\n if (wrappedLines.length === 1) {\n return maxFontHeight;\n } else {\n // For multi-line: standard line spacing + room for tallest font\n const elementMetrics = getFontMetrics(this.fontSize, this.fontFamily, this.bold, this.italic);\n const standardHeight = (wrappedLines.length - 1) * lineSpacing + elementMetrics.height;\n\n // Check if any span extends beyond the standard height\n // The extra height needed is the difference between max ascent and element ascent\n // (since taller fonts extend upward from the baseline)\n const extraAscent = Math.max(0, maxAscent - elementMetrics.ascent);\n const extraDescent = Math.max(0, (maxFontHeight - maxAscent) - (elementMetrics.height - elementMetrics.ascent));\n\n return standardHeight + extraAscent + extraDescent;\n }\n }\n\n /**\n * Check if rich text has per-character formatting that differs from element defaults\n */\n private hasPerCharacterFormatting(): boolean {\n const richText = this.getRichText();\n\n for (const span of richText.spans) {\n // Check if any span has explicit fontSize or fontFamily that differs from element defaults\n if (span.style.fontSize !== undefined && span.style.fontSize !== this.fontSize) {\n return true;\n }\n if (span.style.fontFamily !== undefined && span.style.fontFamily !== this.fontFamily) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Get bounding box in world coordinates\n * For straight text with center-based positioning (x, y is the center)\n * This should match the actual text bounds including wrapping\n */\n getBoundingBox(): BoundingBox {\n const availableWidth = this.transformData.width - HORIZONTAL_PADDING * 2;\n\n // Check if we have per-character formatting that affects height\n let height: number;\n if (this.hasPerCharacterFormatting()) {\n // Use rich text calculation for per-character fonts\n height = this.calculateRichTextHeight();\n } else {\n // Use standard calculation for uniform text\n const result = calculateVisualBoundsWithSpaceCollapsing(\n this.text,\n availableWidth,\n this.fontSize,\n this.fontFamily,\n this.bold,\n this.italic,\n this._lockedLineCount,\n true // strict: wrap exactly at maxWidth\n );\n height = result.height;\n }\n\n return {\n x: this.x - this.transformData.width / 2,\n y: this.y - height / 2,\n width: this.transformData.width,\n height: height,\n };\n }\n\n /**\n * Get visual bounding box (container bounds for selection display)\n * For CustomTransform, this shows the container width (transformData.width)\n * and the actual height based on text wrapping\n * (x, y) is the center of the element\n */\n getVisualBoundingBox(): BoundingBox {\n const availableWidth = this.transformData.width - HORIZONTAL_PADDING * 2;\n\n // Check if we have per-character formatting that affects height\n let visualHeight: number;\n if (this.hasPerCharacterFormatting()) {\n // Use rich text calculation for per-character fonts\n visualHeight = this.calculateRichTextHeight();\n } else {\n // Use standard calculation for uniform text\n const result = calculateVisualBoundsWithSpaceCollapsing(\n this.text,\n availableWidth,\n this.fontSize,\n this.fontFamily,\n this.bold,\n this.italic,\n this._lockedLineCount,\n true // strict: wrap exactly at maxWidth\n );\n visualHeight = result.height;\n }\n\n // Use the container width (transformData.width) for the visual box\n // This shows the resizable container, not just the text\n const visualWidth = this.transformData.width;\n\n // Return container bounds (centered at x, y)\n return {\n x: this.x - visualWidth / 2,\n y: this.y - visualHeight / 2,\n width: visualWidth,\n height: visualHeight,\n };\n }\n\n /**\n * Get rotation anchor (center of the visual bounding box)\n * CustomTransform rotates around the center of the actual text, including multi-line\n */\n getRotationAnchor(): { x: number; y: number } {\n const visualBbox = this.getVisualBoundingBox();\n return {\n x: visualBbox.x + visualBbox.width / 2,\n y: visualBbox.y + visualBbox.height / 2,\n };\n }\n\n /**\n * Render the straight text\n * (x, y) is the center of the element, we rotate around this center point\n * Now uses shared rendering function that works in both main thread and worker\n */\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n // Selection and hover visuals are handled by SelectionRenderer and HoverRenderer.\n // No inline outline drawing needed here.\n\n const elementOpacity = this.opacity ?? 1;\n const hasStroke = !!this.stroke?.enabled;\n\n // When element has opacity < 1 AND a stroke, render to an offscreen canvas\n // at full opacity to prevent stroke/fill compounding (same fix as ShapeElement).\n if (elementOpacity < 1 && hasStroke) {\n this.renderWithOffscreen(ctx, elementOpacity);\n return;\n }\n\n // Apply element opacity\n const previousAlpha = ctx.globalAlpha;\n if (elementOpacity !== 1) {\n ctx.globalAlpha = elementOpacity;\n }\n\n // Render text (fill + stroke handled inside renderCustomTransform)\n const serialized = this.toJSON();\n renderCustomTransform(ctx, serialized as unknown as SerializedTextElement);\n\n // Restore previous alpha\n ctx.globalAlpha = previousAlpha;\n }\n\n /**\n * Render stroke+fill to an offscreen canvas at full opacity, then composite\n * at the element's opacity. Prevents stroke inner half showing through fill.\n */\n private renderWithOffscreen(ctx: CanvasRenderingContext2D, elementOpacity: number): void {\n const strokeWidth = this.stroke?.width || 0;\n const padding = strokeWidth + 20;\n const bbox = this.getVisualBoundingBox();\n const offW = Math.ceil(bbox.width + padding * 2);\n const offH = Math.ceil(bbox.height + padding * 2);\n\n const offCanvas = document.createElement('canvas');\n offCanvas.width = offW;\n offCanvas.height = offH;\n const offCtx = offCanvas.getContext('2d');\n if (!offCtx) {\n // Fallback: render directly with compounding\n const prev = ctx.globalAlpha;\n ctx.globalAlpha = elementOpacity;\n const serialized = this.toJSON();\n renderCustomTransform(ctx, serialized as unknown as SerializedTextElement);\n ctx.globalAlpha = prev;\n return;\n }\n\n // Shift transforms so the element renders centered on the offscreen canvas\n const offsetX = bbox.x + bbox.width / 2;\n const offsetY = bbox.y + bbox.height / 2;\n\n offCtx.translate(offW / 2 - offsetX, offH / 2 - offsetY);\n\n // Render at full opacity\n const serialized = this.toJSON();\n renderCustomTransform(offCtx, serialized as unknown as SerializedTextElement);\n\n // Composite onto main canvas at element opacity\n ctx.save();\n ctx.globalAlpha = elementOpacity;\n ctx.drawImage(offCanvas, offsetX - offW / 2, offsetY - offH / 2);\n ctx.restore();\n }\n\n /**\n * Override getTransformStartData to capture initial line count\n */\n override getTransformStartData(): TransformStartData {\n const startData = super.getTransformStartData();\n\n // Capture current line count at current width\n // This calculates how the text is CURRENTLY wrapping (without any lock from previous resizes)\n // because _lockedLineCount is not serialized and won't be present on the element\n const availableWidth = this.transformData.width - HORIZONTAL_PADDING * 2;\n const lines = wrapText(this.text, availableWidth, this.fontSize, this.fontFamily, this.bold, this.italic);\n startData.lineCount = lines.length;\n\n return startData;\n }\n\n /**\n * Handle resize using standard resize logic\n * Corner handles: uniform scale (fontSize and width both change) - line count locked\n * Side handles: only width changes - text re-wraps naturally\n */\n resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): void {\n // Lock should already be set in startResize() for corner handles\n // For side handles, clear the lock to allow natural re-wrapping\n const isCornerHandle =\n anchor === 'top-left' || anchor === 'top-right' || anchor === 'bottom-left' || anchor === 'bottom-right';\n\n if (!isCornerHandle) {\n // Clear lock for side handles (which should allow text to re-wrap)\n this._lockedLineCount = undefined;\n }\n\n // Perform the standard resize\n standardResize(this, anchor, newWidth, newHeight, startData);\n\n // CRITICAL FIX: After resize, if we have a lock, ensure width is sufficient for the locked line count\n // This prevents the text from wrapping to more lines when the lock is cleared\n if (this._lockedLineCount !== undefined && this._lockedLineCount > 0) {\n // Calculate the actual text width (all words on one line if locked to 1 line)\n const fullTextWidth = measureTextWidth(this.text, this.fontSize, this.fontFamily, this.bold, this.italic);\n\n // Calculate minimum width needed to fit text on the locked line count\n // For 1 line: need full text width + padding (no extra buffer for proper alignment with images)\n // For multiple lines: use proportional estimate\n const minWidth = fullTextWidth / this._lockedLineCount + HORIZONTAL_PADDING * 2;\n\n // Ensure width is never below minimum\n if (this.transformData.width < minWidth) {\n this.transformData.width = Math.ceil(minWidth);\n }\n }\n }\n\n /**\n * Override setText - don't auto-adjust width\n * Let text wrap naturally within the current container width\n */\n setText(newText: string): void {\n super.setText(newText);\n // Don't auto-adjust width - maintain current container width\n // This allows text to wrap to multiple lines as needed\n\n // Clear locked line count when text content changes\n this._lockedLineCount = undefined;\n }\n\n /**\n * Get enabled anchors\n * Straight text supports all 8 handles\n */\n getEnabledAnchors(): ResizeAnchor[] {\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right', 'middle-left', 'middle-right'];\n }\n\n /**\n * Clone this element\n * Override to preserve temporary _lockedLineCount during resize operations\n * (but it still won't be saved in JSON exports)\n */\n clone(): CustomTransform {\n const cloned = super.clone() as CustomTransform;\n\n // Preserve the lock state during cloning (for smooth resize)\n // This won't be saved in JSON because toJSON() doesn't include it\n if (this._lockedLineCount !== undefined) {\n cloned._lockedLineCount = this._lockedLineCount;\n }\n\n return cloned;\n }\n\n /**\n * Serialize with transform data\n * NOTE: _lockedLineCount is intentionally NOT serialized\n * The lock is temporary during resize and should not persist after\n */\n toJSON(): CustomElementConfig {\n const json = {\n ...super.toJSON(),\n transformType: 'custom' as const,\n transformData: {\n type: 'custom' as const,\n controlPoints: this.transformData.controlPoints,\n width: this.transformData.width,\n },\n };\n\n // _lockedLineCount is NOT included in serialization\n // This ensures the lock doesn't persist after a resize operation ends\n\n return json;\n }\n}\n\nexport default CustomTransform;\n","/**\n * Geometry utilities for text element transformations\n * Ported from shared/utils.js and enhanced for custom canvas\n */\n\nimport { RotationUtils } from './RotationUtils.js';\nimport { ROTATION_HANDLE_DISTANCE } from '../constants.js';\nimport type { BoundingBox, Point, ResizeAnchor } from '../types/index.js';\n\n/**\n * Calculate position to keep opposite corner fixed during resize\n * This is the core algorithm that makes corner dragging feel natural\n *\n * @param {Object} startData - Transform start state {x, y, width, height}\n * @param {Object} newDimensions - New dimensions {width, height}\n * @param {string} activeAnchor - Which corner is being dragged\n * @param {number} rotation - Rotation in degrees\n * @returns {Object} New position {x, y}\n */\nexport function calculateFixedCornerPosition(startData: { x: number; y: number; width: number; height: number }, newDimensions: { width: number; height: number }, activeAnchor: string, rotation: number) {\n const rotationRad = RotationUtils.toRadians(rotation);\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Determine which corner should stay fixed (in local coords at START)\n let fixedLocalX = 0;\n let fixedLocalY = 0;\n\n if (activeAnchor === 'top-left') {\n // Fix bottom-right\n fixedLocalX = startData.width;\n fixedLocalY = startData.height;\n } else if (activeAnchor === 'top-right') {\n // Fix bottom-left\n fixedLocalX = 0;\n fixedLocalY = startData.height;\n } else if (activeAnchor === 'bottom-left') {\n // Fix top-right\n fixedLocalX = startData.width;\n fixedLocalY = 0;\n } else if (activeAnchor === 'bottom-right') {\n // Fix top-left\n fixedLocalX = 0;\n fixedLocalY = 0;\n }\n\n // Calculate the fixed corner's position in world coordinates (from start of transform)\n const fixedWorldX = startData.x + (fixedLocalX * cos - fixedLocalY * sin);\n const fixedWorldY = startData.y + (fixedLocalX * sin + fixedLocalY * cos);\n\n // Where that corner is NOW in the new dimensions\n let newFixedLocalX = 0;\n let newFixedLocalY = 0;\n\n if (activeAnchor === 'top-left') {\n newFixedLocalX = newDimensions.width;\n newFixedLocalY = newDimensions.height;\n } else if (activeAnchor === 'top-right') {\n newFixedLocalX = 0;\n newFixedLocalY = newDimensions.height;\n } else if (activeAnchor === 'bottom-left') {\n newFixedLocalX = newDimensions.width;\n newFixedLocalY = 0;\n } else if (activeAnchor === 'bottom-right') {\n newFixedLocalX = 0;\n newFixedLocalY = 0;\n }\n\n // Calculate new position so the fixed corner stays in the same world position\n const newX = fixedWorldX - (newFixedLocalX * cos - newFixedLocalY * sin);\n const newY = fixedWorldY - (newFixedLocalX * sin + newFixedLocalY * cos);\n\n return { x: newX, y: newY };\n}\n\n/**\n * Calculate rotation handle position\n * Positioned below the bottom edge, centered horizontally\n * Distance scales inversely with zoom to maintain consistent screen space distance\n *\n * @param {Object} bbox - Bounding box {x, y, width, height}\n * @param {number} rotation - Rotation in DEGREES (not radians - function converts internally)\n * @param {Object} rotationAnchor - Point to rotate around {x, y}\n * @param {number} zoom - Current zoom level (default 1.0)\n * @returns {Object} Handle position {x, y}\n *\n * IMPORTANT: This function expects rotation in DEGREES and converts to radians internally.\n * Do NOT pass radians to this function or you'll get incorrect positioning.\n * Example: Pass 30 for 30°, NOT 0.524 radians\n */\nexport function calculateRotationHandlePosition(bbox: BoundingBox, rotation: number, rotationAnchor: Point, zoom: number = 1.0) {\n // Local coordinates relative to bbox top-left: center of bottom edge + distance\n // Scale distance inversely with zoom to maintain consistent screen space distance\n // At zoom=0.33: distance = 70 / 0.33 = 212px world → 70px screen\n const localX = bbox.width / 2;\n const localY = bbox.height + (ROTATION_HANDLE_DISTANCE / zoom);\n\n // World position before rotation\n const worldX = bbox.x + localX;\n const worldY = bbox.y + localY;\n\n // Rotate around the rotation anchor\n const rotationRad = RotationUtils.toRadians(rotation);\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Translate to rotation anchor, rotate, translate back\n const dx = worldX - rotationAnchor.x;\n const dy = worldY - rotationAnchor.y;\n\n const rotatedX = dx * cos - dy * sin;\n const rotatedY = dx * sin + dy * cos;\n\n return {\n x: rotationAnchor.x + rotatedX,\n y: rotationAnchor.y + rotatedY,\n };\n}\n\n/**\n * Calculate all 8 resize handle positions\n *\n * @param {Object} bbox - Bounding box {x, y, width, height}\n * @param {number} rotation - Rotation in degrees\n * @param {Object} rotationAnchor - Point to rotate around {x, y}\n * @returns {Object[]} Array of handles with {type, anchor, x, y, cursor}\n */\nexport function calculateResizeHandles(bbox: BoundingBox, rotation: number, rotationAnchor: Point) {\n const rotationRad = RotationUtils.toRadians(rotation);\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Transform a local point (relative to bbox top-left) to world coordinates\n // Then rotate around the rotation anchor\n const transform = (localX: number, localY: number) => {\n // World position before rotation\n const worldX = bbox.x + localX;\n const worldY = bbox.y + localY;\n\n // Translate to rotation anchor, rotate, translate back\n const dx = worldX - rotationAnchor.x;\n const dy = worldY - rotationAnchor.y;\n\n const rotatedX = dx * cos - dy * sin;\n const rotatedY = dx * sin + dy * cos;\n\n return {\n x: rotationAnchor.x + rotatedX,\n y: rotationAnchor.y + rotatedY,\n };\n };\n\n const { width, height } = bbox;\n\n // Helper to create a handle with cursor based on world position\n const createHandle = (type: string, anchor: ResizeAnchor, localX: number, localY: number) => {\n const worldPos = transform(localX, localY);\n const cursor = getCursorForWorldPosition(\n worldPos.x,\n worldPos.y,\n rotationAnchor.x,\n rotationAnchor.y,\n type,\n rotation,\n anchor\n );\n return {\n type,\n anchor,\n ...worldPos,\n cursor,\n };\n };\n\n return [\n // Corner handles\n createHandle('corner', 'top-left', 0, 0),\n createHandle('corner', 'top-right', width, 0),\n createHandle('corner', 'bottom-left', 0, height),\n createHandle('corner', 'bottom-right', width, height),\n // Side handles\n createHandle('edge', 'middle-left', 0, height / 2),\n createHandle('edge', 'middle-right', width, height / 2),\n createHandle('edge', 'middle-top', width / 2, 0),\n createHandle('edge', 'middle-bottom', width / 2, height),\n ];\n}\n\n/**\n * Test if a point is inside a circle\n */\nexport function hitTestCircle(px: number, py: number, cx: number, cy: number, radius: number): boolean {\n const dx = px - cx;\n const dy = py - cy;\n return dx * dx + dy * dy <= radius * radius;\n}\n\n/**\n * Test if a point is inside a rotated rectangle\n */\nexport function hitTestRect(px: number, py: number, rect: BoundingBox, rotation: number): boolean {\n // Transform point to local coordinates\n // Positive rotation to undo the negative forward rotation\n const rotationRad = RotationUtils.toRadiansInverse(rotation); // Inverse rotation\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Translate to origin\n const translatedX = px - rect.x;\n const translatedY = py - rect.y;\n\n // Rotate\n const localX = translatedX * cos - translatedY * sin;\n const localY = translatedX * sin + translatedY * cos;\n\n // Test against axis-aligned box\n return localX >= 0 && localX <= rect.width && localY >= 0 && localY <= rect.height;\n}\n\n/**\n * Measure text width using canvas API\n * @deprecated Use TextMetrics.measureTextWidth instead\n */\nexport function measureTextWidth(\n text: string,\n fontSize: number,\n fontFamily: string = 'Arial',\n bold: boolean = false,\n italic: boolean = false\n): number {\n // Re-export from TextMetrics for backwards compatibility\n // This will be removed in a future version\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d')!;\n const weight = bold ? 'bold' : 'normal';\n const style = italic ? 'italic' : 'normal';\n ctx.font = `${style} ${weight} ${fontSize}px ${fontFamily}`;\n return ctx.measureText(text).width;\n}\n\n/**\n * Calculate bounding box center\n */\nexport function getBoundingBoxCenter(bbox: BoundingBox) {\n return {\n x: bbox.x + bbox.width / 2,\n y: bbox.y + bbox.height / 2,\n };\n}\n\n/**\n * Calculate angle between two points\n */\nexport function calculateAngle(centerX: number, centerY: number, pointX: number, pointY: number): number {\n return Math.atan2(pointY - centerY, pointX - centerX);\n}\n\n/**\n * Rotate a point around a center\n */\nexport function rotatePoint(px: number, py: number, cx: number, cy: number, angle: number) {\n const cos = Math.cos(angle);\n const sin = Math.sin(angle);\n const dx = px - cx;\n const dy = py - cy;\n\n return {\n x: cx + (dx * cos - dy * sin),\n y: cy + (dx * sin + dy * cos),\n };\n}\n\n/**\n * Calculate distance between two points\n */\nexport function distance(x1: number, y1: number, x2: number, y2: number): number {\n const dx = x2 - x1;\n const dy = y2 - y1;\n return Math.sqrt(dx * dx + dy * dy);\n}\n\n/**\n * Calculate distance from a point to a horizontal line\n */\nexport function distanceToHorizontalLine(_px: number, py: number, lineY: number): number {\n return Math.abs(py - lineY);\n}\n\n/**\n * Calculate distance from a point to a vertical line\n */\nexport function distanceToVerticalLine(px: number, _py: number, lineX: number): number {\n return Math.abs(px - lineX);\n}\n\n/**\n * Calculate distance from a point to a line segment\n * @param {number} px - Point X\n * @param {number} py - Point Y\n * @param {number} x1 - Line start X\n * @param {number} y1 - Line start Y\n * @param {number} x2 - Line end X\n * @param {number} y2 - Line end Y\n * @returns {number} Distance to line segment\n */\nexport function distanceToLineSegment(px: number, py: number, x1: number, y1: number, x2: number, y2: number): number {\n const lineLength = distance(x1, y1, x2, y2);\n if (lineLength === 0) return distance(px, py, x1, y1);\n\n // Calculate parameter t that determines closest point on line\n const t = Math.max(0, Math.min(1, ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / (lineLength * lineLength)));\n\n // Calculate closest point on line segment\n const closestX = x1 + t * (x2 - x1);\n const closestY = y1 + t * (y2 - y1);\n\n return distance(px, py, closestX, closestY);\n}\n\n/**\n * Check if two values are within a threshold\n * Useful for snapping detection\n */\nexport function isNear(value1: number, value2: number, threshold: number): boolean {\n return Math.abs(value1 - value2) <= threshold;\n}\n\n/**\n * Get the correct resize cursor accounting for rotation\n * @param {string} anchor - Handle anchor position (e.g., 'top-left', 'top', 'right')\n * @param {number} rotation - Rotation in degrees\n * @returns {string} CSS cursor value\n */\nexport function getRotatedResizeCursor(anchor: string, rotation: number): string {\n // Base cursor angles for each anchor (in degrees, 0 = east)\n const baseCursorAngles: Record<string, number> = {\n top: -90, // ns-resize (north-south)\n bottom: -90, // ns-resize\n left: 0, // ew-resize (east-west)\n right: 0, // ew-resize\n 'top-left': -45, // nwse-resize (northwest-southeast)\n 'bottom-right': -45, // nwse-resize\n 'top-right': 45, // nesw-resize (northeast-southwest)\n 'bottom-left': 45, // nesw-resize\n };\n\n // Get base angle for this anchor\n const baseAngle = baseCursorAngles[anchor] || 0;\n\n // Add rotation to get final angle\n let finalAngle = baseAngle + rotation;\n\n // Normalize to 0-180 range (cursors repeat every 180 degrees)\n finalAngle = ((finalAngle % 180) + 180) % 180;\n\n // Map angle ranges to cursor types\n // Each cursor covers a 45-degree range\n if (finalAngle >= 157.5 || finalAngle < 22.5) {\n return 'ew-resize';\n } else if (finalAngle >= 22.5 && finalAngle < 67.5) {\n return 'nwse-resize';\n } else if (finalAngle >= 67.5 && finalAngle < 112.5) {\n return 'ns-resize';\n } else if (finalAngle >= 112.5 && finalAngle < 157.5) {\n return 'nesw-resize';\n }\n\n return 'default';\n}\n\n/**\n * Get cursor for a handle based on its world position relative to a center point\n * @param {number} handleWorldX - Handle X position in world coordinates\n * @param {number} handleWorldY - Handle Y position in world coordinates\n * @param {number} centerWorldX - Center X position in world coordinates\n * @param {number} centerWorldY - Center Y position in world coordinates\n * @param {string} handleType - 'corner' or 'edge'\n * @param {number} rotation - Element rotation in degrees\n * @param {string} anchor - Handle anchor position (e.g., 'top-left', 'bottom-right')\n * @returns {string} CSS cursor value\n */\nexport function getCursorForWorldPosition(\n handleWorldX: number,\n handleWorldY: number,\n centerWorldX: number,\n centerWorldY: number,\n handleType: string,\n rotation: number = 0,\n anchor: string = ''\n): string {\n // Calculate angle from center to handle in world space\n const dx = handleWorldX - centerWorldX;\n const dy = handleWorldY - centerWorldY;\n\n // Calculate angle in degrees (0 = east, 90 = south, 180 = west, 270 = north)\n let angle = Math.atan2(dy, dx) * (180 / Math.PI);\n\n // Normalize to 0-360\n angle = ((angle % 360) + 360) % 360;\n\n if (handleType === 'corner') {\n // For non-rotated elements (rotation = 0), always show diagonal cursors for corners\n // This provides more intuitive UX even for wide/tall elements where the angle\n // calculation would otherwise suggest horizontal/vertical cursors\n const isUnrotated = Math.abs(rotation) < 0.1; // Within 0.1 degrees of zero\n if (isUnrotated) {\n // Map corner anchors to their diagonal cursors\n if (anchor === 'top-left' || anchor === 'bottom-right') {\n return 'nwse-resize';\n } else if (anchor === 'top-right' || anchor === 'bottom-left') {\n return 'nesw-resize';\n }\n }\n\n // For rotated elements, calculate cursor based on actual angle\n // Determine which quadrant and return appropriate diagonal cursor\n // nwse-resize: top-left to bottom-right (NW-SE diagonal, ~315-45° and ~135-225°)\n // nesw-resize: top-right to bottom-left (NE-SW diagonal, ~45-135° and ~225-315°)\n\n // Normalize to 0-180 for cursor selection (cursors repeat every 180°)\n const normalizedAngle = angle % 180;\n\n // Map to cursor based on angle ranges\n if (normalizedAngle >= 157.5 || normalizedAngle < 22.5) {\n // East/West: horizontal resize\n return 'ew-resize';\n } else if (normalizedAngle >= 22.5 && normalizedAngle < 67.5) {\n // Southeast/Northwest diagonal\n return 'nwse-resize';\n } else if (normalizedAngle >= 67.5 && normalizedAngle < 112.5) {\n // South/North: vertical resize\n return 'ns-resize';\n } else {\n // Southwest/Northeast diagonal\n return 'nesw-resize';\n }\n } else {\n // For edges, determine if it's more horizontal or vertical\n const normalizedAngle = angle % 180;\n\n if (normalizedAngle >= 45 && normalizedAngle < 135) {\n // More vertical\n return 'ns-resize';\n } else {\n // More horizontal\n return 'ew-resize';\n }\n }\n}\n\n/**\n * Canvas Zoom Utilities\n *\n * CRITICAL: Canvas transforms affect visual properties differently:\n * - Line widths ARE affected by canvas transforms (scale with zoom)\n * - Font sizes in ctx.font are NOT affected by canvas transforms\n * - Dash patterns ARE affected by canvas transforms\n *\n * Use these utilities to maintain consistent visual appearance at all zoom levels.\n */\n\n/**\n * Extract the current canvas scale factor from the transform matrix.\n * This accounts for zoom, DPR (device pixel ratio), and any other scaling.\n *\n * @param ctx - Canvas rendering context\n * @returns Current scale factor (e.g., 0.33 for 33% zoom, 1.0 for 100%)\n *\n * @example\n * const scale = getCanvasScale(ctx); // 0.33 at 33% zoom\n * ctx.lineWidth = 3 / scale; // Renders as 3px visually at any zoom\n */\nexport function getCanvasScale(ctx: CanvasRenderingContext2D): number {\n // Fallback for test environments where getTransform() might not be available\n if (!ctx.getTransform) {\n return 1.0;\n }\n\n const transform = ctx.getTransform();\n // Calculate magnitude of the transform matrix (Euclidean norm of the first column)\n // This gives us the overall scale factor regardless of rotation\n return Math.sqrt(transform.a * transform.a + transform.b * transform.b);\n}\n\n/**\n * Set a line width that maintains consistent visual thickness at all zoom levels.\n *\n * Canvas transforms affect line widths, so a 3px line at 33% zoom appears as 1px.\n * This function applies inverse scaling to counteract the zoom effect.\n *\n * @param ctx - Canvas rendering context\n * @param desiredWidth - The visual width you want (in screen pixels)\n *\n * @example\n * // Selection border that's always 3px thick on screen\n * setZoomInvariantLineWidth(ctx, 3);\n * ctx.stroke();\n */\nexport function setZoomInvariantLineWidth(ctx: CanvasRenderingContext2D, desiredWidth: number): void {\n const scale = getCanvasScale(ctx);\n ctx.lineWidth = scale > 0 ? desiredWidth / scale : desiredWidth;\n}\n\n/**\n * Set a line dash pattern that maintains consistent visual appearance at all zoom levels.\n *\n * Canvas transforms affect dash patterns, so [8, 8] at 33% zoom appears as [2.64, 2.64].\n * This function applies inverse scaling to counteract the zoom effect.\n *\n * @param ctx - Canvas rendering context\n * @param pattern - The dash pattern you want (in screen pixels), e.g., [8, 8] for \"8px dash, 8px gap\"\n *\n * @example\n * // Dotted guide line that's always [8, 8] pattern on screen\n * setZoomInvariantLineWidth(ctx, 3);\n * setZoomInvariantDash(ctx, [8, 8]);\n * ctx.stroke();\n */\nexport function setZoomInvariantDash(ctx: CanvasRenderingContext2D, pattern: number[]): void {\n const scale = getCanvasScale(ctx);\n ctx.setLineDash(scale > 0 ? pattern.map(value => value / scale) : pattern);\n}\n\n/**\n * Helper to set both line width and dash pattern with zoom-invariant values.\n * Commonly used for selection visualization and guide lines.\n *\n * @param ctx - Canvas rendering context\n * @param width - Desired line width (in screen pixels)\n * @param dashPattern - Desired dash pattern (in screen pixels)\n *\n * @example\n * // Dotted guide line for selected arch text\n * ctx.strokeStyle = getThemeAccentColor();\n * setZoomInvariantStroke(ctx, 3, [8, 8]);\n * ctx.beginPath();\n * // ... draw path ...\n * ctx.stroke();\n * ctx.setLineDash([]); // Reset to solid\n */\nexport function setZoomInvariantStroke(\n ctx: CanvasRenderingContext2D,\n width: number,\n dashPattern: number[] = []\n): void {\n const scale = getCanvasScale(ctx);\n ctx.lineWidth = scale > 0 ? width / scale : width;\n if (dashPattern.length > 0) {\n ctx.setLineDash(scale > 0 ? dashPattern.map(value => value / scale) : dashPattern);\n }\n}\n\n/**\n * Set a dash pattern that scales with viewport size (appears longer when zoomed out).\n *\n * This creates viewport-adaptive dashes that maintain better visibility when viewing\n * a larger area of the canvas. The dashes are specified in world-space, so they scale\n * naturally with the zoom level:\n * - At 100% zoom: dashes appear at base size\n * - At 33% zoom (larger viewport): dashes appear smaller on screen but proportional to viewport\n * - At 300% zoom (zoomed in): dashes appear larger on screen but proportional to viewport\n *\n * @param ctx - Canvas rendering context\n * @param basePattern - Dash pattern in world-space pixels\n *\n * @example\n * // Guide line with world-space dashes (use larger values than screen-space)\n * setZoomInvariantLineWidth(ctx, 3); // Keep line width consistent\n * setViewportProportionalDash(ctx, [30, 15]); // Use larger values for visibility\n * ctx.stroke();\n */\nexport function setViewportProportionalDash(ctx: CanvasRenderingContext2D, basePattern: number[]): void {\n // Use pattern directly in world-space - canvas transform scales it naturally\n // Larger base values ensure visibility when zoomed out\n ctx.setLineDash(basePattern);\n}\n\n/**\n * Set both line width and dash pattern with hybrid scaling.\n * Line width stays zoom-invariant (consistent screen thickness).\n * Dash pattern uses world-space values (scales with viewport).\n *\n * This is the recommended approach for guide lines and selection visualization,\n * providing consistent visual thickness while maintaining proportional dash spacing.\n *\n * @param ctx - Canvas rendering context\n * @param width - Desired line width (in screen pixels, zoom-invariant)\n * @param basePattern - Dash pattern in world-space pixels (use larger values than screen-space)\n *\n * @example\n * // Circle transform guide: consistent thickness, viewport-proportional dashes\n * ctx.strokeStyle = getThemeAccentColor();\n * setViewportProportionalStroke(ctx, 3, [30, 15]); // 30px dashes in world-space\n * ctx.arc(0, 0, radius, 0, Math.PI * 2);\n * ctx.stroke();\n * ctx.setLineDash([]); // Reset\n */\nexport function setViewportProportionalStroke(\n ctx: CanvasRenderingContext2D,\n width: number,\n basePattern: number[] = []\n): void {\n const scale = getCanvasScale(ctx);\n ctx.lineWidth = scale > 0 ? width / scale : width; // Zoom-invariant width\n if (basePattern.length > 0) {\n ctx.setLineDash(basePattern); // Viewport-proportional dashes (world-space)\n }\n}\n","/**\n * CircleTransform - Text follows a circular path\n * Ported from EditableTextPath.jsx circle mode\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport {\n measureTextWidth,\n calculateFixedCornerPosition,\n} from '../core/GeometryUtils.js';\nimport { renderCircleTransform, type SerializedTextTransformElement } from '../rendering/transform-renderer.js';\nimport type { CircleElementConfig, CircleTransformData, BoundingBox, ResizeAnchor, TransformStartData } from '../types/index.js';\n\nexport class CircleTransform extends TextElement {\n declare transformData: CircleTransformData;\n\n constructor(config: Partial<CircleElementConfig> = {}) {\n super(config);\n this.transformType = 'circle';\n\n // Circle transform data: center point and radius point\n // Position (x, y) is the center of the circle\n const data = config.transformData;\n this.transformData = {\n type: 'circle',\n radius: data?.radius ?? 100,\n scale: data?.scale ?? 1,\n reverse: data?.reverse ?? false,\n };\n }\n\n /**\n * Get bounding box in world coordinates\n * For circle, this is center ± radius (diameter)\n * This is used for transform math (resize, rotation)\n */\n getBoundingBox(): BoundingBox {\n const effectiveRadius = this.transformData.radius * this.transformData.scale;\n const diameter = effectiveRadius * 2;\n\n return {\n x: this.x - effectiveRadius,\n y: this.y - effectiveRadius,\n width: diameter,\n height: diameter,\n };\n }\n\n /**\n * Get visual bounding box (tight fit around actual text)\n * For circular text, calculate the arc bounds where text actually appears\n */\n getVisualBoundingBox(): BoundingBox {\n const effectiveRadius = this.transformData.radius * this.transformData.scale;\n const effectiveFontSize = this.fontSize * this.transformData.scale;\n\n // Measure text to know the arc span\n const actualTextWidth = measureTextWidth(this.text, effectiveFontSize, this.fontFamily, this.bold, this.italic);\n const arcAngle = actualTextWidth / effectiveRadius; // Radians\n\n // Text is centered at top (-PI/2), calculate bounds\n // The text extends arcAngle/2 on each side\n const startAngle = -Math.PI / 2 - arcAngle / 2;\n const endAngle = -Math.PI / 2 + arcAngle / 2;\n\n // Find min/max x and y for the text arc\n // Include some padding for character height (extend radius outward)\n // If circle is too small for the font, clamp innerRadius to a small positive value\n const innerRadius =\n effectiveRadius > effectiveFontSize / 2 ? effectiveRadius - effectiveFontSize / 2 : effectiveRadius * 0.1; // Use 10% of radius as minimum\n const outerRadius = effectiveRadius + effectiveFontSize / 2;\n\n let minX = Infinity,\n maxX = -Infinity;\n let minY = Infinity,\n maxY = -Infinity;\n\n // Sample points along the arc\n const samples = 10;\n for (let i = 0; i <= samples; i++) {\n const angle = startAngle + (endAngle - startAngle) * (i / samples);\n\n // Inner and outer points\n const innerX = this.x + Math.cos(angle) * innerRadius;\n const innerY = this.y + Math.sin(angle) * innerRadius;\n const outerX = this.x + Math.cos(angle) * outerRadius;\n const outerY = this.y + Math.sin(angle) * outerRadius;\n\n minX = Math.min(minX, innerX, outerX);\n maxX = Math.max(maxX, innerX, outerX);\n minY = Math.min(minY, innerY, outerY);\n maxY = Math.max(maxY, innerY, outerY);\n }\n\n return {\n x: minX,\n y: minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n /**\n * Render the circular text\n * Uses canvas arc path like SVG path in original\n * Now uses shared rendering function\n */\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n // Selection and hover visuals are handled by SelectionRenderer and HoverRenderer.\n\n // Apply element opacity\n const previousAlpha = ctx.globalAlpha;\n if (this.opacity !== undefined && this.opacity !== 1) {\n ctx.globalAlpha = this.opacity;\n }\n\n // Use shared rendering function for the text\n renderCircleTransform(ctx, this.toJSON() as unknown as SerializedTextTransformElement);\n\n // Restore previous alpha\n ctx.globalAlpha = previousAlpha;\n }\n\n /**\n * Handle resize\n * For circle, we maintain uniform scale and keep scale on the transform\n */\n resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): void {\n const circleStartData = startData.transformData as unknown as CircleTransformData;\n\n // Calculate uniform scale from average of width/height change\n const avgDimension = (newWidth + newHeight) / 2;\n const startAvgDimension = (startData.width! + startData.height!) / 2;\n const scaleChange = avgDimension / startAvgDimension;\n\n // Apply scale change relative to the starting scale\n const newScale = circleStartData.scale * scaleChange;\n\n // Clamp scale\n const clampedScale = Math.max(0.1, Math.min(10, newScale));\n this.transformData.scale = clampedScale;\n\n // Calculate new position to keep opposite corner fixed\n const bbox = this.getBoundingBox();\n const newPosition = calculateFixedCornerPosition(\n {\n x: startData.x - circleStartData.radius * circleStartData.scale,\n y: startData.y - circleStartData.radius * circleStartData.scale,\n width: circleStartData.radius * 2 * circleStartData.scale,\n height: circleStartData.radius * 2 * circleStartData.scale,\n },\n { width: bbox.width, height: bbox.height },\n anchor,\n this.rotation\n );\n\n if (newPosition) {\n // Position is top-left of bbox, but we need center\n this.x = newPosition.x + bbox.width / 2;\n this.y = newPosition.y + bbox.height / 2;\n }\n }\n\n /**\n * Get enabled anchors\n * Circle supports only corner handles (uniform scale)\n */\n getEnabledAnchors(): ResizeAnchor[] {\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n /**\n * Get effective font size (base fontSize * scale)\n * This is what the user sees\n */\n getEffectiveFontSize(): number {\n return Math.round(this.fontSize * this.transformData.scale * 10) / 10;\n }\n\n /**\n * Set effective font size (adjusts base fontSize to achieve desired effective size)\n */\n setEffectiveFontSize(targetSize: number): void {\n if (Math.abs(this.transformData.scale - 1) > 0.01) {\n // Calculate what base fontSize should be\n const newBaseFontSize = targetSize / this.transformData.scale;\n this.setFontSize(newBaseFontSize);\n } else {\n // Scale is 1, just set directly\n this.setFontSize(targetSize);\n }\n }\n\n /**\n * Serialize with transform data\n */\n toJSON(): CircleElementConfig {\n return {\n ...super.toJSON(),\n transformType: 'circle',\n transformData: {\n type: 'circle',\n radius: this.transformData.radius,\n scale: this.transformData.scale,\n reverse: this.transformData.reverse,\n },\n };\n }\n\n /**\n * Get transform start data\n */\n override getTransformStartData(): TransformStartData {\n const bbox = this.getBoundingBox();\n return {\n id: this.id,\n x: this.x,\n y: this.y,\n width: bbox.width,\n height: bbox.height,\n fontSize: this.fontSize,\n rotation: this.rotation,\n transformData: {\n type: 'circle',\n radius: this.transformData.radius,\n scale: this.transformData.scale,\n reverse: this.transformData.reverse,\n },\n };\n }\n}\n\nexport default CircleTransform;\n","/**\n * ArchTransform - Text curves upward in an arc\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { measureTextWidth } from '../core/GeometryUtils.js';\nimport { renderArchTransform, type SerializedTextTransformElement } from '../rendering/transform-renderer.js';\nimport type { ArchElementConfig, ArchTransformData, BoundingBox, ResizeAnchor, TransformStartData } from '../types/index.js';\n\nexport class ArchTransform extends TextElement {\n declare transformData: ArchTransformData;\n\n constructor(config: Partial<ArchElementConfig> = {}) {\n super(config);\n this.transformType = 'arch';\n\n // Arch transform data: archHeight controls the curve (-1 to 1)\n // Positive = arch up, Negative = arch down\n const data = config.transformData;\n this.transformData = {\n type: 'arch',\n archHeight: data?.archHeight ?? 0.5,\n width: data?.width ?? 200,\n };\n }\n\n getBoundingBox(): BoundingBox {\n const archHeightAbs = Math.abs(this.transformData.archHeight);\n const height = this.fontSize * (1.2 + archHeightAbs);\n\n // Positive arch goes upward (negative y direction)\n // Negative arch goes downward (positive y direction)\n const yOffset = this.transformData.archHeight > 0 ? -height : 0;\n\n return {\n x: this.x - this.transformData.width / 2,\n y: this.y + yOffset,\n width: this.transformData.width,\n height: height,\n };\n }\n\n getVisualBoundingBox(): BoundingBox {\n // For arch, we need to calculate bounds that encompass all skewed characters\n // We simulate the rendering to find the actual min/max positions\n const chars = this.text.split('');\n const charWidths = chars.map((char) => measureTextWidth(char, this.fontSize, this.fontFamily));\n const totalWidth = charWidths.reduce((sum, w) => sum + w, 0);\n const radius = this.transformData.width / 2;\n\n const skewFactor = 0.3;\n\n // Font metrics for character height\n const fontHeight = this.fontSize;\n const halfHeight = fontHeight / 2;\n\n let minX = Infinity;\n let maxX = -Infinity;\n let minY = Infinity;\n let maxY = -Infinity;\n\n // Simulate character positioning and skew transformation\n let currentX = -totalWidth / 2;\n chars.forEach((_char, i) => {\n const charWidth = charWidths[i];\n const centerX = currentX + charWidth / 2;\n const normalizedX = centerX / radius; // -1 to 1\n\n // Calculate y position on parabola\n const lineY = (Math.pow(normalizedX, 2) - 1) * this.transformData.archHeight * this.fontSize;\n\n // Calculate the slope of the curve at this point for skew\n const slope = 2 * normalizedX * this.transformData.archHeight;\n\n // Character corners before skew (relative to character center)\n const corners = [\n { x: -charWidth / 2, y: -halfHeight }, // top-left\n { x: charWidth / 2, y: -halfHeight }, // top-right\n { x: -charWidth / 2, y: halfHeight }, // bottom-left\n { x: charWidth / 2, y: halfHeight }, // bottom-right\n ];\n\n // Apply skew transformation: [1, slope*skewFactor, 0, 1] and translate\n corners.forEach((corner) => {\n // Skew transform: x' = x, y' = y + slope*skewFactor*x\n const skewedX = corner.x;\n const skewedY = corner.y + slope * skewFactor * corner.x;\n\n // Translate to world position\n const worldX = centerX + skewedX;\n const worldY = lineY + skewedY;\n\n minX = Math.min(minX, worldX);\n maxX = Math.max(maxX, worldX);\n minY = Math.min(minY, worldY);\n maxY = Math.max(maxY, worldY);\n });\n\n currentX += charWidth;\n });\n\n // Add a small padding for safety\n const padding = 2;\n minX -= padding;\n maxX += padding;\n minY -= padding;\n maxY += padding;\n\n return {\n x: this.x + minX,\n y: this.y + minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n // Selection and hover visuals are handled by SelectionRenderer and HoverRenderer.\n\n // Apply element opacity\n const previousAlpha = ctx.globalAlpha;\n if (this.opacity !== undefined && this.opacity !== 1) {\n ctx.globalAlpha = this.opacity;\n }\n\n // Use shared rendering function for the text\n renderArchTransform(ctx, this.toJSON() as unknown as SerializedTextTransformElement);\n\n // Restore previous alpha\n ctx.globalAlpha = previousAlpha;\n }\n\n resize(anchor: ResizeAnchor, newWidth: number, _newHeight: number, startData: TransformStartData): void {\n const isCornerHandle = anchor.includes('top') || anchor.includes('bottom');\n const isSideHandle = anchor.includes('middle');\n const archStartData = startData.transformData as ArchTransformData;\n\n if (isCornerHandle) {\n // Corner handle: uniform scale based on width change only (X-axis in local coords)\n //\n // Design decision: We use ONLY the width change (X-axis in local coordinates) to determine\n // the scale factor. This provides intuitive image-like scaling behavior where:\n // - Horizontal mouse movement controls the scale\n // - Aspect ratio is maintained (font size scales proportionally with width)\n // - The dragged corner follows the mouse cursor precisely\n //\n // For arch text, both fontSize and arch width scale together uniformly\n const scale = newWidth / startData.width!;\n\n // Apply uniform scale to font size\n const newFontSize = startData.fontSize! * scale;\n this.setFontSize(newFontSize);\n\n // Apply uniform scale to arch width\n const scaledWidth = archStartData.width * scale;\n this.transformData.width = Math.max(50, scaledWidth);\n\n // Position adjustment is handled by ResizeHandler's fixed corner logic\n // Don't adjust position here to avoid double adjustment\n } else if (isSideHandle) {\n // Side handle: only width changes (fontSize MUST stay the same)\n if (anchor === 'middle-left' || anchor === 'middle-right') {\n // CRITICAL: Do NOT change fontSize for side handles\n // Ensure fontSize stays exactly as it was\n this.fontSize = startData.fontSize!;\n\n // Update width only\n this.transformData.width = Math.max(50, newWidth);\n\n // For arch text, position (x,y) is at the CENTER of the arch (see render method)\n // When resizing with side handles, we want to keep the OPPOSITE edge fixed\n // This means the center point needs to move by half the width change\n // Use bbox width change, not transformData width change\n const deltaWidth = newWidth - startData.width!;\n const rotationRad = (this.rotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n if (anchor === 'middle-left') {\n // Left edge is moving, right edge should stay fixed\n // Center moves by -deltaWidth/2 (moves left in local coords)\n const centerShift = -deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n } else if (anchor === 'middle-right') {\n // Right edge is moving, left edge should stay fixed\n // Center moves by +deltaWidth/2 (moves right in local coords)\n const centerShift = deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n }\n }\n // Note: middle-top and middle-bottom don't affect arch dimensions\n // since height is determined by fontSize and archHeight\n }\n }\n\n getEnabledAnchors(): ResizeAnchor[] {\n // Arch supports only corner handles (no side handles)\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n toJSON(): ArchElementConfig {\n return {\n ...super.toJSON(),\n transformType: 'arch',\n transformData: {\n type: 'arch',\n archHeight: this.transformData.archHeight,\n width: this.transformData.width,\n },\n };\n }\n\n override getTransformStartData(): TransformStartData {\n const bbox = this.getBoundingBox();\n return {\n ...super.getTransformStartData(),\n width: bbox.width,\n height: bbox.height,\n transformData: {\n type: 'arch',\n archHeight: this.transformData.archHeight,\n width: this.transformData.width,\n },\n };\n }\n}\n\nexport default ArchTransform;\n","/**\n * Transform Default Values\n * Single source of truth for all transform default parameters\n */\n\n// Wave Transform Defaults\nexport const WAVE_DEFAULTS = {\n amplitude: 0.2, // 20% - subtle wave effect\n frequency: 1.2, // Smooth curve\n width: 200,\n} as const;\n\n// Flag Transform Defaults\nexport const FLAG_DEFAULTS = {\n amplitude: 0.2, // 20% - subtle flag wave\n frequency: 2, // Standard flag frequency\n width: 200,\n} as const;\n\n// Arch Transform Defaults\nexport const ARCH_DEFAULTS = {\n archHeight: 0.5, // 50% - centered arch\n width: 200,\n} as const;\n\n// Circle Transform Defaults\nexport const CIRCLE_DEFAULTS = {\n radius: 100,\n reverse: false,\n} as const;\n\n// Lean Transform Defaults\nexport const LEAN_DEFAULTS = {\n leanAmount: 0, // 0 - no lean by default\n width: 200,\n} as const;\n\n// Ascend Transform Defaults\nexport const ASCEND_DEFAULTS = {\n ascendAngle: -15, // -15° - nice upward ascent\n width: 200,\n} as const;\n\n// Custom Transform Defaults\nexport const CUSTOM_DEFAULTS = {\n width: 200,\n} as const;\n","/**\n * WaveTransform - Text follows a sine wave pattern\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { measureTextWidth } from '../core/GeometryUtils.js';\nimport { standardResize } from '../core/ResizeUtils.js';\nimport { renderWaveTransform, type SerializedTextTransformElement } from '../rendering/transform-renderer.js';\nimport { WAVE_SKEW_FACTOR } from '../constants.js';\nimport { WAVE_DEFAULTS } from './defaults.js';\nimport type { WaveElementConfig, WaveTransformData, BoundingBox, ResizeAnchor, TransformStartData } from '../types/index.js';\n\nexport class WaveTransform extends TextElement {\n declare transformData: WaveTransformData;\n\n constructor(config: Partial<WaveElementConfig> = {}) {\n super(config);\n this.transformType = 'wave';\n\n // Wave transform data: amplitude and frequency parameters\n const data = config.transformData;\n this.transformData = {\n type: 'wave',\n amplitude: data?.amplitude ?? WAVE_DEFAULTS.amplitude,\n frequency: data?.frequency ?? WAVE_DEFAULTS.frequency,\n width: data?.width ?? WAVE_DEFAULTS.width,\n };\n }\n\n getBoundingBox(): BoundingBox {\n const amplitudeAbs = Math.abs(this.transformData.amplitude);\n const height = this.fontSize * (1.2 + amplitudeAbs * 2);\n\n return {\n x: this.x - this.transformData.width / 2,\n y: this.y - height / 2,\n width: this.transformData.width,\n height: height,\n };\n }\n\n getVisualBoundingBox(): BoundingBox {\n // For wave, we need to calculate bounds that encompass all skewed characters\n const chars = this.text.split('');\n const charWidths = chars.map((char) => measureTextWidth(char, this.fontSize, this.fontFamily));\n const totalWidth = charWidths.reduce((sum, w) => sum + w, 0);\n\n // Font metrics for character height\n const fontHeight = this.fontSize;\n const halfHeight = fontHeight / 2;\n\n let minX = Infinity;\n let maxX = -Infinity;\n let minY = Infinity;\n let maxY = -Infinity;\n\n // Simulate character positioning and skew transformation\n let currentX = -totalWidth / 2;\n chars.forEach((_char, i) => {\n const charWidth = charWidths[i];\n const centerX = currentX + charWidth / 2;\n const normalizedX = centerX / (this.transformData.width / 2); // -1 to 1\n\n // Calculate y position on sine wave\n const lineY =\n this.transformData.amplitude * this.fontSize * Math.sin(this.transformData.frequency * Math.PI * normalizedX);\n\n // Calculate the slope of the sine wave at this point for skew\n const slope =\n this.transformData.amplitude *\n this.transformData.frequency *\n Math.PI *\n Math.cos(this.transformData.frequency * Math.PI * normalizedX);\n\n // Character corners before skew (relative to character center)\n const corners = [\n { x: -charWidth / 2, y: -halfHeight }, // top-left\n { x: charWidth / 2, y: -halfHeight }, // top-right\n { x: -charWidth / 2, y: halfHeight }, // bottom-left\n { x: charWidth / 2, y: halfHeight }, // bottom-right\n ];\n\n // Apply skew transformation: [1, slope*WAVE_SKEW_FACTOR, 0, 1] and translate\n corners.forEach((corner) => {\n // Skew transform: x' = x, y' = y + slope*WAVE_SKEW_FACTOR*x\n const skewedX = corner.x;\n const skewedY = corner.y + slope * WAVE_SKEW_FACTOR * corner.x;\n\n // Translate to world position\n const worldX = centerX + skewedX;\n const worldY = lineY + skewedY;\n\n minX = Math.min(minX, worldX);\n maxX = Math.max(maxX, worldX);\n minY = Math.min(minY, worldY);\n maxY = Math.max(maxY, worldY);\n });\n\n currentX += charWidth;\n });\n\n // Add a small padding for safety\n const padding = 2;\n minX -= padding;\n maxX += padding;\n minY -= padding;\n maxY += padding;\n\n return {\n x: this.x + minX,\n y: this.y + minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n // Selection and hover visuals are handled by SelectionRenderer and HoverRenderer.\n\n // Apply element opacity\n const previousAlpha = ctx.globalAlpha;\n if (this.opacity !== undefined && this.opacity !== 1) {\n ctx.globalAlpha = this.opacity;\n }\n\n // Use shared rendering function for the text\n renderWaveTransform(ctx, this.toJSON() as unknown as SerializedTextTransformElement);\n\n // Restore previous alpha\n ctx.globalAlpha = previousAlpha;\n }\n\n resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): void {\n standardResize(this, anchor, newWidth, newHeight, startData);\n }\n\n getEnabledAnchors(): ResizeAnchor[] {\n // Wave supports only corner handles (no side handles)\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n /**\n * Serialize with transform data\n */\n toJSON(): WaveElementConfig {\n return {\n ...super.toJSON(),\n transformType: 'wave',\n transformData: {\n type: 'wave',\n amplitude: this.transformData.amplitude,\n frequency: this.transformData.frequency,\n width: this.transformData.width,\n },\n };\n }\n}\n\nexport default WaveTransform;\n","/**\n * FlagTransform - Text waves from the center point outward like a flag\n * Similar to wave but amplitude increases from center to edges\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { measureTextWidth } from '../core/GeometryUtils.js';\nimport { renderFlagTransform, type SerializedTextTransformElement } from '../rendering/transform-renderer.js';\nimport { FLAG_DEFAULTS } from './defaults.js';\nimport type { FlagElementConfig, FlagTransformData, BoundingBox, ResizeAnchor, TransformStartData } from '../types/index.js';\n\nexport class FlagTransform extends TextElement {\n declare transformData: FlagTransformData;\n\n constructor(config: Partial<FlagElementConfig> = {}) {\n super(config);\n this.transformType = 'flag';\n\n // Flag transform data: amplitude and frequency parameters\n // Amplitude increases from center to edges\n const data = config.transformData;\n this.transformData = {\n type: 'flag',\n amplitude: data?.amplitude ?? FLAG_DEFAULTS.amplitude,\n frequency: data?.frequency ?? FLAG_DEFAULTS.frequency,\n width: data?.width ?? FLAG_DEFAULTS.width,\n };\n }\n\n getBoundingBox(): BoundingBox {\n const amplitudeAbs = Math.abs(this.transformData.amplitude);\n const height = this.fontSize * (1.2 + amplitudeAbs * 2);\n\n return {\n x: this.x - this.transformData.width / 2,\n y: this.y - height / 2,\n width: this.transformData.width,\n height: height,\n };\n }\n\n getVisualBoundingBox(): BoundingBox {\n // For flag, we need to calculate bounds that encompass all skewed characters\n const chars = this.text.split('');\n const charWidths = chars.map((char) => measureTextWidth(char, this.fontSize, this.fontFamily));\n const totalWidth = charWidths.reduce((sum, w) => sum + w, 0);\n\n const skewFactor = 0.3;\n\n // Font metrics for character height\n const fontHeight = this.fontSize;\n const halfHeight = fontHeight / 2;\n\n let minX = Infinity;\n let maxX = -Infinity;\n let minY = Infinity;\n let maxY = -Infinity;\n\n // Simulate character positioning and skew transformation\n let currentX = -totalWidth / 2;\n chars.forEach((_char, i) => {\n const charWidth = charWidths[i];\n const centerX = currentX + charWidth / 2;\n const normalizedX = centerX / (this.transformData.width / 2); // -1 to 1\n\n // Flag effect: amplitude increases from center to edges\n const distanceFromCenter = Math.abs(normalizedX);\n const flagAmplitude = this.transformData.amplitude * distanceFromCenter;\n\n // Calculate y position on flag wave\n const lineY = flagAmplitude * this.fontSize * Math.sin(this.transformData.frequency * Math.PI * normalizedX);\n\n // Calculate the slope for skew\n const cosComponent =\n flagAmplitude *\n this.transformData.frequency *\n Math.PI *\n Math.cos(this.transformData.frequency * Math.PI * normalizedX);\n const sinComponent =\n this.transformData.amplitude *\n Math.sign(normalizedX) *\n Math.sin(this.transformData.frequency * Math.PI * normalizedX);\n const slope = cosComponent + sinComponent;\n\n // Character corners before skew (relative to character center)\n const corners = [\n { x: -charWidth / 2, y: -halfHeight }, // top-left\n { x: charWidth / 2, y: -halfHeight }, // top-right\n { x: -charWidth / 2, y: halfHeight }, // bottom-left\n { x: charWidth / 2, y: halfHeight }, // bottom-right\n ];\n\n // Apply skew transformation: [1, slope*skewFactor, 0, 1] and translate\n corners.forEach((corner) => {\n // Skew transform: x' = x, y' = y + slope*skewFactor*x\n const skewedX = corner.x;\n const skewedY = corner.y + slope * skewFactor * corner.x;\n\n // Translate to world position\n const worldX = centerX + skewedX;\n const worldY = lineY + skewedY;\n\n minX = Math.min(minX, worldX);\n maxX = Math.max(maxX, worldX);\n minY = Math.min(minY, worldY);\n maxY = Math.max(maxY, worldY);\n });\n\n currentX += charWidth;\n });\n\n // Add a small padding for safety\n const padding = 2;\n minX -= padding;\n maxX += padding;\n minY -= padding;\n maxY += padding;\n\n return {\n x: this.x + minX,\n y: this.y + minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n // Selection and hover visuals are handled by SelectionRenderer and HoverRenderer.\n\n // Apply element opacity\n const previousAlpha = ctx.globalAlpha;\n if (this.opacity !== undefined && this.opacity !== 1) {\n ctx.globalAlpha = this.opacity;\n }\n\n // Use shared rendering function for the text\n renderFlagTransform(ctx, this.toJSON() as unknown as SerializedTextTransformElement);\n\n // Restore previous alpha\n ctx.globalAlpha = previousAlpha;\n }\n\n resize(anchor: ResizeAnchor, newWidth: number, _newHeight: number, startData: TransformStartData): void {\n const isCornerHandle = anchor.includes('top') || anchor.includes('bottom');\n const isSideHandle = anchor.includes('middle');\n const flagStartData = startData.transformData as FlagTransformData;\n\n if (isCornerHandle) {\n // Corner handle: uniform scale\n const scale = newWidth / startData.width!;\n\n const newFontSize = startData.fontSize! * scale;\n this.setFontSize(newFontSize);\n\n const scaledWidth = flagStartData.width * scale;\n this.transformData.width = Math.max(50, scaledWidth);\n } else if (isSideHandle) {\n // Side handle: only width changes\n if (anchor === 'middle-left' || anchor === 'middle-right') {\n this.fontSize = startData.fontSize!;\n this.transformData.width = Math.max(50, newWidth);\n\n // Use bbox width change, not transformData width change\n const deltaWidth = newWidth - startData.width!;\n const rotationRad = (this.rotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n if (anchor === 'middle-left') {\n const centerShift = -deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n } else if (anchor === 'middle-right') {\n const centerShift = deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n }\n }\n }\n }\n\n getEnabledAnchors(): ResizeAnchor[] {\n // Flag supports only corner handles (no side handles)\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n toJSON(): FlagElementConfig {\n return {\n ...super.toJSON(),\n transformType: 'flag',\n transformData: {\n type: 'flag',\n amplitude: this.transformData.amplitude,\n frequency: this.transformData.frequency,\n width: this.transformData.width,\n },\n };\n }\n\n override getTransformStartData(): TransformStartData {\n const bbox = this.getBoundingBox();\n return {\n ...super.getTransformStartData(),\n width: bbox.width,\n height: bbox.height,\n transformData: {\n type: 'flag',\n amplitude: this.transformData.amplitude,\n frequency: this.transformData.frequency,\n width: this.transformData.width,\n },\n };\n }\n}\n\nexport default FlagTransform;\n","/**\n * LeanTransform - Text with slanted/leaning effect\n * Creates a skewed perspective\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { measureTextWidth } from '../core/GeometryUtils.js';\nimport { renderLeanTransform, type SerializedTextTransformElement } from '../rendering/transform-renderer.js';\nimport type { LeanElementConfig, LeanTransformData, BoundingBox, ResizeAnchor, TransformStartData } from '../types/index.js';\n\nexport class LeanTransform extends TextElement {\n declare transformData: LeanTransformData;\n\n constructor(config: Partial<LeanElementConfig> = {}) {\n super(config);\n this.transformType = 'lean';\n\n // Lean transform data: controls the slant (-1 to 1)\n // Positive = slant left, Negative = slant right\n const data = config.transformData;\n this.transformData = {\n type: 'lean',\n leanAmount: data?.leanAmount ?? (data as unknown as Record<string, number> | undefined)?.angleAmount ?? 0,\n width: data?.width ?? 200,\n };\n }\n\n getBoundingBox(): BoundingBox {\n const leanAmountAbs = Math.abs(this.transformData.leanAmount);\n const height = this.fontSize * (1.2 + leanAmountAbs * 0.5);\n\n return {\n x: this.x - this.transformData.width / 2,\n y: this.y - height / 2,\n width: this.transformData.width,\n height: height,\n };\n }\n\n getVisualBoundingBox(): BoundingBox {\n // For lean, the entire text block is skewed, so we need to calculate the transformed bounds\n const actualTextWidth = measureTextWidth(this.text, this.fontSize, this.fontFamily);\n const width = actualTextWidth;\n const height = this.fontSize * 1.2;\n\n // Calculate the skew transformation matrix applied in render()\n // Negate to match the reversed direction in render()\n const skewAngle = (-this.transformData.leanAmount * Math.PI) / 4;\n const skewX = Math.tan(skewAngle);\n\n // Define the four corners of the text block before skew (centered)\n const corners = [\n { x: -width / 2, y: -height / 2 }, // top-left\n { x: width / 2, y: -height / 2 }, // top-right\n { x: -width / 2, y: height / 2 }, // bottom-left\n { x: width / 2, y: height / 2 }, // bottom-right\n ];\n\n let minX = Infinity;\n let maxX = -Infinity;\n let minY = Infinity;\n let maxY = -Infinity;\n\n // Apply skew transformation [1, 0, tan(skewAngle), 1] to each corner\n corners.forEach((corner) => {\n // Skew transform: x' = x + tan(skewAngle)*y, y' = y\n const skewedX = corner.x + skewX * corner.y;\n const skewedY = corner.y;\n\n minX = Math.min(minX, skewedX);\n maxX = Math.max(maxX, skewedX);\n minY = Math.min(minY, skewedY);\n maxY = Math.max(maxY, skewedY);\n });\n\n // Add a small padding for safety\n const padding = 2;\n minX -= padding;\n maxX += padding;\n minY -= padding;\n maxY += padding;\n\n return {\n x: this.x + minX,\n y: this.y + minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n // Selection and hover visuals are handled by SelectionRenderer and HoverRenderer.\n\n // Apply element opacity\n const previousAlpha = ctx.globalAlpha;\n if (this.opacity !== undefined && this.opacity !== 1) {\n ctx.globalAlpha = this.opacity;\n }\n\n // Use shared rendering function for the text\n renderLeanTransform(ctx, this.toJSON() as unknown as SerializedTextTransformElement);\n\n // Restore previous alpha\n ctx.globalAlpha = previousAlpha;\n }\n\n resize(anchor: ResizeAnchor, newWidth: number, _newHeight: number, startData: TransformStartData): void {\n const isCornerHandle = anchor.includes('top') || anchor.includes('bottom');\n const isSideHandle = anchor.includes('middle');\n const leanStartData = startData.transformData as LeanTransformData;\n\n if (isCornerHandle) {\n // Corner handle: uniform scale\n const scale = newWidth / startData.width!;\n\n const newFontSize = startData.fontSize! * scale;\n this.setFontSize(newFontSize);\n\n const scaledWidth = leanStartData.width * scale;\n this.transformData.width = Math.max(50, scaledWidth);\n } else if (isSideHandle) {\n // Side handle: only width changes\n if (anchor === 'middle-left' || anchor === 'middle-right') {\n this.fontSize = startData.fontSize!;\n this.transformData.width = Math.max(50, newWidth);\n\n const deltaWidth = newWidth - leanStartData.width;\n const rotationRad = (this.rotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n if (anchor === 'middle-left') {\n const centerShift = -deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n } else if (anchor === 'middle-right') {\n const centerShift = deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n }\n }\n }\n }\n\n getEnabledAnchors(): ResizeAnchor[] {\n // Lean transform applies a uniform skew to the entire text block\n // Only corner handles make sense for uniform scaling\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n toJSON(): LeanElementConfig {\n return {\n ...super.toJSON(),\n transformType: 'lean',\n transformData: {\n type: 'lean',\n leanAmount: this.transformData.leanAmount,\n width: this.transformData.width,\n },\n };\n }\n\n override getTransformStartData(): TransformStartData {\n const bbox = this.getBoundingBox();\n return {\n ...super.getTransformStartData(),\n width: bbox.width,\n height: bbox.height,\n transformData: {\n type: 'lean',\n leanAmount: this.transformData.leanAmount,\n width: this.transformData.width,\n },\n };\n }\n}\n\nexport default LeanTransform;\n","/**\n * AscendTransform - Text ascends along a straight diagonal line at ~30 degrees\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { measureTextWidth } from '../core/GeometryUtils.js';\nimport { renderAscendTransform, type SerializedTextTransformElement } from '../rendering/transform-renderer.js';\nimport type { AscendElementConfig, AscendTransformData, BoundingBox, ResizeAnchor, TransformStartData } from '../types/index.js';\n\nexport class AscendTransform extends TextElement {\n declare transformData: AscendTransformData;\n\n constructor(config: Partial<AscendElementConfig> = {}) {\n super(config);\n this.transformType = 'ascend';\n\n // Ascend transform data: angle in degrees\n // Internal: -30° (max up) to +30° (max down)\n // Display: +100% (max up) to -100% (max down)\n const data = config.transformData;\n this.transformData = {\n type: 'ascend',\n ascendAngle: data?.ascendAngle ?? -15,\n width: data?.width ?? 200,\n };\n }\n\n getBoundingBox(): BoundingBox {\n const angleRad = (this.transformData.ascendAngle * Math.PI) / 180;\n const width = this.transformData.width;\n\n // Calculate the vertical rise based on the width and angle\n const rise = width * Math.tan(angleRad);\n\n // Account for font size in height calculation\n const height = Math.abs(rise) + this.fontSize * 1.2;\n\n // Determine y offset based on angle direction\n const yOffset = rise > 0 ? -height : 0;\n\n return {\n x: this.x - width / 2,\n y: this.y + yOffset,\n width: width,\n height: height,\n };\n }\n\n getVisualBoundingBox(): BoundingBox {\n // For ascend, we need to calculate bounds that encompass all skewed characters\n // We simulate the rendering to find the actual min/max positions\n const chars = this.text.split('');\n const charWidths = chars.map((char) => measureTextWidth(char, this.fontSize, this.fontFamily));\n const totalWidth = charWidths.reduce((sum, w) => sum + w, 0);\n\n const angleRad = (this.transformData.ascendAngle * Math.PI) / 180;\n const slope = Math.tan(angleRad);\n const skewFactor = 1.0;\n\n // Font metrics for character height\n const fontHeight = this.fontSize;\n const halfHeight = fontHeight / 2;\n\n let minX = Infinity;\n let maxX = -Infinity;\n let minY = Infinity;\n let maxY = -Infinity;\n\n // Simulate character positioning and skew transformation\n let currentX = -totalWidth / 2;\n chars.forEach((_char, i) => {\n const charWidth = charWidths[i];\n const centerX = currentX + charWidth / 2;\n\n // Position on diagonal line\n const lineY = centerX * slope;\n\n // Character corners before skew (relative to character center)\n const corners = [\n { x: -charWidth / 2, y: -halfHeight }, // top-left\n { x: charWidth / 2, y: -halfHeight }, // top-right\n { x: -charWidth / 2, y: halfHeight }, // bottom-left\n { x: charWidth / 2, y: halfHeight }, // bottom-right\n ];\n\n // Apply skew transformation: [1, slope*skewFactor, 0, 1] and translate\n corners.forEach((corner) => {\n // Skew transform: x' = x, y' = y + slope*skewFactor*x\n const skewedX = corner.x;\n const skewedY = corner.y + slope * skewFactor * corner.x;\n\n // Translate to world position\n const worldX = centerX + skewedX;\n const worldY = lineY + skewedY;\n\n minX = Math.min(minX, worldX);\n maxX = Math.max(maxX, worldX);\n minY = Math.min(minY, worldY);\n maxY = Math.max(maxY, worldY);\n });\n\n currentX += charWidth;\n });\n\n // Add a small padding for safety\n const padding = 2;\n minX -= padding;\n maxX += padding;\n minY -= padding;\n maxY += padding;\n\n return {\n x: this.x + minX,\n y: this.y + minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n // Selection and hover visuals are handled by SelectionRenderer and HoverRenderer.\n\n // Apply element opacity\n const previousAlpha = ctx.globalAlpha;\n if (this.opacity !== undefined && this.opacity !== 1) {\n ctx.globalAlpha = this.opacity;\n }\n\n // Use shared rendering function for the text\n renderAscendTransform(ctx, this.toJSON() as unknown as SerializedTextTransformElement);\n\n // Restore previous alpha\n ctx.globalAlpha = previousAlpha;\n }\n\n resize(anchor: ResizeAnchor, newWidth: number, _newHeight: number, startData: TransformStartData): void {\n const isCornerHandle = anchor.includes('top') || anchor.includes('bottom');\n const isSideHandle = anchor.includes('middle');\n const ascendStartData = startData.transformData as AscendTransformData;\n\n if (isCornerHandle) {\n // Corner handle: uniform scale based on width change\n const scale = newWidth / startData.width!;\n\n // Apply uniform scale to font size\n const newFontSize = startData.fontSize! * scale;\n this.setFontSize(newFontSize);\n\n // Apply uniform scale to width\n const scaledWidth = ascendStartData.width * scale;\n this.transformData.width = Math.max(50, scaledWidth);\n } else if (isSideHandle) {\n // Side handle: only width changes (fontSize stays the same)\n if (anchor === 'middle-left' || anchor === 'middle-right') {\n // Keep fontSize constant\n this.fontSize = startData.fontSize!;\n\n // Update width only\n this.transformData.width = Math.max(50, newWidth);\n\n // Adjust center position to keep opposite edge fixed\n // Use bbox width change, not transformData width change\n const deltaWidth = newWidth - startData.width!;\n const rotationRad = (this.rotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n if (anchor === 'middle-left') {\n // Left edge is moving, right edge should stay fixed\n const centerShift = -deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n } else if (anchor === 'middle-right') {\n // Right edge is moving, left edge should stay fixed\n const centerShift = deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n }\n }\n }\n }\n\n getEnabledAnchors(): ResizeAnchor[] {\n // Ascend supports only corner handles (no side handles)\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n toJSON(): AscendElementConfig {\n return {\n ...super.toJSON(),\n transformType: 'ascend',\n transformData: {\n type: 'ascend',\n ascendAngle: this.transformData.ascendAngle,\n width: this.transformData.width,\n },\n };\n }\n\n override getTransformStartData(): TransformStartData {\n const bbox = this.getBoundingBox();\n return {\n ...super.getTransformStartData(),\n width: bbox.width,\n height: bbox.height,\n transformData: {\n type: 'ascend',\n ascendAngle: this.transformData.ascendAngle,\n width: this.transformData.width,\n },\n };\n }\n}\n\nexport default AscendTransform;\n","/**\n * ShapeElement - Element for displaying geometric shapes\n * Supports rectangles, circles, ellipses, triangles, polygons, stars, and lines\n */\n\nimport { BaseElement } from './BaseElement.js';\nimport { RotationUtils } from './RotationUtils.js';\nimport { getThemeShapeFillColor } from '../constants.js';\nimport type {\n ShapeTransformData,\n ShapeElementConfig,\n ResizeAnchor,\n BoundingBox,\n Point,\n ShapeType,\n TransformStartData,\n} from '../types/index.js';\n\nexport class ShapeElement extends BaseElement {\n declare transformType: 'shape';\n declare transformData: ShapeTransformData;\n\n constructor(config: Partial<ShapeElementConfig> = {}) {\n super(config);\n this.transformType = 'shape';\n\n // Initialize transformData with proper ShapeTransformData type\n const defaultShapeType: ShapeType = 'rectangle';\n this.transformData = {\n type: 'shape',\n shapeType: config.transformData?.shapeType || defaultShapeType,\n width: config.transformData?.width || 200,\n height: config.transformData?.height || 200,\n borderRadius: config.transformData?.borderRadius ?? 3, // Default 3% radius (~6px on 200x200 shape, matches hover border)\n radiusX: config.transformData?.radiusX,\n radiusY: config.transformData?.radiusY,\n sides: config.transformData?.sides ?? 5,\n points: config.transformData?.points ?? 5,\n innerRadius: config.transformData?.innerRadius ?? 0.4,\n fillColor: config.transformData?.fillColor || getThemeShapeFillColor(),\n fillOpacity: config.transformData?.fillOpacity ?? 1,\n };\n }\n\n /**\n * Get bounding box in world coordinates\n * Returns the unrotated dimensions - rotation is handled by the renderer\n */\n getBoundingBox(): BoundingBox {\n return {\n x: this.x - this.transformData.width / 2,\n y: this.y - this.transformData.height / 2,\n width: this.transformData.width,\n height: this.transformData.height,\n };\n }\n\n /**\n * Tight visual bounding box for selection chrome.\n *\n * `getBoundingBox()` returns the conceptual `width × height` rect\n * the resize handles act on. Many shape types (circle, polygon,\n * star) render *inscribed* in that rect — their visible extent is\n * smaller than the box, leaving empty corners. The selection\n * border / handles use this tighter bbox so it hugs what the user\n * actually sees instead of including the empty padding.\n *\n * For shapes that fill their box (rectangle, ellipse, triangle,\n * line) this falls through to `getBoundingBox()`.\n *\n * Stroke is intentionally excluded — same convention as\n * `getBoundingBox()`. The chrome aligns with the *fill* edge.\n */\n getVisualBoundingBox(): BoundingBox {\n const { shapeType, width, height } = this.transformData;\n\n // Circle: inscribed in min(width, height) square. Tight bbox is\n // a square of side `min(w,h)` centered on (x, y).\n if (shapeType === 'circle') {\n const r = Math.min(width, height) / 2;\n return {\n x: this.x - r,\n y: this.y - r,\n width: r * 2,\n height: r * 2,\n };\n }\n\n // Polygon / star: inscribed in a circle of radius `min(w,h)/2`.\n // Walk the same vertex sequence the renderer uses to get the\n // exact tight bbox. Star alternates outer / inner radii — both\n // contribute to the bounds.\n if (shapeType === 'polygon' || shapeType === 'star') {\n const r = Math.min(width, height) / 2;\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n\n if (shapeType === 'polygon') {\n const sides = this.transformData.sides || 5;\n for (let i = 0; i < sides; i++) {\n const angle = (i * 2 * Math.PI) / sides - Math.PI / 2;\n const px = r * Math.cos(angle);\n const py = r * Math.sin(angle);\n if (px < minX) minX = px;\n if (px > maxX) maxX = px;\n if (py < minY) minY = py;\n if (py > maxY) maxY = py;\n }\n } else {\n const points = this.transformData.points || 5;\n const outerRadius = r;\n const innerRadius = outerRadius * (this.transformData.innerRadius ?? 0.4);\n for (let i = 0; i < points * 2; i++) {\n const angle = (i * Math.PI) / points - Math.PI / 2;\n const rad = i % 2 === 0 ? outerRadius : innerRadius;\n const px = rad * Math.cos(angle);\n const py = rad * Math.sin(angle);\n if (px < minX) minX = px;\n if (px > maxX) maxX = px;\n if (py < minY) minY = py;\n if (py > maxY) maxY = py;\n }\n }\n\n return {\n x: this.x + minX,\n y: this.y + minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n // Rectangle, rounded-rect, ellipse, triangle, line — these fill\n // their conceptual box, so the standard bbox is already tight.\n return this.getBoundingBox();\n }\n\n /**\n * Get rotation anchor point (center of the shape)\n */\n getRotationAnchor(): Point {\n return { x: this.x, y: this.y };\n }\n\n /**\n * Render the shape\n */\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n const elementOpacity = this.opacity ?? 1;\n const hasStroke = !!this.stroke?.enabled;\n\n // When element opacity < 1 AND stroke is present, render fill+stroke\n // at full opacity on a temp canvas to avoid compounding opacity where\n // fill and stroke overlap (e.g., 50% + 50% ≠ 75%).\n if (elementOpacity < 1 && hasStroke) {\n this.renderWithOffscreen(ctx, elementOpacity);\n return;\n }\n\n ctx.save();\n\n // Translate to shape center and rotate\n ctx.translate(this.x, this.y);\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n\n // Set fill style with combined opacity (element opacity * fill opacity)\n ctx.fillStyle = this.transformData.fillColor || '#3b82f6';\n const fillOpacity = this.transformData.fillOpacity ?? 1;\n ctx.globalAlpha = elementOpacity * fillOpacity;\n\n // Render the specific shape (fill)\n this.renderShape(ctx);\n\n ctx.restore();\n\n // Render stroke if enabled (after restore so we get a fresh transform state)\n if (hasStroke) {\n this.renderStroke(ctx, elementOpacity);\n }\n }\n\n /**\n * Render fill+stroke to a temporary canvas at full opacity, then composite\n * at element opacity. Prevents fill/stroke overlap from compounding.\n */\n private renderWithOffscreen(ctx: CanvasRenderingContext2D, elementOpacity: number): void {\n const { width, height } = this.transformData;\n const strokeWidth = this.stroke?.width || 2;\n const padding = strokeWidth + 2;\n const offW = Math.ceil(width + padding * 2);\n const offH = Math.ceil(height + padding * 2);\n\n const offCanvas = document.createElement('canvas');\n offCanvas.width = offW;\n offCanvas.height = offH;\n const offCtx = offCanvas.getContext('2d');\n if (!offCtx) {\n // Fallback: render directly (with compounding)\n this.renderDirect(ctx, elementOpacity);\n return;\n }\n\n // Render fill+stroke at full opacity, centered on the temp canvas\n offCtx.save();\n offCtx.translate(offW / 2, offH / 2);\n\n // Fill\n offCtx.fillStyle = this.transformData.fillColor || '#3b82f6';\n const fillOpacity = this.transformData.fillOpacity ?? 1;\n offCtx.globalAlpha = fillOpacity;\n offCtx.beginPath();\n this.traceShapePath(offCtx);\n offCtx.fill();\n offCtx.restore();\n\n // Stroke\n offCtx.save();\n offCtx.translate(offW / 2, offH / 2);\n const stroke = this.stroke!;\n offCtx.strokeStyle = stroke.color || '#000000';\n offCtx.lineWidth = stroke.width || 2;\n offCtx.lineCap = stroke.lineCap || 'round';\n offCtx.lineJoin = stroke.lineJoin || 'round';\n offCtx.globalAlpha = stroke.opacity ?? 1;\n if (stroke.dashArray && stroke.dashArray.length > 0) {\n offCtx.setLineDash(stroke.dashArray);\n }\n offCtx.beginPath();\n this.traceShapePath(offCtx);\n offCtx.stroke();\n offCtx.restore();\n\n // Composite onto main canvas at element opacity\n ctx.save();\n ctx.translate(this.x, this.y);\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n ctx.globalAlpha = elementOpacity;\n ctx.drawImage(offCanvas, -offW / 2, -offH / 2);\n ctx.restore();\n }\n\n /**\n * Direct render (no offscreen) — used as fallback\n */\n private renderDirect(ctx: CanvasRenderingContext2D, elementOpacity: number): void {\n ctx.save();\n ctx.translate(this.x, this.y);\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n ctx.fillStyle = this.transformData.fillColor || '#3b82f6';\n ctx.globalAlpha = elementOpacity * (this.transformData.fillOpacity ?? 1);\n this.renderShape(ctx);\n ctx.restore();\n\n if (this.stroke?.enabled) {\n this.renderStroke(ctx, elementOpacity);\n }\n }\n\n /**\n * Trace the shape path without filling or stroking — used by offscreen render\n */\n private traceShapePath(ctx: CanvasRenderingContext2D): void {\n const { shapeType, width, height } = this.transformData;\n\n switch (shapeType) {\n case 'rectangle': {\n const borderRadius = this.transformData.borderRadius || 0;\n const x = -width / 2;\n const y = -height / 2;\n if (borderRadius > 0) {\n const radius = Math.min((borderRadius / 100) * Math.min(width, height), width / 2, height / 2);\n ctx.roundRect(x, y, width, height, radius);\n } else {\n ctx.rect(x, y, width, height);\n }\n break;\n }\n case 'circle': {\n const radius = Math.min(width, height) / 2;\n ctx.arc(0, 0, radius, 0, Math.PI * 2);\n break;\n }\n case 'ellipse':\n ctx.ellipse(0, 0, width / 2, height / 2, 0, 0, Math.PI * 2);\n break;\n case 'triangle': {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n ctx.moveTo(0, -halfHeight);\n ctx.lineTo(halfWidth, halfHeight);\n ctx.lineTo(-halfWidth, halfHeight);\n ctx.closePath();\n break;\n }\n case 'polygon': {\n const sides = this.transformData.sides || 5;\n const r = Math.min(width, height) / 2;\n for (let i = 0; i < sides; i++) {\n const angle = (i * 2 * Math.PI) / sides - Math.PI / 2;\n const px = r * Math.cos(angle);\n const py = r * Math.sin(angle);\n if (i === 0) ctx.moveTo(px, py);\n else ctx.lineTo(px, py);\n }\n ctx.closePath();\n break;\n }\n case 'star': {\n const points = this.transformData.points || 5;\n const outerRadius = Math.min(width, height) / 2;\n const innerRadius = outerRadius * (this.transformData.innerRadius || 0.4);\n for (let i = 0; i < points * 2; i++) {\n const angle = (i * Math.PI) / points - Math.PI / 2;\n const r = i % 2 === 0 ? outerRadius : innerRadius;\n const px = r * Math.cos(angle);\n const py = r * Math.sin(angle);\n if (i === 0) ctx.moveTo(px, py);\n else ctx.lineTo(px, py);\n }\n ctx.closePath();\n break;\n }\n case 'line': {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n ctx.rect(-halfWidth, -halfHeight, width, height);\n break;\n }\n default:\n ctx.rect(-width / 2, -height / 2, width, height);\n }\n }\n\n /**\n * Render the stroke for this shape element\n */\n private renderStroke(ctx: CanvasRenderingContext2D, elementOpacity: number): void {\n ctx.save();\n\n ctx.translate(this.x, this.y);\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n\n const stroke = this.stroke!;\n ctx.strokeStyle = stroke.color || '#000000';\n ctx.lineWidth = stroke.width || 2;\n ctx.lineCap = stroke.lineCap || 'round';\n ctx.lineJoin = stroke.lineJoin || 'round';\n ctx.globalAlpha = elementOpacity * (stroke.opacity ?? 1);\n\n if (stroke.dashArray && stroke.dashArray.length > 0) {\n ctx.setLineDash(stroke.dashArray);\n }\n\n // Recreate the shape path for stroking\n const { shapeType, width, height } = this.transformData;\n ctx.beginPath();\n\n switch (shapeType) {\n case 'rectangle': {\n const borderRadius = this.transformData.borderRadius || 0;\n const x = -width / 2;\n const y = -height / 2;\n if (borderRadius > 0) {\n const radius = Math.min((borderRadius / 100) * Math.min(width, height), width / 2, height / 2);\n ctx.roundRect(x, y, width, height, radius);\n } else {\n ctx.rect(x, y, width, height);\n }\n break;\n }\n case 'circle': {\n const radius = Math.min(width, height) / 2;\n ctx.arc(0, 0, radius, 0, Math.PI * 2);\n break;\n }\n case 'ellipse':\n ctx.ellipse(0, 0, width / 2, height / 2, 0, 0, Math.PI * 2);\n break;\n case 'triangle': {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n ctx.moveTo(0, -halfHeight);\n ctx.lineTo(halfWidth, halfHeight);\n ctx.lineTo(-halfWidth, halfHeight);\n ctx.closePath();\n break;\n }\n case 'polygon': {\n const sides = this.transformData.sides || 5;\n const radius = Math.min(width, height) / 2;\n for (let i = 0; i < sides; i++) {\n const angle = (i * 2 * Math.PI) / sides - Math.PI / 2;\n const px = radius * Math.cos(angle);\n const py = radius * Math.sin(angle);\n if (i === 0) ctx.moveTo(px, py);\n else ctx.lineTo(px, py);\n }\n ctx.closePath();\n break;\n }\n case 'star': {\n const points = this.transformData.points || 5;\n const outerRadius = Math.min(width, height) / 2;\n const innerRadius = outerRadius * (this.transformData.innerRadius || 0.4);\n for (let i = 0; i < points * 2; i++) {\n const angle = (i * Math.PI) / points - Math.PI / 2;\n const r = i % 2 === 0 ? outerRadius : innerRadius;\n const px = r * Math.cos(angle);\n const py = r * Math.sin(angle);\n if (i === 0) ctx.moveTo(px, py);\n else ctx.lineTo(px, py);\n }\n ctx.closePath();\n break;\n }\n case 'line': {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n ctx.rect(-halfWidth, -halfHeight, width, height);\n break;\n }\n default:\n ctx.rect(-width / 2, -height / 2, width, height);\n }\n\n ctx.stroke();\n ctx.restore();\n }\n\n /**\n * Render the specific shape based on shapeType\n */\n private renderShape(ctx: CanvasRenderingContext2D): void {\n const { shapeType, width, height } = this.transformData;\n\n ctx.beginPath();\n\n switch (shapeType) {\n case 'rectangle':\n this.renderRectangle(ctx, width, height);\n break;\n case 'circle':\n this.renderCircle(ctx, Math.min(width, height) / 2);\n break;\n case 'ellipse':\n this.renderEllipse(ctx, width / 2, height / 2);\n break;\n case 'triangle':\n this.renderTriangle(ctx, width, height);\n break;\n case 'polygon':\n this.renderPolygon(ctx, Math.min(width, height) / 2);\n break;\n case 'star':\n this.renderStar(ctx, Math.min(width, height) / 2);\n break;\n case 'line':\n this.renderLine(ctx, width, height);\n return; // Line uses stroke, not fill\n default:\n // Fallback to rectangle\n this.renderRectangle(ctx, width, height);\n }\n\n ctx.fill();\n }\n\n /**\n * Render a rectangle (with optional border radius)\n */\n private renderRectangle(ctx: CanvasRenderingContext2D, width: number, height: number): void {\n const borderRadius = this.transformData.borderRadius || 0;\n const x = -width / 2;\n const y = -height / 2;\n\n if (borderRadius > 0) {\n const radius = Math.min((borderRadius / 100) * Math.min(width, height), width / 2, height / 2);\n ctx.roundRect(x, y, width, height, radius);\n } else {\n ctx.rect(x, y, width, height);\n }\n }\n\n /**\n * Render a circle\n */\n private renderCircle(ctx: CanvasRenderingContext2D, radius: number): void {\n ctx.arc(0, 0, radius, 0, Math.PI * 2);\n }\n\n /**\n * Render an ellipse\n */\n private renderEllipse(ctx: CanvasRenderingContext2D, radiusX: number, radiusY: number): void {\n ctx.ellipse(0, 0, radiusX, radiusY, 0, 0, Math.PI * 2);\n }\n\n /**\n * Render a triangle (isosceles, pointing up)\n */\n private renderTriangle(ctx: CanvasRenderingContext2D, width: number, height: number): void {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n\n ctx.moveTo(0, -halfHeight); // Top point\n ctx.lineTo(halfWidth, halfHeight); // Bottom right\n ctx.lineTo(-halfWidth, halfHeight); // Bottom left\n ctx.closePath();\n }\n\n /**\n * Render a regular polygon\n */\n private renderPolygon(ctx: CanvasRenderingContext2D, radius: number): void {\n const sides = Math.max(3, Math.min(20, this.transformData.sides || 5));\n const angleStep = (Math.PI * 2) / sides;\n const startAngle = -Math.PI / 2; // Start at top\n\n for (let i = 0; i <= sides; i++) {\n const angle = startAngle + angleStep * i;\n const x = Math.cos(angle) * radius;\n const y = Math.sin(angle) * radius;\n\n if (i === 0) {\n ctx.moveTo(x, y);\n } else {\n ctx.lineTo(x, y);\n }\n }\n\n ctx.closePath();\n }\n\n /**\n * Render a star\n */\n private renderStar(ctx: CanvasRenderingContext2D, outerRadius: number): void {\n const points = Math.max(3, Math.min(20, this.transformData.points || 5));\n const innerRadiusRatio = Math.max(0.1, Math.min(0.9, this.transformData.innerRadius || 0.4));\n const innerRadius = outerRadius * innerRadiusRatio;\n const angleStep = Math.PI / points;\n const startAngle = -Math.PI / 2; // Start at top\n\n for (let i = 0; i < points * 2; i++) {\n const angle = startAngle + angleStep * i;\n const radius = i % 2 === 0 ? outerRadius : innerRadius;\n const x = Math.cos(angle) * radius;\n const y = Math.sin(angle) * radius;\n\n if (i === 0) {\n ctx.moveTo(x, y);\n } else {\n ctx.lineTo(x, y);\n }\n }\n\n ctx.closePath();\n }\n\n /**\n * Render a line\n */\n private renderLine(ctx: CanvasRenderingContext2D, width: number, height: number): void {\n // Draw line as a stroke from left to right (or top to bottom if rotated)\n const halfWidth = width / 2;\n const thickness = height;\n\n ctx.moveTo(-halfWidth, 0);\n ctx.lineTo(halfWidth, 0);\n\n // Use stroke instead of fill\n ctx.strokeStyle = this.transformData.fillColor || '#3b82f6';\n ctx.lineWidth = thickness;\n ctx.lineCap = 'round';\n ctx.stroke();\n }\n\n /**\n * Handle resize - maintain aspect ratio for circles, free resize for others\n */\n override resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): boolean {\n const { shapeType } = this.transformData;\n\n // For circles and regular polygons, maintain aspect ratio\n if (shapeType === 'circle' || shapeType === 'polygon' || shapeType === 'star') {\n const scale = Math.max(newWidth / startData.transformData.width, newHeight / startData.transformData.height);\n this.transformData.width = startData.transformData.width * scale;\n this.transformData.height = startData.transformData.height * scale;\n } else if (shapeType === 'line') {\n // For lines, only allow width (length) to change, keep height (thickness) constant\n this.transformData.width = newWidth;\n this.transformData.height = startData.transformData.height;\n } else {\n // For other shapes, allow free resize\n this.transformData.width = newWidth;\n this.transformData.height = newHeight;\n }\n\n // Ensure rotation stays the same\n this.rotation = startData.rotation;\n\n // Calculate fixed corner position (opposite of dragged anchor)\n const fixedCorner = this.getFixedCorner(anchor, startData);\n\n // Update position so the fixed corner stays in place\n // Get the opposite anchor (the one that should stay fixed)\n const oppositeAnchor = this.getOppositeAnchor(anchor);\n const newBbox = this.getBoundingBox();\n const fixedCornerOffset = this.getCornerOffset(oppositeAnchor, newBbox);\n\n this.x = fixedCorner.x - fixedCornerOffset.x;\n this.y = fixedCorner.y - fixedCornerOffset.y;\n\n return true;\n }\n\n /**\n * Get the opposite anchor (the one that should stay fixed during resize)\n */\n private getOppositeAnchor(anchor: ResizeAnchor): ResizeAnchor {\n const opposites: Record<ResizeAnchor, ResizeAnchor> = {\n 'top-left': 'bottom-right',\n 'top-right': 'bottom-left',\n 'bottom-left': 'top-right',\n 'bottom-right': 'top-left',\n 'middle-left': 'middle-right',\n 'middle-right': 'middle-left',\n 'middle-top': 'middle-bottom',\n 'middle-bottom': 'middle-top',\n top: 'bottom',\n bottom: 'top',\n left: 'right',\n right: 'left',\n };\n return opposites[anchor];\n }\n\n /**\n * Get the position of the fixed corner (opposite of dragged anchor)\n */\n private getFixedCorner(anchor: ResizeAnchor, startData: TransformStartData): Point {\n const startBbox = {\n x: startData.x - startData.transformData.width / 2,\n y: startData.y - startData.transformData.height / 2,\n width: startData.transformData.width,\n height: startData.transformData.height,\n };\n\n let fixedX: number, fixedY: number;\n\n switch (anchor) {\n case 'top-left':\n fixedX = startBbox.x + startBbox.width;\n fixedY = startBbox.y + startBbox.height;\n break;\n case 'top-right':\n fixedX = startBbox.x;\n fixedY = startBbox.y + startBbox.height;\n break;\n case 'bottom-left':\n fixedX = startBbox.x + startBbox.width;\n fixedY = startBbox.y;\n break;\n case 'bottom-right':\n fixedX = startBbox.x;\n fixedY = startBbox.y;\n break;\n case 'middle-left':\n fixedX = startBbox.x + startBbox.width;\n fixedY = startBbox.y + startBbox.height / 2;\n break;\n case 'middle-right':\n fixedX = startBbox.x;\n fixedY = startBbox.y + startBbox.height / 2;\n break;\n case 'middle-top':\n fixedX = startBbox.x + startBbox.width / 2;\n fixedY = startBbox.y + startBbox.height;\n break;\n case 'middle-bottom':\n fixedX = startBbox.x + startBbox.width / 2;\n fixedY = startBbox.y;\n break;\n default:\n fixedX = startBbox.x + startBbox.width / 2;\n fixedY = startBbox.y + startBbox.height / 2;\n }\n\n return { x: fixedX, y: fixedY };\n }\n\n /**\n * Get the offset of a corner from the center\n */\n private getCornerOffset(anchor: ResizeAnchor, bbox: BoundingBox): Point {\n let offsetX: number, offsetY: number;\n\n switch (anchor) {\n case 'top-left':\n offsetX = -bbox.width / 2;\n offsetY = -bbox.height / 2;\n break;\n case 'top-right':\n offsetX = bbox.width / 2;\n offsetY = -bbox.height / 2;\n break;\n case 'bottom-left':\n offsetX = -bbox.width / 2;\n offsetY = bbox.height / 2;\n break;\n case 'bottom-right':\n offsetX = bbox.width / 2;\n offsetY = bbox.height / 2;\n break;\n case 'middle-left':\n offsetX = -bbox.width / 2;\n offsetY = 0;\n break;\n case 'middle-right':\n offsetX = bbox.width / 2;\n offsetY = 0;\n break;\n case 'middle-top':\n offsetX = 0;\n offsetY = -bbox.height / 2;\n break;\n case 'middle-bottom':\n offsetX = 0;\n offsetY = bbox.height / 2;\n break;\n default:\n offsetX = 0;\n offsetY = 0;\n }\n\n return { x: offsetX, y: offsetY };\n }\n\n /**\n * Get enabled anchors - all 8 anchors for most shapes, only corners for circles/polygons\n */\n getEnabledAnchors(): ResizeAnchor[] {\n const { shapeType } = this.transformData;\n\n // Circles and regular polygons: only corner handles (to maintain aspect ratio)\n if (shapeType === 'circle' || shapeType === 'polygon' || shapeType === 'star') {\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n // Lines: only left/right handles for length adjustment\n if (shapeType === 'line') {\n return ['middle-left', 'middle-right'];\n }\n\n // Other shapes: all 8 handles\n return [\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n 'middle-left',\n 'middle-right',\n 'middle-top',\n 'middle-bottom',\n ];\n }\n\n /**\n * Clone this element\n */\n clone(): ShapeElement {\n return new ShapeElement(this.toJSON());\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): ShapeElementConfig & { id: string; type: 'shape'; x: number; y: number; rotation: number } {\n const baseJSON = super.toJSON();\n return {\n ...baseJSON,\n id: this.id, // Explicitly include to ensure string type\n type: 'shape' as const,\n transformType: 'shape' as const,\n x: this.x, // Explicitly include to ensure number type\n y: this.y, // Explicitly include to ensure number type\n rotation: this.rotation, // Explicitly include to ensure number type\n transformData: { ...this.transformData },\n };\n }\n}\n\nexport default ShapeElement;\n","/**\n * PathElement - Element for custom bezier paths\n * Supports user-drawn shapes with editable control points\n */\n\nimport { BaseElement } from './BaseElement.js';\nimport { RotationUtils } from './RotationUtils.js';\nimport { getThemeShapeFillColor } from '../constants.js';\nimport type {\n PathTransformData,\n PathElementConfig,\n PathPoint,\n ResizeAnchor,\n BoundingBox,\n Point,\n TransformStartData,\n} from '../types/index.js';\n\nexport class PathElement extends BaseElement {\n declare transformType: 'path';\n declare transformData: PathTransformData;\n\n constructor(config: Partial<PathElementConfig> = {}) {\n super(config);\n this.transformType = 'path';\n\n // Initialize transformData with proper PathTransformData type\n this.transformData = {\n type: 'path',\n points: config.transformData?.points || [],\n closed: config.transformData?.closed ?? false,\n width: config.transformData?.width || 200,\n height: config.transformData?.height || 200,\n fillEnabled: config.transformData?.fillEnabled ?? false,\n fillColor: config.transformData?.fillColor || getThemeShapeFillColor(),\n fillOpacity: config.transformData?.fillOpacity ?? 1,\n strokeEnabled: config.transformData?.strokeEnabled ?? true,\n strokeColor: config.transformData?.strokeColor || '#000000',\n strokeWidth: config.transformData?.strokeWidth || 2,\n };\n\n // If no points provided, start with empty path\n // Points will be added via pen tool\n }\n\n /**\n * Get bounding box in world coordinates\n * Returns the unrotated dimensions - rotation is handled by the renderer\n * For paths, calculates actual bounds from path points\n * The bounding box is centered around the element position (this.x, this.y)\n */\n getBoundingBox(): BoundingBox {\n const localBounds = this.calculatePathBounds();\n\n // The bounding box is centered around the element position\n return {\n x: this.x - localBounds.width / 2,\n y: this.y - localBounds.height / 2,\n width: localBounds.width,\n height: localBounds.height,\n };\n }\n\n /**\n * Get visual bounding box (same as regular bounding box for paths)\n * Used for selection display - wraps tightly around actual path\n */\n getVisualBoundingBox(): BoundingBox {\n return this.getBoundingBox();\n }\n\n /**\n * Get rotation anchor point (center of the path's bounding box)\n */\n getRotationAnchor(): Point {\n // For path elements, rotation happens around the element position\n // which should be the center of the local bounds\n return {\n x: this.x,\n y: this.y,\n };\n }\n\n /**\n * Calculate a point on a cubic Bezier curve\n * @param p0 Start point\n * @param p1 First control point\n * @param p2 Second control point\n * @param p3 End point\n * @param t Parameter from 0 to 1\n */\n private bezierPoint(p0: Point, p1: Point, p2: Point, p3: Point, t: number): Point {\n const mt = 1 - t;\n const mt2 = mt * mt;\n const mt3 = mt2 * mt;\n const t2 = t * t;\n const t3 = t2 * t;\n\n return {\n x: mt3 * p0.x + 3 * mt2 * t * p1.x + 3 * mt * t2 * p2.x + t3 * p3.x,\n y: mt3 * p0.y + 3 * mt2 * t * p1.y + 3 * mt * t2 * p2.y + t3 * p3.y,\n };\n }\n\n /**\n * Calculate bounding box from path points\n * Returns bbox in local coordinates (relative to element position)\n */\n private calculatePathBounds(): BoundingBox {\n const { points } = this.transformData;\n\n if (points.length === 0) {\n return { x: 0, y: 0, width: 0, height: 0 };\n }\n\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n // Calculate bounds including Bezier curve extrema\n for (let i = 0; i < points.length; i++) {\n const currentPoint = points[i];\n const nextPoint = points[(i + 1) % points.length];\n\n // Skip last segment if path is not closed\n if (i === points.length - 1 && !this.transformData.closed) {\n // Include the last point\n minX = Math.min(minX, currentPoint.x);\n minY = Math.min(minY, currentPoint.y);\n maxX = Math.max(maxX, currentPoint.x);\n maxY = Math.max(maxY, currentPoint.y);\n break;\n }\n\n // Include the current anchor point\n minX = Math.min(minX, currentPoint.x);\n minY = Math.min(minY, currentPoint.y);\n maxX = Math.max(maxX, currentPoint.x);\n maxY = Math.max(maxY, currentPoint.y);\n\n // Check if this segment has a curve\n const hasCurve = currentPoint.handleOut || nextPoint.handleIn;\n\n if (hasCurve) {\n // Calculate Bezier curve bounds by sampling points along the curve\n const p0 = { x: currentPoint.x, y: currentPoint.y };\n const p1 = {\n x: currentPoint.x + (currentPoint.handleOut?.x || 0),\n y: currentPoint.y + (currentPoint.handleOut?.y || 0),\n };\n const p2 = {\n x: nextPoint.x + (nextPoint.handleIn?.x || 0),\n y: nextPoint.y + (nextPoint.handleIn?.y || 0),\n };\n const p3 = { x: nextPoint.x, y: nextPoint.y };\n\n // Sample the curve at multiple points to find extrema\n const samples = 20;\n for (let t = 0; t <= samples; t++) {\n const u = t / samples;\n const point = this.bezierPoint(p0, p1, p2, p3, u);\n minX = Math.min(minX, point.x);\n minY = Math.min(minY, point.y);\n maxX = Math.max(maxX, point.x);\n maxY = Math.max(maxY, point.y);\n }\n }\n }\n\n // Add stroke width to bounds if stroke is enabled\n const strokeWidth = this.transformData.strokeEnabled ? this.transformData.strokeWidth || 2 : 0;\n const halfStroke = strokeWidth / 2;\n\n return {\n x: minX - halfStroke,\n y: minY - halfStroke,\n width: maxX - minX + strokeWidth,\n height: maxY - minY + strokeWidth,\n };\n }\n\n /**\n * Update cached bounding box dimensions\n */\n updateBounds(): void {\n const bounds = this.calculatePathBounds();\n this.transformData.width = Math.max(1, bounds.width);\n this.transformData.height = Math.max(1, bounds.height);\n }\n\n /**\n * Render the path\n */\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n const { points, closed, fillEnabled, fillColor, fillOpacity, strokeEnabled, strokeColor, strokeWidth } =\n this.transformData;\n\n if (points.length === 0) {\n return; // Nothing to render\n }\n\n ctx.save();\n\n // Translate to path center and rotate\n ctx.translate(this.x, this.y);\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n\n // Offset to center the path around the rotation point\n const localBounds = this.calculatePathBounds();\n const centerOffsetX = localBounds.x + localBounds.width / 2;\n const centerOffsetY = localBounds.y + localBounds.height / 2;\n ctx.translate(-centerOffsetX, -centerOffsetY);\n\n // Build the path\n ctx.beginPath();\n this.renderPath(ctx);\n\n // Fill if enabled and path is closed\n if (fillEnabled && closed && fillColor) {\n ctx.fillStyle = fillColor;\n ctx.globalAlpha = fillOpacity ?? 1;\n ctx.fill();\n }\n\n // Stroke if enabled\n if (strokeEnabled && strokeColor && strokeWidth) {\n ctx.strokeStyle = strokeColor;\n ctx.lineWidth = strokeWidth;\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n ctx.globalAlpha = 1;\n ctx.stroke();\n }\n\n ctx.restore();\n }\n\n /**\n * Render the path using Canvas API bezier curves\n */\n private renderPath(ctx: CanvasRenderingContext2D): void {\n const { points, closed } = this.transformData;\n\n if (points.length === 0) return;\n\n // Start at first point\n const firstPoint = points[0];\n ctx.moveTo(firstPoint.x, firstPoint.y);\n\n // Draw segments between points\n for (let i = 0; i < points.length; i++) {\n const currentPoint = points[i];\n const nextPoint = points[(i + 1) % points.length];\n\n // Skip last segment if path is not closed\n if (i === points.length - 1 && !closed) {\n break;\n }\n\n // Check if we need to draw a curve or straight line\n const hasCurve = currentPoint.handleOut || nextPoint.handleIn;\n\n if (hasCurve) {\n // Bezier curve\n const cp1x = currentPoint.x + (currentPoint.handleOut?.x || 0);\n const cp1y = currentPoint.y + (currentPoint.handleOut?.y || 0);\n const cp2x = nextPoint.x + (nextPoint.handleIn?.x || 0);\n const cp2y = nextPoint.y + (nextPoint.handleIn?.y || 0);\n\n ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, nextPoint.x, nextPoint.y);\n } else {\n // Straight line\n ctx.lineTo(nextPoint.x, nextPoint.y);\n }\n }\n\n if (closed) {\n ctx.closePath();\n }\n }\n\n /**\n * Handle resize - scale all points relative to center\n */\n override resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): boolean {\n const oldWidth = startData.transformData.width;\n const oldHeight = startData.transformData.height;\n\n if (oldWidth === 0 || oldHeight === 0) {\n return false; // Can't resize empty path\n }\n\n // Calculate scale factors\n const scaleX = newWidth / oldWidth;\n const scaleY = newHeight / oldHeight;\n\n // Get the center of the original bounds to scale from\n // We need to reconstruct the original bounds from startData\n const startPoints = startData.transformData.points as PathPoint[];\n let minX = Infinity,\n minY = Infinity,\n maxX = -Infinity,\n maxY = -Infinity;\n for (const point of startPoints) {\n minX = Math.min(minX, point.x);\n minY = Math.min(minY, point.y);\n maxX = Math.max(maxX, point.x);\n maxY = Math.max(maxY, point.y);\n }\n const centerX = (minX + maxX) / 2;\n const centerY = (minY + maxY) / 2;\n\n // Scale all points and handles from the center\n this.transformData.points = startPoints.map((point) => ({\n ...point,\n x: centerX + (point.x - centerX) * scaleX,\n y: centerY + (point.y - centerY) * scaleY,\n handleIn: point.handleIn ? { x: point.handleIn.x * scaleX, y: point.handleIn.y * scaleY } : undefined,\n handleOut: point.handleOut ? { x: point.handleOut.x * scaleX, y: point.handleOut.y * scaleY } : undefined,\n }));\n\n // Update dimensions\n this.transformData.width = newWidth;\n this.transformData.height = newHeight;\n\n // Ensure rotation stays the same\n this.rotation = startData.rotation;\n\n // Calculate fixed corner position (opposite of dragged anchor)\n const fixedCorner = this.getFixedCorner(anchor, startData);\n\n // Update position so the fixed corner stays in place\n const oppositeAnchor = this.getOppositeAnchor(anchor);\n const newBbox = this.getBoundingBox();\n const fixedCornerOffset = this.getCornerOffset(oppositeAnchor, newBbox);\n\n this.x = fixedCorner.x - fixedCornerOffset.x;\n this.y = fixedCorner.y - fixedCornerOffset.y;\n\n return true;\n }\n\n /**\n * Get the opposite anchor (the one that should stay fixed during resize)\n */\n private getOppositeAnchor(anchor: ResizeAnchor): ResizeAnchor {\n const opposites: Record<ResizeAnchor, ResizeAnchor> = {\n 'top-left': 'bottom-right',\n 'top-right': 'bottom-left',\n 'bottom-left': 'top-right',\n 'bottom-right': 'top-left',\n 'middle-left': 'middle-right',\n 'middle-right': 'middle-left',\n 'middle-top': 'middle-bottom',\n 'middle-bottom': 'middle-top',\n top: 'bottom',\n bottom: 'top',\n left: 'right',\n right: 'left',\n };\n return opposites[anchor];\n }\n\n /**\n * Get the position of the fixed corner (opposite of dragged anchor)\n */\n private getFixedCorner(anchor: ResizeAnchor, startData: TransformStartData): Point {\n const startBbox = {\n x: startData.x - startData.transformData.width / 2,\n y: startData.y - startData.transformData.height / 2,\n width: startData.transformData.width,\n height: startData.transformData.height,\n };\n\n let fixedX: number, fixedY: number;\n\n switch (anchor) {\n case 'top-left':\n fixedX = startBbox.x + startBbox.width;\n fixedY = startBbox.y + startBbox.height;\n break;\n case 'top-right':\n fixedX = startBbox.x;\n fixedY = startBbox.y + startBbox.height;\n break;\n case 'bottom-left':\n fixedX = startBbox.x + startBbox.width;\n fixedY = startBbox.y;\n break;\n case 'bottom-right':\n fixedX = startBbox.x;\n fixedY = startBbox.y;\n break;\n case 'middle-left':\n fixedX = startBbox.x + startBbox.width;\n fixedY = startBbox.y + startBbox.height / 2;\n break;\n case 'middle-right':\n fixedX = startBbox.x;\n fixedY = startBbox.y + startBbox.height / 2;\n break;\n case 'middle-top':\n fixedX = startBbox.x + startBbox.width / 2;\n fixedY = startBbox.y + startBbox.height;\n break;\n case 'middle-bottom':\n fixedX = startBbox.x + startBbox.width / 2;\n fixedY = startBbox.y;\n break;\n default:\n fixedX = startBbox.x + startBbox.width / 2;\n fixedY = startBbox.y + startBbox.height / 2;\n }\n\n return { x: fixedX, y: fixedY };\n }\n\n /**\n * Get the offset of a corner from the center\n */\n private getCornerOffset(anchor: ResizeAnchor, bbox: BoundingBox): Point {\n let offsetX: number, offsetY: number;\n\n switch (anchor) {\n case 'top-left':\n offsetX = -bbox.width / 2;\n offsetY = -bbox.height / 2;\n break;\n case 'top-right':\n offsetX = bbox.width / 2;\n offsetY = -bbox.height / 2;\n break;\n case 'bottom-left':\n offsetX = -bbox.width / 2;\n offsetY = bbox.height / 2;\n break;\n case 'bottom-right':\n offsetX = bbox.width / 2;\n offsetY = bbox.height / 2;\n break;\n case 'middle-left':\n offsetX = -bbox.width / 2;\n offsetY = 0;\n break;\n case 'middle-right':\n offsetX = bbox.width / 2;\n offsetY = 0;\n break;\n case 'middle-top':\n offsetX = 0;\n offsetY = -bbox.height / 2;\n break;\n case 'middle-bottom':\n offsetX = 0;\n offsetY = bbox.height / 2;\n break;\n default:\n offsetX = 0;\n offsetY = 0;\n }\n\n return { x: offsetX, y: offsetY };\n }\n\n /**\n * Get enabled anchors - all 8 anchors for free resize\n */\n getEnabledAnchors(): ResizeAnchor[] {\n return [\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n 'middle-left',\n 'middle-right',\n 'middle-top',\n 'middle-bottom',\n ];\n }\n\n /**\n * Clone this element\n */\n clone(): PathElement {\n return new PathElement(this.toJSON());\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): PathElementConfig & { id: string; type: 'path'; x: number; y: number; rotation: number } {\n const baseJSON = super.toJSON();\n return {\n ...baseJSON,\n id: this.id, // Explicitly include to ensure string type\n type: 'path' as const,\n transformType: 'path' as const,\n x: this.x, // Explicitly include to ensure number type\n y: this.y, // Explicitly include to ensure number type\n rotation: this.rotation, // Explicitly include to ensure number type\n transformData: {\n ...this.transformData,\n // Deep clone points array\n points: this.transformData.points.map((p) => ({\n ...p,\n handleIn: p.handleIn ? { ...p.handleIn } : undefined,\n handleOut: p.handleOut ? { ...p.handleOut } : undefined,\n })),\n },\n };\n }\n}\n\nexport default PathElement;\n","/**\n * Centralized transform registry\n * Single source of truth for all transform types and their configurations\n */\n\nimport {\n CustomTransform,\n CircleTransform,\n ArchTransform,\n WaveTransform,\n FlagTransform,\n LeanTransform,\n AscendTransform,\n} from './index.js';\nimport { ShapeElement } from '../core/ShapeElement.js';\nimport { PathElement } from '../core/PathElement.js';\nimport { ImageElement } from '../core/ImageElement.js';\nimport { GroupElement } from '../core/GroupElement.js';\nimport { WAVE_DEFAULTS, FLAG_DEFAULTS, ARCH_DEFAULTS, LEAN_DEFAULTS, ASCEND_DEFAULTS } from './defaults.js';\nimport type { TransformType, TransformControl } from '../types/index.js';\n\n/**\n * Transform control configurations\n * Defines the sliders and their properties for each transform type\n */\nexport const TRANSFORM_CONTROLS: Partial<Record<TransformType, TransformControl[]>> = {\n circle: [\n {\n key: 'reverse',\n label: 'Reverse Direction',\n type: 'checkbox',\n defaultValue: false,\n },\n ],\n arch: [\n {\n key: 'archHeight',\n label: 'Arch Height',\n defaultInternalValue: ARCH_DEFAULTS.archHeight,\n // Slider 0-100 maps to internal -1 to 1\n toSlider: (internal: number) => ((internal + 1) / 2) * 100,\n fromSlider: (slider: number) => (slider / 100) * 2 - 1,\n toDisplay: (internal: number) => `${(internal * 100).toFixed(0)}%`,\n },\n ],\n wave: [\n {\n key: 'amplitude',\n label: 'Wave Amplitude',\n defaultInternalValue: WAVE_DEFAULTS.amplitude,\n // Slider 0-100 maps to internal -0.5 to 0.5 (-50% to +50%)\n toSlider: (internal: number) => (internal + 0.5) * 100,\n fromSlider: (slider: number) => (slider / 100) - 0.5,\n toDisplay: (internal: number) => `${(internal * 100).toFixed(0)}%`,\n },\n {\n key: 'frequency',\n label: 'Wave Frequency',\n defaultInternalValue: WAVE_DEFAULTS.frequency,\n step: 5, // 5% steps for smoother control\n // Slider 0-100 maps to internal 1 to 5\n toSlider: (internal: number) => ((internal - 1) / 4) * 100,\n fromSlider: (slider: number) => 1 + (slider / 100) * 4,\n toDisplay: (internal: number) => internal.toFixed(1),\n },\n ],\n flag: [\n {\n key: 'amplitude',\n label: 'Flag Amplitude',\n defaultInternalValue: FLAG_DEFAULTS.amplitude,\n // Slider 0-100 maps to internal -0.5 to 0.5 (-50% to +50%)\n toSlider: (internal: number) => (internal + 0.5) * 100,\n fromSlider: (slider: number) => (slider / 100) - 0.5,\n toDisplay: (internal: number) => `${(internal * 100).toFixed(0)}%`,\n },\n {\n key: 'frequency',\n label: 'Flag Frequency',\n defaultInternalValue: FLAG_DEFAULTS.frequency,\n step: 5,\n toSlider: (internal: number) => ((internal - 1) / 4) * 100,\n fromSlider: (slider: number) => 1 + (slider / 100) * 4,\n toDisplay: (internal: number) => internal.toFixed(1),\n },\n ],\n lean: [\n {\n key: 'leanAmount',\n label: 'Lean Amount',\n defaultInternalValue: LEAN_DEFAULTS.leanAmount,\n // Slider 0-100 maps to internal -0.5 to 0.5\n // Display as -100% to 100%\n toSlider: (internal: number) => (internal + 0.5) * 100,\n fromSlider: (slider: number) => slider / 100 - 0.5,\n toDisplay: (internal: number) => `${(internal * 200).toFixed(0)}%`,\n },\n ],\n ascend: [\n {\n key: 'ascendAngle',\n label: 'Ascend Amount',\n defaultInternalValue: ASCEND_DEFAULTS.ascendAngle,\n // Slider 0-100 maps to internal angle +30° to -30°\n // Display: slider 0 = -100% (down), slider 50 = 0%, slider 100 = +100% (up)\n toSlider: (internal: number) => ((30 - internal) / 60) * 100,\n fromSlider: (slider: number) => 30 - (slider / 100) * 60,\n toDisplay: (internal: number) => `${((-internal / 30) * 100).toFixed(0)}%`,\n },\n ],\n shape: [\n {\n key: 'shapeType',\n label: 'Shape Type',\n type: 'select',\n options: [\n { value: 'rectangle', label: 'Rectangle' },\n { value: 'circle', label: 'Circle' },\n { value: 'ellipse', label: 'Ellipse' },\n { value: 'triangle', label: 'Triangle' },\n { value: 'polygon', label: 'Polygon' },\n { value: 'star', label: 'Star' },\n { value: 'line', label: 'Line' },\n ],\n defaultValue: 'rectangle',\n },\n {\n key: 'borderRadius',\n label: 'Border Radius',\n defaultInternalValue: 0,\n // Slider 0-100 maps to internal 0 to 50\n toSlider: (internal: number) => (internal / 50) * 100,\n fromSlider: (slider: number) => (slider / 100) * 50,\n toDisplay: (internal: number) => `${internal.toFixed(0)}%`,\n visibleWhen: (data: Record<string, unknown>) => data.shapeType === 'rectangle',\n },\n {\n key: 'sides',\n label: 'Sides',\n defaultInternalValue: 5,\n step: 1,\n // Slider 0-100 maps to internal 3 to 20\n toSlider: (internal: number) => ((internal - 3) / 17) * 100,\n fromSlider: (slider: number) => Math.round(3 + (slider / 100) * 17),\n toDisplay: (internal: number) => internal.toFixed(0),\n visibleWhen: (data: Record<string, unknown>) => data.shapeType === 'polygon',\n },\n {\n key: 'points',\n label: 'Points',\n defaultInternalValue: 5,\n step: 1,\n // Slider 0-100 maps to internal 3 to 20\n toSlider: (internal: number) => ((internal - 3) / 17) * 100,\n fromSlider: (slider: number) => Math.round(3 + (slider / 100) * 17),\n toDisplay: (internal: number) => internal.toFixed(0),\n visibleWhen: (data: Record<string, unknown>) => data.shapeType === 'star',\n },\n {\n key: 'innerRadius',\n label: 'Inner Radius',\n defaultInternalValue: 0.4,\n // Slider 0-100 maps to internal 0.1 to 0.9\n toSlider: (internal: number) => ((internal - 0.1) / 0.8) * 100,\n fromSlider: (slider: number) => 0.1 + (slider / 100) * 0.8,\n toDisplay: (internal: number) => `${(internal * 100).toFixed(0)}%`,\n visibleWhen: (data: Record<string, unknown>) => data.shapeType === 'star',\n },\n {\n key: 'fillOpacity',\n label: 'Fill Opacity',\n defaultInternalValue: 1,\n // Slider 0-100 maps to internal 0 to 1\n toSlider: (internal: number) => internal * 100,\n fromSlider: (slider: number) => slider / 100,\n toDisplay: (internal: number) => `${(internal * 100).toFixed(0)}%`,\n },\n ],\n};\n\n/**\n * Transform definition shape used in the registry\n */\nexport interface TransformDef {\n id: string;\n label: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Constructors have varying signatures across element types\n Component: new (config: any) => any;\n}\n\n/**\n * Transform type definitions (built-in)\n * Combines metadata (id, label) with component class\n */\nexport const TRANSFORM_TYPES: TransformDef[] = [\n { id: 'custom', label: 'CUSTOM', Component: CustomTransform },\n // NOTE: distort transform is not yet implemented. Removed from registry\n // to prevent it from appearing in UI that iterates over registered transforms.\n // Re-add with a valid Component when implementation is ready.\n { id: 'circle', label: 'CIRCLE', Component: CircleTransform },\n { id: 'lean', label: 'LEAN', Component: LeanTransform },\n { id: 'arch', label: 'ARCH', Component: ArchTransform },\n { id: 'ascend', label: 'ASCEND', Component: AscendTransform },\n { id: 'wave', label: 'WAVE', Component: WaveTransform },\n { id: 'flag', label: 'FLAG', Component: FlagTransform },\n { id: 'shape', label: 'SHAPE', Component: ShapeElement },\n { id: 'path', label: 'PATH', Component: PathElement },\n { id: 'image', label: 'IMAGE', Component: ImageElement },\n // Use a getter to break circular dependency: GroupElement imports getTransformById\n // from this module, so GroupElement may be in the TDZ during module evaluation.\n // The getter defers access until runtime when both modules are fully initialized.\n { id: 'group', label: 'GROUP', get Component() { return GroupElement; } } as TransformDef,\n];\n\n/** Set of built-in transform type IDs (immutable) */\nconst BUILTIN_IDS = new Set(TRANSFORM_TYPES.map((t) => t.id));\n\n/** Dynamic transforms registered at runtime via the plugin API */\nconst _dynamicTransforms = new Map<string, TransformDef>();\n\n/**\n * Check if a transform type ID belongs to a built-in type\n */\nexport function isBuiltinTransformType(id: string): boolean {\n return BUILTIN_IDS.has(id);\n}\n\n/**\n * Register a dynamic transform type at runtime.\n * Throws if the id conflicts with a built-in type or is already registered.\n */\nexport function registerTransform(id: string, def: TransformDef): void {\n if (BUILTIN_IDS.has(id)) {\n throw new Error(\n `Cannot register transform type \"${id}\": conflicts with built-in type`\n );\n }\n if (_dynamicTransforms.has(id)) {\n throw new Error(\n `Cannot register transform type \"${id}\": already registered as a custom type`\n );\n }\n _dynamicTransforms.set(id, def);\n}\n\n/**\n * Unregister a dynamic transform type.\n * Returns true if the type was removed, false if it was not found.\n * Throws if attempting to unregister a built-in type.\n */\nexport function unregisterTransform(id: string): boolean {\n if (BUILTIN_IDS.has(id)) {\n throw new Error(\n `Cannot unregister transform type \"${id}\": built-in types cannot be removed`\n );\n }\n return _dynamicTransforms.delete(id);\n}\n\n/**\n * Get all registered transforms (built-in + dynamic)\n */\nexport function getAllTransforms(): TransformDef[] {\n return [...TRANSFORM_TYPES, ...Array.from(_dynamicTransforms.values())];\n}\n\n/**\n * Get transform definition by id (built-in or dynamic)\n */\nexport function getTransformById(id: string): TransformDef | undefined {\n const builtin = TRANSFORM_TYPES.find((t) => t.id === id);\n if (builtin) return builtin;\n return _dynamicTransforms.get(id);\n}\n\n/**\n * Get transform controls by id\n */\nexport function getTransformControls(id: TransformType): TransformControl[] {\n return TRANSFORM_CONTROLS[id] || [];\n}\n\n/**\n * Reset all dynamic transforms (for testing only)\n * @internal\n */\nexport function _resetDynamicTransforms(): void {\n _dynamicTransforms.clear();\n}\n","/**\n * GroupElement - Container element for grouping multiple elements together\n *\n * Key Design:\n * - Extends BaseElement for common element behavior\n * - Children maintain world coordinates (not local)\n * - Transforms propagate to all children\n * - Calculates bounds from children positions\n */\n\nimport { BaseElement } from './BaseElement.js';\nimport { TextElement } from './TextElement.js';\nimport { ImageElement } from './ImageElement.js';\nimport { ShapeElement } from './ShapeElement.js';\nimport { PathElement } from './PathElement.js';\nimport { Transform } from './Transform.js';\nimport type { AnyElementConfig, ImageTransformData } from '../types/index.js';\nimport { getThemeAccentColor } from '../constants.js';\nimport { getTransformById } from '../transforms/registry.js';\nimport type {\n GroupElementConfig,\n GroupTransformData,\n BoundingBox,\n Point,\n ResizeAnchor,\n TransformStartData,\n} from '../types/index.js';\n\n/** Child element types that can be contained in a group */\ntype ChildElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\nexport class GroupElement extends BaseElement {\n declare transformType: 'group';\n declare transformData: GroupTransformData;\n children: ChildElement[];\n isGroup: boolean = true;\n\n // Cache the base bounding box dimensions (calculated at rotation=0 or when children change)\n private _baseBBoxWidth: number = 0;\n private _baseBBoxHeight: number = 0;\n\n // Cache the rotation center (should stay fixed during rotation)\n private _rotationCenterX: number = 0;\n private _rotationCenterY: number = 0;\n\n constructor(config: Partial<GroupElementConfig> = {}) {\n super(config);\n this.transformType = 'group';\n\n // Handle children - they might be element instances or JSON objects\n const rawChildren = config.children || [];\n this.children = rawChildren.map((child) => {\n // If it's already an element instance (has methods), use it directly\n if (child instanceof BaseElement) {\n return child as ChildElement;\n }\n // Otherwise, it's a JSON object - need to reconstruct it\n // We can't import ElementFactory here due to circular dependency,\n // so we'll reconstruct children manually based on their transformType\n return this._reconstructChildFromJSON(child as unknown as Record<string, unknown>);\n });\n\n this.transformData = {\n type: 'group',\n };\n\n // Calculate initial bounds from children\n if (this.children.length > 0) {\n this.updateBoundsFromChildren(true); // Force calculation on initialization (also sets rotation center)\n }\n }\n\n /**\n * Reconstruct a child element from JSON using the transform registry.\n * Looks up the Component class by transformType and constructs it.\n * Adding new element types only requires updating the registry, not this file.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- JSON deserialization: config comes from toJSON() and constructors accept Partial<Config> with varying signatures\n private _reconstructChildFromJSON(config: any): ChildElement {\n const transformType = config.transformType || config.type;\n const transformDef = getTransformById(transformType);\n\n if (!transformDef || !transformDef.Component) {\n throw new Error(`Unknown transform type: ${transformType}`);\n }\n\n // Registry Components all extend BaseElement which satisfies ChildElement union\n return new transformDef.Component(config) as ChildElement;\n }\n\n /**\n * Calculate group bounds from all children\n * Returns axis-aligned bounding box containing all children\n */\n getBoundingBox(): BoundingBox {\n if (this.children.length === 0) {\n return { x: this.x - 50, y: this.y - 50, width: 100, height: 100 };\n }\n\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n this.children.forEach((child) => {\n const bbox = child.getVisualBoundingBox();\n const corners = this._getRotatedCorners(child, bbox);\n\n corners.forEach((corner) => {\n minX = Math.min(minX, corner.x);\n minY = Math.min(minY, corner.y);\n maxX = Math.max(maxX, corner.x);\n maxY = Math.max(maxY, corner.y);\n });\n });\n\n return {\n x: minX,\n y: minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n /**\n * Visual bounding box uses the oriented bounding box for groups\n * This ensures handles and hover effects stay aligned with the dotted border during rotation\n */\n getVisualBoundingBox(): BoundingBox {\n return this.getOrientedBoundingBox();\n }\n\n /**\n * Get rotation anchor - returns the cached rotation center\n * This ensures consistent rotation behavior even if this.x/this.y change\n */\n getRotationAnchor(): Point {\n return {\n x: this._rotationCenterX || this.x,\n y: this._rotationCenterY || this.y,\n };\n }\n\n /**\n * Get the four corners of an element's bounding box in world space\n */\n private _getRotatedCorners(element: ChildElement, bbox: BoundingBox): Point[] {\n const transform = new Transform(element);\n\n // Calculate the center of the bounding box\n const centerX = bbox.x + bbox.width / 2;\n const centerY = bbox.y + bbox.height / 2;\n\n // Calculate offsets from the element's center to the bbox center\n const offsetX = centerX - element.x;\n const offsetY = centerY - element.y;\n\n // Define corners relative to the bbox center\n const corners = [\n { localX: offsetX - bbox.width / 2, localY: offsetY - bbox.height / 2 },\n { localX: offsetX + bbox.width / 2, localY: offsetY - bbox.height / 2 },\n { localX: offsetX - bbox.width / 2, localY: offsetY + bbox.height / 2 },\n { localX: offsetX + bbox.width / 2, localY: offsetY + bbox.height / 2 },\n ];\n\n return corners.map((corner) => transform.localToWorld(corner.localX, corner.localY));\n }\n\n /**\n * Update group center and size from children\n * @param forceRecalcDimensions - Force recalculation of base dimensions (use when children are modified, not during rotation)\n */\n updateBoundsFromChildren(forceRecalcDimensions: boolean = false): void {\n const bbox = this.getBoundingBox();\n this.x = bbox.x + bbox.width / 2;\n this.y = bbox.y + bbox.height / 2;\n\n // Only recalculate base dimensions when explicitly requested or when cache is empty\n // NEVER recalculate during rotation - the cache should remain stable\n if (forceRecalcDimensions || this._baseBBoxWidth === 0 || this._baseBBoxHeight === 0) {\n const dims = this._calculateInitialDimensions();\n this._baseBBoxWidth = dims.width;\n this._baseBBoxHeight = dims.height;\n // Update rotation center when dimensions are recalculated (group creation or children changed)\n this._rotationCenterX = this.x;\n this._rotationCenterY = this.y;\n }\n }\n\n /**\n * Calculate the axis-aligned bounding box dimensions at current state\n * This should only be called when the group is created or when children change (not during rotation)\n *\n * Like multi-selection, we simply cache the current axis-aligned bbox dimensions\n * and use them for rendering the selection border.\n */\n private _calculateInitialDimensions(): { width: number; height: number } {\n if (this.children.length === 0) {\n return { width: 100, height: 100 };\n }\n\n // Simply use the current axis-aligned bounding box dimensions\n // This is the same approach as multi-selection OBB initialization\n const bbox = this.getBoundingBox();\n const result = {\n width: bbox.width,\n height: bbox.height,\n };\n\n return result;\n }\n\n /**\n * Render the group with clipping support\n * Elements with isClipping=true clip all elements below them in the group\n */\n render(ctx: CanvasRenderingContext2D, isSelected: boolean = false, isHovered: boolean = false): void {\n // Filter out invisible children\n const visibleChildren = this.children.filter((child) => child.visible !== false);\n\n // Check if any visible children have clipping enabled\n const hasClipping = visibleChildren.some((child) => child.isClipping);\n\n\n if (!hasClipping) {\n // No clipping - render normally\n visibleChildren.forEach((child) => {\n child.render(ctx, false);\n });\n } else {\n // Has clipping - render with masks (passing visible children only)\n this._renderWithClipping(ctx, visibleChildren);\n }\n\n // Render group outline when selected or hovered\n if (isSelected) {\n this._renderGroupOutline(ctx, 1.0);\n } else if (isHovered) {\n this._renderGroupOutline(ctx, 0.3);\n }\n }\n\n /**\n * Render group with clipping masks\n * Elements with isClipping=true clip all elements below them\n */\n private _renderWithClipping(\n ctx: CanvasRenderingContext2D,\n children: ChildElement[]\n ): void {\n ctx.save();\n\n const canvas = ctx.canvas;\n const width = canvas.width;\n const height = canvas.height;\n const transform = ctx.getTransform();\n\n // Group children into clipping segments\n // Each segment has content elements and optionally a clipping mask\n const segments: {\n content: ChildElement[];\n mask?: ChildElement;\n }[] = [];\n let currentContent: ChildElement[] = [];\n\n // Iterate through children (bottom to top in visual order)\n for (const child of children) {\n if (child.isClipping) {\n // This element clips everything we've collected so far\n if (currentContent.length > 0) {\n segments.push({ content: currentContent, mask: child });\n currentContent = [];\n } else {\n // Clipping mask with no content below it - just render the mask normally\n segments.push({ content: [child] });\n }\n } else {\n currentContent.push(child);\n }\n }\n\n // Add remaining content without clipping\n if (currentContent.length > 0) {\n segments.push({ content: currentContent });\n }\n\n\n // Render each segment\n for (const segment of segments) {\n if (!segment.mask) {\n // No mask - render normally\n segment.content.forEach((child) => child.render(ctx, false, false));\n } else {\n // Render with clipping mask\n\n const contentCanvas = document.createElement('canvas');\n contentCanvas.width = width;\n contentCanvas.height = height;\n const contentCtx = contentCanvas.getContext('2d');\n if (!contentCtx) continue;\n\n contentCtx.setTransform(transform);\n\n // Render content\n segment.content.forEach((child) => {\n child.render(contentCtx, false, false);\n });\n\n // Create mask\n const maskCanvas = document.createElement('canvas');\n maskCanvas.width = width;\n maskCanvas.height = height;\n const maskCtx = maskCanvas.getContext('2d');\n if (!maskCtx) continue;\n\n maskCtx.setTransform(transform);\n segment.mask.render(maskCtx, false, false);\n\n // Fallback for empty text elements\n if (segment.mask.transformType !== 'image' && segment.mask.transformType !== 'shape') {\n const bbox = segment.mask.getBoundingBox();\n maskCtx.save();\n maskCtx.fillStyle = '#FFFFFF';\n maskCtx.fillRect(bbox.x, bbox.y, bbox.width, bbox.height);\n maskCtx.restore();\n }\n\n // Apply mask\n contentCtx.globalCompositeOperation = 'destination-in';\n contentCtx.setTransform(1, 0, 0, 1, 0, 0);\n contentCtx.drawImage(maskCanvas, 0, 0);\n\n // Draw to main canvas\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.drawImage(contentCanvas, 0, 0);\n ctx.setTransform(transform);\n }\n }\n\n ctx.restore();\n }\n\n /**\n * Render dashed outline around group\n * Uses an oriented bounding box that doesn't change size during rotation\n */\n private _renderGroupOutline(ctx: CanvasRenderingContext2D, opacity: number = 1.0): void {\n // Get the oriented bounding box (tight fit ignoring rotation)\n const obb = this.getOrientedBoundingBox();\n\n ctx.save();\n ctx.globalAlpha = opacity;\n ctx.strokeStyle = getThemeAccentColor();\n ctx.lineWidth = 2;\n ctx.setLineDash([5, 5]);\n\n // Apply rotation around the CACHED rotation center (not current this.x, this.y)\n // The OBB is already centered at the rotation center\n const centerX = this._rotationCenterX || this.x;\n const centerY = this._rotationCenterY || this.y;\n\n if (this.rotation !== 0) {\n ctx.translate(centerX, centerY);\n ctx.rotate((-this.rotation * Math.PI) / 180);\n ctx.translate(-centerX, -centerY);\n }\n\n // Draw the rectangle using OBB coordinates\n ctx.strokeRect(obb.x, obb.y, obb.width, obb.height);\n ctx.setLineDash([]);\n ctx.restore();\n }\n\n /**\n * Get oriented bounding box (uses cached base dimensions for stability during rotation)\n *\n * This follows the same approach as multi-selection:\n * - Cache the initial axis-aligned bbox dimensions when group is created\n * - Use those cached dimensions during rotation (never recalculate)\n * - This keeps the selection border tight and stable during rotation\n */\n private getOrientedBoundingBox(): BoundingBox {\n if (this.children.length === 0) {\n return { x: this.x - 50, y: this.y - 50, width: 100, height: 100 };\n }\n\n // Use cached base dimensions if available, otherwise calculate current bbox\n let width: number, height: number;\n\n if (this._baseBBoxWidth > 0 && this._baseBBoxHeight > 0) {\n // Use cached dimensions (set when group was created or children changed)\n width = this._baseBBoxWidth;\n height = this._baseBBoxHeight;\n } else {\n // Fall back to current bbox (happens on first render before cache is set)\n const bbox = this.getBoundingBox();\n width = bbox.width;\n height = bbox.height;\n\n // Cache for future use\n this._baseBBoxWidth = width;\n this._baseBBoxHeight = height;\n }\n\n // Return box centered at the cached rotation center (not this.x/this.y which might drift)\n const centerX = this._rotationCenterX || this.x;\n const centerY = this._rotationCenterY || this.y;\n return {\n x: centerX - width / 2,\n y: centerY - height / 2,\n width,\n height,\n };\n }\n\n /**\n * Move the group (moves all children)\n */\n move(dx: number, dy: number): void {\n // Update group center position\n this.x += dx;\n this.y += dy;\n\n // Update cached rotation center to follow the group\n this._rotationCenterX += dx;\n this._rotationCenterY += dy;\n\n // Move all children by the same amount\n this.children.forEach((child) => {\n child.move(dx, dy);\n });\n }\n\n /**\n * Rotate the group (rotates all children around group center)\n */\n setRotation(newRotation: number): void {\n const deltaRotation = newRotation - this.rotation;\n this.rotation = newRotation;\n\n // Use cached rotation center for consistency\n const centerX = this._rotationCenterX || this.x;\n const centerY = this._rotationCenterY || this.y;\n\n this.children.forEach((child) => {\n // Rotate child's position around the cached group center\n const rotated = Transform.rotatePointAroundAnchor(child.x, child.y, centerX, centerY, deltaRotation);\n child.x = rotated.x;\n child.y = rotated.y;\n\n // Add rotation to child's own rotation\n child.setRotation(child.rotation + deltaRotation);\n });\n\n // Update this.x and this.y to stay at the rotation center\n // This prevents drift during rotation\n this.x = centerX;\n this.y = centerY;\n }\n\n /**\n * Resize the group (scales all children proportionally)\n * Uses the same fixed-corner approach as multi-selection resize\n */\n resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): void {\n if (!startData.childrenStartData || !startData.width || !startData.height) {\n return;\n }\n\n const scaleX = newWidth / startData.width;\n const scaleY = newHeight / startData.height;\n const scale = (scaleX + scaleY) / 2; // Uniform scale\n\n // Calculate the fixed corner in LOCAL space (unrotated)\n let localFixedX: number, localFixedY: number;\n\n switch (anchor) {\n case 'top-left':\n localFixedX = startData.width / 2;\n localFixedY = startData.height / 2;\n break;\n case 'top-right':\n localFixedX = -startData.width / 2;\n localFixedY = startData.height / 2;\n break;\n case 'bottom-left':\n localFixedX = startData.width / 2;\n localFixedY = -startData.height / 2;\n break;\n case 'bottom-right':\n localFixedX = -startData.width / 2;\n localFixedY = -startData.height / 2;\n break;\n default:\n // Fallback to center\n localFixedX = 0;\n localFixedY = 0;\n }\n\n // Rotate the fixed corner to world space using the group's rotation\n const rotationRad = (-startData.rotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const fixedX = startData.x + (localFixedX * cos - localFixedY * sin);\n const fixedY = startData.y + (localFixedX * sin + localFixedY * cos);\n\n this.children.forEach((child, index) => {\n const childStart = startData.childrenStartData![index];\n\n // Scale position offset from the FIXED corner (not center)\n // This is the same approach as multi-selection resize\n const offsetX = childStart.x - fixedX;\n const offsetY = childStart.y - fixedY;\n const newX = fixedX + offsetX * scale;\n const newY = fixedY + offsetY * scale;\n\n child.x = newX;\n child.y = newY;\n\n // Scale child dimensions using the same approach as multi-selection resize\n if (child instanceof ImageElement) {\n // For images, scale the transform data directly\n const childTransform = childStart.transformData as unknown as ImageTransformData;\n const newChildWidth = childTransform.width * scale;\n const newChildHeight = childTransform.height * scale;\n child.transformData.width = newChildWidth;\n child.transformData.height = newChildHeight;\n } else if (child instanceof GroupElement) {\n // For nested groups, recursively resize\n const newChildWidth = (childStart.width ?? 0) * scale;\n const newChildHeight = (childStart.height ?? 0) * scale;\n child.resize(anchor, newChildWidth, newChildHeight, childStart);\n } else {\n // For text elements, use the element's resize method (handles transform-specific logic)\n const newChildWidth = (childStart.width ?? 0) * scale;\n const newChildHeight = (childStart.height ?? 0) * scale;\n child.resize(anchor, newChildWidth, newChildHeight, childStart);\n }\n });\n\n // Scale the cached dimensions directly (proportional to the scale factor)\n this._baseBBoxWidth = startData.width! * scale;\n this._baseBBoxHeight = startData.height! * scale;\n\n // Calculate the new group center by scaling the offset from the fixed corner\n const centerOffsetX = startData.x - fixedX;\n const centerOffsetY = startData.y - fixedY;\n this.x = fixedX + centerOffsetX * scale;\n this.y = fixedY + centerOffsetY * scale;\n\n // Update the rotation center to the new center position\n this._rotationCenterX = this.x;\n this._rotationCenterY = this.y;\n }\n\n /**\n * Get transform start data (includes children data)\n * Use cached base dimensions instead of axis-aligned bbox for consistency with visual border\n */\n getTransformStartData(): TransformStartData {\n // Use the cached base dimensions which match the oriented bounding box (visual border)\n // This prevents the jump when resizing a rotated group\n const width = this._baseBBoxWidth > 0 ? this._baseBBoxWidth : this.getBoundingBox().width;\n const height = this._baseBBoxHeight > 0 ? this._baseBBoxHeight : this.getBoundingBox().height;\n\n // Use the cached rotation center coordinates to match where the visual border is actually drawn\n // This prevents position jump when starting a resize\n const centerX = this._rotationCenterX || this.x;\n const centerY = this._rotationCenterY || this.y;\n\n return {\n id: this.id,\n x: centerX,\n y: centerY,\n width: width,\n height: height,\n rotation: this.rotation,\n transformData: this.transformData as unknown as Record<string, unknown>,\n childrenStartData: this.children.map((child) => child.getTransformStartData()),\n };\n }\n\n /**\n * Hit test - check if point is inside group's oriented bounding box\n * This allows clicking anywhere within the selection border to drag the group\n */\n hitTest(x: number, y: number): boolean {\n // Use the oriented bounding box (matches the visual selection border)\n const obb = this.getOrientedBoundingBox();\n const centerX = this._rotationCenterX || this.x;\n const centerY = this._rotationCenterY || this.y;\n\n // If not rotated, use simple axis-aligned test\n if (this.rotation === 0) {\n const isInside = x >= obb.x && x <= obb.x + obb.width && y >= obb.y && y <= obb.y + obb.height;\n if (isInside) {\n }\n return isInside;\n }\n\n // For rotated groups, transform the click point to local coordinates\n // then check against the unrotated rectangle\n const rotationRad = (this.rotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Translate point to origin (relative to rotation center)\n const dx = x - centerX;\n const dy = y - centerY;\n\n // Rotate point back to local space (inverse rotation)\n const localX = centerX + (dx * cos - dy * sin);\n const localY = centerY + (dx * sin + dy * cos);\n\n // Check if the local point is within the OBB rectangle\n const isInside = localX >= obb.x && localX <= obb.x + obb.width && localY >= obb.y && localY <= obb.y + obb.height;\n\n if (isInside) {\n }\n\n return isInside;\n }\n\n /**\n * Only corner handles for groups (uniform scale)\n */\n getEnabledAnchors(): ResizeAnchor[] {\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): GroupElementConfig {\n const baseJSON = super.toJSON();\n return {\n ...baseJSON,\n transformType: 'group',\n transformData: {\n type: 'group',\n },\n children: this.children.map((child) => child.toJSON()) as AnyElementConfig[],\n };\n }\n\n /**\n * Clone the group (deep copy with children)\n */\n clone(): GroupElement {\n const clonedChildren = this.children.map((child) => child.clone());\n const clonedGroup = new GroupElement({\n id: this.id,\n x: this.x,\n y: this.y,\n rotation: this.rotation,\n // Children are element instances here; the constructor handles both instances and configs\n children: clonedChildren as unknown as AnyElementConfig[],\n });\n // Preserve cached dimensions to avoid recalculation during rotation\n clonedGroup._baseBBoxWidth = this._baseBBoxWidth;\n clonedGroup._baseBBoxHeight = this._baseBBoxHeight;\n // Preserve cached rotation center\n clonedGroup._rotationCenterX = this._rotationCenterX;\n clonedGroup._rotationCenterY = this._rotationCenterY;\n\n // Copy opacity\n clonedGroup.opacity = this.opacity;\n\n // Copy layer properties\n clonedGroup.name = this.name;\n clonedGroup.visible = this.visible;\n clonedGroup.locked = this.locked;\n\n // Copy effects properties (blendMode covers isClipping via getter)\n if (this.blendMode) clonedGroup.blendMode = this.blendMode;\n if (this.knockoutParts) clonedGroup.knockoutParts = { ...this.knockoutParts };\n if (this.stroke) clonedGroup.stroke = { ...this.stroke };\n if (this.masks) clonedGroup.masks = this.masks.map((m) => ({ ...m }));\n if (this.distressEffect) clonedGroup.distressEffect = { ...this.distressEffect };\n\n return clonedGroup;\n }\n\n /**\n * Add a child to the group\n */\n addChild(child: ChildElement): void {\n this.children.push(child);\n this.updateBoundsFromChildren(true); // Recalculate dimensions when children added\n }\n\n /**\n * Remove a child from the group\n */\n removeChild(childId: string): boolean {\n const index = this.children.findIndex((c) => c.id === childId);\n if (index >= 0) {\n this.children.splice(index, 1);\n this.updateBoundsFromChildren(true); // Recalculate dimensions when children removed\n return true;\n }\n return false;\n }\n\n /**\n * Get all children (for edit mode)\n */\n getChildren(): ChildElement[] {\n return [...this.children];\n }\n\n /**\n * Find which child (if any) was clicked at the given point\n * Returns the child element or null if no child was hit\n */\n hitTestChild(x: number, y: number): ChildElement | null {\n // Test children in reverse order (top to bottom)\n for (let i = this.children.length - 1; i >= 0; i--) {\n const child = this.children[i];\n if (child.hitTest(x, y)) {\n return child;\n }\n }\n return null;\n }\n}\n","/**\n * ElementStore - Normalized element storage with O(1) lookups by ID.\n *\n * Replaces the flat array pattern (`EditorElement[]`) with a Map-based\n * structure while preserving render order via an ordered ID list.\n *\n * Immutable update pattern: every mutation method returns a new instance\n * so React can detect state changes via referential equality.\n *\n * @example\n * ```ts\n * const store = new ElementStore(elements);\n * const el = store.get('element-1'); // O(1) lookup\n * const next = store.update(updatedElement); // Returns new instance\n * const arr = next.toArray(); // Backwards-compatible array\n * ```\n *\n * @module\n */\n\nimport type { TextElement } from './TextElement.js';\nimport type { ImageElement } from './ImageElement.js';\nimport type { GroupElement } from './GroupElement.js';\nimport type { ShapeElement } from './ShapeElement.js';\nimport type { PathElement } from './PathElement.js';\n\n/** Union of all element types that can be stored. */\nexport type StoreElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\nexport class ElementStore {\n private byId: Map<string, StoreElement>;\n private order: string[]; // Maintains render order\n private _cachedArray: StoreElement[] | null = null;\n\n constructor(elements?: StoreElement[]) {\n this.byId = new Map();\n this.order = [];\n if (elements) {\n for (const el of elements) {\n this.byId.set(el.id, el);\n this.order.push(el.id);\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Read operations (O(1) where possible)\n // ---------------------------------------------------------------------------\n\n /** O(1) lookup by ID. */\n get(id: string): StoreElement | undefined {\n return this.byId.get(id);\n }\n\n /** Check whether the store contains an element with the given ID. */\n has(id: string): boolean {\n return this.byId.has(id);\n }\n\n /** Get all elements in render order. */\n getAll(): StoreElement[] {\n return this.order.map((id) => this.byId.get(id)!);\n }\n\n /** Number of elements in the store. */\n get size(): number {\n return this.byId.size;\n }\n\n /** Get the ordered ID list (read-only copy). */\n getOrder(): readonly string[] {\n return this.order;\n }\n\n /** O(n) lookup by name. Returns the first element with the given name, or undefined. */\n getByName(name: string): StoreElement | undefined {\n for (const id of this.order) {\n const el = this.byId.get(id);\n if (el && el.name === name) return el;\n }\n return undefined;\n }\n\n /** O(n) lookup by name. Returns all elements with the given name in render order. */\n getAllByName(name: string): StoreElement[] {\n const result: StoreElement[] = [];\n for (const id of this.order) {\n const el = this.byId.get(id);\n if (el && el.name === name) result.push(el);\n }\n return result;\n }\n\n // ---------------------------------------------------------------------------\n // Immutable mutation operations (return new ElementStore instances)\n // ---------------------------------------------------------------------------\n\n /** Replace an element in-place (preserves order). */\n update(element: StoreElement): ElementStore {\n const next = this.clone();\n next.byId.set(element.id, element);\n // If the element didn't exist, append to order\n if (!this.byId.has(element.id)) {\n next.order.push(element.id);\n }\n return next;\n }\n\n /**\n * Replace an element found by a predicate with a new element.\n * This mirrors `setElements(prev => prev.map(el => el.id === id ? newEl : el))`.\n */\n updateById(id: string, element: StoreElement): ElementStore {\n if (!this.byId.has(id)) return this;\n const next = this.clone();\n // If the id changed (unlikely but safe), remove old and insert new at same position\n if (id !== element.id) {\n next.byId.delete(id);\n const idx = next.order.indexOf(id);\n if (idx !== -1) next.order[idx] = element.id;\n }\n next.byId.set(element.id, element);\n return next;\n }\n\n /** Add an element at the end of the render order. */\n add(element: StoreElement): ElementStore {\n const next = this.clone();\n next.byId.set(element.id, element);\n if (!this.byId.has(element.id)) {\n next.order.push(element.id);\n }\n return next;\n }\n\n /** Insert an element after a specific element ID. */\n insertAfter(element: StoreElement, afterId: string): ElementStore {\n const next = this.clone();\n next.byId.set(element.id, element);\n const idx = next.order.indexOf(afterId);\n if (idx !== -1) {\n next.order.splice(idx + 1, 0, element.id);\n } else {\n // Fallback: append to end\n next.order.push(element.id);\n }\n return next;\n }\n\n /** Remove an element by ID. */\n remove(id: string): ElementStore {\n if (!this.byId.has(id)) return this;\n const next = this.clone();\n next.byId.delete(id);\n next.order = next.order.filter((oid) => oid !== id);\n return next;\n }\n\n /** Reorder element to a new index in the render order. */\n reorder(id: string, newIndex: number): ElementStore {\n const next = this.clone();\n next.order = next.order.filter((oid) => oid !== id);\n next.order.splice(newIndex, 0, id);\n return next;\n }\n\n /**\n * Apply a batch update: replace elements whose IDs match.\n * Returns a new store. Useful for applying multiple updates at once\n * (e.g., reordering by an ID array from undo/redo).\n */\n replaceAll(elements: StoreElement[]): ElementStore {\n return new ElementStore(elements);\n }\n\n /**\n * Filter elements by predicate while preserving order.\n * Returns a new store containing only elements that match.\n */\n filter(predicate: (el: StoreElement) => boolean): ElementStore {\n const filtered = this.getAll().filter(predicate);\n return new ElementStore(filtered);\n }\n\n /**\n * Map over elements in render order, returning a new store.\n * The mapping function receives each element and returns a (possibly modified) element.\n */\n map(fn: (el: StoreElement) => StoreElement): ElementStore {\n const mapped = this.getAll().map(fn);\n return new ElementStore(mapped);\n }\n\n /**\n * Set a completely new ordering for all elements.\n * IDs not in the new order are dropped; new IDs not in the store are ignored.\n */\n setOrder(newOrder: string[]): ElementStore {\n const next = this.clone();\n next.order = newOrder.filter((id) => next.byId.has(id));\n return next;\n }\n\n // ---------------------------------------------------------------------------\n // Conversion helpers\n // ---------------------------------------------------------------------------\n\n /** Convert to a plain array (backwards-compatible with `EditorElement[]`). Cached since the store is immutable. */\n toArray(): StoreElement[] {\n if (this._cachedArray === null) {\n this._cachedArray = this.getAll();\n }\n return this._cachedArray;\n }\n\n /** Create from a plain array. */\n static fromArray(elements: StoreElement[]): ElementStore {\n return new ElementStore(elements);\n }\n\n // ---------------------------------------------------------------------------\n // Internal\n // ---------------------------------------------------------------------------\n\n /** Create a shallow clone (new Map/Array, same element references). */\n private clone(): ElementStore {\n const store = new ElementStore();\n store.byId = new Map(this.byId);\n store.order = [...this.order];\n return store;\n }\n}\n","/**\n * CommandHistory - Manages undo/redo with the Command pattern\n * Stores operations as commands that can be undone and redone\n */\n\nimport type { BaseElement } from './BaseElement.js';\nimport type { Command as CommandInterface } from '../types/index.js';\nimport { ArtboardElement } from './ArtboardElement.js';\nimport { ArtboardManager } from './ArtboardManager.js';\n\n/**\n * Base Command class\n * All commands must implement execute() and undo()\n */\nexport class Command implements CommandInterface {\n /**\n * Execute the command\n */\n execute(): void {\n throw new Error('Command.execute() must be implemented');\n }\n\n /**\n * Undo the command\n */\n undo(): void {\n throw new Error('Command.undo() must be implemented');\n }\n}\n\n/**\n * UpdateElementCommand - Command to update an element's properties\n */\nexport class UpdateElementCommand extends Command {\n elementId: string;\n oldElement: BaseElement | null;\n newElement: BaseElement | null;\n onUpdate: (element: BaseElement) => void;\n\n constructor(\n elementId: string,\n oldElement: BaseElement | null,\n newElement: BaseElement | null,\n onUpdate: (element: BaseElement) => void\n ) {\n super();\n this.elementId = elementId;\n this.oldElement = oldElement ? oldElement.clone() : null;\n this.newElement = newElement ? newElement.clone() : null;\n this.onUpdate = onUpdate;\n }\n\n execute(): void {\n if (this.newElement) {\n this.onUpdate(this.newElement);\n }\n }\n\n undo(): void {\n if (this.oldElement) {\n this.onUpdate(this.oldElement);\n }\n }\n}\n\n/**\n * AddElementCommand - Command to add a new element\n */\nexport class AddElementCommand extends Command {\n element: BaseElement;\n onAdd: (element: BaseElement) => void;\n onRemove: (id: string) => void;\n\n constructor(element: BaseElement, onAdd: (element: BaseElement) => void, onRemove: (id: string) => void) {\n super();\n this.element = element.clone();\n this.onAdd = onAdd;\n this.onRemove = onRemove;\n }\n\n execute(): void {\n this.onAdd(this.element);\n }\n\n undo(): void {\n this.onRemove(this.element.id);\n }\n}\n\n/**\n * RemoveElementCommand - Command to remove an element\n */\nexport class RemoveElementCommand extends Command {\n element: BaseElement;\n onAdd: (element: BaseElement) => void;\n onRemove: (id: string) => void;\n\n constructor(element: BaseElement, onAdd: (element: BaseElement) => void, onRemove: (id: string) => void) {\n super();\n this.element = element.clone();\n this.onAdd = onAdd;\n this.onRemove = onRemove;\n }\n\n execute(): void {\n this.onRemove(this.element.id);\n }\n\n undo(): void {\n this.onAdd(this.element);\n }\n}\n\n/**\n * BatchCommand - Execute multiple commands as one atomic operation\n */\nexport class BatchCommand extends Command {\n commands: Command[];\n\n constructor(commands: Command[] = []) {\n super();\n this.commands = commands;\n }\n\n execute(): void {\n this.commands.forEach((cmd) => cmd.execute());\n }\n\n undo(): void {\n // Undo in reverse order\n for (let i = this.commands.length - 1; i >= 0; i--) {\n this.commands[i].undo();\n }\n }\n}\n\n/**\n * CompoundCommand - Wraps an array of commands into a single undo entry.\n * Used by executeBatch() to group multiple operations atomically.\n *\n * - execute(): runs all commands in order\n * - undo(): reverts all commands in reverse order\n */\nexport class CompoundCommand extends Command {\n private commands: Command[];\n\n constructor(commands: Command[]) {\n super();\n this.commands = commands;\n }\n\n execute(): void {\n this.commands.forEach((cmd) => cmd.execute());\n }\n\n undo(): void {\n // Undo in reverse order for correct rollback\n for (let i = this.commands.length - 1; i >= 0; i--) {\n this.commands[i].undo();\n }\n }\n}\n\n/**\n * CommandHistory - Manages undo/redo history\n */\nexport class CommandHistory {\n history: Command[];\n currentIndex: number;\n maxSize: number;\n\n constructor(maxSize: number = 50) {\n this.history = []; // Array of executed commands\n this.currentIndex = -1; // Current position in history\n this.maxSize = maxSize;\n }\n\n /**\n * Execute a command and add it to history\n */\n execute(command: Command): void {\n // Execute the command\n command.execute();\n\n // Remove any commands after current index (redo history)\n this.history = this.history.slice(0, this.currentIndex + 1);\n\n // Add new command to history\n this.history.push(command);\n this.currentIndex++;\n\n // Limit history size\n if (this.history.length > this.maxSize) {\n this.history.shift();\n this.currentIndex--;\n }\n }\n\n /**\n * Execute multiple commands as a single atomic undo entry.\n * All commands are wrapped in a CompoundCommand so that\n * undo/redo treats the entire batch as one operation.\n *\n * No-op if commands array is empty.\n */\n executeBatch(commands: Command[]): void {\n if (commands.length === 0) {\n return;\n }\n\n const compound = new CompoundCommand(commands);\n this.execute(compound);\n }\n\n /**\n * Undo the last command\n */\n undo(): boolean {\n if (!this.canUndo()) {\n return false;\n }\n\n const command = this.history[this.currentIndex];\n command.undo();\n this.currentIndex--;\n return true;\n }\n\n /**\n * Redo the next command\n */\n redo(): boolean {\n if (!this.canRedo()) {\n return false;\n }\n\n this.currentIndex++;\n const command = this.history[this.currentIndex];\n command.execute();\n return true;\n }\n\n /**\n * Check if undo is available\n */\n canUndo(): boolean {\n return this.currentIndex >= 0;\n }\n\n /**\n * Check if redo is available\n */\n canRedo(): boolean {\n return this.currentIndex < this.history.length - 1;\n }\n\n /**\n * Clear all history\n */\n clear(): void {\n this.history = [];\n this.currentIndex = -1;\n }\n\n /**\n * Get current state info\n */\n getState(): { canUndo: boolean; canRedo: boolean; historySize: number; currentIndex: number } {\n return {\n canUndo: this.canUndo(),\n canRedo: this.canRedo(),\n historySize: this.history.length,\n currentIndex: this.currentIndex,\n };\n }\n}\n\n/**\n * CreateArtboardCommand - Command to create a new artboard\n */\nexport class CreateArtboardCommand extends Command {\n artboard: ArtboardElement;\n artboardManager: ArtboardManager;\n\n constructor(artboard: ArtboardElement, artboardManager: ArtboardManager) {\n super();\n this.artboard = artboard.clone();\n this.artboardManager = artboardManager;\n }\n\n execute(): void {\n // Re-create the artboard in the manager\n const artboard = this.artboard.clone();\n this.artboardManager.createArtboard({\n id: artboard.id,\n name: artboard.name,\n x: artboard.x,\n y: artboard.y,\n width: artboard.width,\n height: artboard.height,\n backgroundColor: artboard.backgroundColor,\n });\n }\n\n undo(): void {\n this.artboardManager.deleteArtboard(this.artboard.id);\n }\n}\n\n/**\n * DeleteArtboardCommand - Command to delete an artboard\n */\nexport class DeleteArtboardCommand extends Command {\n artboard: ArtboardElement;\n artboardManager: ArtboardManager;\n elementsOnArtboard: string[];\n\n constructor(artboard: ArtboardElement, artboardManager: ArtboardManager) {\n super();\n this.artboard = artboard.clone();\n this.artboardManager = artboardManager;\n this.elementsOnArtboard = artboard.getElementIds();\n }\n\n execute(): void {\n this.artboardManager.deleteArtboard(this.artboard.id);\n }\n\n undo(): void {\n // Re-create the artboard\n const artboard = this.artboard.clone();\n this.artboardManager.createArtboard({\n id: artboard.id,\n name: artboard.name,\n x: artboard.x,\n y: artboard.y,\n width: artboard.width,\n height: artboard.height,\n backgroundColor: artboard.backgroundColor,\n elementIds: this.elementsOnArtboard,\n });\n\n // Restore element mappings\n this.elementsOnArtboard.forEach((elementId) => {\n this.artboardManager.addElementToArtboard(elementId, artboard.id);\n });\n }\n}\n\n/**\n * UpdateArtboardCommand - Command to update artboard properties\n */\nexport class UpdateArtboardCommand extends Command {\n artboardId: string;\n oldProperties: Partial<ArtboardElement>;\n newProperties: Partial<ArtboardElement>;\n artboardManager: ArtboardManager;\n\n constructor(\n artboardId: string,\n oldProperties: Partial<ArtboardElement>,\n newProperties: Partial<ArtboardElement>,\n artboardManager: ArtboardManager\n ) {\n super();\n this.artboardId = artboardId;\n this.oldProperties = { ...oldProperties };\n this.newProperties = { ...newProperties };\n this.artboardManager = artboardManager;\n }\n\n execute(): void {\n this.artboardManager.updateArtboard(this.artboardId, this.newProperties);\n }\n\n undo(): void {\n this.artboardManager.updateArtboard(this.artboardId, this.oldProperties);\n }\n}\n\n/**\n * ReorderElementCommand - Command to reorder elements in the list\n */\nexport class ReorderElementCommand extends Command {\n draggedId: string;\n targetId: string;\n position: 'before' | 'after';\n oldOrder: string[];\n newOrder: string[];\n onReorder: (elementIds: string[]) => void;\n\n constructor(\n draggedId: string,\n targetId: string,\n position: 'before' | 'after',\n currentOrder: string[],\n onReorder: (elementIds: string[]) => void\n ) {\n super();\n this.draggedId = draggedId;\n this.targetId = targetId;\n this.position = position;\n this.oldOrder = [...currentOrder];\n this.onReorder = onReorder;\n\n // Calculate new order\n this.newOrder = this.calculateNewOrder();\n }\n\n private calculateNewOrder(): string[] {\n const order = [...this.oldOrder];\n\n // Remove dragged element\n const draggedIndex = order.indexOf(this.draggedId);\n if (draggedIndex === -1) return order;\n order.splice(draggedIndex, 1);\n\n // Find target index in the new array (after removal)\n const targetIndex = order.indexOf(this.targetId);\n if (targetIndex === -1) return order;\n\n // Insert at the appropriate position\n const insertIndex = this.position === 'before' ? targetIndex : targetIndex + 1;\n order.splice(insertIndex, 0, this.draggedId);\n\n return order;\n }\n\n execute(): void {\n this.onReorder(this.newOrder);\n }\n\n undo(): void {\n this.onReorder(this.oldOrder);\n }\n}\n\nexport default CommandHistory;\n","/**\n * HybridHistoryManager - Two-tier undo/redo system\n *\n * Manages:\n * - Global history: Artboard create/delete/rename operations\n * - Per-artboard history: Element operations within each artboard\n *\n * Smart undo/redo automatically determines which history to use based on:\n * - Active artboard\n * - Last operation type\n */\n\nimport { CommandHistory, Command } from './CommandHistory.js';\nimport type { ArtboardManager } from './ArtboardManager.js';\nimport { createLogger } from '../utils/logger.js';\n\nconst logger = createLogger('HybridHistoryManager');\n\ntype HistoryType = 'global' | 'artboard';\n\ninterface HistoryEntry {\n type: HistoryType;\n artboardId?: string; // For artboard-level operations\n timestamp: number;\n}\n\nexport class HybridHistoryManager {\n private globalHistory: CommandHistory;\n private artboardHistories: Map<string, CommandHistory>;\n private lastOperation: HistoryEntry | null;\n private artboardManager: ArtboardManager;\n private maxHistorySize: number;\n private commandExecutedCallbacks: Array<(command: Command, type: HistoryType, artboardId?: string) => void> = [];\n\n constructor(artboardManager: ArtboardManager, maxHistorySize: number = 50) {\n this.globalHistory = new CommandHistory(maxHistorySize);\n this.artboardHistories = new Map();\n this.lastOperation = null;\n this.artboardManager = artboardManager;\n this.maxHistorySize = maxHistorySize;\n }\n\n /**\n * Register a callback to be called whenever a command is executed\n * Returns an unsubscribe function\n */\n onCommandExecuted(callback: (command: Command, type: HistoryType, artboardId?: string) => void): () => void {\n this.commandExecutedCallbacks.push(callback);\n return () => {\n const index = this.commandExecutedCallbacks.indexOf(callback);\n if (index > -1) {\n this.commandExecutedCallbacks.splice(index, 1);\n }\n };\n }\n\n /**\n * Notify all callbacks that a command was executed\n */\n private notifyCommandExecuted(command: Command, type: HistoryType, artboardId?: string): void {\n for (const callback of this.commandExecutedCallbacks) {\n try {\n callback(command, type, artboardId);\n } catch (error) {\n logger.error('Error in command executed callback:', error);\n }\n }\n }\n\n /**\n * Execute a global command (artboard-level operation)\n */\n executeGlobal(command: Command): void {\n this.globalHistory.execute(command);\n this.lastOperation = {\n type: 'global',\n timestamp: Date.now(),\n };\n this.notifyCommandExecuted(command, 'global');\n }\n\n /**\n * Execute a batch of commands as a single global undo entry.\n * No-op if commands array is empty.\n */\n executeBatchGlobal(commands: Command[]): void {\n if (commands.length === 0) {\n return;\n }\n this.globalHistory.executeBatch(commands);\n this.lastOperation = {\n type: 'global',\n timestamp: Date.now(),\n };\n // Notify with the compound command that was created\n const compoundCommand = this.globalHistory.history[this.globalHistory.currentIndex];\n this.notifyCommandExecuted(compoundCommand, 'global');\n }\n\n /**\n * Execute a batch of commands as a single undo entry on a specific artboard.\n * No-op if commands array is empty.\n */\n executeBatchOnArtboard(artboardId: string, commands: Command[]): void {\n if (commands.length === 0) {\n return;\n }\n let history = this.artboardHistories.get(artboardId);\n if (!history) {\n history = new CommandHistory(this.maxHistorySize);\n this.artboardHistories.set(artboardId, history);\n }\n history.executeBatch(commands);\n this.lastOperation = {\n type: 'artboard',\n artboardId,\n timestamp: Date.now(),\n };\n const compoundCommand = history.history[history.currentIndex];\n this.notifyCommandExecuted(compoundCommand, 'artboard', artboardId);\n }\n\n /**\n * Execute a command on a specific artboard\n */\n executeOnArtboard(artboardId: string, command: Command): void {\n // Get or create history for this artboard\n let history = this.artboardHistories.get(artboardId);\n if (!history) {\n history = new CommandHistory(this.maxHistorySize);\n this.artboardHistories.set(artboardId, history);\n }\n\n history.execute(command);\n this.lastOperation = {\n type: 'artboard',\n artboardId,\n timestamp: Date.now(),\n };\n this.notifyCommandExecuted(command, 'artboard', artboardId);\n }\n\n /**\n * Smart undo - determines which history to use\n */\n undo(): boolean {\n // If there's a last operation, undo from that history\n if (this.lastOperation) {\n if (this.lastOperation.type === 'global') {\n return this.undoGlobal();\n } else if (this.lastOperation.artboardId) {\n return this.undoArtboard(this.lastOperation.artboardId);\n }\n }\n\n // Otherwise, try active artboard first, then global\n const activeArtboardId = this.artboardManager.getActiveArtboardId();\n if (activeArtboardId && this.canUndoArtboard(activeArtboardId)) {\n return this.undoArtboard(activeArtboardId);\n }\n\n if (this.canUndoGlobal()) {\n return this.undoGlobal();\n }\n\n return false;\n }\n\n /**\n * Smart redo - determines which history to use\n */\n redo(): boolean {\n // If there's a last operation, redo from that history\n if (this.lastOperation) {\n if (this.lastOperation.type === 'global') {\n return this.redoGlobal();\n } else if (this.lastOperation.artboardId) {\n return this.redoArtboard(this.lastOperation.artboardId);\n }\n }\n\n // Otherwise, try active artboard first, then global\n const activeArtboardId = this.artboardManager.getActiveArtboardId();\n if (activeArtboardId && this.canRedoArtboard(activeArtboardId)) {\n return this.redoArtboard(activeArtboardId);\n }\n\n if (this.canRedoGlobal()) {\n return this.redoGlobal();\n }\n\n return false;\n }\n\n /**\n * Undo global operation\n */\n undoGlobal(): boolean {\n const result = this.globalHistory.undo();\n if (result) {\n this.updateLastOperation('global');\n }\n return result;\n }\n\n /**\n * Redo global operation\n */\n redoGlobal(): boolean {\n const result = this.globalHistory.redo();\n if (result) {\n this.updateLastOperation('global');\n }\n return result;\n }\n\n /**\n * Undo artboard operation\n */\n undoArtboard(artboardId: string): boolean {\n const history = this.artboardHistories.get(artboardId);\n if (!history) {\n return false;\n }\n\n const result = history.undo();\n if (result) {\n this.updateLastOperation('artboard', artboardId);\n }\n return result;\n }\n\n /**\n * Redo artboard operation\n */\n redoArtboard(artboardId: string): boolean {\n const history = this.artboardHistories.get(artboardId);\n if (!history) {\n return false;\n }\n\n const result = history.redo();\n if (result) {\n this.updateLastOperation('artboard', artboardId);\n }\n return result;\n }\n\n /**\n * Check if global undo is available\n */\n canUndoGlobal(): boolean {\n return this.globalHistory.canUndo();\n }\n\n /**\n * Check if global redo is available\n */\n canRedoGlobal(): boolean {\n return this.globalHistory.canRedo();\n }\n\n /**\n * Check if artboard undo is available\n */\n canUndoArtboard(artboardId: string): boolean {\n const history = this.artboardHistories.get(artboardId);\n return history ? history.canUndo() : false;\n }\n\n /**\n * Check if artboard redo is available\n */\n canRedoArtboard(artboardId: string): boolean {\n const history = this.artboardHistories.get(artboardId);\n return history ? history.canRedo() : false;\n }\n\n /**\n * Check if any undo is available (smart check)\n */\n canUndo(): boolean {\n // Check last operation first\n if (this.lastOperation) {\n if (this.lastOperation.type === 'global') {\n return this.canUndoGlobal();\n } else if (this.lastOperation.artboardId) {\n return this.canUndoArtboard(this.lastOperation.artboardId);\n }\n }\n\n // Otherwise check active artboard or global\n const activeArtboardId = this.artboardManager.getActiveArtboardId();\n if (activeArtboardId && this.canUndoArtboard(activeArtboardId)) {\n return true;\n }\n\n return this.canUndoGlobal();\n }\n\n /**\n * Check if any redo is available (smart check)\n */\n canRedo(): boolean {\n // Check last operation first\n if (this.lastOperation) {\n if (this.lastOperation.type === 'global') {\n return this.canRedoGlobal();\n } else if (this.lastOperation.artboardId) {\n return this.canRedoArtboard(this.lastOperation.artboardId);\n }\n }\n\n // Otherwise check active artboard or global\n const activeArtboardId = this.artboardManager.getActiveArtboardId();\n if (activeArtboardId && this.canRedoArtboard(activeArtboardId)) {\n return true;\n }\n\n return this.canRedoGlobal();\n }\n\n /**\n * Clear all histories\n */\n clear(): void {\n this.globalHistory.clear();\n this.artboardHistories.clear();\n this.lastOperation = null;\n }\n\n /**\n * Clear history for a specific artboard\n */\n clearArtboardHistory(artboardId: string): void {\n this.artboardHistories.delete(artboardId);\n if (this.lastOperation?.artboardId === artboardId) {\n this.lastOperation = null;\n }\n }\n\n /**\n * Get history for a specific artboard (create if doesn't exist)\n */\n getArtboardHistory(artboardId: string): CommandHistory {\n let history = this.artboardHistories.get(artboardId);\n if (!history) {\n history = new CommandHistory(this.maxHistorySize);\n this.artboardHistories.set(artboardId, history);\n }\n return history;\n }\n\n /**\n * Get global history\n */\n getGlobalHistory(): CommandHistory {\n return this.globalHistory;\n }\n\n /**\n * Get state of all histories\n */\n getState(): {\n global: {\n canUndo: boolean;\n canRedo: boolean;\n historySize: number;\n };\n artboards: Map<\n string,\n {\n canUndo: boolean;\n canRedo: boolean;\n historySize: number;\n }\n >;\n lastOperation: HistoryEntry | null;\n } {\n const globalState = this.globalHistory.getState();\n const artboardStates = new Map();\n\n this.artboardHistories.forEach((history, artboardId) => {\n const state = history.getState();\n artboardStates.set(artboardId, {\n canUndo: state.canUndo,\n canRedo: state.canRedo,\n historySize: state.historySize,\n });\n });\n\n return {\n global: {\n canUndo: globalState.canUndo,\n canRedo: globalState.canRedo,\n historySize: globalState.historySize,\n },\n artboards: artboardStates,\n lastOperation: this.lastOperation,\n };\n }\n\n /**\n * Update last operation tracker\n */\n private updateLastOperation(type: HistoryType, artboardId?: string): void {\n this.lastOperation = {\n type,\n artboardId,\n timestamp: Date.now(),\n };\n }\n\n /**\n * Get description of what will be undone\n */\n getUndoDescription(): string {\n if (this.lastOperation) {\n if (this.lastOperation.type === 'global') {\n return 'Undo artboard operation';\n } else if (this.lastOperation.artboardId) {\n const artboard = this.artboardManager.getArtboard(this.lastOperation.artboardId);\n return artboard ? `Undo in \"${artboard.name}\"` : 'Undo element operation';\n }\n }\n\n const activeArtboardId = this.artboardManager.getActiveArtboardId();\n if (activeArtboardId && this.canUndoArtboard(activeArtboardId)) {\n const artboard = this.artboardManager.getArtboard(activeArtboardId);\n return artboard ? `Undo in \"${artboard.name}\"` : 'Undo element operation';\n }\n\n if (this.canUndoGlobal()) {\n return 'Undo artboard operation';\n }\n\n return 'Nothing to undo';\n }\n\n /**\n * Get description of what will be redone\n */\n getRedoDescription(): string {\n if (this.lastOperation) {\n if (this.lastOperation.type === 'global') {\n return 'Redo artboard operation';\n } else if (this.lastOperation.artboardId) {\n const artboard = this.artboardManager.getArtboard(this.lastOperation.artboardId);\n return artboard ? `Redo in \"${artboard.name}\"` : 'Redo element operation';\n }\n }\n\n const activeArtboardId = this.artboardManager.getActiveArtboardId();\n if (activeArtboardId && this.canRedoArtboard(activeArtboardId)) {\n const artboard = this.artboardManager.getArtboard(activeArtboardId);\n return artboard ? `Redo in \"${artboard.name}\"` : 'Redo element operation';\n }\n\n if (this.canRedoGlobal()) {\n return 'Redo artboard operation';\n }\n\n return 'Nothing to redo';\n }\n}\n\nexport default HybridHistoryManager;\n"],"names":["getDefaultExportFromCjs","x","browser","process","cachedSetTimeout","cachedClearTimeout","defaultSetTimout","defaultClearTimeout","runTimeout","fun","runClearTimeout","marker","queue","draining","currentQueue","queueIndex","cleanUpNextTick","drainQueue","timeout","len","args","i","Item","array","noop","name","dir","browserExports","process$1","nextArtboardId","ArtboardElement","config","px","py","element","bbox","elementId","tolerance","inXRange","inYRange","nearLeft","nearRight","nearTop","nearBottom","updates","newWidth","newHeight","newX","newY","ArtboardManager","artboard","artboardId","elementIds","remainingArtboards","previousArtboardId","previousArtboard","y","artboards","fromIndex","toIndex","entries","moved","a","data","elements","e","_artboardId","validElementIds","id","RotationUtils","degrees","normalized","nextId","BaseElement","value","visualBbox","rotationAnchor","rotationRad","cos","sin","dx","dy","localX","localY","transformedX","transformedY","ctx","m","newRotation","_a","Transform","worldX","worldY","rad","rotX","rotY","pointX","pointY","anchorX","anchorY","angleDegrees","elementSnapshot","TSHIRT_FONTS","getFontNames","f","CATEGORY_LABELS","getThemeAccentColor","target","getCurrentTheme","getThemeShapeFillColor","getThemeSpacingColor","getThemeAccentColorWithAlpha","alpha","color","hex","r","g","b","getThemeTextSelectionColor","getThemeAccentHoverColor","getThemeTooltipBackground","getThemeTooltipForeground","getThemeTooltipShadowColor","bg","getThemeArtboardBorderColor","getThemeArtboardLabelColor","getThemeHoverBorderColor","getThemePenPathColor","getThemePenAnchorFillColor","getThemePenHandleColor","PREVIEW_ELEMENT_OPACITY","DEFAULT_ARTBOARD_COLOR","ROTATION_HANDLE_DISTANCE","CORNER_HANDLE_VISUAL_RADIUS","EDGE_HANDLE_VISUAL_LENGTH","CORNER_HANDLE_HIT_RADIUS","EDGE_HANDLE_LENGTH","EDGE_HANDLE_HIT_WIDTH","SPACING_SNAP_THRESHOLD","SPACING_LABEL_TEXT_COLOR","SPACING_LABEL_BORDER_RADIUS","MIN_CROP_SIZE","FONT_FAMILIES","FONT_SIZES","HORIZONTAL_PADDING","LINE_HEIGHT_MULTIPLIER","MIN_WIDTH","MIN_FONT_SIZE","MAX_FONT_SIZE","SYSTEM_FONTS","WAVE_SKEW_FACTOR","LogLevel","Logger","level","enabled","_message","_args","message","scopeName","ScopedLogger","logger","createLogger","listeners","subscribeToImageLoads","listener","emitImageLoaded","error","ImageCache","url","existing","img","entry","totalRefs","IMAGE_LOAD_TIMEOUT_MS","IMAGE_LOAD_MAX_RETRIES","IMAGE_LOAD_BASE_DELAY_MS","imageTransformData","startData","ImageElement","_b","_c","_d","_e","_f","_g","_h","_i","_j","isSvg","settled","settle","originalOnload","originalOnerror","ev","delay","tempImg","canvas","blob","cropWidth","cropHeight","localOffsetX","localOffsetY","flipH","flipV","worldOffsetX","worldOffsetY","frameCenterX","frameCenterY","local","halfWidth","halfHeight","halfCropWidth","halfCropHeight","_isSelected","_isHovered","borderRadius","radius","fullImageX","fullImageY","bgColor","textColor","anchor","widthScale","heightScale","proposedScale","effectiveAnchor","finalScale","currentScaleRelativeToStart","oldCropCenterXLocal","oldCropCenterYLocal","worldOffset","fixedCornerWorldX","fixedCornerWorldY","fixedCornerLocalX","fixedCornerLocalY","fixedCornerWorldOffset","fixedCornerWorldOffsetX","fixedCornerWorldOffsetY","newFixedCornerLocalX","newFixedCornerLocalY","newFixedCornerWorldOffset","newFixedCornerWorldOffsetX","newFixedCornerWorldOffsetY","newFrameCenterX","newFrameCenterY","oldCropCenterWorldX","oldCropCenterWorldY","oldCropWorldWidth","oldCropWorldHeight","newCropWidthNorm","newCropHeightNorm","localOffset","cropCenterXLocal","cropCenterYLocal","newCropX","newCropY","newCropCenterXLocal","newCropCenterYLocal","newLocalOffsetX","newLocalOffsetY","newWorldOffset","newWorldOffsetX","newWorldOffsetY","cropBox","corner","flippedX","flippedY","rotatedX","rotatedY","zoom","hitScale","CORNER_HIT_RADIUS_SCALED","EDGE_LENGTH_SCALED","EDGE_WIDTH_SCALED","corners","worldCorner","edges","edge","worldEdge","cropX","cropY","adjustPosition","clampedWidth","clampedHeight","clampedX","clampedY","oldCropCenterX","oldCropCenterY","newCropCenterX","newCropCenterY","width","height","halfW","halfH","fixedCornerLocal","startFrameCenter","scale","fixedCornerWorld","newFrameWidth","newFrameHeight","cropWorldWidth","cropWorldHeight","cropNormWidth","cropNormHeight","newFrameCenter","cropCenterWorld","cropCenterLocalX","cropCenterLocalY","cropState","cropLeft","cropRight","cropTop","cropBottom","currentScale","lo","hi","testScale","json","imageUrl","configWithoutUrl","cloned","RichText","spans","text","style","s","charIndex","currentIndex","span","start","end","newSpans","spanStart","spanEnd","overlapStart","overlapEnd","index","offset","length","deleteStart","deleteEnd","merged","current","next","property","isCustomTransform","isCircleTransform","isArchTransform","isWaveTransform","isFlagTransform","isImageTransform","isGroupTransform","isTextElementConfig","isImageElementConfig","isCustomElementConfig","isCircleElementConfig","isGroupElementConfig","isShapeElementConfig","isPathElementConfig","isShapeTransform","isPathTransform","hasStroke","hasMasks","isKnockout","hasDistressEffect","TextElement","_ctx","Constructor","newText","newRichText","richText","newFontSize","newColor","oldColor","_anchor","_newWidth","_newHeight","_startData","_measureCanvas","getMeasureCanvas","buildFontString","fontSize","fontFamily","bold","italic","parts","wrapText","maxWidth","lockedLineCount","strict","explicitLines","allWrappedLines","line","wrappedLine","leadingSpacesMatch","trailingSpacesMatch","leadingSpaces","trailingSpaces","words","lines","wordsPerLine","lineWords","allText","allTextWithSpaces","allTextWidth","isSmallSize","wrapTolerance","currentLine","lineTolerance","word","chars","charBuffer","char","testBuffer","testLine","firstLineWithSpace","lastIdx","lastLineWithSpace","measureTextWidth","getFontMetrics","metrics","ascent","descent","calculateVisualBoundsWithSpaceCollapsing","hasExplicitNewlines","paragraphMetadata","_","maxLineWidth","fontMetrics","totalLineCount","isParagraphStart","isParagraphEnd","visibleText","leadingSpacesCount","trailingSpacesCount","lineWidth","lineHeight","lineSpacing","createOffscreenCanvas","applyStrokeToChar","stroke","prevAlpha","renderTransformWithOffscreenStroke","elementData","renderFn","strokeOpacity","elementAlpha","hasStrokeOpacity","hasElementOpacity","strokeWidth","td","textEstimate","containerSpan","estimatedSpan","featherPad","padding","estWidth","archExtra","radiusExtra","estHeight","transform","offW","offH","offCanvas","offCtx","elemX","elemY","centerPixelX","centerPixelY","drawBackX","drawBackY","savedStrokeOpa","savedOpacity","savedEnabled","renderCircleTransform","renderCircleTransformDirect","transformData","weight","charWidths","totalWidth","sum","w","currentAngle","angleStep","renderWaveTransform","renderWaveTransformDirect","currentX","charWidth","normalizedX","slope","renderArchTransform","renderArchTransformDirect","renderAscendTransform","renderAscendTransformDirect","angleRad","renderLeanTransform","renderLeanTransformDirect","skewAngle","renderFlagTransform","renderFlagTransformDirect","distanceFromCenter","flagAmplitude","cosComponent","sinComponent","createTextPath","_renderingContext","textElement","textWidth","actualWidth","createImagePath","maxRadius","createCirclePath","createRectPath","applyStrokeStyle","renderTextStroke","renderingContext","isKnockoutRender","renderTextStrokeViaOffscreen","renderTextStrokeDirect","feather","customWidth","originalOpacity","textAlign","availableWidth","visualHeight","topLeftX","topLeftY","xPos","yPos","renderImageStroke","renderPathStroke","pathFn","log","fontkitModule","getFontkit","FontAnalyzerService","oldestKey","oldestTime","key","cssUrl","css","fontFaceRegex","fontFaces","bestUrl","bestScore","fontFaceContent","urlMatch","hasUnicodeRange","unicodeRangeMatch","score","cacheKey","cached","fontUrl","response","arrayBuffer","buffer","fontOrCollection","font","limit","glyphs","maxGlyphs","glyphId","glyph","codePoints","unicode","baseCharMatch","friendlyName","num","category","nameLower","character","alternates","codePoint","defaultGlyph","baseName","foundCount","pattern","gsub","feature","tag","lookupIndex","lookup","subtable","coverage","substituteGlyphIndex","coverageIndex","altSubtable","alternateSet","altGlyphIndex","features","uniqueFeatures","glyphIndex","size","glyphWidth","glyphHeight","glyphCenterX","glyphCenterY","canvasCenterX","canvasCenterY","svgPath","path2D","ranges","range","substitute","count","FontAnalyzer","renderTextWithOpenTypeFeaturesSync","options","featureArray","availableFeatures","feat","featuresObject","run","startX","position","pathData","dMatch","renderTextWithGlyphOverridesSync","glyphOverrides","hasPUA","str","cp","overrideMap","override","wrapRichTextSpans","getMeasureContext","measureText","stylesMatch","s1","s2","mergedSpans","fullText","charToSpan","spanIndex","currentWord","wordStart","currentLineWidth","finishLine","_wordIdx","wordSpans","currentSpanIdx","currentText","charIdx","spanIdx","wordWidth","lastSpan","splitRichTextIntoLines","part","renderRichTextFillOnly","lineSpans","wrappedLines","idx","lineMetrics","lineIndex","spanWidths","lineAscent","fullLineText","adjustedLineWidth","charIndexInLine","spanStartIndex","spanEndIndex","hiddenCharsInSpan","trailingSpaceStartIndex","leadingTrimAmount","visibleCharsInSpan","elementFontMetrics","totalHeight","lineData","baselineY","underline","strikethrough","renderText","renderedWidth","underlineY","strikethroughY","_measureCtx","applySpaceLayoutRules","renderTextFillOnly","lineText","renderMultilineText","alignment","containerWidth","horizontalPadding","openTypeFeatures","containsPUACharacters","hasOpenTypeFeatures","hasGlyphOverrides","hasPUACharacters","needsFontkitRendering","fontkitFont","err","charOffset","lineLength","lineGlyphOverrides","relativeIndex","underlineX","strikethroughX","renderCustomTransform","renderCustomTransformNormal","decorationX","strikeY","renderTextElement","elementOpacity","renderTextElementWithOffscreen","previousAlpha","renderTextElementInner","prev","getTransformWidth","setTransformWidth","handleCornerResize","startTransformData","startTransformWidth","startWidth","calculatedFontSize","scaledRichText","scaledCharSize","elemTransformData","scaledWidth","handleSideResize","oldWidth","widthChange","standardResize","isCornerHandle","isSideHandle","CustomTransform","wLine","maxFontHeight","maxAscent","spanFontSize","spanFontFamily","spanBold","spanItalic","elementMetrics","standardHeight","extraAscent","extraDescent","visualWidth","serialized","offsetX","offsetY","minWidth","calculateFixedCornerPosition","newDimensions","activeAnchor","rotation","fixedLocalX","fixedLocalY","fixedWorldX","fixedWorldY","newFixedLocalX","newFixedLocalY","calculateRotationHandlePosition","calculateResizeHandles","createHandle","type","worldPos","cursor","getCursorForWorldPosition","hitTestCircle","cx","cy","hitTestRect","rect","translatedX","translatedY","getBoundingBoxCenter","calculateAngle","centerX","centerY","isNear","value1","value2","threshold","handleWorldX","handleWorldY","centerWorldX","centerWorldY","handleType","angle","normalizedAngle","getCanvasScale","setZoomInvariantStroke","dashPattern","CircleTransform","effectiveRadius","diameter","effectiveFontSize","arcAngle","startAngle","endAngle","innerRadius","outerRadius","minX","maxX","minY","maxY","samples","innerX","innerY","outerX","outerY","circleStartData","avgDimension","startAvgDimension","scaleChange","newScale","clampedScale","newPosition","targetSize","newBaseFontSize","ArchTransform","archHeightAbs","yOffset","skewFactor","_char","lineY","skewedX","skewedY","archStartData","deltaWidth","centerShift","WAVE_DEFAULTS","FLAG_DEFAULTS","ARCH_DEFAULTS","LEAN_DEFAULTS","ASCEND_DEFAULTS","WaveTransform","amplitudeAbs","FlagTransform","flagStartData","LeanTransform","leanAmountAbs","skewX","leanStartData","AscendTransform","rise","ascendStartData","ShapeElement","_k","defaultShapeType","shapeType","sides","points","fillOpacity","radiusX","radiusY","innerRadiusRatio","thickness","fixedCorner","oppositeAnchor","newBbox","fixedCornerOffset","startBbox","fixedX","fixedY","PathElement","localBounds","p0","p1","p2","p3","t","mt","mt2","mt3","t2","t3","currentPoint","nextPoint","u","point","halfStroke","bounds","closed","fillEnabled","fillColor","strokeEnabled","strokeColor","centerOffsetX","centerOffsetY","firstPoint","cp1x","cp1y","cp2x","cp2y","oldHeight","scaleX","scaleY","startPoints","p","TRANSFORM_CONTROLS","internal","slider","TRANSFORM_TYPES","GroupElement","BUILTIN_IDS","_dynamicTransforms","registerTransform","def","unregisterTransform","getTransformById","builtin","getTransformControls","rawChildren","child","transformType","transformDef","forceRecalcDimensions","dims","isSelected","isHovered","visibleChildren","children","segments","currentContent","segment","contentCanvas","contentCtx","maskCanvas","maskCtx","opacity","obb","deltaRotation","rotated","localFixedX","localFixedY","childStart","childTransform","newChildWidth","newChildHeight","clonedChildren","clonedGroup","childId","c","ElementStore","el","result","afterId","oid","newIndex","predicate","filtered","fn","mapped","newOrder","store","Command","UpdateElementCommand","oldElement","newElement","onUpdate","AddElementCommand","onAdd","onRemove","RemoveElementCommand","CompoundCommand","commands","cmd","CommandHistory","maxSize","command","compound","CreateArtboardCommand","artboardManager","DeleteArtboardCommand","UpdateArtboardCommand","oldProperties","newProperties","ReorderElementCommand","draggedId","targetId","currentOrder","onReorder","order","draggedIndex","targetIndex","insertIndex","HybridHistoryManager","maxHistorySize","callback","compoundCommand","history","activeArtboardId","globalState","artboardStates","state"],"mappings":"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","x_google_ignoreList":[0]}
|
|
1
|
+
{"version":3,"file":"HybridHistoryManager-jBBnVim8.js","sources":["../../../node_modules/.pnpm/vite-plugin-node-polyfills@0.24.0_rollup@4.60.3_vite@5.4.21_@types+node@25.7.0_lightningcss@1.32.0_terser@5.47.1_/node_modules/vite-plugin-node-polyfills/shims/process/dist/index.js","../src/core/ArtboardElement.ts","../src/core/ArtboardManager.ts","../src/core/RotationUtils.ts","../src/core/BaseElement.ts","../src/core/Transform.ts","../src/fonts/google-fonts.ts","../src/constants.ts","../src/utils/logger.ts","../src/core/ImageLoadEvents.ts","../src/core/ImageCache.ts","../src/core/ImageElement.ts","../src/types/index.ts","../src/core/TextElement.ts","../src/core/TextMetrics.ts","../src/rendering/transform-renderer.ts","../src/rendering/stroke-utils.ts","../src/rendering/StrokeRenderer.ts","../src/utils/FontAnalyzer.ts","../src/utils/GlyphRenderer.ts","../src/rendering/rich-text-renderer.ts","../src/rendering/text-renderer.ts","../src/core/ResizeUtils.ts","../src/transforms/CustomTransform.ts","../src/core/GeometryUtils.ts","../src/transforms/CircleTransform.ts","../src/transforms/ArchTransform.ts","../src/transforms/defaults.ts","../src/transforms/WaveTransform.ts","../src/transforms/FlagTransform.ts","../src/transforms/LeanTransform.ts","../src/transforms/AscendTransform.ts","../src/core/ShapeElement.ts","../src/core/PathElement.ts","../src/transforms/registry.ts","../src/core/GroupElement.ts","../src/core/ElementStore.ts","../src/core/CommandHistory.ts","../src/core/HybridHistoryManager.ts"],"sourcesContent":["function getDefaultExportFromCjs (x) {\n\treturn x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;\n}\n\nvar browser = {exports: {}};\n\n// shim for using process in browser\nvar process = browser.exports = {};\n\n// cached from whatever global is present so that test runners that stub it\n// don't break things. But we need to wrap it in a try catch in case it is\n// wrapped in strict mode code which doesn't define any globals. It's inside a\n// function because try/catches deoptimize in certain engines.\n\nvar cachedSetTimeout;\nvar cachedClearTimeout;\n\nfunction defaultSetTimout() {\n throw new Error('setTimeout has not been defined');\n}\nfunction defaultClearTimeout () {\n throw new Error('clearTimeout has not been defined');\n}\n(function () {\n try {\n if (typeof setTimeout === 'function') {\n cachedSetTimeout = setTimeout;\n } else {\n cachedSetTimeout = defaultSetTimout;\n }\n } catch (e) {\n cachedSetTimeout = defaultSetTimout;\n }\n try {\n if (typeof clearTimeout === 'function') {\n cachedClearTimeout = clearTimeout;\n } else {\n cachedClearTimeout = defaultClearTimeout;\n }\n } catch (e) {\n cachedClearTimeout = defaultClearTimeout;\n }\n} ());\nfunction runTimeout(fun) {\n if (cachedSetTimeout === setTimeout) {\n //normal enviroments in sane situations\n return setTimeout(fun, 0);\n }\n // if setTimeout wasn't available but was latter defined\n if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) {\n cachedSetTimeout = setTimeout;\n return setTimeout(fun, 0);\n }\n try {\n // when when somebody has screwed with setTimeout but no I.E. maddness\n return cachedSetTimeout(fun, 0);\n } catch(e){\n try {\n // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally\n return cachedSetTimeout.call(null, fun, 0);\n } catch(e){\n // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error\n return cachedSetTimeout.call(this, fun, 0);\n }\n }\n\n\n}\nfunction runClearTimeout(marker) {\n if (cachedClearTimeout === clearTimeout) {\n //normal enviroments in sane situations\n return clearTimeout(marker);\n }\n // if clearTimeout wasn't available but was latter defined\n if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) {\n cachedClearTimeout = clearTimeout;\n return clearTimeout(marker);\n }\n try {\n // when when somebody has screwed with setTimeout but no I.E. maddness\n return cachedClearTimeout(marker);\n } catch (e){\n try {\n // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally\n return cachedClearTimeout.call(null, marker);\n } catch (e){\n // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error.\n // Some versions of I.E. have different rules for clearTimeout vs setTimeout\n return cachedClearTimeout.call(this, marker);\n }\n }\n\n\n\n}\nvar queue = [];\nvar draining = false;\nvar currentQueue;\nvar queueIndex = -1;\n\nfunction cleanUpNextTick() {\n if (!draining || !currentQueue) {\n return;\n }\n draining = false;\n if (currentQueue.length) {\n queue = currentQueue.concat(queue);\n } else {\n queueIndex = -1;\n }\n if (queue.length) {\n drainQueue();\n }\n}\n\nfunction drainQueue() {\n if (draining) {\n return;\n }\n var timeout = runTimeout(cleanUpNextTick);\n draining = true;\n\n var len = queue.length;\n while(len) {\n currentQueue = queue;\n queue = [];\n while (++queueIndex < len) {\n if (currentQueue) {\n currentQueue[queueIndex].run();\n }\n }\n queueIndex = -1;\n len = queue.length;\n }\n currentQueue = null;\n draining = false;\n runClearTimeout(timeout);\n}\n\nprocess.nextTick = function (fun) {\n var args = new Array(arguments.length - 1);\n if (arguments.length > 1) {\n for (var i = 1; i < arguments.length; i++) {\n args[i - 1] = arguments[i];\n }\n }\n queue.push(new Item(fun, args));\n if (queue.length === 1 && !draining) {\n runTimeout(drainQueue);\n }\n};\n\n// v8 likes predictible objects\nfunction Item(fun, array) {\n this.fun = fun;\n this.array = array;\n}\nItem.prototype.run = function () {\n this.fun.apply(null, this.array);\n};\nprocess.title = 'browser';\nprocess.browser = true;\nprocess.env = {};\nprocess.argv = [];\nprocess.version = ''; // empty string to avoid regexp issues\nprocess.versions = {};\n\nfunction noop() {}\n\nprocess.on = noop;\nprocess.addListener = noop;\nprocess.once = noop;\nprocess.off = noop;\nprocess.removeListener = noop;\nprocess.removeAllListeners = noop;\nprocess.emit = noop;\nprocess.prependListener = noop;\nprocess.prependOnceListener = noop;\n\nprocess.listeners = function (name) { return [] };\n\nprocess.binding = function (name) {\n throw new Error('process.binding is not supported');\n};\n\nprocess.cwd = function () { return '/' };\nprocess.chdir = function (dir) {\n throw new Error('process.chdir is not supported');\n};\nprocess.umask = function() { return 0; };\n\nvar browserExports = browser.exports;\nconst process$1 = /*@__PURE__*/getDefaultExportFromCjs(browserExports);\n\nexport { process$1 as default, process$1 as process };\n//# sourceMappingURL=index.js.map\n","/**\n * ArtboardElement - Container for elements with fixed bounds (like Figma frames)\n * Unlike GroupElement, artboards have:\n * - Fixed dimensions (width/height)\n * - Background color\n * - Export boundaries\n * - Named for organization\n */\n\nimport type { TextElement } from './TextElement.js';\nimport type { ImageElement } from './ImageElement.js';\nimport type { GroupElement } from './GroupElement.js';\nimport type { BoundingBox, Point, ArtboardConfig, ArtboardBackgroundType, ArtboardDistressTexture, ArtboardImageMask, ClipShape } from '../types/index.js';\n\nlet nextArtboardId = 1;\n\nexport class ArtboardElement {\n id: string;\n name: string;\n x: number;\n y: number;\n width: number;\n height: number;\n backgroundColor: string;\n backgroundType: ArtboardBackgroundType;\n backgroundTexture?: string;\n exportBackground: boolean;\n transformType: 'artboard' = 'artboard';\n\n // Clip shape for artboard content (affects both rendering and export)\n clipShape?: ClipShape;\n\n // Preview only - NOT exported to PNG (for t-shirt color preview)\n previewBackgroundColor?: string;\n\n // Artboard-level distress texture\n distressTexture?: ArtboardDistressTexture;\n\n // Artboard-level image mask\n imageMask?: ArtboardImageMask;\n\n // Children elements (stored by ID for serialization, actual elements managed externally)\n private elementIds: Set<string>;\n\n constructor(config: Partial<ArtboardConfig> = {}) {\n this.id = config.id || `artboard-${nextArtboardId++}`;\n this.name = config.name || `Artboard ${nextArtboardId}`;\n this.x = config.x !== undefined ? config.x : 0;\n this.y = config.y !== undefined ? config.y : 0;\n this.width = config.width || 1920;\n this.height = config.height || 1080;\n this.backgroundColor = config.backgroundColor || '#ffffff';\n\n // Determine background type from config or backgroundColor\n if (config.backgroundType) {\n this.backgroundType = config.backgroundType;\n } else if (config.backgroundColor === 'transparent') {\n this.backgroundType = 'transparent';\n } else if (config.backgroundTexture) {\n this.backgroundType = 'texture';\n } else {\n this.backgroundType = 'color';\n }\n\n this.backgroundTexture = config.backgroundTexture;\n this.exportBackground = config.exportBackground ?? false; // Defaults to false\n this.clipShape = config.clipShape; // undefined means no clipping (same as 'rectangle')\n this.previewBackgroundColor = config.previewBackgroundColor;\n this.distressTexture = config.distressTexture ? { ...config.distressTexture } : undefined;\n this.imageMask = config.imageMask ? { ...config.imageMask } : undefined;\n this.elementIds = new Set(config.elementIds || []);\n }\n\n /**\n * Get bounding box of the artboard\n */\n getBoundingBox(): BoundingBox {\n return {\n x: this.x,\n y: this.y,\n width: this.width,\n height: this.height,\n };\n }\n\n /**\n * Get center point of the artboard\n */\n getCenter(): Point {\n return {\n x: this.x + this.width / 2,\n y: this.y + this.height / 2,\n };\n }\n\n /**\n * Check if a point is within artboard bounds\n */\n containsPoint(px: number, py: number): boolean {\n return px >= this.x && px <= this.x + this.width && py >= this.y && py <= this.y + this.height;\n }\n\n /**\n * Check if an element's bounding box is within artboard bounds\n */\n containsElement(element: TextElement | ImageElement | GroupElement): boolean {\n const bbox = element.getBoundingBox();\n return (\n bbox.x >= this.x &&\n bbox.y >= this.y &&\n bbox.x + bbox.width <= this.x + this.width &&\n bbox.y + bbox.height <= this.y + this.height\n );\n }\n\n /**\n * Add an element ID to this artboard\n */\n addElementId(elementId: string): void {\n this.elementIds.add(elementId);\n }\n\n /**\n * Remove an element ID from this artboard\n */\n removeElementId(elementId: string): void {\n this.elementIds.delete(elementId);\n }\n\n /**\n * Check if artboard contains an element ID\n */\n hasElementId(elementId: string): boolean {\n return this.elementIds.has(elementId);\n }\n\n /**\n * Get all element IDs in this artboard\n */\n getElementIds(): string[] {\n return Array.from(this.elementIds);\n }\n\n /**\n * Get number of elements in artboard\n */\n getElementCount(): number {\n return this.elementIds.size;\n }\n\n /**\n * Clear all element IDs\n */\n clearElementIds(): void {\n this.elementIds.clear();\n }\n\n /**\n * Test if a point hits the artboard border (for selection)\n * Returns true if point is near the border (within tolerance)\n */\n hitTestBorder(px: number, py: number, tolerance: number = 5): boolean {\n const inXRange = px >= this.x - tolerance && px <= this.x + this.width + tolerance;\n const inYRange = py >= this.y - tolerance && py <= this.y + this.height + tolerance;\n\n // Check if point is near any edge\n const nearLeft = Math.abs(px - this.x) <= tolerance && inYRange;\n const nearRight = Math.abs(px - (this.x + this.width)) <= tolerance && inYRange;\n const nearTop = Math.abs(py - this.y) <= tolerance && inXRange;\n const nearBottom = Math.abs(py - (this.y + this.height)) <= tolerance && inXRange;\n\n return nearLeft || nearRight || nearTop || nearBottom;\n }\n\n /**\n * Clone the artboard\n */\n clone(): ArtboardElement {\n return new ArtboardElement({\n id: this.id,\n name: this.name,\n x: this.x,\n y: this.y,\n width: this.width,\n height: this.height,\n backgroundColor: this.backgroundColor,\n backgroundType: this.backgroundType,\n backgroundTexture: this.backgroundTexture,\n exportBackground: this.exportBackground,\n clipShape: this.clipShape,\n previewBackgroundColor: this.previewBackgroundColor,\n distressTexture: this.distressTexture ? { ...this.distressTexture } : undefined,\n imageMask: this.imageMask ? { ...this.imageMask } : undefined,\n elementIds: Array.from(this.elementIds),\n });\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): ArtboardConfig {\n return {\n id: this.id,\n name: this.name,\n x: this.x,\n y: this.y,\n width: this.width,\n height: this.height,\n backgroundColor: this.backgroundColor,\n backgroundType: this.backgroundType,\n ...(this.backgroundTexture && { backgroundTexture: this.backgroundTexture }),\n exportBackground: this.exportBackground,\n elementIds: Array.from(this.elementIds),\n transformType: 'artboard' as const,\n // Clip shape for content clipping\n ...(this.clipShape && { clipShape: this.clipShape }),\n // Preview only - saved to project file, but NOT exported to PNG\n ...(this.previewBackgroundColor && { previewBackgroundColor: this.previewBackgroundColor }),\n // Artboard-level distress texture\n ...(this.distressTexture && { distressTexture: { ...this.distressTexture } }),\n // Artboard-level image mask\n ...(this.imageMask && { imageMask: { ...this.imageMask } }),\n };\n }\n\n /**\n * Create from JSON\n */\n static fromJSON(config: ArtboardConfig): ArtboardElement {\n return new ArtboardElement(config);\n }\n\n /**\n * Update artboard properties\n */\n updateProperties(\n updates: Partial<{\n name: string;\n x: number;\n y: number;\n width: number;\n height: number;\n backgroundColor: string;\n backgroundType: ArtboardBackgroundType;\n backgroundTexture: string;\n exportBackground: boolean;\n clipShape: ClipShape;\n distressTexture: ArtboardDistressTexture | undefined;\n imageMask: ArtboardImageMask | undefined;\n }>\n ): void {\n if (updates.name !== undefined) this.name = updates.name;\n if (updates.x !== undefined) this.x = updates.x;\n if (updates.y !== undefined) this.y = updates.y;\n if (updates.width !== undefined) this.width = Math.max(100, updates.width); // Min width\n if (updates.height !== undefined) this.height = Math.max(100, updates.height); // Min height\n if (updates.backgroundColor !== undefined) this.backgroundColor = updates.backgroundColor;\n if (updates.backgroundType !== undefined) this.backgroundType = updates.backgroundType;\n if (updates.backgroundTexture !== undefined) this.backgroundTexture = updates.backgroundTexture;\n if (updates.exportBackground !== undefined) this.exportBackground = updates.exportBackground;\n if (updates.clipShape !== undefined) this.clipShape = updates.clipShape;\n if ('distressTexture' in updates) this.distressTexture = updates.distressTexture ? { ...updates.distressTexture } : undefined;\n if ('imageMask' in updates) this.imageMask = updates.imageMask ? { ...updates.imageMask } : undefined;\n }\n\n /**\n * Resize artboard (with constraints)\n */\n resize(newWidth: number, newHeight: number): void {\n this.width = Math.max(100, newWidth);\n this.height = Math.max(100, newHeight);\n }\n\n /**\n * Move artboard\n */\n move(newX: number, newY: number): void {\n this.x = newX;\n this.y = newY;\n }\n}\n\nexport default ArtboardElement;\n","/**\n * ArtboardManager - Manages artboard lifecycle and element-to-artboard mapping\n * Handles creation, deletion, and tracking of artboards\n */\n\nimport { ArtboardElement } from './ArtboardElement.js';\nimport type { TextElement } from './TextElement.js';\nimport type { ImageElement } from './ImageElement.js';\nimport type { GroupElement } from './GroupElement.js';\nimport type { ArtboardConfig } from '../types/index.js';\n\nexport class ArtboardManager {\n private artboards: Map<string, ArtboardElement>;\n private elementToArtboard: Map<string, string>; // elementId -> artboardId\n private activeArtboardId: string | null;\n\n constructor() {\n this.artboards = new Map();\n this.elementToArtboard = new Map();\n this.activeArtboardId = null;\n }\n\n /**\n * Create a new artboard\n *\n * NOTE: This method sets the newly created artboard as active. When creating\n * multiple artboards in a loop (e.g., during initialization), the LAST artboard\n * will end up as active. If you need a specific artboard to be active after\n * batch creation, call setActiveArtboard() explicitly afterward.\n *\n * SnowconeCanvas handles this via hasInitialSyncRef - it suppresses onArtboardChange\n * callbacks until the controlled activeArtboard prop has been synced.\n */\n createArtboard(config: Partial<ArtboardConfig> = {}): ArtboardElement {\n const artboard = new ArtboardElement(config);\n this.artboards.set(artboard.id, artboard);\n\n // Always set newly created artboard as active\n // See note above about implications for batch creation\n this.activeArtboardId = artboard.id;\n\n return artboard;\n }\n\n /**\n * Delete an artboard\n * Returns the IDs of elements that were on the artboard\n */\n deleteArtboard(artboardId: string): string[] {\n const artboard = this.artboards.get(artboardId);\n if (!artboard) {\n return [];\n }\n\n // Get all element IDs from the artboard\n const elementIds = artboard.getElementIds();\n\n // Remove element-to-artboard mappings\n elementIds.forEach((elementId) => {\n this.elementToArtboard.delete(elementId);\n });\n\n // Remove artboard\n this.artboards.delete(artboardId);\n\n // Update active artboard if deleted\n if (this.activeArtboardId === artboardId) {\n // Set to first available artboard or null\n const remainingArtboards = Array.from(this.artboards.keys());\n this.activeArtboardId = remainingArtboards.length > 0 ? remainingArtboards[0] : null;\n }\n\n return elementIds;\n }\n\n /**\n * Get an artboard by ID\n */\n getArtboard(artboardId: string): ArtboardElement | undefined {\n return this.artboards.get(artboardId);\n }\n\n /**\n * Get all artboards\n */\n getAllArtboards(): ArtboardElement[] {\n return Array.from(this.artboards.values());\n }\n\n /**\n * Get artboard IDs\n */\n getArtboardIds(): string[] {\n return Array.from(this.artboards.keys());\n }\n\n /**\n * Set active artboard\n */\n setActiveArtboard(artboardId: string | null): void {\n if (artboardId === null || this.artboards.has(artboardId)) {\n this.activeArtboardId = artboardId;\n }\n }\n\n /**\n * Get active artboard\n */\n getActiveArtboard(): ArtboardElement | null {\n return this.activeArtboardId ? this.artboards.get(this.activeArtboardId) || null : null;\n }\n\n /**\n * Get active artboard ID\n */\n getActiveArtboardId(): string | null {\n return this.activeArtboardId;\n }\n\n /**\n * Add an element to an artboard\n */\n addElementToArtboard(elementId: string, artboardId: string): boolean {\n const artboard = this.artboards.get(artboardId);\n if (!artboard) {\n return false;\n }\n\n // Remove from previous artboard if exists\n const previousArtboardId = this.elementToArtboard.get(elementId);\n if (previousArtboardId) {\n const previousArtboard = this.artboards.get(previousArtboardId);\n previousArtboard?.removeElementId(elementId);\n }\n\n // Add to new artboard\n artboard.addElementId(elementId);\n this.elementToArtboard.set(elementId, artboardId);\n return true;\n }\n\n /**\n * Remove an element from its artboard\n */\n removeElementFromArtboard(elementId: string): void {\n const artboardId = this.elementToArtboard.get(elementId);\n if (artboardId) {\n const artboard = this.artboards.get(artboardId);\n artboard?.removeElementId(elementId);\n this.elementToArtboard.delete(elementId);\n }\n }\n\n /**\n * Get the artboard that contains an element\n */\n getArtboardForElement(elementId: string): ArtboardElement | null {\n const artboardId = this.elementToArtboard.get(elementId);\n return artboardId ? this.artboards.get(artboardId) || null : null;\n }\n\n /**\n * Get artboard ID for an element\n */\n getArtboardIdForElement(elementId: string): string | null {\n return this.elementToArtboard.get(elementId) || null;\n }\n\n /**\n * Get all elements on an artboard\n * Note: Returns element IDs, not actual element objects\n */\n getElementsOnArtboard(artboardId: string): string[] {\n const artboard = this.artboards.get(artboardId);\n return artboard ? artboard.getElementIds() : [];\n }\n\n /**\n * Find artboard at a point (for selection)\n */\n findArtboardAtPoint(x: number, y: number): ArtboardElement | null {\n // Check in reverse order (top artboard first)\n const artboards = Array.from(this.artboards.values()).reverse();\n\n for (const artboard of artboards) {\n if (artboard.containsPoint(x, y)) {\n return artboard;\n }\n }\n\n return null;\n }\n\n /**\n * Find artboard border at point (for resize handles)\n */\n findArtboardBorderAtPoint(x: number, y: number, tolerance: number = 5): ArtboardElement | null {\n const artboards = Array.from(this.artboards.values()).reverse();\n\n for (const artboard of artboards) {\n if (artboard.hitTestBorder(x, y, tolerance)) {\n return artboard;\n }\n }\n\n return null;\n }\n\n /**\n * Determine which artboard an element should belong to based on position\n */\n findArtboardForPosition(x: number, y: number): ArtboardElement | null {\n // Find first artboard that contains this point\n return this.findArtboardAtPoint(x, y);\n }\n\n /**\n * Update artboard properties\n */\n updateArtboard(artboardId: string, updates: Partial<ArtboardConfig>): boolean {\n const artboard = this.artboards.get(artboardId);\n if (!artboard) {\n return false;\n }\n\n artboard.updateProperties(updates);\n return true;\n }\n\n /**\n * Rename an artboard\n */\n renameArtboard(artboardId: string, name: string): boolean {\n return this.updateArtboard(artboardId, { name });\n }\n\n /**\n * Check if an artboard exists\n */\n hasArtboard(artboardId: string): boolean {\n return this.artboards.has(artboardId);\n }\n\n /**\n * Get artboard count\n */\n getArtboardCount(): number {\n return this.artboards.size;\n }\n\n /**\n * Reorder artboards by moving the artboard at fromIndex to toIndex.\n * Preserves Map insertion order by rebuilding the internal Map.\n */\n reorderArtboards(fromIndex: number, toIndex: number): void {\n const entries = Array.from(this.artboards.entries());\n if (fromIndex < 0 || fromIndex >= entries.length) return;\n if (toIndex < 0 || toIndex >= entries.length) return;\n if (fromIndex === toIndex) return;\n\n const [moved] = entries.splice(fromIndex, 1);\n entries.splice(toIndex, 0, moved);\n\n this.artboards = new Map(entries);\n }\n\n /**\n * Clear all artboards\n */\n clear(): void {\n this.artboards.clear();\n this.elementToArtboard.clear();\n this.activeArtboardId = null;\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): {\n artboards: ArtboardConfig[];\n activeArtboardId: string | null;\n } {\n return {\n artboards: Array.from(this.artboards.values()).map((a) => a.toJSON()),\n activeArtboardId: this.activeArtboardId,\n };\n }\n\n /**\n * Load from JSON\n */\n fromJSON(data: { artboards: ArtboardConfig[]; activeArtboardId: string | null }): void {\n this.clear();\n\n // Restore artboards\n data.artboards.forEach((config) => {\n const artboard = ArtboardElement.fromJSON(config);\n this.artboards.set(artboard.id, artboard);\n\n // Restore element mappings\n artboard.getElementIds().forEach((elementId) => {\n this.elementToArtboard.set(elementId, artboard.id);\n });\n });\n\n // Restore active artboard\n this.activeArtboardId = data.activeArtboardId;\n }\n\n /**\n * Validate element-to-artboard mappings\n * Ensures all elements have valid artboard assignments\n */\n validateMappings(elements: (TextElement | ImageElement | GroupElement)[]): void {\n const elementIds = new Set(elements.map((e) => e.id));\n\n // Remove mappings for elements that no longer exist\n for (const [elementId, _artboardId] of this.elementToArtboard.entries()) {\n if (!elementIds.has(elementId)) {\n this.removeElementFromArtboard(elementId);\n }\n }\n\n // Ensure all artboards have valid element IDs\n for (const artboard of this.artboards.values()) {\n const validElementIds = artboard.getElementIds().filter((id) => elementIds.has(id));\n artboard.clearElementIds();\n validElementIds.forEach((id) => artboard.addElementId(id));\n }\n }\n}\n\nexport default ArtboardManager;\n","/**\n * RotationUtils - Centralized rotation convention utilities\n *\n * Convention: Clockwise rotations in UI are represented as positive degrees,\n * but canvas rendering uses counter-clockwise rotation (hence the negative sign).\n */\nexport const RotationUtils = {\n /**\n * Forward transform (local → world, for rendering)\n * Converts degrees to radians with the canvas convention (negative = clockwise)\n * @param {number} degrees - Rotation in degrees (positive = clockwise in UI)\n * @returns {number} Rotation in radians for canvas context\n */\n toRadians(degrees: number): number {\n return (-degrees * Math.PI) / 180;\n },\n\n /**\n * Inverse transform (world → local, for hit testing)\n * Converts degrees to radians without negation\n * @param {number} degrees - Rotation in degrees\n * @returns {number} Rotation in radians\n */\n toRadiansInverse(degrees: number): number {\n return (degrees * Math.PI) / 180;\n },\n\n /**\n * Normalize angle to -180 to +180 range\n * @param {number} degrees - Angle in degrees\n * @returns {number} Normalized angle in range [-180, 180]\n */\n normalize(degrees: number): number {\n let normalized = ((degrees % 360) + 360) % 360;\n if (normalized > 180) {\n normalized -= 360;\n }\n return normalized;\n },\n};\n","/**\n * BaseElement - Abstract base class for all canvas elements\n * Defines common interface and shared behavior for all element types\n */\n\nimport { RotationUtils } from './RotationUtils.js';\nimport type {\n TransformType,\n AnyTransformData,\n BoundingBox,\n Point,\n ResizeAnchor,\n BlendMode,\n KnockoutConfig,\n StrokeConfig,\n MaskDefinition,\n DistressEffect,\n BaseElementConfig,\n TransformStartData,\n} from '../types/index.js';\n\nlet nextId = 1;\n\n/**\n * Reset the ID counter (useful for testing)\n */\nexport function resetElementIdCounter(): void {\n nextId = 1;\n}\n\nexport abstract class BaseElement {\n id: string;\n x: number;\n y: number;\n rotation: number;\n opacity: number;\n transformType: TransformType;\n transformData: AnyTransformData;\n\n // Effects properties\n blendMode?: BlendMode;\n knockoutParts?: KnockoutConfig;\n stroke?: StrokeConfig;\n masks?: MaskDefinition[];\n distressEffect?: DistressEffect;\n\n // Layer properties\n name?: string;\n visible?: boolean;\n locked?: boolean;\n\n /** Whether this element clips content below it (derived from blendMode === 'clip') */\n get isClipping(): boolean {\n return this.blendMode === 'clip';\n }\n\n set isClipping(value: boolean | undefined) {\n if (value) {\n this.blendMode = 'clip';\n if (!this.knockoutParts) {\n this.knockoutParts = { fill: true, scope: 'group' };\n }\n } else if (this.blendMode === 'clip') {\n this.blendMode = 'normal';\n }\n }\n\n constructor(config: Partial<BaseElementConfig> = {}) {\n this.id = config.id || `element-${nextId++}`;\n this.x = config.x !== undefined ? config.x : 100;\n this.y = config.y !== undefined ? config.y : 100;\n this.rotation = config.rotation !== undefined ? config.rotation : 0;\n this.opacity = config.opacity !== undefined ? config.opacity : 1;\n\n // Transform-specific data (overridden by subclasses)\n this.transformType = config.transformType || 'custom';\n this.transformData = (config.transformData as AnyTransformData) || ({} as AnyTransformData);\n\n // Effects properties\n this.blendMode = config.blendMode;\n this.knockoutParts = config.knockoutParts;\n this.stroke = config.stroke;\n this.masks = config.masks;\n this.distressEffect = config.distressEffect;\n\n // Legacy migration: isClipping without blendMode → set blendMode = 'clip'\n if (config.isClipping && !config.blendMode) {\n this.blendMode = 'clip';\n if (!this.knockoutParts) {\n this.knockoutParts = { fill: true, scope: 'group' };\n }\n }\n\n // Layer properties\n this.name = config.name;\n this.visible = config.visible !== undefined ? config.visible : true;\n this.locked = config.locked !== undefined ? config.locked : false;\n }\n\n /**\n * Get bounding box in world coordinates\n * Used for transform math (resize, rotation, handles)\n * Must be implemented by subclasses\n */\n abstract getBoundingBox(): BoundingBox;\n\n /**\n * Get visual bounding box (tight fit around actual rendered content)\n * Used for selection display - should match what user sees\n * Default: same as getBoundingBox(), override for tighter fit\n */\n getVisualBoundingBox(): BoundingBox {\n return this.getBoundingBox();\n }\n\n /**\n * Get rotation anchor point (the point around which element rotates)\n * Default: returns element's position (x, y)\n * Override if rotation should be around a different point\n */\n getRotationAnchor(): Point {\n return { x: this.x, y: this.y };\n }\n\n /**\n * Test if a point hits this element\n * Uses visual bounding box for better user experience\n */\n hitTest(px: number, py: number): boolean {\n const visualBbox = this.getVisualBoundingBox();\n const rotationAnchor = this.getRotationAnchor();\n\n // Transform point to local coordinates (undo rotation)\n const rotationRad = RotationUtils.toRadiansInverse(this.rotation);\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Translate to rotation anchor\n const dx = px - rotationAnchor.x;\n const dy = py - rotationAnchor.y;\n\n // Rotate\n const localX = dx * cos - dy * sin;\n const localY = dx * sin + dy * cos;\n\n // Translate back\n const transformedX = rotationAnchor.x + localX;\n const transformedY = rotationAnchor.y + localY;\n\n // Test against axis-aligned visual bbox\n return (\n transformedX >= visualBbox.x &&\n transformedX <= visualBbox.x + visualBbox.width &&\n transformedY >= visualBbox.y &&\n transformedY <= visualBbox.y + visualBbox.height\n );\n }\n\n /**\n * Render the element\n * Must be implemented by subclasses\n */\n abstract render(ctx: CanvasRenderingContext2D, isSelected?: boolean, isHovered?: boolean): void;\n\n /**\n * Apply canvas transform (translate, rotate)\n * Common implementation for all elements\n */\n applyCanvasTransform(ctx: CanvasRenderingContext2D): void {\n ctx.translate(this.x, this.y);\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n }\n\n /**\n * Serialize to JSON\n * Returns base element properties - subclasses should extend this\n */\n toJSON(): BaseElementConfig {\n return {\n id: this.id,\n x: this.x,\n y: this.y,\n rotation: this.rotation,\n opacity: this.opacity,\n transformType: this.transformType,\n transformData: { ...this.transformData },\n // Effects properties (only include if defined)\n ...(this.blendMode && { blendMode: this.blendMode }),\n ...(this.knockoutParts && { knockoutParts: { ...this.knockoutParts } }),\n ...(this.stroke && { stroke: { ...this.stroke } }),\n ...(this.masks && { masks: this.masks.map((m) => ({ ...m })) }),\n ...(this.distressEffect && { distressEffect: { ...this.distressEffect } }),\n // Layer properties (only include if defined)\n ...(this.name && { name: this.name }),\n ...(this.visible !== undefined && { visible: this.visible }),\n ...(this.locked !== undefined && { locked: this.locked }),\n ...(this.isClipping && { isClipping: true }),\n };\n }\n\n /**\n * Clone this element\n * Uses toJSON() to serialize and reconstruct\n */\n abstract clone(): BaseElement;\n\n /**\n * Update position during drag\n */\n move(dx: number, dy: number): void {\n this.x += dx;\n this.y += dy;\n }\n\n /**\n * Update rotation\n */\n setRotation(newRotation: number): void {\n this.rotation = newRotation;\n }\n\n /**\n * Get enabled resize anchors for this element type\n * Default: all 8 anchors\n * Override in subclasses if needed\n */\n getEnabledAnchors(): ResizeAnchor[] {\n return [\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n 'middle-left',\n 'middle-right',\n 'middle-top',\n 'middle-bottom',\n ];\n }\n\n /**\n * Handle resize transform\n * Must be implemented by subclasses\n */\n abstract resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): void | boolean;\n\n /**\n * Called when transform starts\n * Returns data to be passed to resize()\n */\n getTransformStartData(): TransformStartData {\n const bbox = this.getBoundingBox();\n return {\n id: this.id,\n x: this.x,\n y: this.y,\n width: bbox.width,\n height: bbox.height,\n rotation: this.rotation,\n transformData: { ...this.transformData },\n // Capture stroke width for proportional scaling during resize\n strokeWidth: this.stroke?.width,\n };\n }\n}\n\nexport default BaseElement;\n","/**\n * Transform - Centralized coordinate transformation utilities\n *\n * ROTATION CONVENTION:\n * -------------------\n * - UI: Positive rotation = CLOCKWISE (e.g., +45° rotates 45° clockwise)\n * - Canvas rendering: Uses ctx.rotate() with NEGATIVE angle (negative = clockwise in canvas)\n * - Mathematical convention: Standard rotation matrices\n *\n * COORDINATE SPACES:\n * -----------------\n * - WORLD space: The canvas coordinate system (absolute positions)\n * - LOCAL space: The element's own coordinate system (relative to element center, unrotated)\n *\n * TRANSFORMS:\n * ----------\n * - FORWARD transform (local → world): Used for rendering, uses NEGATIVE angle\n * - INVERSE transform (world → local): Used for hit testing and input, uses POSITIVE angle\n *\n * WHY THE SIGN DIFFERENCE?\n * -----------------------\n * Canvas rotation is counter-clockwise by default. To make positive rotations appear\n * clockwise in the UI, we negate the angle when rendering. For coordinate transforms:\n * - To go from local→world (rendering), we use the RENDERING angle (negative)\n * - To go from world→local (input), we UNDO the rendering rotation (positive)\n *\n * EXAMPLE:\n * -------\n * An element at (100, 100) rotated 45° clockwise:\n * - Rendering: ctx.rotate((-45 * Math.PI) / 180) // negative = clockwise\n * - Mouse at (150, 120) in world coords\n * - Convert to local: use +45° to undo the -45° rendering rotation\n * - Result: local coordinates relative to element's center\n */\n\n/** Minimal interface for objects that can be used with Transform */\nexport interface Transformable {\n x: number;\n y: number;\n rotation: number;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- extensible interface for elements with varying properties\n [key: string]: any;\n}\n\nexport class Transform {\n element: Transformable;\n\n /**\n * Create a transform helper for an element\n * @param element - Element with x, y, rotation properties\n */\n constructor(element: Transformable) {\n this.element = element;\n }\n\n /**\n * Convert a point from world coordinates to local coordinates\n * This is an INVERSE transform (world → local)\n * Uses positive angle with standard rotation matrix formula\n *\n * @param worldX - X coordinate in world space\n * @param worldY - Y coordinate in world space\n * @returns Point in local coordinates\n */\n worldToLocal(worldX: number, worldY: number) {\n // Get positive angle for inverse transform\n const rad = (this.element.rotation * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n\n // Translate to element's origin\n const dx = worldX - this.element.x;\n const dy = worldY - this.element.y;\n\n // Apply rotation (same formula as forward, but this represents viewing from rotated frame)\n return {\n x: dx * cos - dy * sin,\n y: dx * sin + dy * cos,\n };\n }\n\n /**\n * Convert a point from local coordinates to world coordinates\n * This is a FORWARD transform (local → world)\n * Uses NEGATIVE angle matching the rendering convention\n *\n * @param localX - X coordinate in local space\n * @param localY - Y coordinate in local space\n * @returns Point in world coordinates\n */\n localToWorld(localX: number, localY: number) {\n // Get negative angle for forward transform (matches rendering)\n const rad = (-this.element.rotation * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n\n // Apply forward rotation\n const rotX = localX * cos - localY * sin;\n const rotY = localX * sin + localY * cos;\n\n // Translate to world position\n return {\n x: this.element.x + rotX,\n y: this.element.y + rotY,\n };\n }\n\n /**\n * Convert a delta (movement/offset) from world space to local space\n * This is useful for drag operations - no translation, just rotation\n * Uses positive angle with standard rotation matrix formula\n *\n * @param dx - Delta X in world space\n * @param dy - Delta Y in world space\n * @returns Delta in local space\n */\n worldDeltaToLocal(dx: number, dy: number) {\n const rad = (this.element.rotation * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n\n return {\n dx: dx * cos - dy * sin,\n dy: dx * sin + dy * cos,\n };\n }\n\n /**\n * Convert a delta (movement/offset) from local space to world space\n * Uses NEGATIVE angle (forward transform, matches rendering)\n *\n * @param dx - Delta X in local space\n * @param dy - Delta Y in local space\n * @returns Delta in world space\n */\n localDeltaToWorld(dx: number, dy: number) {\n const rad = (-this.element.rotation * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n\n return {\n dx: dx * cos - dy * sin,\n dy: dx * sin + dy * cos,\n };\n }\n\n /**\n * Rotate a point around a custom anchor point\n * Useful for rotation handles and pivot-based operations\n *\n * @param pointX - Point X in world space\n * @param pointY - Point Y in world space\n * @param anchorX - Anchor X in world space\n * @param anchorY - Anchor Y in world space\n * @param angleDegrees - Rotation angle in degrees (positive = clockwise in UI)\n * @returns Rotated point in world space\n */\n static rotatePointAroundAnchor(pointX: number, pointY: number, anchorX: number, anchorY: number, angleDegrees: number) {\n // Use negative angle for forward transform (matches rendering convention)\n const rad = (-angleDegrees * Math.PI) / 180;\n const cos = Math.cos(rad);\n const sin = Math.sin(rad);\n\n // Translate to anchor origin\n const dx = pointX - anchorX;\n const dy = pointY - anchorY;\n\n // Rotate\n const rotX = dx * cos - dy * sin;\n const rotY = dx * sin + dy * cos;\n\n // Translate back\n return {\n x: anchorX + rotX,\n y: anchorY + rotY,\n };\n }\n\n /**\n * Get rotation angle in radians for rendering (forward transform)\n * Returns NEGATIVE angle (positive UI rotation → clockwise canvas rotation)\n *\n * @returns Rotation in radians for use with ctx.rotate()\n */\n getRenderingAngle() {\n return (-this.element.rotation * Math.PI) / 180;\n }\n\n /**\n * Get rotation angle in radians for inverse transforms\n * Returns POSITIVE angle (for converting world → local)\n *\n * @returns Rotation in radians for inverse transforms\n */\n getInverseAngle() {\n return (this.element.rotation * Math.PI) / 180;\n }\n\n /**\n * Get precomputed cos/sin for rendering (forward transform)\n * Useful when you need to do multiple transformations with the same angle\n *\n * @returns Cosine and sine of rendering angle\n */\n getRenderingCosSin() {\n const rad = this.getRenderingAngle();\n return {\n cos: Math.cos(rad),\n sin: Math.sin(rad),\n };\n }\n\n /**\n * Get precomputed cos/sin for inverse transforms\n * Useful when you need to do multiple transformations with the same angle\n *\n * @returns Cosine and sine of inverse angle\n */\n getInverseCosSin() {\n const rad = this.getInverseAngle();\n return {\n cos: Math.cos(rad),\n sin: Math.sin(rad),\n };\n }\n\n /**\n * Create a transform for an element snapshot (from startData)\n * Useful during drag operations when you need to use the original rotation\n *\n * @param elementSnapshot - Object with x, y, rotation properties\n * @returns Transform instance for the snapshot\n */\n static fromSnapshot(elementSnapshot: Transformable) {\n return new Transform(elementSnapshot);\n }\n}\n\nexport default Transform;\n","/**\n * Google Fonts configuration for Snowcone Canvas\n * Extensive list of fonts optimized for t-shirt and merchandise design\n */\n\nexport type FontCategory =\n | 'sans-serif'\n | 'serif'\n | 'slab-serif' // NEW - Bold, chunky serifs (popular for t-shirts!)\n | 'display'\n | 'script'\n | 'vintage'\n | 'decorative'\n | 'monospace'\n | 'system';\n\nexport type FontSource = 'google' | 'monotype' | 'system';\n\nexport interface FontDefinition {\n name: string;\n category: FontCategory;\n weights?: number[]; // Available font weights (e.g., 400, 700)\n\n // Google Fonts (existing)\n googleFont: boolean; // Whether this needs to be loaded from Google Fonts\n\n // Monotype Fonts (NEW)\n monotypeFont?: boolean;\n monotypeId?: string; // For WebFont kit generation\n foundry?: string; // \"Monotype\", \"Linotype\", etc.\n tags?: string[]; // Descriptive tags from Monotype\n preview?: string; // Preview image URL\n\n // Metadata\n source?: FontSource;\n description?: string;\n}\n\n/**\n * Extensive font list for t-shirt design (~212 fonts)\n * Organized by category for easy browsing.\n *\n * Note: We deliberately DO NOT include \"system\" fonts (Arial, Impact,\n * Times New Roman, Helvetica, etc.). Those aren't on Google Fonts and\n * aren't installed on every platform we ship to — Android in\n * particular ships only Roboto + Noto + a handful of others, so any\n * design referencing Impact / Verdana / Tahoma renders as a default\n * sans there. Sticking to Google Fonts means whatever a user picks\n * looks the same on iOS, macOS, Windows, Android, and Chrome OS.\n */\nexport const TSHIRT_FONTS: FontDefinition[] = [\n // Bold/Display Fonts - Perfect for statements and headlines (49 fonts)\n {\n name: 'Bebas Neue',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Modern condensed display',\n },\n {\n name: 'Bevan',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold slab serif display',\n },\n {\n name: 'Bigshot One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold elegant display',\n },\n {\n name: 'Anton',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Super bold display font',\n },\n {\n name: 'Oswald',\n category: 'display',\n weights: [200, 300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Condensed sans-serif',\n },\n {\n name: 'Archivo Black',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Heavy weight display',\n },\n {\n name: 'Alfa Slab One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold slab serif, varsity style',\n },\n {\n name: 'Bowlby One SC',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Blocky display, sports style',\n },\n {\n name: 'Black Ops One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Military/stencil style',\n },\n {\n name: 'Abril Fatface',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold high-contrast display',\n },\n {\n name: 'Bangers',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Comic book style',\n },\n {\n name: 'Titan One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Heavy impactful display',\n },\n {\n name: 'Lilita One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Rounded display font',\n },\n {\n name: 'Fugaz One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold geometric display',\n },\n {\n name: 'Russo One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold sans display',\n },\n {\n name: 'Passion One',\n category: 'display',\n weights: [400, 700, 900],\n googleFont: true,\n description: 'Condensed bold display',\n },\n {\n name: 'Righteous',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: '1980s-style display',\n },\n {\n name: 'Bungee',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold urban/street style',\n },\n {\n name: 'Barlow Condensed',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Condensed sans display',\n },\n {\n name: 'Staatliches',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold condensed display',\n },\n {\n name: 'Rubik Mono One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Blocky monoline display',\n },\n {\n name: 'Ultra',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Ultra bold serif',\n },\n {\n name: 'Audiowide',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Tech/futuristic display',\n },\n {\n name: 'Changa One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Rounded bold display',\n },\n {\n name: 'Lobster',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold script display',\n },\n {\n name: 'Coda',\n category: 'display',\n weights: [400, 800],\n googleFont: true,\n description: 'Bold humanist sans',\n },\n {\n name: 'Teko',\n category: 'display',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Narrow sans display',\n },\n {\n name: 'Fredoka One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Rounded bold display',\n },\n {\n name: 'Caprasimo',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Retro rounded display',\n },\n {\n name: 'Fjalla One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Bold condensed sans',\n },\n {\n name: 'Secular One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Hebrew-inspired display',\n },\n {\n name: 'Saira',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Semi-condensed sans display',\n },\n {\n name: 'Barlow Semi Condensed',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Semi-condensed low-contrast',\n },\n {\n name: 'Exo',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Geometric technological display',\n },\n {\n name: 'Saira Condensed',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Condensed display font',\n },\n {\n name: 'Fredoka',\n category: 'display',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Rounded friendly display',\n },\n {\n name: 'Comfortaa',\n category: 'display',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Rounded geometric display',\n },\n {\n name: 'Questrial',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Simple sans-serif display',\n },\n {\n name: 'Lexend',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Variable sans for readability',\n },\n {\n name: 'Darker Grotesque',\n category: 'display',\n weights: [300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Contemporary grotesque sans',\n },\n {\n name: 'Bakbak One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Heavy condensed display',\n },\n {\n name: 'Turret Road',\n category: 'display',\n weights: [200, 300, 400, 500, 700, 800],\n googleFont: true,\n description: 'Geometric display font',\n },\n {\n name: 'Zilla Slab Highlight',\n category: 'display',\n weights: [400, 700],\n googleFont: true,\n description: 'Highlighted slab serif',\n },\n {\n name: 'Bree Serif',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Upright italic slab',\n },\n {\n name: 'Overpass',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Highway-inspired sans',\n },\n {\n name: 'Archivo',\n category: 'display',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Grotesque sans-serif',\n },\n {\n name: 'Chakra Petch',\n category: 'display',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Futuristic Thai-inspired',\n },\n {\n name: 'Concert One',\n category: 'display',\n weights: [400],\n googleFont: true,\n description: 'Rounded grotesque display',\n },\n\n // Sans Serif - Modern and clean (44 fonts)\n {\n name: 'Roboto',\n category: 'sans-serif',\n weights: [100, 300, 400, 500, 700, 900],\n googleFont: true,\n description: 'Clean, modern sans-serif',\n },\n {\n name: 'Montserrat',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Geometric sans-serif',\n },\n {\n name: 'Open Sans',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Highly readable',\n },\n {\n name: 'Poppins',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Geometric with personality',\n },\n {\n name: 'Work Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Contemporary sans-serif',\n },\n {\n name: 'Raleway',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Elegant sans-serif',\n },\n {\n name: 'Lato',\n category: 'sans-serif',\n weights: [100, 300, 400, 700, 900],\n googleFont: true,\n description: 'Warm sans-serif',\n },\n {\n name: 'Nunito',\n category: 'sans-serif',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Rounded sans-serif',\n },\n {\n name: 'Outfit',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Modern geometric sans',\n },\n {\n name: 'Inter',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Clean interface font',\n },\n {\n name: 'Quicksand',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Friendly rounded sans',\n },\n {\n name: 'Rubik',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Slightly rounded sans',\n },\n {\n name: 'Barlow',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Low-contrast sans',\n },\n {\n name: 'Josefin Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Geometric vintage sans',\n },\n {\n name: 'Exo 2',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Contemporary geometric',\n },\n {\n name: 'Kanit',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Modern loopless Thai',\n },\n {\n name: 'Hind',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Clean humanist sans',\n },\n {\n name: 'Alata',\n category: 'sans-serif',\n weights: [400],\n googleFont: true,\n description: 'Geometric sans',\n },\n {\n name: 'Urbanist',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Geometric low-contrast',\n },\n {\n name: 'Manrope',\n category: 'sans-serif',\n weights: [200, 300, 400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Modern geometric sans',\n },\n {\n name: 'Be Vietnam Pro',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Modern Vietnamese-optimized sans',\n },\n {\n name: 'Belanosima',\n category: 'sans-serif',\n weights: [400, 600, 700],\n googleFont: true,\n description: 'Contemporary sans-serif',\n },\n {\n name: 'DM Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Low-contrast geometric sans',\n },\n {\n name: 'Source Sans 3',\n category: 'sans-serif',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Adobe sans-serif family',\n },\n {\n name: 'Source Sans Pro',\n category: 'sans-serif',\n weights: [200, 300, 400, 600, 700, 900],\n googleFont: true,\n description: 'Adobe sans-serif (legacy)',\n },\n {\n name: 'IBM Plex Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700],\n googleFont: true,\n description: 'IBM corporate sans',\n },\n {\n name: 'Karla',\n category: 'sans-serif',\n weights: [200, 300, 400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Grotesque sans-serif',\n },\n {\n name: 'PT Sans',\n category: 'sans-serif',\n weights: [400, 700],\n googleFont: true,\n description: 'Russian universal sans',\n },\n {\n name: 'Fira Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Mozilla humanist sans',\n },\n {\n name: 'Space Grotesk',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Proportional space age sans',\n },\n {\n name: 'Syne',\n category: 'sans-serif',\n weights: [400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Geometric variable sans',\n },\n {\n name: 'Libre Franklin',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Interpretation of Franklin Gothic',\n },\n {\n name: 'Alegreya Sans',\n category: 'sans-serif',\n weights: [100, 300, 400, 500, 700, 800, 900],\n googleFont: true,\n description: 'Humanist sans companion',\n },\n {\n name: 'Archivo Narrow',\n category: 'sans-serif',\n weights: [400, 500, 600, 700],\n googleFont: true,\n description: 'Narrow grotesque sans',\n },\n {\n name: 'Chivo',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Grotesque sans-serif',\n },\n {\n name: 'Neuton',\n category: 'sans-serif',\n weights: [200, 300, 400, 700, 800],\n googleFont: true,\n description: 'Serif-adjacent sans',\n },\n {\n name: 'Prompt',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Loopless Thai sans',\n },\n {\n name: 'Noto Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Google universal sans',\n },\n {\n name: 'Nunito Sans',\n category: 'sans-serif',\n weights: [200, 300, 400, 600, 700, 800, 900],\n googleFont: true,\n description: 'Rounded sans-serif',\n },\n {\n name: 'Proza Libre',\n category: 'sans-serif',\n weights: [400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Humanist sans-serif',\n },\n {\n name: 'Cabin',\n category: 'sans-serif',\n weights: [400, 500, 600, 700],\n googleFont: true,\n description: 'Humanist sans inspired by Edward Johnston',\n },\n {\n name: 'Signika',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Sans-serif for signage',\n },\n {\n name: 'Public Sans',\n category: 'sans-serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Strong neutral sans',\n },\n {\n name: 'Plus Jakarta Sans',\n category: 'sans-serif',\n weights: [200, 300, 400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Geometric neo-grotesque',\n },\n {\n name: 'Red Hat Display',\n category: 'sans-serif',\n weights: [300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Display variant of Red Hat sans',\n },\n\n // Script/Handwritten - Personal and creative (26 fonts)\n {\n name: 'Pacifico',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Retro surf script',\n },\n {\n name: 'Dancing Script',\n category: 'script',\n weights: [400, 500, 600, 700],\n googleFont: true,\n description: 'Elegant handwriting',\n },\n {\n name: 'Satisfy',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual handwritten',\n },\n {\n name: 'Permanent Marker',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Bold marker style',\n },\n {\n name: 'Caveat',\n category: 'script',\n weights: [400, 500, 600, 700],\n googleFont: true,\n description: 'Modern handwritten',\n },\n {\n name: 'Shadows Into Light',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Friendly handwriting',\n },\n {\n name: 'Kaushan Script',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Bold script',\n },\n {\n name: 'Amatic SC',\n category: 'script',\n weights: [400, 700],\n googleFont: true,\n description: 'Hand-drawn style',\n },\n {\n name: 'Indie Flower',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual handwriting',\n },\n {\n name: 'Cookie',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Brush script',\n },\n {\n name: 'Sacramento',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Elegant script',\n },\n {\n name: 'Allura',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Formal script',\n },\n {\n name: 'Great Vibes',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Elegant calligraphy',\n },\n {\n name: 'Yellowtail',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Flat-nib script',\n },\n {\n name: 'Courgette',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual upright script',\n },\n {\n name: 'Beth Ellen',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Handwritten marker style',\n },\n {\n name: 'Covered By Your Grace',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual handwritten',\n },\n {\n name: 'Nothing You Could Do',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Hand-drawn lettering',\n },\n {\n name: 'Homemade Apple',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Thin marker handwriting',\n },\n {\n name: 'Reenie Beanie',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual handwritten note style',\n },\n {\n name: 'Patrick Hand',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Handwriting font',\n },\n {\n name: 'Architects Daughter',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Architect-style handwriting',\n },\n {\n name: 'Handlee',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual handwriting',\n },\n {\n name: 'Zeyada',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Bouncy handwritten script',\n },\n {\n name: 'Gloria Hallelujah',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Comic-style handwriting',\n },\n {\n name: 'Bad Script',\n category: 'script',\n weights: [400],\n googleFont: true,\n description: 'Casual Cyrillic script',\n },\n\n // Serif - Classic and elegant (28 fonts)\n {\n name: 'Playfair Display',\n category: 'serif',\n weights: [400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'High-contrast elegant',\n },\n {\n name: 'Merriweather',\n category: 'serif',\n weights: [300, 400, 700, 900],\n googleFont: true,\n description: 'Readable serif',\n },\n {\n name: 'Lora',\n category: 'serif',\n weights: [400, 500, 600, 700],\n googleFont: true,\n description: 'Contemporary serif',\n },\n {\n name: 'Crimson Text',\n category: 'serif',\n weights: [400, 600, 700],\n googleFont: true,\n description: 'Classic book typography',\n },\n {\n name: 'PT Serif',\n category: 'serif',\n weights: [400, 700],\n googleFont: true,\n description: 'Transitional serif',\n },\n {\n name: 'Libre Baskerville',\n category: 'serif',\n weights: [400, 700],\n googleFont: true,\n description: 'Classic serif',\n },\n {\n name: 'Bitter',\n category: 'serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Contemporary slab serif',\n },\n {\n name: 'Cormorant Garamond',\n category: 'serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Display serif',\n },\n {\n name: 'Cinzel',\n category: 'serif',\n weights: [400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Classical serif capitals',\n },\n {\n name: 'Zilla Slab',\n category: 'serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Contemporary slab serif',\n },\n {\n name: 'Cardo',\n category: 'serif',\n weights: [400, 700],\n googleFont: true,\n description: 'Large text serif',\n },\n {\n name: 'Spectral',\n category: 'serif',\n weights: [200, 300, 400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Efficient serif',\n },\n {\n name: 'Literata',\n category: 'serif',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Modern serif',\n },\n {\n name: 'Cantata One',\n category: 'serif',\n weights: [400],\n googleFont: true,\n description: 'Humanist slab serif',\n },\n {\n name: 'Bellefair',\n category: 'serif',\n weights: [400],\n googleFont: true,\n description: 'Elegant high-contrast serif',\n },\n {\n name: 'Source Serif Pro',\n category: 'serif',\n weights: [200, 300, 400, 600, 700, 900],\n googleFont: true,\n description: 'Adobe serif family',\n },\n {\n name: 'Cormorant',\n category: 'serif',\n weights: [300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Display serif typeface',\n },\n {\n name: 'Eczar',\n category: 'serif',\n weights: [400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Hybrid serif for screen',\n },\n {\n name: 'Alegreya',\n category: 'serif',\n weights: [400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Humanist serif',\n },\n {\n name: 'Fraunces',\n category: 'serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Variable display serif',\n },\n {\n name: 'Inknut Antiqua',\n category: 'serif',\n weights: [300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Display serif',\n },\n {\n name: 'BioRhyme',\n category: 'serif',\n weights: [200, 300, 400, 700, 800],\n googleFont: true,\n description: 'Rounded slab serif',\n },\n {\n name: 'Slabo 27px',\n category: 'serif',\n weights: [400],\n googleFont: true,\n description: 'Display serif at 27px',\n },\n {\n name: 'Slabo 13px',\n category: 'serif',\n weights: [400],\n googleFont: true,\n description: 'Text serif at 13px',\n },\n {\n name: 'Crimson Pro',\n category: 'serif',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Modern oldstyle serif',\n },\n {\n name: 'Noto Serif',\n category: 'serif',\n weights: [100, 200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Google universal serif',\n },\n {\n name: 'EB Garamond',\n category: 'serif',\n weights: [400, 500, 600, 700, 800],\n googleFont: true,\n description: 'Classical Garamond revival',\n },\n {\n name: 'Vollkorn',\n category: 'serif',\n weights: [400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Quiet, moderate serif',\n },\n {\n name: 'Young Serif',\n category: 'serif',\n weights: [400, 500, 600, 700],\n googleFont: true,\n description: 'Modern retro serif',\n },\n\n // Vintage/Retro - Nostalgic and stylish (49 fonts)\n // Art Deco & 1920s-30s Style\n {\n name: 'Monoton',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Art deco display',\n },\n {\n name: 'Limelight',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Art deco geometric sans',\n },\n {\n name: 'Poiret One',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Art deco constructivist',\n },\n {\n name: 'Italiana',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Art deco calligraphic',\n },\n {\n name: 'Federo',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Art nouveau geometric',\n },\n {\n name: 'Dorsa',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Art deco display',\n },\n\n // Groovy/Psychedelic 60s-70s Style\n {\n name: 'Boogaloo',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Groovy rounded display',\n },\n {\n name: 'Gorditas',\n category: 'vintage',\n weights: [400, 700],\n googleFont: true,\n description: 'Bubble slab with hearts',\n },\n {\n name: 'Shrikhand',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Retro curved display',\n },\n {\n name: 'Kumar One',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Rounded bubble display',\n },\n {\n name: 'Chicle',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Bubble gum style',\n },\n {\n name: 'Flavors',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Retro bubble font',\n },\n {\n name: 'Chango',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Groovy rounded bold',\n },\n {\n name: 'Purple Purse',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Hippie funky casual',\n },\n {\n name: 'Super Dream',\n category: 'vintage',\n weights: [400],\n googleFont: false,\n description: '70s groovy bubble font',\n },\n\n // Retro Script & Sign Painting\n {\n name: 'Yesteryear',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Vintage cursive script',\n },\n {\n name: 'Mrs Sheppards',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Old American script',\n },\n {\n name: 'Condiment',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Vintage sign design',\n },\n\n // Western/Cowboy Style\n {\n name: 'Rye',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Western Victorian',\n },\n {\n name: 'Sancreek',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Western slab serif',\n },\n {\n name: 'Smokum',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Western playful slab',\n },\n {\n name: 'Diplomata',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Bold western display',\n },\n {\n name: 'Ewert',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Ornamental wood type',\n },\n {\n name: 'Macondo',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Western carnival style',\n },\n {\n name: 'Wellfleet',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Vintage slab serif',\n },\n {\n name: 'Ranchers',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Western slab serif',\n },\n\n // Circus/Carnival/Theater\n {\n name: 'Bungee Shade',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Layered circus display',\n },\n {\n name: 'Peralta',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Rounded circus style',\n },\n {\n name: 'Kelly Slab',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Retro slab serif',\n },\n {\n name: 'Snippet',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Vintage condensed',\n },\n\n // Retro Gaming & Tech\n {\n name: 'Press Start 2P',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: '8-bit retro gaming',\n },\n {\n name: 'VT323',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Terminal monospace',\n },\n\n // Victorian/Gothic/Medieval\n {\n name: 'UnifrakturCook',\n category: 'vintage',\n weights: [700],\n googleFont: true,\n description: 'Gothic blackletter',\n },\n {\n name: 'Sevillana',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Victorian ornate',\n },\n {\n name: 'Uncial Antiqua',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Medieval uncial',\n },\n {\n name: 'Piedra',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Stone carved display',\n },\n\n // Horror/Heavy Metal\n {\n name: 'Creepster',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Horror Halloween style',\n },\n {\n name: 'Nosifer',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Horror dripping',\n },\n {\n name: 'Eater',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Zombie horror',\n },\n {\n name: 'Metal Mania',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Heavy metal style',\n },\n {\n name: 'Butcherman',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Zombified horror',\n },\n\n // Typewriter & Classic\n {\n name: 'Special Elite',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Typewriter style',\n },\n\n // Soviet/Constructivist\n {\n name: 'Stalinist One',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Soviet constructivist',\n },\n {\n name: 'Ruslan Display',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Russian display style',\n },\n\n // Additional Retro Display\n {\n name: 'Corben',\n category: 'vintage',\n weights: [400, 700],\n googleFont: true,\n description: 'Mid-century rounded',\n },\n {\n name: 'Kranky',\n category: 'vintage',\n weights: [400],\n googleFont: true,\n description: 'Quirky handwritten retro',\n },\n\n // Decorative/Unique (10+ fonts)\n {\n name: 'Orbitron',\n category: 'decorative',\n weights: [400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Futuristic geometric',\n },\n {\n name: 'Playball',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Baseball script',\n },\n {\n name: 'Sedgwick Ave Display',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Graffiti style',\n },\n {\n name: 'Modak',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Rounded heavy display',\n },\n {\n name: 'Fascinate',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Broadway/theater',\n },\n {\n name: 'Bungee Inline',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Inline display',\n },\n {\n name: 'Emblema One',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Decorative caps',\n },\n {\n name: 'Fascinate Inline',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Inline Broadway',\n },\n {\n name: 'New Rocker',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Rock and roll',\n },\n {\n name: 'Bigelow Rules',\n category: 'decorative',\n weights: [400],\n googleFont: true,\n description: 'Playful cursive display',\n },\n\n // Monospace (useful for tech designs)\n {\n name: 'Roboto Mono',\n category: 'monospace',\n weights: [100, 200, 300, 400, 500, 600, 700],\n googleFont: true,\n description: 'Modern monospace',\n },\n {\n name: 'Courier Prime',\n category: 'monospace',\n weights: [400, 700],\n googleFont: true,\n description: 'Typewriter monospace',\n },\n {\n name: 'Space Mono',\n category: 'monospace',\n weights: [400, 700],\n googleFont: true,\n description: 'Retro-futuristic mono',\n },\n {\n name: 'Inconsolata',\n category: 'monospace',\n weights: [200, 300, 400, 500, 600, 700, 800, 900],\n googleFont: true,\n description: 'Humanist monospace',\n },\n];\n\n/**\n * Generate Google Fonts CSS URL for all fonts\n */\nexport function getGoogleFontsUrl(): string {\n const googleFonts = TSHIRT_FONTS.filter((f) => f.googleFont);\n\n const fontSpecs = googleFonts.map((font) => {\n const weights = font.weights || [400];\n const weightStr = weights.join(';');\n return `${font.name.replace(/ /g, '+')}:wght@${weightStr}`;\n });\n\n return `https://fonts.googleapis.com/css2?${fontSpecs.join('&')}&display=swap`;\n}\n\n/**\n * Get font names only (for dropdown)\n */\nexport function getFontNames(): string[] {\n return TSHIRT_FONTS.map((f) => f.name);\n}\n\n/**\n * Get fonts grouped by category\n */\nexport function getFontsByCategory(): Record<FontCategory, FontDefinition[]> {\n const grouped: Record<string, FontDefinition[]> = {};\n\n TSHIRT_FONTS.forEach((font) => {\n if (!grouped[font.category]) {\n grouped[font.category] = [];\n }\n grouped[font.category].push(font);\n });\n\n return grouped as Record<FontCategory, FontDefinition[]>;\n}\n\n/**\n * Category display names\n */\nexport const CATEGORY_LABELS: Record<FontCategory, string> = {\n system: 'System Fonts',\n display: 'Bold & Display',\n 'sans-serif': 'Sans Serif',\n serif: 'Serif',\n 'slab-serif': 'Slab Serif', // NEW - Chunky serifs for bold designs\n script: 'Script & Handwritten',\n vintage: 'Vintage & Retro',\n decorative: 'Decorative & Unique',\n monospace: 'Monospace',\n};\n","/**\n * Central constants file for theme values, colors, and magic numbers\n * Eliminates duplication across the codebase\n */\n\nimport { getFontNames } from './fonts/google-fonts.js';\n\n/**\n * Get the current theme's accent color from HeroUI CSS variables\n * Returns oklch() color from --accent variable\n */\nexport function getThemeAccentColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0% 0 0)'; // Fallback for SSR (black for default light theme)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--accent').trim();\n return color || 'oklch(0% 0 0)'; // Fallback if variable not found\n}\n\n/**\n * Get the current theme's accent foreground color from HeroUI CSS variables\n * This is the contrasting color for text/icons on accent backgrounds\n */\nexport function getThemeAccentForegroundColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(100% 0 0)'; // Fallback for SSR (white)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--accent-foreground').trim();\n return color || 'oklch(100% 0 0)'; // Fallback if variable not found\n}\n\n/**\n * Get the current theme name from the data-theme attribute\n */\nexport function getCurrentTheme(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'light';\n }\n \n if (element) {\n const themeAttr = element.getAttribute('data-theme');\n if (themeAttr) return themeAttr;\n \n const closestTheme = element.closest('[data-theme]');\n if (closestTheme) {\n return closestTheme.getAttribute('data-theme') || 'light';\n }\n }\n \n return document.documentElement.getAttribute('data-theme') || 'light';\n}\n\n/**\n * Get the default shape fill color based on the current theme\n * Returns colors that complement each theme's aesthetic\n */\nexport function getThemeShapeFillColor(): string {\n const theme = getCurrentTheme();\n\n // Use theme-appropriate blue shades for shapes\n if (theme.includes('dark')) {\n return '#60a5fa'; // Lighter blue for better visibility on dark backgrounds\n } else {\n return '#3b82f6'; // Classic blue for light theme\n }\n}\n\n/**\n * Get the spacing indicator color from the current theme\n */\nexport function getThemeSpacingColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return '#FF0000'; // Fallback for SSR\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--color-spacing-indicator').trim();\n return color || '#FF0000'; // Fallback if variable not found\n}\n\n/**\n * Get the current theme's accent color with alpha transparency\n * Handles both oklch() colors (from HeroUI theme) and hex colors (legacy)\n */\nexport function getThemeAccentColorWithAlpha(alpha: number = 0.1, element?: HTMLElement): string {\n const color = getThemeAccentColor(element);\n\n // If it's an oklch color, add alpha channel\n if (color.startsWith('oklch(')) {\n // oklch(L C H) → oklch(L C H / alpha)\n const withoutClosing = color.slice(0, -1); // Remove closing )\n return `${withoutClosing} / ${alpha})`;\n }\n\n // Legacy hex color support\n if (color.startsWith('#')) {\n const hex = color.replace('#', '');\n const r = parseInt(hex.substring(0, 2), 16);\n const g = parseInt(hex.substring(2, 4), 16);\n const b = parseInt(hex.substring(4, 6), 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n\n // Fallback - return color as-is\n return color;\n}\n\n/**\n * Get the current theme's text selection color from CSS variables\n * Returns the theme-specific selection highlight color (already includes opacity)\n */\nexport function getThemeTextSelectionColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0% 0 0 / 0.45)'; // Fallback for SSR (default light theme - black at 45%)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--color-text-selection').trim();\n return color || 'oklch(0% 0 0 / 0.45)'; // Fallback if variable not found\n}\n\n/**\n * Get the current theme's hover accent color\n */\nexport function getThemeAccentHoverColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return '#b5f747'; // Fallback for SSR (light theme default)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--color-accent-hover').trim();\n return color || '#b5f747'; // Fallback if variable not found\n}\n\n/**\n * Get the current theme's hover accent color with alpha channel\n */\nexport function getThemeAccentHoverColorWithAlpha(alpha: number = 0.1, element?: HTMLElement): string {\n const color = getThemeAccentHoverColor(element);\n\n // If it's an oklch color, add alpha channel\n if (color.startsWith('oklch(')) {\n // oklch(L C H) → oklch(L C H / alpha)\n const withoutClosing = color.slice(0, -1); // Remove closing )\n return `${withoutClosing} / ${alpha})`;\n }\n\n // Legacy hex color support\n if (color.startsWith('#')) {\n const hex = color.replace('#', '');\n const r = parseInt(hex.substring(0, 2), 16);\n const g = parseInt(hex.substring(2, 4), 16);\n const b = parseInt(hex.substring(4, 6), 16);\n return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n }\n\n // Fallback - return color as-is\n return color;\n}\n\n/**\n * Get the background color for canvas-rendered tooltips.\n * Uses --foreground so the tooltip is dark in light mode and light in dark mode,\n * ensuring contrast against the typically light artboard canvas.\n */\nexport function getThemeTooltipBackground(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0.2103 0.0059 285.89)'; // Fallback for SSR (dark for light theme)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--foreground').trim();\n return color || 'oklch(0.2103 0.0059 285.89)';\n}\n\n/**\n * Get the text color for canvas-rendered tooltips.\n * Uses --background so text contrasts with getThemeTooltipBackground().\n */\nexport function getThemeTooltipForeground(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0.9607 0 0)'; // Fallback for SSR (light for light theme)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--background').trim();\n return color || 'oklch(0.9607 0 0)';\n}\n\n/**\n * Get the shadow color for canvas-rendered tooltips.\n * Derives from the tooltip background at 0.2 opacity.\n */\nexport function getThemeTooltipShadowColor(element?: HTMLElement): string {\n const bg = getThemeTooltipBackground(element);\n if (bg.startsWith('oklch(')) {\n return `${bg.slice(0, -1)} / 0.2)`;\n }\n return 'rgba(0,0,0,0.2)';\n}\n\n/**\n * Get the border color for artboard outlines.\n * Uses --divider for a theme-appropriate muted border that adapts to light/dark mode.\n */\nexport function getThemeArtboardBorderColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0% 0 0 / 0.2)'; // Fallback for SSR\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--divider').trim();\n return color || 'oklch(0% 0 0 / 0.2)';\n}\n\n/**\n * Get the label text color for artboard names.\n * Uses --foreground at 50% opacity for a muted appearance that adapts to light/dark mode.\n */\nexport function getThemeArtboardLabelColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0% 0 0 / 0.5)'; // Fallback for SSR\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--foreground').trim();\n if (color.startsWith('oklch(')) {\n return `${color.slice(0, -1)} / 0.5)`;\n }\n return color || 'oklch(0% 0 0 / 0.5)';\n}\n\n/**\n * Get the current theme's hover border color (uses hover accent color)\n */\nexport function getThemeHoverBorderColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return '#b5f747'; // Fallback for SSR (uses hover accent color)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--color-accent-hover-border').trim();\n return color || '#b5f747'; // Fallback if variable not found\n}\n\n/**\n * Get the pen tool path stroke color.\n * Uses --foreground so the path line is dark in light mode and light in dark mode,\n * ensuring visibility against both white and dark artboard backgrounds.\n */\nexport function getThemePenPathColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0.2103 0.0059 285.89)'; // Fallback for SSR (dark for light theme)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--foreground').trim();\n return color || 'oklch(0.2103 0.0059 285.89)';\n}\n\n/**\n * Get the fill color for pen tool anchor points.\n * Uses --background so anchor circles are visible against the accent-color stroke on both light and dark themes.\n */\nexport function getThemePenAnchorFillColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(1 0 0)'; // Fallback for SSR (white, suitable for light theme)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--background').trim();\n return color || 'oklch(1 0 0)';\n}\n\n/**\n * Get the pen tool bezier handle color (control lines and dots).\n * Uses --foreground at 50% opacity for a muted appearance that adapts to light/dark mode.\n */\nexport function getThemePenHandleColor(element?: HTMLElement): string {\n if (typeof window === 'undefined' || typeof document === 'undefined') {\n return 'oklch(0.2103 0.0059 285.89 / 0.5)'; // Fallback for SSR (muted dark)\n }\n const target = element || document.documentElement;\n const color = getComputedStyle(target).getPropertyValue('--foreground').trim();\n if (color.startsWith('oklch(')) {\n return `${color.slice(0, -1)} / 0.5)`;\n }\n return color || 'oklch(0.2103 0.0059 285.89 / 0.5)';\n}\n\n// Selection and UI colors - dynamic based on current theme\n// Call these functions at render time (not at import time) to get the current theme color.\nexport function getSelectionColor(): string {\n return getThemeAccentColor();\n}\nexport function getSelectionFillColor(): string {\n return getThemeAccentColorWithAlpha(0.1);\n}\nexport const HOVER_COLOR = '#fbbf24'; // Amber\n\n// Preview rendering settings (UI only, never exported)\nexport const PREVIEW_ELEMENT_OPACITY = 1.0; // Full opacity\n\n// Default element colors\nexport const DEFAULT_ARTBOARD_COLOR = '#ffffff'; // White\n// Note: DEFAULT_SHAPE_FILL_COLOR is deprecated - use getThemeShapeFillColor() instead\nexport const DEFAULT_SHAPE_FILL_COLOR = '#3b82f6'; // Blue (fallback only)\n\n// Handle dimensions\nexport const HANDLE_SIZE = 12;\nexport const HANDLE_RADIUS = 6;\nexport const ROTATION_HANDLE_DISTANCE = 20; // Distance from bottom edge to rotation handle\nexport const HANDLE_HIT_TOLERANCE = 8; // Hit test tolerance for handles\n\n// Crop handle dimensions - VISUAL sizes (what users see)\nexport const CORNER_HANDLE_VISUAL_RADIUS = 6; // Visual radius of circular corner handles\nexport const EDGE_HANDLE_VISUAL_LENGTH = 24; // Visual length of edge rectangles\n\n// Crop handle dimensions - HIT ZONE sizes (larger for comfortable touch/mouse interaction)\nexport const CORNER_HANDLE_HIT_RADIUS = 16; // Hit zone radius for circular corner handles\nexport const EDGE_HANDLE_LENGTH = 36; // Hit zone length for edge handles\nexport const EDGE_HANDLE_HIT_WIDTH = 24; // Hit zone WIDTH for edge handles (perpendicular to edge)\n\n// Snap system\nexport const ALIGNMENT_SNAP_THRESHOLD = 2; // Distance threshold for edge/center alignment snapping in pixels\nexport const SPACING_SNAP_THRESHOLD = 4; // Distance threshold for spacing pattern snapping in pixels\n\n// Spacing indicator colors\n// Note: Use getThemeSpacingColor() for dynamic theme-based colors\nexport const SPACING_LABEL_TEXT_COLOR = '#FFFFFF'; // White text for spacing labels\nexport const SPACING_LABEL_BORDER_RADIUS = 2; // Border radius for spacing labels in pixels\n\n// Crop constraints\nexport const MIN_CROP_SIZE = 0.1; // Minimum crop size (10% of image)\n\n// Font options - imported from Google Fonts configuration\nexport const FONT_FAMILIES = getFontNames();\n\nexport const FONT_SIZES = [8, 10, 12, 14, 16, 18, 20, 24, 28, 32, 36, 48, 64, 72, 96, 128];\n\n// Text layout\nexport const HORIZONTAL_PADDING = 0;\nexport const LINE_HEIGHT_MULTIPLIER = 1.2;\n\n// Transform constraints\nexport const MIN_WIDTH = 50;\nexport const MIN_FONT_SIZE = 8;\nexport const MAX_FONT_SIZE = 999;\n\n// Names that, even though they're allowed CSS values, we do NOT\n// want to load from Google Fonts. Used by the export worker and\n// FontAnalyzer as a deny-list. Kept for safety when legacy designs\n// reference these family names — we don't want to chase a 404 on\n// Google's CDN trying to download \"Impact\" from there. The font\n// browser doesn't expose any of these for selection (see\n// `TSHIRT_FONTS` in `fonts/google-fonts.ts`).\nexport const SYSTEM_FONTS = new Set([\n 'Arial', 'Arial Black', 'Verdana', 'Tahoma', 'Trebuchet MS',\n 'Impact', 'Times New Roman', 'Georgia', 'Garamond', 'Courier New',\n 'Brush Script MT', 'Palatino', 'Baskerville', 'Helvetica',\n 'Helvetica Neue', 'Comic Sans MS', 'Lucida Console', 'Monaco',\n 'Consolas', 'Courier',\n]);\n\n// Wave transform\nexport const WAVE_SKEW_FACTOR = 0.3;\nexport const WAVE_PATH_STEPS = 100;\n","/**\n * Centralized debug logger utility\n * Replaces scattered console.log statements with a consistent, filterable logging system\n */\n\nexport enum LogLevel {\n DEBUG = 0,\n INFO = 1,\n WARN = 2,\n ERROR = 3,\n NONE = 4,\n}\n\nclass Logger {\n private level: LogLevel = LogLevel.DEBUG;\n private enabled = typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production'; // Only enabled in development by default\n\n /**\n * Set the minimum log level to display\n */\n setLevel(level: LogLevel): void {\n this.level = level;\n }\n\n /**\n * Enable or disable logging globally\n */\n setEnabled(enabled: boolean): void {\n this.enabled = enabled;\n }\n\n /**\n * Get current log level\n */\n getLevel(): LogLevel {\n return this.level;\n }\n\n /**\n * Check if logging is enabled\n */\n isEnabled(): boolean {\n return this.enabled;\n }\n\n /**\n * Log debug message (verbose, development only)\n */\n debug(_message: string, ..._args: unknown[]): void {\n // Debug logging intentionally no-ops in production\n }\n\n /**\n * Log info message (general information)\n */\n info(message: string, ...args: unknown[]): void {\n if (this.enabled && this.level <= LogLevel.INFO) {\n console.info(`[INFO] ${message}`, ...args);\n }\n }\n\n /**\n * Log warning message (potential issues)\n */\n warn(message: string, ...args: unknown[]): void {\n if (this.enabled && this.level <= LogLevel.WARN) {\n console.warn(`[WARN] ${message}`, ...args);\n }\n }\n\n /**\n * Log error message (critical issues)\n */\n error(message: string, ...args: unknown[]): void {\n if (this.enabled && this.level <= LogLevel.ERROR) {\n console.error(`[ERROR] ${message}`, ...args);\n }\n }\n\n /**\n * Create a scoped logger with a prefix for a specific module\n */\n scope(scopeName: string): ScopedLogger {\n return new ScopedLogger(this, scopeName);\n }\n}\n\n/**\n * Scoped logger for specific modules/components\n */\nclass ScopedLogger {\n constructor(private logger: Logger, private scopeName: string) {}\n\n debug(message: string, ...args: unknown[]): void {\n this.logger.debug(`[${this.scopeName}] ${message}`, ...args);\n }\n\n info(message: string, ...args: unknown[]): void {\n this.logger.info(`[${this.scopeName}] ${message}`, ...args);\n }\n\n warn(message: string, ...args: unknown[]): void {\n this.logger.warn(`[${this.scopeName}] ${message}`, ...args);\n }\n\n error(message: string, ...args: unknown[]): void {\n this.logger.error(`[${this.scopeName}] ${message}`, ...args);\n }\n}\n\n// Export singleton instance\nexport const logger = new Logger();\n\n// Export scoped logger factory\nexport const createLogger = (scopeName: string): ScopedLogger => {\n return logger.scope(scopeName);\n};\n\n// Usage examples:\n// import { logger } from '@/utils/logger';\n// logger.debug('Canvas rendered', { width, height });\n// logger.info('Element added', element);\n// logger.warn('Performance issue detected', duration);\n// logger.error('Failed to export', error);\n\n// Or create a scoped logger:\n// import { createLogger } from '@/utils/logger';\n// const log = createLogger('CanvasEditor');\n// log.debug('Resize started');\n","/**\n * ImageLoadEvents - Simple event system for notifying when images finish loading\n *\n * This solves the issue where the canvas shows \"Loading...\" even after an image\n * has loaded, because React doesn't know the element's internal state changed.\n *\n * When ImageElement finishes loading an image, it emits an event.\n * CanvasEditor subscribes to this event and triggers a re-render.\n */\n\nimport { createLogger } from '../utils/logger.js';\n\nconst logger = createLogger('ImageLoadEvents');\n\ntype ImageLoadListener = (elementId: string) => void;\n\nconst listeners = new Set<ImageLoadListener>();\n\n/**\n * Subscribe to image load events\n * @param listener - Callback called when any ImageElement finishes loading\n * @returns Unsubscribe function\n */\nexport function subscribeToImageLoads(listener: ImageLoadListener): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n}\n\n/**\n * Emit an image load event\n * Called by ImageElement when an image finishes loading\n * @param elementId - The ID of the element that finished loading\n */\nexport function emitImageLoaded(elementId: string): void {\n listeners.forEach(listener => {\n try {\n listener(elementId);\n } catch (error) {\n logger.error('Listener error:', error);\n }\n });\n}\n","/**\n * Shared image cache for HTMLImageElement instances.\n * Prevents duplicate loading of the same URL across elements.\n * Reference-counted -- images are evicted when no elements reference them.\n *\n * Usage:\n * const cache = ImageCache.getInstance();\n * const img = cache.acquire('https://example.com/photo.jpg');\n * // ... use img ...\n * cache.release('https://example.com/photo.jpg');\n */\n\ninterface CacheEntry {\n image: HTMLImageElement;\n refCount: number;\n loaded: boolean;\n}\n\nexport class ImageCache {\n private static instance: ImageCache;\n private cache: Map<string, CacheEntry>;\n\n private constructor() {\n this.cache = new Map();\n }\n\n /**\n * Get the singleton ImageCache instance.\n */\n static getInstance(): ImageCache {\n if (!ImageCache.instance) {\n ImageCache.instance = new ImageCache();\n }\n return ImageCache.instance;\n }\n\n /**\n * Acquire an HTMLImageElement for the given URL.\n * If the URL is already cached, increments the reference count and returns the existing element.\n * If not cached, creates a new HTMLImageElement, starts loading, and caches it.\n *\n * @param url - The image URL to load\n * @returns The cached or newly created HTMLImageElement\n */\n acquire(url: string): HTMLImageElement {\n const existing = this.cache.get(url);\n if (existing) {\n existing.refCount++;\n return existing.image;\n }\n\n // Create new entry\n const img = new Image();\n img.crossOrigin = 'anonymous';\n\n const entry: CacheEntry = {\n image: img,\n refCount: 1,\n loaded: false,\n };\n\n img.onload = () => {\n entry.loaded = true;\n };\n\n img.onerror = () => {\n // Mark as loaded (with error) so callers can detect failure\n // The image element itself will have naturalWidth === 0\n entry.loaded = true;\n };\n\n this.cache.set(url, entry);\n\n // Don't attempt to load synthetic URIs (e.g. builtin-mask:splatter).\n // The caller is responsible for setting img.src to a real URL or data URL.\n if (!url.includes(':') || /^https?:\\/\\/|^data:|^blob:/.test(url)) {\n img.src = url;\n }\n\n return img;\n }\n\n /**\n * Release a reference to a cached image.\n * When the reference count reaches zero, the entry is evicted from the cache.\n *\n * @param url - The image URL to release\n */\n release(url: string): void {\n const entry = this.cache.get(url);\n if (!entry) return;\n\n entry.refCount--;\n if (entry.refCount <= 0) {\n this.cache.delete(url);\n }\n }\n\n /**\n * Get a cached HTMLImageElement without incrementing the reference count.\n * Returns undefined if the URL is not in the cache.\n *\n * @param url - The image URL to look up\n */\n get(url: string): HTMLImageElement | undefined {\n return this.cache.get(url)?.image;\n }\n\n /**\n * Check if a URL is in the cache.\n *\n * @param url - The image URL to check\n */\n has(url: string): boolean {\n return this.cache.has(url);\n }\n\n /**\n * Get cache statistics.\n *\n * @returns Object with entry count and total reference count across all entries\n */\n getStats(): { entries: number; totalRefs: number } {\n let totalRefs = 0;\n for (const entry of this.cache.values()) {\n totalRefs += entry.refCount;\n }\n return { entries: this.cache.size, totalRefs };\n }\n\n /**\n * Clear the entire cache.\n * Primarily for testing -- removes all entries regardless of reference count.\n */\n clear(): void {\n this.cache.clear();\n }\n}\n","/**\n * ImageElement - Element for displaying images\n * Images maintain aspect ratio and can be dragged, scaled, and rotated\n */\n\nimport { BaseElement } from './BaseElement.js';\nimport { RotationUtils } from './RotationUtils.js';\nimport { Transform } from './Transform.js';\nimport { CORNER_HANDLE_HIT_RADIUS, EDGE_HANDLE_LENGTH, EDGE_HANDLE_HIT_WIDTH } from '../constants.js';\nimport { emitImageLoaded } from './ImageLoadEvents.js';\nimport { ImageCache } from './ImageCache.js';\nimport { createLogger } from '../utils/logger.js';\nimport type { ImageTransformData, ImageElementConfig, ResizeAnchor, BoundingBox, Point, TransformStartData } from '../types/index.js';\n\nconst logger = createLogger('ImageElement');\n\n/** Image loading state for retry logic and UI feedback */\nexport type ImageLoadState = 'loading' | 'loaded' | 'error' | 'retrying';\n\n/** Default timeout for image loading in milliseconds */\nconst IMAGE_LOAD_TIMEOUT_MS = 30000;\n\n/** Maximum number of retry attempts */\nconst IMAGE_LOAD_MAX_RETRIES = 3;\n\n/** Base delay for exponential backoff in milliseconds */\nconst IMAGE_LOAD_BASE_DELAY_MS = 1000;\n\n/** Helper to access transformData as ImageTransformData from TransformStartData */\nfunction imageTransformData(startData: TransformStartData): ImageTransformData {\n return startData.transformData as unknown as ImageTransformData;\n}\n\nexport class ImageElement extends BaseElement {\n declare transformType: 'image';\n declare transformData: ImageTransformData;\n imageUrl: string;\n imageLoaded: boolean;\n imageElement: HTMLImageElement | null;\n imageAspectRatio: number;\n isCropping: boolean;\n onLoadCallback: ((element: ImageElement) => void) | null;\n isSvg: boolean; // Track if this is an SVG image\n preserveDimensions: boolean; // If true, don't recalculate dimensions on image load\n imageLoadState: ImageLoadState; // Current image loading state\n imageLoadError: Error | null; // Error from last failed load attempt\n private svgBlobUrl: string | null = null; // Track blob URL for SVG high-DPI rendering cleanup\n private loadTimeoutId: ReturnType<typeof setTimeout> | null = null; // Timeout for current load attempt\n private retryAttempt: number = 0; // Current retry attempt number\n private cachedImageUrl: string | null = null; // Track URL acquired from ImageCache for release on destroy\n\n constructor(config: Partial<ImageElementConfig> = {}) {\n super(config);\n this.transformType = 'image';\n\n // Initialize transformData with proper ImageTransformData type\n this.transformData = {\n type: 'image',\n width: config.transformData?.width || 200,\n height: config.transformData?.height || 200,\n cropX: config.transformData?.cropX ?? 0,\n cropY: config.transformData?.cropY ?? 0,\n cropWidth: config.transformData?.cropWidth ?? 1,\n cropHeight: config.transformData?.cropHeight ?? 1,\n flipHorizontal: config.transformData?.flipHorizontal ?? false,\n flipVertical: config.transformData?.flipVertical ?? false,\n borderRadius: config.transformData?.borderRadius ?? 0,\n };\n\n // Image-specific properties\n this.imageUrl = config.imageUrl || '';\n this.imageLoaded = false;\n this.imageElement = null;\n this.imageAspectRatio = config.imageAspectRatio || 1; // width / height\n this.isSvg = config.imageUrl?.toLowerCase().endsWith('.svg') || false;\n this.preserveDimensions = config.preserveDimensions ?? false;\n this.imageLoadState = config.imageUrl ? 'loading' : 'loaded';\n this.imageLoadError = null;\n\n // Crop mode state\n this.isCropping = false;\n\n // Callback for when image loads and dimensions change\n this.onLoadCallback = config.onLoadCallback || null;\n\n // Load the image\n if (this.imageUrl) {\n this.loadImage(this.imageUrl);\n }\n }\n\n /**\n * Load image from URL\n * For SVG files, we render to a high-DPI canvas to maintain quality\n */\n loadImage(url: string): void {\n const isSvg = url.toLowerCase().endsWith('.svg');\n this.isSvg = isSvg;\n\n if (isSvg) {\n // For SVG: Load at high resolution and convert to canvas\n this.loadSvgAsHighDpi(url);\n } else {\n // For regular images: Load normally\n this.loadRegularImage(url);\n }\n }\n\n /**\n * Load regular raster image (PNG, JPG, etc.) with timeout and retry logic.\n * Uses exponential backoff: 1s, 2s, 4s between retries.\n */\n private loadRegularImage(url: string): void {\n this.retryAttempt = 0;\n this.imageLoadError = null;\n this.imageLoadState = 'loading';\n this.attemptLoadRegularImage(url);\n }\n\n /**\n * Single attempt to load a regular image with timeout.\n * Called by loadRegularImage and retry logic.\n *\n * Uses the shared ImageCache to avoid duplicate loading of the same URL\n * across multiple ImageElement instances.\n */\n private attemptLoadRegularImage(url: string): void {\n // Clean up any previous timeout\n this.clearLoadTimeout();\n\n // Release previous cache entry if switching URLs\n this.releaseImageCache();\n\n const imageCache = ImageCache.getInstance();\n const img = imageCache.acquire(url);\n this.cachedImageUrl = url;\n\n let settled = false;\n\n const settle = () => {\n settled = true;\n this.clearLoadTimeout();\n };\n\n // If the image is already loaded (from cache), handle immediately\n if (img.complete && img.naturalWidth > 0) {\n settle();\n this.imageElement = img;\n this.imageLoaded = true;\n this.imageLoadState = 'loaded';\n this.imageLoadError = null;\n this.imageAspectRatio = img.naturalWidth / img.naturalHeight;\n\n if (!this.preserveDimensions) {\n this.transformData.height = this.transformData.width / this.imageAspectRatio;\n }\n\n if (this.onLoadCallback) {\n this.onLoadCallback(this.clone());\n }\n\n emitImageLoaded(this.id);\n return;\n }\n\n // Store original handlers in case the image already has them from the cache\n const originalOnload = img.onload;\n const originalOnerror = img.onerror;\n\n img.onload = (ev) => {\n if (settled) return;\n settle();\n\n this.imageElement = img;\n this.imageLoaded = true;\n this.imageLoadState = 'loaded';\n this.imageLoadError = null;\n this.imageAspectRatio = img.naturalWidth / img.naturalHeight;\n\n // Only recalculate dimensions if not explicitly provided (preserveDimensions flag)\n // This allows callers to provide explicit \"cover\" sizing that won't be overwritten\n if (!this.preserveDimensions) {\n // Update display height to maintain aspect ratio based on current width\n this.transformData.height = this.transformData.width / this.imageAspectRatio;\n }\n\n // Notify parent that dimensions have changed\n if (this.onLoadCallback) {\n this.onLoadCallback(this.clone());\n }\n\n // Emit global event to trigger canvas re-render\n // This ensures the \"Loading...\" placeholder is replaced with the actual image\n emitImageLoaded(this.id);\n\n // Call original handler if present (for cache bookkeeping)\n if (originalOnload && originalOnload !== img.onload) {\n (originalOnload as EventListener)(ev);\n }\n };\n\n img.onerror = (ev) => {\n if (settled) return;\n settle();\n\n const error = new Error(`Failed to load image: ${url}`);\n this.handleLoadFailure(url, error);\n\n // Call original handler if present\n if (originalOnerror && originalOnerror !== img.onerror) {\n (originalOnerror as EventListener)(ev as Event);\n }\n };\n\n // Set timeout for this attempt\n this.loadTimeoutId = setTimeout(() => {\n if (settled) return;\n settle();\n\n // Abort the in-progress load by clearing the src\n img.src = '';\n const error = new Error(`Image load timed out after ${IMAGE_LOAD_TIMEOUT_MS}ms: ${url}`);\n this.handleLoadFailure(url, error);\n }, IMAGE_LOAD_TIMEOUT_MS);\n\n // If the image is newly created by the cache, src is already set.\n // If it's a retry on a previously failed image, we need to re-set src.\n if (!img.src || img.src !== url) {\n img.src = url;\n }\n\n // Re-check complete after attaching handlers — the image may have loaded\n // between the first check (line 146) and the handler attachment above.\n // This race is common with data URLs and cached images where the browser\n // fires onload synchronously during img.src assignment.\n if (!settled && img.complete && img.naturalWidth > 0) {\n img.onload?.(new Event('load'));\n }\n }\n\n /**\n * Handle a load failure - either retry with backoff or set error state.\n */\n private handleLoadFailure(url: string, error: Error): void {\n this.retryAttempt++;\n\n if (this.retryAttempt < IMAGE_LOAD_MAX_RETRIES) {\n // Exponential backoff: 1s, 2s, 4s, ...\n const delay = IMAGE_LOAD_BASE_DELAY_MS * Math.pow(2, this.retryAttempt - 1);\n logger.warn(`Image load attempt ${this.retryAttempt} failed, retrying in ${delay}ms:`, url);\n this.imageLoadState = 'retrying';\n this.imageLoadError = error;\n\n // Emit event so placeholder updates to show \"Retrying...\"\n emitImageLoaded(this.id);\n\n this.loadTimeoutId = setTimeout(() => {\n this.attemptLoadRegularImage(url);\n }, delay);\n } else {\n // All retries exhausted\n logger.error(`Image load failed after ${IMAGE_LOAD_MAX_RETRIES} attempts:`, url);\n this.imageLoaded = false;\n this.imageLoadState = 'error';\n this.imageLoadError = error;\n\n // Emit event so placeholder updates to show error state\n emitImageLoaded(this.id);\n }\n }\n\n /**\n * Clear any pending load timeout\n */\n private clearLoadTimeout(): void {\n if (this.loadTimeoutId !== null) {\n clearTimeout(this.loadTimeoutId);\n this.loadTimeoutId = null;\n }\n }\n\n /**\n * Release the current image from the shared cache.\n * Called when switching URLs or destroying the element.\n */\n private releaseImageCache(): void {\n if (this.cachedImageUrl) {\n ImageCache.getInstance().release(this.cachedImageUrl);\n this.cachedImageUrl = null;\n }\n }\n\n /**\n * Manually retry loading the image after a failure.\n * Resets retry count and starts fresh.\n */\n retryImageLoad(): void {\n if (!this.imageUrl) return;\n\n logger.info('Manual retry triggered for image:', this.imageUrl);\n this.clearLoadTimeout();\n this.imageLoaded = false;\n this.imageElement = null;\n\n if (this.isSvg) {\n this.imageLoadState = 'loading';\n this.imageLoadError = null;\n this.loadSvgAsHighDpi(this.imageUrl);\n } else {\n this.loadRegularImage(this.imageUrl);\n }\n }\n\n /**\n * Revoke any existing SVG blob URL to prevent memory leaks\n * iOS Safari has strict blob URL limits; this prevents accumulation\n */\n private revokeSvgBlobUrl(): void {\n if (this.svgBlobUrl) {\n URL.revokeObjectURL(this.svgBlobUrl);\n this.svgBlobUrl = null;\n }\n }\n\n /**\n * Load SVG and convert to high-DPI canvas for crisp rendering\n * SVG is rendered at 4x resolution to maintain quality when scaled\n */\n private loadSvgAsHighDpi(url: string): void {\n // Revoke any existing SVG blob URL before creating a new one\n this.revokeSvgBlobUrl();\n const tempImg = new Image();\n tempImg.crossOrigin = 'anonymous';\n\n tempImg.onload = () => {\n // Render SVG to a high-DPI canvas (4x resolution)\n const scale = 4; // 4x resolution for crisp quality\n const canvas = document.createElement('canvas');\n canvas.width = tempImg.width * scale;\n canvas.height = tempImg.height * scale;\n\n const ctx = canvas.getContext('2d')!;\n ctx.scale(scale, scale);\n ctx.drawImage(tempImg, 0, 0);\n\n // Convert canvas to blob and create object URL\n canvas.toBlob((blob) => {\n if (!blob) {\n logger.error('Failed to convert SVG to blob');\n this.imageLoaded = false;\n return;\n }\n\n // Store blob URL for later cleanup (iOS Safari memory management)\n this.svgBlobUrl = URL.createObjectURL(blob);\n\n // Now load the high-DPI version\n this.imageElement = new Image();\n this.imageElement.crossOrigin = 'anonymous';\n this.imageElement.onload = () => {\n this.imageLoaded = true;\n this.imageAspectRatio = this.imageElement!.width / this.imageElement!.height;\n\n // Only recalculate dimensions if not explicitly provided (preserveDimensions flag)\n if (!this.preserveDimensions) {\n // Update display height to maintain aspect ratio\n this.transformData.height = this.transformData.width / this.imageAspectRatio;\n }\n\n // Notify parent\n if (this.onLoadCallback) {\n this.onLoadCallback(this.clone());\n }\n\n // Emit global event to trigger canvas re-render\n // This ensures the \"Loading...\" placeholder is replaced with the actual image\n emitImageLoaded(this.id);\n\n // Note: We no longer revoke the blob URL here via setTimeout\n // Instead, it's tracked in this.svgBlobUrl and cleaned up via destroy()\n };\n\n this.imageElement.src = this.svgBlobUrl;\n }, 'image/png');\n };\n\n tempImg.onerror = () => {\n logger.error('Failed to load SVG:', url);\n this.imageLoaded = false;\n };\n\n tempImg.src = url;\n }\n\n /**\n * Get bounding box in world coordinates\n * Returns the unrotated dimensions - rotation is handled by the renderer\n * When not in crop mode, returns only the cropped area dimensions\n */\n getBoundingBox(): BoundingBox {\n if (this.isCropping) {\n // In crop mode: Need to show the full image frame\n // this.x, this.y is at the center of the visible crop area\n // We need to calculate where the full frame would be, accounting for rotation and flips\n\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n // Offset from crop center to frame center in local space (before rotation and flip)\n let localOffsetX =\n this.transformData.width / 2 - (this.transformData.cropX * this.transformData.width + cropWidth / 2);\n let localOffsetY =\n this.transformData.height / 2 - (this.transformData.cropY * this.transformData.height + cropHeight / 2);\n\n // Account for flips: when flipped, the offset direction is reversed\n const flipH = this.transformData.flipHorizontal ? -1 : 1;\n const flipV = this.transformData.flipVertical ? -1 : 1;\n localOffsetX *= flipH;\n localOffsetY *= flipV;\n\n // Rotate the offset to world space\n const cos = Math.cos(RotationUtils.toRadians(this.rotation));\n const sin = Math.sin(RotationUtils.toRadians(this.rotation));\n const worldOffsetX = localOffsetX * cos - localOffsetY * sin;\n const worldOffsetY = localOffsetX * sin + localOffsetY * cos;\n\n // Calculate frame center in world space\n const frameCenterX = this.x + worldOffsetX;\n const frameCenterY = this.y + worldOffsetY;\n\n return {\n x: frameCenterX - this.transformData.width / 2,\n y: frameCenterY - this.transformData.height / 2,\n width: this.transformData.width,\n height: this.transformData.height,\n };\n } else {\n // Not in crop mode: only the cropped area\n // this.x, this.y is already at the center of the cropped area\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n return {\n x: this.x - cropWidth / 2,\n y: this.y - cropHeight / 2,\n width: cropWidth,\n height: cropHeight,\n };\n }\n }\n\n /**\n * Get visual bounding box - returns the cropped area bounds\n * This is used for group bounds calculation and should represent the visible area\n */\n getVisualBoundingBox(): BoundingBox {\n // Always return the cropped area, even during crop mode\n // This ensures group bounds update correctly while cropping\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n return {\n x: this.x - cropWidth / 2,\n y: this.y - cropHeight / 2,\n width: cropWidth,\n height: cropHeight,\n };\n }\n\n /**\n * Get rotation anchor (center of the visible image area)\n * In crop mode: returns the center of the full frame\n * Not in crop mode: returns this.x, this.y (center of cropped area)\n */\n getRotationAnchor(): Point {\n if (this.isCropping) {\n // In crop mode: return the center of the full frame\n // this.x, this.y is at the crop center, so we need to calculate the frame center\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n // Offset from crop center to frame center in local space (before rotation and flip)\n let localOffsetX =\n this.transformData.width / 2 - (this.transformData.cropX * this.transformData.width + cropWidth / 2);\n let localOffsetY =\n this.transformData.height / 2 - (this.transformData.cropY * this.transformData.height + cropHeight / 2);\n\n // Account for flips: when flipped, the offset direction is reversed\n // This is because flip inverts the coordinate system\n const flipH = this.transformData.flipHorizontal ? -1 : 1;\n const flipV = this.transformData.flipVertical ? -1 : 1;\n localOffsetX *= flipH;\n localOffsetY *= flipV;\n\n // Rotate the offset to world space\n const cos = Math.cos(RotationUtils.toRadians(this.rotation));\n const sin = Math.sin(RotationUtils.toRadians(this.rotation));\n const worldOffsetX = localOffsetX * cos - localOffsetY * sin;\n const worldOffsetY = localOffsetX * sin + localOffsetY * cos;\n\n // Calculate frame center in world space\n return {\n x: this.x + worldOffsetX,\n y: this.y + worldOffsetY,\n };\n } else {\n // Not in crop mode: this.x, this.y is already at the center of the visible area\n return {\n x: this.x,\n y: this.y,\n };\n }\n }\n\n /**\n * Hit test for image - checks if point is inside the visible (cropped) area\n */\n hitTest(worldX: number, worldY: number): boolean {\n // Convert to local coordinates (relative to this.x, this.y)\n const local = this.worldToLocal(worldX, worldY);\n\n if (this.isCropping) {\n // In crop mode: hit test against full image frame\n // Calculate where the full frame is relative to the crop center\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n const localOffsetX =\n this.transformData.width / 2 - (this.transformData.cropX * this.transformData.width + cropWidth / 2);\n const localOffsetY =\n this.transformData.height / 2 - (this.transformData.cropY * this.transformData.height + cropHeight / 2);\n\n const halfWidth = this.transformData.width / 2;\n const halfHeight = this.transformData.height / 2;\n\n return (\n local.x >= localOffsetX - halfWidth &&\n local.x <= localOffsetX + halfWidth &&\n local.y >= localOffsetY - halfHeight &&\n local.y <= localOffsetY + halfHeight\n );\n } else {\n // Not in crop mode: hit test against cropped area only\n // The crop is centered at this.x, this.y, so just check the crop size\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n const halfCropWidth = cropWidth / 2;\n const halfCropHeight = cropHeight / 2;\n\n return (\n local.x >= -halfCropWidth && local.x <= halfCropWidth && local.y >= -halfCropHeight && local.y <= halfCropHeight\n );\n }\n }\n\n /**\n * Render the image\n */\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n if (!this.imageLoaded || !this.imageElement) {\n // Show placeholder while loading\n this.renderPlaceholder(ctx);\n return;\n }\n\n\n ctx.save();\n\n // Apply element opacity\n if (this.opacity !== undefined && this.opacity !== 1) {\n ctx.globalAlpha = this.opacity;\n }\n\n // Translate to the visible area center and rotate\n ctx.translate(this.x, this.y);\n // Negate rotation for clockwise-negative convention\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n\n // Apply flip transformations\n const flipH = this.transformData.flipHorizontal ? -1 : 1;\n const flipV = this.transformData.flipVertical ? -1 : 1;\n ctx.scale(flipH, flipV);\n\n if (this.isCropping) {\n // In crop mode: render the full image frame\n // Calculate where the frame is relative to the crop center\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n const localOffsetX =\n this.transformData.width / 2 - (this.transformData.cropX * this.transformData.width + cropWidth / 2);\n const localOffsetY =\n this.transformData.height / 2 - (this.transformData.cropY * this.transformData.height + cropHeight / 2);\n\n const x = localOffsetX - this.transformData.width / 2;\n const y = localOffsetY - this.transformData.height / 2;\n\n // Apply border radius if set\n const borderRadius = this.transformData.borderRadius || 0;\n if (borderRadius > 0) {\n ctx.save();\n const radius = Math.min(\n (borderRadius / 100) * Math.min(this.transformData.width, this.transformData.height),\n this.transformData.width / 2,\n this.transformData.height / 2\n );\n ctx.beginPath();\n ctx.roundRect(x, y, this.transformData.width, this.transformData.height, radius);\n ctx.clip();\n }\n\n // Draw the full image\n ctx.drawImage(\n this.imageElement,\n 0,\n 0,\n this.imageElement.width,\n this.imageElement.height,\n x,\n y,\n this.transformData.width,\n this.transformData.height\n );\n\n if (borderRadius > 0) {\n ctx.restore();\n }\n } else if (this.masks && this.masks.length > 0) {\n // Has masks: render the full image without crop clipping.\n // The mask shape defines the visible area instead of the crop rectangle.\n // Crop values (cropX/Y) still offset the image within the mask.\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n const x = -cropWidth / 2;\n const y = -cropHeight / 2;\n\n // Calculate where the full image should be drawn so the crop offset\n // positions the image content within the mask shape\n const fullImageX = x - this.transformData.cropX * this.transformData.width;\n const fullImageY = y - this.transformData.cropY * this.transformData.height;\n\n ctx.drawImage(\n this.imageElement,\n 0,\n 0,\n this.imageElement.width,\n this.imageElement.height,\n fullImageX,\n fullImageY,\n this.transformData.width,\n this.transformData.height\n );\n } else {\n // Not in crop mode: render only the cropped area\n // The visible crop is centered at the origin\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n const x = -cropWidth / 2;\n const y = -cropHeight / 2;\n\n // Apply border radius clipping\n const borderRadius = this.transformData.borderRadius || 0;\n ctx.save();\n const radius = Math.min((borderRadius / 100) * Math.min(cropWidth, cropHeight), cropWidth / 2, cropHeight / 2);\n ctx.beginPath();\n ctx.roundRect(x, y, cropWidth, cropHeight, radius);\n ctx.clip();\n\n // Draw the full image, but only the cropped area is visible due to clipping\n // Calculate where the full image should be drawn so the crop appears at the origin\n const fullImageX = x - this.transformData.cropX * this.transformData.width;\n const fullImageY = y - this.transformData.cropY * this.transformData.height;\n\n ctx.drawImage(\n this.imageElement,\n 0,\n 0,\n this.imageElement.width,\n this.imageElement.height,\n fullImageX,\n fullImageY,\n this.transformData.width,\n this.transformData.height\n );\n\n ctx.restore();\n }\n\n ctx.restore();\n }\n\n /**\n * Render placeholder based on current image load state.\n * Shows different visuals for loading, retrying, error, and no-image states.\n */\n renderPlaceholder(ctx: CanvasRenderingContext2D): void {\n ctx.save();\n\n // Translate to the visible area center and rotate\n ctx.translate(this.x, this.y);\n // Negate rotation for clockwise-negative convention\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n\n // For placeholder, just show the crop size centered\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n const x = -cropWidth / 2;\n const y = -cropHeight / 2;\n\n // Choose background and message based on load state\n let bgColor: string;\n let textColor: string;\n let message: string;\n\n if (this.imageLoadState === 'error') {\n bgColor = '#f0e0e0'; // Light red/pink tint for error\n textColor = '#993333';\n message = 'Image failed to load';\n } else if (this.imageLoadState === 'retrying') {\n bgColor = '#e0e0e0';\n textColor = '#666';\n message = 'Retrying...';\n } else if (this.imageLoadState === 'loading') {\n bgColor = '#e0e0e0';\n textColor = '#666';\n message = 'Loading...';\n } else {\n // No URL set or other state\n bgColor = '#e0e0e0';\n textColor = '#666';\n message = this.imageUrl ? 'Loading...' : 'No image';\n }\n\n // Draw background rectangle\n ctx.fillStyle = bgColor;\n ctx.fillRect(x, y, cropWidth, cropHeight);\n\n // Draw placeholder text at center\n ctx.fillStyle = textColor;\n ctx.font = '14px system-ui, -apple-system, sans-serif';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(message, 0, 0);\n\n ctx.restore();\n }\n\n /**\n * Handle resize - maintain aspect ratio\n * @returns {boolean} true if resize was applied\n */\n override resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): boolean {\n // Calculate the proposed scale from startData\n // In crop mode: scale relative to frame dimensions\n // Not in crop mode: scale relative to visible (cropped) dimensions\n let widthScale, heightScale;\n if (this.isCropping) {\n // In crop mode: newWidth/newHeight are based on frame bounding box\n widthScale = newWidth / imageTransformData(startData).width;\n heightScale = newHeight / imageTransformData(startData).height;\n } else {\n // Not in crop mode: newWidth/newHeight are based on cropped area bounding box\n // We need to scale relative to the visible crop dimensions\n widthScale = newWidth / (startData.width ?? 1);\n heightScale = newHeight / (startData.height ?? 1);\n }\n const proposedScale = (widthScale + heightScale) / 2;\n\n // Remap anchor when flipped\n let effectiveAnchor = anchor;\n if (this.isCropping) {\n effectiveAnchor = this.remapAnchorForFlips(anchor, startData);\n }\n\n // Apply crop constraints if in crop mode\n let finalScale = proposedScale;\n if (this.isCropping) {\n const currentScaleRelativeToStart = this.transformData.width / imageTransformData(startData).width;\n\n // Only apply constraint when trying to shrink\n if (proposedScale < currentScaleRelativeToStart) {\n finalScale = this.calculateMinScaleForCropBounds(\n effectiveAnchor,\n startData,\n proposedScale,\n currentScaleRelativeToStart\n );\n }\n }\n\n // Apply scale to both dimensions while maintaining aspect ratio\n this.transformData.width = imageTransformData(startData).width * finalScale;\n this.transformData.height = this.transformData.width / this.imageAspectRatio;\n\n // Ensure rotation stays the same\n this.rotation = startData.rotation;\n\n // For crop mode, we need to get the actual frame bounds, not the crop bounds\n // startData.x, startData.y is the crop center in crop mode, frame center otherwise\n // We need to calculate the frame center and corners\n\n let frameCenterX, frameCenterY;\n\n if (this.isCropping) {\n // Calculate where the frame center was in world coordinates\n // Crop center in frame-local coordinates (frame center is at 0,0 in local coords)\n // Frame spans from (-width/2, -height/2) to (width/2, height/2)\n // Crop spans from (cropX * width - width/2, cropY * height - height/2) to (...)\n const oldCropCenterXLocal =\n (imageTransformData(startData).cropX + imageTransformData(startData).cropWidth / 2) * imageTransformData(startData).width -\n imageTransformData(startData).width / 2;\n const oldCropCenterYLocal =\n (imageTransformData(startData).cropY + imageTransformData(startData).cropHeight / 2) * imageTransformData(startData).height -\n imageTransformData(startData).height / 2;\n\n // This is the offset from frame center to crop center in local coords\n const localOffsetX = oldCropCenterXLocal;\n const localOffsetY = oldCropCenterYLocal;\n\n // Rotate to world coords (forward transform: local → world)\n // NOTE: No flip transformation needed here!\n // The local offsets are stored in unflipped coordinate space,\n // and this.x/this.y are also in unflipped world space.\n // The flip only affects rendering, not the coordinate calculations.\n const startTransform = Transform.fromSnapshot(startData);\n const worldOffset = startTransform.localDeltaToWorld(localOffsetX, localOffsetY);\n const worldOffsetX = worldOffset.dx;\n const worldOffsetY = worldOffset.dy;\n\n // startData.x, startData.y is crop center, so frame center is:\n frameCenterX = startData.x - worldOffsetX;\n frameCenterY = startData.y - worldOffsetY;\n } else {\n // Not in crop mode: startData.x, startData.y is the crop center (visual center)\n // We need to calculate the frame center from the crop center\n const oldCropCenterXLocal =\n (imageTransformData(startData).cropX + imageTransformData(startData).cropWidth / 2) * imageTransformData(startData).width -\n imageTransformData(startData).width / 2;\n const oldCropCenterYLocal =\n (imageTransformData(startData).cropY + imageTransformData(startData).cropHeight / 2) * imageTransformData(startData).height -\n imageTransformData(startData).height / 2;\n\n // This is the offset from frame center to crop center in local coords\n const localOffsetX = oldCropCenterXLocal;\n const localOffsetY = oldCropCenterYLocal;\n\n // Rotate to world coords (forward transform: local → world)\n const startTransform = Transform.fromSnapshot(startData);\n const worldOffset = startTransform.localDeltaToWorld(localOffsetX, localOffsetY);\n const worldOffsetX = worldOffset.dx;\n const worldOffsetY = worldOffset.dy;\n\n // startData.x, startData.y is crop center, so frame center is:\n frameCenterX = startData.x - worldOffsetX;\n frameCenterY = startData.y - worldOffsetY;\n }\n\n // Calculate the FIXED corner point in world coordinates at start\n // In crop mode, the fixed corner is the OPPOSITE of the dragged anchor\n // Use effectiveAnchor (which accounts for flips) instead of anchor\n let fixedCornerWorldX, fixedCornerWorldY;\n\n // Fixed corner positions in local frame coordinates (unrotated, centered at frame center)\n // These are the OPPOSITE corners from the anchor being dragged\n let fixedCornerLocalX = 0;\n let fixedCornerLocalY = 0;\n\n if (effectiveAnchor === 'top-left') {\n // Dragging top-left, so fix bottom-right\n fixedCornerLocalX = imageTransformData(startData).width / 2;\n fixedCornerLocalY = imageTransformData(startData).height / 2;\n } else if (effectiveAnchor === 'top-right') {\n // Dragging top-right, so fix bottom-left\n fixedCornerLocalX = -imageTransformData(startData).width / 2;\n fixedCornerLocalY = imageTransformData(startData).height / 2;\n } else if (effectiveAnchor === 'bottom-left') {\n // Dragging bottom-left, so fix top-right\n fixedCornerLocalX = imageTransformData(startData).width / 2;\n fixedCornerLocalY = -imageTransformData(startData).height / 2;\n } else if (effectiveAnchor === 'bottom-right') {\n // Dragging bottom-right, so fix top-left\n fixedCornerLocalX = -imageTransformData(startData).width / 2;\n fixedCornerLocalY = -imageTransformData(startData).height / 2;\n } else if (effectiveAnchor === 'middle-left') {\n // Dragging left edge, so fix right edge\n fixedCornerLocalX = imageTransformData(startData).width / 2;\n fixedCornerLocalY = 0;\n } else if (effectiveAnchor === 'middle-right') {\n // Dragging right edge, so fix left edge\n fixedCornerLocalX = -imageTransformData(startData).width / 2;\n fixedCornerLocalY = 0;\n } else if (effectiveAnchor === 'middle-top') {\n // Dragging top edge, so fix bottom edge\n fixedCornerLocalX = 0;\n fixedCornerLocalY = imageTransformData(startData).height / 2;\n } else if (effectiveAnchor === 'middle-bottom') {\n // Dragging bottom edge, so fix top edge\n fixedCornerLocalX = 0;\n fixedCornerLocalY = -imageTransformData(startData).height / 2;\n }\n\n // Rotate fixed corner to world coordinates (forward transform: local → world)\n // NOTE: No flip transformation needed!\n // The fixed corner positions are in unflipped local space,\n // and we convert them directly to unflipped world space.\n const startTransformForCorner = Transform.fromSnapshot(startData);\n const fixedCornerWorldOffset = startTransformForCorner.localDeltaToWorld(fixedCornerLocalX, fixedCornerLocalY);\n const fixedCornerWorldOffsetX = fixedCornerWorldOffset.dx;\n const fixedCornerWorldOffsetY = fixedCornerWorldOffset.dy;\n\n fixedCornerWorldX = frameCenterX + fixedCornerWorldOffsetX;\n fixedCornerWorldY = frameCenterY + fixedCornerWorldOffsetY;\n\n // Calculate new frame center position\n // The fixed corner (opposite of the dragged anchor) should stay at fixedCornerWorldX/Y\n // We need to find where the frame center should be so that the fixed corner ends up there\n\n // Calculate where the fixed corner will be in the NEW frame's local coordinates\n // (same relative position as before, but with new dimensions)\n let newFixedCornerLocalX = 0;\n let newFixedCornerLocalY = 0;\n\n if (effectiveAnchor === 'top-left') {\n // Fixed corner is bottom-right\n newFixedCornerLocalX = this.transformData.width / 2;\n newFixedCornerLocalY = this.transformData.height / 2;\n } else if (effectiveAnchor === 'top-right') {\n // Fixed corner is bottom-left\n newFixedCornerLocalX = -this.transformData.width / 2;\n newFixedCornerLocalY = this.transformData.height / 2;\n } else if (effectiveAnchor === 'bottom-left') {\n // Fixed corner is top-right\n newFixedCornerLocalX = this.transformData.width / 2;\n newFixedCornerLocalY = -this.transformData.height / 2;\n } else if (effectiveAnchor === 'bottom-right') {\n // Fixed corner is top-left\n newFixedCornerLocalX = -this.transformData.width / 2;\n newFixedCornerLocalY = -this.transformData.height / 2;\n } else if (effectiveAnchor === 'middle-left') {\n // Fixed edge is right\n newFixedCornerLocalX = this.transformData.width / 2;\n newFixedCornerLocalY = 0;\n } else if (effectiveAnchor === 'middle-right') {\n // Fixed edge is left\n newFixedCornerLocalX = -this.transformData.width / 2;\n newFixedCornerLocalY = 0;\n } else if (effectiveAnchor === 'middle-top') {\n // Fixed edge is bottom\n newFixedCornerLocalX = 0;\n newFixedCornerLocalY = this.transformData.height / 2;\n } else if (effectiveAnchor === 'middle-bottom') {\n // Fixed edge is top\n newFixedCornerLocalX = 0;\n newFixedCornerLocalY = -this.transformData.height / 2;\n }\n\n // Rotate the new fixed corner position to world coordinates (forward transform: local → world)\n // NOTE: No flip transformation needed!\n const newTransform = new Transform(this);\n const newFixedCornerWorldOffset = newTransform.localDeltaToWorld(newFixedCornerLocalX, newFixedCornerLocalY);\n const newFixedCornerWorldOffsetX = newFixedCornerWorldOffset.dx;\n const newFixedCornerWorldOffsetY = newFixedCornerWorldOffset.dy;\n\n // Calculate new frame center: the fixed corner stays at the same world position\n // fixedCornerWorld = frameCenter + fixedCornerWorldOffset\n // Therefore: frameCenter = fixedCornerWorld - fixedCornerWorldOffset\n const newFrameCenterX = fixedCornerWorldX - newFixedCornerWorldOffsetX;\n const newFrameCenterY = fixedCornerWorldY - newFixedCornerWorldOffsetY;\n\n // In crop mode, we need to keep the crop zone at the same world size and position\n if (this.isCropping) {\n // The crop zone should stay the same size AND position in world coordinates\n // Calculate the old crop center in world coordinates\n const oldCropCenterWorldX = startData.x;\n const oldCropCenterWorldY = startData.y;\n\n // Calculate the old crop size in world coordinates\n const oldCropWorldWidth = imageTransformData(startData).cropWidth * imageTransformData(startData).width;\n const oldCropWorldHeight = imageTransformData(startData).cropHeight * imageTransformData(startData).height;\n\n // The crop center stays at the same world position\n this.x = oldCropCenterWorldX;\n this.y = oldCropCenterWorldY;\n\n // Calculate new normalized crop dimensions (fixed world size / new frame size)\n const newCropWidthNorm = oldCropWorldWidth / this.transformData.width;\n const newCropHeightNorm = oldCropWorldHeight / this.transformData.height;\n\n // Now figure out where the crop is positioned within the new frame\n // Get the offset from new frame center to crop center in world coords\n const worldOffsetX = oldCropCenterWorldX - newFrameCenterX;\n const worldOffsetY = oldCropCenterWorldY - newFrameCenterY;\n\n // Convert to local coords (relative to the new frame) - inverse transform: world → local\n const newFrameTransform = new Transform(this);\n const localOffset = newFrameTransform.worldDeltaToLocal(worldOffsetX, worldOffsetY);\n const localOffsetX = localOffset.dx;\n const localOffsetY = localOffset.dy;\n\n // The crop center in local frame coordinates (from frame top-left)\n const cropCenterXLocal = this.transformData.width / 2 + localOffsetX;\n const cropCenterYLocal = this.transformData.height / 2 + localOffsetY;\n\n // Convert to normalized coordinates (0-1) and get top-left corner\n const newCropX = cropCenterXLocal / this.transformData.width - newCropWidthNorm / 2;\n const newCropY = cropCenterYLocal / this.transformData.height - newCropHeightNorm / 2;\n\n // Update crop parameters\n // No clamping needed - we already checked bounds and reverted if out of bounds\n this.transformData.cropX = newCropX;\n this.transformData.cropY = newCropY;\n this.transformData.cropWidth = newCropWidthNorm;\n this.transformData.cropHeight = newCropHeightNorm;\n } else {\n // Not in crop mode: this.x, this.y should be at the crop center (visual center)\n // Calculate the new crop center from the new frame center\n\n // Calculate crop center position in the NEW frame's local coordinates\n const newCropCenterXLocal =\n (this.transformData.cropX + this.transformData.cropWidth / 2) * this.transformData.width -\n this.transformData.width / 2;\n const newCropCenterYLocal =\n (this.transformData.cropY + this.transformData.cropHeight / 2) * this.transformData.height -\n this.transformData.height / 2;\n\n // This is the offset from frame center to crop center in local coords\n const newLocalOffsetX = newCropCenterXLocal;\n const newLocalOffsetY = newCropCenterYLocal;\n\n // Rotate to world coords (forward transform: local → world)\n const newTransform = new Transform(this);\n const newWorldOffset = newTransform.localDeltaToWorld(newLocalOffsetX, newLocalOffsetY);\n const newWorldOffsetX = newWorldOffset.dx;\n const newWorldOffsetY = newWorldOffset.dy;\n\n // Crop center = frame center + offset\n this.x = newFrameCenterX + newWorldOffsetX;\n this.y = newFrameCenterY + newWorldOffsetY;\n }\n\n return true; // Resize was applied successfully\n }\n\n /**\n * Get enabled anchors - only corner handles for images\n */\n getEnabledAnchors(): ResizeAnchor[] {\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n /**\n * Enter crop mode\n * Just sets the flag - position stays at the cropped area center\n */\n enterCropMode(): void {\n this.isCropping = true;\n }\n\n /**\n * Exit crop mode\n * Just clears the flag - position stays the same\n */\n exitCropMode(): void {\n this.isCropping = false;\n }\n\n /**\n * Get crop box bounds in local coordinates (before rotation)\n * Returns the rectangle where the crop box should be rendered\n * Local coordinates are relative to this.x, this.y (the center of the cropped area)\n */\n getCropBoxBounds(): BoundingBox | null {\n if (!this.isCropping) return null;\n\n // In crop mode, this.x/this.y is at the center of the cropped area (what's visible)\n // The crop box represents this visible area, so it's always centered at the origin\n const cropWidth = this.transformData.width * this.transformData.cropWidth;\n const cropHeight = this.transformData.height * this.transformData.cropHeight;\n\n // Since this.x, this.y is the crop center, the crop box is centered at origin in local coords\n return {\n x: -cropWidth / 2,\n y: -cropHeight / 2,\n width: cropWidth,\n height: cropHeight,\n };\n }\n\n /**\n * Get crop box corners in world coordinates (accounting for rotation)\n * Returns array of {x, y} points for each corner\n */\n getCropBoxWorldCorners(): Point[] | null {\n const cropBox = this.getCropBoxBounds();\n if (!cropBox) return null;\n\n const cos = Math.cos(RotationUtils.toRadians(this.rotation));\n const sin = Math.sin(RotationUtils.toRadians(this.rotation));\n\n // Define corners in local space\n const corners = [\n { x: cropBox.x, y: cropBox.y }, // top-left\n { x: cropBox.x + cropBox.width, y: cropBox.y }, // top-right\n { x: cropBox.x, y: cropBox.y + cropBox.height }, // bottom-left\n { x: cropBox.x + cropBox.width, y: cropBox.y + cropBox.height }, // bottom-right\n ];\n\n // Transform to world coordinates\n return corners.map((corner) => ({\n x: this.x + corner.x * cos - corner.y * sin,\n y: this.y + corner.x * sin + corner.y * cos,\n }));\n }\n\n /**\n * Convert world coordinates to local coordinates (accounting for rotation and flips)\n * Local coordinates are relative to this.x, this.y (the center of the visible area)\n */\n worldToLocal(worldX: number, worldY: number): Point {\n // Translate relative to the visible area center\n const dx = worldX - this.x;\n const dy = worldY - this.y;\n\n // Rotate back (inverse rotation) - positive to undo the negative forward rotation\n const cos = Math.cos(RotationUtils.toRadiansInverse(this.rotation));\n const sin = Math.sin(RotationUtils.toRadiansInverse(this.rotation));\n\n let localX = dx * cos - dy * sin;\n let localY = dx * sin + dy * cos;\n\n // Apply inverse flip transformation\n // Since flip is a scale by ±1, the inverse is the same operation\n const flipH = this.transformData.flipHorizontal ? -1 : 1;\n const flipV = this.transformData.flipVertical ? -1 : 1;\n localX *= flipH;\n localY *= flipV;\n\n return {\n x: localX,\n y: localY,\n };\n }\n\n /**\n * Convert local coordinates to world coordinates (accounting for rotation and flips)\n * Inverse of worldToLocal\n * Canvas transformations are applied in reverse: scale -> rotate -> translate\n */\n localToWorld(localX: number, localY: number): Point {\n // Apply flip transformation first (ctx.scale is applied first to points)\n const flipH = this.transformData.flipHorizontal ? -1 : 1;\n const flipV = this.transformData.flipVertical ? -1 : 1;\n let flippedX = localX * flipH;\n let flippedY = localY * flipV;\n\n // Then rotate (ctx.rotate is applied second)\n const cos = Math.cos(RotationUtils.toRadians(this.rotation));\n const sin = Math.sin(RotationUtils.toRadians(this.rotation));\n\n const rotatedX = flippedX * cos - flippedY * sin;\n const rotatedY = flippedX * sin + flippedY * cos;\n\n // Finally translate to world position (ctx.translate is applied last)\n return {\n x: this.x + rotatedX,\n y: this.y + rotatedY,\n };\n }\n\n /**\n * Hit test for crop box handles in world coordinates\n * Returns {type: 'corner'|'edge', anchor: string} if hit, null otherwise\n * @param worldX - X coordinate in world space\n * @param worldY - Y coordinate in world space\n * @param zoom - Current zoom level (used to scale hit areas for consistent interaction)\n */\n hitTestCropHandle(\n worldX: number,\n worldY: number,\n zoom: number = 1.0\n ): { type: 'corner' | 'edge'; anchor: string; worldX: number; worldY: number } | null {\n if (!this.isCropping) return null;\n\n const cropBox = this.getCropBoxBounds();\n if (!cropBox) return null;\n\n // Convert world coordinates to local\n const local = this.worldToLocal(worldX, worldY);\n\n // Scale hit areas by 1/zoom to maintain consistent screen-space hit targets\n // When zoomed out (zoom < 1), hit areas need to be larger in world space\n const hitScale = 1 / zoom;\n\n // Crop handle constants - scaled for zoom\n const CORNER_HIT_RADIUS_SCALED = CORNER_HANDLE_HIT_RADIUS * hitScale;\n const EDGE_LENGTH_SCALED = EDGE_HANDLE_LENGTH * hitScale;\n const EDGE_WIDTH_SCALED = EDGE_HANDLE_HIT_WIDTH * hitScale; // Width perpendicular to edge\n\n // Test corner handles first (higher priority)\n // Circular hit zones for corners\n const corners = [\n { x: cropBox.x, y: cropBox.y, anchor: 'top-left' },\n { x: cropBox.x + cropBox.width, y: cropBox.y, anchor: 'top-right' },\n { x: cropBox.x, y: cropBox.y + cropBox.height, anchor: 'bottom-left' },\n { x: cropBox.x + cropBox.width, y: cropBox.y + cropBox.height, anchor: 'bottom-right' },\n ];\n\n for (const corner of corners) {\n const dx = local.x - corner.x;\n const dy = local.y - corner.y;\n const distance = Math.sqrt(dx * dx + dy * dy);\n\n // Circular hit zone\n if (distance <= CORNER_HIT_RADIUS_SCALED) {\n const worldCorner = this.localToWorld(corner.x, corner.y);\n return { type: 'corner', anchor: corner.anchor, worldX: worldCorner.x, worldY: worldCorner.y };\n }\n }\n\n // Test edge handles (rectangles - wide for top/bottom, tall for left/right)\n const edges = [\n { x: cropBox.x + cropBox.width / 2, y: cropBox.y, anchor: 'top', orientation: 'horizontal' },\n { x: cropBox.x + cropBox.width / 2, y: cropBox.y + cropBox.height, anchor: 'bottom', orientation: 'horizontal' },\n { x: cropBox.x, y: cropBox.y + cropBox.height / 2, anchor: 'left', orientation: 'vertical' },\n { x: cropBox.x + cropBox.width, y: cropBox.y + cropBox.height / 2, anchor: 'right', orientation: 'vertical' },\n ];\n\n for (const edge of edges) {\n const dx = local.x - edge.x;\n const dy = local.y - edge.y;\n\n // Check if within the rectangular bounds\n // Use large hit zones for comfortable touch/mouse interaction\n let halfWidth, halfHeight;\n if (edge.orientation === 'horizontal') {\n // Top/bottom: wide along edge, uses EDGE_WIDTH for perpendicular hit zone\n halfWidth = EDGE_LENGTH_SCALED / 2;\n halfHeight = EDGE_WIDTH_SCALED / 2;\n } else {\n // Left/right: tall along edge, uses EDGE_WIDTH for perpendicular hit zone\n halfWidth = EDGE_WIDTH_SCALED / 2;\n halfHeight = EDGE_LENGTH_SCALED / 2;\n }\n\n if (Math.abs(dx) <= halfWidth && Math.abs(dy) <= halfHeight) {\n // Convert edge position from local to world coordinates to determine screen position\n const worldEdge = this.localToWorld(edge.x, edge.y);\n return { type: 'edge', anchor: edge.anchor, worldX: worldEdge.x, worldY: worldEdge.y };\n }\n }\n\n return null;\n }\n\n /**\n * Hit test for inside crop box in world coordinates\n * Used for dragging the image within the crop area\n */\n hitTestCropBox(worldX: number, worldY: number): boolean {\n if (!this.isCropping) return false;\n\n const cropBox = this.getCropBoxBounds();\n if (!cropBox) return false;\n\n // Convert world coordinates to local\n const local = this.worldToLocal(worldX, worldY);\n\n return (\n local.x >= cropBox.x &&\n local.x <= cropBox.x + cropBox.width &&\n local.y >= cropBox.y &&\n local.y <= cropBox.y + cropBox.height\n );\n }\n\n /**\n * Update crop box (normalized 0-1 coordinates)\n * @param {number} cropX - Normalized X position (0-1)\n * @param {number} cropY - Normalized Y position (0-1)\n * @param {number} cropWidth - Normalized width (0-1)\n * @param {number} cropHeight - Normalized height (0-1)\n * @param {boolean} adjustPosition - Whether to adjust this.x, this.y to keep visual position stable (default: false)\n */\n updateCrop(\n cropX: number,\n cropY: number,\n cropWidth: number,\n cropHeight: number,\n adjustPosition: boolean = false\n ): void {\n // Ensure minimum crop size\n const minSize = 0.1;\n\n // Clamp width and height first (ensure they're valid sizes)\n let clampedWidth = Math.max(minSize, Math.min(1, cropWidth));\n let clampedHeight = Math.max(minSize, Math.min(1, cropHeight));\n\n // Then clamp position so crop box stays within 0-1 bounds\n // If position + size > 1, adjust position to keep right/bottom edge at 1\n let clampedX = Math.max(0, Math.min(1 - clampedWidth, cropX));\n let clampedY = Math.max(0, Math.min(1 - clampedHeight, cropY));\n\n // Final safety check: if the box is still outside bounds, reduce size\n if (clampedX + clampedWidth > 1) {\n clampedWidth = 1 - clampedX;\n }\n if (clampedY + clampedHeight > 1) {\n clampedHeight = 1 - clampedY;\n }\n\n // If adjustPosition is true, calculate offset to keep visual position stable\n let worldOffsetX = 0;\n let worldOffsetY = 0;\n\n if (adjustPosition) {\n // Calculate the old crop center in local coordinates\n const oldCropCenterX =\n this.transformData.cropX * this.transformData.width +\n (this.transformData.cropWidth * this.transformData.width) / 2;\n const oldCropCenterY =\n this.transformData.cropY * this.transformData.height +\n (this.transformData.cropHeight * this.transformData.height) / 2;\n\n // Calculate the new crop center in local coordinates\n const newCropCenterX = clampedX * this.transformData.width + (clampedWidth * this.transformData.width) / 2;\n const newCropCenterY = clampedY * this.transformData.height + (clampedHeight * this.transformData.height) / 2;\n\n // Calculate the offset between old and new crop centers in local space\n const localOffsetX = newCropCenterX - oldCropCenterX;\n const localOffsetY = newCropCenterY - oldCropCenterY;\n\n // Rotate the offset to world space\n const cos = Math.cos(RotationUtils.toRadians(this.rotation));\n const sin = Math.sin(RotationUtils.toRadians(this.rotation));\n worldOffsetX = localOffsetX * cos - localOffsetY * sin;\n worldOffsetY = localOffsetX * sin + localOffsetY * cos;\n }\n\n // Update the crop parameters\n this.transformData.cropX = clampedX;\n this.transformData.cropY = clampedY;\n this.transformData.cropWidth = clampedWidth;\n this.transformData.cropHeight = clampedHeight;\n\n // Adjust this.x, this.y if requested\n if (adjustPosition) {\n this.x += worldOffsetX;\n this.y += worldOffsetY;\n }\n }\n\n /**\n * Get the local position of the corner opposite to the given anchor\n * Corners are relative to frame center\n */\n private getOppositeCornerLocalPosition(\n anchor: ResizeAnchor,\n width: number,\n height: number\n ): { x: number; y: number } {\n const halfW = width / 2;\n const halfH = height / 2;\n\n const oppositeCorners: Record<ResizeAnchor, { x: number; y: number }> = {\n 'top-left': { x: halfW, y: halfH }, // Opposite is bottom-right\n 'top-right': { x: -halfW, y: halfH }, // Opposite is bottom-left\n 'bottom-left': { x: halfW, y: -halfH }, // Opposite is top-right\n 'bottom-right': { x: -halfW, y: -halfH }, // Opposite is top-left\n top: { x: 0, y: halfH }, // Opposite is bottom edge\n bottom: { x: 0, y: -halfH }, // Opposite is top edge\n left: { x: halfW, y: 0 }, // Opposite is right edge\n right: { x: -halfW, y: 0 }, // Opposite is left edge\n 'middle-top': { x: 0, y: halfH }, // Opposite is bottom edge\n 'middle-bottom': { x: 0, y: -halfH }, // Opposite is top edge\n 'middle-left': { x: halfW, y: 0 }, // Opposite is right edge\n 'middle-right': { x: -halfW, y: 0 }, // Opposite is left edge\n };\n\n return oppositeCorners[anchor];\n }\n\n /**\n * Calculate frame center position from crop center position\n * In crop mode, this.x/y is the crop center, not the frame center\n */\n private calculateFrameCenterFromCropCenter(startData: TransformStartData): { x: number; y: number } {\n const cropWidth = imageTransformData(startData).width * imageTransformData(startData).cropWidth;\n const cropHeight = imageTransformData(startData).height * imageTransformData(startData).cropHeight;\n\n // Offset from crop center to frame center in local space\n // NOTE: No flip transformation needed here!\n // The local offsets are stored in unflipped coordinate space,\n // and this.x/this.y are also in unflipped world space.\n // The flip only affects rendering, not the coordinate calculations.\n const localOffsetX =\n imageTransformData(startData).width / 2 -\n (imageTransformData(startData).cropX * imageTransformData(startData).width + cropWidth / 2);\n const localOffsetY =\n imageTransformData(startData).height / 2 -\n (imageTransformData(startData).cropY * imageTransformData(startData).height + cropHeight / 2);\n\n // Rotate to world space\n const transform = new Transform(startData);\n const worldOffset = transform.localDeltaToWorld(localOffsetX, localOffsetY);\n\n // Frame center = crop center + offset\n return {\n x: startData.x + worldOffset.dx,\n y: startData.y + worldOffset.dy,\n };\n }\n\n /**\n * Get the position of the fixed corner (opposite of anchor) in world coordinates\n */\n private getFixedCornerWorldPosition(anchor: ResizeAnchor, startData: TransformStartData): { x: number; y: number } {\n // Determine fixed corner position in start frame's local coords\n const fixedCornerLocal = this.getOppositeCornerLocalPosition(\n anchor,\n imageTransformData(startData).width,\n imageTransformData(startData).height\n );\n\n // Calculate start frame center in world coords\n const startFrameCenter = this.calculateFrameCenterFromCropCenter(startData);\n\n // Transform to world coords\n const startTransform = new Transform(startData);\n const fixedCornerWorldOffset = startTransform.localDeltaToWorld(fixedCornerLocal.x, fixedCornerLocal.y);\n\n return {\n x: startFrameCenter.x + fixedCornerWorldOffset.dx,\n y: startFrameCenter.y + fixedCornerWorldOffset.dy,\n };\n }\n\n /**\n * Calculate where the frame center would be after resize\n * The opposite corner from the anchor stays fixed in world space\n */\n private calculateFrameCenterAfterResize(\n scale: number,\n anchor: ResizeAnchor,\n startData: TransformStartData\n ): { x: number; y: number } {\n // Get the fixed corner position in world coords (opposite of dragged anchor)\n const fixedCornerWorld = this.getFixedCornerWorldPosition(anchor, startData);\n\n // Calculate new frame dimensions\n const newFrameWidth = imageTransformData(startData).width * scale;\n const newFrameHeight = newFrameWidth / this.imageAspectRatio;\n\n // Get fixed corner position in new frame's local coords\n const fixedCornerLocal = this.getOppositeCornerLocalPosition(anchor, newFrameWidth, newFrameHeight);\n\n // Transform fixed corner local position to world coords\n const transform = new Transform({\n x: 0, // Will calculate frame center\n y: 0,\n rotation: startData.rotation,\n });\n\n const fixedCornerWorldOffset = transform.localDeltaToWorld(fixedCornerLocal.x, fixedCornerLocal.y);\n\n // Frame center = fixed corner world position - fixed corner offset in world coords\n return {\n x: fixedCornerWorld.x - fixedCornerWorldOffset.dx,\n y: fixedCornerWorld.y - fixedCornerWorldOffset.dy,\n };\n }\n\n /**\n * Calculate crop state (position and size in normalized coords) at a given scale\n * This is the core geometric calculation that determines where the crop zone\n * would be positioned within the frame after resize.\n */\n private calculateCropStateAtScale(\n scale: number,\n anchor: ResizeAnchor,\n startData: TransformStartData\n ): { cropX: number; cropY: number; cropWidth: number; cropHeight: number } {\n // Calculate new frame dimensions\n const newFrameWidth = imageTransformData(startData).width * scale;\n const newFrameHeight = newFrameWidth / this.imageAspectRatio;\n\n // Crop zone world size (constant)\n const cropWorldWidth = imageTransformData(startData).cropWidth * imageTransformData(startData).width;\n const cropWorldHeight = imageTransformData(startData).cropHeight * imageTransformData(startData).height;\n\n // Crop zone normalized size in new frame\n const cropNormWidth = cropWorldWidth / newFrameWidth;\n const cropNormHeight = cropWorldHeight / newFrameHeight;\n\n // Calculate new frame center position\n // The frame resizes with the opposite corner staying fixed\n const newFrameCenter = this.calculateFrameCenterAfterResize(scale, anchor, startData);\n\n // Calculate crop center position (stays at same world position)\n const cropCenterWorld = {\n x: startData.x, // In crop mode, this.x/y is the crop center\n y: startData.y,\n };\n\n // Calculate offset from new frame center to crop center (in world coords)\n const worldOffsetX = cropCenterWorld.x - newFrameCenter.x;\n const worldOffsetY = cropCenterWorld.y - newFrameCenter.y;\n\n // Convert offset to local frame coordinates\n const transform = new Transform({\n x: newFrameCenter.x,\n y: newFrameCenter.y,\n rotation: startData.rotation,\n });\n\n const localOffset = transform.worldDeltaToLocal(worldOffsetX, worldOffsetY);\n\n // Calculate crop center in frame-local coordinates\n const cropCenterLocalX = newFrameWidth / 2 + localOffset.dx;\n const cropCenterLocalY = newFrameHeight / 2 + localOffset.dy;\n\n // Convert to normalized coordinates (0-1)\n const cropX = cropCenterLocalX / newFrameWidth - cropNormWidth / 2;\n const cropY = cropCenterLocalY / newFrameHeight - cropNormHeight / 2;\n\n return {\n cropX,\n cropY,\n cropWidth: cropNormWidth,\n cropHeight: cropNormHeight,\n };\n }\n\n /**\n * Check if a given scale keeps crop within frame bounds\n */\n private isScaleValidForCropBounds(scale: number, anchor: ResizeAnchor, startData: TransformStartData): boolean {\n const EPSILON = 0.0001; // Tolerance for floating point comparison\n\n // Calculate what the crop state would be at this scale\n const cropState = this.calculateCropStateAtScale(scale, anchor, startData);\n\n // Check if crop width/height exceed 1.0 (impossible - crop larger than frame)\n if (cropState.cropWidth > 1.0 + EPSILON || cropState.cropHeight > 1.0 + EPSILON) {\n return false;\n }\n\n // Check if crop is within bounds [0, 1] in both dimensions\n const cropLeft = cropState.cropX;\n const cropRight = cropState.cropX + cropState.cropWidth;\n const cropTop = cropState.cropY;\n const cropBottom = cropState.cropY + cropState.cropHeight;\n\n // Crop must be fully within frame: 0 <= crop edges <= 1\n const withinBounds =\n cropLeft >= -EPSILON && cropTop >= -EPSILON && cropRight <= 1.0 + EPSILON && cropBottom <= 1.0 + EPSILON;\n\n return withinBounds;\n }\n\n /**\n * Calculate minimum scale that keeps crop within frame bounds\n * Uses binary search to find the largest scale >= proposedScale that satisfies constraints\n */\n private calculateMinScaleForCropBounds(\n anchor: ResizeAnchor,\n startData: TransformStartData,\n proposedScale: number,\n currentScale: number\n ): number {\n const EPSILON = 0.0001; // Tolerance for floating point comparison\n const MAX_ITERATIONS = 20; // More than enough for precision\n\n // If proposed scale is already valid, use it\n if (this.isScaleValidForCropBounds(proposedScale, anchor, startData)) {\n return proposedScale;\n }\n\n // Binary search for the minimum valid scale\n // We're looking for the smallest scale that still satisfies constraints\n let lo = proposedScale; // Known invalid (too small)\n let hi = currentScale; // Known valid (current state)\n\n for (let i = 0; i < MAX_ITERATIONS; i++) {\n const testScale = (lo + hi) / 2;\n\n if (this.isScaleValidForCropBounds(testScale, anchor, startData)) {\n // This scale works - try going smaller (closer to proposed)\n hi = testScale;\n } else {\n // This scale fails - need to go larger\n lo = testScale;\n }\n\n // Early exit if we've converged\n if (Math.abs(hi - lo) < EPSILON) {\n break;\n }\n }\n\n return hi; // Return the smallest valid scale we found\n }\n\n /**\n * Remap anchor to account for flips\n */\n private remapAnchorForFlips(anchor: ResizeAnchor, startData: TransformStartData): ResizeAnchor {\n let effectiveAnchor = anchor;\n const flipH = imageTransformData(startData).flipHorizontal;\n const flipV = imageTransformData(startData).flipVertical;\n\n if (flipH) {\n if (effectiveAnchor.includes('left')) {\n effectiveAnchor = effectiveAnchor.replace('left', 'right') as ResizeAnchor;\n } else if (effectiveAnchor.includes('right')) {\n effectiveAnchor = effectiveAnchor.replace('right', 'left') as ResizeAnchor;\n }\n }\n\n if (flipV) {\n if (effectiveAnchor.includes('top')) {\n effectiveAnchor = effectiveAnchor.replace('top', 'bottom') as ResizeAnchor;\n } else if (effectiveAnchor.includes('bottom')) {\n effectiveAnchor = effectiveAnchor.replace('bottom', 'top') as ResizeAnchor;\n }\n }\n\n return effectiveAnchor;\n }\n\n /**\n * Clone this element\n */\n clone(): ImageElement {\n const json = this.toJSON();\n // CRITICAL: Remove imageUrl to prevent constructor from loading image again\n // We'll manually assign the already-loaded image element instead\n const { imageUrl, ...configWithoutUrl } = json;\n\n const cloned = new ImageElement(configWithoutUrl);\n\n // Manually set image properties from the original element\n cloned.imageUrl = this.imageUrl;\n cloned.imageElement = this.imageElement;\n cloned.imageLoaded = this.imageLoaded;\n cloned.imageAspectRatio = this.imageAspectRatio;\n cloned.isCropping = this.isCropping; // Preserve crop mode state\n cloned.onLoadCallback = this.onLoadCallback; // Preserve load callback\n cloned.isSvg = this.isSvg; // Preserve SVG flag\n cloned.imageLoadState = this.imageLoadState; // Preserve load state\n cloned.imageLoadError = this.imageLoadError; // Preserve load error\n cloned.preserveDimensions = this.preserveDimensions; // Preserve dimension lock for cover crop\n\n return cloned;\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): ImageElementConfig & { id: string; type: 'image'; x: number; y: number; rotation: number } {\n const baseJSON = super.toJSON();\n return {\n ...baseJSON,\n id: this.id, // Explicitly include to ensure string type\n type: 'image' as const,\n transformType: 'image' as const,\n x: this.x, // Explicitly include to ensure number type\n y: this.y, // Explicitly include to ensure number type\n rotation: this.rotation, // Explicitly include to ensure number type\n imageUrl: this.imageUrl,\n imageAspectRatio: this.imageAspectRatio,\n transformData: { ...this.transformData },\n };\n }\n\n /**\n * Clean up resources held by this element\n * Call this when the element is being removed from the canvas to prevent memory leaks\n * Particularly important for iOS Safari which has strict memory limits\n */\n destroy(): void {\n // Clear any pending load timeout or retry\n this.clearLoadTimeout();\n\n // Release image from shared cache (decrements refCount, evicts at 0)\n this.releaseImageCache();\n\n // Revoke SVG blob URL if present\n this.revokeSvgBlobUrl();\n\n // Clear references to allow garbage collection\n this.imageElement = null;\n this.onLoadCallback = null;\n this.imageLoadError = null;\n }\n}\n\nexport default ImageElement;\n","/**\n * Comprehensive TypeScript type definitions for custom-canvas\n * Uses discriminated unions for type-safe transform handling\n */\n\n// ============================================================================\n// Core Element Types\n// ============================================================================\n\nexport type TransformType =\n | 'custom'\n | 'distort'\n | 'circle'\n | 'lean'\n | 'arch'\n | 'ascend'\n | 'wave'\n | 'flag'\n | 'image'\n | 'group'\n | 'artboard'\n | 'shape'\n | 'path';\n\nexport interface BoundingBox {\n x: number;\n y: number;\n width: number;\n height: number;\n}\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport type ResizeAnchor =\n | 'top-left'\n | 'top-right'\n | 'bottom-left'\n | 'bottom-right'\n | 'top'\n | 'bottom'\n | 'left'\n | 'right'\n | 'middle-left'\n | 'middle-right'\n | 'middle-top'\n | 'middle-bottom';\n\nexport type TextAlign = 'left' | 'center' | 'right';\n\n// ============================================================================\n// Transform Data Types (Discriminated Unions)\n// ============================================================================\n\nexport interface CustomTransformData {\n type: 'custom';\n controlPoints: Point[];\n /** Container width for text wrapping */\n width: number;\n}\n\nexport interface DistortTransformData {\n type: 'distort';\n [key: string]: unknown; // Placeholder until DistortTransform is implemented\n}\n\nexport interface CircleTransformData {\n type: 'circle';\n radius: number;\n scale: number;\n reverse: boolean;\n}\n\nexport interface LeanTransformData {\n type: 'lean';\n leanAmount: number; // -0.5 to 0.5\n /** Container width for text layout */\n width: number;\n}\n\nexport interface ArchTransformData {\n type: 'arch';\n archHeight: number; // -1 to 1\n /** Container width for text layout */\n width: number;\n}\n\nexport interface AscendTransformData {\n type: 'ascend';\n ascendAngle: number; // -30 to 30 degrees\n /** Container width for text layout */\n width: number;\n}\n\nexport interface WaveTransformData {\n type: 'wave';\n amplitude: number; // 0 to 1\n frequency: number; // 1 to 5\n /** Container width for text layout */\n width: number;\n}\n\nexport interface FlagTransformData {\n type: 'flag';\n amplitude: number; // 0 to 1\n frequency: number; // 1 to 5\n /** Container width for text layout */\n width: number;\n}\n\nexport interface ImageTransformData {\n type: 'image';\n width: number;\n height: number;\n // Crop properties (normalized 0-1)\n cropX: number;\n cropY: number;\n cropWidth: number;\n cropHeight: number;\n // Flip properties\n flipHorizontal: boolean;\n flipVertical: boolean;\n // Border radius (0-50)\n borderRadius: number;\n}\n\nexport interface GroupTransformData {\n type: 'group';\n}\n\nexport interface ArtboardTransformData {\n type: 'artboard';\n}\n\n// Shape types and data\nexport type ShapeType = 'rectangle' | 'circle' | 'ellipse' | 'triangle' | 'polygon' | 'star' | 'line';\n\nexport interface ShapeTransformData {\n type: 'shape';\n shapeType: ShapeType;\n\n // Common properties\n width: number;\n height: number;\n\n // Rectangle-specific\n borderRadius?: number; // 0-50 (percentage)\n\n // Circle/Ellipse-specific\n radiusX?: number;\n radiusY?: number;\n\n // Polygon-specific\n sides?: number; // 3-20\n\n // Star-specific\n points?: number; // 3-20\n innerRadius?: number; // 0-1 (relative to outer radius)\n\n // Fill properties\n fillColor?: string;\n fillOpacity?: number; // 0-1\n}\n\n// ============================================================================\n// Path Types (Custom Bezier Paths)\n// ============================================================================\n\n/**\n * Path point types define how curve handles behave\n */\nexport type PathPointType =\n | 'corner' // Sharp corner, no curve handles\n | 'smooth' // Smooth curve, handles are mirrored and equal length\n | 'bezier'; // Asymmetric curve, independent handle control\n\n/**\n * A single point in a path with optional bezier control handles\n * All coordinates are relative to the path element's position\n */\nexport interface PathPoint {\n id: string;\n x: number;\n y: number;\n type: PathPointType;\n\n // Control handles for bezier curves (relative to point position)\n // handleIn: control point for curve coming INTO this point\n // handleOut: control point for curve going OUT from this point\n handleIn?: Point;\n handleOut?: Point;\n}\n\n/**\n * Transform data for custom path elements\n */\nexport interface PathTransformData {\n type: 'path';\n\n // Array of points defining the path\n points: PathPoint[];\n\n // Whether the path is closed (connects last point to first)\n closed: boolean;\n\n // Bounding box dimensions (cached for performance)\n width: number;\n height: number;\n\n // Fill properties (only apply if path is closed)\n fillEnabled?: boolean;\n fillColor?: string;\n fillOpacity?: number;\n\n // Stroke properties (always apply)\n strokeEnabled?: boolean;\n strokeColor?: string;\n strokeWidth?: number;\n}\n\n// Discriminated union of all transform data types\nexport type AnyTransformData =\n | CustomTransformData\n | DistortTransformData\n | CircleTransformData\n | LeanTransformData\n | ArchTransformData\n | AscendTransformData\n | WaveTransformData\n | FlagTransformData\n | ImageTransformData\n | GroupTransformData\n | ArtboardTransformData\n | ShapeTransformData\n | PathTransformData;\n\n// ============================================================================\n// Transform Start Data (for interaction state)\n// ============================================================================\n\nexport interface TransformStartData {\n id: string;\n x: number;\n y: number;\n rotation: number;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- transformData is intentionally flexible; consumers cast to specific types\n transformData: Record<string, any>;\n // For bounding box operations\n width?: number;\n height?: number;\n // For text elements\n fontSize?: number;\n richText?: unknown;\n lineCount?: number;\n // For visual bbox (added by InteractionStateMachine)\n visualX?: number;\n visualY?: number;\n visualWidth?: number;\n visualHeight?: number;\n bboxX?: number;\n bboxY?: number;\n // For image crop mode\n cropX?: number;\n cropY?: number;\n cropWidth?: number;\n cropHeight?: number;\n // For stroke scaling during resize\n strokeWidth?: number;\n // For group operations\n childrenStartData?: TransformStartData[];\n // Allow additional properties for transform-specific start data\n [key: string]: unknown;\n}\n\n// ============================================================================\n// Blend Modes & Compositing\n// ============================================================================\n\nexport type BlendMode =\n | 'normal' // Standard blending\n | 'knockout' // Cuts through to artboard (destination-out)\n | 'clip'; // Clips content to shape (destination-in)\n// Future: 'multiply' | 'screen' | 'overlay' | etc.\n\nexport type CompositingScope = 'group' | 'artboard';\nexport type KnockoutScope = CompositingScope; // backward compat alias\n\nexport interface CompositingConfig {\n fill?: boolean; // Element fill participates in compositing\n stroke?: boolean; // Element stroke participates in compositing\n scope?: CompositingScope; // 'group' (default) or 'artboard'\n}\nexport type KnockoutConfig = CompositingConfig; // backward compat alias\n\n// ============================================================================\n// Stroke System\n// ============================================================================\n\nexport interface StrokeConfig {\n enabled: boolean;\n color: string;\n width: number;\n\n // Style options\n dashArray?: number[]; // [5, 5] for dashed lines\n lineCap?: 'butt' | 'round' | 'square';\n lineJoin?: 'miter' | 'round' | 'bevel';\n miterLimit?: number;\n\n // Visual effects\n opacity?: number; // 0-1\n feather?: number; // 0-20 blur radius\n}\n\n/**\n * Rendering context metadata passed to renderers\n * Used to customize rendering behavior for specific contexts (e.g., knockout, export, preview)\n */\nexport interface RenderingContext {\n /** True when rendering for knockout compositing (affects stroke color/opacity) */\n isKnockout?: boolean;\n /** True when rendering for export (may skip certain preview-only features) */\n isExport?: boolean;\n /** True when rendering for mask application */\n isMask?: boolean;\n /** True when the caller has already applied element positioning (translate/rotate) */\n positionApplied?: boolean;\n}\n\n// ============================================================================\n// Mask System\n// ============================================================================\n\nexport type MaskType =\n | 'clip' // Hard edge clipping (text, shape clipping)\n | 'alpha' // Alpha/transparency mask\n | 'luma' // Luminosity mask\n | 'distress'; // Texture/grunge mask (HIGH PRIORITY for t-shirt design)\n\nexport interface MaskDefinition {\n id: string;\n type: MaskType;\n\n // The element that defines the mask\n // Positioned relative to the masked element\n maskElement: AnyElementConfig;\n\n // Mask properties\n inverted?: boolean; // Invert the mask\n feather?: number; // Blur edges (0-100)\n opacity?: number; // Mask strength (0-1)\n blendMode?: string; // How this mask combines with other masks\n}\n\n// ============================================================================\n// Distress Effects (HIGH PRIORITY for t-shirt design)\n// ============================================================================\n\nexport type DistressStyle = 'worn' | 'cracked' | 'grunge' | 'retro' | 'custom';\n\nexport interface DistressEffect {\n enabled: boolean;\n style: DistressStyle;\n intensity: number; // 0-100 (overall effect strength)\n\n // Effect parameters\n fadeAmount?: number; // 0-100 (color fading)\n grainAmount?: number; // 0-100 (film grain/noise)\n scratchAmount?: number; // 0-100 (scratch marks)\n edgeWear?: number; // 0-100 (worn edges)\n\n // Custom texture (for 'custom' style)\n textureUrl?: string; // URL to distress texture image\n textureOpacity?: number; // 0-1 (how strong the texture is)\n textureBlendMode?: 'multiply' | 'screen' | 'overlay';\n\n // Random seed (for consistent results)\n seed?: number;\n}\n\n// ============================================================================\n// Glyph & OpenType Feature Types\n// ============================================================================\n\n/**\n * Per-character glyph override\n * Allows substituting specific characters with alternate glyphs from the font\n */\nexport interface GlyphOverride {\n /** Index of the character in the text string */\n charIndex: number;\n /** Glyph index in the font file */\n glyphIndex: number;\n /** Unicode codepoint of the original character */\n unicode: string;\n /** Human-readable name of the alternate (e.g., \"Swash A\", \"Fancy G\") */\n alternateName?: string;\n}\n\n/**\n * OpenType feature settings for advanced typography\n * Features are applied globally to the entire text element\n */\nexport interface OpenTypeFeatures {\n /** Standard ligatures (fi, fl, etc.) */\n liga?: boolean;\n /** Discretionary ligatures (ct, st, etc.) */\n dlig?: boolean;\n /** Contextual alternates */\n calt?: boolean;\n /** Swash alternates */\n swsh?: boolean;\n /** Small capitals */\n smcp?: boolean;\n /** All small capitals */\n c2sc?: boolean;\n /** Oldstyle figures (0-9) */\n onum?: boolean;\n /** Lining figures (0-9) */\n lnum?: boolean;\n /** Tabular figures */\n tnum?: boolean;\n /** Proportional figures */\n pnum?: boolean;\n /** Stylistic sets (ss01-ss20) */\n ss01?: boolean;\n ss02?: boolean;\n ss03?: boolean;\n ss04?: boolean;\n ss05?: boolean;\n ss06?: boolean;\n ss07?: boolean;\n ss08?: boolean;\n ss09?: boolean;\n ss10?: boolean;\n ss11?: boolean;\n ss12?: boolean;\n ss13?: boolean;\n ss14?: boolean;\n ss15?: boolean;\n ss16?: boolean;\n ss17?: boolean;\n ss18?: boolean;\n ss19?: boolean;\n ss20?: boolean;\n /** Character variants (cv01-cv99) - common ones */\n cv01?: boolean;\n cv02?: boolean;\n cv03?: boolean;\n cv04?: boolean;\n cv05?: boolean;\n /** Glyph composition/decomposition */\n ccmp?: boolean;\n /** Localized forms */\n locl?: boolean;\n /** Kerning */\n kern?: boolean;\n /** Mark positioning */\n mark?: boolean;\n /** Mark-to-mark positioning */\n mkmk?: boolean;\n}\n\n/**\n * Metadata about available glyphs for a specific character\n */\nexport interface GlyphAlternate {\n /** Glyph index in the font file */\n glyphIndex: number;\n /** Unicode codepoint this glyph represents */\n unicode: string;\n /** Human-readable name (e.g., \"A.swash\", \"g.alt01\") */\n name: string;\n /** Category of alternate (swash, stylistic, etc.) */\n category?: 'default' | 'swash' | 'stylistic' | 'contextual' | 'ligature';\n /** Preview data URL (base64-encoded image) */\n previewDataUrl?: string;\n}\n\n// ============================================================================\n// Rich Text System (Character-Level Formatting)\n// ============================================================================\n\n/**\n * Character-level styling properties\n * Each property is optional - undefined means inherit from element defaults\n */\nexport interface CharacterStyle {\n /** Text color (CSS color string) */\n color?: string;\n /** Font family name */\n fontFamily?: string;\n /** Font size in pixels */\n fontSize?: number;\n /** Bold weight */\n bold?: boolean;\n /** Italic style */\n italic?: boolean;\n /** Underline decoration */\n underline?: boolean;\n /** Strikethrough decoration */\n strikethrough?: boolean;\n}\n\n/**\n * A span of text with uniform styling\n * Used for efficient storage of rich text (avoids per-character arrays)\n */\nexport interface TextSpan {\n /** The text content of this span */\n text: string;\n /** Style properties for this span (optional properties inherit from element defaults) */\n style: CharacterStyle;\n}\n\n/**\n * Rich text data structure with helper methods\n * Stores text as an array of styled spans for efficient rendering\n */\nexport class RichText {\n spans: TextSpan[];\n\n constructor(spans: TextSpan[] = []) {\n this.spans = spans.length > 0 ? spans : [{ text: '', style: {} }];\n this.normalize();\n }\n\n /**\n * Create RichText from plain text string (no special formatting)\n */\n static fromPlainText(text: string, style: CharacterStyle = {}): RichText {\n return new RichText([{ text, style }]);\n }\n\n /**\n * Get the full text content (all spans concatenated)\n */\n getText(): string {\n return this.spans.map((s) => s.text).join('');\n }\n\n /**\n * Get the total character count\n */\n getLength(): number {\n return this.getText().length;\n }\n\n /**\n * Get the style at a specific character index\n * @param charIndex Character index (0-based)\n * @returns The style object for the span containing this character\n */\n getStyleAt(charIndex: number): CharacterStyle {\n // Handle empty RichText\n if (this.spans.length === 0) return {};\n\n // Handle negative index - return first span style\n if (charIndex < 0) return this.spans[0].style;\n\n let currentIndex = 0;\n for (const span of this.spans) {\n if (charIndex < currentIndex + span.text.length) {\n return span.style;\n }\n currentIndex += span.text.length;\n }\n\n // At or past end - return LAST span's style (not empty object)\n // This ensures new characters typed at end inherit the previous style\n return this.spans[this.spans.length - 1].style;\n }\n\n /**\n * Apply a style to a character range\n * @param start Start index (inclusive)\n * @param end End index (exclusive)\n * @param style Style to apply (merged with existing styles)\n */\n applyStyle(start: number, end: number, style: CharacterStyle): void {\n if (start >= end || start < 0 || end > this.getLength()) {\n return;\n }\n\n const newSpans: TextSpan[] = [];\n let currentIndex = 0;\n\n for (const span of this.spans) {\n const spanStart = currentIndex;\n const spanEnd = currentIndex + span.text.length;\n\n // Case 1: Span is completely before the range\n if (spanEnd <= start) {\n newSpans.push(span);\n currentIndex = spanEnd;\n continue;\n }\n\n // Case 2: Span is completely after the range\n if (spanStart >= end) {\n newSpans.push(span);\n currentIndex = spanEnd;\n continue;\n }\n\n // Case 3: Span overlaps with range - split it\n const overlapStart = Math.max(start, spanStart);\n const overlapEnd = Math.min(end, spanEnd);\n\n // Before overlap\n if (overlapStart > spanStart) {\n newSpans.push({\n text: span.text.substring(0, overlapStart - spanStart),\n style: span.style,\n });\n }\n\n // Overlap region - apply new style\n newSpans.push({\n text: span.text.substring(overlapStart - spanStart, overlapEnd - spanStart),\n style: { ...span.style, ...style },\n });\n\n // After overlap\n if (overlapEnd < spanEnd) {\n newSpans.push({\n text: span.text.substring(overlapEnd - spanStart),\n style: span.style,\n });\n }\n\n currentIndex = spanEnd;\n }\n\n this.spans = newSpans;\n this.normalize();\n }\n\n /**\n * Insert text at a specific position with a given style\n * @param index Character index to insert at\n * @param text Text to insert\n * @param style Style for the inserted text\n */\n insert(index: number, text: string, style: CharacterStyle): void {\n if (text.length === 0) return;\n\n const newSpans: TextSpan[] = [];\n let currentIndex = 0;\n\n for (const span of this.spans) {\n const spanStart = currentIndex;\n const spanEnd = currentIndex + span.text.length;\n\n // Insert point is before this span\n if (index <= spanStart && newSpans.length === 0) {\n newSpans.push({ text, style });\n }\n\n // Insert point is within this span\n if (index > spanStart && index <= spanEnd) {\n const offset = index - spanStart;\n // Split the span\n newSpans.push({\n text: span.text.substring(0, offset),\n style: span.style,\n });\n newSpans.push({ text, style });\n newSpans.push({\n text: span.text.substring(offset),\n style: span.style,\n });\n currentIndex = spanEnd;\n continue;\n }\n\n newSpans.push(span);\n currentIndex = spanEnd;\n }\n\n // Insert at end if we haven't inserted yet\n if (index >= this.getLength() && !newSpans.some((s) => s.text === text)) {\n newSpans.push({ text, style });\n }\n\n this.spans = newSpans;\n this.normalize();\n }\n\n /**\n * Delete text in a character range\n * @param start Start index (inclusive)\n * @param end End index (exclusive)\n */\n delete(start: number, end: number): void {\n // Clamp to valid range instead of silently failing\n const length = this.getLength();\n start = Math.max(0, Math.min(start, length));\n end = Math.max(0, Math.min(end, length));\n\n // Nothing to delete if range is empty after clamping\n if (start >= end) {\n return;\n }\n\n const newSpans: TextSpan[] = [];\n let currentIndex = 0;\n\n for (const span of this.spans) {\n const spanStart = currentIndex;\n const spanEnd = currentIndex + span.text.length;\n\n // Span is completely before deletion range\n if (spanEnd <= start) {\n newSpans.push(span);\n currentIndex = spanEnd;\n continue;\n }\n\n // Span is completely after deletion range\n if (spanStart >= end) {\n newSpans.push(span);\n currentIndex = spanEnd;\n continue;\n }\n\n // Span overlaps with deletion range\n const deleteStart = Math.max(start, spanStart);\n const deleteEnd = Math.min(end, spanEnd);\n\n // Keep text before deletion\n if (deleteStart > spanStart) {\n newSpans.push({\n text: span.text.substring(0, deleteStart - spanStart),\n style: span.style,\n });\n }\n\n // Keep text after deletion\n if (deleteEnd < spanEnd) {\n newSpans.push({\n text: span.text.substring(deleteEnd - spanStart),\n style: span.style,\n });\n }\n\n currentIndex = spanEnd;\n }\n\n this.spans = newSpans.length > 0 ? newSpans : [{ text: '', style: {} }];\n this.normalize();\n }\n\n /**\n * Normalize spans by:\n * 1. Removing empty spans\n * 2. Merging adjacent spans with identical styles\n * This prevents fragmentation and improves performance\n */\n private normalize(): void {\n // Step 1: Remove empty spans\n this.spans = this.spans.filter((s) => s.text !== '');\n\n // Step 2: Ensure at least one span exists (empty span for empty RichText)\n if (this.spans.length === 0) {\n this.spans = [{ text: '', style: {} }];\n return;\n }\n\n // Step 3: Merge adjacent spans with identical styles\n if (this.spans.length <= 1) return;\n\n const merged: TextSpan[] = [];\n let current = this.spans[0];\n\n for (let i = 1; i < this.spans.length; i++) {\n const next = this.spans[i];\n\n // Check if styles are identical\n if (this.stylesEqual(current.style, next.style)) {\n // Merge spans\n current = {\n text: current.text + next.text,\n style: current.style,\n };\n } else {\n merged.push(current);\n current = next;\n }\n }\n\n merged.push(current);\n this.spans = merged;\n }\n\n /**\n * Check if two styles are equal\n */\n private stylesEqual(a: CharacterStyle, b: CharacterStyle): boolean {\n return (\n a.color === b.color &&\n a.fontFamily === b.fontFamily &&\n a.fontSize === b.fontSize &&\n a.bold === b.bold &&\n a.italic === b.italic &&\n a.underline === b.underline &&\n a.strikethrough === b.strikethrough\n );\n }\n\n /**\n * Clone this RichText instance\n */\n clone(): RichText {\n return new RichText(\n this.spans.map((span) => ({\n text: span.text,\n style: { ...span.style },\n }))\n );\n }\n\n /**\n * Clear a specific style property from all spans.\n * Used when element-level property changes should override character-level.\n * After clearing, spans will inherit the property from element defaults.\n * @param property The style property to clear (e.g., 'fontFamily', 'bold')\n */\n clearStyleProperty(property: keyof CharacterStyle): void {\n for (const span of this.spans) {\n if (span.style[property] !== undefined) {\n delete span.style[property];\n }\n }\n this.normalize();\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): { spans: TextSpan[] } {\n return { spans: this.spans };\n }\n\n /**\n * Deserialize from JSON\n */\n static fromJSON(json: { spans: TextSpan[] }): RichText {\n return new RichText(json.spans);\n }\n}\n\n// ============================================================================\n// Element Configuration Types (Discriminated Unions)\n// ============================================================================\n\nexport interface BaseElementConfig {\n id?: string;\n x?: number;\n y?: number;\n rotation?: number;\n opacity?: number;\n\n // Transform type and data (specific to each element type)\n transformType?: TransformType;\n transformData?: Partial<AnyTransformData>;\n\n // Compositing\n blendMode?: BlendMode;\n knockoutParts?: KnockoutConfig;\n\n // Effects\n stroke?: StrokeConfig;\n masks?: MaskDefinition[];\n distressEffect?: DistressEffect;\n\n // Layer properties (for layers panel)\n name?: string;\n visible?: boolean;\n locked?: boolean;\n\n // Clipping mask - when true, this element clips all elements below it in the same group\n isClipping?: boolean;\n}\n\nexport interface BaseTextElementConfig extends BaseElementConfig {\n // Rich text support (new)\n richText?: RichText | { spans: TextSpan[] };\n // Legacy plain text (kept for backward compatibility)\n text?: string;\n // Element-level style defaults (used when richText spans don't specify)\n fontSize?: number;\n fontFamily?: string;\n color?: string;\n textAlign?: TextAlign;\n bold?: boolean;\n italic?: boolean;\n underline?: boolean;\n strikethrough?: boolean;\n\n // Glyph & OpenType features\n glyphOverrides?: GlyphOverride[];\n openTypeFeatures?: OpenTypeFeatures;\n}\n\n// Text element configs with specific transform types\nexport interface CustomElementConfig extends BaseTextElementConfig {\n transformType: 'custom';\n transformData?: Partial<CustomTransformData>;\n}\n\nexport interface DistortElementConfig extends BaseTextElementConfig {\n transformType: 'distort';\n transformData?: Partial<DistortTransformData>;\n}\n\nexport interface CircleElementConfig extends BaseTextElementConfig {\n transformType: 'circle';\n transformData?: Partial<CircleTransformData>;\n}\n\nexport interface LeanElementConfig extends BaseTextElementConfig {\n transformType: 'lean';\n transformData?: Partial<LeanTransformData>;\n}\n\nexport interface ArchElementConfig extends BaseTextElementConfig {\n transformType: 'arch';\n transformData?: Partial<ArchTransformData>;\n}\n\nexport interface AscendElementConfig extends BaseTextElementConfig {\n transformType: 'ascend';\n transformData?: Partial<AscendTransformData>;\n}\n\nexport interface WaveElementConfig extends BaseTextElementConfig {\n transformType: 'wave';\n transformData?: Partial<WaveTransformData>;\n}\n\nexport interface FlagElementConfig extends BaseTextElementConfig {\n transformType: 'flag';\n transformData?: Partial<FlagTransformData>;\n}\n\nexport interface ImageElementConfig extends BaseElementConfig {\n transformType: 'image';\n transformData?: Partial<ImageTransformData>;\n imageUrl?: string;\n imageAspectRatio?: number;\n /**\n * Callback invoked when the image finishes loading.\n *\n * The parameter is an `ImageElement` instance (clone of the loaded element).\n * Import `ImageElement` from `@snowcone-app/canvas/advanced` to narrow the type.\n *\n * @example\n * ```ts\n * import type { ImageElement } from '@snowcone-app/canvas/advanced';\n * const config: ImageElementConfig = {\n * transformType: 'image',\n * onLoadCallback(el) {\n * const img = el as ImageElement;\n * console.log(img.id, img.transformData.width);\n * },\n * };\n * ```\n */\n // Method syntax provides bivariant parameter checking, allowing callers to\n // type the parameter as ImageElement without importing it in this file.\n onLoadCallback?(element: BaseElementConfig & { readonly id: string; readonly transformType: 'image' }): void;\n /**\n * If true, dimensions (width/height) won't be recalculated when the image loads.\n * Use this when you've explicitly calculated dimensions (e.g., for \"cover\" sizing).\n */\n preserveDimensions?: boolean;\n}\n\nexport interface GroupElementConfig extends BaseElementConfig {\n transformType: 'group';\n transformData?: Partial<GroupTransformData>;\n children?: AnyElementConfig[];\n}\n\nexport interface ShapeElementConfig extends BaseElementConfig {\n transformType: 'shape';\n transformData?: Partial<ShapeTransformData>;\n}\n\nexport interface PathElementConfig extends BaseElementConfig {\n transformType: 'path';\n transformData?: Partial<PathTransformData>;\n}\n\nexport type ArtboardBackgroundType = 'color' | 'transparent' | 'texture';\n\n// Artboard-level distress texture (applied after all elements render)\nexport interface ArtboardDistressTexture {\n enabled: boolean;\n textureUrl: string; // URL to grayscale JPEG texture\n intensity: number; // 0-100\n}\n\n// Artboard-level image mask (applied after distress texture)\nexport interface ArtboardImageMask {\n enabled: boolean;\n imageUrl: string;\n maskType: 'clip' | 'alpha' | 'luma';\n opacity: number; // 0-100\n inverted?: boolean;\n}\n\n// Clip shape types for artboard content clipping\nexport type ClipShape =\n | 'rectangle' // Full artboard bounds (default, no clipping)\n | 'circle' // Circle inscribed in artboard\n | { type: 'rounded'; radius: number } // Rounded rectangle with specified corner radius\n | { type: 'path'; d: string } // Custom SVG path data\n // Union of multiple piece-local SVG paths, each translated to its\n // (x, y) inside the artboard frame and optionally rotated by 0/90/180/270\n // around the piece center. Used for ADR-0054 multi-piece spreads\n // (e.g. AFOOES hoodie) so artwork visually clips to the union of piece\n // silhouettes instead of the bounding rectangle. `baseWidth`/`baseHeight`\n // are the path's pre-rotation extents (the piece's unrotated bbox);\n // needed only when `rotation` is non-zero so we can rotate around the\n // piece's geometric center.\n | {\n type: 'composite-path';\n pieces: Array<{\n d: string;\n x: number;\n y: number;\n rotation?: 0 | 90 | 180 | 270;\n baseWidth?: number;\n baseHeight?: number;\n }>;\n };\n\nexport interface ArtboardConfig {\n id?: string;\n name?: string;\n x?: number;\n y?: number;\n width?: number;\n height?: number;\n backgroundColor?: string;\n backgroundType?: ArtboardBackgroundType;\n backgroundTexture?: string; // Texture ID or URL\n exportBackground?: boolean; // Whether to export background (defaults to false)\n elementIds?: string[];\n transformType?: 'artboard';\n\n // Clip shape for artboard content\n // - 'rectangle' or undefined: No clipping (content renders to artboard edges)\n // - 'circle': Content clipped to circle inscribed in artboard\n // - { type: 'rounded', radius: number }: Rounded rectangle corners\n // - { type: 'path', d: string }: Custom SVG path\n clipShape?: ClipShape;\n\n // Preview only - NOT exported to PNG (for t-shirt color preview)\n previewBackgroundColor?: string;\n\n // Artboard-level distress texture (applied after all elements, before UI layers)\n distressTexture?: ArtboardDistressTexture;\n\n // Artboard-level image mask (applied after distress texture)\n imageMask?: ArtboardImageMask;\n}\n\n// Discriminated union of all element configs\nexport type AnyElementConfig =\n | CustomElementConfig\n | DistortElementConfig\n | CircleElementConfig\n | LeanElementConfig\n | ArchElementConfig\n | AscendElementConfig\n | WaveElementConfig\n | FlagElementConfig\n | ImageElementConfig\n | GroupElementConfig\n | ShapeElementConfig\n | PathElementConfig;\n\n// Helper type for text-only elements (excludes image, shape, and path)\nexport type TextOnlyElementConfig = Exclude<\n AnyElementConfig,\n ImageElementConfig | ShapeElementConfig | PathElementConfig\n>;\n\n// ============================================================================\n// Interaction Types\n// ============================================================================\n\nexport type InteractionMode = 'idle' | 'drag' | 'resize' | 'rotate' | 'crop' | 'pen' | 'edit-path' | 'pinch';\n\nexport interface InteractionContext<T = unknown> {\n mode: InteractionMode;\n startX: number;\n startY: number;\n startData: TransformStartData;\n activeHandle?: ResizeAnchor;\n element: T; // Will be TextElement or its subclasses\n}\n\n// ============================================================================\n// Command History Types\n// ============================================================================\n\nexport interface Command {\n execute(): void;\n undo(): void;\n}\n\nexport interface ElementUpdate<T = unknown> {\n oldElement: T;\n newElement: T;\n}\n\nexport interface MultiElementUpdate<T = unknown> {\n updates: Map<string, ElementUpdate<T>>;\n}\n\n// ============================================================================\n// Selection Types\n// ============================================================================\n\nexport type SelectionState =\n | { type: 'none' }\n | { type: 'single'; elementId: string }\n | { type: 'multiple'; elementIds: Set<string> };\n\n// ============================================================================\n// Snap System Types\n// ============================================================================\n\nexport interface SnapGuide {\n type: 'vertical' | 'horizontal';\n sourceId?: string;\n targetId?: string;\n // For vertical guides\n x?: number;\n y1?: number;\n y2?: number;\n // For horizontal guides\n y?: number;\n x1?: number;\n x2?: number;\n}\n\nexport interface SnapResult {\n x: number;\n y: number;\n snappedX: boolean;\n snappedY: boolean;\n guides: SnapGuide[];\n}\n\n// ============================================================================\n// Spacing Indicator Types\n// ============================================================================\n\nexport interface SpacingIndicator {\n type: 'horizontal' | 'vertical';\n distance: number;\n isSnapTarget?: boolean; // If true, render in green to show snap will happen\n isAltHover?: boolean; // If true, render in purple to show Alt+hover measurements\n // For horizontal indicators\n x1?: number;\n x2?: number;\n y?: number;\n // For vertical indicators\n x?: number;\n y1?: number;\n y2?: number;\n // Label position\n labelX: number;\n labelY: number;\n}\n\n// ============================================================================\n// Resize Types\n// ============================================================================\n\nexport interface ResizeResult {\n newWidth: number;\n newHeight: number;\n newX: number;\n newY: number;\n newRotation: number;\n}\n\nexport interface ResizeParams<T = unknown> {\n element: T;\n anchor: ResizeAnchor;\n dx: number;\n dy: number;\n startData: TransformStartData;\n maintainAspectRatio?: boolean;\n}\n\n// ============================================================================\n// Render Types\n// ============================================================================\n\nexport interface RenderContext {\n ctx: CanvasRenderingContext2D;\n isSelected: boolean;\n isHovered?: boolean;\n scale?: number;\n}\n\n// ============================================================================\n// Transform Handle Types\n// ============================================================================\n\nexport interface HandleInfo {\n anchor: ResizeAnchor;\n x: number;\n y: number;\n cursor: string;\n}\n\n// ============================================================================\n// Transform Control Types (for UI)\n// ============================================================================\n\nexport interface TransformControl {\n key: string;\n label: string;\n type?: 'checkbox' | 'slider' | 'select';\n defaultValue?: number | boolean | string;\n defaultInternalValue?: number;\n step?: number;\n options?: Array<{ value: string; label: string }>;\n toSlider?: (internal: number) => number;\n fromSlider?: (slider: number) => number;\n toDisplay?: (internal: number) => string;\n visibleWhen?: (data: Record<string, unknown>) => boolean;\n}\n\nexport interface TransformTypeDefinition {\n id: TransformType;\n label: string;\n Component: new (config?: Partial<BaseTextElementConfig>) => unknown; // Element class constructor\n}\n\n// ============================================================================\n// Utility Types\n// ============================================================================\n\n// Extract element type by transform type\nexport type ExtractElementByType<T extends TransformType> = Extract<AnyElementConfig, { transformType: T }>;\n\n// Extract transform data by type\nexport type ExtractTransformData<T extends TransformType> = Extract<AnyTransformData, { type: T }>;\n\n// ============================================================================\n// Type Guards\n// ============================================================================\n\n// Transform data type guards\nexport function isCustomTransform(data: AnyTransformData): data is CustomTransformData {\n return data.type === 'custom';\n}\n\nexport function isDistortTransform(data: AnyTransformData): data is DistortTransformData {\n return data.type === 'distort';\n}\n\nexport function isCircleTransform(data: AnyTransformData): data is CircleTransformData {\n return data.type === 'circle';\n}\n\nexport function isLeanTransform(data: AnyTransformData): data is LeanTransformData {\n return data.type === 'lean';\n}\n\nexport function isArchTransform(data: AnyTransformData): data is ArchTransformData {\n return data.type === 'arch';\n}\n\nexport function isAscendTransform(data: AnyTransformData): data is AscendTransformData {\n return data.type === 'ascend';\n}\n\nexport function isWaveTransform(data: AnyTransformData): data is WaveTransformData {\n return data.type === 'wave';\n}\n\nexport function isFlagTransform(data: AnyTransformData): data is FlagTransformData {\n return data.type === 'flag';\n}\n\nexport function isImageTransform(data: AnyTransformData): data is ImageTransformData {\n return data.type === 'image';\n}\n\nexport function isGroupTransform(data: AnyTransformData): data is GroupTransformData {\n return data.type === 'group';\n}\n\nexport function isArtboardTransform(data: AnyTransformData): data is ArtboardTransformData {\n return data.type === 'artboard';\n}\n\n// Element config type guards\nexport function isTextElementConfig(config: AnyElementConfig): config is TextOnlyElementConfig {\n return config.transformType !== 'image' && config.transformType !== 'group';\n}\n\nexport function isImageElementConfig(config: AnyElementConfig): config is ImageElementConfig {\n return config.transformType === 'image';\n}\n\nexport function isCustomElementConfig(config: AnyElementConfig): config is CustomElementConfig {\n return config.transformType === 'custom';\n}\n\nexport function isCircleElementConfig(config: AnyElementConfig): config is CircleElementConfig {\n return config.transformType === 'circle';\n}\n\nexport function isLeanElementConfig(config: AnyElementConfig): config is LeanElementConfig {\n return config.transformType === 'lean';\n}\n\nexport function isArchElementConfig(config: AnyElementConfig): config is ArchElementConfig {\n return config.transformType === 'arch';\n}\n\nexport function isAscendElementConfig(config: AnyElementConfig): config is AscendElementConfig {\n return config.transformType === 'ascend';\n}\n\nexport function isWaveElementConfig(config: AnyElementConfig): config is WaveElementConfig {\n return config.transformType === 'wave';\n}\n\nexport function isFlagElementConfig(config: AnyElementConfig): config is FlagElementConfig {\n return config.transformType === 'flag';\n}\n\nexport function isGroupElementConfig(config: AnyElementConfig): config is GroupElementConfig {\n return config.transformType === 'group';\n}\n\nexport function isShapeElementConfig(config: AnyElementConfig): config is ShapeElementConfig {\n return config.transformType === 'shape';\n}\n\nexport function isPathElementConfig(config: AnyElementConfig): config is PathElementConfig {\n return config.transformType === 'path';\n}\n\n// ============================================================================\n// Transform Data Type Guards\n// ============================================================================\n\nexport function isShapeTransform(data: AnyTransformData): data is ShapeTransformData {\n return data.type === 'shape';\n}\n\nexport function isPathTransform(data: AnyTransformData): data is PathTransformData {\n return data.type === 'path';\n}\n\n// ============================================================================\n// New Feature Type Guards\n// ============================================================================\n\n// Stroke type guard\nexport function hasStroke(config: AnyElementConfig): boolean {\n return config.stroke?.enabled === true;\n}\n\n// Masks type guard\nexport function hasMasks(config: AnyElementConfig): boolean {\n return Array.isArray(config.masks) && config.masks.length > 0;\n}\n\n// Knockout type guard (also matches clip mode)\nexport function isKnockout(config: AnyElementConfig): boolean {\n return config.blendMode === 'knockout' || config.blendMode === 'clip';\n}\n\n// Distress effect type guard\nexport function hasDistressEffect(config: AnyElementConfig): boolean {\n return config.distressEffect?.enabled === true;\n}\n\n// ============================================================================\n// Canvas Error Types\n// ============================================================================\n\n/**\n * Categories of errors that can occur within the canvas.\n *\n * - 'render': Errors during canvas rendering (element draw, compositing)\n * - 'export': Errors during artboard export (PNG generation, worker failures)\n * - 'import': Errors loading or deserializing saved state / initial elements\n * - 'image': Errors loading images (network failures, CORS, decode errors)\n * - 'worker': Errors in the export web worker\n * - 'state': Errors in state management (artboard creation, element updates)\n * - 'unknown': Uncategorized errors caught by the ErrorBoundary\n */\nexport type CanvasErrorCategory = 'render' | 'export' | 'import' | 'image' | 'worker' | 'state' | 'unknown';\n\n/**\n * Structured error type for all canvas errors.\n * Provides consistent error reporting with category, context, and recoverability info.\n *\n * @example\n * ```tsx\n * <SnowconeCanvas\n * onError={(error) => {\n * if (error.category === 'image' && error.recoverable) {\n * showToast('Image failed to load, please try another');\n * } else if (!error.recoverable) {\n * reportCrash(error);\n * }\n * }}\n * />\n * ```\n */\nexport interface CanvasError {\n /** The category of this error */\n category: CanvasErrorCategory;\n /** Human-readable error message */\n message: string;\n /** The original Error object, if available */\n originalError?: Error;\n /** The element ID involved, if applicable */\n elementId?: string;\n /** The artboard ID involved, if applicable */\n artboardId?: string;\n /** Whether the canvas can continue operating after this error */\n recoverable: boolean;\n}\n","/**\n * TextElement - Base class for all text-based elements\n * Extends BaseElement with text-specific properties and behavior\n */\n\nimport { BaseElement } from './BaseElement.js';\nimport { MIN_FONT_SIZE, MAX_FONT_SIZE } from '../constants.js';\nimport type {\n TextAlign,\n BoundingBox,\n BaseTextElementConfig,\n ResizeAnchor,\n GlyphOverride,\n OpenTypeFeatures,\n CharacterStyle,\n TransformStartData,\n} from '../types/index.js';\nimport { RichText } from '../types/index.js';\n\nexport class TextElement extends BaseElement {\n // Rich text support\n richText?: RichText;\n // Legacy plain text (kept for backward compatibility)\n text: string;\n // Element-level style defaults (used when richText spans don't specify)\n fontSize: number;\n fontFamily: string;\n color: string;\n textAlign: TextAlign;\n bold: boolean;\n italic: boolean;\n underline: boolean;\n strikethrough: boolean;\n\n // Glyph and OpenType features\n glyphOverrides?: GlyphOverride[];\n openTypeFeatures?: OpenTypeFeatures;\n\n constructor(config: Partial<BaseTextElementConfig> = {}) {\n super(config);\n\n // Set default transform type for text elements\n this.transformType = config.transformType || 'custom';\n\n // Element-level defaults\n this.fontSize = config.fontSize || 72;\n this.fontFamily = config.fontFamily || 'Arial';\n this.color = config.color || '#333333';\n this.bold = config.bold !== undefined ? config.bold : false;\n this.italic = config.italic !== undefined ? config.italic : false;\n this.underline = config.underline !== undefined ? config.underline : false;\n this.strikethrough = config.strikethrough !== undefined ? config.strikethrough : false;\n this.textAlign = config.textAlign || 'center';\n\n // Rich text initialization with backward compatibility\n if (config.richText) {\n // New format: use richText\n if (config.richText instanceof RichText) {\n this.richText = config.richText;\n } else {\n // Deserialize from JSON\n this.richText = RichText.fromJSON(config.richText);\n }\n this.text = this.richText.getText();\n } else if (config.text !== undefined) {\n // Legacy format: migrate to richText\n // Create richText from plain text with EMPTY style so it inherits from element defaults\n this.text = config.text;\n this.richText = RichText.fromPlainText(config.text, {});\n } else {\n // No text provided: default\n this.text = 'Text';\n this.richText = RichText.fromPlainText('Text', {});\n }\n\n // Glyph and OpenType features\n this.glyphOverrides = config.glyphOverrides;\n this.openTypeFeatures = config.openTypeFeatures;\n }\n\n /**\n * Get bounding box in world coordinates\n * Must be implemented by subclasses\n */\n getBoundingBox(): BoundingBox {\n throw new Error('getBoundingBox() must be implemented by subclass');\n }\n\n /**\n * Render the element\n * Must be implemented by subclasses\n */\n render(_ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n throw new Error('render() must be implemented by subclass');\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): BaseTextElementConfig {\n const baseJSON = super.toJSON();\n return {\n ...baseJSON,\n // Serialize rich text\n ...(this.richText && { richText: this.richText.toJSON() }),\n // Keep legacy text for backward compatibility\n text: this.text,\n fontSize: this.fontSize,\n fontFamily: this.fontFamily,\n color: this.color,\n textAlign: this.textAlign,\n bold: this.bold,\n italic: this.italic,\n underline: this.underline,\n strikethrough: this.strikethrough,\n // Typography properties (only include if defined)\n ...(this.openTypeFeatures && { openTypeFeatures: { ...this.openTypeFeatures } }),\n ...(this.glyphOverrides && { glyphOverrides: this.glyphOverrides.map((g) => ({ ...g })) }),\n };\n }\n\n /**\n * Clone this element\n */\n clone(): TextElement {\n const Constructor = this.constructor as new (config: Partial<BaseTextElementConfig>) => TextElement;\n return new Constructor(this.toJSON());\n }\n\n /**\n * Update text content\n * @param newText - Plain text to set (resets to element defaults)\n */\n setText(newText: string): void {\n this.text = newText;\n // Update richText as well - use empty style to inherit from element defaults\n this.richText = RichText.fromPlainText(newText, {});\n }\n\n /**\n * Get the plain text content\n */\n getText(): string {\n return this.richText?.getText() || this.text;\n }\n\n /**\n * Get the rich text object\n */\n getRichText(): RichText {\n if (!this.richText) {\n // Migrate on-demand if needed - use empty style to inherit from element defaults\n this.richText = RichText.fromPlainText(this.text, {});\n }\n return this.richText;\n }\n\n /**\n * Set the rich text object\n */\n setRichText(newRichText: RichText): void {\n this.richText = newRichText;\n this.text = newRichText.getText();\n }\n\n /**\n * Get the element-level default style\n */\n getDefaultStyle(): CharacterStyle {\n return {\n color: this.color,\n fontFamily: this.fontFamily,\n fontSize: this.fontSize,\n bold: this.bold,\n italic: this.italic,\n underline: this.underline,\n strikethrough: this.strikethrough,\n };\n }\n\n /**\n * Apply character-level formatting to a text range\n */\n applyFormatting(start: number, end: number, style: CharacterStyle): void {\n const richText = this.getRichText();\n richText.applyStyle(start, end, style);\n this.text = richText.getText();\n }\n\n /**\n * Update font size\n */\n setFontSize(newFontSize: number): void {\n this.fontSize = Math.max(MIN_FONT_SIZE, Math.min(MAX_FONT_SIZE, newFontSize));\n }\n\n /**\n * Update color\n * Updates both element-level default and all rich text spans that currently match the old color\n */\n setColor(newColor: string): void {\n const oldColor = this.color;\n this.color = newColor;\n\n // Also update all rich text spans that have the old color or inherit from element default\n if (this.richText) {\n for (let i = 0; i < this.richText.spans.length; i++) {\n const span = this.richText.spans[i];\n if (span.style.color === undefined || span.style.color === oldColor) {\n span.style.color = newColor;\n }\n }\n }\n }\n\n /**\n * Handle resize transform\n * Must be implemented by subclasses\n */\n resize(_anchor: ResizeAnchor, _newWidth: number, _newHeight: number, _startData: TransformStartData): void | boolean {\n throw new Error('resize() must be implemented by subclass');\n }\n\n /**\n * Called when transform starts\n * Returns data to be passed to resize()\n */\n override getTransformStartData(): TransformStartData {\n const baseData = super.getTransformStartData();\n return {\n ...baseData,\n fontSize: this.fontSize,\n // Capture rich text state for proportional scaling during resize\n richText: this.richText ? this.richText.clone() : null,\n };\n }\n}\n\nexport default TextElement;\n","/**\n * TextMetrics - Shared utilities for text measurement and layout\n * Reduces duplication across transform types\n */\n\nimport { LINE_HEIGHT_MULTIPLIER } from '../constants.js';\n\n// Reusable canvas for text measurements (works in both main thread and worker)\nlet _measureCanvas: HTMLCanvasElement | OffscreenCanvas | null = null;\n\n/**\n * Get or create a canvas for text measurements.\n * Uses OffscreenCanvas in worker contexts where document is unavailable.\n * @private\n */\nfunction getMeasureCanvas(): HTMLCanvasElement | OffscreenCanvas {\n if (!_measureCanvas) {\n if (typeof document !== 'undefined') {\n _measureCanvas = document.createElement('canvas');\n } else if (typeof OffscreenCanvas !== 'undefined') {\n _measureCanvas = new OffscreenCanvas(1, 1);\n } else {\n throw new Error('No canvas available for text measurement');\n }\n }\n return _measureCanvas;\n}\n\n/**\n * Build font string with optional bold and italic\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {boolean} bold - Whether to use bold weight\n * @param {boolean} italic - Whether to use italic style\n * @returns {string} Font string for ctx.font\n */\nexport function buildFontString(\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false\n): string {\n // Build font string parts, only including non-default values\n const parts: string[] = [];\n\n // Only add italic if true (omit 'normal' as it's the default)\n if (italic) {\n parts.push('italic');\n }\n\n // Only add bold if true (omit 'normal' as it's the default)\n if (bold) {\n parts.push('bold');\n }\n\n // Always include font size and family\n parts.push(`${fontSize}px`);\n parts.push(fontFamily);\n\n return parts.join(' ');\n}\n\n/**\n * Word wrap text to fit within a given width\n * @param {string} text - Text to wrap\n * @param {number} maxWidth - Maximum width in pixels\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {boolean} bold - Whether text is bold\n * @param {boolean} italic - Whether text is italic\n * @param {number} lockedLineCount - Optional locked line count (forces exact number of lines during corner resize)\n * @param {boolean} strict - If true, disable tolerance (wrap exactly at maxWidth, matching richText behavior)\n * @returns {string[]} Array of lines\n */\nexport function wrapText(\n text: string,\n maxWidth: number,\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false,\n lockedLineCount?: number,\n strict: boolean = false\n): string[] {\n const canvas = getMeasureCanvas();\n const ctx = canvas.getContext('2d')!;\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n\n // If text is all whitespace, return as-is (no wrapping needed)\n if (text.trim().length === 0) {\n return [text];\n }\n\n // Handle explicit newlines: split by \\n first, wrap each line separately\n if (text.includes('\\n')) {\n const explicitLines = text.split('\\n');\n const allWrappedLines: string[] = [];\n\n for (let i = 0; i < explicitLines.length; i++) {\n const line = explicitLines[i];\n const wrappedLine = wrapText(line, maxWidth, fontSize, fontFamily, bold, italic, undefined, strict);\n allWrappedLines.push(...wrappedLine);\n }\n\n return allWrappedLines;\n }\n\n // Preserve leading and trailing spaces\n const leadingSpacesMatch = text.match(/^\\s*/);\n const trailingSpacesMatch = text.match(/\\s*$/);\n const leadingSpaces = leadingSpacesMatch ? leadingSpacesMatch[0] : '';\n const trailingSpaces = trailingSpacesMatch ? trailingSpacesMatch[0] : '';\n const trimmedText = text.substring(leadingSpaces.length, text.length - trailingSpaces.length);\n\n const words = trimmedText.split(' ');\n\n // If line count is locked (during corner resize), force that exact number of lines\n if (lockedLineCount !== undefined && lockedLineCount > 0) {\n if (lockedLineCount === 1) {\n return [leadingSpaces + words.join(' ') + trailingSpaces];\n }\n\n // Distribute words as evenly as possible across locked line count\n const lines: string[] = [];\n const wordsPerLine = Math.ceil(words.length / lockedLineCount);\n\n for (let i = 0; i < words.length; i += wordsPerLine) {\n const lineWords = words.slice(i, i + wordsPerLine);\n lines.push(lineWords.join(' '));\n }\n\n // Add leading spaces to first line, trailing spaces to last line\n if (lines.length > 0) {\n lines[0] = leadingSpaces + lines[0];\n lines[lines.length - 1] = lines[lines.length - 1] + trailingSpaces;\n }\n\n return lines;\n }\n\n // Normal wrapping logic (no lock)\n // First pass: check if all text fits on one line with generous tolerance\n const allText = words.join(' ');\n // CRITICAL FIX: Measure WITH leading/trailing spaces to avoid \"fits but doesn't fit\" bug\n // where we measure \"Foo\" but return \" Foo\" which is wider\n const allTextWithSpaces = leadingSpaces + allText + trailingSpaces;\n const allTextWidth = ctx.measureText(allTextWithSpaces).width;\n\n // Use a percentage-based tolerance that scales with maxWidth\n // This provides consistent behavior regardless of text size during resize\n // At smaller sizes, use higher percentages to provide more stability\n // In strict mode, use zero tolerance to match richText wrapping behavior\n const isSmallSize = maxWidth < 200;\n const wrapTolerancePercent = strict ? 0 : (isSmallSize ? 0.08 : 0.05);\n const wrapTolerance = strict ? 0 : Math.max(20, maxWidth * wrapTolerancePercent);\n\n // If text fits on one line (with tolerance), keep it that way\n if (allTextWidth <= maxWidth + wrapTolerance) {\n return [allTextWithSpaces];\n }\n\n // Otherwise, perform word wrapping with smaller percentage-based tolerance\n const lines: string[] = [];\n let currentLine = '';\n const lineTolerancePercent = strict ? 0 : (isSmallSize ? 0.05 : 0.03);\n const lineTolerance = strict ? 0 : Math.max(15, maxWidth * lineTolerancePercent);\n\n for (let i = 0; i < words.length; i++) {\n const word = words[i];\n const wordWidth = ctx.measureText(word).width;\n\n // Check if word itself is too wide (needs character-level breaking)\n if (wordWidth > maxWidth + lineTolerance) {\n // Finish current line if it has content\n if (currentLine.length > 0) {\n lines.push(currentLine);\n currentLine = '';\n }\n\n // Break word at character level\n const chars = Array.from(word);\n let charBuffer = '';\n\n for (const char of chars) {\n const testBuffer = charBuffer + char;\n const bufferWidth = ctx.measureText(testBuffer).width;\n\n if (bufferWidth > maxWidth + lineTolerance && charBuffer.length > 0) {\n // Finish current line with buffer\n lines.push(charBuffer);\n charBuffer = char;\n } else {\n charBuffer = testBuffer;\n }\n }\n\n // Set remaining buffer as current line\n currentLine = charBuffer;\n } else {\n // Word fits on a line - check if we need to wrap\n const testLine = currentLine.length > 0 ? currentLine + ' ' + word : word;\n const metrics = ctx.measureText(testLine);\n\n if (metrics.width > maxWidth + lineTolerance && currentLine.length > 0) {\n lines.push(currentLine);\n currentLine = word;\n } else {\n currentLine = testLine;\n }\n }\n }\n\n if (currentLine.length > 0) {\n lines.push(currentLine);\n }\n\n // Add leading spaces to first line, trailing spaces to last line\n // CRITICAL FIX: Check if adding spaces makes lines too wide\n if (lines.length > 0) {\n // Check if adding leading space makes first line too wide\n if (leadingSpaces.length > 0) {\n const firstLineWithSpace = leadingSpaces + lines[0];\n const firstLineWidth = ctx.measureText(firstLineWithSpace).width;\n\n if (firstLineWidth > maxWidth + lineTolerance) {\n // Leading space doesn't fit on same line as content - put it on its own line\n // This matches rich text behavior where \" Foo\" wraps to [\" \", \"Foo\"]\n lines.unshift(leadingSpaces);\n } else {\n // Fits - add to first line\n lines[0] = firstLineWithSpace;\n }\n }\n\n // Check if adding trailing space makes last line too wide\n if (trailingSpaces.length > 0) {\n const lastIdx = lines.length - 1;\n const lastLineWithSpace = lines[lastIdx] + trailingSpaces;\n const lastLineWidth = ctx.measureText(lastLineWithSpace).width;\n\n if (lastLineWidth > maxWidth + lineTolerance) {\n // Trailing space doesn't fit - put it on its own line\n lines.push(trailingSpaces);\n } else {\n // Fits - add to last line\n lines[lastIdx] = lastLineWithSpace;\n }\n }\n }\n\n return lines;\n}\n\n/**\n * Measure width of text\n * @param {string} text - Text to measure\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {boolean} bold - Whether text is bold\n * @param {boolean} italic - Whether text is italic\n * @returns {number} Width in pixels\n */\nexport function measureTextWidth(\n text: string,\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false\n): number {\n const canvas = getMeasureCanvas();\n const ctx = canvas.getContext('2d')!;\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n return ctx.measureText(text).width;\n}\n\n/**\n * Get font metrics for accurate text positioning\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {boolean} bold - Whether text is bold\n * @param {boolean} italic - Whether text is italic\n * @returns {Object} {ascent, descent, height}\n */\nexport function getFontMetrics(\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false\n): { ascent: number; descent: number; height: number } {\n const canvas = getMeasureCanvas();\n const ctx = canvas.getContext('2d')!;\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n\n // Measure sample text with tall ascenders and deep descenders\n const sampleText = 'ÁÉÍgjpqy';\n const metrics = ctx.measureText(sampleText);\n\n const ascent = metrics.actualBoundingBoxAscent || fontSize * 0.8;\n const descent = metrics.actualBoundingBoxDescent || fontSize * 0.2;\n const height = ascent + descent;\n\n return { ascent, descent, height };\n}\n\n/**\n * Calculate visual bounds for multi-line text\n * @param {string[]} lines - Array of text lines\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {number} lineHeight - Line height multiplier (e.g., 1.2)\n * @returns {Object} {width, height}\n */\nexport function calculateTextBounds(\n lines: string[],\n fontSize: number,\n fontFamily: string,\n lineHeight: number = 1.2\n): { width: number; height: number } {\n const fontMetrics = getFontMetrics(fontSize, fontFamily);\n\n // Find max width of all lines\n let maxWidth = 0;\n for (const line of lines) {\n const width = measureTextWidth(line, fontSize, fontFamily);\n maxWidth = Math.max(maxWidth, width);\n }\n\n // Calculate height\n let height;\n if (lines.length === 1) {\n height = fontMetrics.height;\n } else {\n // All lines except last use full lineHeight, last line uses measured height\n const lineSpacing = fontSize * lineHeight;\n height = (lines.length - 1) * lineSpacing + fontMetrics.height;\n }\n\n return { width: maxWidth, height };\n}\n\n/**\n * Render multi-line text with alignment and text decorations\n * @param {CanvasRenderingContext2D} ctx - Canvas context\n * @param {string[]} lines - Array of text lines\n * @param {number} x - X position (meaning depends on alignment)\n * @param {number} y - Y position (top of first line baseline)\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {string} alignment - 'left', 'center', or 'right'\n * @param {number} containerWidth - Width of text container (for alignment)\n * @param {number} horizontalPadding - Padding on sides\n * @param {number} lineHeight - Line height multiplier\n * @param {boolean} bold - Whether text is bold\n * @param {boolean} italic - Whether text is italic\n * @param {boolean} underline - Whether text is underlined\n * @param {boolean} strikethrough - Whether text has strikethrough\n */\nexport function renderMultilineText(\n ctx: CanvasRenderingContext2D,\n lines: string[],\n x: number,\n y: number,\n fontSize: number,\n fontFamily: string,\n alignment: CanvasTextAlign = 'left',\n containerWidth: number = 0,\n horizontalPadding: number = 0,\n lineHeight: number = 1.2,\n bold: boolean = false,\n italic: boolean = false,\n underline: boolean = false,\n strikethrough: boolean = false\n): void {\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n ctx.textBaseline = 'alphabetic';\n ctx.textAlign = alignment;\n\n const fontMetrics = getFontMetrics(fontSize, fontFamily, bold, italic);\n const lineSpacing = fontSize * lineHeight;\n\n lines.forEach((line: string, index: number) => {\n let xPos = x + horizontalPadding;\n\n if (alignment === 'center') {\n xPos = x + containerWidth / 2;\n } else if (alignment === 'right') {\n xPos = x + containerWidth - horizontalPadding;\n }\n\n const yPos = y + fontMetrics.ascent + index * lineSpacing;\n ctx.fillText(line, xPos, yPos);\n\n // Draw underline if enabled\n if (underline) {\n const lineWidth = measureTextWidth(line, fontSize, fontFamily, bold, italic);\n let underlineX = xPos;\n\n if (alignment === 'center') {\n underlineX = xPos - lineWidth / 2;\n } else if (alignment === 'right') {\n underlineX = xPos - lineWidth;\n }\n\n const underlineY = yPos + fontSize * 0.1; // Slightly below baseline\n ctx.beginPath();\n ctx.moveTo(underlineX, underlineY);\n ctx.lineTo(underlineX + lineWidth, underlineY);\n ctx.lineWidth = Math.max(1, fontSize * 0.05);\n ctx.strokeStyle = ctx.fillStyle as string;\n ctx.stroke();\n }\n\n // Draw strikethrough if enabled\n if (strikethrough) {\n const lineWidth = measureTextWidth(line, fontSize, fontFamily, bold, italic);\n let strikeX = xPos;\n\n if (alignment === 'center') {\n strikeX = xPos - lineWidth / 2;\n } else if (alignment === 'right') {\n strikeX = xPos - lineWidth;\n }\n\n const strikeY = yPos - fontMetrics.ascent * 0.4; // Middle of text\n ctx.beginPath();\n ctx.moveTo(strikeX, strikeY);\n ctx.lineTo(strikeX + lineWidth, strikeY);\n ctx.lineWidth = Math.max(1, fontSize * 0.05);\n ctx.strokeStyle = ctx.fillStyle as string;\n ctx.stroke();\n }\n });\n}\n\n/**\n * Calculate container height for text with given line count\n * @param {number} lineCount - Number of lines\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {number} lineHeight - Line height multiplier\n * @returns {number} Container height in pixels\n */\nexport function calculateContainerHeight(\n lineCount: number,\n fontSize: number,\n fontFamily: string,\n lineHeight: number = 1.2\n): number {\n if (lineCount === 1) {\n return fontSize * lineHeight;\n }\n\n const fontMetrics = getFontMetrics(fontSize, fontFamily);\n const lineSpacing = fontSize * lineHeight;\n return (lineCount - 1) * lineSpacing + fontMetrics.height;\n}\n\n/**\n * Calculate visual bounds for wrapped text with space collapsing applied\n * This matches the actual rendering behavior where trailing spaces on wrapped lines are hidden\n *\n * @param {string} text - Text to measure\n * @param {number} maxWidth - Maximum width for wrapping (or Infinity for no wrapping)\n * @param {number} fontSize - Font size\n * @param {string} fontFamily - Font family name\n * @param {boolean} bold - Whether text is bold\n * @param {boolean} italic - Whether text is italic\n * @param {number} lockedLineCount - Optional locked line count (forces exact number of lines during corner resize)\n * @param {boolean} strict - If true, disable tolerance (wrap exactly at maxWidth)\n * @returns {Object} {width, height, lines} - Adjusted dimensions and line array\n */\nexport function calculateVisualBoundsWithSpaceCollapsing(\n text: string,\n maxWidth: number,\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false,\n lockedLineCount?: number,\n strict: boolean = false\n): { width: number; height: number; lines: string[] } {\n // Get wrapped lines (which preserve trailing spaces)\n const lines = wrapText(text, maxWidth, fontSize, fontFamily, bold, italic, lockedLineCount, strict);\n\n // Determine paragraph boundaries (lines separated by explicit \\n)\n // In wrapText, all lines from word-wrapping are from the same paragraph\n // Only explicit \\n characters create paragraph boundaries\n const hasExplicitNewlines = text.includes('\\n');\n\n // If no explicit newlines, all wrapped lines are from one paragraph\n // First line is paragraph start, last line is paragraph end\n // Middle lines are neither (so their trailing spaces get hidden)\n const paragraphMetadata = lines.map((_, index) => ({\n isParagraphStart: !hasExplicitNewlines && index === 0,\n isParagraphEnd: !hasExplicitNewlines && index === lines.length - 1,\n }));\n\n // Calculate metrics for each line with space collapsing applied\n const canvas = getMeasureCanvas();\n const ctx = canvas.getContext('2d')!;\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n\n let maxLineWidth = 0;\n const fontMetrics = getFontMetrics(fontSize, fontFamily, bold, italic);\n\n // Apply space collapsing and collect visible lines\n // CRITICAL: We track total line count (including empty lines after collapsing)\n // to match cursor positions and selection bounds\n const visibleLines: string[] = [];\n let totalLineCount = 0;\n\n lines.forEach((line, index) => {\n const isParagraphStart = paragraphMetadata[index].isParagraphStart;\n const isParagraphEnd = paragraphMetadata[index].isParagraphEnd;\n\n // Apply space layout rules (matching renderRichTextFillOnly logic)\n let visibleText = line;\n\n // Hide leading spaces when NOT at paragraph start\n if (!isParagraphStart) {\n const leadingSpacesMatch = line.match(/^ +/);\n const leadingSpacesCount = leadingSpacesMatch ? leadingSpacesMatch[0].length : 0;\n if (leadingSpacesCount > 0) {\n visibleText = visibleText.substring(leadingSpacesCount);\n }\n }\n\n // Hide trailing spaces when NOT at paragraph end\n if (!isParagraphEnd) {\n const trailingSpacesMatch = visibleText.match(/ +$/);\n const trailingSpacesCount = trailingSpacesMatch ? trailingSpacesMatch[0].length : 0;\n if (trailingSpacesCount > 0) {\n visibleText = visibleText.substring(0, visibleText.length - trailingSpacesCount);\n }\n }\n\n // Count this line regardless of whether it's empty\n totalLineCount++;\n\n // Only measure width for non-empty lines\n if (visibleText.length > 0) {\n visibleLines.push(visibleText);\n\n // Measure the visible text width\n const lineWidth = ctx.measureText(visibleText).width;\n maxLineWidth = Math.max(maxLineWidth, lineWidth);\n }\n });\n\n // Calculate height based on TOTAL line count (including empty lines)\n // This ensures selection bounds and cursor positions align correctly\n // Example: \" Foo\" wrapping to [\" \", \"Foo\"] has 2 lines even though first is empty\n let height;\n // Use shared constant for line height to ensure consistency with renderers\n const lineHeight = LINE_HEIGHT_MULTIPLIER; \n if (totalLineCount === 1) {\n height = fontMetrics.height;\n } else if (totalLineCount === 0) {\n // No lines at all - return zero height\n height = 0;\n } else {\n const lineSpacing = fontSize * lineHeight;\n height = (totalLineCount - 1) * lineSpacing + fontMetrics.height;\n }\n\n return {\n width: maxLineWidth,\n height: height,\n lines: lines,\n };\n}\n\nexport default {\n wrapText,\n measureTextWidth,\n getFontMetrics,\n calculateTextBounds,\n renderMultilineText,\n calculateContainerHeight,\n calculateVisualBoundsWithSpaceCollapsing,\n};\n","/**\n * Transform Renderer - Pure rendering functions for all text transform types\n * Works with both CanvasRenderingContext2D and OffscreenCanvasRenderingContext2D\n */\n\nimport { buildFontString } from './canvas-renderer.js';\nimport { WAVE_SKEW_FACTOR } from '../constants.js';\nimport type {\n AnyTransformData,\n CircleTransformData,\n WaveTransformData,\n ArchTransformData,\n AscendTransformData,\n LeanTransformData,\n FlagTransformData,\n} from '../types/index.js';\n\ntype RenderContext = CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;\n\nexport interface SerializedTextTransformElement {\n id: string;\n type: string;\n text: string;\n x: number;\n y: number;\n rotation: number;\n fontSize: number;\n fontFamily: string;\n color: string;\n bold: boolean;\n italic: boolean;\n transformData: AnyTransformData;\n stroke?: {\n enabled: boolean;\n color: string;\n width: number;\n opacity?: number;\n lineCap?: CanvasLineCap;\n lineJoin?: CanvasLineJoin;\n miterLimit?: number;\n feather?: number;\n };\n}\n\n/**\n * Create an offscreen canvas compatible with both main thread and Web Worker.\n * Uses OffscreenCanvas when available, falls back to document.createElement.\n */\nfunction createOffscreenCanvas(\n width: number,\n height: number\n): HTMLCanvasElement | OffscreenCanvas {\n if (typeof OffscreenCanvas !== 'undefined') {\n return new OffscreenCanvas(width, height);\n }\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n return canvas;\n}\n\n/**\n * Apply stroke style to context and call strokeText before fillText.\n * This creates an outer-stroke effect: fill drawn on top covers the inner half.\n *\n * When called via the offscreen approach (stroke opacity < 1), opacity has\n * already been stripped so this renders at full alpha per character.\n */\nfunction applyStrokeToChar(ctx: RenderContext, char: string, stroke: SerializedTextTransformElement['stroke']): void {\n if (!stroke?.enabled) return;\n ctx.strokeStyle = stroke.color;\n ctx.lineWidth = stroke.width;\n ctx.lineCap = stroke.lineCap || 'round';\n ctx.lineJoin = stroke.lineJoin || 'round';\n if (stroke.miterLimit !== undefined) ctx.miterLimit = stroke.miterLimit;\n if (stroke.opacity !== undefined) {\n const prevAlpha = ctx.globalAlpha;\n ctx.globalAlpha = stroke.opacity;\n ctx.strokeText(char, 0, 0);\n ctx.globalAlpha = prevAlpha;\n } else {\n ctx.strokeText(char, 0, 0);\n }\n}\n\n/**\n * Wrap a transform render function with offscreen compositing when stroke\n * opacity < 1. Renders all strokes at full opacity on a temp canvas, then\n * composites the temp canvas onto the main canvas at the desired opacity.\n * This prevents overlap darkening where adjacent characters' strokes overlap.\n *\n * The renderFn is called twice when offscreen is needed:\n * 1. On the offscreen canvas with stroke.opacity = 1 (stroke-only pass)\n * 2. On the main canvas with stroke disabled (fill-only pass)\n * When offscreen is NOT needed, renderFn is called once normally.\n */\nfunction renderTransformWithOffscreenStroke(\n ctx: RenderContext,\n elementData: SerializedTextTransformElement,\n renderFn: (ctx: RenderContext, data: SerializedTextTransformElement) => void\n): void {\n const stroke = elementData.stroke;\n const strokeOpacity = stroke?.opacity ?? 1;\n const elementAlpha = ctx.globalAlpha;\n const hasStrokeOpacity = stroke?.enabled && strokeOpacity < 1;\n const hasElementOpacity = stroke?.enabled && elementAlpha < 1;\n const needsOffscreen = hasStrokeOpacity || hasElementOpacity;\n\n if (!needsOffscreen) {\n // No offscreen needed — render normally\n renderFn(ctx, elementData);\n return;\n }\n\n // Estimate bounds for the offscreen canvas — be generous to avoid clipping.\n // Characters can extend well beyond the transform width/radius, especially\n // for arch/circle transforms where text measured width exceeds the container.\n const fontSize = elementData.fontSize || 24;\n const strokeWidth = stroke!.width || 0;\n const td = elementData.transformData as { width?: number; radius?: number; archHeight?: number };\n const textEstimate = fontSize * elementData.text.length * 0.7;\n const containerSpan = td.width ?? (td.radius ? (td.radius * 2) : 0);\n // Use the larger of container span and text estimate — text can overflow container\n const estimatedSpan = Math.max(containerSpan, textEstimate);\n const featherPad = (stroke!.feather || 0) * 2;\n const padding = strokeWidth * 4 + featherPad + fontSize + 40;\n const estWidth = estimatedSpan + padding;\n // Arch/circle transforms need extra height for curvature\n const archExtra = td.archHeight ? Math.abs(td.archHeight) * fontSize * 2 : 0;\n const radiusExtra = td.radius ? td.radius : 0;\n const estHeight = fontSize * 3 + padding + archExtra + radiusExtra;\n\n // Account for current canvas scale\n const transform = ctx.getTransform();\n const scale = Math.max(Math.abs(transform.a), Math.abs(transform.d), 1);\n\n // Use even dimensions to avoid fractional half-offsets\n const offW = Math.ceil(estWidth * scale / 2) * 2;\n const offH = Math.ceil(estHeight * scale / 2) * 2;\n\n if (offW <= 0 || offH <= 0) {\n renderFn(ctx, elementData);\n return;\n }\n\n const offCanvas = createOffscreenCanvas(offW, offH);\n const offCtx = offCanvas.getContext('2d') as RenderContext | null;\n\n if (!offCtx) {\n renderFn(ctx, elementData);\n return;\n }\n\n // Replicate the main canvas transform, shifted so the element center\n // maps to the center of the offscreen canvas.\n const elemX = elementData.x || 0;\n const elemY = elementData.y || 0;\n const centerPixelX = transform.a * elemX + transform.c * elemY + transform.e;\n const centerPixelY = transform.b * elemX + transform.d * elemY + transform.f;\n\n // Use rounded draw-back coordinates for pixel-perfect compositing\n const drawBackX = Math.round(centerPixelX - offW / 2);\n const drawBackY = Math.round(centerPixelY - offH / 2);\n\n offCtx.setTransform(\n transform.a, transform.b,\n transform.c, transform.d,\n offW / 2 - centerPixelX + transform.e,\n offH / 2 - centerPixelY + transform.f\n );\n\n // When element opacity is involved, render the entire element (stroke + fill)\n // at full opacity on the offscreen canvas, then composite everything at once.\n // This prevents overlap darkening where adjacent characters' strokes overlap.\n if (hasElementOpacity) {\n // Render at full alpha on offscreen canvas\n offCtx.globalAlpha = 1.0;\n const savedStrokeOpa = stroke!.opacity;\n if (hasStrokeOpacity) stroke!.opacity = 1.0;\n renderFn(offCtx, elementData);\n if (hasStrokeOpacity) stroke!.opacity = savedStrokeOpa;\n\n // Composite entire offscreen result at element opacity\n ctx.save();\n ctx.resetTransform();\n ctx.globalAlpha = elementAlpha;\n ctx.drawImage(offCanvas as CanvasImageSource, drawBackX, drawBackY);\n ctx.restore();\n return;\n }\n\n // Stroke opacity only (no element opacity) — original behavior:\n // Render stroke at full opacity on offscreen, fill-only on main canvas.\n const savedOpacity = stroke!.opacity;\n stroke!.opacity = 1.0;\n renderFn(offCtx, elementData);\n stroke!.opacity = savedOpacity;\n\n // Now render fill-only on the main canvas (disable stroke temporarily).\n const savedEnabled = stroke!.enabled;\n stroke!.enabled = false;\n renderFn(ctx, elementData);\n stroke!.enabled = savedEnabled;\n\n // Composite the offscreen stroke layer onto the main canvas at desired opacity.\n ctx.save();\n ctx.resetTransform();\n ctx.globalAlpha = strokeOpacity;\n ctx.drawImage(\n offCanvas as CanvasImageSource,\n drawBackX,\n drawBackY\n );\n ctx.restore();\n}\n\n// ============================================================================\n// Circle Transform\n// ============================================================================\n\nexport function renderCircleTransform(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n renderTransformWithOffscreenStroke(ctx, elementData, renderCircleTransformDirect);\n}\n\nfunction renderCircleTransformDirect(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n const transformData = elementData.transformData as CircleTransformData;\n\n ctx.save();\n\n // Move to center\n ctx.translate(elementData.x, elementData.y);\n // Use negative angle for clockwise rotation\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n\n // Now apply scale for rendering\n ctx.scale(transformData.scale, transformData.scale);\n\n // Set up rendering at base font size in scaled coordinate space\n const weight = elementData.bold ? 'bold' : 'normal';\n const style = elementData.italic ? 'italic' : 'normal';\n ctx.font = `${style} ${weight} ${elementData.fontSize}px ${elementData.fontFamily}`;\n ctx.fillStyle = elementData.color;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n\n // Measure text widths in the current (scaled) coordinate space\n // This gives us measurements that are already in scaled units\n const chars = elementData.text.split('');\n const charWidths = chars.map((char) => ctx.measureText(char).width);\n const totalWidth = charWidths.reduce((sum, w) => sum + w, 0);\n\n if (transformData.reverse) {\n // Bottom text: start from right side and go left\n let currentAngle = Math.PI / 2 + totalWidth / (2 * transformData.radius);\n\n chars.forEach((char, i) => {\n const charWidth = charWidths[i];\n const angleStep = charWidth / transformData.radius;\n\n const x = Math.cos(currentAngle - angleStep / 2) * transformData.radius;\n const y = Math.sin(currentAngle - angleStep / 2) * transformData.radius;\n\n ctx.save();\n ctx.translate(x, y);\n ctx.rotate(currentAngle - angleStep / 2 - Math.PI / 2);\n applyStrokeToChar(ctx, char, elementData.stroke);\n ctx.fillText(char, 0, 0);\n ctx.restore();\n\n currentAngle -= angleStep;\n });\n } else {\n // Top text: start from left side and go right\n let currentAngle = -Math.PI / 2 - totalWidth / (2 * transformData.radius);\n\n chars.forEach((char, i) => {\n const charWidth = charWidths[i];\n const angleStep = charWidth / transformData.radius;\n\n const x = Math.cos(currentAngle + angleStep / 2) * transformData.radius;\n const y = Math.sin(currentAngle + angleStep / 2) * transformData.radius;\n\n ctx.save();\n ctx.translate(x, y);\n ctx.rotate(currentAngle + angleStep / 2 + Math.PI / 2);\n applyStrokeToChar(ctx, char, elementData.stroke);\n ctx.fillText(char, 0, 0);\n ctx.restore();\n\n currentAngle += angleStep;\n });\n }\n\n ctx.restore();\n}\n\n// ============================================================================\n// Wave Transform\n// ============================================================================\n\nexport function renderWaveTransform(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n renderTransformWithOffscreenStroke(ctx, elementData, renderWaveTransformDirect);\n}\n\nfunction renderWaveTransformDirect(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n const transformData = elementData.transformData as WaveTransformData;\n\n ctx.save();\n\n // Draw text along wave path\n ctx.translate(elementData.x, elementData.y);\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n\n // Set font to element's fontSize (canvas transform will handle zoom)\n ctx.font = buildFontString(elementData.fontSize, elementData.fontFamily, elementData.bold, elementData.italic);\n ctx.fillStyle = elementData.color;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n\n const chars = elementData.text.split('');\n const totalWidth = chars.reduce((sum, char) => sum + ctx.measureText(char).width, 0);\n\n // Draw each character along the wave with proper perspective/skew\n let currentX = -totalWidth / 2;\n chars.forEach((char) => {\n const charWidth = ctx.measureText(char).width;\n const x = currentX + charWidth / 2;\n const normalizedX = x / (transformData.width / 2);\n\n // Calculate y position on sine wave\n const y = transformData.amplitude * elementData.fontSize * Math.sin(transformData.frequency * Math.PI * normalizedX);\n\n // Calculate the slope of the sine wave at this point for skew\n const slope =\n transformData.amplitude *\n transformData.frequency *\n Math.PI *\n Math.cos(transformData.frequency * Math.PI * normalizedX);\n\n ctx.save();\n ctx.translate(x, y);\n\n // Apply vertical skew based on the slope\n ctx.transform(1, slope * WAVE_SKEW_FACTOR, 0, 1, 0, 0);\n\n applyStrokeToChar(ctx, char, elementData.stroke);\n ctx.fillText(char, 0, 0);\n ctx.restore();\n\n currentX += charWidth;\n });\n\n ctx.restore();\n}\n\n// ============================================================================\n// Arch Transform\n// ============================================================================\n\nexport function renderArchTransform(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n renderTransformWithOffscreenStroke(ctx, elementData, renderArchTransformDirect);\n}\n\nfunction renderArchTransformDirect(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n const transformData = elementData.transformData as ArchTransformData;\n\n ctx.save();\n\n // Draw text along arch path\n ctx.translate(elementData.x, elementData.y);\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n\n // Set font to element's fontSize (canvas transform will handle zoom)\n ctx.font = buildFontString(elementData.fontSize, elementData.fontFamily, elementData.bold, elementData.italic);\n ctx.fillStyle = elementData.color;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n\n const chars = elementData.text.split('');\n const totalWidth = chars.reduce((sum, char) => sum + ctx.measureText(char).width, 0);\n const radius = transformData.width / 2;\n\n // Draw each character along the arch with proper perspective/skew\n let currentX = -totalWidth / 2;\n chars.forEach((char) => {\n const charWidth = ctx.measureText(char).width;\n const x = currentX + charWidth / 2;\n const normalizedX = x / radius;\n\n // Calculate y position on parabola\n const y = (Math.pow(normalizedX, 2) - 1) * transformData.archHeight * elementData.fontSize;\n\n // Calculate the slope of the curve at this point for skew\n const slope = 2 * normalizedX * transformData.archHeight;\n\n // Apply skew transformation to create perspective effect\n ctx.save();\n ctx.translate(x, y);\n\n // Apply vertical skew based on the slope\n const skewFactor = 0.3;\n ctx.transform(1, slope * skewFactor, 0, 1, 0, 0);\n\n applyStrokeToChar(ctx, char, elementData.stroke);\n ctx.fillText(char, 0, 0);\n ctx.restore();\n\n currentX += charWidth;\n });\n\n ctx.restore();\n}\n\n// ============================================================================\n// Ascend Transform\n// ============================================================================\n\nexport function renderAscendTransform(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n renderTransformWithOffscreenStroke(ctx, elementData, renderAscendTransformDirect);\n}\n\nfunction renderAscendTransformDirect(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n const transformData = elementData.transformData as AscendTransformData;\n\n ctx.save();\n\n // Draw text along diagonal path\n ctx.translate(elementData.x, elementData.y);\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n\n // Set font to element's fontSize (canvas transform will handle zoom)\n ctx.font = buildFontString(elementData.fontSize, elementData.fontFamily, elementData.bold, elementData.italic);\n ctx.fillStyle = elementData.color;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n\n const chars = elementData.text.split('');\n const totalWidth = chars.reduce((sum, char) => sum + ctx.measureText(char).width, 0);\n const angleRad = (transformData.ascendAngle * Math.PI) / 180;\n\n // Draw each character along the diagonal line with skew perspective\n let currentX = -totalWidth / 2;\n chars.forEach((char) => {\n const charWidth = ctx.measureText(char).width;\n const x = currentX + charWidth / 2;\n\n // Calculate y position on straight diagonal line\n const y = x * Math.tan(angleRad);\n\n // Calculate the slope of the diagonal line for skew\n const slope = Math.tan(angleRad);\n\n // Apply skew transformation to create perspective effect\n ctx.save();\n ctx.translate(x, y);\n\n // Apply vertical skew based on the slope\n const skewFactor = 1.0;\n ctx.transform(1, slope * skewFactor, 0, 1, 0, 0);\n\n applyStrokeToChar(ctx, char, elementData.stroke);\n ctx.fillText(char, 0, 0);\n ctx.restore();\n\n currentX += charWidth;\n });\n\n ctx.restore();\n}\n\n// ============================================================================\n// Lean Transform\n// ============================================================================\n\nexport function renderLeanTransform(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n renderTransformWithOffscreenStroke(ctx, elementData, renderLeanTransformDirect);\n}\n\nfunction renderLeanTransformDirect(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n const transformData = elementData.transformData as LeanTransformData;\n\n ctx.save();\n\n // Draw text with skew/slant\n ctx.translate(elementData.x, elementData.y);\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n\n // Apply skew transform based on leanAmount\n const skewAngle = (-transformData.leanAmount * Math.PI) / 4;\n ctx.transform(1, 0, Math.tan(skewAngle), 1, 0, 0);\n\n ctx.font = buildFontString(elementData.fontSize, elementData.fontFamily, elementData.bold, elementData.italic);\n ctx.fillStyle = elementData.color;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n\n // Draw text normally - skew transform handles the slant\n // Stroke before fill for outer-stroke effect\n if (elementData.stroke?.enabled) {\n ctx.save();\n ctx.strokeStyle = elementData.stroke.color;\n ctx.lineWidth = elementData.stroke.width;\n ctx.lineCap = elementData.stroke.lineCap || 'round';\n ctx.lineJoin = elementData.stroke.lineJoin || 'round';\n if (elementData.stroke.opacity !== undefined) ctx.globalAlpha = elementData.stroke.opacity;\n ctx.strokeText(elementData.text, 0, 0);\n ctx.restore();\n }\n ctx.fillText(elementData.text, 0, 0);\n\n ctx.restore();\n}\n\n// ============================================================================\n// Flag Transform\n// ============================================================================\n\nexport function renderFlagTransform(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n renderTransformWithOffscreenStroke(ctx, elementData, renderFlagTransformDirect);\n}\n\nfunction renderFlagTransformDirect(ctx: RenderContext, elementData: SerializedTextTransformElement): void {\n const transformData = elementData.transformData as FlagTransformData;\n\n ctx.save();\n\n // Draw text along flag wave path\n ctx.translate(elementData.x, elementData.y);\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n\n // Set font to element's fontSize (canvas transform will handle zoom)\n ctx.font = buildFontString(elementData.fontSize, elementData.fontFamily, elementData.bold, elementData.italic);\n ctx.fillStyle = elementData.color;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n\n const chars = elementData.text.split('');\n const totalWidth = chars.reduce((sum, char) => sum + ctx.measureText(char).width, 0);\n\n // Draw each character along the flag wave with proper perspective/skew\n let currentX = -totalWidth / 2;\n chars.forEach((char) => {\n const charWidth = ctx.measureText(char).width;\n const x = currentX + charWidth / 2;\n const normalizedX = x / (transformData.width / 2);\n\n // Flag effect: amplitude increases from center to edges\n const distanceFromCenter = Math.abs(normalizedX);\n const flagAmplitude = transformData.amplitude * distanceFromCenter;\n\n // Calculate y position on flag wave\n const y = flagAmplitude * elementData.fontSize * Math.sin(transformData.frequency * Math.PI * normalizedX);\n\n // Calculate the slope for skew\n const cosComponent =\n flagAmplitude * transformData.frequency * Math.PI * Math.cos(transformData.frequency * Math.PI * normalizedX);\n const sinComponent =\n transformData.amplitude * Math.sign(normalizedX) * Math.sin(transformData.frequency * Math.PI * normalizedX);\n const slope = cosComponent + sinComponent;\n\n ctx.save();\n ctx.translate(x, y);\n\n // Apply vertical skew based on the slope\n const skewFactor = 0.3;\n ctx.transform(1, slope * skewFactor, 0, 1, 0, 0);\n\n applyStrokeToChar(ctx, char, elementData.stroke);\n ctx.fillText(char, 0, 0);\n ctx.restore();\n\n currentX += charWidth;\n });\n\n ctx.restore();\n}\n","/**\n * Stroke utility functions for path creation\n */\n\nimport type { AnyElementConfig, BaseTextElementConfig, ImageElementConfig, CustomElementConfig, RenderingContext } from '../types/index.js';\nimport { buildFontString, measureTextWidth } from '../core/TextMetrics.js';\n\n/**\n * Create text path for stroking\n * Handles all text transform types\n */\nexport function createTextPath(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n element: AnyElementConfig,\n _renderingContext?: RenderingContext\n): void {\n // Position and rotate\n ctx.translate(element.x || 0, element.y || 0);\n ctx.rotate((-(element.rotation || 0) * Math.PI) / 180);\n\n // Narrow to text element config to access text properties\n const textElement = element as BaseTextElementConfig;\n\n // Set font for text measurement\n const fontSize = textElement.fontSize || 24;\n const fontFamily = textElement.fontFamily || 'Arial';\n const bold = textElement.bold || false;\n const italic = textElement.italic || false;\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n\n const text = textElement.text || '';\n\n // For straight text (custom transform), stroke the bounding box\n if (element.transformType === 'custom') {\n const customElement = element as CustomElementConfig;\n const width = customElement.transformData?.width || 200;\n\n // Create rectangle path around text bounds\n const textWidth = measureTextWidth(text, fontSize, fontFamily, bold, italic);\n const actualWidth = Math.min(textWidth, width);\n const height = fontSize * 1.2;\n\n ctx.rect(-actualWidth / 2, -height / 2, actualWidth, height);\n }\n // For text on path transforms, stroke the text outline\n else {\n // For curved/transformed text, we'll stroke along the text baseline\n // This is a simplified approach - full text outline requires more complex path generation\n\n // Approximate by drawing along baseline\n const textWidth = ctx.measureText(text).width;\n\n ctx.beginPath();\n ctx.moveTo(-textWidth / 2, 0);\n ctx.lineTo(textWidth / 2, 0);\n ctx.moveTo(-textWidth / 2, -fontSize);\n ctx.lineTo(textWidth / 2, -fontSize);\n ctx.moveTo(-textWidth / 2, fontSize * 0.2);\n ctx.lineTo(textWidth / 2, fontSize * 0.2);\n }\n}\n\n/**\n * Create image boundary path for stroking\n */\nexport function createImagePath(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n element: ImageElementConfig\n): void {\n if (!element.transformData) return;\n\n const { borderRadius = 0 } = element.transformData;\n const width = element.transformData.width ?? 100;\n const height = element.transformData.height ?? 100;\n\n // Position and rotate\n ctx.translate(element.x || 0, element.y || 0);\n ctx.rotate((-(element.rotation || 0) * Math.PI) / 180);\n\n // Create path with border radius\n ctx.beginPath();\n\n if (borderRadius > 0) {\n // Rounded rectangle\n const maxRadius = Math.min(width, height) / 2;\n const radius = Math.min(borderRadius, maxRadius);\n\n const x = -width / 2;\n const y = -height / 2;\n\n ctx.moveTo(x + radius, y);\n ctx.lineTo(x + width - radius, y);\n ctx.arcTo(x + width, y, x + width, y + radius, radius);\n ctx.lineTo(x + width, y + height - radius);\n ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius);\n ctx.lineTo(x + radius, y + height);\n ctx.arcTo(x, y + height, x, y + height - radius, radius);\n ctx.lineTo(x, y + radius);\n ctx.arcTo(x, y, x + radius, y, radius);\n ctx.closePath();\n } else {\n // Simple rectangle\n ctx.rect(-width / 2, -height / 2, width, height);\n }\n}\n\n/**\n * Create circular path (for circle transform or shapes)\n */\nexport function createCirclePath(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n x: number,\n y: number,\n radius: number\n): void {\n ctx.beginPath();\n ctx.arc(x, y, radius, 0, Math.PI * 2);\n ctx.closePath();\n}\n\n/**\n * Create rectangular path\n */\nexport function createRectPath(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n x: number,\n y: number,\n width: number,\n height: number\n): void {\n ctx.beginPath();\n ctx.rect(x, y, width, height);\n ctx.closePath();\n}\n","/**\n * StrokeRenderer - Universal stroke rendering for all element types\n *\n * IMPORTANT: This renders the ELEMENT STROKE EFFECT (element.stroke property)\n * This is NOT the hover ring effect (see CanvasRenderer.ts for hover rings)\n *\n * This renderer is ONLY used when:\n * - User explicitly enables stroke in the effects panel\n * - element.stroke.enabled = true\n *\n * Provides stroke rendering capabilities for:\n * - Text elements (all transform types)\n * - Image elements\n * - Shape elements\n * - Groups\n */\n\nimport type {\n StrokeConfig,\n AnyElementConfig,\n ImageElementConfig,\n RenderingContext,\n CustomElementConfig,\n BaseTextElementConfig,\n} from '../types/index.js';\nimport { createImagePath } from './stroke-utils.js';\nimport { getFontMetrics, calculateVisualBoundsWithSpaceCollapsing } from '../core/TextMetrics.js';\nimport { HORIZONTAL_PADDING, LINE_HEIGHT_MULTIPLIER } from '../constants.js';\n\n/**\n * Apply stroke style to canvas context\n */\nexport function applyStrokeStyle(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n stroke: StrokeConfig\n): void {\n ctx.strokeStyle = stroke.color;\n ctx.lineWidth = stroke.width;\n ctx.lineCap = stroke.lineCap || 'butt';\n ctx.lineJoin = stroke.lineJoin || 'miter';\n\n if (stroke.miterLimit !== undefined) {\n ctx.miterLimit = stroke.miterLimit;\n }\n\n if (stroke.dashArray && stroke.dashArray.length > 0) {\n ctx.setLineDash(stroke.dashArray);\n } else {\n ctx.setLineDash([]);\n }\n\n if (stroke.opacity !== undefined) {\n ctx.globalAlpha = stroke.opacity;\n }\n}\n\n/**\n * Create an offscreen canvas compatible with both main thread and Web Worker.\n * Uses OffscreenCanvas when available, falls back to document.createElement.\n */\nfunction createOffscreenCanvas(\n width: number,\n height: number\n): HTMLCanvasElement | OffscreenCanvas {\n if (typeof OffscreenCanvas !== 'undefined') {\n return new OffscreenCanvas(width, height);\n }\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n return canvas;\n}\n\n/**\n * Render stroke for text element\n * Uses ctx.strokeText() for proper text outline stroking\n * Now supports text wrapping for CustomTransform elements\n *\n * When stroke.opacity < 1, renders the entire stroke to a temporary canvas at\n * full opacity, then composites that layer onto the main canvas at the desired\n * opacity. This prevents darkening where adjacent characters' strokes overlap.\n */\nexport function renderTextStroke(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n element: AnyElementConfig,\n renderingContext?: RenderingContext\n): void {\n if (!element.stroke?.enabled) return;\n\n // This function is only called for text elements; narrow to access text properties\n const textElement = element as BaseTextElementConfig;\n const text = textElement.text || '';\n if (!text) return;\n\n // Check if this is for a knockout operation (rendering to offscreen canvas)\n const isKnockoutRender = renderingContext?.isKnockout === true;\n\n // When stroke has sub-1 opacity (and this isn't a knockout render), render the\n // entire stroke to a temp canvas at full opacity, then composite at the target\n // opacity. This avoids overlap darkening between adjacent characters' strokes.\n const strokeOpacity = element.stroke.opacity ?? 1;\n const needsOffscreen = !isKnockoutRender && strokeOpacity < 1;\n\n if (needsOffscreen) {\n try {\n renderTextStrokeViaOffscreen(ctx, element, textElement, renderingContext, strokeOpacity);\n } catch {\n // Fallback: render directly if offscreen approach fails\n // (e.g., getTransform() not available in test mocks)\n renderTextStrokeDirect(ctx, element, textElement, renderingContext, false);\n }\n } else {\n renderTextStrokeDirect(ctx, element, textElement, renderingContext, isKnockoutRender);\n }\n}\n\n/**\n * Render stroke via an offscreen canvas to apply opacity uniformly.\n * Draws all stroke paths at full opacity on a temp canvas, then composites\n * that canvas onto the main canvas at the desired opacity.\n */\nfunction renderTextStrokeViaOffscreen(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n element: AnyElementConfig,\n textElement: BaseTextElementConfig,\n renderingContext: RenderingContext | undefined,\n strokeOpacity: number\n): void {\n const text = textElement.text || '';\n\n // Estimate the bounds we need for the offscreen canvas\n const fontSize = textElement.fontSize || 24;\n const strokeWidth = element.stroke!.width;\n const feather = element.stroke!.feather || 0;\n const isCustomTransform = element.transformType === 'custom' &&\n (element as CustomElementConfig).transformData?.width;\n const customWidth = isCustomTransform\n ? ((element as CustomElementConfig).transformData?.width ?? 200)\n : 0;\n\n // Estimate element dimensions in world space\n const estWidth = isCustomTransform\n ? customWidth + strokeWidth * 2 + feather * 2 + 40\n : fontSize * text.length * 0.7 + strokeWidth * 2 + feather * 2 + 40;\n const estHeight = fontSize * 3 + strokeWidth * 2 + feather * 2 + 40;\n\n // Account for current canvas scale to ensure crisp rendering\n const transform = ctx.getTransform();\n const scale = Math.max(Math.abs(transform.a), Math.abs(transform.d), 1);\n\n // Use even dimensions so that offW/2 and offH/2 are integers, avoiding\n // fractional pixel offsets that cause sub-pixel rendering differences\n // between main thread and worker (the 0.21% export parity diff).\n const offW = Math.ceil(estWidth * scale / 2) * 2;\n const offH = Math.ceil(estHeight * scale / 2) * 2;\n\n // Guard against invalid canvas dimensions\n if (offW <= 0 || offH <= 0) {\n // Fallback: render directly with per-character opacity\n renderTextStrokeDirect(ctx, element, textElement, renderingContext, false);\n return;\n }\n\n const offCanvas = createOffscreenCanvas(offW, offH);\n const offCtx = offCanvas.getContext('2d') as\n | CanvasRenderingContext2D\n | OffscreenCanvasRenderingContext2D\n | null;\n\n if (!offCtx) {\n // Fallback: render directly with per-character opacity\n renderTextStrokeDirect(ctx, element, textElement, renderingContext, false);\n return;\n }\n\n // Set up the offscreen canvas transform so the element center maps to the\n // center of the offscreen canvas. We replicate the main canvas transform\n // but shift the translation so the element is centered in the temp canvas.\n const elemX = element.x || 0;\n const elemY = element.y || 0;\n\n // Where the element center would be in pixel space on the main canvas\n const centerPixelX = transform.a * elemX + transform.c * elemY + transform.e;\n const centerPixelY = transform.b * elemX + transform.d * elemY + transform.f;\n\n // Shift so element center maps to offscreen center\n offCtx.setTransform(\n transform.a, transform.b,\n transform.c, transform.d,\n offW / 2 - centerPixelX + transform.e,\n offH / 2 - centerPixelY + transform.f\n );\n\n // Render the stroke at full opacity onto the offscreen canvas.\n // Override stroke opacity to 1.0 for this render.\n const originalOpacity = element.stroke!.opacity;\n element.stroke!.opacity = 1.0;\n renderTextStrokeDirect(offCtx, element, textElement, renderingContext, false);\n element.stroke!.opacity = originalOpacity;\n\n // Composite the offscreen canvas onto the main canvas at the desired opacity.\n // Round the draw-back coordinates to avoid sub-pixel positioning differences\n // between main thread and worker environments.\n ctx.save();\n ctx.resetTransform();\n ctx.globalAlpha = strokeOpacity;\n ctx.drawImage(\n offCanvas as CanvasImageSource,\n Math.round(centerPixelX - offW / 2),\n Math.round(centerPixelY - offH / 2)\n );\n ctx.restore();\n}\n\n/**\n * Render stroke directly onto the provided canvas context.\n * This is the original rendering logic, used both for full-opacity strokes\n * and as the inner renderer for the offscreen opacity approach.\n */\nfunction renderTextStrokeDirect(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n element: AnyElementConfig,\n textElement: BaseTextElementConfig,\n renderingContext: RenderingContext | undefined,\n isKnockoutRender: boolean\n): void {\n const text = textElement.text || '';\n\n ctx.save();\n\n // Position and rotate - match main text rendering\n // Skip if the caller (e.g. ElementRenderUtils.renderElement) already applied positioning\n if (!renderingContext?.positionApplied) {\n ctx.translate(element.x || 0, element.y || 0);\n ctx.rotate((-(element.rotation || 0) * Math.PI) / 180);\n }\n\n // Set font - match exactly how main text is rendered\n const fontSize = textElement.fontSize || 24;\n const fontFamily = textElement.fontFamily || 'Arial';\n const bold = textElement.bold || false;\n const italic = textElement.italic || false;\n const textAlign = textElement.textAlign || 'center';\n\n ctx.font = `${italic ? 'italic ' : ''}${bold ? 'bold ' : ''}${fontSize}px ${fontFamily}`;\n ctx.textAlign = textAlign as CanvasTextAlign;\n ctx.textBaseline = 'alphabetic'; // MATCH main text rendering baseline\n\n // Apply stroke style\n if (isKnockoutRender) {\n // For knockout, render with full opacity black - color doesn't matter for destination-out\n ctx.strokeStyle = '#000000';\n ctx.globalAlpha = 1.0;\n } else {\n ctx.strokeStyle = element.stroke!.color;\n if (element.stroke!.opacity !== undefined) {\n ctx.globalAlpha = element.stroke!.opacity;\n }\n }\n\n ctx.lineWidth = element.stroke!.width;\n ctx.lineCap = element.stroke!.lineCap || 'round';\n ctx.lineJoin = element.stroke!.lineJoin || 'round';\n\n if (element.stroke!.miterLimit !== undefined) {\n ctx.miterLimit = element.stroke!.miterLimit;\n }\n\n // Feathering (blur) - only supported in regular canvas context\n // Don't apply feather for knockout renders\n if (!isKnockoutRender && element.stroke!.feather && element.stroke!.feather > 0) {\n if ('filter' in ctx) {\n (ctx as CanvasRenderingContext2D).filter = `blur(${element.stroke!.feather}px)`;\n }\n }\n\n // Calculate font metrics\n const fontMetrics = getFontMetrics(fontSize, fontFamily, bold, italic);\n\n // Check if this is a CustomTransform with width (needs text wrapping)\n const isCustomTransform = element.transformType === 'custom' &&\n (element as CustomElementConfig).transformData?.width;\n\n if (isCustomTransform) {\n // CustomTransform: Handle text wrapping\n const transformData = (element as CustomElementConfig).transformData;\n const width = transformData?.width ?? 200;\n const availableWidth = width - HORIZONTAL_PADDING * 2;\n\n // CRITICAL FIX: Use calculateVisualBoundsWithSpaceCollapsing to match selection box exactly\n // This ensures stroke wraps the same way as the bounding box and main rendering\n const { lines, height: visualHeight } = calculateVisualBoundsWithSpaceCollapsing(\n text,\n availableWidth,\n fontSize,\n fontFamily,\n bold,\n italic,\n undefined, // No locked line count for stroke\n true // strict: wrap exactly at maxWidth\n );\n\n // Calculate top-left position (same as fill rendering)\n const topLeftX = -width / 2;\n const topLeftY = -visualHeight / 2;\n\n // Stroke each line (matching renderMultilineText positioning)\n // CRITICAL: We must stroke at the correct Y position even when lines are empty\n // Example: [\" \", \"Foo\", \"Bar\"] - line 0 is empty but occupies space, so \"Foo\" renders at line 0 position\n const lineSpacing = fontSize * LINE_HEIGHT_MULTIPLIER;\n lines.forEach((line: string, index: number) => {\n // Apply space layout rules (matching renderRichTextFillOnly logic)\n // For wrapped text (not explicit newlines), first line is paragraph start, last is paragraph end\n const isParagraphStart = index === 0;\n const isParagraphEnd = index === lines.length - 1;\n\n // Apply space collapsing to match visual rendering\n let visibleText = line;\n\n // Hide leading spaces when NOT at paragraph start\n if (!isParagraphStart) {\n const leadingSpacesMatch = line.match(/^ +/);\n const leadingSpacesCount = leadingSpacesMatch ? leadingSpacesMatch[0].length : 0;\n if (leadingSpacesCount > 0) {\n visibleText = visibleText.substring(leadingSpacesCount);\n }\n }\n\n // Hide trailing spaces when NOT at paragraph end\n if (!isParagraphEnd) {\n const trailingSpacesMatch = visibleText.match(/ +$/);\n const trailingSpacesCount = trailingSpacesMatch ? trailingSpacesMatch[0].length : 0;\n if (trailingSpacesCount > 0) {\n visibleText = visibleText.substring(0, visibleText.length - trailingSpacesCount);\n }\n }\n\n // CRITICAL: Stroke ALL lines, even empty ones!\n // Even though strokeText(\" \") won't draw anything visible, we MUST call it\n // to maintain correct layout positions. Otherwise the 2 visible lines get\n // vertically centered within the 3-line height container.\n let xPos = topLeftX + HORIZONTAL_PADDING;\n\n if (textAlign === 'center') {\n xPos = topLeftX + width / 2;\n } else if (textAlign === 'right') {\n xPos = topLeftX + width - HORIZONTAL_PADDING;\n }\n\n // Use index (not a visual line counter) to match the actual line position\n const yPos = topLeftY + fontMetrics.ascent + index * lineSpacing;\n ctx.strokeText(visibleText, xPos, yPos); // Stroke even if empty for correct positioning\n });\n } else {\n // Other transforms: Single line rendering (original behavior)\n const visualHeight = fontMetrics.height;\n const topLeftY = -visualHeight / 2;\n const yPos = topLeftY + fontMetrics.ascent;\n ctx.strokeText(text, 0, yPos);\n }\n\n ctx.restore();\n}\n\n/**\n * Render stroke for image element\n */\nexport function renderImageStroke(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n element: ImageElementConfig,\n renderingContext?: RenderingContext\n): void {\n if (!element.stroke?.enabled) return;\n if (!element.transformData) return;\n\n const isKnockoutRender = renderingContext?.isKnockout === true;\n\n ctx.save();\n\n // Apply stroke style\n if (isKnockoutRender) {\n // For knockout, render with full opacity\n ctx.strokeStyle = '#000000';\n ctx.lineWidth = element.stroke.width;\n ctx.lineCap = element.stroke.lineCap || 'butt';\n ctx.lineJoin = element.stroke.lineJoin || 'miter';\n ctx.globalAlpha = 1.0;\n if (element.stroke.miterLimit !== undefined) {\n ctx.miterLimit = element.stroke.miterLimit;\n }\n } else {\n applyStrokeStyle(ctx, element.stroke);\n }\n\n // Feathering (blur) - only supported in regular canvas context\n // Don't apply feather for knockout renders\n if (!isKnockoutRender && element.stroke.feather && element.stroke.feather > 0) {\n if ('filter' in ctx) {\n (ctx as CanvasRenderingContext2D).filter = `blur(${element.stroke.feather}px)`;\n }\n }\n\n // Create image boundary path and stroke it\n createImagePath(ctx, element);\n ctx.stroke();\n\n ctx.restore();\n}\n\n/**\n * Render stroke for arbitrary path\n * Used for custom shapes and transforms\n */\nexport function renderPathStroke(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n stroke: StrokeConfig,\n pathFn: () => void\n): void {\n if (!stroke.enabled) return;\n\n ctx.save();\n\n // Apply stroke style\n applyStrokeStyle(ctx, stroke);\n\n // Feathering (blur) - only supported in regular canvas context\n if (stroke.feather && stroke.feather > 0) {\n if ('filter' in ctx) {\n (ctx as CanvasRenderingContext2D).filter = `blur(${stroke.feather}px)`;\n }\n }\n\n // Execute path function and stroke it\n ctx.beginPath();\n pathFn();\n ctx.stroke();\n\n ctx.restore();\n}\n","/**\n * FontAnalyzer - Utility for analyzing fonts and extracting glyph alternates\n *\n * Features:\n * - Fetch and parse font files using fontkit v2.0+ (supports WOFF2 natively in browser)\n * - Extract glyph alternates from GSUB tables\n * - Generate preview images for glyphs\n * - Cache parsed fonts for performance\n */\n\nimport type { GlyphAlternate } from '../types';\nimport { SYSTEM_FONTS } from '../constants.js';\nimport { createLogger } from './logger.js';\n\n/**\n * Minimal type for fontkit Font objects.\n * The fontkit library doesn't export clean TS types for browser usage,\n * so we define the subset we use here.\n */\nexport interface FontkitGlyph {\n id: number;\n name: string;\n advanceWidth: number;\n codePoints?: number[];\n bbox?: { minX: number; minY: number; maxX: number; maxY: number };\n path: {\n toSVG: () => string | null;\n toPath?: () => string | null;\n };\n}\n\nexport interface FontkitGlyphRun {\n glyphs: FontkitGlyph[];\n positions: Array<{ xOffset: number; yOffset: number }>;\n}\n\nexport interface FontkitFont {\n unitsPerEm: number;\n numGlyphs: number;\n GSUB?: {\n featureList?: Array<{\n tag: string;\n feature?: { lookupListIndexes?: number[] };\n }>;\n lookupList?: { lookups?: Array<{ lookupType: number; subtables?: Record<string, unknown>[] }> };\n };\n GPOS?: {\n featureList?: Array<{ tag: string }>;\n };\n layout(text: string, features: Record<string, boolean>, script?: string): FontkitGlyphRun;\n getGlyph(glyphId: number): FontkitGlyph | null;\n glyphForCodePoint(codePoint: number): FontkitGlyph | null;\n}\n\nconst log = createLogger('FontAnalyzer');\n\n// Fontkit is lazy-loaded to reduce initial bundle size (~1MB)\n// It's only needed for OpenType features, glyph alternates, and PUA characters\nlet fontkitModule: typeof import('fontkit') | null = null;\n\nasync function getFontkit() {\n if (!fontkitModule) {\n fontkitModule = await import('fontkit');\n }\n return fontkitModule;\n}\n\ninterface FontCacheEntry {\n font: FontkitFont;\n url: string;\n timestamp: number;\n}\n\nclass FontAnalyzerService {\n private fontCache: Map<string, FontCacheEntry> = new Map();\n private failedFonts: Set<string> = new Set(); // Track fonts that failed to load (CORS, network errors)\n private readonly CACHE_DURATION = 1000 * 60 * 30; // 30 minutes\n private readonly MAX_CACHE_SIZE = 5; // Limit cache to prevent iOS Safari memory crashes\n\n /**\n * Evict the oldest font from cache if over the limit\n * Uses LRU (Least Recently Used) strategy based on timestamp\n */\n private evictOldestFont(): void {\n if (this.fontCache.size <= this.MAX_CACHE_SIZE) return;\n\n // Find oldest entry by timestamp\n let oldestKey: string | null = null;\n let oldestTime = Date.now();\n\n this.fontCache.forEach((entry, key) => {\n if (entry.timestamp < oldestTime) {\n oldestTime = entry.timestamp;\n oldestKey = key;\n }\n });\n\n if (oldestKey) {\n this.fontCache.delete(oldestKey);\n }\n }\n\n /**\n * Check if a font is a system font\n */\n private isSystemFont(fontFamily: string): boolean {\n return SYSTEM_FONTS.has(fontFamily);\n }\n\n /**\n * Get Google Fonts API URL for a specific font family\n */\n private getGoogleFontUrl(fontFamily: string, weight: number = 400): string {\n // Google Fonts API v2 URL format\n const formattedFamily = fontFamily.replace(/\\s+/g, '+');\n return `https://fonts.googleapis.com/css2?family=${formattedFamily}:wght@${weight}&display=swap`;\n }\n\n /**\n * Extract actual font file URL from Google Fonts CSS\n */\n private async extractFontFileUrl(fontFamily: string, weight: number = 400): Promise<string | null> {\n try {\n const cssUrl = this.getGoogleFontUrl(fontFamily, weight);\n\n const response = await fetch(cssUrl);\n const css = await response.text();\n\n\n // Extract all @font-face blocks\n const fontFaceRegex = /@font-face\\s*{([^}]*)}/g;\n const fontFaces = Array.from(css.matchAll(fontFaceRegex));\n\n if (fontFaces.length === 0) {\n log.warn('No @font-face found in CSS');\n return null;\n }\n\n\n // Look for the @font-face with the most complete character coverage\n // Priority: 1) No unicode-range (complete), 2) \"latin\" subset, 3) Largest unicode-range\n let bestUrl: string | null = null;\n let bestScore = -1;\n\n for (let i = 0; i < fontFaces.length; i++) {\n const fontFaceContent = fontFaces[i][1];\n const urlMatch = fontFaceContent.match(/url\\((https:\\/\\/fonts\\.gstatic\\.com\\/[^)]+)\\)/);\n const hasUnicodeRange = fontFaceContent.includes('unicode-range');\n const unicodeRangeMatch = fontFaceContent.match(/unicode-range:\\s*([^;]+);/);\n\n if (!urlMatch) continue;\n\n const url = urlMatch[1];\n let score = 0;\n\n // No unicode-range = complete font (best)\n if (!hasUnicodeRange) {\n score = 1000;\n }\n // Count unicode ranges (more = better coverage)\n else if (unicodeRangeMatch) {\n const rangeContent = unicodeRangeMatch[1];\n score = (rangeContent.match(/U\\+/g) || []).length;\n }\n\n\n if (score > bestScore) {\n bestScore = score;\n bestUrl = url;\n }\n }\n\n if (bestUrl) {\n return bestUrl;\n }\n\n log.warn('No font URL found in CSS for', fontFamily);\n return null;\n } catch (error) {\n log.error('Error extracting font file URL:', error);\n return null;\n }\n }\n\n /**\n * Load and parse a font file\n */\n async loadFont(fontFamily: string, weight: number = 400): Promise<FontkitFont | null> {\n // System fonts are already available in the browser, no need to load\n if (this.isSystemFont(fontFamily)) {\n return null;\n }\n\n const cacheKey = `${fontFamily}-${weight}`;\n\n // Check if font previously failed (avoids repeated CORS/network errors)\n if (this.failedFonts.has(cacheKey)) {\n return null;\n }\n\n // Check cache\n const cached = this.fontCache.get(cacheKey);\n if (cached && Date.now() - cached.timestamp < this.CACHE_DURATION) {\n // Update timestamp on access (LRU behavior)\n cached.timestamp = Date.now();\n return cached.font;\n }\n\n try {\n\n // Get font file URL\n const fontUrl = await this.extractFontFileUrl(fontFamily, weight);\n if (!fontUrl) {\n // Mark as failed to avoid repeated attempts (especially for CORS errors)\n this.failedFonts.add(cacheKey);\n return null;\n }\n\n\n // Fetch font file as ArrayBuffer\n const response = await fetch(fontUrl);\n if (!response.ok) {\n log.error(`Font fetch failed: ${response.status} ${response.statusText}`);\n this.failedFonts.add(cacheKey);\n return null;\n }\n\n const arrayBuffer = await response.arrayBuffer();\n\n // Parse with fontkit (supports WOFF2, WOFF, TTF, OTF natively)\n const buffer = new Uint8Array(arrayBuffer);\n const fontkit = await getFontkit();\n // fontkit types expect Buffer but Uint8Array is accepted at runtime in browser environments\n const fontOrCollection = fontkit.create(buffer as unknown as Buffer);\n\n // Handle FontCollection (TTC files) vs single Font\n const font =\n 'fonts' in fontOrCollection\n ? fontOrCollection.fonts[0] // Get first font from collection\n : fontOrCollection; // Single font\n\n\n // Cache the result\n this.fontCache.set(cacheKey, {\n font,\n url: fontUrl,\n timestamp: Date.now(),\n });\n\n // Evict oldest font if cache is full (prevents iOS Safari memory crashes)\n this.evictOldestFont();\n\n return font;\n } catch (error) {\n log.error(`Error loading font ${fontFamily}:`, error);\n // Mark as failed to avoid repeated network/CORS errors\n this.failedFonts.add(cacheKey);\n return null;\n }\n }\n\n /**\n * Get font from cache synchronously (for rendering)\n * Returns null if font is not cached - font should be pre-loaded before rendering\n */\n getFontSync(fontFamily: string, weight: number = 400): FontkitFont | null {\n const cacheKey = `${fontFamily}-${weight}`;\n const cached = this.fontCache.get(cacheKey);\n\n if (cached && Date.now() - cached.timestamp < this.CACHE_DURATION) {\n // Update timestamp on access (LRU behavior - recently used fonts stay in cache)\n cached.timestamp = Date.now();\n return cached.font;\n }\n\n return null;\n }\n\n /**\n * Clear font cache (useful for debugging or forcing reload)\n * Also clears failed fonts to allow retry\n */\n clearCache(fontFamily?: string, weight?: number): void {\n if (fontFamily) {\n const cacheKey = `${fontFamily}-${weight || 400}`;\n this.fontCache.delete(cacheKey);\n this.failedFonts.delete(cacheKey);\n } else {\n this.fontCache.clear();\n this.failedFonts.clear();\n }\n }\n\n /**\n * Get all glyphs in a font (for browsing)\n */\n async getAllGlyphs(\n fontFamily: string,\n weight: number = 400,\n limit: number = 1500 // Increased limit to catch more alternates\n ): Promise<GlyphAlternate[]> {\n // System fonts don't have accessible glyph data via Google Fonts\n if (this.isSystemFont(fontFamily)) {\n return [];\n }\n\n const font = await this.loadFont(fontFamily, weight);\n if (!font) {\n return [];\n }\n\n const glyphs: GlyphAlternate[] = [];\n\n\n // Iterate through all glyphs in the font\n const maxGlyphs = Math.min(font.numGlyphs, limit);\n for (let glyphId = 0; glyphId < maxGlyphs; glyphId++) {\n try {\n const glyph = font.getGlyph(glyphId);\n if (!glyph || !glyph.name) continue;\n\n // Skip .notdef and other special glyphs\n if (glyph.name === '.notdef' || glyph.name.startsWith('.null')) {\n continue;\n }\n\n // Get unicode value if available\n const codePoints = glyph.codePoints || [];\n let unicode = codePoints.length > 0 ? String.fromCodePoint(codePoints[0]) : '';\n\n // If no unicode, try to extract the base character from the glyph name\n // For glyphs like \"T.alt\", \"W.swash\", extract \"T\" or \"W\"\n if (!unicode || unicode.length === 0) {\n const baseCharMatch = glyph.name.match(/^([A-Za-z0-9])[._]/);\n if (baseCharMatch) {\n unicode = baseCharMatch[1];\n }\n }\n\n // Generate a friendly name\n let friendlyName = glyph.name;\n if (unicode && unicode.length > 0 && /\\S/.test(unicode)) {\n // Try to create a more readable name\n if (glyph.name.includes('.swash')) {\n friendlyName = `${unicode} Swash`;\n } else if (glyph.name.match(/\\.alt(\\d+)?/)) {\n const num = glyph.name.match(/\\.alt(\\d+)/)?.[1] || '';\n friendlyName = `${unicode} Alternate${num ? ' ' + num : ''}`;\n } else if (glyph.name.match(/\\.ss(\\d+)/)) {\n const num = glyph.name.match(/\\.ss(\\d+)/)?.[1];\n friendlyName = `${unicode} Stylistic Set ${num}`;\n } else {\n // Use the unicode character as a prefix\n friendlyName = `${unicode} - ${glyph.name}`;\n }\n }\n\n // Categorize the glyph - check for various alternate patterns\n let category: GlyphAlternate['category'] = 'default';\n const nameLower = glyph.name.toLowerCase();\n\n if (nameLower.includes('swash') || nameLower.includes('cswh')) {\n category = 'swash';\n } else if (glyph.name.match(/\\.(alt|ss\\d+|cv\\d+|salt|ornament)/)) {\n category = 'stylistic';\n } else if (glyph.name.match(/_\\d+$/)) {\n // Pattern like T_001, T_002\n category = 'stylistic';\n } else if (nameLower.match(/liga|dlig/)) {\n category = 'ligature';\n }\n\n // Only show alternates, swashes, and ligatures\n // Skip anything categorized as \"default\" - those aren't alternate glyphs\n if (category === 'default') {\n continue;\n }\n\n // Only add glyphs that have a valid unicode character (skip if we couldn't determine one)\n if (unicode && unicode.length > 0) {\n glyphs.push({\n glyphIndex: glyph.id,\n unicode: unicode,\n name: friendlyName,\n category,\n previewDataUrl: this.generateGlyphPreview(font, glyph.id, 80),\n });\n }\n } catch (e) {\n // Skip glyphs that can't be loaded\n }\n }\n\n return glyphs;\n }\n\n /**\n * Get glyph alternates for a specific character\n */\n async getGlyphAlternates(fontFamily: string, character: string, weight: number = 400): Promise<GlyphAlternate[]> {\n const font = await this.loadFont(fontFamily, weight);\n if (!font) {\n return [];\n }\n\n const alternates: GlyphAlternate[] = [];\n\n // Get default glyph\n const codePoint = character.codePointAt(0);\n if (codePoint === undefined) {\n return [];\n }\n\n const defaultGlyph = font.glyphForCodePoint(codePoint);\n if (defaultGlyph) {\n alternates.push({\n glyphIndex: defaultGlyph.id,\n unicode: character,\n name: defaultGlyph.name || 'Default',\n category: 'default',\n previewDataUrl: this.generateGlyphPreview(font, defaultGlyph.id, 80),\n });\n }\n\n // Search for glyphs by name pattern\n // Many fonts have alternates as separate glyphs (e.g., \"W.swash\", \"W.alt01\", \"W_001\")\n const baseName = defaultGlyph?.name;\n\n // Try to iterate through all glyphs\n if (baseName) {\n let foundCount = 0;\n\n // Fontkit doesn't expose _glyphs directly, but we can iterate by glyph ID\n for (let glyphId = 0; glyphId < font.numGlyphs; glyphId++) {\n try {\n const glyph = font.getGlyph(glyphId);\n if (!glyph || !glyph.name) continue;\n\n // Check if this glyph name is a variant of the base character\n // Patterns: W.swash, W.alt01, W_001, W.ss01, etc.\n const namePatterns = [\n new RegExp(`^${baseName}\\\\.(swash|alt|ss\\\\d+|salt|ornament)`, 'i'),\n new RegExp(`^${baseName}_\\\\d+$`, 'i'),\n new RegExp(`^${baseName}\\\\.\\\\d+$`, 'i'),\n ];\n\n const isVariant = namePatterns.some((pattern) => pattern.test(glyph.name));\n\n if (isVariant && glyph.id !== defaultGlyph.id) {\n foundCount++;\n\n // Extract a friendly name from the glyph name\n let friendlyName = glyph.name;\n if (glyph.name.includes('.swash')) {\n friendlyName = `${character} Swash`;\n } else if (glyph.name.match(/\\.alt(\\d+)?/)) {\n const num = glyph.name.match(/\\.alt(\\d+)/)?.[1] || '';\n friendlyName = `${character} Alternate${num ? ' ' + num : ''}`;\n } else if (glyph.name.match(/\\.ss(\\d+)/)) {\n const num = glyph.name.match(/\\.ss(\\d+)/)?.[1];\n friendlyName = `${character} Stylistic Set ${num}`;\n }\n\n alternates.push({\n glyphIndex: glyph.id,\n unicode: character,\n name: friendlyName,\n category: 'stylistic',\n previewDataUrl: this.generateGlyphPreview(font, glyph.id, 80),\n });\n }\n } catch (e) {\n // Skip glyphs that can't be loaded\n }\n }\n\n }\n\n // Check GSUB table for alternates\n if (font.GSUB) {\n const gsub = font.GSUB;\n\n // Look for single substitution features (e.g., swsh, ss01, etc.)\n if (gsub.featureList) {\n for (const feature of gsub.featureList) {\n const tag = feature.tag;\n\n // Skip if not a relevant feature\n if (!this.isRelevantFeature(tag)) {\n continue;\n }\n\n // Look through lookups\n if (feature.feature && feature.feature.lookupListIndexes) {\n for (const lookupIndex of feature.feature.lookupListIndexes) {\n const lookup = gsub.lookupList?.lookups?.[lookupIndex];\n if (!lookup) continue;\n\n // Single substitution (Type 1)\n if (lookup.lookupType === 1) {\n for (const subtable of lookup.subtables || []) {\n const coverage = subtable.coverage as Record<string, unknown> | undefined;\n if (!coverage) continue;\n\n // Check if our character is in the coverage\n const coverageIndex = this.getCoverageIndex(coverage, defaultGlyph!.id);\n if (coverageIndex !== -1) {\n const substituteGlyphIndex = this.getSubstituteGlyph(subtable as Record<string, unknown>, defaultGlyph!.id);\n if (substituteGlyphIndex !== null) {\n alternates.push({\n glyphIndex: substituteGlyphIndex,\n unicode: character,\n name: `${character} (${tag})`,\n category: this.categorizeFeature(tag) as GlyphAlternate['category'],\n previewDataUrl: this.generateGlyphPreview(font, substituteGlyphIndex, 80),\n });\n }\n }\n }\n }\n\n // Alternate substitution (Type 3)\n else if (lookup.lookupType === 3) {\n for (const subtable of lookup.subtables || []) {\n const coverage = subtable.coverage as Record<string, unknown> | undefined;\n if (!coverage) continue;\n\n const coverageIndex = this.getCoverageIndex(coverage, defaultGlyph!.id);\n const altSubtable = subtable as Record<string, unknown> & { alternateSets?: number[][] };\n if (coverageIndex !== -1 && altSubtable.alternateSets) {\n const alternateSet = altSubtable.alternateSets[coverageIndex];\n if (alternateSet) {\n for (const altGlyphIndex of alternateSet) {\n alternates.push({\n glyphIndex: altGlyphIndex,\n unicode: character,\n name: `${character} (${tag} alt)`,\n category: this.categorizeFeature(tag),\n previewDataUrl: this.generateGlyphPreview(font, altGlyphIndex, 80),\n });\n }\n }\n }\n }\n }\n }\n }\n }\n }\n }\n\n return alternates;\n }\n\n /**\n * Get available OpenType features for a font\n */\n async getAvailableFeatures(fontFamily: string, weight: number = 400): Promise<string[]> {\n\n const font = await this.loadFont(fontFamily, weight);\n if (!font) {\n log.warn(`Font ${fontFamily} failed to load`);\n return [];\n }\n\n try {\n const features: string[] = [];\n\n // Extract features from GSUB table\n if (font.GSUB && font.GSUB.featureList) {\n const uniqueFeatures = new Set<string>();\n for (const feature of font.GSUB.featureList) {\n if (feature.tag) {\n uniqueFeatures.add(feature.tag);\n }\n }\n features.push(...Array.from(uniqueFeatures));\n }\n\n // Extract features from GPOS table (positioning features like kern)\n if (font.GPOS && font.GPOS.featureList) {\n const uniqueFeatures = new Set<string>();\n for (const feature of font.GPOS.featureList) {\n if (feature.tag) {\n uniqueFeatures.add(feature.tag);\n }\n }\n features.push(...Array.from(uniqueFeatures));\n }\n\n return features;\n } catch (error) {\n log.error(`Error getting features for ${fontFamily}:`, error);\n return [];\n }\n }\n\n /**\n * Generate a preview image for a glyph\n */\n private generateGlyphPreview(font: FontkitFont, glyphIndex: number, size: number = 80): string {\n const canvas = document.createElement('canvas');\n canvas.width = size;\n canvas.height = size;\n const ctx = canvas.getContext('2d');\n\n if (!ctx) {\n return '';\n }\n\n try {\n const glyph = font.getGlyph(glyphIndex);\n if (!glyph) {\n return '';\n }\n\n // Clear canvas\n ctx.clearRect(0, 0, size, size);\n\n // Get glyph bounding box\n const bbox = glyph.bbox;\n if (!bbox) {\n return '';\n }\n\n const glyphWidth = bbox.maxX - bbox.minX;\n const glyphHeight = bbox.maxY - bbox.minY;\n\n if (glyphWidth === 0 || glyphHeight === 0) {\n return '';\n }\n\n // Use more conservative padding to prevent clipping (60% instead of 80%)\n const padding = 0.6;\n const scale = Math.min(size / glyphWidth, size / glyphHeight) * padding;\n\n // Center the glyph more carefully\n // Calculate the center of the glyph in its own coordinate space\n const glyphCenterX = (bbox.minX + bbox.maxX) / 2;\n const glyphCenterY = (bbox.minY + bbox.maxY) / 2;\n\n // Canvas center\n const canvasCenterX = size / 2;\n const canvasCenterY = size / 2;\n\n // Get SVG path and render it\n const svgPath = glyph.path.toSVG();\n if (!svgPath) {\n return '';\n }\n\n // Create Path2D from SVG path\n const path2D = new Path2D(svgPath);\n\n // Apply transformations\n ctx.save();\n\n // Move to canvas center\n ctx.translate(canvasCenterX, canvasCenterY);\n\n // Scale (flip Y for font coordinates)\n ctx.scale(scale, -scale);\n\n // Move glyph center to origin\n ctx.translate(-glyphCenterX, -glyphCenterY);\n\n ctx.fillStyle = '#000000';\n ctx.fill(path2D);\n ctx.restore();\n\n // Convert to data URL\n return canvas.toDataURL('image/png');\n } catch (error) {\n log.error('Error generating glyph preview:', error);\n return '';\n }\n }\n\n /**\n * Check if a feature tag is relevant for glyph alternates\n */\n private isRelevantFeature(tag: string): boolean {\n const relevantFeatures = [\n 'swsh',\n 'cswh',\n 'salt',\n 'ss01',\n 'ss02',\n 'ss03',\n 'ss04',\n 'ss05',\n 'ss06',\n 'ss07',\n 'ss08',\n 'ss09',\n 'ss10',\n 'ss11',\n 'ss12',\n 'ss13',\n 'ss14',\n 'ss15',\n 'ss16',\n 'ss17',\n 'ss18',\n 'ss19',\n 'ss20',\n 'cv01',\n 'cv02',\n 'cv03',\n 'cv04',\n 'cv05',\n 'cv06',\n 'cv07',\n 'cv08',\n 'cv09',\n 'cv10',\n 'aalt',\n 'dlig',\n 'liga',\n ];\n return relevantFeatures.includes(tag);\n }\n\n /**\n * Categorize a feature tag\n */\n private categorizeFeature(tag: string): GlyphAlternate['category'] {\n if (tag.startsWith('swsh') || tag === 'cswh') return 'swash';\n if (tag.startsWith('ss') || tag.startsWith('cv') || tag === 'salt') return 'stylistic';\n if (tag === 'liga' || tag === 'dlig') return 'ligature';\n if (tag === 'calt') return 'contextual';\n return 'stylistic';\n }\n\n /**\n * Get coverage index for a glyph\n */\n private getCoverageIndex(coverage: Record<string, unknown>, glyphIndex: number): number {\n if (!coverage) return -1;\n\n // Format 1: List of glyph IDs\n if (coverage.format === 1 && coverage.glyphs) {\n return (coverage.glyphs as number[]).indexOf(glyphIndex);\n }\n\n // Format 2: Ranges\n if (coverage.format === 2 && coverage.ranges) {\n const ranges = coverage.ranges as Array<{ start: number; end: number; index: number }>;\n for (let i = 0; i < ranges.length; i++) {\n const range = ranges[i];\n if (glyphIndex >= range.start && glyphIndex <= range.end) {\n return range.index + (glyphIndex - range.start);\n }\n }\n }\n\n return -1;\n }\n\n /**\n * Get substitute glyph from a single substitution subtable\n */\n private getSubstituteGlyph(subtable: Record<string, unknown>, glyphIndex: number): number | null {\n if (!subtable) return null;\n\n // Format 1: Delta\n if (subtable.substFormat === 1 && typeof subtable.deltaGlyphId === 'number') {\n return glyphIndex + subtable.deltaGlyphId;\n }\n\n // Format 2: Mapping\n if (subtable.substFormat === 2 && subtable.substitute) {\n const coverageIndex = this.getCoverageIndex(subtable.coverage as Record<string, unknown>, glyphIndex);\n const substitute = subtable.substitute as number[];\n if (coverageIndex !== -1 && substitute[coverageIndex] !== undefined) {\n return substitute[coverageIndex];\n }\n }\n\n return null;\n }\n\n /**\n * Get the count of alternate glyphs in a font (fast - no preview generation)\n */\n async getAlternateGlyphCount(fontFamily: string, weight: number = 400): Promise<number> {\n const font = await this.loadFont(fontFamily, weight);\n if (!font) {\n return 0;\n }\n\n let count = 0;\n\n // Iterate through all glyphs in the font\n const maxGlyphs = Math.min(font.numGlyphs, 1500);\n for (let glyphId = 0; glyphId < maxGlyphs; glyphId++) {\n try {\n const glyph = font.getGlyph(glyphId);\n if (!glyph || !glyph.name) continue;\n\n // Skip .notdef and other special glyphs\n if (glyph.name === '.notdef' || glyph.name.startsWith('.null')) {\n continue;\n }\n\n // Get unicode value if available\n const codePoints = glyph.codePoints || [];\n let unicode = codePoints.length > 0 ? String.fromCodePoint(codePoints[0]) : '';\n\n // If no unicode, try to extract the base character from the glyph name\n if (!unicode || unicode.length === 0) {\n const baseCharMatch = glyph.name.match(/^([A-Za-z0-9])[._]/);\n if (baseCharMatch) {\n unicode = baseCharMatch[1];\n }\n }\n\n // Categorize the glyph - check for various alternate patterns\n let category = 'default';\n const nameLower = glyph.name.toLowerCase();\n\n if (nameLower.includes('swash') || nameLower.includes('cswh')) {\n category = 'swash';\n } else if (glyph.name.match(/\\.(alt|ss\\d+|cv\\d+|salt|ornament)/)) {\n category = 'stylistic';\n } else if (glyph.name.match(/_\\d+$/)) {\n category = 'stylistic';\n } else if (nameLower.match(/liga|dlig/)) {\n category = 'ligature';\n }\n\n // Only count alternates, swashes, and ligatures\n if (category !== 'default' && unicode && unicode.length > 0) {\n count++;\n }\n } catch (e) {\n // Skip glyphs that can't be loaded\n }\n }\n\n return count;\n }\n\n /**\n * Get cache statistics\n */\n getCacheStats(): { size: number; entries: string[] } {\n return {\n size: this.fontCache.size,\n entries: Array.from(this.fontCache.keys()),\n };\n }\n}\n\n// Export singleton instance\nexport const FontAnalyzer = new FontAnalyzerService();\n","/**\n * GlyphRenderer - Utility for rendering text with glyph overrides and OpenType features\n *\n * This provides enhanced text rendering that supports:\n * - Per-character glyph substitution\n * - OpenType feature application\n */\n\nimport type { GlyphOverride, OpenTypeFeatures } from '../types';\nimport type { FontkitFont } from './FontAnalyzer';\nimport { FontAnalyzer } from './FontAnalyzer';\nimport { createLogger } from './logger';\n\nconst logger = createLogger('GlyphRenderer');\n\n/**\n * Build CSS font-feature-settings string from OpenTypeFeatures\n */\nexport function buildFontFeatureSettings(features?: OpenTypeFeatures): string {\n if (!features) return '';\n\n const settings: string[] = [];\n\n Object.entries(features).forEach(([tag, enabled]) => {\n if (enabled) {\n settings.push(`\"${tag}\" 1`);\n }\n });\n\n return settings.join(', ');\n}\n\n/**\n * Apply OpenType features to canvas context via font string\n * Note: Browser support for font-feature-settings in canvas is limited\n * This works in Chrome 99+, Firefox 105+, Safari 16.4+\n */\nexport function applyOpenTypeFeaturestoContext(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n features?: OpenTypeFeatures\n): void {\n if (!features || Object.keys(features).length === 0) return;\n\n // Use fontVariantCaps for small caps (better browser support)\n if (features.smcp || features.c2sc) {\n (ctx as CanvasRenderingContext2D & { fontVariantCaps?: string }).fontVariantCaps = 'small-caps';\n }\n\n // Note: Direct font-feature-settings support in canvas is limited\n // For full support, text would need to be rendered to DOM first, then drawn to canvas\n}\n\n/**\n * Render text with OpenType features using fontkit (synchronous version)\n * This method renders glyphs as paths with features applied\n * Requires font to be pre-loaded and passed as parameter\n */\nexport function renderTextWithOpenTypeFeaturesSync(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n font: FontkitFont,\n text: string,\n x: number,\n y: number,\n fontSize: number,\n features: OpenTypeFeatures,\n options: {\n color?: string;\n align?: 'left' | 'center' | 'right';\n } = {}\n): void {\n try {\n // Build feature array for fontkit\n const featureArray: string[] = [];\n Object.entries(features).forEach(([tag, enabled]) => {\n if (enabled) {\n featureArray.push(tag);\n }\n });\n\n\n // Check available GSUB features\n const availableFeatures: string[] = [];\n if (font.GSUB && font.GSUB.featureList) {\n font.GSUB.featureList.forEach((feat: { tag: string }) => {\n availableFeatures.push(feat.tag);\n });\n }\n\n\n\n // Use fontkit's layout engine to apply OpenType features\n // fontkit.layout(text, features, script, language, direction)\n\n // Build features object (fontkit prefers object format)\n const featuresObject: Record<string, boolean> = {};\n\n // Always enable required features for proper text shaping\n // These are the \"default\" features that should always be on\n featuresObject.ccmp = true; // Glyph composition/decomposition (required)\n featuresObject.locl = true; // Localized forms\n featuresObject.kern = true; // Kerning\n\n // Add user-selected features on top of defaults\n featureArray.forEach((tag) => {\n featuresObject[tag] = true;\n });\n\n // For script/cursive fonts, if calt is enabled, also enable related features\n // that work together to create proper connections\n if (featuresObject.calt) {\n featuresObject.liga = true; // Standard ligatures (needed for connections)\n featuresObject.init = true; // Initial forms\n featuresObject.fina = true; // Final forms\n // Note: These features work TOGETHER - calt triggers contextual substitutions,\n // while init/fina/liga provide the alternate glyphs\n }\n\n\n\n // Try layout with features and script tag\n // Script tag 'latn' is required for proper Latin font shaping\n const run = font.layout(text, featuresObject, 'latn');\n\n\n // Calculate scale from font units to pixels\n const scale = fontSize / font.unitsPerEm;\n\n // Calculate total width for alignment\n let totalWidth = 0;\n run.glyphs.forEach((glyph) => {\n totalWidth += glyph.advanceWidth * scale;\n });\n\n // Adjust starting x position based on alignment\n let startX = x;\n if (options.align === 'center') {\n startX = x - totalWidth / 2;\n } else if (options.align === 'right') {\n startX = x - totalWidth;\n }\n\n // Render each glyph\n ctx.save();\n ctx.fillStyle = options.color || '#000000';\n\n let currentX = startX;\n run.glyphs.forEach((glyph, index: number) => {\n const position = run.positions[index];\n\n // Get the path data - try multiple approaches\n let pathData: string | null = null;\n\n try {\n // Method 1: Try toPath() for d attribute (standard fontkit)\n if (glyph.path && typeof glyph.path.toPath === 'function') {\n pathData = glyph.path.toPath();\n }\n // Method 2: Try toSVG() and extract or use directly\n else if (glyph.path && typeof glyph.path.toSVG === 'function') {\n const svgPath = glyph.path.toSVG();\n\n // Check if it's already path data (starts with M, L, C, etc.)\n if (svgPath && /^[MmLlHhVvCcSsQqTtAaZz]/.test(svgPath)) {\n pathData = svgPath;\n } else {\n // Try to extract d attribute\n const dMatch = svgPath?.match(/\\bd=[\"']([^\"']+)[\"']/);\n pathData = dMatch ? dMatch[1] : null;\n }\n }\n } catch (e) {\n logger.warn('Error getting path for glyph:', glyph.name, e);\n }\n\n if (index === 0) {\n }\n\n if (pathData) {\n // Create Path2D from path data\n const path2D = new Path2D(pathData);\n\n // Apply transformations for position and scale\n ctx.save();\n ctx.translate(currentX + position.xOffset * scale, y + position.yOffset * scale);\n ctx.scale(scale, -scale); // Flip Y axis for proper rendering\n ctx.fill(path2D);\n ctx.restore();\n }\n\n currentX += glyph.advanceWidth * scale;\n });\n\n ctx.restore();\n } catch (error) {\n logger.error('Error rendering with fontkit:', error);\n // Fallback to standard rendering\n ctx.fillStyle = options.color || '#000000';\n ctx.textAlign = options.align || 'left';\n ctx.fillText(text, x, y);\n }\n}\n\n/**\n * Render text with OpenType features using fontkit (async version)\n * This method renders glyphs as paths with features applied\n */\nexport async function renderTextWithOpenTypeFeatures(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n text: string,\n x: number,\n y: number,\n fontSize: number,\n fontFamily: string,\n features: OpenTypeFeatures,\n options: {\n color?: string;\n bold?: boolean;\n italic?: boolean;\n align?: 'left' | 'center' | 'right';\n } = {}\n): Promise<void> {\n // Load font using FontAnalyzer\n const font = await FontAnalyzer.loadFont(fontFamily, options.bold ? 700 : 400);\n if (!font) {\n // Fallback to standard rendering\n ctx.fillStyle = options.color || '#000000';\n ctx.textAlign = options.align || 'left';\n ctx.fillText(text, x, y);\n return;\n }\n\n // Use the synchronous version with the loaded font\n renderTextWithOpenTypeFeaturesSync(ctx, font, text, x, y, fontSize, features, options);\n}\n\n/**\n * Render text with glyph overrides using fontkit (synchronous version)\n * Requires font to be pre-loaded and passed as parameter\n */\nexport function renderTextWithGlyphOverridesSync(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n font: FontkitFont,\n text: string,\n x: number,\n y: number,\n fontSize: number,\n glyphOverrides?: GlyphOverride[],\n options: {\n color?: string;\n align?: 'left' | 'center' | 'right';\n } = {}\n): void {\n // Helper to detect PUA characters\n const containsPUA = (str: string): boolean => {\n for (const char of str) {\n const cp = char.codePointAt(0);\n if (cp && cp >= 0xf0000 && cp <= 0xffffd) return true;\n }\n return false;\n };\n\n const hasPUA = containsPUA(text);\n\n // If no glyph overrides AND no PUA characters, use standard canvas text rendering\n if ((!glyphOverrides || glyphOverrides.length === 0) && !hasPUA) {\n ctx.fillStyle = options.color || '#000000';\n ctx.textAlign = options.align || 'left';\n ctx.fillText(text, x, y);\n return;\n }\n\n\n // Create a map of character indices to glyph overrides\n const overrideMap = new Map<number, number>();\n if (glyphOverrides) {\n glyphOverrides.forEach((override) => {\n overrideMap.set(override.charIndex, override.glyphIndex);\n });\n }\n\n // Calculate scale from font units to pixels\n const scale = fontSize / font.unitsPerEm;\n\n const chars = Array.from(text); // Use Array.from to handle multi-byte chars\n\n // Calculate total width for alignment\n let totalWidth = 0;\n chars.forEach((char, index) => {\n const codePoint = char.codePointAt(0) || 0;\n let glyphId: number | undefined;\n\n // Check if character is in our PUA range (U+F0000 to U+FFFFD)\n if (codePoint >= 0xf0000 && codePoint <= 0xffffd) {\n // Extract glyph ID from PUA codepoint\n glyphId = codePoint - 0xf0000;\n } else {\n // Check for override (legacy system)\n glyphId = overrideMap.get(index);\n\n if (glyphId === undefined) {\n const defaultGlyph = font.glyphForCodePoint(codePoint);\n glyphId = defaultGlyph ? defaultGlyph.id : 0;\n }\n }\n\n const glyph = font.getGlyph(glyphId);\n if (glyph) {\n totalWidth += glyph.advanceWidth * scale;\n }\n });\n\n // Adjust starting x position based on alignment\n let startX = x;\n if (options.align === 'center') {\n startX = x - totalWidth / 2;\n } else if (options.align === 'right') {\n startX = x - totalWidth;\n }\n\n // Render each character\n ctx.save();\n ctx.fillStyle = options.color || '#000000';\n\n let currentX = startX;\n\n chars.forEach((char, index) => {\n // Get the glyph ID\n const codePoint = char.codePointAt(0) || 0;\n let glyphId: number | undefined;\n\n // Check if character is in our PUA range (U+F0000 to U+FFFFD)\n // These are our mapped alternate glyphs\n if (codePoint >= 0xf0000 && codePoint <= 0xffffd) {\n // Extract glyph ID from PUA codepoint\n glyphId = codePoint - 0xf0000;\n } else {\n // Check for override (legacy system)\n glyphId = overrideMap.get(index);\n\n if (glyphId === undefined) {\n // Use default glyph\n const defaultGlyph = font.glyphForCodePoint(codePoint);\n glyphId = defaultGlyph ? defaultGlyph.id : 0;\n }\n }\n\n // Get the glyph\n const glyph = font.getGlyph(glyphId);\n\n if (codePoint >= 0xf0000 && codePoint <= 0xffffd) {\n }\n\n if (glyph) {\n // Get SVG path\n const svgPath = glyph.path.toSVG();\n\n if (svgPath) {\n const path2D = new Path2D(svgPath);\n\n ctx.save();\n ctx.translate(currentX, y);\n ctx.scale(scale, -scale);\n ctx.fill(path2D);\n ctx.restore();\n }\n\n currentX += glyph.advanceWidth * scale;\n }\n });\n\n ctx.restore();\n}\n\n/**\n * Render text with glyph overrides using fontkit (async version)\n * Loads the font and calls the synchronous version\n */\nexport async function renderTextWithGlyphOverrides(\n ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,\n text: string,\n x: number,\n y: number,\n fontSize: number,\n fontFamily: string,\n glyphOverrides?: GlyphOverride[],\n options: {\n color?: string;\n bold?: boolean;\n italic?: boolean;\n align?: 'left' | 'center' | 'right';\n } = {}\n): Promise<void> {\n // Load font using FontAnalyzer\n const font = await FontAnalyzer.loadFont(fontFamily, options.bold ? 700 : 400);\n if (!font) {\n // Fallback to standard rendering\n ctx.fillStyle = options.color || '#000000';\n ctx.textAlign = options.align || 'left';\n ctx.fillText(text, x, y);\n return;\n }\n\n // Use the synchronous version with the loaded font\n renderTextWithGlyphOverridesSync(ctx, font, text, x, y, fontSize, glyphOverrides, options);\n}\n\n/**\n * Calculate text width with glyph overrides using fontkit API\n */\nfunction calculateTextWidthWithOverrides(\n font: FontkitFont,\n text: string,\n overrideMap: Map<number, number>,\n scale: number\n): number {\n const chars = Array.from(text);\n let totalWidth = 0;\n\n chars.forEach((char, index) => {\n const codePoint = char.codePointAt(0) || 0;\n let glyphId: number | undefined;\n\n // Check if character is in the PUA range (U+F0000 to U+FFFFD)\n if (codePoint >= 0xf0000 && codePoint <= 0xffffd) {\n glyphId = codePoint - 0xf0000;\n } else {\n glyphId = overrideMap.get(index);\n if (glyphId === undefined) {\n const defaultGlyph = font.glyphForCodePoint(codePoint);\n glyphId = defaultGlyph ? defaultGlyph.id : 0;\n }\n }\n\n const glyph = font.getGlyph(glyphId);\n if (glyph) {\n totalWidth += glyph.advanceWidth * scale;\n }\n });\n\n return totalWidth;\n}\n\n/**\n * Measure text width with glyph overrides\n */\nexport async function measureTextWithGlyphOverrides(\n text: string,\n fontSize: number,\n fontFamily: string,\n glyphOverrides?: GlyphOverride[],\n options: {\n bold?: boolean;\n } = {}\n): Promise<number> {\n // If no glyph overrides, use standard measurement\n if (!glyphOverrides || glyphOverrides.length === 0) {\n // Create a temporary canvas context for measurement\n const canvas =\n typeof OffscreenCanvas !== 'undefined' ? new OffscreenCanvas(1, 1) : document.createElement('canvas');\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return 0;\n\n const weight = options.bold ? 'bold' : 'normal';\n ctx.font = `${weight} ${fontSize}px ${fontFamily}`;\n return ctx.measureText(text).width;\n }\n\n // Load font and calculate with overrides\n const font = await FontAnalyzer.loadFont(fontFamily, options.bold ? 700 : 400);\n if (!font) {\n // Fallback\n const canvas =\n typeof OffscreenCanvas !== 'undefined' ? new OffscreenCanvas(1, 1) : document.createElement('canvas');\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return 0;\n\n const weight = options.bold ? 'bold' : 'normal';\n ctx.font = `${weight} ${fontSize}px ${fontFamily}`;\n return ctx.measureText(text).width;\n }\n\n const overrideMap = new Map<number, number>();\n glyphOverrides.forEach((override) => {\n overrideMap.set(override.charIndex, override.glyphIndex);\n });\n\n const scale = fontSize / font.unitsPerEm;\n return calculateTextWidthWithOverrides(font, text, overrideMap, scale);\n}\n","/**\n * Rich Text Renderer - Multi-line rich text rendering with per-span styling.\n *\n * Handles word wrapping, line splitting, and rendering of styled text spans\n * with support for mixed fonts, sizes, colors, and text decorations.\n */\n\nimport { LINE_HEIGHT_MULTIPLIER } from '../constants.js';\nimport type { TextAlign, CharacterStyle } from '../types/index.js';\nimport { RichText } from '../types/index.js';\nimport type { RenderContext } from './renderer-types.js';\nimport { buildFontString, getFontMetrics } from './text-renderer.js';\n\n/**\n * Wrap rich text spans to fit within a given width, preserving character-level formatting\n *\n * This function handles character-level formatting correctly by:\n * 1. First merging consecutive spans with the same style\n * 2. Finding word boundaries (spaces) across spans\n * 3. Wrapping at word boundaries, not at span boundaries\n */\nexport function wrapRichTextSpans(\n spans: Array<{ text: string; style: CharacterStyle }>,\n maxWidth: number,\n element: {\n fontSize: number;\n fontFamily: string;\n bold?: boolean;\n italic?: boolean;\n }\n): Array<{ text: string; style: CharacterStyle }[]> {\n const ctx = getMeasureContext();\n const lines: Array<{ text: string; style: CharacterStyle }[]> = [];\n\n // Helper to measure a text segment with specific styling\n const measureText = (text: string, style: CharacterStyle): number => {\n const fontSize = style.fontSize || element.fontSize;\n const fontFamily = style.fontFamily || element.fontFamily;\n const bold = style.bold !== undefined ? style.bold : element.bold || false;\n const italic = style.italic !== undefined ? style.italic : element.italic || false;\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n return ctx.measureText(text).width;\n };\n\n // Helper to check if two styles match\n const stylesMatch = (s1: CharacterStyle, s2: CharacterStyle): boolean => {\n return (\n s1.fontSize === s2.fontSize &&\n s1.fontFamily === s2.fontFamily &&\n s1.color === s2.color &&\n s1.bold === s2.bold &&\n s1.italic === s2.italic &&\n s1.underline === s2.underline &&\n s1.strikethrough === s2.strikethrough\n );\n };\n\n // Step 1: Merge consecutive spans with identical styles\n const mergedSpans: Array<{ text: string; style: CharacterStyle }> = [];\n spans.forEach((span) => {\n if (mergedSpans.length > 0 && stylesMatch(mergedSpans[mergedSpans.length - 1].style, span.style)) {\n mergedSpans[mergedSpans.length - 1].text += span.text;\n } else {\n mergedSpans.push({ text: span.text, style: span.style });\n }\n });\n\n // Step 2: Reconstruct text with character positions tracking which span each character belongs to\n let fullText = '';\n const charToSpan: number[] = []; // Maps character index to span index\n mergedSpans.forEach((span, spanIndex) => {\n for (let i = 0; i < span.text.length; i++) {\n fullText += span.text[i];\n charToSpan.push(spanIndex);\n }\n });\n\n // Step 3: Split text into words (by spaces)\n const words: Array<{ text: string; startIdx: number; endIdx: number }> = [];\n let currentWord = '';\n let wordStart = 0;\n\n for (let i = 0; i < fullText.length; i++) {\n const char = fullText[i];\n if (char === ' ') {\n if (currentWord.length > 0) {\n words.push({ text: currentWord, startIdx: wordStart, endIdx: i - 1 });\n currentWord = '';\n }\n // Add space as a separate \"word\"\n words.push({ text: ' ', startIdx: i, endIdx: i });\n } else {\n if (currentWord.length === 0) {\n wordStart = i;\n }\n currentWord += char;\n }\n }\n if (currentWord.length > 0) {\n words.push({ text: currentWord, startIdx: wordStart, endIdx: fullText.length - 1 });\n }\n\n // Step 4: Wrap words into lines\n let currentLine: { text: string; style: CharacterStyle }[] = [];\n let currentLineWidth = 0;\n\n const finishLine = () => {\n if (currentLine.length > 0) {\n lines.push(currentLine);\n currentLine = [];\n currentLineWidth = 0;\n }\n };\n\n words.forEach((word, _wordIdx) => {\n // Build spans for this word from the original merged spans\n const wordSpans: Array<{ text: string; style: CharacterStyle }> = [];\n let currentSpanIdx = charToSpan[word.startIdx];\n let currentText = '';\n\n for (let charIdx = word.startIdx; charIdx <= word.endIdx; charIdx++) {\n const spanIdx = charToSpan[charIdx];\n if (spanIdx !== currentSpanIdx) {\n // Style changed, finish current span\n wordSpans.push({ text: currentText, style: mergedSpans[currentSpanIdx].style });\n currentText = fullText[charIdx];\n currentSpanIdx = spanIdx;\n } else {\n currentText += fullText[charIdx];\n }\n }\n if (currentText.length > 0) {\n wordSpans.push({ text: currentText, style: mergedSpans[currentSpanIdx].style });\n }\n\n // Measure word width\n const wordWidth = wordSpans.reduce((sum, span) => sum + measureText(span.text, span.style), 0);\n\n // Check if we need to wrap\n if (word.text === ' ') {\n // Space character - always add to current line (will be trimmed later based on paragraph boundaries)\n if (currentLine.length > 0) {\n const lastSpan = currentLine[currentLine.length - 1];\n if (stylesMatch(lastSpan.style, wordSpans[0].style)) {\n lastSpan.text += ' ';\n } else {\n currentLine.push({ text: ' ', style: wordSpans[0].style });\n }\n } else {\n // Leading space - preserve it (width calc will trim if not paragraph start)\n currentLine.push({ text: ' ', style: wordSpans[0].style });\n }\n currentLineWidth += wordWidth;\n } else {\n // Regular word\n // Check if word itself is too wide (needs character-level breaking)\n if (wordWidth > maxWidth) {\n // Word is too wide - break it character by character\n // First, finish current line if it has content\n if (currentLine.length > 0) {\n finishLine();\n }\n\n // Break word at character level\n for (const span of wordSpans) {\n const chars = Array.from(span.text);\n let charBuffer = '';\n\n for (const char of chars) {\n const charWidth = measureText(char, span.style);\n\n // Check if adding this character would exceed max width\n if (currentLineWidth + charWidth > maxWidth && currentLineWidth > 0) {\n // Finish current line with buffer\n if (charBuffer.length > 0) {\n if (currentLine.length > 0) {\n const lastSpan = currentLine[currentLine.length - 1];\n if (stylesMatch(lastSpan.style, span.style)) {\n lastSpan.text += charBuffer;\n } else {\n currentLine.push({ text: charBuffer, style: span.style });\n }\n } else {\n currentLine.push({ text: charBuffer, style: span.style });\n }\n charBuffer = '';\n }\n finishLine();\n }\n\n charBuffer += char;\n currentLineWidth += charWidth;\n }\n\n // Add remaining buffer to current line\n if (charBuffer.length > 0) {\n if (currentLine.length > 0) {\n const lastSpan = currentLine[currentLine.length - 1];\n if (stylesMatch(lastSpan.style, span.style)) {\n lastSpan.text += charBuffer;\n } else {\n currentLine.push({ text: charBuffer, style: span.style });\n }\n } else {\n currentLine.push({ text: charBuffer, style: span.style });\n }\n }\n }\n } else {\n // Word fits on a line - check if we need to wrap to fit it\n if (currentLineWidth + wordWidth > maxWidth && currentLine.length > 0) {\n // Don't remove trailing space - it will be hidden during rendering based on paragraph rules\n // Removing it here breaks cursor position mapping\n finishLine();\n }\n\n // Add word spans to current line\n wordSpans.forEach((span) => {\n if (currentLine.length > 0) {\n const lastSpan = currentLine[currentLine.length - 1];\n if (stylesMatch(lastSpan.style, span.style)) {\n lastSpan.text += span.text;\n } else {\n currentLine.push({ ...span });\n }\n } else {\n currentLine.push({ ...span });\n }\n });\n currentLineWidth += wordWidth;\n }\n }\n });\n\n finishLine();\n\n // If no lines were created, return at least one empty line\n if (lines.length === 0) {\n lines.push([]);\n }\n\n return lines;\n}\n\n/**\n * Split rich text into lines by explicit newlines, preserving span formatting\n */\nexport function splitRichTextIntoLines(richText: RichText): Array<{ text: string; style: CharacterStyle }[]> {\n const lines: Array<{ text: string; style: CharacterStyle }[]> = [];\n let currentLine: { text: string; style: CharacterStyle }[] = [];\n\n richText.spans.forEach((span) => {\n // Split span text by newlines\n const parts = span.text.split('\\n');\n\n parts.forEach((part, i) => {\n if (part.length > 0) {\n // Add non-empty text to current line\n currentLine.push({ text: part, style: span.style });\n }\n\n // If this isn't the last part, we hit a newline\n if (i < parts.length - 1) {\n lines.push(currentLine);\n currentLine = [];\n }\n });\n });\n\n // Add the last line if it has content\n if (currentLine.length > 0) {\n lines.push(currentLine);\n }\n\n // If no lines were created, return at least one empty line\n if (lines.length === 0) {\n lines.push([]);\n }\n\n return lines;\n}\n\n/**\n * Render rich text with per-span styling (fill only, no effects)\n * Supports multi-line text with line breaks and word wrapping\n */\nexport function renderRichTextFillOnly(\n ctx: RenderContext,\n richText: RichText,\n element: {\n fontSize: number;\n fontFamily: string;\n color: string;\n bold?: boolean;\n italic?: boolean;\n underline?: boolean;\n strikethrough?: boolean;\n textAlign?: TextAlign;\n },\n maxWidth?: number,\n lockedLineCount?: number\n): void {\n // FIX: Set default fill color at start to prevent inheritance issues\n // This ensures text is rendered in the correct color even if first spans are empty\n ctx.fillStyle = element.color;\n\n const textAlign = element.textAlign || 'center';\n\n // Split into lines by explicit newlines first\n const explicitLines = splitRichTextIntoLines(richText);\n\n // Then apply word wrapping to each line if maxWidth is specified\n // Track paragraph boundaries (which lines are paragraph starts/ends)\n let lines: Array<{ text: string; style: CharacterStyle }[]>;\n let paragraphMetadata: Array<{ isParagraphStart: boolean; isParagraphEnd: boolean }>;\n\n if (maxWidth !== undefined && maxWidth > 0 && !lockedLineCount) {\n // Only wrap if no locked line count (during corner resize, bypass wrapping)\n lines = [];\n paragraphMetadata = [];\n explicitLines.forEach((lineSpans) => {\n const wrappedLines = wrapRichTextSpans(lineSpans, maxWidth, element);\n // Mark first wrapped line as paragraph start, last as paragraph end\n wrappedLines.forEach((wrappedLine, idx) => {\n lines.push(wrappedLine);\n paragraphMetadata.push({\n isParagraphStart: idx === 0,\n isParagraphEnd: idx === wrappedLines.length - 1,\n });\n });\n });\n } else {\n // No wrapping if locked line count is set or maxWidth not specified\n lines = explicitLines;\n // Each explicit line is both a paragraph start and end\n paragraphMetadata = explicitLines.map(() => ({\n isParagraphStart: true,\n isParagraphEnd: true,\n }));\n }\n\n // Calculate metrics for each line\n const lineMetrics: Array<{\n spans: { text: string; style: CharacterStyle }[];\n width: number;\n height: number;\n ascent: number;\n spanWidths: number[];\n isParagraphStart: boolean;\n isParagraphEnd: boolean;\n }> = [];\n\n let maxLineHeight = 0;\n\n lines.forEach((lineSpans, lineIndex) => {\n let lineWidth = 0;\n const spanWidths: number[] = [];\n let lineHeight = 0;\n let lineAscent = 0;\n\n // Get paragraph boundary metadata for space layout rules\n const isParagraphStart = paragraphMetadata[lineIndex].isParagraphStart;\n const isParagraphEnd = paragraphMetadata[lineIndex].isParagraphEnd;\n\n lineSpans.forEach((span) => {\n const fontSize = span.style.fontSize !== undefined ? span.style.fontSize : element.fontSize;\n const fontFamily = span.style.fontFamily !== undefined ? span.style.fontFamily : element.fontFamily;\n const bold = span.style.bold !== undefined ? span.style.bold : element.bold || false;\n const italic = span.style.italic !== undefined ? span.style.italic : element.italic || false;\n\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n const width = ctx.measureText(span.text).width;\n spanWidths.push(width);\n lineWidth += width;\n\n // Get metrics for this span\n const metrics = getFontMetrics(fontSize, fontFamily, bold, italic);\n lineHeight = Math.max(lineHeight, metrics.height);\n lineAscent = Math.max(lineAscent, metrics.ascent);\n });\n\n // Apply space layout rules to line width for alignment calculation\n // Calculate actual rendered width by summing visible characters in each span\n const fullLineText = lineSpans.map((span) => span.text).join('');\n const leadingSpacesMatch = fullLineText.match(/^ +/);\n const trailingSpacesMatch = fullLineText.match(/ +$/);\n const leadingSpacesCount = leadingSpacesMatch ? leadingSpacesMatch[0].length : 0;\n const trailingSpacesCount = trailingSpacesMatch ? trailingSpacesMatch[0].length : 0;\n\n // Calculate adjusted width by measuring only visible characters\n let adjustedLineWidth = 0;\n let charIndexInLine = 0;\n\n lineSpans.forEach((span) => {\n const fontSize = span.style.fontSize !== undefined ? span.style.fontSize : element.fontSize;\n const fontFamily = span.style.fontFamily !== undefined ? span.style.fontFamily : element.fontFamily;\n const bold = span.style.bold !== undefined ? span.style.bold : element.bold || false;\n const italic = span.style.italic !== undefined ? span.style.italic : element.italic || false;\n\n // Determine which part of this span is visible\n let visibleText = span.text;\n const spanStartIndex = charIndexInLine;\n const spanEndIndex = charIndexInLine + span.text.length;\n\n // Check if this span contains leading spaces that should be hidden\n // Leading spaces are hidden when NOT at paragraph start\n if (!isParagraphStart && leadingSpacesCount > 0 && spanStartIndex < leadingSpacesCount) {\n const hiddenCharsInSpan = Math.min(leadingSpacesCount - spanStartIndex, span.text.length);\n visibleText = visibleText.substring(hiddenCharsInSpan);\n }\n\n // Check if this span contains trailing spaces that should be hidden\n // Trailing spaces are hidden when NOT at paragraph end\n if (!isParagraphEnd && trailingSpacesCount > 0) {\n const trailingSpaceStartIndex = fullLineText.length - trailingSpacesCount;\n if (spanEndIndex > trailingSpaceStartIndex) {\n // Calculate how many characters from the START of the already-trimmed visibleText we should keep\n const leadingTrimAmount =\n !isParagraphStart && leadingSpacesCount > 0 && spanStartIndex < leadingSpacesCount\n ? Math.min(leadingSpacesCount - spanStartIndex, span.text.length)\n : 0;\n const visibleCharsInSpan = Math.max(0, trailingSpaceStartIndex - spanStartIndex - leadingTrimAmount);\n visibleText = visibleText.substring(0, visibleCharsInSpan);\n }\n }\n\n charIndexInLine += span.text.length;\n\n // Measure the visible text with this span's font\n if (visibleText.length > 0) {\n ctx.save();\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n adjustedLineWidth += ctx.measureText(visibleText).width;\n ctx.restore();\n }\n });\n\n // CRITICAL FIX: Don't skip whitespace-only lines\n // We need to count them for cursor positions and selection bounds to align\n // Example: \" Foo\" wrapping to [\" \", \"Foo\"] has 2 lines, cursor can be on line 0 or 1\n // Even though line 0 renders as empty (trailing space hidden), it still occupies vertical space\n lineMetrics.push({\n spans: lineSpans,\n width: adjustedLineWidth, // Use adjusted width for alignment (may be 0 for empty lines)\n height: lineHeight,\n ascent: lineAscent,\n spanWidths,\n isParagraphStart, // Store metadata with line\n isParagraphEnd,\n });\n\n maxLineHeight = Math.max(maxLineHeight, lineHeight);\n });\n\n // CRITICAL FIX: Use consistent height calculation with TextMetrics to ensure Fill matches Stroke\n // TextMetrics uses element.fontSize * LINE_HEIGHT_MULTIPLIER for spacing\n // and element font metrics for the height component.\n // We must use the SAME model here to avoid ghosting/misalignment.\n const elementFontMetrics = getFontMetrics(element.fontSize, element.fontFamily, element.bold, element.italic);\n const lineSpacing = element.fontSize * LINE_HEIGHT_MULTIPLIER;\n\n let totalHeight;\n if (lineMetrics.length === 0) {\n totalHeight = 0;\n } else if (lineMetrics.length === 1) {\n // For single line, use element metrics to match stroke\n totalHeight = elementFontMetrics.height;\n } else {\n // For multi-line, match TextMetrics formula\n totalHeight = (lineMetrics.length - 1) * lineSpacing + elementFontMetrics.height;\n }\n\n // Start from top of text block (vertically centered)\n const topLeftY = -totalHeight / 2;\n\n // Render each line\n lineMetrics.forEach((lineData, lineIndex) => {\n const { spans, width: lineWidth, isParagraphStart, isParagraphEnd } = lineData;\n\n // Calculate starting X position based on alignment.\n //\n // The element box is centered at (0, 0), so it spans [-w/2, +w/2]\n // where `w` is the available inner width (`maxWidth`). Aligning\n // text to the *bounding box edges* — not to x=0 — is what the\n // stroke renderer does (StrokeRenderer.ts ~line 342) and what\n // users expect: \"left aligned\" means flush with the left edge of\n // the box, \"right aligned\" means flush with the right edge.\n //\n // Earlier this anchored every alignment at x=0 (center), so for\n // left/right alignment the fill text stayed near center while the\n // stroke moved to the box edges — they diverged visibly. With\n // `maxWidth` undefined (legacy paths without an explicit box) we\n // fall back to the old anchor-at-zero behaviour, since there's no\n // box edge to align to.\n let currentX: number;\n if (textAlign === 'center') {\n currentX = -lineWidth / 2;\n } else if (textAlign === 'right') {\n currentX = maxWidth !== undefined ? maxWidth / 2 - lineWidth : -lineWidth;\n } else {\n // left\n currentX = maxWidth !== undefined ? -maxWidth / 2 : 0;\n }\n\n // Baseline Y position for this line\n // Match TextMetrics positioning: topLeftY + ascent + index * spacing\n const baselineY = topLeftY + elementFontMetrics.ascent + lineIndex * lineSpacing;\n\n // Note: We use elementFontMetrics.ascent for the line position,\n // but individual spans might have different ascents.\n // We align them to this common baseline.\n\n // Apply space layout rules: determine which spaces to hide\n const fullLineText = spans.map((s) => s.text).join('');\n const leadingSpacesMatch = fullLineText.match(/^ +/);\n const trailingSpacesMatch = fullLineText.match(/ +$/);\n const leadingSpacesCount = leadingSpacesMatch ? leadingSpacesMatch[0].length : 0;\n const trailingSpacesCount = trailingSpacesMatch ? trailingSpacesMatch[0].length : 0;\n\n // Track character position in full line to identify spaces\n let charIndexInLine = 0;\n\n // Render each span in this line\n spans.forEach((span) => {\n const fontSize = span.style.fontSize !== undefined ? span.style.fontSize : element.fontSize;\n const fontFamily = span.style.fontFamily !== undefined ? span.style.fontFamily : element.fontFamily;\n // Use explicit undefined check for color (consistent with bold/italic pattern)\n // Falsy check would incorrectly fall back if color is empty string\n const color = span.style.color !== undefined ? span.style.color : element.color;\n const bold = span.style.bold !== undefined ? span.style.bold : element.bold || false;\n const italic = span.style.italic !== undefined ? span.style.italic : element.italic || false;\n const underline = span.style.underline !== undefined ? span.style.underline : element.underline || false;\n const strikethrough = span.style.strikethrough !== undefined ? span.style.strikethrough : element.strikethrough || false;\n\n // Apply space layout rules to this span's text\n let renderText = span.text;\n let spanStartIndex = charIndexInLine;\n let spanEndIndex = charIndexInLine + span.text.length;\n\n // Check if this span contains leading spaces that should be hidden\n // Leading spaces are hidden when NOT at paragraph start\n if (!isParagraphStart && leadingSpacesCount > 0 && spanStartIndex < leadingSpacesCount) {\n // This span overlaps with hidden leading spaces\n const hiddenCharsInSpan = Math.min(leadingSpacesCount - spanStartIndex, span.text.length);\n renderText = renderText.substring(hiddenCharsInSpan);\n }\n\n // Check if this span contains trailing spaces that should be hidden\n // Trailing spaces are hidden when NOT at paragraph end\n if (!isParagraphEnd && trailingSpacesCount > 0) {\n const trailingSpaceStartIndex = fullLineText.length - trailingSpacesCount;\n if (spanEndIndex > trailingSpaceStartIndex) {\n // Calculate how many characters from the START of the already-trimmed renderText we should keep\n const leadingTrimAmount =\n !isParagraphStart && leadingSpacesCount > 0 && spanStartIndex < leadingSpacesCount\n ? Math.min(leadingSpacesCount - spanStartIndex, span.text.length)\n : 0;\n const visibleCharsInSpan = Math.max(0, trailingSpaceStartIndex - spanStartIndex - leadingTrimAmount);\n renderText = renderText.substring(0, visibleCharsInSpan);\n }\n }\n\n charIndexInLine += span.text.length;\n\n // Only render if there's visible text\n if (renderText.length > 0) {\n // Set font and color for this span\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n ctx.fillStyle = color;\n ctx.textBaseline = 'alphabetic';\n ctx.textAlign = 'left'; // We handle alignment manually\n\n // Render this span at the common baseline\n // With textBaseline='alphabetic', canvas automatically aligns text to the baseline\n // All spans should use the SAME Y coordinate for proper baseline alignment\n ctx.fillText(renderText, currentX, baselineY);\n\n // Measure the rendered text width for positioning\n const renderedWidth = ctx.measureText(renderText).width;\n\n // Draw underline if enabled\n if (underline) {\n const underlineY = baselineY + fontSize * 0.1;\n ctx.beginPath();\n ctx.moveTo(currentX, underlineY);\n ctx.lineTo(currentX + renderedWidth, underlineY);\n ctx.lineWidth = Math.max(1, fontSize * 0.05);\n ctx.strokeStyle = color;\n ctx.stroke();\n }\n\n // Draw strikethrough if enabled\n if (strikethrough) {\n const strikethroughY = baselineY - fontSize * 0.25;\n ctx.beginPath();\n ctx.moveTo(currentX, strikethroughY);\n ctx.lineTo(currentX + renderedWidth, strikethroughY);\n ctx.lineWidth = Math.max(1, fontSize * 0.05);\n ctx.strokeStyle = color;\n ctx.stroke();\n }\n\n // Move X position for next span (using rendered width, not original span width)\n currentX += renderedWidth;\n }\n });\n\n // Move Y position for next line\n // currentY += lineHeight; // No longer needed with absolute positioning\n /*\n if (lineIndex < lineMetrics.length - 1) {\n currentY += maxLineHeight * (lineSpacing - 1);\n }\n */\n });\n}\n\n// ============================================================================\n// Private helpers\n// ============================================================================\n\n/** Worker-compatible measurement context (mirrors text-renderer.ts) */\nlet _measureCtx: RenderContext | null = null;\n\nfunction getMeasureContext(): RenderContext {\n if (!_measureCtx) {\n if (typeof OffscreenCanvas !== 'undefined') {\n const canvas = new OffscreenCanvas(1, 1);\n _measureCtx = canvas.getContext('2d') as OffscreenCanvasRenderingContext2D;\n } else if (typeof document !== 'undefined') {\n const canvas = document.createElement('canvas');\n _measureCtx = canvas.getContext('2d')!;\n } else {\n throw new Error('No canvas context available');\n }\n }\n return _measureCtx;\n}\n","/**\n * Text Renderer - Text measurement utilities and plain text rendering functions.\n *\n * Worker-compatible: uses OffscreenCanvas instead of DOM APIs.\n * These functions are NOT duplicates of src/core/TextMetrics.ts — see the note\n * in canvas-renderer.ts for the distinction.\n */\n\nimport { LINE_HEIGHT_MULTIPLIER, HORIZONTAL_PADDING } from '../constants.js';\nimport type {\n AnyElementConfig,\n CustomElementConfig,\n TextAlign,\n OpenTypeFeatures,\n} from '../types/index.js';\nimport { RichText } from '../types/index.js';\nimport {\n renderCircleTransform,\n renderWaveTransform,\n renderArchTransform,\n renderAscendTransform,\n renderLeanTransform,\n renderFlagTransform,\n} from './transform-renderer.js';\nimport { renderTextStroke } from './StrokeRenderer.js';\nimport { calculateVisualBoundsWithSpaceCollapsing } from '../core/TextMetrics.js';\nimport { FontAnalyzer } from '../utils/FontAnalyzer.js';\nimport {\n renderTextWithGlyphOverridesSync,\n renderTextWithOpenTypeFeaturesSync,\n} from '../utils/GlyphRenderer.js';\nimport { createLogger } from '../utils/logger.js';\nimport type { RenderContext, SerializedTextElement } from './renderer-types.js';\nimport { renderRichTextFillOnly } from './rich-text-renderer.js';\n\nconst logger = createLogger('canvas-renderer');\n\n// ============================================================================\n// Text Measurement Utilities (Worker-Compatible)\n// ============================================================================\n//\n// IMPORTANT: These functions are NOT duplicates of src/core/TextMetrics.ts!\n//\n// This file is used in Web Workers (see src/workers/export-worker.ts) which\n// don't have access to the DOM (no `document.createElement`).\n//\n// - **TextMetrics.ts** - Main thread only (uses HTMLCanvasElement via document.createElement)\n// - **text-renderer.ts** - Worker-compatible (uses OffscreenCanvas)\n//\n// Files that run ONLY in main thread (CanvasRenderer.ts, StrokeRenderer.ts, etc.)\n// should import from TextMetrics.ts to avoid confusion.\n//\n// Files that run in WORKERS (this file, transform-renderer.ts) must use these\n// OffscreenCanvas-compatible versions.\n// ============================================================================\n\n// Use a global measurement context that works in both environments\nlet _measureCtx: RenderContext | null = null;\n\n/**\n * Get or create a measurement context\n * Works in both main thread (HTMLCanvasElement) and worker (OffscreenCanvas)\n */\nfunction getMeasureContext(): RenderContext {\n if (!_measureCtx) {\n if (typeof OffscreenCanvas !== 'undefined') {\n // In worker or modern browser\n const canvas = new OffscreenCanvas(1, 1);\n _measureCtx = canvas.getContext('2d') as OffscreenCanvasRenderingContext2D;\n } else if (typeof document !== 'undefined') {\n // In main thread, old browser\n const canvas = document.createElement('canvas');\n _measureCtx = canvas.getContext('2d')!;\n } else {\n throw new Error('No canvas context available');\n }\n }\n return _measureCtx;\n}\n\n/**\n * Build font string with optional bold and italic\n */\nexport function buildFontString(\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false\n): string {\n const parts: string[] = [];\n\n if (italic) {\n parts.push('italic');\n }\n\n if (bold) {\n parts.push('bold');\n }\n\n parts.push(`${fontSize}px`);\n parts.push(fontFamily);\n\n return parts.join(' ');\n}\n\n/**\n * Measure text width\n */\nexport function measureTextWidth(\n text: string,\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false\n): number {\n const ctx = getMeasureContext();\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n return ctx.measureText(text).width;\n}\n\n/**\n * Get font metrics for accurate text positioning\n */\nexport function getFontMetrics(\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false\n): { ascent: number; descent: number; height: number } {\n const ctx = getMeasureContext();\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n\n const sampleText = 'ÁÉÍgjpqy';\n const metrics = ctx.measureText(sampleText);\n\n const ascent = metrics.actualBoundingBoxAscent || fontSize * 0.8;\n const descent = metrics.actualBoundingBoxDescent || fontSize * 0.2;\n const height = ascent + descent;\n\n return { ascent, descent, height };\n}\n\n/**\n * Apply space layout rules to text lines for rendering\n * Hides leading/trailing spaces based on paragraph boundaries to match HTML/CSS text rendering\n *\n * Rules (matching browser text rendering behavior):\n * - Leading spaces are hidden when NOT at paragraph start\n * - Trailing spaces are hidden when NOT at paragraph end\n * - Paragraph = text separated by explicit \\n characters\n *\n * Example:\n * Input: [\"Foo \", \"Bar\"] (from wrapping \"Foo Bar\", no explicit \\n)\n * Output: [\"Foo\", \"Bar\"] (trailing space on line 1 hidden)\n *\n * @param lines - Array of text lines (from wrapText or split by \\n)\n * @param hasExplicitNewlines - Whether original text contained \\n characters\n * @returns Lines with space collapsing applied for rendering\n */\nexport function applySpaceLayoutRules(lines: string[], hasExplicitNewlines: boolean): string[] {\n // If no explicit newlines, all lines are from one paragraph\n // First line = paragraph start, last line = paragraph end\n const paragraphMetadata = lines.map((_, index) => ({\n isParagraphStart: !hasExplicitNewlines && index === 0,\n isParagraphEnd: !hasExplicitNewlines && index === lines.length - 1,\n }));\n\n const collapsedLines = lines.map((line, index) => {\n // Edge case: If line is ALL whitespace, collapse to empty regardless of paragraph position\n // This handles cases like \" Foo Bar\" wrapping to [\" \", \"Foo\", \"Bar\"] where line 1 should be hidden\n if (line.trim().length === 0) {\n return '';\n }\n\n const isParagraphStart = paragraphMetadata[index].isParagraphStart;\n const isParagraphEnd = paragraphMetadata[index].isParagraphEnd;\n\n let visibleText = line;\n\n // Hide leading spaces when NOT at paragraph start\n if (!isParagraphStart) {\n visibleText = visibleText.replace(/^ +/, '');\n }\n\n // Hide trailing spaces when NOT at paragraph end\n if (!isParagraphEnd) {\n visibleText = visibleText.replace(/ +$/, '');\n }\n\n return visibleText;\n });\n\n // CRITICAL: Filter out empty lines to prevent gaps in layout\n // Without this, height calculations and Y positioning include the empty line\n return collapsedLines.filter((line) => line.length > 0);\n}\n\n/**\n * Render text fill only (without transforms or effects)\n * Shared utility used by main rendering and knockout compositing\n *\n * IMPORTANT: This renders ONLY the fill, positioned and transformed correctly\n * It does NOT render strokes, masks, or other effects\n */\nexport function renderTextFillOnly(\n ctx: RenderContext,\n element: {\n text: string;\n fontSize: number;\n fontFamily: string;\n bold?: boolean;\n italic?: boolean;\n textAlign?: TextAlign;\n color: string;\n }\n): void {\n const fontSize = element.fontSize;\n const fontFamily = element.fontFamily;\n const bold = element.bold || false;\n const italic = element.italic || false;\n const textAlign = element.textAlign || 'center';\n const text = element.text;\n\n // Set up font\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n ctx.textAlign = textAlign as CanvasTextAlign;\n ctx.textBaseline = 'alphabetic';\n ctx.fillStyle = element.color;\n\n // Calculate Y position using font metrics for perfect alignment\n const fontMetrics = getFontMetrics(fontSize, fontFamily, bold, italic);\n const visualHeight = fontMetrics.height;\n const topLeftY = -visualHeight / 2;\n const yPos = topLeftY + fontMetrics.ascent;\n\n // Render fill only\n ctx.fillText(text, 0, yPos);\n}\n\n/**\n * Word wrap text to fit within a given width\n */\nexport function wrapText(\n text: string,\n maxWidth: number,\n fontSize: number,\n fontFamily: string,\n bold: boolean = false,\n italic: boolean = false,\n lockedLineCount?: number\n): string[] {\n const ctx = getMeasureContext();\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n\n // If text is all whitespace, return as-is (no wrapping needed)\n if (text.trim().length === 0) {\n return [text];\n }\n\n // Handle explicit newlines: split by \\n first, wrap each line separately, then rejoin\n // This preserves multi-line structure like \" Foo\\nBar\"\n if (text.includes('\\n')) {\n const explicitLines = text.split('\\n');\n const allWrappedLines: string[] = [];\n\n for (let i = 0; i < explicitLines.length; i++) {\n const line = explicitLines[i];\n // Recursively wrap each line (without locked line count, as that applies to the whole text)\n const wrappedLine = wrapText(line, maxWidth, fontSize, fontFamily, bold, italic);\n allWrappedLines.push(...wrappedLine);\n }\n\n return allWrappedLines;\n }\n\n // Preserve leading and trailing spaces to prevent character loss\n // Example: \" Hello World \" -> leadingSpaces=\" \", trimmedText=\"Hello World\", trailingSpaces=\" \"\n const leadingSpacesMatch = text.match(/^\\s*/);\n const trailingSpacesMatch = text.match(/\\s*$/);\n const leadingSpaces = leadingSpacesMatch ? leadingSpacesMatch[0] : '';\n const trailingSpaces = trailingSpacesMatch ? trailingSpacesMatch[0] : '';\n const trimmedText = text.substring(leadingSpaces.length, text.length - trailingSpaces.length);\n\n const words = trimmedText.split(' ');\n\n // If line count is locked (during corner resize), force that exact number of lines\n if (lockedLineCount !== undefined && lockedLineCount > 0) {\n if (lockedLineCount === 1) {\n return [leadingSpaces + words.join(' ') + trailingSpaces];\n }\n\n // Distribute words as evenly as possible across locked line count\n const lines: string[] = [];\n const wordsPerLine = Math.ceil(words.length / lockedLineCount);\n\n for (let i = 0; i < words.length; i += wordsPerLine) {\n const lineWords = words.slice(i, i + wordsPerLine);\n const lineText = lineWords.join(' ');\n\n // Add leading spaces to first line, trailing spaces to last line\n if (i === 0 && i + wordsPerLine >= words.length) {\n // Single line result\n lines.push(leadingSpaces + lineText + trailingSpaces);\n } else if (i === 0) {\n // First line\n lines.push(leadingSpaces + lineText);\n } else if (i + wordsPerLine >= words.length) {\n // Last line\n lines.push(lineText + trailingSpaces);\n } else {\n // Middle lines\n lines.push(lineText);\n }\n }\n\n return lines;\n }\n\n // Normal wrapping logic (no lock)\n // First pass: check if all text fits on one line with generous tolerance\n const allText = words.join(' ');\n const allTextWithSpaces = leadingSpaces + allText + trailingSpaces;\n const allTextWidth = ctx.measureText(allTextWithSpaces).width;\n\n const isSmallSize = maxWidth < 200;\n const wrapTolerancePercent = isSmallSize ? 0.08 : 0.05;\n const wrapTolerance = Math.max(20, maxWidth * wrapTolerancePercent);\n\n if (allTextWidth <= maxWidth + wrapTolerance) {\n return [allTextWithSpaces];\n }\n\n // Otherwise, perform word wrapping\n const lines: string[] = [];\n let currentLine = words[0];\n const lineTolerancePercent = isSmallSize ? 0.05 : 0.03;\n const lineTolerance = Math.max(15, maxWidth * lineTolerancePercent);\n\n for (let i = 1; i < words.length; i++) {\n const word = words[i];\n const testLine = currentLine + ' ' + word;\n const metrics = ctx.measureText(testLine);\n\n if (metrics.width > maxWidth + lineTolerance) {\n lines.push(currentLine);\n currentLine = word;\n } else {\n currentLine = testLine;\n }\n }\n lines.push(currentLine);\n\n // Add leading spaces to first line and trailing spaces to last line\n if (lines.length > 0) {\n lines[0] = leadingSpaces + lines[0];\n lines[lines.length - 1] = lines[lines.length - 1] + trailingSpaces;\n }\n\n return lines;\n}\n\n/**\n * Render multi-line text with alignment and text decorations\n * Works with both CanvasRenderingContext2D and OffscreenCanvasRenderingContext2D\n */\nexport function renderMultilineText(\n ctx: RenderContext,\n lines: string[],\n x: number,\n y: number,\n fontSize: number,\n fontFamily: string,\n alignment: CanvasTextAlign = 'left',\n containerWidth: number = 0,\n horizontalPadding: number = 0,\n lineHeight: number = 1.2,\n bold: boolean = false,\n italic: boolean = false,\n underline: boolean = false,\n strikethrough: boolean = false,\n openTypeFeatures?: OpenTypeFeatures,\n glyphOverrides?: import('../types/index.js').GlyphOverride[]\n): void {\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n ctx.textBaseline = 'alphabetic';\n ctx.textAlign = alignment;\n\n // Helper function to detect PUA characters (U+F0000 to U+FFFFD)\n const containsPUACharacters = (text: string): boolean => {\n for (const char of text) {\n const codePoint = char.codePointAt(0);\n if (codePoint && codePoint >= 0xf0000 && codePoint <= 0xffffd) {\n return true;\n }\n }\n return false;\n };\n\n // Check if we should use fontkit rendering for OpenType features, glyph overrides, OR PUA characters\n const hasOpenTypeFeatures = openTypeFeatures && Object.values(openTypeFeatures).some((enabled) => enabled);\n const hasGlyphOverrides = glyphOverrides && glyphOverrides.length > 0;\n const allText = lines.join(' ');\n const hasPUACharacters = containsPUACharacters(allText);\n const needsFontkitRendering = hasOpenTypeFeatures || hasGlyphOverrides || hasPUACharacters;\n\n const fontkitFont = needsFontkitRendering ? FontAnalyzer.getFontSync(fontFamily, bold ? 700 : 400) : null;\n\n // If we need fontkit rendering but font isn't loaded, try to load it\n // (This shouldn't happen if fonts are pre-loaded properly, but provides a fallback)\n if (needsFontkitRendering && !fontkitFont) {\n logger.warn(\n `[renderMultilineText] Font ${fontFamily} not loaded for fontkit rendering. Text may not render correctly. Has PUA: ${hasPUACharacters}, Text: \"${allText}\"`\n );\n // Try to load it asynchronously for next render\n FontAnalyzer.loadFont(fontFamily, bold ? 700 : 400).catch((err) => {\n logger.error('[renderMultilineText] Error loading font:', err);\n });\n }\n\n const fontMetrics = getFontMetrics(fontSize, fontFamily, bold, italic);\n const lineSpacing = fontSize * lineHeight;\n\n // Calculate character offset for each line (for glyph overrides)\n let charOffset = 0;\n\n lines.forEach((line: string, lineIndex: number) => {\n let xPos = x + horizontalPadding;\n\n if (alignment === 'center') {\n xPos = x + containerWidth / 2;\n } else if (alignment === 'right') {\n xPos = x + containerWidth - horizontalPadding;\n }\n\n const yPos = y + fontMetrics.ascent + lineIndex * lineSpacing;\n\n // Filter glyph overrides for this line\n const lineLength = Array.from(line).length; // Handle multi-byte characters\n const lineGlyphOverrides = hasGlyphOverrides\n ? glyphOverrides!\n .filter((override) => {\n const relativeIndex = override.charIndex - charOffset;\n return relativeIndex >= 0 && relativeIndex < lineLength;\n })\n .map((override) => ({\n ...override,\n charIndex: override.charIndex - charOffset, // Adjust to line-relative index\n }))\n : [];\n\n // Use fontkit rendering if needed and font is loaded\n if (fontkitFont && (hasOpenTypeFeatures || lineGlyphOverrides.length > 0 || hasPUACharacters)) {\n // If we have glyph overrides OR PUA characters for this line, use glyph override rendering\n // (The renderTextWithGlyphOverridesSync function handles both)\n if (lineGlyphOverrides.length > 0 || containsPUACharacters(line)) {\n renderTextWithGlyphOverridesSync(ctx, fontkitFont, line, xPos, yPos, fontSize, lineGlyphOverrides, {\n color: ctx.fillStyle as string,\n align: (alignment === 'start' || alignment === 'end' ? 'left' : alignment) as 'left' | 'right' | 'center',\n });\n }\n // Otherwise use OpenType features rendering\n else if (openTypeFeatures) {\n renderTextWithOpenTypeFeaturesSync(ctx, fontkitFont, line, xPos, yPos, fontSize, openTypeFeatures, {\n color: ctx.fillStyle as string,\n align: (alignment === 'start' || alignment === 'end' ? 'left' : alignment) as 'left' | 'right' | 'center',\n });\n }\n } else {\n ctx.fillText(line, xPos, yPos);\n }\n\n // Update character offset for next line (include line break)\n charOffset += lineLength + 1; // +1 for the line break/space\n\n // Draw underline if enabled\n if (underline) {\n const lineWidth = measureTextWidth(line, fontSize, fontFamily, bold, italic);\n let underlineX = xPos;\n\n if (alignment === 'center') {\n underlineX = xPos - lineWidth / 2;\n } else if (alignment === 'right') {\n underlineX = xPos - lineWidth;\n }\n\n const underlineY = yPos + fontSize * 0.1;\n ctx.beginPath();\n ctx.moveTo(underlineX, underlineY);\n ctx.lineTo(underlineX + lineWidth, underlineY);\n ctx.lineWidth = Math.max(1, fontSize * 0.05);\n ctx.strokeStyle = ctx.fillStyle as string;\n ctx.stroke();\n }\n\n // Draw strikethrough if enabled\n if (strikethrough) {\n const lineWidth = measureTextWidth(line, fontSize, fontFamily, bold, italic);\n let strikethroughX = xPos;\n\n if (alignment === 'center') {\n strikethroughX = xPos - lineWidth / 2;\n } else if (alignment === 'right') {\n strikethroughX = xPos - lineWidth;\n }\n\n const strikethroughY = yPos - fontSize * 0.25;\n ctx.beginPath();\n ctx.moveTo(strikethroughX, strikethroughY);\n ctx.lineTo(strikethroughX + lineWidth, strikethroughY);\n ctx.lineWidth = Math.max(1, fontSize * 0.05);\n ctx.strokeStyle = ctx.fillStyle as string;\n ctx.stroke();\n }\n });\n}\n\n// ============================================================================\n// Element Rendering Functions\n// ============================================================================\n\n/**\n * Render a CustomTransform (straight text) element\n * Pure function that works in both main thread and worker\n */\nexport function renderCustomTransform(ctx: RenderContext, elementData: SerializedTextElement): void {\n // Knockout compositing is handled in CompositingRenderer.ts\n renderCustomTransformNormal(ctx, elementData);\n}\n\nfunction renderCustomTransformNormal(ctx: RenderContext, elementData: SerializedTextElement): void {\n // Render stroke BEFORE fill for outer-stroke effect.\n // Canvas strokeText draws centered on the glyph outline; drawing fill on top\n // covers the inner half, leaving only the outer half visible as an outline.\n if (elementData.stroke?.enabled) {\n renderTextStroke(ctx, elementData as unknown as AnyElementConfig);\n }\n\n ctx.save();\n\n // Translate to center (x, y), rotate\n ctx.translate(elementData.x, elementData.y);\n // Use negative angle for clockwise rotation (matching UI convention)\n ctx.rotate((-elementData.rotation * Math.PI) / 180);\n\n const transformData = elementData.transformData as CustomElementConfig['transformData'];\n const availableWidth = (transformData?.width ?? 100) - HORIZONTAL_PADDING * 2;\n\n // Check if we have rich text\n if (elementData.richText) {\n // Rich text rendering with word wrapping support\n const richText = RichText.fromJSON(elementData.richText);\n\n ctx.save();\n // Position at center\n ctx.translate(0, 0);\n\n // Check for locked line count in element data (set during corner resize)\n const lockedLineCount = elementData._lockedLineCount;\n\n // Render with word wrapping based on available width\n renderRichTextFillOnly(\n ctx,\n richText,\n {\n fontSize: elementData.fontSize,\n fontFamily: elementData.fontFamily,\n color: elementData.color,\n bold: elementData.bold,\n italic: elementData.italic,\n underline: elementData.underline,\n strikethrough: elementData.strikethrough,\n textAlign: elementData.textAlign,\n },\n availableWidth,\n lockedLineCount\n );\n\n ctx.restore();\n } else {\n // Plain text rendering - fall back to old behavior\n if (elementData.text) {\n ctx.fillStyle = elementData.color;\n\n const lockedLineCount = elementData._lockedLineCount;\n\n // CRITICAL: Use calculateVisualBoundsWithSpaceCollapsing with strict: true to match\n // the selection box (getBoundingBox) and stroke rendering (renderTextStroke).\n // This ensures the fill text aligns perfectly with the stroke/highlight.\n const { lines, height: visualHeight } = calculateVisualBoundsWithSpaceCollapsing(\n elementData.text,\n availableWidth,\n elementData.fontSize,\n elementData.fontFamily,\n elementData.bold,\n elementData.italic,\n lockedLineCount,\n true // strict: true\n );\n\n const fontMetrics = getFontMetrics(\n elementData.fontSize,\n elementData.fontFamily,\n elementData.bold,\n elementData.italic\n );\n\n const topLeftX = -(transformData?.width ?? 100) / 2;\n const topLeftY = -visualHeight / 2;\n const lineSpacing = elementData.fontSize * LINE_HEIGHT_MULTIPLIER;\n\n // Render lines with space collapsing logic matching renderTextStroke\n ctx.font = `${elementData.italic ? 'italic ' : ''}${elementData.bold ? 'bold ' : ''}${elementData.fontSize}px ${elementData.fontFamily}`;\n ctx.textBaseline = 'alphabetic';\n ctx.textAlign = elementData.textAlign as CanvasTextAlign;\n\n lines.forEach((line, index) => {\n // Match space collapsing logic from renderTextStroke\n const isParagraphStart = index === 0;\n const isParagraphEnd = index === lines.length - 1;\n\n let visibleText = line;\n\n // Hide leading spaces when NOT at paragraph start\n if (!isParagraphStart) {\n const leadingSpacesMatch = line.match(/^ +/);\n const leadingSpacesCount = leadingSpacesMatch ? leadingSpacesMatch[0].length : 0;\n if (leadingSpacesCount > 0) {\n visibleText = visibleText.substring(leadingSpacesCount);\n }\n }\n\n // Hide trailing spaces when NOT at paragraph end\n if (!isParagraphEnd) {\n const trailingSpacesMatch = visibleText.match(/ +$/);\n const trailingSpacesCount = trailingSpacesMatch ? trailingSpacesMatch[0].length : 0;\n if (trailingSpacesCount > 0) {\n visibleText = visibleText.substring(0, visibleText.length - trailingSpacesCount);\n }\n }\n\n let xPos = topLeftX + HORIZONTAL_PADDING;\n if (elementData.textAlign === 'center') {\n xPos = topLeftX + (transformData?.width ?? 100) / 2;\n } else if (elementData.textAlign === 'right') {\n xPos = topLeftX + (transformData?.width ?? 100) - HORIZONTAL_PADDING;\n }\n\n const yPos = topLeftY + fontMetrics.ascent + index * lineSpacing;\n ctx.fillText(visibleText, xPos, yPos);\n\n // Draw underline/strikethrough if needed (simplified, only if visible text)\n if (visibleText.length > 0) {\n const lineWidth = ctx.measureText(visibleText).width;\n let decorationX = xPos;\n if (elementData.textAlign === 'center') decorationX -= lineWidth / 2;\n else if (elementData.textAlign === 'right') decorationX -= lineWidth;\n\n if (elementData.underline) {\n const underlineY = yPos + elementData.fontSize * 0.1;\n ctx.fillRect(decorationX, underlineY, lineWidth, Math.max(1, elementData.fontSize * 0.05));\n }\n if (elementData.strikethrough) {\n const strikeY = yPos - fontMetrics.ascent * 0.4;\n ctx.fillRect(decorationX, strikeY, lineWidth, Math.max(1, elementData.fontSize * 0.05));\n }\n }\n });\n }\n }\n\n ctx.restore();\n}\n\n/**\n * Render a text element based on its type\n * This is the main entry point for rendering text elements in the worker\n */\nexport function renderTextElement(ctx: RenderContext, elementData: SerializedTextElement): void {\n const elementOpacity = elementData.opacity ?? 1;\n const hasStroke = !!elementData.stroke?.enabled;\n\n // When element has opacity < 1 AND a stroke, render to an offscreen canvas\n // at full opacity, then composite at the element's opacity. This prevents\n // the inner half of the stroke from showing through the semi-transparent fill\n // (canvas strokeText draws centered on the glyph outline).\n //\n // ONLY for 'custom' transforms: non-custom transforms (arch, circle, wave,\n // etc.) render stroke per-character inside the transform renderer, and their\n // main-thread render() method just sets globalAlpha directly without offscreen\n // compositing. Using offscreen here for those types produces a different\n // result (uniform composite vs per-draw globalAlpha), causing export diffs.\n const isCustomTransform = elementData.type === 'custom';\n if (elementOpacity < 1 && hasStroke && isCustomTransform) {\n renderTextElementWithOffscreen(ctx, elementData, elementOpacity);\n return;\n }\n\n // Apply opacity if specified\n const previousAlpha = ctx.globalAlpha;\n if (elementOpacity !== 1) {\n ctx.globalAlpha = elementOpacity;\n }\n\n renderTextElementInner(ctx, elementData);\n\n // Restore previous alpha\n ctx.globalAlpha = previousAlpha;\n}\n\n/**\n * Render text via offscreen canvas to avoid stroke/fill opacity compounding.\n */\nfunction renderTextElementWithOffscreen(\n ctx: RenderContext,\n elementData: SerializedTextElement,\n elementOpacity: number\n): void {\n // Estimate bounds for the offscreen canvas\n const td = elementData.transformData as { width?: number } | undefined;\n const fontSize = elementData.fontSize || 24;\n const strokeWidth = elementData.stroke?.width || 0;\n const estWidth = (td?.width ?? fontSize * elementData.text.length * 0.7) + strokeWidth * 2 + 40;\n const estHeight = fontSize * 3 + strokeWidth * 2 + 40; // generous for multi-line\n\n const transform = ctx.getTransform();\n const scale = Math.max(Math.abs(transform.a), Math.abs(transform.d), 1);\n\n const offW = Math.ceil(estWidth * scale);\n const offH = Math.ceil(estHeight * scale);\n\n let offCanvas: HTMLCanvasElement | OffscreenCanvas;\n if (typeof OffscreenCanvas !== 'undefined') {\n offCanvas = new OffscreenCanvas(offW, offH);\n } else {\n offCanvas = document.createElement('canvas');\n offCanvas.width = offW;\n offCanvas.height = offH;\n }\n\n const offCtx = offCanvas.getContext('2d') as RenderContext;\n if (!offCtx) {\n // Fallback: render directly with compounding\n const prev = ctx.globalAlpha;\n ctx.globalAlpha = elementOpacity;\n renderTextElementInner(ctx, elementData);\n ctx.globalAlpha = prev;\n return;\n }\n\n // Replicate the main canvas transform, shifted so the element center maps\n // to the center of the offscreen canvas\n const centerPixelX = transform.a * elementData.x + transform.e;\n const centerPixelY = transform.d * elementData.y + transform.f;\n\n offCtx.setTransform(\n transform.a, transform.b,\n transform.c, transform.d,\n transform.e - centerPixelX + offW / 2,\n transform.f - centerPixelY + offH / 2,\n );\n\n // Render at full opacity on offscreen\n renderTextElementInner(offCtx, elementData);\n\n // Composite onto main canvas at element opacity\n ctx.save();\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.globalAlpha = elementOpacity;\n ctx.drawImage(offCanvas, centerPixelX - offW / 2, centerPixelY - offH / 2);\n ctx.restore();\n}\n\n/**\n * Inner render dispatch — renders at whatever globalAlpha is currently set.\n */\nfunction renderTextElementInner(ctx: RenderContext, elementData: SerializedTextElement): void {\n // Non-custom transforms handle stroke internally (per-character along the path).\n // Custom transform handles stroke in renderCustomTransformNormal.\n switch (elementData.type) {\n case 'custom':\n renderCustomTransform(ctx, elementData);\n break;\n case 'circle':\n renderCircleTransform(ctx, elementData);\n break;\n case 'wave':\n renderWaveTransform(ctx, elementData);\n break;\n case 'arch':\n renderArchTransform(ctx, elementData);\n break;\n case 'ascend':\n renderAscendTransform(ctx, elementData);\n break;\n case 'lean':\n renderLeanTransform(ctx, elementData);\n break;\n case 'flag':\n renderFlagTransform(ctx, elementData);\n break;\n default:\n logger.warn(`[canvas-renderer] Unsupported transform type: ${elementData.type}`);\n // Fallback: render as CustomTransform\n renderCustomTransform(ctx, elementData);\n }\n}\n","/**\n * ResizeUtils - Common resize logic shared across transforms\n * Eliminates duplication in transform resize() methods\n */\n\nimport { RotationUtils } from './RotationUtils.js';\nimport { MIN_WIDTH } from '../constants.js';\nimport type { TextElement } from './TextElement.js';\nimport type { TransformStartData } from '../types/index.js';\nimport { RichText } from '../types/index.js';\n\n/** Helper to get width from transform data (which has index signature) */\nfunction getTransformWidth(data: Record<string, unknown>): number | undefined {\n return typeof data.width === 'number' ? data.width : undefined;\n}\n\n/** Helper to set width on transform data */\nfunction setTransformWidth(data: Record<string, unknown>, value: number): void {\n data.width = value;\n}\n\n/**\n * Handle corner resize (uniform scaling)\n * Corner handles scale both fontSize and width proportionally\n */\nexport function handleCornerResize(element: TextElement, newWidth: number, startData: TransformStartData) {\n // Use width change only (X-axis in local coordinates) for scale factor\n // This provides intuitive image-like scaling behavior\n // For text elements with transformData.width, use that instead of bbox width to avoid\n // feedback loops from text wrapping changing the bbox dimensions\n const startTransformData = startData.transformData as Record<string, unknown>;\n const startTransformWidth = getTransformWidth(startTransformData);\n const startWidth = startTransformWidth !== undefined ? startTransformWidth : (startData.width ?? 0);\n const scale = newWidth / startWidth;\n\n // Apply uniform scale to font size\n // Always update font size to match the scale - this is critical to prevent\n // text wrapping flicker where width changes but fontSize lags behind\n // At smaller sizes, round to whole pixels for more stability\n const calculatedFontSize = (startData.fontSize ?? element.fontSize) * scale;\n let newFontSize;\n if (calculatedFontSize < 16) {\n // At very small sizes, round to whole pixels\n newFontSize = Math.round(calculatedFontSize);\n } else {\n // At larger sizes, round to 0.5px increments\n newFontSize = Math.round(calculatedFontSize * 2) / 2;\n }\n element.setFontSize(newFontSize);\n\n // ALSO scale all character-level font sizes in rich text\n // Scale from the ORIGINAL startData.richText, not current state\n if (startData.richText && startData.richText instanceof RichText && typeof element.setRichText === 'function') {\n const originalRichText = startData.richText;\n const scaledRichText = originalRichText.clone();\n\n // Scale each span's fontSize by the same scale factor\n for (const span of scaledRichText.spans) {\n if (span.style.fontSize !== undefined) {\n const scaledCharSize = span.style.fontSize * scale;\n // Apply same rounding logic as element-level fontSize\n if (scaledCharSize < 16) {\n span.style.fontSize = Math.round(scaledCharSize);\n } else {\n span.style.fontSize = Math.round(scaledCharSize * 2) / 2;\n }\n }\n }\n // Update the element with scaled rich text\n element.setRichText(scaledRichText);\n }\n\n // Apply uniform scale to width\n const elemTransformData = element.transformData as Record<string, unknown>;\n const currentTransformWidth = getTransformWidth(elemTransformData);\n if (currentTransformWidth !== undefined && startTransformWidth !== undefined) {\n const scaledWidth = startTransformWidth * scale;\n // Round to nearest pixel to avoid flickering during resize (prevents sub-pixel\n // width changes from causing text to wrap/unwrap repeatedly)\n // Use a very small minimum (20px) to allow proportional scaling at small sizes\n const minWidth = 20;\n setTransformWidth(elemTransformData, Math.max(minWidth, Math.round(scaledWidth)));\n }\n\n // Position adjustment is handled by ResizeHandler's fixed corner logic\n // Don't adjust position here to avoid double adjustment\n}\n\n/**\n * Handle side resize (width only, no fontSize change)\n * Side handles only change width while keeping fontSize constant\n */\nexport function handleSideResize(element: TextElement, anchor: string, newWidth: number, startData: TransformStartData) {\n // Only handle left/right side anchors\n if (anchor !== 'middle-left' && anchor !== 'middle-right') {\n return;\n }\n\n // CRITICAL: Do NOT change fontSize for side handles\n element.fontSize = startData.fontSize ?? element.fontSize;\n\n // Update width only\n const elemTransformData = element.transformData as Record<string, unknown>;\n const currentTransformWidth = getTransformWidth(elemTransformData);\n if (currentTransformWidth !== undefined) {\n setTransformWidth(elemTransformData, Math.max(MIN_WIDTH, newWidth));\n\n // For center-based transforms, adjust position to keep opposite edge fixed\n const rotationRad = RotationUtils.toRadiansInverse(element.rotation);\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Calculate the change in width\n const startTransformData = startData.transformData as Record<string, unknown>;\n const startTransformWidth = getTransformWidth(startTransformData);\n const oldWidth = startTransformWidth ?? startData.width ?? 0;\n const newTransformWidth = getTransformWidth(elemTransformData) ?? 0;\n const widthChange = newTransformWidth - oldWidth;\n\n // The center needs to shift by half the width change\n if (anchor === 'middle-left') {\n // Left edge moving, right edge fixed\n element.x = startData.x - (widthChange / 2) * cos;\n element.y = startData.y - (widthChange / 2) * sin;\n } else if (anchor === 'middle-right') {\n // Right edge moving, left edge fixed\n element.x = startData.x + (widthChange / 2) * cos;\n element.y = startData.y + (widthChange / 2) * sin;\n }\n }\n}\n\n/**\n * Standard resize implementation for transforms with width-based containers\n * Handles both corner (uniform scale) and side (width only) handles\n */\nexport function standardResize(element: TextElement, anchor: string, newWidth: number, _newHeight: number, startData: TransformStartData) {\n const isCornerHandle = anchor.includes('top') || anchor.includes('bottom');\n const isSideHandle = anchor.includes('middle');\n\n if (isCornerHandle) {\n handleCornerResize(element, newWidth, startData);\n } else if (isSideHandle) {\n handleSideResize(element, anchor, newWidth, startData);\n }\n}\n","/**\n * CustomTransform - Straight text (simplest transform)\n * This is the baseline implementation that others build upon\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { measureTextWidth, wrapText, calculateVisualBoundsWithSpaceCollapsing, getFontMetrics } from '../core/TextMetrics.js';\nimport { renderCustomTransform, splitRichTextIntoLines, wrapRichTextSpans, type SerializedTextElement } from '../rendering/canvas-renderer.js';\nimport { standardResize } from '../core/ResizeUtils.js';\nimport { MIN_WIDTH, HORIZONTAL_PADDING, LINE_HEIGHT_MULTIPLIER } from '../constants.js';\nimport type { CustomElementConfig, CustomTransformData, BoundingBox, ResizeAnchor, CharacterStyle, TransformStartData } from '../types/index.js';\n\nexport class CustomTransform extends TextElement {\n declare transformData: CustomTransformData;\n _lockedLineCount?: number; // Temporary property used during resize operations only\n\n constructor(config: Partial<CustomElementConfig> = {}) {\n super(config);\n this.transformType = 'custom';\n // _lockedLineCount is not restored from config - it's temporary and not serialized\n\n // Custom transform data: controlPoints and width\n // If no width is provided, auto-fit to text content\n const data = config.transformData;\n const width =\n data?.width !== undefined\n ? data.width\n : Math.max(\n MIN_WIDTH,\n measureTextWidth(this.text, this.fontSize, this.fontFamily, this.bold, this.italic) + HORIZONTAL_PADDING * 2\n );\n\n this.transformData = {\n type: 'custom',\n controlPoints: data?.controlPoints ?? [],\n width: width,\n };\n }\n\n /**\n * Calculate height for rich text considering per-character fonts\n * Returns the total height based on the tallest font in each line\n */\n private calculateRichTextHeight(): number {\n const richText = this.getRichText();\n const availableWidth = this.transformData.width - HORIZONTAL_PADDING * 2;\n\n // Split into lines and wrap\n const explicitLines = splitRichTextIntoLines(richText);\n\n // Build wrapped lines\n const wrappedLines: Array<{ text: string; style: CharacterStyle }[]> = [];\n\n explicitLines.forEach((lineSpans) => {\n const wrapped = wrapRichTextSpans(lineSpans, availableWidth, {\n fontSize: this.fontSize,\n fontFamily: this.fontFamily,\n bold: this.bold,\n italic: this.italic,\n });\n wrapped.forEach((wLine) => {\n wrappedLines.push(wLine);\n });\n });\n\n if (wrappedLines.length === 0) {\n return 0;\n }\n\n // Calculate max font height across all spans to get proper total height\n // This ensures the bounding box encompasses all characters regardless of font size\n let maxFontHeight = 0;\n let maxAscent = 0;\n\n wrappedLines.forEach((lineSpans) => {\n lineSpans.forEach((span) => {\n const spanFontSize = span.style.fontSize !== undefined ? span.style.fontSize : this.fontSize;\n const spanFontFamily = span.style.fontFamily !== undefined ? span.style.fontFamily : this.fontFamily;\n const spanBold = span.style.bold !== undefined ? span.style.bold : this.bold;\n const spanItalic = span.style.italic !== undefined ? span.style.italic : this.italic;\n\n const metrics = getFontMetrics(spanFontSize, spanFontFamily, spanBold, spanItalic);\n maxFontHeight = Math.max(maxFontHeight, metrics.height);\n maxAscent = Math.max(maxAscent, metrics.ascent);\n });\n });\n\n // Use element-level line spacing for consistency with rendering\n const lineSpacing = this.fontSize * LINE_HEIGHT_MULTIPLIER;\n\n // Calculate total height\n // The rendering positions lines at lineSpacing intervals, using element-level font metrics\n // But individual characters can extend beyond this if they have larger fonts\n // We need to account for:\n // 1. The space taken by line positioning: (numLines - 1) * lineSpacing\n // 2. The height of the tallest font (to ensure all characters fit)\n if (wrappedLines.length === 1) {\n return maxFontHeight;\n } else {\n // For multi-line: standard line spacing + room for tallest font\n const elementMetrics = getFontMetrics(this.fontSize, this.fontFamily, this.bold, this.italic);\n const standardHeight = (wrappedLines.length - 1) * lineSpacing + elementMetrics.height;\n\n // Check if any span extends beyond the standard height\n // The extra height needed is the difference between max ascent and element ascent\n // (since taller fonts extend upward from the baseline)\n const extraAscent = Math.max(0, maxAscent - elementMetrics.ascent);\n const extraDescent = Math.max(0, (maxFontHeight - maxAscent) - (elementMetrics.height - elementMetrics.ascent));\n\n return standardHeight + extraAscent + extraDescent;\n }\n }\n\n /**\n * Check if rich text has per-character formatting that differs from element defaults\n */\n private hasPerCharacterFormatting(): boolean {\n const richText = this.getRichText();\n\n for (const span of richText.spans) {\n // Check if any span has explicit fontSize or fontFamily that differs from element defaults\n if (span.style.fontSize !== undefined && span.style.fontSize !== this.fontSize) {\n return true;\n }\n if (span.style.fontFamily !== undefined && span.style.fontFamily !== this.fontFamily) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Get bounding box in world coordinates\n * For straight text with center-based positioning (x, y is the center)\n * This should match the actual text bounds including wrapping\n */\n getBoundingBox(): BoundingBox {\n const availableWidth = this.transformData.width - HORIZONTAL_PADDING * 2;\n\n // Check if we have per-character formatting that affects height\n let height: number;\n if (this.hasPerCharacterFormatting()) {\n // Use rich text calculation for per-character fonts\n height = this.calculateRichTextHeight();\n } else {\n // Use standard calculation for uniform text\n const result = calculateVisualBoundsWithSpaceCollapsing(\n this.text,\n availableWidth,\n this.fontSize,\n this.fontFamily,\n this.bold,\n this.italic,\n this._lockedLineCount,\n true // strict: wrap exactly at maxWidth\n );\n height = result.height;\n }\n\n return {\n x: this.x - this.transformData.width / 2,\n y: this.y - height / 2,\n width: this.transformData.width,\n height: height,\n };\n }\n\n /**\n * Get visual bounding box (container bounds for selection display)\n * For CustomTransform, this shows the container width (transformData.width)\n * and the actual height based on text wrapping\n * (x, y) is the center of the element\n */\n getVisualBoundingBox(): BoundingBox {\n const availableWidth = this.transformData.width - HORIZONTAL_PADDING * 2;\n\n // Check if we have per-character formatting that affects height\n let visualHeight: number;\n if (this.hasPerCharacterFormatting()) {\n // Use rich text calculation for per-character fonts\n visualHeight = this.calculateRichTextHeight();\n } else {\n // Use standard calculation for uniform text\n const result = calculateVisualBoundsWithSpaceCollapsing(\n this.text,\n availableWidth,\n this.fontSize,\n this.fontFamily,\n this.bold,\n this.italic,\n this._lockedLineCount,\n true // strict: wrap exactly at maxWidth\n );\n visualHeight = result.height;\n }\n\n // Use the container width (transformData.width) for the visual box\n // This shows the resizable container, not just the text\n const visualWidth = this.transformData.width;\n\n // Return container bounds (centered at x, y)\n return {\n x: this.x - visualWidth / 2,\n y: this.y - visualHeight / 2,\n width: visualWidth,\n height: visualHeight,\n };\n }\n\n /**\n * Get rotation anchor (center of the visual bounding box)\n * CustomTransform rotates around the center of the actual text, including multi-line\n */\n getRotationAnchor(): { x: number; y: number } {\n const visualBbox = this.getVisualBoundingBox();\n return {\n x: visualBbox.x + visualBbox.width / 2,\n y: visualBbox.y + visualBbox.height / 2,\n };\n }\n\n /**\n * Render the straight text\n * (x, y) is the center of the element, we rotate around this center point\n * Now uses shared rendering function that works in both main thread and worker\n */\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n // Selection and hover visuals are handled by SelectionRenderer and HoverRenderer.\n // No inline outline drawing needed here.\n\n const elementOpacity = this.opacity ?? 1;\n const hasStroke = !!this.stroke?.enabled;\n\n // When element has opacity < 1 AND a stroke, render to an offscreen canvas\n // at full opacity to prevent stroke/fill compounding (same fix as ShapeElement).\n if (elementOpacity < 1 && hasStroke) {\n this.renderWithOffscreen(ctx, elementOpacity);\n return;\n }\n\n // Apply element opacity\n const previousAlpha = ctx.globalAlpha;\n if (elementOpacity !== 1) {\n ctx.globalAlpha = elementOpacity;\n }\n\n // Render text (fill + stroke handled inside renderCustomTransform)\n const serialized = this.toJSON();\n renderCustomTransform(ctx, serialized as unknown as SerializedTextElement);\n\n // Restore previous alpha\n ctx.globalAlpha = previousAlpha;\n }\n\n /**\n * Render stroke+fill to an offscreen canvas at full opacity, then composite\n * at the element's opacity. Prevents stroke inner half showing through fill.\n */\n private renderWithOffscreen(ctx: CanvasRenderingContext2D, elementOpacity: number): void {\n const strokeWidth = this.stroke?.width || 0;\n const padding = strokeWidth + 20;\n const bbox = this.getVisualBoundingBox();\n const offW = Math.ceil(bbox.width + padding * 2);\n const offH = Math.ceil(bbox.height + padding * 2);\n\n const offCanvas = document.createElement('canvas');\n offCanvas.width = offW;\n offCanvas.height = offH;\n const offCtx = offCanvas.getContext('2d');\n if (!offCtx) {\n // Fallback: render directly with compounding\n const prev = ctx.globalAlpha;\n ctx.globalAlpha = elementOpacity;\n const serialized = this.toJSON();\n renderCustomTransform(ctx, serialized as unknown as SerializedTextElement);\n ctx.globalAlpha = prev;\n return;\n }\n\n // Shift transforms so the element renders centered on the offscreen canvas\n const offsetX = bbox.x + bbox.width / 2;\n const offsetY = bbox.y + bbox.height / 2;\n\n offCtx.translate(offW / 2 - offsetX, offH / 2 - offsetY);\n\n // Render at full opacity\n const serialized = this.toJSON();\n renderCustomTransform(offCtx, serialized as unknown as SerializedTextElement);\n\n // Composite onto main canvas at element opacity\n ctx.save();\n ctx.globalAlpha = elementOpacity;\n ctx.drawImage(offCanvas, offsetX - offW / 2, offsetY - offH / 2);\n ctx.restore();\n }\n\n /**\n * Override getTransformStartData to capture initial line count\n */\n override getTransformStartData(): TransformStartData {\n const startData = super.getTransformStartData();\n\n // Capture current line count at current width\n // This calculates how the text is CURRENTLY wrapping (without any lock from previous resizes)\n // because _lockedLineCount is not serialized and won't be present on the element\n const availableWidth = this.transformData.width - HORIZONTAL_PADDING * 2;\n const lines = wrapText(this.text, availableWidth, this.fontSize, this.fontFamily, this.bold, this.italic);\n startData.lineCount = lines.length;\n\n return startData;\n }\n\n /**\n * Handle resize using standard resize logic\n * Corner handles: uniform scale (fontSize and width both change) - line count locked\n * Side handles: only width changes - text re-wraps naturally\n */\n resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): void {\n // Lock should already be set in startResize() for corner handles\n // For side handles, clear the lock to allow natural re-wrapping\n const isCornerHandle =\n anchor === 'top-left' || anchor === 'top-right' || anchor === 'bottom-left' || anchor === 'bottom-right';\n\n if (!isCornerHandle) {\n // Clear lock for side handles (which should allow text to re-wrap)\n this._lockedLineCount = undefined;\n }\n\n // Perform the standard resize\n standardResize(this, anchor, newWidth, newHeight, startData);\n\n // CRITICAL FIX: After resize, if we have a lock, ensure width is sufficient for the locked line count\n // This prevents the text from wrapping to more lines when the lock is cleared\n if (this._lockedLineCount !== undefined && this._lockedLineCount > 0) {\n // Calculate the actual text width (all words on one line if locked to 1 line)\n const fullTextWidth = measureTextWidth(this.text, this.fontSize, this.fontFamily, this.bold, this.italic);\n\n // Calculate minimum width needed to fit text on the locked line count\n // For 1 line: need full text width + padding (no extra buffer for proper alignment with images)\n // For multiple lines: use proportional estimate\n const minWidth = fullTextWidth / this._lockedLineCount + HORIZONTAL_PADDING * 2;\n\n // Ensure width is never below minimum\n if (this.transformData.width < minWidth) {\n this.transformData.width = Math.ceil(minWidth);\n }\n }\n }\n\n /**\n * Override setText - don't auto-adjust width\n * Let text wrap naturally within the current container width\n */\n setText(newText: string): void {\n super.setText(newText);\n // Don't auto-adjust width - maintain current container width\n // This allows text to wrap to multiple lines as needed\n\n // Clear locked line count when text content changes\n this._lockedLineCount = undefined;\n }\n\n /**\n * Get enabled anchors\n * Straight text supports all 8 handles\n */\n getEnabledAnchors(): ResizeAnchor[] {\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right', 'middle-left', 'middle-right'];\n }\n\n /**\n * Clone this element\n * Override to preserve temporary _lockedLineCount during resize operations\n * (but it still won't be saved in JSON exports)\n */\n clone(): CustomTransform {\n const cloned = super.clone() as CustomTransform;\n\n // Preserve the lock state during cloning (for smooth resize)\n // This won't be saved in JSON because toJSON() doesn't include it\n if (this._lockedLineCount !== undefined) {\n cloned._lockedLineCount = this._lockedLineCount;\n }\n\n return cloned;\n }\n\n /**\n * Serialize with transform data\n * NOTE: _lockedLineCount is intentionally NOT serialized\n * The lock is temporary during resize and should not persist after\n */\n toJSON(): CustomElementConfig {\n const json = {\n ...super.toJSON(),\n transformType: 'custom' as const,\n transformData: {\n type: 'custom' as const,\n controlPoints: this.transformData.controlPoints,\n width: this.transformData.width,\n },\n };\n\n // _lockedLineCount is NOT included in serialization\n // This ensures the lock doesn't persist after a resize operation ends\n\n return json;\n }\n}\n\nexport default CustomTransform;\n","/**\n * Geometry utilities for text element transformations\n * Ported from shared/utils.js and enhanced for custom canvas\n */\n\nimport { RotationUtils } from './RotationUtils.js';\nimport { ROTATION_HANDLE_DISTANCE } from '../constants.js';\nimport type { BoundingBox, Point, ResizeAnchor } from '../types/index.js';\n\n/**\n * Calculate position to keep opposite corner fixed during resize\n * This is the core algorithm that makes corner dragging feel natural\n *\n * @param {Object} startData - Transform start state {x, y, width, height}\n * @param {Object} newDimensions - New dimensions {width, height}\n * @param {string} activeAnchor - Which corner is being dragged\n * @param {number} rotation - Rotation in degrees\n * @returns {Object} New position {x, y}\n */\nexport function calculateFixedCornerPosition(startData: { x: number; y: number; width: number; height: number }, newDimensions: { width: number; height: number }, activeAnchor: string, rotation: number) {\n const rotationRad = RotationUtils.toRadians(rotation);\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Determine which corner should stay fixed (in local coords at START)\n let fixedLocalX = 0;\n let fixedLocalY = 0;\n\n if (activeAnchor === 'top-left') {\n // Fix bottom-right\n fixedLocalX = startData.width;\n fixedLocalY = startData.height;\n } else if (activeAnchor === 'top-right') {\n // Fix bottom-left\n fixedLocalX = 0;\n fixedLocalY = startData.height;\n } else if (activeAnchor === 'bottom-left') {\n // Fix top-right\n fixedLocalX = startData.width;\n fixedLocalY = 0;\n } else if (activeAnchor === 'bottom-right') {\n // Fix top-left\n fixedLocalX = 0;\n fixedLocalY = 0;\n }\n\n // Calculate the fixed corner's position in world coordinates (from start of transform)\n const fixedWorldX = startData.x + (fixedLocalX * cos - fixedLocalY * sin);\n const fixedWorldY = startData.y + (fixedLocalX * sin + fixedLocalY * cos);\n\n // Where that corner is NOW in the new dimensions\n let newFixedLocalX = 0;\n let newFixedLocalY = 0;\n\n if (activeAnchor === 'top-left') {\n newFixedLocalX = newDimensions.width;\n newFixedLocalY = newDimensions.height;\n } else if (activeAnchor === 'top-right') {\n newFixedLocalX = 0;\n newFixedLocalY = newDimensions.height;\n } else if (activeAnchor === 'bottom-left') {\n newFixedLocalX = newDimensions.width;\n newFixedLocalY = 0;\n } else if (activeAnchor === 'bottom-right') {\n newFixedLocalX = 0;\n newFixedLocalY = 0;\n }\n\n // Calculate new position so the fixed corner stays in the same world position\n const newX = fixedWorldX - (newFixedLocalX * cos - newFixedLocalY * sin);\n const newY = fixedWorldY - (newFixedLocalX * sin + newFixedLocalY * cos);\n\n return { x: newX, y: newY };\n}\n\n/**\n * Calculate rotation handle position\n * Positioned below the bottom edge, centered horizontally\n * Distance scales inversely with zoom to maintain consistent screen space distance\n *\n * @param {Object} bbox - Bounding box {x, y, width, height}\n * @param {number} rotation - Rotation in DEGREES (not radians - function converts internally)\n * @param {Object} rotationAnchor - Point to rotate around {x, y}\n * @param {number} zoom - Current zoom level (default 1.0)\n * @returns {Object} Handle position {x, y}\n *\n * IMPORTANT: This function expects rotation in DEGREES and converts to radians internally.\n * Do NOT pass radians to this function or you'll get incorrect positioning.\n * Example: Pass 30 for 30°, NOT 0.524 radians\n */\nexport function calculateRotationHandlePosition(bbox: BoundingBox, rotation: number, rotationAnchor: Point, zoom: number = 1.0) {\n // Local coordinates relative to bbox top-left: center of bottom edge + distance\n // Scale distance inversely with zoom to maintain consistent screen space distance\n // At zoom=0.33: distance = 70 / 0.33 = 212px world → 70px screen\n const localX = bbox.width / 2;\n const localY = bbox.height + (ROTATION_HANDLE_DISTANCE / zoom);\n\n // World position before rotation\n const worldX = bbox.x + localX;\n const worldY = bbox.y + localY;\n\n // Rotate around the rotation anchor\n const rotationRad = RotationUtils.toRadians(rotation);\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Translate to rotation anchor, rotate, translate back\n const dx = worldX - rotationAnchor.x;\n const dy = worldY - rotationAnchor.y;\n\n const rotatedX = dx * cos - dy * sin;\n const rotatedY = dx * sin + dy * cos;\n\n return {\n x: rotationAnchor.x + rotatedX,\n y: rotationAnchor.y + rotatedY,\n };\n}\n\n/**\n * Calculate all 8 resize handle positions\n *\n * @param {Object} bbox - Bounding box {x, y, width, height}\n * @param {number} rotation - Rotation in degrees\n * @param {Object} rotationAnchor - Point to rotate around {x, y}\n * @returns {Object[]} Array of handles with {type, anchor, x, y, cursor}\n */\nexport function calculateResizeHandles(bbox: BoundingBox, rotation: number, rotationAnchor: Point) {\n const rotationRad = RotationUtils.toRadians(rotation);\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Transform a local point (relative to bbox top-left) to world coordinates\n // Then rotate around the rotation anchor\n const transform = (localX: number, localY: number) => {\n // World position before rotation\n const worldX = bbox.x + localX;\n const worldY = bbox.y + localY;\n\n // Translate to rotation anchor, rotate, translate back\n const dx = worldX - rotationAnchor.x;\n const dy = worldY - rotationAnchor.y;\n\n const rotatedX = dx * cos - dy * sin;\n const rotatedY = dx * sin + dy * cos;\n\n return {\n x: rotationAnchor.x + rotatedX,\n y: rotationAnchor.y + rotatedY,\n };\n };\n\n const { width, height } = bbox;\n\n // Helper to create a handle with cursor based on world position\n const createHandle = (type: string, anchor: ResizeAnchor, localX: number, localY: number) => {\n const worldPos = transform(localX, localY);\n const cursor = getCursorForWorldPosition(\n worldPos.x,\n worldPos.y,\n rotationAnchor.x,\n rotationAnchor.y,\n type,\n rotation,\n anchor\n );\n return {\n type,\n anchor,\n ...worldPos,\n cursor,\n };\n };\n\n return [\n // Corner handles\n createHandle('corner', 'top-left', 0, 0),\n createHandle('corner', 'top-right', width, 0),\n createHandle('corner', 'bottom-left', 0, height),\n createHandle('corner', 'bottom-right', width, height),\n // Side handles\n createHandle('edge', 'middle-left', 0, height / 2),\n createHandle('edge', 'middle-right', width, height / 2),\n createHandle('edge', 'middle-top', width / 2, 0),\n createHandle('edge', 'middle-bottom', width / 2, height),\n ];\n}\n\n/**\n * Test if a point is inside a circle\n */\nexport function hitTestCircle(px: number, py: number, cx: number, cy: number, radius: number): boolean {\n const dx = px - cx;\n const dy = py - cy;\n return dx * dx + dy * dy <= radius * radius;\n}\n\n/**\n * Test if a point is inside a rotated rectangle\n */\nexport function hitTestRect(px: number, py: number, rect: BoundingBox, rotation: number): boolean {\n // Transform point to local coordinates\n // Positive rotation to undo the negative forward rotation\n const rotationRad = RotationUtils.toRadiansInverse(rotation); // Inverse rotation\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Translate to origin\n const translatedX = px - rect.x;\n const translatedY = py - rect.y;\n\n // Rotate\n const localX = translatedX * cos - translatedY * sin;\n const localY = translatedX * sin + translatedY * cos;\n\n // Test against axis-aligned box\n return localX >= 0 && localX <= rect.width && localY >= 0 && localY <= rect.height;\n}\n\n/**\n * Measure text width using canvas API\n * @deprecated Use TextMetrics.measureTextWidth instead\n */\nexport function measureTextWidth(\n text: string,\n fontSize: number,\n fontFamily: string = 'Arial',\n bold: boolean = false,\n italic: boolean = false\n): number {\n // Re-export from TextMetrics for backwards compatibility\n // This will be removed in a future version\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d')!;\n const weight = bold ? 'bold' : 'normal';\n const style = italic ? 'italic' : 'normal';\n ctx.font = `${style} ${weight} ${fontSize}px ${fontFamily}`;\n return ctx.measureText(text).width;\n}\n\n/**\n * Calculate bounding box center\n */\nexport function getBoundingBoxCenter(bbox: BoundingBox) {\n return {\n x: bbox.x + bbox.width / 2,\n y: bbox.y + bbox.height / 2,\n };\n}\n\n/**\n * Calculate angle between two points\n */\nexport function calculateAngle(centerX: number, centerY: number, pointX: number, pointY: number): number {\n return Math.atan2(pointY - centerY, pointX - centerX);\n}\n\n/**\n * Rotate a point around a center\n */\nexport function rotatePoint(px: number, py: number, cx: number, cy: number, angle: number) {\n const cos = Math.cos(angle);\n const sin = Math.sin(angle);\n const dx = px - cx;\n const dy = py - cy;\n\n return {\n x: cx + (dx * cos - dy * sin),\n y: cy + (dx * sin + dy * cos),\n };\n}\n\n/**\n * Calculate distance between two points\n */\nexport function distance(x1: number, y1: number, x2: number, y2: number): number {\n const dx = x2 - x1;\n const dy = y2 - y1;\n return Math.sqrt(dx * dx + dy * dy);\n}\n\n/**\n * Calculate distance from a point to a horizontal line\n */\nexport function distanceToHorizontalLine(_px: number, py: number, lineY: number): number {\n return Math.abs(py - lineY);\n}\n\n/**\n * Calculate distance from a point to a vertical line\n */\nexport function distanceToVerticalLine(px: number, _py: number, lineX: number): number {\n return Math.abs(px - lineX);\n}\n\n/**\n * Calculate distance from a point to a line segment\n * @param {number} px - Point X\n * @param {number} py - Point Y\n * @param {number} x1 - Line start X\n * @param {number} y1 - Line start Y\n * @param {number} x2 - Line end X\n * @param {number} y2 - Line end Y\n * @returns {number} Distance to line segment\n */\nexport function distanceToLineSegment(px: number, py: number, x1: number, y1: number, x2: number, y2: number): number {\n const lineLength = distance(x1, y1, x2, y2);\n if (lineLength === 0) return distance(px, py, x1, y1);\n\n // Calculate parameter t that determines closest point on line\n const t = Math.max(0, Math.min(1, ((px - x1) * (x2 - x1) + (py - y1) * (y2 - y1)) / (lineLength * lineLength)));\n\n // Calculate closest point on line segment\n const closestX = x1 + t * (x2 - x1);\n const closestY = y1 + t * (y2 - y1);\n\n return distance(px, py, closestX, closestY);\n}\n\n/**\n * Check if two values are within a threshold\n * Useful for snapping detection\n */\nexport function isNear(value1: number, value2: number, threshold: number): boolean {\n return Math.abs(value1 - value2) <= threshold;\n}\n\n/**\n * Get the correct resize cursor accounting for rotation\n * @param {string} anchor - Handle anchor position (e.g., 'top-left', 'top', 'right')\n * @param {number} rotation - Rotation in degrees\n * @returns {string} CSS cursor value\n */\nexport function getRotatedResizeCursor(anchor: string, rotation: number): string {\n // Base cursor angles for each anchor (in degrees, 0 = east)\n const baseCursorAngles: Record<string, number> = {\n top: -90, // ns-resize (north-south)\n bottom: -90, // ns-resize\n left: 0, // ew-resize (east-west)\n right: 0, // ew-resize\n 'top-left': -45, // nwse-resize (northwest-southeast)\n 'bottom-right': -45, // nwse-resize\n 'top-right': 45, // nesw-resize (northeast-southwest)\n 'bottom-left': 45, // nesw-resize\n };\n\n // Get base angle for this anchor\n const baseAngle = baseCursorAngles[anchor] || 0;\n\n // Add rotation to get final angle\n let finalAngle = baseAngle + rotation;\n\n // Normalize to 0-180 range (cursors repeat every 180 degrees)\n finalAngle = ((finalAngle % 180) + 180) % 180;\n\n // Map angle ranges to cursor types\n // Each cursor covers a 45-degree range\n if (finalAngle >= 157.5 || finalAngle < 22.5) {\n return 'ew-resize';\n } else if (finalAngle >= 22.5 && finalAngle < 67.5) {\n return 'nwse-resize';\n } else if (finalAngle >= 67.5 && finalAngle < 112.5) {\n return 'ns-resize';\n } else if (finalAngle >= 112.5 && finalAngle < 157.5) {\n return 'nesw-resize';\n }\n\n return 'default';\n}\n\n/**\n * Get cursor for a handle based on its world position relative to a center point\n * @param {number} handleWorldX - Handle X position in world coordinates\n * @param {number} handleWorldY - Handle Y position in world coordinates\n * @param {number} centerWorldX - Center X position in world coordinates\n * @param {number} centerWorldY - Center Y position in world coordinates\n * @param {string} handleType - 'corner' or 'edge'\n * @param {number} rotation - Element rotation in degrees\n * @param {string} anchor - Handle anchor position (e.g., 'top-left', 'bottom-right')\n * @returns {string} CSS cursor value\n */\nexport function getCursorForWorldPosition(\n handleWorldX: number,\n handleWorldY: number,\n centerWorldX: number,\n centerWorldY: number,\n handleType: string,\n rotation: number = 0,\n anchor: string = ''\n): string {\n // Calculate angle from center to handle in world space\n const dx = handleWorldX - centerWorldX;\n const dy = handleWorldY - centerWorldY;\n\n // Calculate angle in degrees (0 = east, 90 = south, 180 = west, 270 = north)\n let angle = Math.atan2(dy, dx) * (180 / Math.PI);\n\n // Normalize to 0-360\n angle = ((angle % 360) + 360) % 360;\n\n if (handleType === 'corner') {\n // For non-rotated elements (rotation = 0), always show diagonal cursors for corners\n // This provides more intuitive UX even for wide/tall elements where the angle\n // calculation would otherwise suggest horizontal/vertical cursors\n const isUnrotated = Math.abs(rotation) < 0.1; // Within 0.1 degrees of zero\n if (isUnrotated) {\n // Map corner anchors to their diagonal cursors\n if (anchor === 'top-left' || anchor === 'bottom-right') {\n return 'nwse-resize';\n } else if (anchor === 'top-right' || anchor === 'bottom-left') {\n return 'nesw-resize';\n }\n }\n\n // For rotated elements, calculate cursor based on actual angle\n // Determine which quadrant and return appropriate diagonal cursor\n // nwse-resize: top-left to bottom-right (NW-SE diagonal, ~315-45° and ~135-225°)\n // nesw-resize: top-right to bottom-left (NE-SW diagonal, ~45-135° and ~225-315°)\n\n // Normalize to 0-180 for cursor selection (cursors repeat every 180°)\n const normalizedAngle = angle % 180;\n\n // Map to cursor based on angle ranges\n if (normalizedAngle >= 157.5 || normalizedAngle < 22.5) {\n // East/West: horizontal resize\n return 'ew-resize';\n } else if (normalizedAngle >= 22.5 && normalizedAngle < 67.5) {\n // Southeast/Northwest diagonal\n return 'nwse-resize';\n } else if (normalizedAngle >= 67.5 && normalizedAngle < 112.5) {\n // South/North: vertical resize\n return 'ns-resize';\n } else {\n // Southwest/Northeast diagonal\n return 'nesw-resize';\n }\n } else {\n // For edges, determine if it's more horizontal or vertical\n const normalizedAngle = angle % 180;\n\n if (normalizedAngle >= 45 && normalizedAngle < 135) {\n // More vertical\n return 'ns-resize';\n } else {\n // More horizontal\n return 'ew-resize';\n }\n }\n}\n\n/**\n * Canvas Zoom Utilities\n *\n * CRITICAL: Canvas transforms affect visual properties differently:\n * - Line widths ARE affected by canvas transforms (scale with zoom)\n * - Font sizes in ctx.font are NOT affected by canvas transforms\n * - Dash patterns ARE affected by canvas transforms\n *\n * Use these utilities to maintain consistent visual appearance at all zoom levels.\n */\n\n/**\n * Extract the current canvas scale factor from the transform matrix.\n * This accounts for zoom, DPR (device pixel ratio), and any other scaling.\n *\n * @param ctx - Canvas rendering context\n * @returns Current scale factor (e.g., 0.33 for 33% zoom, 1.0 for 100%)\n *\n * @example\n * const scale = getCanvasScale(ctx); // 0.33 at 33% zoom\n * ctx.lineWidth = 3 / scale; // Renders as 3px visually at any zoom\n */\nexport function getCanvasScale(ctx: CanvasRenderingContext2D): number {\n // Fallback for test environments where getTransform() might not be available\n if (!ctx.getTransform) {\n return 1.0;\n }\n\n const transform = ctx.getTransform();\n // Calculate magnitude of the transform matrix (Euclidean norm of the first column)\n // This gives us the overall scale factor regardless of rotation\n return Math.sqrt(transform.a * transform.a + transform.b * transform.b);\n}\n\n/**\n * Set a line width that maintains consistent visual thickness at all zoom levels.\n *\n * Canvas transforms affect line widths, so a 3px line at 33% zoom appears as 1px.\n * This function applies inverse scaling to counteract the zoom effect.\n *\n * @param ctx - Canvas rendering context\n * @param desiredWidth - The visual width you want (in screen pixels)\n *\n * @example\n * // Selection border that's always 3px thick on screen\n * setZoomInvariantLineWidth(ctx, 3);\n * ctx.stroke();\n */\nexport function setZoomInvariantLineWidth(ctx: CanvasRenderingContext2D, desiredWidth: number): void {\n const scale = getCanvasScale(ctx);\n ctx.lineWidth = scale > 0 ? desiredWidth / scale : desiredWidth;\n}\n\n/**\n * Set a line dash pattern that maintains consistent visual appearance at all zoom levels.\n *\n * Canvas transforms affect dash patterns, so [8, 8] at 33% zoom appears as [2.64, 2.64].\n * This function applies inverse scaling to counteract the zoom effect.\n *\n * @param ctx - Canvas rendering context\n * @param pattern - The dash pattern you want (in screen pixels), e.g., [8, 8] for \"8px dash, 8px gap\"\n *\n * @example\n * // Dotted guide line that's always [8, 8] pattern on screen\n * setZoomInvariantLineWidth(ctx, 3);\n * setZoomInvariantDash(ctx, [8, 8]);\n * ctx.stroke();\n */\nexport function setZoomInvariantDash(ctx: CanvasRenderingContext2D, pattern: number[]): void {\n const scale = getCanvasScale(ctx);\n ctx.setLineDash(scale > 0 ? pattern.map(value => value / scale) : pattern);\n}\n\n/**\n * Helper to set both line width and dash pattern with zoom-invariant values.\n * Commonly used for selection visualization and guide lines.\n *\n * @param ctx - Canvas rendering context\n * @param width - Desired line width (in screen pixels)\n * @param dashPattern - Desired dash pattern (in screen pixels)\n *\n * @example\n * // Dotted guide line for selected arch text\n * ctx.strokeStyle = getThemeAccentColor();\n * setZoomInvariantStroke(ctx, 3, [8, 8]);\n * ctx.beginPath();\n * // ... draw path ...\n * ctx.stroke();\n * ctx.setLineDash([]); // Reset to solid\n */\nexport function setZoomInvariantStroke(\n ctx: CanvasRenderingContext2D,\n width: number,\n dashPattern: number[] = []\n): void {\n const scale = getCanvasScale(ctx);\n ctx.lineWidth = scale > 0 ? width / scale : width;\n if (dashPattern.length > 0) {\n ctx.setLineDash(scale > 0 ? dashPattern.map(value => value / scale) : dashPattern);\n }\n}\n\n/**\n * Set a dash pattern that scales with viewport size (appears longer when zoomed out).\n *\n * This creates viewport-adaptive dashes that maintain better visibility when viewing\n * a larger area of the canvas. The dashes are specified in world-space, so they scale\n * naturally with the zoom level:\n * - At 100% zoom: dashes appear at base size\n * - At 33% zoom (larger viewport): dashes appear smaller on screen but proportional to viewport\n * - At 300% zoom (zoomed in): dashes appear larger on screen but proportional to viewport\n *\n * @param ctx - Canvas rendering context\n * @param basePattern - Dash pattern in world-space pixels\n *\n * @example\n * // Guide line with world-space dashes (use larger values than screen-space)\n * setZoomInvariantLineWidth(ctx, 3); // Keep line width consistent\n * setViewportProportionalDash(ctx, [30, 15]); // Use larger values for visibility\n * ctx.stroke();\n */\nexport function setViewportProportionalDash(ctx: CanvasRenderingContext2D, basePattern: number[]): void {\n // Use pattern directly in world-space - canvas transform scales it naturally\n // Larger base values ensure visibility when zoomed out\n ctx.setLineDash(basePattern);\n}\n\n/**\n * Set both line width and dash pattern with hybrid scaling.\n * Line width stays zoom-invariant (consistent screen thickness).\n * Dash pattern uses world-space values (scales with viewport).\n *\n * This is the recommended approach for guide lines and selection visualization,\n * providing consistent visual thickness while maintaining proportional dash spacing.\n *\n * @param ctx - Canvas rendering context\n * @param width - Desired line width (in screen pixels, zoom-invariant)\n * @param basePattern - Dash pattern in world-space pixels (use larger values than screen-space)\n *\n * @example\n * // Circle transform guide: consistent thickness, viewport-proportional dashes\n * ctx.strokeStyle = getThemeAccentColor();\n * setViewportProportionalStroke(ctx, 3, [30, 15]); // 30px dashes in world-space\n * ctx.arc(0, 0, radius, 0, Math.PI * 2);\n * ctx.stroke();\n * ctx.setLineDash([]); // Reset\n */\nexport function setViewportProportionalStroke(\n ctx: CanvasRenderingContext2D,\n width: number,\n basePattern: number[] = []\n): void {\n const scale = getCanvasScale(ctx);\n ctx.lineWidth = scale > 0 ? width / scale : width; // Zoom-invariant width\n if (basePattern.length > 0) {\n ctx.setLineDash(basePattern); // Viewport-proportional dashes (world-space)\n }\n}\n","/**\n * CircleTransform - Text follows a circular path\n * Ported from EditableTextPath.jsx circle mode\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport {\n measureTextWidth,\n calculateFixedCornerPosition,\n} from '../core/GeometryUtils.js';\nimport { renderCircleTransform, type SerializedTextTransformElement } from '../rendering/transform-renderer.js';\nimport type { CircleElementConfig, CircleTransformData, BoundingBox, ResizeAnchor, TransformStartData } from '../types/index.js';\n\nexport class CircleTransform extends TextElement {\n declare transformData: CircleTransformData;\n\n constructor(config: Partial<CircleElementConfig> = {}) {\n super(config);\n this.transformType = 'circle';\n\n // Circle transform data: center point and radius point\n // Position (x, y) is the center of the circle\n const data = config.transformData;\n this.transformData = {\n type: 'circle',\n radius: data?.radius ?? 100,\n scale: data?.scale ?? 1,\n reverse: data?.reverse ?? false,\n };\n }\n\n /**\n * Get bounding box in world coordinates\n * For circle, this is center ± radius (diameter)\n * This is used for transform math (resize, rotation)\n */\n getBoundingBox(): BoundingBox {\n const effectiveRadius = this.transformData.radius * this.transformData.scale;\n const diameter = effectiveRadius * 2;\n\n return {\n x: this.x - effectiveRadius,\n y: this.y - effectiveRadius,\n width: diameter,\n height: diameter,\n };\n }\n\n /**\n * Get visual bounding box (tight fit around actual text)\n * For circular text, calculate the arc bounds where text actually appears\n */\n getVisualBoundingBox(): BoundingBox {\n const effectiveRadius = this.transformData.radius * this.transformData.scale;\n const effectiveFontSize = this.fontSize * this.transformData.scale;\n\n // Measure text to know the arc span\n const actualTextWidth = measureTextWidth(this.text, effectiveFontSize, this.fontFamily, this.bold, this.italic);\n const arcAngle = actualTextWidth / effectiveRadius; // Radians\n\n // Text is centered at top (-PI/2), calculate bounds\n // The text extends arcAngle/2 on each side\n const startAngle = -Math.PI / 2 - arcAngle / 2;\n const endAngle = -Math.PI / 2 + arcAngle / 2;\n\n // Find min/max x and y for the text arc\n // Include some padding for character height (extend radius outward)\n // If circle is too small for the font, clamp innerRadius to a small positive value\n const innerRadius =\n effectiveRadius > effectiveFontSize / 2 ? effectiveRadius - effectiveFontSize / 2 : effectiveRadius * 0.1; // Use 10% of radius as minimum\n const outerRadius = effectiveRadius + effectiveFontSize / 2;\n\n let minX = Infinity,\n maxX = -Infinity;\n let minY = Infinity,\n maxY = -Infinity;\n\n // Sample points along the arc\n const samples = 10;\n for (let i = 0; i <= samples; i++) {\n const angle = startAngle + (endAngle - startAngle) * (i / samples);\n\n // Inner and outer points\n const innerX = this.x + Math.cos(angle) * innerRadius;\n const innerY = this.y + Math.sin(angle) * innerRadius;\n const outerX = this.x + Math.cos(angle) * outerRadius;\n const outerY = this.y + Math.sin(angle) * outerRadius;\n\n minX = Math.min(minX, innerX, outerX);\n maxX = Math.max(maxX, innerX, outerX);\n minY = Math.min(minY, innerY, outerY);\n maxY = Math.max(maxY, innerY, outerY);\n }\n\n return {\n x: minX,\n y: minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n /**\n * Render the circular text\n * Uses canvas arc path like SVG path in original\n * Now uses shared rendering function\n */\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n // Selection and hover visuals are handled by SelectionRenderer and HoverRenderer.\n\n // Apply element opacity\n const previousAlpha = ctx.globalAlpha;\n if (this.opacity !== undefined && this.opacity !== 1) {\n ctx.globalAlpha = this.opacity;\n }\n\n // Use shared rendering function for the text\n renderCircleTransform(ctx, this.toJSON() as unknown as SerializedTextTransformElement);\n\n // Restore previous alpha\n ctx.globalAlpha = previousAlpha;\n }\n\n /**\n * Handle resize\n * For circle, we maintain uniform scale and keep scale on the transform\n */\n resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): void {\n const circleStartData = startData.transformData as unknown as CircleTransformData;\n\n // Calculate uniform scale from average of width/height change\n const avgDimension = (newWidth + newHeight) / 2;\n const startAvgDimension = (startData.width! + startData.height!) / 2;\n const scaleChange = avgDimension / startAvgDimension;\n\n // Apply scale change relative to the starting scale\n const newScale = circleStartData.scale * scaleChange;\n\n // Clamp scale\n const clampedScale = Math.max(0.1, Math.min(10, newScale));\n this.transformData.scale = clampedScale;\n\n // Calculate new position to keep opposite corner fixed\n const bbox = this.getBoundingBox();\n const newPosition = calculateFixedCornerPosition(\n {\n x: startData.x - circleStartData.radius * circleStartData.scale,\n y: startData.y - circleStartData.radius * circleStartData.scale,\n width: circleStartData.radius * 2 * circleStartData.scale,\n height: circleStartData.radius * 2 * circleStartData.scale,\n },\n { width: bbox.width, height: bbox.height },\n anchor,\n this.rotation\n );\n\n if (newPosition) {\n // Position is top-left of bbox, but we need center\n this.x = newPosition.x + bbox.width / 2;\n this.y = newPosition.y + bbox.height / 2;\n }\n }\n\n /**\n * Get enabled anchors\n * Circle supports only corner handles (uniform scale)\n */\n getEnabledAnchors(): ResizeAnchor[] {\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n /**\n * Get effective font size (base fontSize * scale)\n * This is what the user sees\n */\n getEffectiveFontSize(): number {\n return Math.round(this.fontSize * this.transformData.scale * 10) / 10;\n }\n\n /**\n * Set effective font size (adjusts base fontSize to achieve desired effective size)\n */\n setEffectiveFontSize(targetSize: number): void {\n if (Math.abs(this.transformData.scale - 1) > 0.01) {\n // Calculate what base fontSize should be\n const newBaseFontSize = targetSize / this.transformData.scale;\n this.setFontSize(newBaseFontSize);\n } else {\n // Scale is 1, just set directly\n this.setFontSize(targetSize);\n }\n }\n\n /**\n * Serialize with transform data\n */\n toJSON(): CircleElementConfig {\n return {\n ...super.toJSON(),\n transformType: 'circle',\n transformData: {\n type: 'circle',\n radius: this.transformData.radius,\n scale: this.transformData.scale,\n reverse: this.transformData.reverse,\n },\n };\n }\n\n /**\n * Get transform start data\n */\n override getTransformStartData(): TransformStartData {\n const bbox = this.getBoundingBox();\n return {\n id: this.id,\n x: this.x,\n y: this.y,\n width: bbox.width,\n height: bbox.height,\n fontSize: this.fontSize,\n rotation: this.rotation,\n transformData: {\n type: 'circle',\n radius: this.transformData.radius,\n scale: this.transformData.scale,\n reverse: this.transformData.reverse,\n },\n };\n }\n}\n\nexport default CircleTransform;\n","/**\n * ArchTransform - Text curves upward in an arc\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { measureTextWidth } from '../core/GeometryUtils.js';\nimport { renderArchTransform, type SerializedTextTransformElement } from '../rendering/transform-renderer.js';\nimport type { ArchElementConfig, ArchTransformData, BoundingBox, ResizeAnchor, TransformStartData } from '../types/index.js';\n\nexport class ArchTransform extends TextElement {\n declare transformData: ArchTransformData;\n\n constructor(config: Partial<ArchElementConfig> = {}) {\n super(config);\n this.transformType = 'arch';\n\n // Arch transform data: archHeight controls the curve (-1 to 1)\n // Positive = arch up, Negative = arch down\n const data = config.transformData;\n this.transformData = {\n type: 'arch',\n archHeight: data?.archHeight ?? 0.5,\n width: data?.width ?? 200,\n };\n }\n\n getBoundingBox(): BoundingBox {\n const archHeightAbs = Math.abs(this.transformData.archHeight);\n const height = this.fontSize * (1.2 + archHeightAbs);\n\n // Positive arch goes upward (negative y direction)\n // Negative arch goes downward (positive y direction)\n const yOffset = this.transformData.archHeight > 0 ? -height : 0;\n\n return {\n x: this.x - this.transformData.width / 2,\n y: this.y + yOffset,\n width: this.transformData.width,\n height: height,\n };\n }\n\n getVisualBoundingBox(): BoundingBox {\n // For arch, we need to calculate bounds that encompass all skewed characters\n // We simulate the rendering to find the actual min/max positions\n const chars = this.text.split('');\n const charWidths = chars.map((char) => measureTextWidth(char, this.fontSize, this.fontFamily));\n const totalWidth = charWidths.reduce((sum, w) => sum + w, 0);\n const radius = this.transformData.width / 2;\n\n const skewFactor = 0.3;\n\n // Font metrics for character height\n const fontHeight = this.fontSize;\n const halfHeight = fontHeight / 2;\n\n let minX = Infinity;\n let maxX = -Infinity;\n let minY = Infinity;\n let maxY = -Infinity;\n\n // Simulate character positioning and skew transformation\n let currentX = -totalWidth / 2;\n chars.forEach((_char, i) => {\n const charWidth = charWidths[i];\n const centerX = currentX + charWidth / 2;\n const normalizedX = centerX / radius; // -1 to 1\n\n // Calculate y position on parabola\n const lineY = (Math.pow(normalizedX, 2) - 1) * this.transformData.archHeight * this.fontSize;\n\n // Calculate the slope of the curve at this point for skew\n const slope = 2 * normalizedX * this.transformData.archHeight;\n\n // Character corners before skew (relative to character center)\n const corners = [\n { x: -charWidth / 2, y: -halfHeight }, // top-left\n { x: charWidth / 2, y: -halfHeight }, // top-right\n { x: -charWidth / 2, y: halfHeight }, // bottom-left\n { x: charWidth / 2, y: halfHeight }, // bottom-right\n ];\n\n // Apply skew transformation: [1, slope*skewFactor, 0, 1] and translate\n corners.forEach((corner) => {\n // Skew transform: x' = x, y' = y + slope*skewFactor*x\n const skewedX = corner.x;\n const skewedY = corner.y + slope * skewFactor * corner.x;\n\n // Translate to world position\n const worldX = centerX + skewedX;\n const worldY = lineY + skewedY;\n\n minX = Math.min(minX, worldX);\n maxX = Math.max(maxX, worldX);\n minY = Math.min(minY, worldY);\n maxY = Math.max(maxY, worldY);\n });\n\n currentX += charWidth;\n });\n\n // Add a small padding for safety\n const padding = 2;\n minX -= padding;\n maxX += padding;\n minY -= padding;\n maxY += padding;\n\n return {\n x: this.x + minX,\n y: this.y + minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n // Selection and hover visuals are handled by SelectionRenderer and HoverRenderer.\n\n // Apply element opacity\n const previousAlpha = ctx.globalAlpha;\n if (this.opacity !== undefined && this.opacity !== 1) {\n ctx.globalAlpha = this.opacity;\n }\n\n // Use shared rendering function for the text\n renderArchTransform(ctx, this.toJSON() as unknown as SerializedTextTransformElement);\n\n // Restore previous alpha\n ctx.globalAlpha = previousAlpha;\n }\n\n resize(anchor: ResizeAnchor, newWidth: number, _newHeight: number, startData: TransformStartData): void {\n const isCornerHandle = anchor.includes('top') || anchor.includes('bottom');\n const isSideHandle = anchor.includes('middle');\n const archStartData = startData.transformData as ArchTransformData;\n\n if (isCornerHandle) {\n // Corner handle: uniform scale based on width change only (X-axis in local coords)\n //\n // Design decision: We use ONLY the width change (X-axis in local coordinates) to determine\n // the scale factor. This provides intuitive image-like scaling behavior where:\n // - Horizontal mouse movement controls the scale\n // - Aspect ratio is maintained (font size scales proportionally with width)\n // - The dragged corner follows the mouse cursor precisely\n //\n // For arch text, both fontSize and arch width scale together uniformly\n const scale = newWidth / startData.width!;\n\n // Apply uniform scale to font size\n const newFontSize = startData.fontSize! * scale;\n this.setFontSize(newFontSize);\n\n // Apply uniform scale to arch width\n const scaledWidth = archStartData.width * scale;\n this.transformData.width = Math.max(50, scaledWidth);\n\n // Position adjustment is handled by ResizeHandler's fixed corner logic\n // Don't adjust position here to avoid double adjustment\n } else if (isSideHandle) {\n // Side handle: only width changes (fontSize MUST stay the same)\n if (anchor === 'middle-left' || anchor === 'middle-right') {\n // CRITICAL: Do NOT change fontSize for side handles\n // Ensure fontSize stays exactly as it was\n this.fontSize = startData.fontSize!;\n\n // Update width only\n this.transformData.width = Math.max(50, newWidth);\n\n // For arch text, position (x,y) is at the CENTER of the arch (see render method)\n // When resizing with side handles, we want to keep the OPPOSITE edge fixed\n // This means the center point needs to move by half the width change\n // Use bbox width change, not transformData width change\n const deltaWidth = newWidth - startData.width!;\n const rotationRad = (this.rotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n if (anchor === 'middle-left') {\n // Left edge is moving, right edge should stay fixed\n // Center moves by -deltaWidth/2 (moves left in local coords)\n const centerShift = -deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n } else if (anchor === 'middle-right') {\n // Right edge is moving, left edge should stay fixed\n // Center moves by +deltaWidth/2 (moves right in local coords)\n const centerShift = deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n }\n }\n // Note: middle-top and middle-bottom don't affect arch dimensions\n // since height is determined by fontSize and archHeight\n }\n }\n\n getEnabledAnchors(): ResizeAnchor[] {\n // Arch supports only corner handles (no side handles)\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n toJSON(): ArchElementConfig {\n return {\n ...super.toJSON(),\n transformType: 'arch',\n transformData: {\n type: 'arch',\n archHeight: this.transformData.archHeight,\n width: this.transformData.width,\n },\n };\n }\n\n override getTransformStartData(): TransformStartData {\n const bbox = this.getBoundingBox();\n return {\n ...super.getTransformStartData(),\n width: bbox.width,\n height: bbox.height,\n transformData: {\n type: 'arch',\n archHeight: this.transformData.archHeight,\n width: this.transformData.width,\n },\n };\n }\n}\n\nexport default ArchTransform;\n","/**\n * Transform Default Values\n * Single source of truth for all transform default parameters\n */\n\n// Wave Transform Defaults\nexport const WAVE_DEFAULTS = {\n amplitude: 0.2, // 20% - subtle wave effect\n frequency: 1.2, // Smooth curve\n width: 200,\n} as const;\n\n// Flag Transform Defaults\nexport const FLAG_DEFAULTS = {\n amplitude: 0.2, // 20% - subtle flag wave\n frequency: 2, // Standard flag frequency\n width: 200,\n} as const;\n\n// Arch Transform Defaults\nexport const ARCH_DEFAULTS = {\n archHeight: 0.5, // 50% - centered arch\n width: 200,\n} as const;\n\n// Circle Transform Defaults\nexport const CIRCLE_DEFAULTS = {\n radius: 100,\n reverse: false,\n} as const;\n\n// Lean Transform Defaults\nexport const LEAN_DEFAULTS = {\n leanAmount: 0, // 0 - no lean by default\n width: 200,\n} as const;\n\n// Ascend Transform Defaults\nexport const ASCEND_DEFAULTS = {\n ascendAngle: -15, // -15° - nice upward ascent\n width: 200,\n} as const;\n\n// Custom Transform Defaults\nexport const CUSTOM_DEFAULTS = {\n width: 200,\n} as const;\n","/**\n * WaveTransform - Text follows a sine wave pattern\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { measureTextWidth } from '../core/GeometryUtils.js';\nimport { standardResize } from '../core/ResizeUtils.js';\nimport { renderWaveTransform, type SerializedTextTransformElement } from '../rendering/transform-renderer.js';\nimport { WAVE_SKEW_FACTOR } from '../constants.js';\nimport { WAVE_DEFAULTS } from './defaults.js';\nimport type { WaveElementConfig, WaveTransformData, BoundingBox, ResizeAnchor, TransformStartData } from '../types/index.js';\n\nexport class WaveTransform extends TextElement {\n declare transformData: WaveTransformData;\n\n constructor(config: Partial<WaveElementConfig> = {}) {\n super(config);\n this.transformType = 'wave';\n\n // Wave transform data: amplitude and frequency parameters\n const data = config.transformData;\n this.transformData = {\n type: 'wave',\n amplitude: data?.amplitude ?? WAVE_DEFAULTS.amplitude,\n frequency: data?.frequency ?? WAVE_DEFAULTS.frequency,\n width: data?.width ?? WAVE_DEFAULTS.width,\n };\n }\n\n getBoundingBox(): BoundingBox {\n const amplitudeAbs = Math.abs(this.transformData.amplitude);\n const height = this.fontSize * (1.2 + amplitudeAbs * 2);\n\n return {\n x: this.x - this.transformData.width / 2,\n y: this.y - height / 2,\n width: this.transformData.width,\n height: height,\n };\n }\n\n getVisualBoundingBox(): BoundingBox {\n // For wave, we need to calculate bounds that encompass all skewed characters\n const chars = this.text.split('');\n const charWidths = chars.map((char) => measureTextWidth(char, this.fontSize, this.fontFamily));\n const totalWidth = charWidths.reduce((sum, w) => sum + w, 0);\n\n // Font metrics for character height\n const fontHeight = this.fontSize;\n const halfHeight = fontHeight / 2;\n\n let minX = Infinity;\n let maxX = -Infinity;\n let minY = Infinity;\n let maxY = -Infinity;\n\n // Simulate character positioning and skew transformation\n let currentX = -totalWidth / 2;\n chars.forEach((_char, i) => {\n const charWidth = charWidths[i];\n const centerX = currentX + charWidth / 2;\n const normalizedX = centerX / (this.transformData.width / 2); // -1 to 1\n\n // Calculate y position on sine wave\n const lineY =\n this.transformData.amplitude * this.fontSize * Math.sin(this.transformData.frequency * Math.PI * normalizedX);\n\n // Calculate the slope of the sine wave at this point for skew\n const slope =\n this.transformData.amplitude *\n this.transformData.frequency *\n Math.PI *\n Math.cos(this.transformData.frequency * Math.PI * normalizedX);\n\n // Character corners before skew (relative to character center)\n const corners = [\n { x: -charWidth / 2, y: -halfHeight }, // top-left\n { x: charWidth / 2, y: -halfHeight }, // top-right\n { x: -charWidth / 2, y: halfHeight }, // bottom-left\n { x: charWidth / 2, y: halfHeight }, // bottom-right\n ];\n\n // Apply skew transformation: [1, slope*WAVE_SKEW_FACTOR, 0, 1] and translate\n corners.forEach((corner) => {\n // Skew transform: x' = x, y' = y + slope*WAVE_SKEW_FACTOR*x\n const skewedX = corner.x;\n const skewedY = corner.y + slope * WAVE_SKEW_FACTOR * corner.x;\n\n // Translate to world position\n const worldX = centerX + skewedX;\n const worldY = lineY + skewedY;\n\n minX = Math.min(minX, worldX);\n maxX = Math.max(maxX, worldX);\n minY = Math.min(minY, worldY);\n maxY = Math.max(maxY, worldY);\n });\n\n currentX += charWidth;\n });\n\n // Add a small padding for safety\n const padding = 2;\n minX -= padding;\n maxX += padding;\n minY -= padding;\n maxY += padding;\n\n return {\n x: this.x + minX,\n y: this.y + minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n // Selection and hover visuals are handled by SelectionRenderer and HoverRenderer.\n\n // Apply element opacity\n const previousAlpha = ctx.globalAlpha;\n if (this.opacity !== undefined && this.opacity !== 1) {\n ctx.globalAlpha = this.opacity;\n }\n\n // Use shared rendering function for the text\n renderWaveTransform(ctx, this.toJSON() as unknown as SerializedTextTransformElement);\n\n // Restore previous alpha\n ctx.globalAlpha = previousAlpha;\n }\n\n resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): void {\n standardResize(this, anchor, newWidth, newHeight, startData);\n }\n\n getEnabledAnchors(): ResizeAnchor[] {\n // Wave supports only corner handles (no side handles)\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n /**\n * Serialize with transform data\n */\n toJSON(): WaveElementConfig {\n return {\n ...super.toJSON(),\n transformType: 'wave',\n transformData: {\n type: 'wave',\n amplitude: this.transformData.amplitude,\n frequency: this.transformData.frequency,\n width: this.transformData.width,\n },\n };\n }\n}\n\nexport default WaveTransform;\n","/**\n * FlagTransform - Text waves from the center point outward like a flag\n * Similar to wave but amplitude increases from center to edges\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { measureTextWidth } from '../core/GeometryUtils.js';\nimport { renderFlagTransform, type SerializedTextTransformElement } from '../rendering/transform-renderer.js';\nimport { FLAG_DEFAULTS } from './defaults.js';\nimport type { FlagElementConfig, FlagTransformData, BoundingBox, ResizeAnchor, TransformStartData } from '../types/index.js';\n\nexport class FlagTransform extends TextElement {\n declare transformData: FlagTransformData;\n\n constructor(config: Partial<FlagElementConfig> = {}) {\n super(config);\n this.transformType = 'flag';\n\n // Flag transform data: amplitude and frequency parameters\n // Amplitude increases from center to edges\n const data = config.transformData;\n this.transformData = {\n type: 'flag',\n amplitude: data?.amplitude ?? FLAG_DEFAULTS.amplitude,\n frequency: data?.frequency ?? FLAG_DEFAULTS.frequency,\n width: data?.width ?? FLAG_DEFAULTS.width,\n };\n }\n\n getBoundingBox(): BoundingBox {\n const amplitudeAbs = Math.abs(this.transformData.amplitude);\n const height = this.fontSize * (1.2 + amplitudeAbs * 2);\n\n return {\n x: this.x - this.transformData.width / 2,\n y: this.y - height / 2,\n width: this.transformData.width,\n height: height,\n };\n }\n\n getVisualBoundingBox(): BoundingBox {\n // For flag, we need to calculate bounds that encompass all skewed characters\n const chars = this.text.split('');\n const charWidths = chars.map((char) => measureTextWidth(char, this.fontSize, this.fontFamily));\n const totalWidth = charWidths.reduce((sum, w) => sum + w, 0);\n\n const skewFactor = 0.3;\n\n // Font metrics for character height\n const fontHeight = this.fontSize;\n const halfHeight = fontHeight / 2;\n\n let minX = Infinity;\n let maxX = -Infinity;\n let minY = Infinity;\n let maxY = -Infinity;\n\n // Simulate character positioning and skew transformation\n let currentX = -totalWidth / 2;\n chars.forEach((_char, i) => {\n const charWidth = charWidths[i];\n const centerX = currentX + charWidth / 2;\n const normalizedX = centerX / (this.transformData.width / 2); // -1 to 1\n\n // Flag effect: amplitude increases from center to edges\n const distanceFromCenter = Math.abs(normalizedX);\n const flagAmplitude = this.transformData.amplitude * distanceFromCenter;\n\n // Calculate y position on flag wave\n const lineY = flagAmplitude * this.fontSize * Math.sin(this.transformData.frequency * Math.PI * normalizedX);\n\n // Calculate the slope for skew\n const cosComponent =\n flagAmplitude *\n this.transformData.frequency *\n Math.PI *\n Math.cos(this.transformData.frequency * Math.PI * normalizedX);\n const sinComponent =\n this.transformData.amplitude *\n Math.sign(normalizedX) *\n Math.sin(this.transformData.frequency * Math.PI * normalizedX);\n const slope = cosComponent + sinComponent;\n\n // Character corners before skew (relative to character center)\n const corners = [\n { x: -charWidth / 2, y: -halfHeight }, // top-left\n { x: charWidth / 2, y: -halfHeight }, // top-right\n { x: -charWidth / 2, y: halfHeight }, // bottom-left\n { x: charWidth / 2, y: halfHeight }, // bottom-right\n ];\n\n // Apply skew transformation: [1, slope*skewFactor, 0, 1] and translate\n corners.forEach((corner) => {\n // Skew transform: x' = x, y' = y + slope*skewFactor*x\n const skewedX = corner.x;\n const skewedY = corner.y + slope * skewFactor * corner.x;\n\n // Translate to world position\n const worldX = centerX + skewedX;\n const worldY = lineY + skewedY;\n\n minX = Math.min(minX, worldX);\n maxX = Math.max(maxX, worldX);\n minY = Math.min(minY, worldY);\n maxY = Math.max(maxY, worldY);\n });\n\n currentX += charWidth;\n });\n\n // Add a small padding for safety\n const padding = 2;\n minX -= padding;\n maxX += padding;\n minY -= padding;\n maxY += padding;\n\n return {\n x: this.x + minX,\n y: this.y + minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n // Selection and hover visuals are handled by SelectionRenderer and HoverRenderer.\n\n // Apply element opacity\n const previousAlpha = ctx.globalAlpha;\n if (this.opacity !== undefined && this.opacity !== 1) {\n ctx.globalAlpha = this.opacity;\n }\n\n // Use shared rendering function for the text\n renderFlagTransform(ctx, this.toJSON() as unknown as SerializedTextTransformElement);\n\n // Restore previous alpha\n ctx.globalAlpha = previousAlpha;\n }\n\n resize(anchor: ResizeAnchor, newWidth: number, _newHeight: number, startData: TransformStartData): void {\n const isCornerHandle = anchor.includes('top') || anchor.includes('bottom');\n const isSideHandle = anchor.includes('middle');\n const flagStartData = startData.transformData as FlagTransformData;\n\n if (isCornerHandle) {\n // Corner handle: uniform scale\n const scale = newWidth / startData.width!;\n\n const newFontSize = startData.fontSize! * scale;\n this.setFontSize(newFontSize);\n\n const scaledWidth = flagStartData.width * scale;\n this.transformData.width = Math.max(50, scaledWidth);\n } else if (isSideHandle) {\n // Side handle: only width changes\n if (anchor === 'middle-left' || anchor === 'middle-right') {\n this.fontSize = startData.fontSize!;\n this.transformData.width = Math.max(50, newWidth);\n\n // Use bbox width change, not transformData width change\n const deltaWidth = newWidth - startData.width!;\n const rotationRad = (this.rotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n if (anchor === 'middle-left') {\n const centerShift = -deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n } else if (anchor === 'middle-right') {\n const centerShift = deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n }\n }\n }\n }\n\n getEnabledAnchors(): ResizeAnchor[] {\n // Flag supports only corner handles (no side handles)\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n toJSON(): FlagElementConfig {\n return {\n ...super.toJSON(),\n transformType: 'flag',\n transformData: {\n type: 'flag',\n amplitude: this.transformData.amplitude,\n frequency: this.transformData.frequency,\n width: this.transformData.width,\n },\n };\n }\n\n override getTransformStartData(): TransformStartData {\n const bbox = this.getBoundingBox();\n return {\n ...super.getTransformStartData(),\n width: bbox.width,\n height: bbox.height,\n transformData: {\n type: 'flag',\n amplitude: this.transformData.amplitude,\n frequency: this.transformData.frequency,\n width: this.transformData.width,\n },\n };\n }\n}\n\nexport default FlagTransform;\n","/**\n * LeanTransform - Text with slanted/leaning effect\n * Creates a skewed perspective\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { measureTextWidth } from '../core/GeometryUtils.js';\nimport { renderLeanTransform, type SerializedTextTransformElement } from '../rendering/transform-renderer.js';\nimport type { LeanElementConfig, LeanTransformData, BoundingBox, ResizeAnchor, TransformStartData } from '../types/index.js';\n\nexport class LeanTransform extends TextElement {\n declare transformData: LeanTransformData;\n\n constructor(config: Partial<LeanElementConfig> = {}) {\n super(config);\n this.transformType = 'lean';\n\n // Lean transform data: controls the slant (-1 to 1)\n // Positive = slant left, Negative = slant right\n const data = config.transformData;\n this.transformData = {\n type: 'lean',\n leanAmount: data?.leanAmount ?? (data as unknown as Record<string, number> | undefined)?.angleAmount ?? 0,\n width: data?.width ?? 200,\n };\n }\n\n getBoundingBox(): BoundingBox {\n const leanAmountAbs = Math.abs(this.transformData.leanAmount);\n const height = this.fontSize * (1.2 + leanAmountAbs * 0.5);\n\n return {\n x: this.x - this.transformData.width / 2,\n y: this.y - height / 2,\n width: this.transformData.width,\n height: height,\n };\n }\n\n getVisualBoundingBox(): BoundingBox {\n // For lean, the entire text block is skewed, so we need to calculate the transformed bounds\n const actualTextWidth = measureTextWidth(this.text, this.fontSize, this.fontFamily);\n const width = actualTextWidth;\n const height = this.fontSize * 1.2;\n\n // Calculate the skew transformation matrix applied in render()\n // Negate to match the reversed direction in render()\n const skewAngle = (-this.transformData.leanAmount * Math.PI) / 4;\n const skewX = Math.tan(skewAngle);\n\n // Define the four corners of the text block before skew (centered)\n const corners = [\n { x: -width / 2, y: -height / 2 }, // top-left\n { x: width / 2, y: -height / 2 }, // top-right\n { x: -width / 2, y: height / 2 }, // bottom-left\n { x: width / 2, y: height / 2 }, // bottom-right\n ];\n\n let minX = Infinity;\n let maxX = -Infinity;\n let minY = Infinity;\n let maxY = -Infinity;\n\n // Apply skew transformation [1, 0, tan(skewAngle), 1] to each corner\n corners.forEach((corner) => {\n // Skew transform: x' = x + tan(skewAngle)*y, y' = y\n const skewedX = corner.x + skewX * corner.y;\n const skewedY = corner.y;\n\n minX = Math.min(minX, skewedX);\n maxX = Math.max(maxX, skewedX);\n minY = Math.min(minY, skewedY);\n maxY = Math.max(maxY, skewedY);\n });\n\n // Add a small padding for safety\n const padding = 2;\n minX -= padding;\n maxX += padding;\n minY -= padding;\n maxY += padding;\n\n return {\n x: this.x + minX,\n y: this.y + minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n // Selection and hover visuals are handled by SelectionRenderer and HoverRenderer.\n\n // Apply element opacity\n const previousAlpha = ctx.globalAlpha;\n if (this.opacity !== undefined && this.opacity !== 1) {\n ctx.globalAlpha = this.opacity;\n }\n\n // Use shared rendering function for the text\n renderLeanTransform(ctx, this.toJSON() as unknown as SerializedTextTransformElement);\n\n // Restore previous alpha\n ctx.globalAlpha = previousAlpha;\n }\n\n resize(anchor: ResizeAnchor, newWidth: number, _newHeight: number, startData: TransformStartData): void {\n const isCornerHandle = anchor.includes('top') || anchor.includes('bottom');\n const isSideHandle = anchor.includes('middle');\n const leanStartData = startData.transformData as LeanTransformData;\n\n if (isCornerHandle) {\n // Corner handle: uniform scale\n const scale = newWidth / startData.width!;\n\n const newFontSize = startData.fontSize! * scale;\n this.setFontSize(newFontSize);\n\n const scaledWidth = leanStartData.width * scale;\n this.transformData.width = Math.max(50, scaledWidth);\n } else if (isSideHandle) {\n // Side handle: only width changes\n if (anchor === 'middle-left' || anchor === 'middle-right') {\n this.fontSize = startData.fontSize!;\n this.transformData.width = Math.max(50, newWidth);\n\n const deltaWidth = newWidth - leanStartData.width;\n const rotationRad = (this.rotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n if (anchor === 'middle-left') {\n const centerShift = -deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n } else if (anchor === 'middle-right') {\n const centerShift = deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n }\n }\n }\n }\n\n getEnabledAnchors(): ResizeAnchor[] {\n // Lean transform applies a uniform skew to the entire text block\n // Only corner handles make sense for uniform scaling\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n toJSON(): LeanElementConfig {\n return {\n ...super.toJSON(),\n transformType: 'lean',\n transformData: {\n type: 'lean',\n leanAmount: this.transformData.leanAmount,\n width: this.transformData.width,\n },\n };\n }\n\n override getTransformStartData(): TransformStartData {\n const bbox = this.getBoundingBox();\n return {\n ...super.getTransformStartData(),\n width: bbox.width,\n height: bbox.height,\n transformData: {\n type: 'lean',\n leanAmount: this.transformData.leanAmount,\n width: this.transformData.width,\n },\n };\n }\n}\n\nexport default LeanTransform;\n","/**\n * AscendTransform - Text ascends along a straight diagonal line at ~30 degrees\n */\n\nimport { TextElement } from '../core/TextElement.js';\nimport { measureTextWidth } from '../core/GeometryUtils.js';\nimport { renderAscendTransform, type SerializedTextTransformElement } from '../rendering/transform-renderer.js';\nimport type { AscendElementConfig, AscendTransformData, BoundingBox, ResizeAnchor, TransformStartData } from '../types/index.js';\n\nexport class AscendTransform extends TextElement {\n declare transformData: AscendTransformData;\n\n constructor(config: Partial<AscendElementConfig> = {}) {\n super(config);\n this.transformType = 'ascend';\n\n // Ascend transform data: angle in degrees\n // Internal: -30° (max up) to +30° (max down)\n // Display: +100% (max up) to -100% (max down)\n const data = config.transformData;\n this.transformData = {\n type: 'ascend',\n ascendAngle: data?.ascendAngle ?? -15,\n width: data?.width ?? 200,\n };\n }\n\n getBoundingBox(): BoundingBox {\n const angleRad = (this.transformData.ascendAngle * Math.PI) / 180;\n const width = this.transformData.width;\n\n // Calculate the vertical rise based on the width and angle\n const rise = width * Math.tan(angleRad);\n\n // Account for font size in height calculation\n const height = Math.abs(rise) + this.fontSize * 1.2;\n\n // Determine y offset based on angle direction\n const yOffset = rise > 0 ? -height : 0;\n\n return {\n x: this.x - width / 2,\n y: this.y + yOffset,\n width: width,\n height: height,\n };\n }\n\n getVisualBoundingBox(): BoundingBox {\n // For ascend, we need to calculate bounds that encompass all skewed characters\n // We simulate the rendering to find the actual min/max positions\n const chars = this.text.split('');\n const charWidths = chars.map((char) => measureTextWidth(char, this.fontSize, this.fontFamily));\n const totalWidth = charWidths.reduce((sum, w) => sum + w, 0);\n\n const angleRad = (this.transformData.ascendAngle * Math.PI) / 180;\n const slope = Math.tan(angleRad);\n const skewFactor = 1.0;\n\n // Font metrics for character height\n const fontHeight = this.fontSize;\n const halfHeight = fontHeight / 2;\n\n let minX = Infinity;\n let maxX = -Infinity;\n let minY = Infinity;\n let maxY = -Infinity;\n\n // Simulate character positioning and skew transformation\n let currentX = -totalWidth / 2;\n chars.forEach((_char, i) => {\n const charWidth = charWidths[i];\n const centerX = currentX + charWidth / 2;\n\n // Position on diagonal line\n const lineY = centerX * slope;\n\n // Character corners before skew (relative to character center)\n const corners = [\n { x: -charWidth / 2, y: -halfHeight }, // top-left\n { x: charWidth / 2, y: -halfHeight }, // top-right\n { x: -charWidth / 2, y: halfHeight }, // bottom-left\n { x: charWidth / 2, y: halfHeight }, // bottom-right\n ];\n\n // Apply skew transformation: [1, slope*skewFactor, 0, 1] and translate\n corners.forEach((corner) => {\n // Skew transform: x' = x, y' = y + slope*skewFactor*x\n const skewedX = corner.x;\n const skewedY = corner.y + slope * skewFactor * corner.x;\n\n // Translate to world position\n const worldX = centerX + skewedX;\n const worldY = lineY + skewedY;\n\n minX = Math.min(minX, worldX);\n maxX = Math.max(maxX, worldX);\n minY = Math.min(minY, worldY);\n maxY = Math.max(maxY, worldY);\n });\n\n currentX += charWidth;\n });\n\n // Add a small padding for safety\n const padding = 2;\n minX -= padding;\n maxX += padding;\n minY -= padding;\n maxY += padding;\n\n return {\n x: this.x + minX,\n y: this.y + minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n // Selection and hover visuals are handled by SelectionRenderer and HoverRenderer.\n\n // Apply element opacity\n const previousAlpha = ctx.globalAlpha;\n if (this.opacity !== undefined && this.opacity !== 1) {\n ctx.globalAlpha = this.opacity;\n }\n\n // Use shared rendering function for the text\n renderAscendTransform(ctx, this.toJSON() as unknown as SerializedTextTransformElement);\n\n // Restore previous alpha\n ctx.globalAlpha = previousAlpha;\n }\n\n resize(anchor: ResizeAnchor, newWidth: number, _newHeight: number, startData: TransformStartData): void {\n const isCornerHandle = anchor.includes('top') || anchor.includes('bottom');\n const isSideHandle = anchor.includes('middle');\n const ascendStartData = startData.transformData as AscendTransformData;\n\n if (isCornerHandle) {\n // Corner handle: uniform scale based on width change\n const scale = newWidth / startData.width!;\n\n // Apply uniform scale to font size\n const newFontSize = startData.fontSize! * scale;\n this.setFontSize(newFontSize);\n\n // Apply uniform scale to width\n const scaledWidth = ascendStartData.width * scale;\n this.transformData.width = Math.max(50, scaledWidth);\n } else if (isSideHandle) {\n // Side handle: only width changes (fontSize stays the same)\n if (anchor === 'middle-left' || anchor === 'middle-right') {\n // Keep fontSize constant\n this.fontSize = startData.fontSize!;\n\n // Update width only\n this.transformData.width = Math.max(50, newWidth);\n\n // Adjust center position to keep opposite edge fixed\n // Use bbox width change, not transformData width change\n const deltaWidth = newWidth - startData.width!;\n const rotationRad = (this.rotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n if (anchor === 'middle-left') {\n // Left edge is moving, right edge should stay fixed\n const centerShift = -deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n } else if (anchor === 'middle-right') {\n // Right edge is moving, left edge should stay fixed\n const centerShift = deltaWidth / 2;\n this.x = startData.x + centerShift * cos;\n this.y = startData.y + centerShift * sin;\n }\n }\n }\n }\n\n getEnabledAnchors(): ResizeAnchor[] {\n // Ascend supports only corner handles (no side handles)\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n toJSON(): AscendElementConfig {\n return {\n ...super.toJSON(),\n transformType: 'ascend',\n transformData: {\n type: 'ascend',\n ascendAngle: this.transformData.ascendAngle,\n width: this.transformData.width,\n },\n };\n }\n\n override getTransformStartData(): TransformStartData {\n const bbox = this.getBoundingBox();\n return {\n ...super.getTransformStartData(),\n width: bbox.width,\n height: bbox.height,\n transformData: {\n type: 'ascend',\n ascendAngle: this.transformData.ascendAngle,\n width: this.transformData.width,\n },\n };\n }\n}\n\nexport default AscendTransform;\n","/**\n * ShapeElement - Element for displaying geometric shapes\n * Supports rectangles, circles, ellipses, triangles, polygons, stars, and lines\n */\n\nimport { BaseElement } from './BaseElement.js';\nimport { RotationUtils } from './RotationUtils.js';\nimport { getThemeShapeFillColor } from '../constants.js';\nimport type {\n ShapeTransformData,\n ShapeElementConfig,\n ResizeAnchor,\n BoundingBox,\n Point,\n ShapeType,\n TransformStartData,\n} from '../types/index.js';\n\nexport class ShapeElement extends BaseElement {\n declare transformType: 'shape';\n declare transformData: ShapeTransformData;\n\n constructor(config: Partial<ShapeElementConfig> = {}) {\n super(config);\n this.transformType = 'shape';\n\n // Initialize transformData with proper ShapeTransformData type\n const defaultShapeType: ShapeType = 'rectangle';\n this.transformData = {\n type: 'shape',\n shapeType: config.transformData?.shapeType || defaultShapeType,\n width: config.transformData?.width || 200,\n height: config.transformData?.height || 200,\n borderRadius: config.transformData?.borderRadius ?? 3, // Default 3% radius (~6px on 200x200 shape, matches hover border)\n radiusX: config.transformData?.radiusX,\n radiusY: config.transformData?.radiusY,\n sides: config.transformData?.sides ?? 5,\n points: config.transformData?.points ?? 5,\n innerRadius: config.transformData?.innerRadius ?? 0.4,\n fillColor: config.transformData?.fillColor || getThemeShapeFillColor(),\n fillOpacity: config.transformData?.fillOpacity ?? 1,\n };\n }\n\n /**\n * Get bounding box in world coordinates\n * Returns the unrotated dimensions - rotation is handled by the renderer\n */\n getBoundingBox(): BoundingBox {\n return {\n x: this.x - this.transformData.width / 2,\n y: this.y - this.transformData.height / 2,\n width: this.transformData.width,\n height: this.transformData.height,\n };\n }\n\n /**\n * Tight visual bounding box for selection chrome.\n *\n * `getBoundingBox()` returns the conceptual `width × height` rect\n * the resize handles act on. Many shape types (circle, polygon,\n * star) render *inscribed* in that rect — their visible extent is\n * smaller than the box, leaving empty corners. The selection\n * border / handles use this tighter bbox so it hugs what the user\n * actually sees instead of including the empty padding.\n *\n * For shapes that fill their box (rectangle, ellipse, triangle,\n * line) this falls through to `getBoundingBox()`.\n *\n * Stroke is intentionally excluded — same convention as\n * `getBoundingBox()`. The chrome aligns with the *fill* edge.\n */\n getVisualBoundingBox(): BoundingBox {\n const { shapeType, width, height } = this.transformData;\n\n // Circle: inscribed in min(width, height) square. Tight bbox is\n // a square of side `min(w,h)` centered on (x, y).\n if (shapeType === 'circle') {\n const r = Math.min(width, height) / 2;\n return {\n x: this.x - r,\n y: this.y - r,\n width: r * 2,\n height: r * 2,\n };\n }\n\n // Polygon / star: inscribed in a circle of radius `min(w,h)/2`.\n // Walk the same vertex sequence the renderer uses to get the\n // exact tight bbox. Star alternates outer / inner radii — both\n // contribute to the bounds.\n if (shapeType === 'polygon' || shapeType === 'star') {\n const r = Math.min(width, height) / 2;\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n\n if (shapeType === 'polygon') {\n const sides = this.transformData.sides || 5;\n for (let i = 0; i < sides; i++) {\n const angle = (i * 2 * Math.PI) / sides - Math.PI / 2;\n const px = r * Math.cos(angle);\n const py = r * Math.sin(angle);\n if (px < minX) minX = px;\n if (px > maxX) maxX = px;\n if (py < minY) minY = py;\n if (py > maxY) maxY = py;\n }\n } else {\n const points = this.transformData.points || 5;\n const outerRadius = r;\n const innerRadius = outerRadius * (this.transformData.innerRadius ?? 0.4);\n for (let i = 0; i < points * 2; i++) {\n const angle = (i * Math.PI) / points - Math.PI / 2;\n const rad = i % 2 === 0 ? outerRadius : innerRadius;\n const px = rad * Math.cos(angle);\n const py = rad * Math.sin(angle);\n if (px < minX) minX = px;\n if (px > maxX) maxX = px;\n if (py < minY) minY = py;\n if (py > maxY) maxY = py;\n }\n }\n\n return {\n x: this.x + minX,\n y: this.y + minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n // Rectangle, rounded-rect, ellipse, triangle, line — these fill\n // their conceptual box, so the standard bbox is already tight.\n return this.getBoundingBox();\n }\n\n /**\n * Get rotation anchor point (center of the shape)\n */\n getRotationAnchor(): Point {\n return { x: this.x, y: this.y };\n }\n\n /**\n * Render the shape\n */\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n const elementOpacity = this.opacity ?? 1;\n const hasStroke = !!this.stroke?.enabled;\n\n // When element opacity < 1 AND stroke is present, render fill+stroke\n // at full opacity on a temp canvas to avoid compounding opacity where\n // fill and stroke overlap (e.g., 50% + 50% ≠ 75%).\n if (elementOpacity < 1 && hasStroke) {\n this.renderWithOffscreen(ctx, elementOpacity);\n return;\n }\n\n ctx.save();\n\n // Translate to shape center and rotate\n ctx.translate(this.x, this.y);\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n\n // Set fill style with combined opacity (element opacity * fill opacity)\n ctx.fillStyle = this.transformData.fillColor || '#3b82f6';\n const fillOpacity = this.transformData.fillOpacity ?? 1;\n ctx.globalAlpha = elementOpacity * fillOpacity;\n\n // Render the specific shape (fill)\n this.renderShape(ctx);\n\n ctx.restore();\n\n // Render stroke if enabled (after restore so we get a fresh transform state)\n if (hasStroke) {\n this.renderStroke(ctx, elementOpacity);\n }\n }\n\n /**\n * Render fill+stroke to a temporary canvas at full opacity, then composite\n * at element opacity. Prevents fill/stroke overlap from compounding.\n */\n private renderWithOffscreen(ctx: CanvasRenderingContext2D, elementOpacity: number): void {\n const { width, height } = this.transformData;\n const strokeWidth = this.stroke?.width || 2;\n const padding = strokeWidth + 2;\n const offW = Math.ceil(width + padding * 2);\n const offH = Math.ceil(height + padding * 2);\n\n const offCanvas = document.createElement('canvas');\n offCanvas.width = offW;\n offCanvas.height = offH;\n const offCtx = offCanvas.getContext('2d');\n if (!offCtx) {\n // Fallback: render directly (with compounding)\n this.renderDirect(ctx, elementOpacity);\n return;\n }\n\n // Render fill+stroke at full opacity, centered on the temp canvas\n offCtx.save();\n offCtx.translate(offW / 2, offH / 2);\n\n // Fill\n offCtx.fillStyle = this.transformData.fillColor || '#3b82f6';\n const fillOpacity = this.transformData.fillOpacity ?? 1;\n offCtx.globalAlpha = fillOpacity;\n offCtx.beginPath();\n this.traceShapePath(offCtx);\n offCtx.fill();\n offCtx.restore();\n\n // Stroke\n offCtx.save();\n offCtx.translate(offW / 2, offH / 2);\n const stroke = this.stroke!;\n offCtx.strokeStyle = stroke.color || '#000000';\n offCtx.lineWidth = stroke.width || 2;\n offCtx.lineCap = stroke.lineCap || 'round';\n offCtx.lineJoin = stroke.lineJoin || 'round';\n offCtx.globalAlpha = stroke.opacity ?? 1;\n if (stroke.dashArray && stroke.dashArray.length > 0) {\n offCtx.setLineDash(stroke.dashArray);\n }\n offCtx.beginPath();\n this.traceShapePath(offCtx);\n offCtx.stroke();\n offCtx.restore();\n\n // Composite onto main canvas at element opacity\n ctx.save();\n ctx.translate(this.x, this.y);\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n ctx.globalAlpha = elementOpacity;\n ctx.drawImage(offCanvas, -offW / 2, -offH / 2);\n ctx.restore();\n }\n\n /**\n * Direct render (no offscreen) — used as fallback\n */\n private renderDirect(ctx: CanvasRenderingContext2D, elementOpacity: number): void {\n ctx.save();\n ctx.translate(this.x, this.y);\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n ctx.fillStyle = this.transformData.fillColor || '#3b82f6';\n ctx.globalAlpha = elementOpacity * (this.transformData.fillOpacity ?? 1);\n this.renderShape(ctx);\n ctx.restore();\n\n if (this.stroke?.enabled) {\n this.renderStroke(ctx, elementOpacity);\n }\n }\n\n /**\n * Trace the shape path without filling or stroking — used by offscreen render\n */\n private traceShapePath(ctx: CanvasRenderingContext2D): void {\n const { shapeType, width, height } = this.transformData;\n\n switch (shapeType) {\n case 'rectangle': {\n const borderRadius = this.transformData.borderRadius || 0;\n const x = -width / 2;\n const y = -height / 2;\n if (borderRadius > 0) {\n const radius = Math.min((borderRadius / 100) * Math.min(width, height), width / 2, height / 2);\n ctx.roundRect(x, y, width, height, radius);\n } else {\n ctx.rect(x, y, width, height);\n }\n break;\n }\n case 'circle': {\n const radius = Math.min(width, height) / 2;\n ctx.arc(0, 0, radius, 0, Math.PI * 2);\n break;\n }\n case 'ellipse':\n ctx.ellipse(0, 0, width / 2, height / 2, 0, 0, Math.PI * 2);\n break;\n case 'triangle': {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n ctx.moveTo(0, -halfHeight);\n ctx.lineTo(halfWidth, halfHeight);\n ctx.lineTo(-halfWidth, halfHeight);\n ctx.closePath();\n break;\n }\n case 'polygon': {\n const sides = this.transformData.sides || 5;\n const r = Math.min(width, height) / 2;\n for (let i = 0; i < sides; i++) {\n const angle = (i * 2 * Math.PI) / sides - Math.PI / 2;\n const px = r * Math.cos(angle);\n const py = r * Math.sin(angle);\n if (i === 0) ctx.moveTo(px, py);\n else ctx.lineTo(px, py);\n }\n ctx.closePath();\n break;\n }\n case 'star': {\n const points = this.transformData.points || 5;\n const outerRadius = Math.min(width, height) / 2;\n const innerRadius = outerRadius * (this.transformData.innerRadius || 0.4);\n for (let i = 0; i < points * 2; i++) {\n const angle = (i * Math.PI) / points - Math.PI / 2;\n const r = i % 2 === 0 ? outerRadius : innerRadius;\n const px = r * Math.cos(angle);\n const py = r * Math.sin(angle);\n if (i === 0) ctx.moveTo(px, py);\n else ctx.lineTo(px, py);\n }\n ctx.closePath();\n break;\n }\n case 'line': {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n ctx.rect(-halfWidth, -halfHeight, width, height);\n break;\n }\n default:\n ctx.rect(-width / 2, -height / 2, width, height);\n }\n }\n\n /**\n * Render the stroke for this shape element\n */\n private renderStroke(ctx: CanvasRenderingContext2D, elementOpacity: number): void {\n ctx.save();\n\n ctx.translate(this.x, this.y);\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n\n const stroke = this.stroke!;\n ctx.strokeStyle = stroke.color || '#000000';\n ctx.lineWidth = stroke.width || 2;\n ctx.lineCap = stroke.lineCap || 'round';\n ctx.lineJoin = stroke.lineJoin || 'round';\n ctx.globalAlpha = elementOpacity * (stroke.opacity ?? 1);\n\n if (stroke.dashArray && stroke.dashArray.length > 0) {\n ctx.setLineDash(stroke.dashArray);\n }\n\n // Recreate the shape path for stroking\n const { shapeType, width, height } = this.transformData;\n ctx.beginPath();\n\n switch (shapeType) {\n case 'rectangle': {\n const borderRadius = this.transformData.borderRadius || 0;\n const x = -width / 2;\n const y = -height / 2;\n if (borderRadius > 0) {\n const radius = Math.min((borderRadius / 100) * Math.min(width, height), width / 2, height / 2);\n ctx.roundRect(x, y, width, height, radius);\n } else {\n ctx.rect(x, y, width, height);\n }\n break;\n }\n case 'circle': {\n const radius = Math.min(width, height) / 2;\n ctx.arc(0, 0, radius, 0, Math.PI * 2);\n break;\n }\n case 'ellipse':\n ctx.ellipse(0, 0, width / 2, height / 2, 0, 0, Math.PI * 2);\n break;\n case 'triangle': {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n ctx.moveTo(0, -halfHeight);\n ctx.lineTo(halfWidth, halfHeight);\n ctx.lineTo(-halfWidth, halfHeight);\n ctx.closePath();\n break;\n }\n case 'polygon': {\n const sides = this.transformData.sides || 5;\n const radius = Math.min(width, height) / 2;\n for (let i = 0; i < sides; i++) {\n const angle = (i * 2 * Math.PI) / sides - Math.PI / 2;\n const px = radius * Math.cos(angle);\n const py = radius * Math.sin(angle);\n if (i === 0) ctx.moveTo(px, py);\n else ctx.lineTo(px, py);\n }\n ctx.closePath();\n break;\n }\n case 'star': {\n const points = this.transformData.points || 5;\n const outerRadius = Math.min(width, height) / 2;\n const innerRadius = outerRadius * (this.transformData.innerRadius || 0.4);\n for (let i = 0; i < points * 2; i++) {\n const angle = (i * Math.PI) / points - Math.PI / 2;\n const r = i % 2 === 0 ? outerRadius : innerRadius;\n const px = r * Math.cos(angle);\n const py = r * Math.sin(angle);\n if (i === 0) ctx.moveTo(px, py);\n else ctx.lineTo(px, py);\n }\n ctx.closePath();\n break;\n }\n case 'line': {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n ctx.rect(-halfWidth, -halfHeight, width, height);\n break;\n }\n default:\n ctx.rect(-width / 2, -height / 2, width, height);\n }\n\n ctx.stroke();\n ctx.restore();\n }\n\n /**\n * Render the specific shape based on shapeType\n */\n private renderShape(ctx: CanvasRenderingContext2D): void {\n const { shapeType, width, height } = this.transformData;\n\n ctx.beginPath();\n\n switch (shapeType) {\n case 'rectangle':\n this.renderRectangle(ctx, width, height);\n break;\n case 'circle':\n this.renderCircle(ctx, Math.min(width, height) / 2);\n break;\n case 'ellipse':\n this.renderEllipse(ctx, width / 2, height / 2);\n break;\n case 'triangle':\n this.renderTriangle(ctx, width, height);\n break;\n case 'polygon':\n this.renderPolygon(ctx, Math.min(width, height) / 2);\n break;\n case 'star':\n this.renderStar(ctx, Math.min(width, height) / 2);\n break;\n case 'line':\n this.renderLine(ctx, width, height);\n return; // Line uses stroke, not fill\n default:\n // Fallback to rectangle\n this.renderRectangle(ctx, width, height);\n }\n\n ctx.fill();\n }\n\n /**\n * Render a rectangle (with optional border radius)\n */\n private renderRectangle(ctx: CanvasRenderingContext2D, width: number, height: number): void {\n const borderRadius = this.transformData.borderRadius || 0;\n const x = -width / 2;\n const y = -height / 2;\n\n if (borderRadius > 0) {\n const radius = Math.min((borderRadius / 100) * Math.min(width, height), width / 2, height / 2);\n ctx.roundRect(x, y, width, height, radius);\n } else {\n ctx.rect(x, y, width, height);\n }\n }\n\n /**\n * Render a circle\n */\n private renderCircle(ctx: CanvasRenderingContext2D, radius: number): void {\n ctx.arc(0, 0, radius, 0, Math.PI * 2);\n }\n\n /**\n * Render an ellipse\n */\n private renderEllipse(ctx: CanvasRenderingContext2D, radiusX: number, radiusY: number): void {\n ctx.ellipse(0, 0, radiusX, radiusY, 0, 0, Math.PI * 2);\n }\n\n /**\n * Render a triangle (isosceles, pointing up)\n */\n private renderTriangle(ctx: CanvasRenderingContext2D, width: number, height: number): void {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n\n ctx.moveTo(0, -halfHeight); // Top point\n ctx.lineTo(halfWidth, halfHeight); // Bottom right\n ctx.lineTo(-halfWidth, halfHeight); // Bottom left\n ctx.closePath();\n }\n\n /**\n * Render a regular polygon\n */\n private renderPolygon(ctx: CanvasRenderingContext2D, radius: number): void {\n const sides = Math.max(3, Math.min(20, this.transformData.sides || 5));\n const angleStep = (Math.PI * 2) / sides;\n const startAngle = -Math.PI / 2; // Start at top\n\n for (let i = 0; i <= sides; i++) {\n const angle = startAngle + angleStep * i;\n const x = Math.cos(angle) * radius;\n const y = Math.sin(angle) * radius;\n\n if (i === 0) {\n ctx.moveTo(x, y);\n } else {\n ctx.lineTo(x, y);\n }\n }\n\n ctx.closePath();\n }\n\n /**\n * Render a star\n */\n private renderStar(ctx: CanvasRenderingContext2D, outerRadius: number): void {\n const points = Math.max(3, Math.min(20, this.transformData.points || 5));\n const innerRadiusRatio = Math.max(0.1, Math.min(0.9, this.transformData.innerRadius || 0.4));\n const innerRadius = outerRadius * innerRadiusRatio;\n const angleStep = Math.PI / points;\n const startAngle = -Math.PI / 2; // Start at top\n\n for (let i = 0; i < points * 2; i++) {\n const angle = startAngle + angleStep * i;\n const radius = i % 2 === 0 ? outerRadius : innerRadius;\n const x = Math.cos(angle) * radius;\n const y = Math.sin(angle) * radius;\n\n if (i === 0) {\n ctx.moveTo(x, y);\n } else {\n ctx.lineTo(x, y);\n }\n }\n\n ctx.closePath();\n }\n\n /**\n * Render a line\n */\n private renderLine(ctx: CanvasRenderingContext2D, width: number, height: number): void {\n // Draw line as a stroke from left to right (or top to bottom if rotated)\n const halfWidth = width / 2;\n const thickness = height;\n\n ctx.moveTo(-halfWidth, 0);\n ctx.lineTo(halfWidth, 0);\n\n // Use stroke instead of fill\n ctx.strokeStyle = this.transformData.fillColor || '#3b82f6';\n ctx.lineWidth = thickness;\n ctx.lineCap = 'round';\n ctx.stroke();\n }\n\n /**\n * Handle resize - maintain aspect ratio for circles, free resize for others\n */\n override resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): boolean {\n const { shapeType } = this.transformData;\n\n // For circles and regular polygons, maintain aspect ratio\n if (shapeType === 'circle' || shapeType === 'polygon' || shapeType === 'star') {\n const scale = Math.max(newWidth / startData.transformData.width, newHeight / startData.transformData.height);\n this.transformData.width = startData.transformData.width * scale;\n this.transformData.height = startData.transformData.height * scale;\n } else if (shapeType === 'line') {\n // For lines, only allow width (length) to change, keep height (thickness) constant\n this.transformData.width = newWidth;\n this.transformData.height = startData.transformData.height;\n } else {\n // For other shapes, allow free resize\n this.transformData.width = newWidth;\n this.transformData.height = newHeight;\n }\n\n // Ensure rotation stays the same\n this.rotation = startData.rotation;\n\n // Calculate fixed corner position (opposite of dragged anchor)\n const fixedCorner = this.getFixedCorner(anchor, startData);\n\n // Update position so the fixed corner stays in place\n // Get the opposite anchor (the one that should stay fixed)\n const oppositeAnchor = this.getOppositeAnchor(anchor);\n const newBbox = this.getBoundingBox();\n const fixedCornerOffset = this.getCornerOffset(oppositeAnchor, newBbox);\n\n this.x = fixedCorner.x - fixedCornerOffset.x;\n this.y = fixedCorner.y - fixedCornerOffset.y;\n\n return true;\n }\n\n /**\n * Get the opposite anchor (the one that should stay fixed during resize)\n */\n private getOppositeAnchor(anchor: ResizeAnchor): ResizeAnchor {\n const opposites: Record<ResizeAnchor, ResizeAnchor> = {\n 'top-left': 'bottom-right',\n 'top-right': 'bottom-left',\n 'bottom-left': 'top-right',\n 'bottom-right': 'top-left',\n 'middle-left': 'middle-right',\n 'middle-right': 'middle-left',\n 'middle-top': 'middle-bottom',\n 'middle-bottom': 'middle-top',\n top: 'bottom',\n bottom: 'top',\n left: 'right',\n right: 'left',\n };\n return opposites[anchor];\n }\n\n /**\n * Get the position of the fixed corner (opposite of dragged anchor)\n */\n private getFixedCorner(anchor: ResizeAnchor, startData: TransformStartData): Point {\n const startBbox = {\n x: startData.x - startData.transformData.width / 2,\n y: startData.y - startData.transformData.height / 2,\n width: startData.transformData.width,\n height: startData.transformData.height,\n };\n\n let fixedX: number, fixedY: number;\n\n switch (anchor) {\n case 'top-left':\n fixedX = startBbox.x + startBbox.width;\n fixedY = startBbox.y + startBbox.height;\n break;\n case 'top-right':\n fixedX = startBbox.x;\n fixedY = startBbox.y + startBbox.height;\n break;\n case 'bottom-left':\n fixedX = startBbox.x + startBbox.width;\n fixedY = startBbox.y;\n break;\n case 'bottom-right':\n fixedX = startBbox.x;\n fixedY = startBbox.y;\n break;\n case 'middle-left':\n fixedX = startBbox.x + startBbox.width;\n fixedY = startBbox.y + startBbox.height / 2;\n break;\n case 'middle-right':\n fixedX = startBbox.x;\n fixedY = startBbox.y + startBbox.height / 2;\n break;\n case 'middle-top':\n fixedX = startBbox.x + startBbox.width / 2;\n fixedY = startBbox.y + startBbox.height;\n break;\n case 'middle-bottom':\n fixedX = startBbox.x + startBbox.width / 2;\n fixedY = startBbox.y;\n break;\n default:\n fixedX = startBbox.x + startBbox.width / 2;\n fixedY = startBbox.y + startBbox.height / 2;\n }\n\n return { x: fixedX, y: fixedY };\n }\n\n /**\n * Get the offset of a corner from the center\n */\n private getCornerOffset(anchor: ResizeAnchor, bbox: BoundingBox): Point {\n let offsetX: number, offsetY: number;\n\n switch (anchor) {\n case 'top-left':\n offsetX = -bbox.width / 2;\n offsetY = -bbox.height / 2;\n break;\n case 'top-right':\n offsetX = bbox.width / 2;\n offsetY = -bbox.height / 2;\n break;\n case 'bottom-left':\n offsetX = -bbox.width / 2;\n offsetY = bbox.height / 2;\n break;\n case 'bottom-right':\n offsetX = bbox.width / 2;\n offsetY = bbox.height / 2;\n break;\n case 'middle-left':\n offsetX = -bbox.width / 2;\n offsetY = 0;\n break;\n case 'middle-right':\n offsetX = bbox.width / 2;\n offsetY = 0;\n break;\n case 'middle-top':\n offsetX = 0;\n offsetY = -bbox.height / 2;\n break;\n case 'middle-bottom':\n offsetX = 0;\n offsetY = bbox.height / 2;\n break;\n default:\n offsetX = 0;\n offsetY = 0;\n }\n\n return { x: offsetX, y: offsetY };\n }\n\n /**\n * Get enabled anchors - all 8 anchors for most shapes, only corners for circles/polygons\n */\n getEnabledAnchors(): ResizeAnchor[] {\n const { shapeType } = this.transformData;\n\n // Circles and regular polygons: only corner handles (to maintain aspect ratio)\n if (shapeType === 'circle' || shapeType === 'polygon' || shapeType === 'star') {\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n // Lines: only left/right handles for length adjustment\n if (shapeType === 'line') {\n return ['middle-left', 'middle-right'];\n }\n\n // Other shapes: all 8 handles\n return [\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n 'middle-left',\n 'middle-right',\n 'middle-top',\n 'middle-bottom',\n ];\n }\n\n /**\n * Clone this element\n */\n clone(): ShapeElement {\n return new ShapeElement(this.toJSON());\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): ShapeElementConfig & { id: string; type: 'shape'; x: number; y: number; rotation: number } {\n const baseJSON = super.toJSON();\n return {\n ...baseJSON,\n id: this.id, // Explicitly include to ensure string type\n type: 'shape' as const,\n transformType: 'shape' as const,\n x: this.x, // Explicitly include to ensure number type\n y: this.y, // Explicitly include to ensure number type\n rotation: this.rotation, // Explicitly include to ensure number type\n transformData: { ...this.transformData },\n };\n }\n}\n\nexport default ShapeElement;\n","/**\n * PathElement - Element for custom bezier paths\n * Supports user-drawn shapes with editable control points\n */\n\nimport { BaseElement } from './BaseElement.js';\nimport { RotationUtils } from './RotationUtils.js';\nimport { getThemeShapeFillColor } from '../constants.js';\nimport type {\n PathTransformData,\n PathElementConfig,\n PathPoint,\n ResizeAnchor,\n BoundingBox,\n Point,\n TransformStartData,\n} from '../types/index.js';\n\nexport class PathElement extends BaseElement {\n declare transformType: 'path';\n declare transformData: PathTransformData;\n\n constructor(config: Partial<PathElementConfig> = {}) {\n super(config);\n this.transformType = 'path';\n\n // Initialize transformData with proper PathTransformData type\n this.transformData = {\n type: 'path',\n points: config.transformData?.points || [],\n closed: config.transformData?.closed ?? false,\n width: config.transformData?.width || 200,\n height: config.transformData?.height || 200,\n fillEnabled: config.transformData?.fillEnabled ?? false,\n fillColor: config.transformData?.fillColor || getThemeShapeFillColor(),\n fillOpacity: config.transformData?.fillOpacity ?? 1,\n strokeEnabled: config.transformData?.strokeEnabled ?? true,\n strokeColor: config.transformData?.strokeColor || '#000000',\n strokeWidth: config.transformData?.strokeWidth || 2,\n };\n\n // If no points provided, start with empty path\n // Points will be added via pen tool\n }\n\n /**\n * Get bounding box in world coordinates\n * Returns the unrotated dimensions - rotation is handled by the renderer\n * For paths, calculates actual bounds from path points\n * The bounding box is centered around the element position (this.x, this.y)\n */\n getBoundingBox(): BoundingBox {\n const localBounds = this.calculatePathBounds();\n\n // The bounding box is centered around the element position\n return {\n x: this.x - localBounds.width / 2,\n y: this.y - localBounds.height / 2,\n width: localBounds.width,\n height: localBounds.height,\n };\n }\n\n /**\n * Get visual bounding box (same as regular bounding box for paths)\n * Used for selection display - wraps tightly around actual path\n */\n getVisualBoundingBox(): BoundingBox {\n return this.getBoundingBox();\n }\n\n /**\n * Get rotation anchor point (center of the path's bounding box)\n */\n getRotationAnchor(): Point {\n // For path elements, rotation happens around the element position\n // which should be the center of the local bounds\n return {\n x: this.x,\n y: this.y,\n };\n }\n\n /**\n * Calculate a point on a cubic Bezier curve\n * @param p0 Start point\n * @param p1 First control point\n * @param p2 Second control point\n * @param p3 End point\n * @param t Parameter from 0 to 1\n */\n private bezierPoint(p0: Point, p1: Point, p2: Point, p3: Point, t: number): Point {\n const mt = 1 - t;\n const mt2 = mt * mt;\n const mt3 = mt2 * mt;\n const t2 = t * t;\n const t3 = t2 * t;\n\n return {\n x: mt3 * p0.x + 3 * mt2 * t * p1.x + 3 * mt * t2 * p2.x + t3 * p3.x,\n y: mt3 * p0.y + 3 * mt2 * t * p1.y + 3 * mt * t2 * p2.y + t3 * p3.y,\n };\n }\n\n /**\n * Calculate bounding box from path points\n * Returns bbox in local coordinates (relative to element position)\n */\n private calculatePathBounds(): BoundingBox {\n const { points } = this.transformData;\n\n if (points.length === 0) {\n return { x: 0, y: 0, width: 0, height: 0 };\n }\n\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n // Calculate bounds including Bezier curve extrema\n for (let i = 0; i < points.length; i++) {\n const currentPoint = points[i];\n const nextPoint = points[(i + 1) % points.length];\n\n // Skip last segment if path is not closed\n if (i === points.length - 1 && !this.transformData.closed) {\n // Include the last point\n minX = Math.min(minX, currentPoint.x);\n minY = Math.min(minY, currentPoint.y);\n maxX = Math.max(maxX, currentPoint.x);\n maxY = Math.max(maxY, currentPoint.y);\n break;\n }\n\n // Include the current anchor point\n minX = Math.min(minX, currentPoint.x);\n minY = Math.min(minY, currentPoint.y);\n maxX = Math.max(maxX, currentPoint.x);\n maxY = Math.max(maxY, currentPoint.y);\n\n // Check if this segment has a curve\n const hasCurve = currentPoint.handleOut || nextPoint.handleIn;\n\n if (hasCurve) {\n // Calculate Bezier curve bounds by sampling points along the curve\n const p0 = { x: currentPoint.x, y: currentPoint.y };\n const p1 = {\n x: currentPoint.x + (currentPoint.handleOut?.x || 0),\n y: currentPoint.y + (currentPoint.handleOut?.y || 0),\n };\n const p2 = {\n x: nextPoint.x + (nextPoint.handleIn?.x || 0),\n y: nextPoint.y + (nextPoint.handleIn?.y || 0),\n };\n const p3 = { x: nextPoint.x, y: nextPoint.y };\n\n // Sample the curve at multiple points to find extrema\n const samples = 20;\n for (let t = 0; t <= samples; t++) {\n const u = t / samples;\n const point = this.bezierPoint(p0, p1, p2, p3, u);\n minX = Math.min(minX, point.x);\n minY = Math.min(minY, point.y);\n maxX = Math.max(maxX, point.x);\n maxY = Math.max(maxY, point.y);\n }\n }\n }\n\n // Add stroke width to bounds if stroke is enabled\n const strokeWidth = this.transformData.strokeEnabled ? this.transformData.strokeWidth || 2 : 0;\n const halfStroke = strokeWidth / 2;\n\n return {\n x: minX - halfStroke,\n y: minY - halfStroke,\n width: maxX - minX + strokeWidth,\n height: maxY - minY + strokeWidth,\n };\n }\n\n /**\n * Update cached bounding box dimensions\n */\n updateBounds(): void {\n const bounds = this.calculatePathBounds();\n this.transformData.width = Math.max(1, bounds.width);\n this.transformData.height = Math.max(1, bounds.height);\n }\n\n /**\n * Render the path\n */\n render(ctx: CanvasRenderingContext2D, _isSelected: boolean = false, _isHovered: boolean = false): void {\n const { points, closed, fillEnabled, fillColor, fillOpacity, strokeEnabled, strokeColor, strokeWidth } =\n this.transformData;\n\n if (points.length === 0) {\n return; // Nothing to render\n }\n\n ctx.save();\n\n // Translate to path center and rotate\n ctx.translate(this.x, this.y);\n ctx.rotate(RotationUtils.toRadians(this.rotation));\n\n // Offset to center the path around the rotation point\n const localBounds = this.calculatePathBounds();\n const centerOffsetX = localBounds.x + localBounds.width / 2;\n const centerOffsetY = localBounds.y + localBounds.height / 2;\n ctx.translate(-centerOffsetX, -centerOffsetY);\n\n // Build the path\n ctx.beginPath();\n this.renderPath(ctx);\n\n // Fill if enabled and path is closed\n if (fillEnabled && closed && fillColor) {\n ctx.fillStyle = fillColor;\n ctx.globalAlpha = fillOpacity ?? 1;\n ctx.fill();\n }\n\n // Stroke if enabled\n if (strokeEnabled && strokeColor && strokeWidth) {\n ctx.strokeStyle = strokeColor;\n ctx.lineWidth = strokeWidth;\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n ctx.globalAlpha = 1;\n ctx.stroke();\n }\n\n ctx.restore();\n }\n\n /**\n * Render the path using Canvas API bezier curves\n */\n private renderPath(ctx: CanvasRenderingContext2D): void {\n const { points, closed } = this.transformData;\n\n if (points.length === 0) return;\n\n // Start at first point\n const firstPoint = points[0];\n ctx.moveTo(firstPoint.x, firstPoint.y);\n\n // Draw segments between points\n for (let i = 0; i < points.length; i++) {\n const currentPoint = points[i];\n const nextPoint = points[(i + 1) % points.length];\n\n // Skip last segment if path is not closed\n if (i === points.length - 1 && !closed) {\n break;\n }\n\n // Check if we need to draw a curve or straight line\n const hasCurve = currentPoint.handleOut || nextPoint.handleIn;\n\n if (hasCurve) {\n // Bezier curve\n const cp1x = currentPoint.x + (currentPoint.handleOut?.x || 0);\n const cp1y = currentPoint.y + (currentPoint.handleOut?.y || 0);\n const cp2x = nextPoint.x + (nextPoint.handleIn?.x || 0);\n const cp2y = nextPoint.y + (nextPoint.handleIn?.y || 0);\n\n ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, nextPoint.x, nextPoint.y);\n } else {\n // Straight line\n ctx.lineTo(nextPoint.x, nextPoint.y);\n }\n }\n\n if (closed) {\n ctx.closePath();\n }\n }\n\n /**\n * Handle resize - scale all points relative to center\n */\n override resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): boolean {\n const oldWidth = startData.transformData.width;\n const oldHeight = startData.transformData.height;\n\n if (oldWidth === 0 || oldHeight === 0) {\n return false; // Can't resize empty path\n }\n\n // Calculate scale factors\n const scaleX = newWidth / oldWidth;\n const scaleY = newHeight / oldHeight;\n\n // Get the center of the original bounds to scale from\n // We need to reconstruct the original bounds from startData\n const startPoints = startData.transformData.points as PathPoint[];\n let minX = Infinity,\n minY = Infinity,\n maxX = -Infinity,\n maxY = -Infinity;\n for (const point of startPoints) {\n minX = Math.min(minX, point.x);\n minY = Math.min(minY, point.y);\n maxX = Math.max(maxX, point.x);\n maxY = Math.max(maxY, point.y);\n }\n const centerX = (minX + maxX) / 2;\n const centerY = (minY + maxY) / 2;\n\n // Scale all points and handles from the center\n this.transformData.points = startPoints.map((point) => ({\n ...point,\n x: centerX + (point.x - centerX) * scaleX,\n y: centerY + (point.y - centerY) * scaleY,\n handleIn: point.handleIn ? { x: point.handleIn.x * scaleX, y: point.handleIn.y * scaleY } : undefined,\n handleOut: point.handleOut ? { x: point.handleOut.x * scaleX, y: point.handleOut.y * scaleY } : undefined,\n }));\n\n // Update dimensions\n this.transformData.width = newWidth;\n this.transformData.height = newHeight;\n\n // Ensure rotation stays the same\n this.rotation = startData.rotation;\n\n // Calculate fixed corner position (opposite of dragged anchor)\n const fixedCorner = this.getFixedCorner(anchor, startData);\n\n // Update position so the fixed corner stays in place\n const oppositeAnchor = this.getOppositeAnchor(anchor);\n const newBbox = this.getBoundingBox();\n const fixedCornerOffset = this.getCornerOffset(oppositeAnchor, newBbox);\n\n this.x = fixedCorner.x - fixedCornerOffset.x;\n this.y = fixedCorner.y - fixedCornerOffset.y;\n\n return true;\n }\n\n /**\n * Get the opposite anchor (the one that should stay fixed during resize)\n */\n private getOppositeAnchor(anchor: ResizeAnchor): ResizeAnchor {\n const opposites: Record<ResizeAnchor, ResizeAnchor> = {\n 'top-left': 'bottom-right',\n 'top-right': 'bottom-left',\n 'bottom-left': 'top-right',\n 'bottom-right': 'top-left',\n 'middle-left': 'middle-right',\n 'middle-right': 'middle-left',\n 'middle-top': 'middle-bottom',\n 'middle-bottom': 'middle-top',\n top: 'bottom',\n bottom: 'top',\n left: 'right',\n right: 'left',\n };\n return opposites[anchor];\n }\n\n /**\n * Get the position of the fixed corner (opposite of dragged anchor)\n */\n private getFixedCorner(anchor: ResizeAnchor, startData: TransformStartData): Point {\n const startBbox = {\n x: startData.x - startData.transformData.width / 2,\n y: startData.y - startData.transformData.height / 2,\n width: startData.transformData.width,\n height: startData.transformData.height,\n };\n\n let fixedX: number, fixedY: number;\n\n switch (anchor) {\n case 'top-left':\n fixedX = startBbox.x + startBbox.width;\n fixedY = startBbox.y + startBbox.height;\n break;\n case 'top-right':\n fixedX = startBbox.x;\n fixedY = startBbox.y + startBbox.height;\n break;\n case 'bottom-left':\n fixedX = startBbox.x + startBbox.width;\n fixedY = startBbox.y;\n break;\n case 'bottom-right':\n fixedX = startBbox.x;\n fixedY = startBbox.y;\n break;\n case 'middle-left':\n fixedX = startBbox.x + startBbox.width;\n fixedY = startBbox.y + startBbox.height / 2;\n break;\n case 'middle-right':\n fixedX = startBbox.x;\n fixedY = startBbox.y + startBbox.height / 2;\n break;\n case 'middle-top':\n fixedX = startBbox.x + startBbox.width / 2;\n fixedY = startBbox.y + startBbox.height;\n break;\n case 'middle-bottom':\n fixedX = startBbox.x + startBbox.width / 2;\n fixedY = startBbox.y;\n break;\n default:\n fixedX = startBbox.x + startBbox.width / 2;\n fixedY = startBbox.y + startBbox.height / 2;\n }\n\n return { x: fixedX, y: fixedY };\n }\n\n /**\n * Get the offset of a corner from the center\n */\n private getCornerOffset(anchor: ResizeAnchor, bbox: BoundingBox): Point {\n let offsetX: number, offsetY: number;\n\n switch (anchor) {\n case 'top-left':\n offsetX = -bbox.width / 2;\n offsetY = -bbox.height / 2;\n break;\n case 'top-right':\n offsetX = bbox.width / 2;\n offsetY = -bbox.height / 2;\n break;\n case 'bottom-left':\n offsetX = -bbox.width / 2;\n offsetY = bbox.height / 2;\n break;\n case 'bottom-right':\n offsetX = bbox.width / 2;\n offsetY = bbox.height / 2;\n break;\n case 'middle-left':\n offsetX = -bbox.width / 2;\n offsetY = 0;\n break;\n case 'middle-right':\n offsetX = bbox.width / 2;\n offsetY = 0;\n break;\n case 'middle-top':\n offsetX = 0;\n offsetY = -bbox.height / 2;\n break;\n case 'middle-bottom':\n offsetX = 0;\n offsetY = bbox.height / 2;\n break;\n default:\n offsetX = 0;\n offsetY = 0;\n }\n\n return { x: offsetX, y: offsetY };\n }\n\n /**\n * Get enabled anchors - all 8 anchors for free resize\n */\n getEnabledAnchors(): ResizeAnchor[] {\n return [\n 'top-left',\n 'top-right',\n 'bottom-left',\n 'bottom-right',\n 'middle-left',\n 'middle-right',\n 'middle-top',\n 'middle-bottom',\n ];\n }\n\n /**\n * Clone this element\n */\n clone(): PathElement {\n return new PathElement(this.toJSON());\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): PathElementConfig & { id: string; type: 'path'; x: number; y: number; rotation: number } {\n const baseJSON = super.toJSON();\n return {\n ...baseJSON,\n id: this.id, // Explicitly include to ensure string type\n type: 'path' as const,\n transformType: 'path' as const,\n x: this.x, // Explicitly include to ensure number type\n y: this.y, // Explicitly include to ensure number type\n rotation: this.rotation, // Explicitly include to ensure number type\n transformData: {\n ...this.transformData,\n // Deep clone points array\n points: this.transformData.points.map((p) => ({\n ...p,\n handleIn: p.handleIn ? { ...p.handleIn } : undefined,\n handleOut: p.handleOut ? { ...p.handleOut } : undefined,\n })),\n },\n };\n }\n}\n\nexport default PathElement;\n","/**\n * Centralized transform registry\n * Single source of truth for all transform types and their configurations\n */\n\nimport {\n CustomTransform,\n CircleTransform,\n ArchTransform,\n WaveTransform,\n FlagTransform,\n LeanTransform,\n AscendTransform,\n} from './index.js';\nimport { ShapeElement } from '../core/ShapeElement.js';\nimport { PathElement } from '../core/PathElement.js';\nimport { ImageElement } from '../core/ImageElement.js';\nimport { GroupElement } from '../core/GroupElement.js';\nimport { WAVE_DEFAULTS, FLAG_DEFAULTS, ARCH_DEFAULTS, LEAN_DEFAULTS, ASCEND_DEFAULTS } from './defaults.js';\nimport type { TransformType, TransformControl } from '../types/index.js';\n\n/**\n * Transform control configurations\n * Defines the sliders and their properties for each transform type\n */\nexport const TRANSFORM_CONTROLS: Partial<Record<TransformType, TransformControl[]>> = {\n circle: [\n {\n key: 'reverse',\n label: 'Reverse Direction',\n type: 'checkbox',\n defaultValue: false,\n },\n ],\n arch: [\n {\n key: 'archHeight',\n label: 'Arch Height',\n defaultInternalValue: ARCH_DEFAULTS.archHeight,\n // Slider 0-100 maps to internal -1 to 1\n toSlider: (internal: number) => ((internal + 1) / 2) * 100,\n fromSlider: (slider: number) => (slider / 100) * 2 - 1,\n toDisplay: (internal: number) => `${(internal * 100).toFixed(0)}%`,\n },\n ],\n wave: [\n {\n key: 'amplitude',\n label: 'Wave Amplitude',\n defaultInternalValue: WAVE_DEFAULTS.amplitude,\n // Slider 0-100 maps to internal -0.5 to 0.5 (-50% to +50%)\n toSlider: (internal: number) => (internal + 0.5) * 100,\n fromSlider: (slider: number) => (slider / 100) - 0.5,\n toDisplay: (internal: number) => `${(internal * 100).toFixed(0)}%`,\n },\n {\n key: 'frequency',\n label: 'Wave Frequency',\n defaultInternalValue: WAVE_DEFAULTS.frequency,\n step: 5, // 5% steps for smoother control\n // Slider 0-100 maps to internal 1 to 5\n toSlider: (internal: number) => ((internal - 1) / 4) * 100,\n fromSlider: (slider: number) => 1 + (slider / 100) * 4,\n toDisplay: (internal: number) => internal.toFixed(1),\n },\n ],\n flag: [\n {\n key: 'amplitude',\n label: 'Flag Amplitude',\n defaultInternalValue: FLAG_DEFAULTS.amplitude,\n // Slider 0-100 maps to internal -0.5 to 0.5 (-50% to +50%)\n toSlider: (internal: number) => (internal + 0.5) * 100,\n fromSlider: (slider: number) => (slider / 100) - 0.5,\n toDisplay: (internal: number) => `${(internal * 100).toFixed(0)}%`,\n },\n {\n key: 'frequency',\n label: 'Flag Frequency',\n defaultInternalValue: FLAG_DEFAULTS.frequency,\n step: 5,\n toSlider: (internal: number) => ((internal - 1) / 4) * 100,\n fromSlider: (slider: number) => 1 + (slider / 100) * 4,\n toDisplay: (internal: number) => internal.toFixed(1),\n },\n ],\n lean: [\n {\n key: 'leanAmount',\n label: 'Lean Amount',\n defaultInternalValue: LEAN_DEFAULTS.leanAmount,\n // Slider 0-100 maps to internal -0.5 to 0.5\n // Display as -100% to 100%\n toSlider: (internal: number) => (internal + 0.5) * 100,\n fromSlider: (slider: number) => slider / 100 - 0.5,\n toDisplay: (internal: number) => `${(internal * 200).toFixed(0)}%`,\n },\n ],\n ascend: [\n {\n key: 'ascendAngle',\n label: 'Ascend Amount',\n defaultInternalValue: ASCEND_DEFAULTS.ascendAngle,\n // Slider 0-100 maps to internal angle +30° to -30°\n // Display: slider 0 = -100% (down), slider 50 = 0%, slider 100 = +100% (up)\n toSlider: (internal: number) => ((30 - internal) / 60) * 100,\n fromSlider: (slider: number) => 30 - (slider / 100) * 60,\n toDisplay: (internal: number) => `${((-internal / 30) * 100).toFixed(0)}%`,\n },\n ],\n shape: [\n {\n key: 'shapeType',\n label: 'Shape Type',\n type: 'select',\n options: [\n { value: 'rectangle', label: 'Rectangle' },\n { value: 'circle', label: 'Circle' },\n { value: 'ellipse', label: 'Ellipse' },\n { value: 'triangle', label: 'Triangle' },\n { value: 'polygon', label: 'Polygon' },\n { value: 'star', label: 'Star' },\n { value: 'line', label: 'Line' },\n ],\n defaultValue: 'rectangle',\n },\n {\n key: 'borderRadius',\n label: 'Border Radius',\n defaultInternalValue: 0,\n // Slider 0-100 maps to internal 0 to 50\n toSlider: (internal: number) => (internal / 50) * 100,\n fromSlider: (slider: number) => (slider / 100) * 50,\n toDisplay: (internal: number) => `${internal.toFixed(0)}%`,\n visibleWhen: (data: Record<string, unknown>) => data.shapeType === 'rectangle',\n },\n {\n key: 'sides',\n label: 'Sides',\n defaultInternalValue: 5,\n step: 1,\n // Slider 0-100 maps to internal 3 to 20\n toSlider: (internal: number) => ((internal - 3) / 17) * 100,\n fromSlider: (slider: number) => Math.round(3 + (slider / 100) * 17),\n toDisplay: (internal: number) => internal.toFixed(0),\n visibleWhen: (data: Record<string, unknown>) => data.shapeType === 'polygon',\n },\n {\n key: 'points',\n label: 'Points',\n defaultInternalValue: 5,\n step: 1,\n // Slider 0-100 maps to internal 3 to 20\n toSlider: (internal: number) => ((internal - 3) / 17) * 100,\n fromSlider: (slider: number) => Math.round(3 + (slider / 100) * 17),\n toDisplay: (internal: number) => internal.toFixed(0),\n visibleWhen: (data: Record<string, unknown>) => data.shapeType === 'star',\n },\n {\n key: 'innerRadius',\n label: 'Inner Radius',\n defaultInternalValue: 0.4,\n // Slider 0-100 maps to internal 0.1 to 0.9\n toSlider: (internal: number) => ((internal - 0.1) / 0.8) * 100,\n fromSlider: (slider: number) => 0.1 + (slider / 100) * 0.8,\n toDisplay: (internal: number) => `${(internal * 100).toFixed(0)}%`,\n visibleWhen: (data: Record<string, unknown>) => data.shapeType === 'star',\n },\n {\n key: 'fillOpacity',\n label: 'Fill Opacity',\n defaultInternalValue: 1,\n // Slider 0-100 maps to internal 0 to 1\n toSlider: (internal: number) => internal * 100,\n fromSlider: (slider: number) => slider / 100,\n toDisplay: (internal: number) => `${(internal * 100).toFixed(0)}%`,\n },\n ],\n};\n\n/**\n * Transform definition shape used in the registry\n */\nexport interface TransformDef {\n id: string;\n label: string;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Constructors have varying signatures across element types\n Component: new (config: any) => any;\n}\n\n/**\n * Transform type definitions (built-in)\n * Combines metadata (id, label) with component class\n */\nexport const TRANSFORM_TYPES: TransformDef[] = [\n { id: 'custom', label: 'CUSTOM', Component: CustomTransform },\n // NOTE: distort transform is not yet implemented. Removed from registry\n // to prevent it from appearing in UI that iterates over registered transforms.\n // Re-add with a valid Component when implementation is ready.\n { id: 'circle', label: 'CIRCLE', Component: CircleTransform },\n { id: 'lean', label: 'LEAN', Component: LeanTransform },\n { id: 'arch', label: 'ARCH', Component: ArchTransform },\n { id: 'ascend', label: 'ASCEND', Component: AscendTransform },\n { id: 'wave', label: 'WAVE', Component: WaveTransform },\n { id: 'flag', label: 'FLAG', Component: FlagTransform },\n { id: 'shape', label: 'SHAPE', Component: ShapeElement },\n { id: 'path', label: 'PATH', Component: PathElement },\n { id: 'image', label: 'IMAGE', Component: ImageElement },\n // Use a getter to break circular dependency: GroupElement imports getTransformById\n // from this module, so GroupElement may be in the TDZ during module evaluation.\n // The getter defers access until runtime when both modules are fully initialized.\n { id: 'group', label: 'GROUP', get Component() { return GroupElement; } } as TransformDef,\n];\n\n/** Set of built-in transform type IDs (immutable) */\nconst BUILTIN_IDS = new Set(TRANSFORM_TYPES.map((t) => t.id));\n\n/** Dynamic transforms registered at runtime via the plugin API */\nconst _dynamicTransforms = new Map<string, TransformDef>();\n\n/**\n * Check if a transform type ID belongs to a built-in type\n */\nexport function isBuiltinTransformType(id: string): boolean {\n return BUILTIN_IDS.has(id);\n}\n\n/**\n * Register a dynamic transform type at runtime.\n * Throws if the id conflicts with a built-in type or is already registered.\n */\nexport function registerTransform(id: string, def: TransformDef): void {\n if (BUILTIN_IDS.has(id)) {\n throw new Error(\n `Cannot register transform type \"${id}\": conflicts with built-in type`\n );\n }\n if (_dynamicTransforms.has(id)) {\n throw new Error(\n `Cannot register transform type \"${id}\": already registered as a custom type`\n );\n }\n _dynamicTransforms.set(id, def);\n}\n\n/**\n * Unregister a dynamic transform type.\n * Returns true if the type was removed, false if it was not found.\n * Throws if attempting to unregister a built-in type.\n */\nexport function unregisterTransform(id: string): boolean {\n if (BUILTIN_IDS.has(id)) {\n throw new Error(\n `Cannot unregister transform type \"${id}\": built-in types cannot be removed`\n );\n }\n return _dynamicTransforms.delete(id);\n}\n\n/**\n * Get all registered transforms (built-in + dynamic)\n */\nexport function getAllTransforms(): TransformDef[] {\n return [...TRANSFORM_TYPES, ...Array.from(_dynamicTransforms.values())];\n}\n\n/**\n * Get transform definition by id (built-in or dynamic)\n */\nexport function getTransformById(id: string): TransformDef | undefined {\n const builtin = TRANSFORM_TYPES.find((t) => t.id === id);\n if (builtin) return builtin;\n return _dynamicTransforms.get(id);\n}\n\n/**\n * Get transform controls by id\n */\nexport function getTransformControls(id: TransformType): TransformControl[] {\n return TRANSFORM_CONTROLS[id] || [];\n}\n\n/**\n * Reset all dynamic transforms (for testing only)\n * @internal\n */\nexport function _resetDynamicTransforms(): void {\n _dynamicTransforms.clear();\n}\n","/**\n * GroupElement - Container element for grouping multiple elements together\n *\n * Key Design:\n * - Extends BaseElement for common element behavior\n * - Children maintain world coordinates (not local)\n * - Transforms propagate to all children\n * - Calculates bounds from children positions\n */\n\nimport { BaseElement } from './BaseElement.js';\nimport { TextElement } from './TextElement.js';\nimport { ImageElement } from './ImageElement.js';\nimport { ShapeElement } from './ShapeElement.js';\nimport { PathElement } from './PathElement.js';\nimport { Transform } from './Transform.js';\nimport type { AnyElementConfig, ImageTransformData } from '../types/index.js';\nimport { getThemeAccentColor } from '../constants.js';\nimport { getTransformById } from '../transforms/registry.js';\nimport type {\n GroupElementConfig,\n GroupTransformData,\n BoundingBox,\n Point,\n ResizeAnchor,\n TransformStartData,\n} from '../types/index.js';\n\n/** Child element types that can be contained in a group */\ntype ChildElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\nexport class GroupElement extends BaseElement {\n declare transformType: 'group';\n declare transformData: GroupTransformData;\n children: ChildElement[];\n isGroup: boolean = true;\n\n // Cache the base bounding box dimensions (calculated at rotation=0 or when children change)\n private _baseBBoxWidth: number = 0;\n private _baseBBoxHeight: number = 0;\n\n // Cache the rotation center (should stay fixed during rotation)\n private _rotationCenterX: number = 0;\n private _rotationCenterY: number = 0;\n\n constructor(config: Partial<GroupElementConfig> = {}) {\n super(config);\n this.transformType = 'group';\n\n // Handle children - they might be element instances or JSON objects\n const rawChildren = config.children || [];\n this.children = rawChildren.map((child) => {\n // If it's already an element instance (has methods), use it directly\n if (child instanceof BaseElement) {\n return child as ChildElement;\n }\n // Otherwise, it's a JSON object - need to reconstruct it\n // We can't import ElementFactory here due to circular dependency,\n // so we'll reconstruct children manually based on their transformType\n return this._reconstructChildFromJSON(child as unknown as Record<string, unknown>);\n });\n\n this.transformData = {\n type: 'group',\n };\n\n // Calculate initial bounds from children\n if (this.children.length > 0) {\n this.updateBoundsFromChildren(true); // Force calculation on initialization (also sets rotation center)\n }\n }\n\n /**\n * Reconstruct a child element from JSON using the transform registry.\n * Looks up the Component class by transformType and constructs it.\n * Adding new element types only requires updating the registry, not this file.\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any -- JSON deserialization: config comes from toJSON() and constructors accept Partial<Config> with varying signatures\n private _reconstructChildFromJSON(config: any): ChildElement {\n const transformType = config.transformType || config.type;\n const transformDef = getTransformById(transformType);\n\n if (!transformDef || !transformDef.Component) {\n throw new Error(`Unknown transform type: ${transformType}`);\n }\n\n // Registry Components all extend BaseElement which satisfies ChildElement union\n return new transformDef.Component(config) as ChildElement;\n }\n\n /**\n * Calculate group bounds from all children\n * Returns axis-aligned bounding box containing all children\n */\n getBoundingBox(): BoundingBox {\n if (this.children.length === 0) {\n return { x: this.x - 50, y: this.y - 50, width: 100, height: 100 };\n }\n\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n this.children.forEach((child) => {\n const bbox = child.getVisualBoundingBox();\n const corners = this._getRotatedCorners(child, bbox);\n\n corners.forEach((corner) => {\n minX = Math.min(minX, corner.x);\n minY = Math.min(minY, corner.y);\n maxX = Math.max(maxX, corner.x);\n maxY = Math.max(maxY, corner.y);\n });\n });\n\n return {\n x: minX,\n y: minY,\n width: maxX - minX,\n height: maxY - minY,\n };\n }\n\n /**\n * Visual bounding box uses the oriented bounding box for groups\n * This ensures handles and hover effects stay aligned with the dotted border during rotation\n */\n getVisualBoundingBox(): BoundingBox {\n return this.getOrientedBoundingBox();\n }\n\n /**\n * Get rotation anchor - returns the cached rotation center\n * This ensures consistent rotation behavior even if this.x/this.y change\n */\n getRotationAnchor(): Point {\n return {\n x: this._rotationCenterX || this.x,\n y: this._rotationCenterY || this.y,\n };\n }\n\n /**\n * Get the four corners of an element's bounding box in world space\n */\n private _getRotatedCorners(element: ChildElement, bbox: BoundingBox): Point[] {\n const transform = new Transform(element);\n\n // Calculate the center of the bounding box\n const centerX = bbox.x + bbox.width / 2;\n const centerY = bbox.y + bbox.height / 2;\n\n // Calculate offsets from the element's center to the bbox center\n const offsetX = centerX - element.x;\n const offsetY = centerY - element.y;\n\n // Define corners relative to the bbox center\n const corners = [\n { localX: offsetX - bbox.width / 2, localY: offsetY - bbox.height / 2 },\n { localX: offsetX + bbox.width / 2, localY: offsetY - bbox.height / 2 },\n { localX: offsetX - bbox.width / 2, localY: offsetY + bbox.height / 2 },\n { localX: offsetX + bbox.width / 2, localY: offsetY + bbox.height / 2 },\n ];\n\n return corners.map((corner) => transform.localToWorld(corner.localX, corner.localY));\n }\n\n /**\n * Update group center and size from children\n * @param forceRecalcDimensions - Force recalculation of base dimensions (use when children are modified, not during rotation)\n */\n updateBoundsFromChildren(forceRecalcDimensions: boolean = false): void {\n const bbox = this.getBoundingBox();\n this.x = bbox.x + bbox.width / 2;\n this.y = bbox.y + bbox.height / 2;\n\n // Only recalculate base dimensions when explicitly requested or when cache is empty\n // NEVER recalculate during rotation - the cache should remain stable\n if (forceRecalcDimensions || this._baseBBoxWidth === 0 || this._baseBBoxHeight === 0) {\n const dims = this._calculateInitialDimensions();\n this._baseBBoxWidth = dims.width;\n this._baseBBoxHeight = dims.height;\n // Update rotation center when dimensions are recalculated (group creation or children changed)\n this._rotationCenterX = this.x;\n this._rotationCenterY = this.y;\n }\n }\n\n /**\n * Calculate the axis-aligned bounding box dimensions at current state\n * This should only be called when the group is created or when children change (not during rotation)\n *\n * Like multi-selection, we simply cache the current axis-aligned bbox dimensions\n * and use them for rendering the selection border.\n */\n private _calculateInitialDimensions(): { width: number; height: number } {\n if (this.children.length === 0) {\n return { width: 100, height: 100 };\n }\n\n // Simply use the current axis-aligned bounding box dimensions\n // This is the same approach as multi-selection OBB initialization\n const bbox = this.getBoundingBox();\n const result = {\n width: bbox.width,\n height: bbox.height,\n };\n\n return result;\n }\n\n /**\n * Render the group with clipping support\n * Elements with isClipping=true clip all elements below them in the group\n */\n render(ctx: CanvasRenderingContext2D, isSelected: boolean = false, isHovered: boolean = false): void {\n // Filter out invisible children\n const visibleChildren = this.children.filter((child) => child.visible !== false);\n\n // Check if any visible children have clipping enabled\n const hasClipping = visibleChildren.some((child) => child.isClipping);\n\n\n if (!hasClipping) {\n // No clipping - render normally\n visibleChildren.forEach((child) => {\n child.render(ctx, false);\n });\n } else {\n // Has clipping - render with masks (passing visible children only)\n this._renderWithClipping(ctx, visibleChildren);\n }\n\n // Render group outline when selected or hovered\n if (isSelected) {\n this._renderGroupOutline(ctx, 1.0);\n } else if (isHovered) {\n this._renderGroupOutline(ctx, 0.3);\n }\n }\n\n /**\n * Render group with clipping masks\n * Elements with isClipping=true clip all elements below them\n */\n private _renderWithClipping(\n ctx: CanvasRenderingContext2D,\n children: ChildElement[]\n ): void {\n ctx.save();\n\n const canvas = ctx.canvas;\n const width = canvas.width;\n const height = canvas.height;\n const transform = ctx.getTransform();\n\n // Group children into clipping segments\n // Each segment has content elements and optionally a clipping mask\n const segments: {\n content: ChildElement[];\n mask?: ChildElement;\n }[] = [];\n let currentContent: ChildElement[] = [];\n\n // Iterate through children (bottom to top in visual order)\n for (const child of children) {\n if (child.isClipping) {\n // This element clips everything we've collected so far\n if (currentContent.length > 0) {\n segments.push({ content: currentContent, mask: child });\n currentContent = [];\n } else {\n // Clipping mask with no content below it - just render the mask normally\n segments.push({ content: [child] });\n }\n } else {\n currentContent.push(child);\n }\n }\n\n // Add remaining content without clipping\n if (currentContent.length > 0) {\n segments.push({ content: currentContent });\n }\n\n\n // Render each segment\n for (const segment of segments) {\n if (!segment.mask) {\n // No mask - render normally\n segment.content.forEach((child) => child.render(ctx, false, false));\n } else {\n // Render with clipping mask\n\n const contentCanvas = document.createElement('canvas');\n contentCanvas.width = width;\n contentCanvas.height = height;\n const contentCtx = contentCanvas.getContext('2d');\n if (!contentCtx) continue;\n\n contentCtx.setTransform(transform);\n\n // Render content\n segment.content.forEach((child) => {\n child.render(contentCtx, false, false);\n });\n\n // Create mask\n const maskCanvas = document.createElement('canvas');\n maskCanvas.width = width;\n maskCanvas.height = height;\n const maskCtx = maskCanvas.getContext('2d');\n if (!maskCtx) continue;\n\n maskCtx.setTransform(transform);\n segment.mask.render(maskCtx, false, false);\n\n // Fallback for empty text elements\n if (segment.mask.transformType !== 'image' && segment.mask.transformType !== 'shape') {\n const bbox = segment.mask.getBoundingBox();\n maskCtx.save();\n maskCtx.fillStyle = '#FFFFFF';\n maskCtx.fillRect(bbox.x, bbox.y, bbox.width, bbox.height);\n maskCtx.restore();\n }\n\n // Apply mask\n contentCtx.globalCompositeOperation = 'destination-in';\n contentCtx.setTransform(1, 0, 0, 1, 0, 0);\n contentCtx.drawImage(maskCanvas, 0, 0);\n\n // Draw to main canvas\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.drawImage(contentCanvas, 0, 0);\n ctx.setTransform(transform);\n }\n }\n\n ctx.restore();\n }\n\n /**\n * Render dashed outline around group\n * Uses an oriented bounding box that doesn't change size during rotation\n */\n private _renderGroupOutline(ctx: CanvasRenderingContext2D, opacity: number = 1.0): void {\n // Get the oriented bounding box (tight fit ignoring rotation)\n const obb = this.getOrientedBoundingBox();\n\n ctx.save();\n ctx.globalAlpha = opacity;\n ctx.strokeStyle = getThemeAccentColor();\n ctx.lineWidth = 2;\n ctx.setLineDash([5, 5]);\n\n // Apply rotation around the CACHED rotation center (not current this.x, this.y)\n // The OBB is already centered at the rotation center\n const centerX = this._rotationCenterX || this.x;\n const centerY = this._rotationCenterY || this.y;\n\n if (this.rotation !== 0) {\n ctx.translate(centerX, centerY);\n ctx.rotate((-this.rotation * Math.PI) / 180);\n ctx.translate(-centerX, -centerY);\n }\n\n // Draw the rectangle using OBB coordinates\n ctx.strokeRect(obb.x, obb.y, obb.width, obb.height);\n ctx.setLineDash([]);\n ctx.restore();\n }\n\n /**\n * Get oriented bounding box (uses cached base dimensions for stability during rotation)\n *\n * This follows the same approach as multi-selection:\n * - Cache the initial axis-aligned bbox dimensions when group is created\n * - Use those cached dimensions during rotation (never recalculate)\n * - This keeps the selection border tight and stable during rotation\n */\n private getOrientedBoundingBox(): BoundingBox {\n if (this.children.length === 0) {\n return { x: this.x - 50, y: this.y - 50, width: 100, height: 100 };\n }\n\n // Use cached base dimensions if available, otherwise calculate current bbox\n let width: number, height: number;\n\n if (this._baseBBoxWidth > 0 && this._baseBBoxHeight > 0) {\n // Use cached dimensions (set when group was created or children changed)\n width = this._baseBBoxWidth;\n height = this._baseBBoxHeight;\n } else {\n // Fall back to current bbox (happens on first render before cache is set)\n const bbox = this.getBoundingBox();\n width = bbox.width;\n height = bbox.height;\n\n // Cache for future use\n this._baseBBoxWidth = width;\n this._baseBBoxHeight = height;\n }\n\n // Return box centered at the cached rotation center (not this.x/this.y which might drift)\n const centerX = this._rotationCenterX || this.x;\n const centerY = this._rotationCenterY || this.y;\n return {\n x: centerX - width / 2,\n y: centerY - height / 2,\n width,\n height,\n };\n }\n\n /**\n * Move the group (moves all children)\n */\n move(dx: number, dy: number): void {\n // Update group center position\n this.x += dx;\n this.y += dy;\n\n // Update cached rotation center to follow the group\n this._rotationCenterX += dx;\n this._rotationCenterY += dy;\n\n // Move all children by the same amount\n this.children.forEach((child) => {\n child.move(dx, dy);\n });\n }\n\n /**\n * Rotate the group (rotates all children around group center)\n */\n setRotation(newRotation: number): void {\n const deltaRotation = newRotation - this.rotation;\n this.rotation = newRotation;\n\n // Use cached rotation center for consistency\n const centerX = this._rotationCenterX || this.x;\n const centerY = this._rotationCenterY || this.y;\n\n this.children.forEach((child) => {\n // Rotate child's position around the cached group center\n const rotated = Transform.rotatePointAroundAnchor(child.x, child.y, centerX, centerY, deltaRotation);\n child.x = rotated.x;\n child.y = rotated.y;\n\n // Add rotation to child's own rotation\n child.setRotation(child.rotation + deltaRotation);\n });\n\n // Update this.x and this.y to stay at the rotation center\n // This prevents drift during rotation\n this.x = centerX;\n this.y = centerY;\n }\n\n /**\n * Resize the group (scales all children proportionally)\n * Uses the same fixed-corner approach as multi-selection resize\n */\n resize(anchor: ResizeAnchor, newWidth: number, newHeight: number, startData: TransformStartData): void {\n if (!startData.childrenStartData || !startData.width || !startData.height) {\n return;\n }\n\n const scaleX = newWidth / startData.width;\n const scaleY = newHeight / startData.height;\n const scale = (scaleX + scaleY) / 2; // Uniform scale\n\n // Calculate the fixed corner in LOCAL space (unrotated)\n let localFixedX: number, localFixedY: number;\n\n switch (anchor) {\n case 'top-left':\n localFixedX = startData.width / 2;\n localFixedY = startData.height / 2;\n break;\n case 'top-right':\n localFixedX = -startData.width / 2;\n localFixedY = startData.height / 2;\n break;\n case 'bottom-left':\n localFixedX = startData.width / 2;\n localFixedY = -startData.height / 2;\n break;\n case 'bottom-right':\n localFixedX = -startData.width / 2;\n localFixedY = -startData.height / 2;\n break;\n default:\n // Fallback to center\n localFixedX = 0;\n localFixedY = 0;\n }\n\n // Rotate the fixed corner to world space using the group's rotation\n const rotationRad = (-startData.rotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n const fixedX = startData.x + (localFixedX * cos - localFixedY * sin);\n const fixedY = startData.y + (localFixedX * sin + localFixedY * cos);\n\n this.children.forEach((child, index) => {\n const childStart = startData.childrenStartData![index];\n\n // Scale position offset from the FIXED corner (not center)\n // This is the same approach as multi-selection resize\n const offsetX = childStart.x - fixedX;\n const offsetY = childStart.y - fixedY;\n const newX = fixedX + offsetX * scale;\n const newY = fixedY + offsetY * scale;\n\n child.x = newX;\n child.y = newY;\n\n // Scale child dimensions using the same approach as multi-selection resize\n if (child instanceof ImageElement) {\n // For images, scale the transform data directly\n const childTransform = childStart.transformData as unknown as ImageTransformData;\n const newChildWidth = childTransform.width * scale;\n const newChildHeight = childTransform.height * scale;\n child.transformData.width = newChildWidth;\n child.transformData.height = newChildHeight;\n } else if (child instanceof GroupElement) {\n // For nested groups, recursively resize\n const newChildWidth = (childStart.width ?? 0) * scale;\n const newChildHeight = (childStart.height ?? 0) * scale;\n child.resize(anchor, newChildWidth, newChildHeight, childStart);\n } else {\n // For text elements, use the element's resize method (handles transform-specific logic)\n const newChildWidth = (childStart.width ?? 0) * scale;\n const newChildHeight = (childStart.height ?? 0) * scale;\n child.resize(anchor, newChildWidth, newChildHeight, childStart);\n }\n });\n\n // Scale the cached dimensions directly (proportional to the scale factor)\n this._baseBBoxWidth = startData.width! * scale;\n this._baseBBoxHeight = startData.height! * scale;\n\n // Calculate the new group center by scaling the offset from the fixed corner\n const centerOffsetX = startData.x - fixedX;\n const centerOffsetY = startData.y - fixedY;\n this.x = fixedX + centerOffsetX * scale;\n this.y = fixedY + centerOffsetY * scale;\n\n // Update the rotation center to the new center position\n this._rotationCenterX = this.x;\n this._rotationCenterY = this.y;\n }\n\n /**\n * Get transform start data (includes children data)\n * Use cached base dimensions instead of axis-aligned bbox for consistency with visual border\n */\n getTransformStartData(): TransformStartData {\n // Use the cached base dimensions which match the oriented bounding box (visual border)\n // This prevents the jump when resizing a rotated group\n const width = this._baseBBoxWidth > 0 ? this._baseBBoxWidth : this.getBoundingBox().width;\n const height = this._baseBBoxHeight > 0 ? this._baseBBoxHeight : this.getBoundingBox().height;\n\n // Use the cached rotation center coordinates to match where the visual border is actually drawn\n // This prevents position jump when starting a resize\n const centerX = this._rotationCenterX || this.x;\n const centerY = this._rotationCenterY || this.y;\n\n return {\n id: this.id,\n x: centerX,\n y: centerY,\n width: width,\n height: height,\n rotation: this.rotation,\n transformData: this.transformData as unknown as Record<string, unknown>,\n childrenStartData: this.children.map((child) => child.getTransformStartData()),\n };\n }\n\n /**\n * Hit test - check if point is inside group's oriented bounding box\n * This allows clicking anywhere within the selection border to drag the group\n */\n hitTest(x: number, y: number): boolean {\n // Use the oriented bounding box (matches the visual selection border)\n const obb = this.getOrientedBoundingBox();\n const centerX = this._rotationCenterX || this.x;\n const centerY = this._rotationCenterY || this.y;\n\n // If not rotated, use simple axis-aligned test\n if (this.rotation === 0) {\n const isInside = x >= obb.x && x <= obb.x + obb.width && y >= obb.y && y <= obb.y + obb.height;\n if (isInside) {\n }\n return isInside;\n }\n\n // For rotated groups, transform the click point to local coordinates\n // then check against the unrotated rectangle\n const rotationRad = (this.rotation * Math.PI) / 180;\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Translate point to origin (relative to rotation center)\n const dx = x - centerX;\n const dy = y - centerY;\n\n // Rotate point back to local space (inverse rotation)\n const localX = centerX + (dx * cos - dy * sin);\n const localY = centerY + (dx * sin + dy * cos);\n\n // Check if the local point is within the OBB rectangle\n const isInside = localX >= obb.x && localX <= obb.x + obb.width && localY >= obb.y && localY <= obb.y + obb.height;\n\n if (isInside) {\n }\n\n return isInside;\n }\n\n /**\n * Only corner handles for groups (uniform scale)\n */\n getEnabledAnchors(): ResizeAnchor[] {\n return ['top-left', 'top-right', 'bottom-left', 'bottom-right'];\n }\n\n /**\n * Serialize to JSON\n */\n toJSON(): GroupElementConfig {\n const baseJSON = super.toJSON();\n return {\n ...baseJSON,\n transformType: 'group',\n transformData: {\n type: 'group',\n },\n children: this.children.map((child) => child.toJSON()) as AnyElementConfig[],\n };\n }\n\n /**\n * Clone the group (deep copy with children)\n */\n clone(): GroupElement {\n const clonedChildren = this.children.map((child) => child.clone());\n const clonedGroup = new GroupElement({\n id: this.id,\n x: this.x,\n y: this.y,\n rotation: this.rotation,\n // Children are element instances here; the constructor handles both instances and configs\n children: clonedChildren as unknown as AnyElementConfig[],\n });\n // Preserve cached dimensions to avoid recalculation during rotation\n clonedGroup._baseBBoxWidth = this._baseBBoxWidth;\n clonedGroup._baseBBoxHeight = this._baseBBoxHeight;\n // Preserve cached rotation center\n clonedGroup._rotationCenterX = this._rotationCenterX;\n clonedGroup._rotationCenterY = this._rotationCenterY;\n\n // Copy opacity\n clonedGroup.opacity = this.opacity;\n\n // Copy layer properties\n clonedGroup.name = this.name;\n clonedGroup.visible = this.visible;\n clonedGroup.locked = this.locked;\n\n // Copy effects properties (blendMode covers isClipping via getter)\n if (this.blendMode) clonedGroup.blendMode = this.blendMode;\n if (this.knockoutParts) clonedGroup.knockoutParts = { ...this.knockoutParts };\n if (this.stroke) clonedGroup.stroke = { ...this.stroke };\n if (this.masks) clonedGroup.masks = this.masks.map((m) => ({ ...m }));\n if (this.distressEffect) clonedGroup.distressEffect = { ...this.distressEffect };\n\n return clonedGroup;\n }\n\n /**\n * Add a child to the group\n */\n addChild(child: ChildElement): void {\n this.children.push(child);\n this.updateBoundsFromChildren(true); // Recalculate dimensions when children added\n }\n\n /**\n * Remove a child from the group\n */\n removeChild(childId: string): boolean {\n const index = this.children.findIndex((c) => c.id === childId);\n if (index >= 0) {\n this.children.splice(index, 1);\n this.updateBoundsFromChildren(true); // Recalculate dimensions when children removed\n return true;\n }\n return false;\n }\n\n /**\n * Get all children (for edit mode)\n */\n getChildren(): ChildElement[] {\n return [...this.children];\n }\n\n /**\n * Find which child (if any) was clicked at the given point\n * Returns the child element or null if no child was hit\n */\n hitTestChild(x: number, y: number): ChildElement | null {\n // Test children in reverse order (top to bottom)\n for (let i = this.children.length - 1; i >= 0; i--) {\n const child = this.children[i];\n if (child.hitTest(x, y)) {\n return child;\n }\n }\n return null;\n }\n}\n","/**\n * ElementStore - Normalized element storage with O(1) lookups by ID.\n *\n * Replaces the flat array pattern (`EditorElement[]`) with a Map-based\n * structure while preserving render order via an ordered ID list.\n *\n * Immutable update pattern: every mutation method returns a new instance\n * so React can detect state changes via referential equality.\n *\n * @example\n * ```ts\n * const store = new ElementStore(elements);\n * const el = store.get('element-1'); // O(1) lookup\n * const next = store.update(updatedElement); // Returns new instance\n * const arr = next.toArray(); // Backwards-compatible array\n * ```\n *\n * @module\n */\n\nimport type { TextElement } from './TextElement.js';\nimport type { ImageElement } from './ImageElement.js';\nimport type { GroupElement } from './GroupElement.js';\nimport type { ShapeElement } from './ShapeElement.js';\nimport type { PathElement } from './PathElement.js';\n\n/** Union of all element types that can be stored. */\nexport type StoreElement = TextElement | ImageElement | GroupElement | ShapeElement | PathElement;\n\nexport class ElementStore {\n private byId: Map<string, StoreElement>;\n private order: string[]; // Maintains render order\n private _cachedArray: StoreElement[] | null = null;\n\n constructor(elements?: StoreElement[]) {\n this.byId = new Map();\n this.order = [];\n if (elements) {\n for (const el of elements) {\n this.byId.set(el.id, el);\n this.order.push(el.id);\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Read operations (O(1) where possible)\n // ---------------------------------------------------------------------------\n\n /** O(1) lookup by ID. */\n get(id: string): StoreElement | undefined {\n return this.byId.get(id);\n }\n\n /** Check whether the store contains an element with the given ID. */\n has(id: string): boolean {\n return this.byId.has(id);\n }\n\n /** Get all elements in render order. */\n getAll(): StoreElement[] {\n return this.order.map((id) => this.byId.get(id)!);\n }\n\n /** Number of elements in the store. */\n get size(): number {\n return this.byId.size;\n }\n\n /** Get the ordered ID list (read-only copy). */\n getOrder(): readonly string[] {\n return this.order;\n }\n\n /** O(n) lookup by name. Returns the first element with the given name, or undefined. */\n getByName(name: string): StoreElement | undefined {\n for (const id of this.order) {\n const el = this.byId.get(id);\n if (el && el.name === name) return el;\n }\n return undefined;\n }\n\n /** O(n) lookup by name. Returns all elements with the given name in render order. */\n getAllByName(name: string): StoreElement[] {\n const result: StoreElement[] = [];\n for (const id of this.order) {\n const el = this.byId.get(id);\n if (el && el.name === name) result.push(el);\n }\n return result;\n }\n\n // ---------------------------------------------------------------------------\n // Immutable mutation operations (return new ElementStore instances)\n // ---------------------------------------------------------------------------\n\n /** Replace an element in-place (preserves order). */\n update(element: StoreElement): ElementStore {\n const next = this.clone();\n next.byId.set(element.id, element);\n // If the element didn't exist, append to order\n if (!this.byId.has(element.id)) {\n next.order.push(element.id);\n }\n return next;\n }\n\n /**\n * Replace an element found by a predicate with a new element.\n * This mirrors `setElements(prev => prev.map(el => el.id === id ? newEl : el))`.\n */\n updateById(id: string, element: StoreElement): ElementStore {\n if (!this.byId.has(id)) return this;\n const next = this.clone();\n // If the id changed (unlikely but safe), remove old and insert new at same position\n if (id !== element.id) {\n next.byId.delete(id);\n const idx = next.order.indexOf(id);\n if (idx !== -1) next.order[idx] = element.id;\n }\n next.byId.set(element.id, element);\n return next;\n }\n\n /** Add an element at the end of the render order. */\n add(element: StoreElement): ElementStore {\n const next = this.clone();\n next.byId.set(element.id, element);\n if (!this.byId.has(element.id)) {\n next.order.push(element.id);\n }\n return next;\n }\n\n /** Insert an element after a specific element ID. */\n insertAfter(element: StoreElement, afterId: string): ElementStore {\n const next = this.clone();\n next.byId.set(element.id, element);\n const idx = next.order.indexOf(afterId);\n if (idx !== -1) {\n next.order.splice(idx + 1, 0, element.id);\n } else {\n // Fallback: append to end\n next.order.push(element.id);\n }\n return next;\n }\n\n /** Remove an element by ID. */\n remove(id: string): ElementStore {\n if (!this.byId.has(id)) return this;\n const next = this.clone();\n next.byId.delete(id);\n next.order = next.order.filter((oid) => oid !== id);\n return next;\n }\n\n /** Reorder element to a new index in the render order. */\n reorder(id: string, newIndex: number): ElementStore {\n const next = this.clone();\n next.order = next.order.filter((oid) => oid !== id);\n next.order.splice(newIndex, 0, id);\n return next;\n }\n\n /**\n * Apply a batch update: replace elements whose IDs match.\n * Returns a new store. Useful for applying multiple updates at once\n * (e.g., reordering by an ID array from undo/redo).\n */\n replaceAll(elements: StoreElement[]): ElementStore {\n return new ElementStore(elements);\n }\n\n /**\n * Filter elements by predicate while preserving order.\n * Returns a new store containing only elements that match.\n */\n filter(predicate: (el: StoreElement) => boolean): ElementStore {\n const filtered = this.getAll().filter(predicate);\n return new ElementStore(filtered);\n }\n\n /**\n * Map over elements in render order, returning a new store.\n * The mapping function receives each element and returns a (possibly modified) element.\n */\n map(fn: (el: StoreElement) => StoreElement): ElementStore {\n const mapped = this.getAll().map(fn);\n return new ElementStore(mapped);\n }\n\n /**\n * Set a completely new ordering for all elements.\n * IDs not in the new order are dropped; new IDs not in the store are ignored.\n */\n setOrder(newOrder: string[]): ElementStore {\n const next = this.clone();\n next.order = newOrder.filter((id) => next.byId.has(id));\n return next;\n }\n\n // ---------------------------------------------------------------------------\n // Conversion helpers\n // ---------------------------------------------------------------------------\n\n /** Convert to a plain array (backwards-compatible with `EditorElement[]`). Cached since the store is immutable. */\n toArray(): StoreElement[] {\n if (this._cachedArray === null) {\n this._cachedArray = this.getAll();\n }\n return this._cachedArray;\n }\n\n /** Create from a plain array. */\n static fromArray(elements: StoreElement[]): ElementStore {\n return new ElementStore(elements);\n }\n\n // ---------------------------------------------------------------------------\n // Internal\n // ---------------------------------------------------------------------------\n\n /** Create a shallow clone (new Map/Array, same element references). */\n private clone(): ElementStore {\n const store = new ElementStore();\n store.byId = new Map(this.byId);\n store.order = [...this.order];\n return store;\n }\n}\n","/**\n * CommandHistory - Manages undo/redo with the Command pattern\n * Stores operations as commands that can be undone and redone\n */\n\nimport type { BaseElement } from './BaseElement.js';\nimport type { Command as CommandInterface } from '../types/index.js';\nimport { ArtboardElement } from './ArtboardElement.js';\nimport { ArtboardManager } from './ArtboardManager.js';\n\n/**\n * Base Command class\n * All commands must implement execute() and undo()\n */\nexport class Command implements CommandInterface {\n /**\n * Execute the command\n */\n execute(): void {\n throw new Error('Command.execute() must be implemented');\n }\n\n /**\n * Undo the command\n */\n undo(): void {\n throw new Error('Command.undo() must be implemented');\n }\n}\n\n/**\n * UpdateElementCommand - Command to update an element's properties\n */\nexport class UpdateElementCommand extends Command {\n elementId: string;\n oldElement: BaseElement | null;\n newElement: BaseElement | null;\n onUpdate: (element: BaseElement) => void;\n\n constructor(\n elementId: string,\n oldElement: BaseElement | null,\n newElement: BaseElement | null,\n onUpdate: (element: BaseElement) => void\n ) {\n super();\n this.elementId = elementId;\n this.oldElement = oldElement ? oldElement.clone() : null;\n this.newElement = newElement ? newElement.clone() : null;\n this.onUpdate = onUpdate;\n }\n\n execute(): void {\n if (this.newElement) {\n this.onUpdate(this.newElement);\n }\n }\n\n undo(): void {\n if (this.oldElement) {\n this.onUpdate(this.oldElement);\n }\n }\n}\n\n/**\n * AddElementCommand - Command to add a new element\n */\nexport class AddElementCommand extends Command {\n element: BaseElement;\n onAdd: (element: BaseElement) => void;\n onRemove: (id: string) => void;\n\n constructor(element: BaseElement, onAdd: (element: BaseElement) => void, onRemove: (id: string) => void) {\n super();\n this.element = element.clone();\n this.onAdd = onAdd;\n this.onRemove = onRemove;\n }\n\n execute(): void {\n this.onAdd(this.element);\n }\n\n undo(): void {\n this.onRemove(this.element.id);\n }\n}\n\n/**\n * RemoveElementCommand - Command to remove an element\n */\nexport class RemoveElementCommand extends Command {\n element: BaseElement;\n onAdd: (element: BaseElement) => void;\n onRemove: (id: string) => void;\n\n constructor(element: BaseElement, onAdd: (element: BaseElement) => void, onRemove: (id: string) => void) {\n super();\n this.element = element.clone();\n this.onAdd = onAdd;\n this.onRemove = onRemove;\n }\n\n execute(): void {\n this.onRemove(this.element.id);\n }\n\n undo(): void {\n this.onAdd(this.element);\n }\n}\n\n/**\n * BatchCommand - Execute multiple commands as one atomic operation\n */\nexport class BatchCommand extends Command {\n commands: Command[];\n\n constructor(commands: Command[] = []) {\n super();\n this.commands = commands;\n }\n\n execute(): void {\n this.commands.forEach((cmd) => cmd.execute());\n }\n\n undo(): void {\n // Undo in reverse order\n for (let i = this.commands.length - 1; i >= 0; i--) {\n this.commands[i].undo();\n }\n }\n}\n\n/**\n * CompoundCommand - Wraps an array of commands into a single undo entry.\n * Used by executeBatch() to group multiple operations atomically.\n *\n * - execute(): runs all commands in order\n * - undo(): reverts all commands in reverse order\n */\nexport class CompoundCommand extends Command {\n private commands: Command[];\n\n constructor(commands: Command[]) {\n super();\n this.commands = commands;\n }\n\n execute(): void {\n this.commands.forEach((cmd) => cmd.execute());\n }\n\n undo(): void {\n // Undo in reverse order for correct rollback\n for (let i = this.commands.length - 1; i >= 0; i--) {\n this.commands[i].undo();\n }\n }\n}\n\n/**\n * CommandHistory - Manages undo/redo history\n */\nexport class CommandHistory {\n history: Command[];\n currentIndex: number;\n maxSize: number;\n\n constructor(maxSize: number = 50) {\n this.history = []; // Array of executed commands\n this.currentIndex = -1; // Current position in history\n this.maxSize = maxSize;\n }\n\n /**\n * Execute a command and add it to history\n */\n execute(command: Command): void {\n // Execute the command\n command.execute();\n\n // Remove any commands after current index (redo history)\n this.history = this.history.slice(0, this.currentIndex + 1);\n\n // Add new command to history\n this.history.push(command);\n this.currentIndex++;\n\n // Limit history size\n if (this.history.length > this.maxSize) {\n this.history.shift();\n this.currentIndex--;\n }\n }\n\n /**\n * Execute multiple commands as a single atomic undo entry.\n * All commands are wrapped in a CompoundCommand so that\n * undo/redo treats the entire batch as one operation.\n *\n * No-op if commands array is empty.\n */\n executeBatch(commands: Command[]): void {\n if (commands.length === 0) {\n return;\n }\n\n const compound = new CompoundCommand(commands);\n this.execute(compound);\n }\n\n /**\n * Undo the last command\n */\n undo(): boolean {\n if (!this.canUndo()) {\n return false;\n }\n\n const command = this.history[this.currentIndex];\n command.undo();\n this.currentIndex--;\n return true;\n }\n\n /**\n * Redo the next command\n */\n redo(): boolean {\n if (!this.canRedo()) {\n return false;\n }\n\n this.currentIndex++;\n const command = this.history[this.currentIndex];\n command.execute();\n return true;\n }\n\n /**\n * Check if undo is available\n */\n canUndo(): boolean {\n return this.currentIndex >= 0;\n }\n\n /**\n * Check if redo is available\n */\n canRedo(): boolean {\n return this.currentIndex < this.history.length - 1;\n }\n\n /**\n * Clear all history\n */\n clear(): void {\n this.history = [];\n this.currentIndex = -1;\n }\n\n /**\n * Get current state info\n */\n getState(): { canUndo: boolean; canRedo: boolean; historySize: number; currentIndex: number } {\n return {\n canUndo: this.canUndo(),\n canRedo: this.canRedo(),\n historySize: this.history.length,\n currentIndex: this.currentIndex,\n };\n }\n}\n\n/**\n * CreateArtboardCommand - Command to create a new artboard\n */\nexport class CreateArtboardCommand extends Command {\n artboard: ArtboardElement;\n artboardManager: ArtboardManager;\n\n constructor(artboard: ArtboardElement, artboardManager: ArtboardManager) {\n super();\n this.artboard = artboard.clone();\n this.artboardManager = artboardManager;\n }\n\n execute(): void {\n // Re-create the artboard in the manager\n const artboard = this.artboard.clone();\n this.artboardManager.createArtboard({\n id: artboard.id,\n name: artboard.name,\n x: artboard.x,\n y: artboard.y,\n width: artboard.width,\n height: artboard.height,\n backgroundColor: artboard.backgroundColor,\n });\n }\n\n undo(): void {\n this.artboardManager.deleteArtboard(this.artboard.id);\n }\n}\n\n/**\n * DeleteArtboardCommand - Command to delete an artboard\n */\nexport class DeleteArtboardCommand extends Command {\n artboard: ArtboardElement;\n artboardManager: ArtboardManager;\n elementsOnArtboard: string[];\n\n constructor(artboard: ArtboardElement, artboardManager: ArtboardManager) {\n super();\n this.artboard = artboard.clone();\n this.artboardManager = artboardManager;\n this.elementsOnArtboard = artboard.getElementIds();\n }\n\n execute(): void {\n this.artboardManager.deleteArtboard(this.artboard.id);\n }\n\n undo(): void {\n // Re-create the artboard\n const artboard = this.artboard.clone();\n this.artboardManager.createArtboard({\n id: artboard.id,\n name: artboard.name,\n x: artboard.x,\n y: artboard.y,\n width: artboard.width,\n height: artboard.height,\n backgroundColor: artboard.backgroundColor,\n elementIds: this.elementsOnArtboard,\n });\n\n // Restore element mappings\n this.elementsOnArtboard.forEach((elementId) => {\n this.artboardManager.addElementToArtboard(elementId, artboard.id);\n });\n }\n}\n\n/**\n * UpdateArtboardCommand - Command to update artboard properties\n */\nexport class UpdateArtboardCommand extends Command {\n artboardId: string;\n oldProperties: Partial<ArtboardElement>;\n newProperties: Partial<ArtboardElement>;\n artboardManager: ArtboardManager;\n\n constructor(\n artboardId: string,\n oldProperties: Partial<ArtboardElement>,\n newProperties: Partial<ArtboardElement>,\n artboardManager: ArtboardManager\n ) {\n super();\n this.artboardId = artboardId;\n this.oldProperties = { ...oldProperties };\n this.newProperties = { ...newProperties };\n this.artboardManager = artboardManager;\n }\n\n execute(): void {\n this.artboardManager.updateArtboard(this.artboardId, this.newProperties);\n }\n\n undo(): void {\n this.artboardManager.updateArtboard(this.artboardId, this.oldProperties);\n }\n}\n\n/**\n * ReorderElementCommand - Command to reorder elements in the list\n */\nexport class ReorderElementCommand extends Command {\n draggedId: string;\n targetId: string;\n position: 'before' | 'after';\n oldOrder: string[];\n newOrder: string[];\n onReorder: (elementIds: string[]) => void;\n\n constructor(\n draggedId: string,\n targetId: string,\n position: 'before' | 'after',\n currentOrder: string[],\n onReorder: (elementIds: string[]) => void\n ) {\n super();\n this.draggedId = draggedId;\n this.targetId = targetId;\n this.position = position;\n this.oldOrder = [...currentOrder];\n this.onReorder = onReorder;\n\n // Calculate new order\n this.newOrder = this.calculateNewOrder();\n }\n\n private calculateNewOrder(): string[] {\n const order = [...this.oldOrder];\n\n // Remove dragged element\n const draggedIndex = order.indexOf(this.draggedId);\n if (draggedIndex === -1) return order;\n order.splice(draggedIndex, 1);\n\n // Find target index in the new array (after removal)\n const targetIndex = order.indexOf(this.targetId);\n if (targetIndex === -1) return order;\n\n // Insert at the appropriate position\n const insertIndex = this.position === 'before' ? targetIndex : targetIndex + 1;\n order.splice(insertIndex, 0, this.draggedId);\n\n return order;\n }\n\n execute(): void {\n this.onReorder(this.newOrder);\n }\n\n undo(): void {\n this.onReorder(this.oldOrder);\n }\n}\n\nexport default CommandHistory;\n","/**\n * HybridHistoryManager - Two-tier undo/redo system\n *\n * Manages:\n * - Global history: Artboard create/delete/rename operations\n * - Per-artboard history: Element operations within each artboard\n *\n * Smart undo/redo automatically determines which history to use based on:\n * - Active artboard\n * - Last operation type\n */\n\nimport { CommandHistory, Command } from './CommandHistory.js';\nimport type { ArtboardManager } from './ArtboardManager.js';\nimport { createLogger } from '../utils/logger.js';\n\nconst logger = createLogger('HybridHistoryManager');\n\ntype HistoryType = 'global' | 'artboard';\n\ninterface HistoryEntry {\n type: HistoryType;\n artboardId?: string; // For artboard-level operations\n timestamp: number;\n}\n\nexport class HybridHistoryManager {\n private globalHistory: CommandHistory;\n private artboardHistories: Map<string, CommandHistory>;\n private lastOperation: HistoryEntry | null;\n private artboardManager: ArtboardManager;\n private maxHistorySize: number;\n private commandExecutedCallbacks: Array<(command: Command, type: HistoryType, artboardId?: string) => void> = [];\n\n constructor(artboardManager: ArtboardManager, maxHistorySize: number = 50) {\n this.globalHistory = new CommandHistory(maxHistorySize);\n this.artboardHistories = new Map();\n this.lastOperation = null;\n this.artboardManager = artboardManager;\n this.maxHistorySize = maxHistorySize;\n }\n\n /**\n * Register a callback to be called whenever a command is executed\n * Returns an unsubscribe function\n */\n onCommandExecuted(callback: (command: Command, type: HistoryType, artboardId?: string) => void): () => void {\n this.commandExecutedCallbacks.push(callback);\n return () => {\n const index = this.commandExecutedCallbacks.indexOf(callback);\n if (index > -1) {\n this.commandExecutedCallbacks.splice(index, 1);\n }\n };\n }\n\n /**\n * Notify all callbacks that a command was executed\n */\n private notifyCommandExecuted(command: Command, type: HistoryType, artboardId?: string): void {\n for (const callback of this.commandExecutedCallbacks) {\n try {\n callback(command, type, artboardId);\n } catch (error) {\n logger.error('Error in command executed callback:', error);\n }\n }\n }\n\n /**\n * Execute a global command (artboard-level operation)\n */\n executeGlobal(command: Command): void {\n this.globalHistory.execute(command);\n this.lastOperation = {\n type: 'global',\n timestamp: Date.now(),\n };\n this.notifyCommandExecuted(command, 'global');\n }\n\n /**\n * Execute a batch of commands as a single global undo entry.\n * No-op if commands array is empty.\n */\n executeBatchGlobal(commands: Command[]): void {\n if (commands.length === 0) {\n return;\n }\n this.globalHistory.executeBatch(commands);\n this.lastOperation = {\n type: 'global',\n timestamp: Date.now(),\n };\n // Notify with the compound command that was created\n const compoundCommand = this.globalHistory.history[this.globalHistory.currentIndex];\n this.notifyCommandExecuted(compoundCommand, 'global');\n }\n\n /**\n * Execute a batch of commands as a single undo entry on a specific artboard.\n * No-op if commands array is empty.\n */\n executeBatchOnArtboard(artboardId: string, commands: Command[]): void {\n if (commands.length === 0) {\n return;\n }\n let history = this.artboardHistories.get(artboardId);\n if (!history) {\n history = new CommandHistory(this.maxHistorySize);\n this.artboardHistories.set(artboardId, history);\n }\n history.executeBatch(commands);\n this.lastOperation = {\n type: 'artboard',\n artboardId,\n timestamp: Date.now(),\n };\n const compoundCommand = history.history[history.currentIndex];\n this.notifyCommandExecuted(compoundCommand, 'artboard', artboardId);\n }\n\n /**\n * Execute a command on a specific artboard\n */\n executeOnArtboard(artboardId: string, command: Command): void {\n // Get or create history for this artboard\n let history = this.artboardHistories.get(artboardId);\n if (!history) {\n history = new CommandHistory(this.maxHistorySize);\n this.artboardHistories.set(artboardId, history);\n }\n\n history.execute(command);\n this.lastOperation = {\n type: 'artboard',\n artboardId,\n timestamp: Date.now(),\n };\n this.notifyCommandExecuted(command, 'artboard', artboardId);\n }\n\n /**\n * Smart undo - determines which history to use\n */\n undo(): boolean {\n // If there's a last operation, undo from that history\n if (this.lastOperation) {\n if (this.lastOperation.type === 'global') {\n return this.undoGlobal();\n } else if (this.lastOperation.artboardId) {\n return this.undoArtboard(this.lastOperation.artboardId);\n }\n }\n\n // Otherwise, try active artboard first, then global\n const activeArtboardId = this.artboardManager.getActiveArtboardId();\n if (activeArtboardId && this.canUndoArtboard(activeArtboardId)) {\n return this.undoArtboard(activeArtboardId);\n }\n\n if (this.canUndoGlobal()) {\n return this.undoGlobal();\n }\n\n return false;\n }\n\n /**\n * Smart redo - determines which history to use\n */\n redo(): boolean {\n // If there's a last operation, redo from that history\n if (this.lastOperation) {\n if (this.lastOperation.type === 'global') {\n return this.redoGlobal();\n } else if (this.lastOperation.artboardId) {\n return this.redoArtboard(this.lastOperation.artboardId);\n }\n }\n\n // Otherwise, try active artboard first, then global\n const activeArtboardId = this.artboardManager.getActiveArtboardId();\n if (activeArtboardId && this.canRedoArtboard(activeArtboardId)) {\n return this.redoArtboard(activeArtboardId);\n }\n\n if (this.canRedoGlobal()) {\n return this.redoGlobal();\n }\n\n return false;\n }\n\n /**\n * Undo global operation\n */\n undoGlobal(): boolean {\n const result = this.globalHistory.undo();\n if (result) {\n this.updateLastOperation('global');\n }\n return result;\n }\n\n /**\n * Redo global operation\n */\n redoGlobal(): boolean {\n const result = this.globalHistory.redo();\n if (result) {\n this.updateLastOperation('global');\n }\n return result;\n }\n\n /**\n * Undo artboard operation\n */\n undoArtboard(artboardId: string): boolean {\n const history = this.artboardHistories.get(artboardId);\n if (!history) {\n return false;\n }\n\n const result = history.undo();\n if (result) {\n this.updateLastOperation('artboard', artboardId);\n }\n return result;\n }\n\n /**\n * Redo artboard operation\n */\n redoArtboard(artboardId: string): boolean {\n const history = this.artboardHistories.get(artboardId);\n if (!history) {\n return false;\n }\n\n const result = history.redo();\n if (result) {\n this.updateLastOperation('artboard', artboardId);\n }\n return result;\n }\n\n /**\n * Check if global undo is available\n */\n canUndoGlobal(): boolean {\n return this.globalHistory.canUndo();\n }\n\n /**\n * Check if global redo is available\n */\n canRedoGlobal(): boolean {\n return this.globalHistory.canRedo();\n }\n\n /**\n * Check if artboard undo is available\n */\n canUndoArtboard(artboardId: string): boolean {\n const history = this.artboardHistories.get(artboardId);\n return history ? history.canUndo() : false;\n }\n\n /**\n * Check if artboard redo is available\n */\n canRedoArtboard(artboardId: string): boolean {\n const history = this.artboardHistories.get(artboardId);\n return history ? history.canRedo() : false;\n }\n\n /**\n * Check if any undo is available (smart check)\n */\n canUndo(): boolean {\n // Check last operation first\n if (this.lastOperation) {\n if (this.lastOperation.type === 'global') {\n return this.canUndoGlobal();\n } else if (this.lastOperation.artboardId) {\n return this.canUndoArtboard(this.lastOperation.artboardId);\n }\n }\n\n // Otherwise check active artboard or global\n const activeArtboardId = this.artboardManager.getActiveArtboardId();\n if (activeArtboardId && this.canUndoArtboard(activeArtboardId)) {\n return true;\n }\n\n return this.canUndoGlobal();\n }\n\n /**\n * Check if any redo is available (smart check)\n */\n canRedo(): boolean {\n // Check last operation first\n if (this.lastOperation) {\n if (this.lastOperation.type === 'global') {\n return this.canRedoGlobal();\n } else if (this.lastOperation.artboardId) {\n return this.canRedoArtboard(this.lastOperation.artboardId);\n }\n }\n\n // Otherwise check active artboard or global\n const activeArtboardId = this.artboardManager.getActiveArtboardId();\n if (activeArtboardId && this.canRedoArtboard(activeArtboardId)) {\n return true;\n }\n\n return this.canRedoGlobal();\n }\n\n /**\n * Clear all histories\n */\n clear(): void {\n this.globalHistory.clear();\n this.artboardHistories.clear();\n this.lastOperation = null;\n }\n\n /**\n * Clear history for a specific artboard\n */\n clearArtboardHistory(artboardId: string): void {\n this.artboardHistories.delete(artboardId);\n if (this.lastOperation?.artboardId === artboardId) {\n this.lastOperation = null;\n }\n }\n\n /**\n * Get history for a specific artboard (create if doesn't exist)\n */\n getArtboardHistory(artboardId: string): CommandHistory {\n let history = this.artboardHistories.get(artboardId);\n if (!history) {\n history = new CommandHistory(this.maxHistorySize);\n this.artboardHistories.set(artboardId, history);\n }\n return history;\n }\n\n /**\n * Get global history\n */\n getGlobalHistory(): CommandHistory {\n return this.globalHistory;\n }\n\n /**\n * Get state of all histories\n */\n getState(): {\n global: {\n canUndo: boolean;\n canRedo: boolean;\n historySize: number;\n };\n artboards: Map<\n string,\n {\n canUndo: boolean;\n canRedo: boolean;\n historySize: number;\n }\n >;\n lastOperation: HistoryEntry | null;\n } {\n const globalState = this.globalHistory.getState();\n const artboardStates = new Map();\n\n this.artboardHistories.forEach((history, artboardId) => {\n const state = history.getState();\n artboardStates.set(artboardId, {\n canUndo: state.canUndo,\n canRedo: state.canRedo,\n historySize: state.historySize,\n });\n });\n\n return {\n global: {\n canUndo: globalState.canUndo,\n canRedo: globalState.canRedo,\n historySize: globalState.historySize,\n },\n artboards: artboardStates,\n lastOperation: this.lastOperation,\n };\n }\n\n /**\n * Update last operation tracker\n */\n private updateLastOperation(type: HistoryType, artboardId?: string): void {\n this.lastOperation = {\n type,\n artboardId,\n timestamp: Date.now(),\n };\n }\n\n /**\n * Get description of what will be undone\n */\n getUndoDescription(): string {\n if (this.lastOperation) {\n if (this.lastOperation.type === 'global') {\n return 'Undo artboard operation';\n } else if (this.lastOperation.artboardId) {\n const artboard = this.artboardManager.getArtboard(this.lastOperation.artboardId);\n return artboard ? `Undo in \"${artboard.name}\"` : 'Undo element operation';\n }\n }\n\n const activeArtboardId = this.artboardManager.getActiveArtboardId();\n if (activeArtboardId && this.canUndoArtboard(activeArtboardId)) {\n const artboard = this.artboardManager.getArtboard(activeArtboardId);\n return artboard ? `Undo in \"${artboard.name}\"` : 'Undo element operation';\n }\n\n if (this.canUndoGlobal()) {\n return 'Undo artboard operation';\n }\n\n return 'Nothing to undo';\n }\n\n /**\n * Get description of what will be redone\n */\n getRedoDescription(): string {\n if (this.lastOperation) {\n if (this.lastOperation.type === 'global') {\n return 'Redo artboard operation';\n } else if (this.lastOperation.artboardId) {\n const artboard = this.artboardManager.getArtboard(this.lastOperation.artboardId);\n return artboard ? `Redo in \"${artboard.name}\"` : 'Redo element operation';\n }\n }\n\n const activeArtboardId = this.artboardManager.getActiveArtboardId();\n if (activeArtboardId && this.canRedoArtboard(activeArtboardId)) {\n const artboard = this.artboardManager.getArtboard(activeArtboardId);\n return artboard ? `Redo in \"${artboard.name}\"` : 'Redo element operation';\n }\n\n if (this.canRedoGlobal()) {\n return 'Redo artboard operation';\n }\n\n return 'Nothing to redo';\n }\n}\n\nexport default HybridHistoryManager;\n"],"names":["getDefaultExportFromCjs","x","browser","process","cachedSetTimeout","cachedClearTimeout","defaultSetTimout","defaultClearTimeout","runTimeout","fun","runClearTimeout","marker","queue","draining","currentQueue","queueIndex","cleanUpNextTick","drainQueue","timeout","len","args","i","Item","array","noop","name","dir","browserExports","process$1","nextArtboardId","ArtboardElement","config","px","py","element","bbox","elementId","tolerance","inXRange","inYRange","nearLeft","nearRight","nearTop","nearBottom","updates","newWidth","newHeight","newX","newY","ArtboardManager","artboard","artboardId","elementIds","remainingArtboards","previousArtboardId","previousArtboard","y","artboards","fromIndex","toIndex","entries","moved","a","data","elements","e","_artboardId","validElementIds","id","RotationUtils","degrees","normalized","nextId","BaseElement","value","visualBbox","rotationAnchor","rotationRad","cos","sin","dx","dy","localX","localY","transformedX","transformedY","ctx","m","newRotation","_a","Transform","worldX","worldY","rad","rotX","rotY","pointX","pointY","anchorX","anchorY","angleDegrees","elementSnapshot","TSHIRT_FONTS","getFontNames","f","CATEGORY_LABELS","getThemeAccentColor","target","getCurrentTheme","getThemeShapeFillColor","getThemeSpacingColor","getThemeAccentColorWithAlpha","alpha","color","hex","r","g","b","getThemeTextSelectionColor","getThemeAccentHoverColor","getThemeTooltipBackground","getThemeTooltipForeground","getThemeTooltipShadowColor","bg","getThemeArtboardBorderColor","getThemeArtboardLabelColor","getThemeHoverBorderColor","getThemePenPathColor","getThemePenAnchorFillColor","getThemePenHandleColor","PREVIEW_ELEMENT_OPACITY","DEFAULT_ARTBOARD_COLOR","ROTATION_HANDLE_DISTANCE","CORNER_HANDLE_VISUAL_RADIUS","EDGE_HANDLE_VISUAL_LENGTH","CORNER_HANDLE_HIT_RADIUS","EDGE_HANDLE_LENGTH","EDGE_HANDLE_HIT_WIDTH","SPACING_SNAP_THRESHOLD","SPACING_LABEL_TEXT_COLOR","SPACING_LABEL_BORDER_RADIUS","MIN_CROP_SIZE","FONT_FAMILIES","FONT_SIZES","HORIZONTAL_PADDING","LINE_HEIGHT_MULTIPLIER","MIN_WIDTH","MIN_FONT_SIZE","MAX_FONT_SIZE","SYSTEM_FONTS","WAVE_SKEW_FACTOR","LogLevel","Logger","level","enabled","_message","_args","message","scopeName","ScopedLogger","logger","createLogger","listeners","subscribeToImageLoads","listener","emitImageLoaded","error","ImageCache","url","existing","img","entry","totalRefs","IMAGE_LOAD_TIMEOUT_MS","IMAGE_LOAD_MAX_RETRIES","IMAGE_LOAD_BASE_DELAY_MS","imageTransformData","startData","ImageElement","_b","_c","_d","_e","_f","_g","_h","_i","_j","isSvg","settled","settle","originalOnload","originalOnerror","ev","delay","tempImg","canvas","blob","cropWidth","cropHeight","localOffsetX","localOffsetY","flipH","flipV","worldOffsetX","worldOffsetY","frameCenterX","frameCenterY","local","halfWidth","halfHeight","halfCropWidth","halfCropHeight","_isSelected","_isHovered","borderRadius","radius","fullImageX","fullImageY","bgColor","textColor","anchor","widthScale","heightScale","proposedScale","effectiveAnchor","finalScale","currentScaleRelativeToStart","oldCropCenterXLocal","oldCropCenterYLocal","worldOffset","fixedCornerWorldX","fixedCornerWorldY","fixedCornerLocalX","fixedCornerLocalY","fixedCornerWorldOffset","fixedCornerWorldOffsetX","fixedCornerWorldOffsetY","newFixedCornerLocalX","newFixedCornerLocalY","newFixedCornerWorldOffset","newFixedCornerWorldOffsetX","newFixedCornerWorldOffsetY","newFrameCenterX","newFrameCenterY","oldCropCenterWorldX","oldCropCenterWorldY","oldCropWorldWidth","oldCropWorldHeight","newCropWidthNorm","newCropHeightNorm","localOffset","cropCenterXLocal","cropCenterYLocal","newCropX","newCropY","newCropCenterXLocal","newCropCenterYLocal","newLocalOffsetX","newLocalOffsetY","newWorldOffset","newWorldOffsetX","newWorldOffsetY","cropBox","corner","flippedX","flippedY","rotatedX","rotatedY","zoom","hitScale","CORNER_HIT_RADIUS_SCALED","EDGE_LENGTH_SCALED","EDGE_WIDTH_SCALED","corners","worldCorner","edges","edge","worldEdge","cropX","cropY","adjustPosition","clampedWidth","clampedHeight","clampedX","clampedY","oldCropCenterX","oldCropCenterY","newCropCenterX","newCropCenterY","width","height","halfW","halfH","fixedCornerLocal","startFrameCenter","scale","fixedCornerWorld","newFrameWidth","newFrameHeight","cropWorldWidth","cropWorldHeight","cropNormWidth","cropNormHeight","newFrameCenter","cropCenterWorld","cropCenterLocalX","cropCenterLocalY","cropState","cropLeft","cropRight","cropTop","cropBottom","currentScale","lo","hi","testScale","json","imageUrl","configWithoutUrl","cloned","RichText","spans","text","style","s","charIndex","currentIndex","span","start","end","newSpans","spanStart","spanEnd","overlapStart","overlapEnd","index","offset","length","deleteStart","deleteEnd","merged","current","next","property","isCustomTransform","isCircleTransform","isArchTransform","isWaveTransform","isFlagTransform","isImageTransform","isGroupTransform","isTextElementConfig","isImageElementConfig","isCustomElementConfig","isCircleElementConfig","isGroupElementConfig","isShapeElementConfig","isPathElementConfig","isShapeTransform","isPathTransform","hasStroke","hasMasks","isKnockout","hasDistressEffect","TextElement","_ctx","Constructor","newText","newRichText","richText","newFontSize","newColor","oldColor","_anchor","_newWidth","_newHeight","_startData","_measureCanvas","getMeasureCanvas","buildFontString","fontSize","fontFamily","bold","italic","parts","wrapText","maxWidth","lockedLineCount","strict","explicitLines","allWrappedLines","line","wrappedLine","leadingSpacesMatch","trailingSpacesMatch","leadingSpaces","trailingSpaces","words","lines","wordsPerLine","lineWords","allText","allTextWithSpaces","allTextWidth","isSmallSize","wrapTolerance","currentLine","lineTolerance","word","chars","charBuffer","char","testBuffer","testLine","firstLineWithSpace","lastIdx","lastLineWithSpace","measureTextWidth","getFontMetrics","metrics","ascent","descent","calculateVisualBoundsWithSpaceCollapsing","hasExplicitNewlines","paragraphMetadata","_","maxLineWidth","fontMetrics","totalLineCount","isParagraphStart","isParagraphEnd","visibleText","leadingSpacesCount","trailingSpacesCount","lineWidth","lineHeight","lineSpacing","createOffscreenCanvas","applyStrokeToChar","stroke","prevAlpha","renderTransformWithOffscreenStroke","elementData","renderFn","strokeOpacity","elementAlpha","hasStrokeOpacity","hasElementOpacity","strokeWidth","td","textEstimate","containerSpan","estimatedSpan","featherPad","padding","estWidth","archExtra","radiusExtra","estHeight","transform","offW","offH","offCanvas","offCtx","elemX","elemY","centerPixelX","centerPixelY","drawBackX","drawBackY","savedStrokeOpa","savedOpacity","savedEnabled","renderCircleTransform","renderCircleTransformDirect","transformData","weight","charWidths","totalWidth","sum","w","currentAngle","angleStep","renderWaveTransform","renderWaveTransformDirect","currentX","charWidth","normalizedX","slope","renderArchTransform","renderArchTransformDirect","renderAscendTransform","renderAscendTransformDirect","angleRad","renderLeanTransform","renderLeanTransformDirect","skewAngle","renderFlagTransform","renderFlagTransformDirect","distanceFromCenter","flagAmplitude","cosComponent","sinComponent","createTextPath","_renderingContext","textElement","textWidth","actualWidth","createImagePath","maxRadius","createCirclePath","createRectPath","applyStrokeStyle","renderTextStroke","renderingContext","isKnockoutRender","renderTextStrokeViaOffscreen","renderTextStrokeDirect","feather","customWidth","originalOpacity","textAlign","availableWidth","visualHeight","topLeftX","topLeftY","xPos","yPos","renderImageStroke","renderPathStroke","pathFn","log","fontkitModule","getFontkit","FontAnalyzerService","oldestKey","oldestTime","key","cssUrl","css","fontFaceRegex","fontFaces","bestUrl","bestScore","fontFaceContent","urlMatch","hasUnicodeRange","unicodeRangeMatch","score","cacheKey","cached","fontUrl","response","arrayBuffer","buffer","fontOrCollection","font","limit","glyphs","maxGlyphs","glyphId","glyph","codePoints","unicode","baseCharMatch","friendlyName","num","category","nameLower","character","alternates","codePoint","defaultGlyph","baseName","foundCount","pattern","gsub","feature","tag","lookupIndex","lookup","subtable","coverage","substituteGlyphIndex","coverageIndex","altSubtable","alternateSet","altGlyphIndex","features","uniqueFeatures","glyphIndex","size","glyphWidth","glyphHeight","glyphCenterX","glyphCenterY","canvasCenterX","canvasCenterY","svgPath","path2D","ranges","range","substitute","count","FontAnalyzer","renderTextWithOpenTypeFeaturesSync","options","featureArray","availableFeatures","feat","featuresObject","run","startX","position","pathData","dMatch","renderTextWithGlyphOverridesSync","glyphOverrides","hasPUA","str","cp","overrideMap","override","wrapRichTextSpans","getMeasureContext","measureText","stylesMatch","s1","s2","mergedSpans","fullText","charToSpan","spanIndex","currentWord","wordStart","currentLineWidth","finishLine","_wordIdx","wordSpans","currentSpanIdx","currentText","charIdx","spanIdx","wordWidth","lastSpan","splitRichTextIntoLines","part","renderRichTextFillOnly","lineSpans","wrappedLines","idx","lineMetrics","lineIndex","spanWidths","lineAscent","fullLineText","adjustedLineWidth","charIndexInLine","spanStartIndex","spanEndIndex","hiddenCharsInSpan","trailingSpaceStartIndex","leadingTrimAmount","visibleCharsInSpan","elementFontMetrics","totalHeight","lineData","baselineY","underline","strikethrough","renderText","renderedWidth","underlineY","strikethroughY","_measureCtx","applySpaceLayoutRules","renderTextFillOnly","lineText","renderMultilineText","alignment","containerWidth","horizontalPadding","openTypeFeatures","containsPUACharacters","hasOpenTypeFeatures","hasGlyphOverrides","hasPUACharacters","needsFontkitRendering","fontkitFont","err","charOffset","lineLength","lineGlyphOverrides","relativeIndex","underlineX","strikethroughX","renderCustomTransform","renderCustomTransformNormal","decorationX","strikeY","renderTextElement","elementOpacity","renderTextElementWithOffscreen","previousAlpha","renderTextElementInner","prev","getTransformWidth","setTransformWidth","handleCornerResize","startTransformData","startTransformWidth","startWidth","calculatedFontSize","scaledRichText","scaledCharSize","elemTransformData","scaledWidth","handleSideResize","oldWidth","widthChange","standardResize","isCornerHandle","isSideHandle","CustomTransform","wLine","maxFontHeight","maxAscent","spanFontSize","spanFontFamily","spanBold","spanItalic","elementMetrics","standardHeight","extraAscent","extraDescent","visualWidth","serialized","offsetX","offsetY","minWidth","calculateFixedCornerPosition","newDimensions","activeAnchor","rotation","fixedLocalX","fixedLocalY","fixedWorldX","fixedWorldY","newFixedLocalX","newFixedLocalY","calculateRotationHandlePosition","calculateResizeHandles","createHandle","type","worldPos","cursor","getCursorForWorldPosition","hitTestCircle","cx","cy","hitTestRect","rect","translatedX","translatedY","getBoundingBoxCenter","calculateAngle","centerX","centerY","isNear","value1","value2","threshold","handleWorldX","handleWorldY","centerWorldX","centerWorldY","handleType","angle","normalizedAngle","getCanvasScale","setZoomInvariantStroke","dashPattern","CircleTransform","effectiveRadius","diameter","effectiveFontSize","arcAngle","startAngle","endAngle","innerRadius","outerRadius","minX","maxX","minY","maxY","samples","innerX","innerY","outerX","outerY","circleStartData","avgDimension","startAvgDimension","scaleChange","newScale","clampedScale","newPosition","targetSize","newBaseFontSize","ArchTransform","archHeightAbs","yOffset","skewFactor","_char","lineY","skewedX","skewedY","archStartData","deltaWidth","centerShift","WAVE_DEFAULTS","FLAG_DEFAULTS","ARCH_DEFAULTS","LEAN_DEFAULTS","ASCEND_DEFAULTS","WaveTransform","amplitudeAbs","FlagTransform","flagStartData","LeanTransform","leanAmountAbs","skewX","leanStartData","AscendTransform","rise","ascendStartData","ShapeElement","_k","defaultShapeType","shapeType","sides","points","fillOpacity","radiusX","radiusY","innerRadiusRatio","thickness","fixedCorner","oppositeAnchor","newBbox","fixedCornerOffset","startBbox","fixedX","fixedY","PathElement","localBounds","p0","p1","p2","p3","t","mt","mt2","mt3","t2","t3","currentPoint","nextPoint","u","point","halfStroke","bounds","closed","fillEnabled","fillColor","strokeEnabled","strokeColor","centerOffsetX","centerOffsetY","firstPoint","cp1x","cp1y","cp2x","cp2y","oldHeight","scaleX","scaleY","startPoints","p","TRANSFORM_CONTROLS","internal","slider","TRANSFORM_TYPES","GroupElement","BUILTIN_IDS","_dynamicTransforms","registerTransform","def","unregisterTransform","getTransformById","builtin","getTransformControls","rawChildren","child","transformType","transformDef","forceRecalcDimensions","dims","isSelected","isHovered","visibleChildren","children","segments","currentContent","segment","contentCanvas","contentCtx","maskCanvas","maskCtx","opacity","obb","deltaRotation","rotated","localFixedX","localFixedY","childStart","childTransform","newChildWidth","newChildHeight","clonedChildren","clonedGroup","childId","c","ElementStore","el","result","afterId","oid","newIndex","predicate","filtered","fn","mapped","newOrder","store","Command","UpdateElementCommand","oldElement","newElement","onUpdate","AddElementCommand","onAdd","onRemove","RemoveElementCommand","CompoundCommand","commands","cmd","CommandHistory","maxSize","command","compound","CreateArtboardCommand","artboardManager","DeleteArtboardCommand","UpdateArtboardCommand","oldProperties","newProperties","ReorderElementCommand","draggedId","targetId","currentOrder","onReorder","order","draggedIndex","targetIndex","insertIndex","HybridHistoryManager","maxHistorySize","callback","compoundCommand","history","activeArtboardId","globalState","artboardStates","state"],"mappings":"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","x_google_ignoreList":[0]}
|