@snowcone-app/canvas 0.21.2 → 0.21.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/STYLING_CONTRACT.md +14 -0
  2. package/dist/{CanvasRenderer-B0szG0Us.js → CanvasRenderer-6_HhR4-q.js} +170 -189
  3. package/dist/CanvasRenderer-6_HhR4-q.js.map +1 -0
  4. package/dist/{ContextualToolbars-BYnovWnN.js → ContextualToolbars-BDEt9ND0.js} +1619 -1846
  5. package/dist/ContextualToolbars-BDEt9ND0.js.map +1 -0
  6. package/dist/{EditorCanvasImpl-BmLHjXe7.js → EditorCanvasImpl-M7rw1c1k.js} +5 -5
  7. package/dist/{EditorCanvasImpl-BmLHjXe7.js.map → EditorCanvasImpl-M7rw1c1k.js.map} +1 -1
  8. package/dist/ImagePanel-BwqFDKvW.js +951 -0
  9. package/dist/ImagePanel-BwqFDKvW.js.map +1 -0
  10. package/dist/{SnowconeCanvas-Bj2773ym.js → SnowconeCanvas-Cx7nXlBI.js} +19 -18
  11. package/dist/{SnowconeCanvas-Bj2773ym.js.map → SnowconeCanvas-Cx7nXlBI.js.map} +1 -1
  12. package/dist/{UnifiedFontService-C8gaspRD.js → UnifiedFontService-CC3CSmH5.js} +1042 -984
  13. package/dist/UnifiedFontService-CC3CSmH5.js.map +1 -0
  14. package/dist/advanced.mjs +97 -96
  15. package/dist/advanced.mjs.map +1 -1
  16. package/dist/components/editor/LazyImageBrowser.d.ts +7 -0
  17. package/dist/components/editor/commerceStore.d.ts +13 -0
  18. package/dist/custom-icons-BW6hqmkL.js +236 -0
  19. package/dist/custom-icons-BW6hqmkL.js.map +1 -0
  20. package/dist/{embed-pM7ZkFuz.js → embed-QBZkgWDr.js} +287 -272
  21. package/dist/{embed-pM7ZkFuz.js.map → embed-QBZkgWDr.js.map} +1 -1
  22. package/dist/embed.mjs +3 -3
  23. package/dist/guards-8rkFAMBs.js +24 -0
  24. package/dist/guards-8rkFAMBs.js.map +1 -0
  25. package/dist/index.mjs +5 -5
  26. package/dist/internals.mjs +1 -1
  27. package/dist/style.css +1 -1
  28. package/dist/{template-fields-BsnHMf1i.js → template-fields-C2pSfMBh.js} +57 -39
  29. package/dist/template-fields-C2pSfMBh.js.map +1 -0
  30. package/dist/validation-C9Fe9ahK.js +8367 -0
  31. package/dist/validation-C9Fe9ahK.js.map +1 -0
  32. package/example/package.json +1 -1
  33. package/package.json +1 -1
  34. package/dist/CanvasRenderer-B0szG0Us.js.map +0 -1
  35. package/dist/ContextualToolbars-BYnovWnN.js.map +0 -1
  36. package/dist/UnifiedFontService-C8gaspRD.js.map +0 -1
  37. package/dist/template-fields-BsnHMf1i.js.map +0 -1
  38. package/dist/validation-DdVeWnlo.js +0 -9308
  39. package/dist/validation-DdVeWnlo.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CanvasRenderer-6_HhR4-q.js","sources":["../src/theme.ts","../src/components/CanvasEditor/renderers/renderingConstants.ts","../src/core/TransformHandles.ts","../src/core/ResizeHandler.ts","../src/core/ResizePipeline.ts","../src/core/InteractionStateMachine.ts","../src/core/RotationAnchorResolver.ts","../src/core/SpatialGrid.ts","../src/core/AlignmentSnapSystem.ts","../src/core/SpacingSystem.ts","../src/core/ArtboardRenderer.ts","../src/core/SelectionRenderer.ts","../src/core/HoverRenderer.ts","../src/core/InteractionFeedbackRenderer.ts","../src/core/EditModeRenderer.ts","../src/core/CanvasRenderer.ts"],"sourcesContent":["/**\n * Theme constants for the application\n * Centralized color palette and design tokens\n */\n\nimport { getThemeAccentColor, getThemeAccentHoverColorWithAlpha } from './constants.js';\n\nexport const colors = {\n // Primary brand color (dynamically from theme)\n get primary() {\n return getThemeAccentColor();\n },\n\n // Accent colors\n accent: {\n blue: '#0066FF',\n purple: '#8B5CF6',\n orange: '#F97316',\n },\n\n // UI colors\n background: '#f5f5f5',\n surface: '#ffffff',\n border: '#e5e5e5',\n\n // Text colors\n text: {\n primary: '#1f2937',\n secondary: '#6b7280',\n tertiary: '#9ca3af',\n },\n\n // State colors\n success: '#10b981',\n warning: '#f59e0b',\n error: '#ef4444',\n info: '#3b82f6',\n\n // Interaction states (dynamically from theme)\n get hover() {\n return getThemeAccentHoverColorWithAlpha(0.1);\n },\n get active() {\n return getThemeAccentHoverColorWithAlpha(0.2);\n },\n disabled: '#d1d5db',\n};\n\nexport const theme = {\n colors,\n\n // Spacing scale (in pixels)\n spacing: {\n xs: 4,\n sm: 8,\n md: 16,\n lg: 24,\n xl: 32,\n xxl: 48,\n },\n\n // Border radius\n borderRadius: {\n sm: 4,\n md: 8,\n lg: 12,\n full: 9999,\n },\n\n // Typography\n fontSize: {\n xs: 12,\n sm: 14,\n base: 16,\n lg: 18,\n xl: 20,\n xxl: 24,\n },\n\n // Z-index layers\n zIndex: {\n base: 0,\n dropdown: 10,\n modal: 100,\n tooltip: 1000,\n editor: 1000,\n },\n\n // Canvas-specific\n canvas: {\n get selectionColor() {\n return getThemeAccentColor();\n },\n get snapGuideColor() {\n return getThemeAccentColor();\n },\n get rotationHandleColor() {\n return getThemeAccentColor();\n },\n handleSize: 8,\n handleHoverSize: 10,\n snapThreshold: 8, // pixels\n snapGuideDash: [5, 5],\n snapGuideWidth: 1,\n selectionPadding: 1, // pixels - visual padding around selection box\n\n // Spacing indicators\n spacingIndicatorColor: '#FF0000', // Red for spacing measurement lines\n spacingIndicatorWidth: 1.5, // Slightly thicker than snap guides\n spacingDetectionThreshold: 50, // Show spacing within 50px\n spacingLabelFontSize: 11,\n spacingLabelFontFamily: \"-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif\",\n spacingLabelBackground: '#FFFFFF', // White background for labels\n },\n};\n\nexport default theme;\n","/**\n * Shared rendering constants for canvas UI elements\n * Provides DRY (Don't Repeat Yourself) constants for consistent rendering\n * All values should be divided by zoom to maintain visual consistency at different zoom levels\n */\n\n/**\n * Line width for selection borders, handles, and UI elements\n * Should be divided by zoom: lineWidth = SELECTION_LINE_WIDTH / zoom\n */\nexport const SELECTION_LINE_WIDTH = 2;\n\n/**\n * Corner resize handle radius\n * Should be divided by zoom: radius = HANDLE_RADIUS / zoom\n */\nexport const HANDLE_RADIUS = 6;\n\n/**\n * Rotation handle radius (larger than corner handles)\n * Should be divided by zoom: radius = ROTATION_HANDLE_RADIUS / zoom\n */\nexport const ROTATION_HANDLE_RADIUS = 16;\n\n/**\n * Dash pattern for dotted lines (multi-selection group bounds)\n * Should be divided by zoom: setLineDash([DASH_SIZE / zoom, DASH_GAP / zoom])\n */\nexport const DASH_SIZE = 5;\nexport const DASH_GAP = 5;\n\n/**\n * Rotation handle icon scale factor\n */\nexport const ROTATION_ICON_SCALE = 0.5;\n\n/**\n * Helper function to get zoom-adjusted line width\n */\nexport function getLineWidth(zoom: number): number {\n return SELECTION_LINE_WIDTH / zoom;\n}\n\n/**\n * Helper function to get zoom-adjusted handle radius\n */\nexport function getHandleRadius(zoom: number): number {\n return HANDLE_RADIUS / zoom;\n}\n\n/**\n * Helper function to get zoom-adjusted rotation handle radius\n */\nexport function getRotationHandleRadius(zoom: number): number {\n return ROTATION_HANDLE_RADIUS / zoom;\n}\n\n/**\n * Helper function to get zoom-adjusted dash pattern\n */\nexport function getDashPattern(zoom: number): number[] {\n return [DASH_SIZE / zoom, DASH_GAP / zoom];\n}\n\n/**\n * Renders the rotation handle icon (refresh-ccw icon from Lucide)\n * Internal helper - use renderRotationHandle for complete handle rendering\n * @param ctx - Canvas rendering context\n * @param x - X position of the rotation handle center (in current coordinate space)\n * @param y - Y position of the rotation handle center (in current coordinate space)\n * @param zoom - Current zoom level (scales icon to maintain consistent screen size)\n */\nfunction renderRotationHandleIcon(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n zoom: number\n): void {\n ctx.save();\n\n // Draw refresh-ccw icon (Lucide icon)\n // Scale inversely with zoom to maintain constant screen size\n const scale = ROTATION_ICON_SCALE / zoom;\n ctx.translate(x, y);\n ctx.scale(scale, scale);\n\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1.5; // Consistent line width for clean appearance\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n\n // Center the icon (24x24 viewBox)\n ctx.translate(-12, -12);\n\n // Path 1: M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\n ctx.beginPath();\n ctx.moveTo(21, 12);\n ctx.arc(12, 12, 9, 0, -Math.PI / 2, true);\n ctx.bezierCurveTo(3.5, 4.5, 4, 6, 3, 8);\n ctx.stroke();\n\n // Path 2: M3 3v5h5 (arrow at top-left)\n ctx.beginPath();\n ctx.moveTo(3, 3);\n ctx.lineTo(3, 8);\n ctx.lineTo(8, 8);\n ctx.stroke();\n\n // Path 3: M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16\n ctx.beginPath();\n ctx.moveTo(3, 12);\n ctx.arc(12, 12, 9, Math.PI, Math.PI / 2, true);\n ctx.bezierCurveTo(20.5, 19.5, 20, 18, 21, 16);\n ctx.stroke();\n\n // Path 4: M16 16h5v5 (arrow at bottom-right)\n ctx.beginPath();\n ctx.moveTo(16, 16);\n ctx.lineTo(21, 16);\n ctx.lineTo(21, 21);\n ctx.stroke();\n\n ctx.restore();\n}\n\n/**\n * Renders complete rotation handle (circle background + icon)\n * Provides consistent rotation handle rendering across single and multi-selection\n * Works in world coordinate space with zoom compensation for UI elements\n *\n * @param ctx - Canvas rendering context\n * @param x - X position in world coordinates\n * @param y - Y position in world coordinates\n * @param zoom - Current zoom level (default 1.0)\n * @param accentColor - Border color (default from theme)\n * @param isHovering - Whether handle is being hovered (increases border width)\n */\nexport function renderRotationHandle(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n zoom: number = 1.0,\n accentColor: string = '#3b82f6',\n isHovering: boolean = false\n): void {\n const rotationHandleRadius = getRotationHandleRadius(zoom);\n ctx.save();\n\n // Circle background - white fill with accent border\n ctx.fillStyle = '#fff';\n ctx.strokeStyle = accentColor;\n ctx.lineWidth = isHovering ? getLineWidth(zoom) * 1.25 : getLineWidth(zoom);\n\n ctx.beginPath();\n ctx.arc(x, y, rotationHandleRadius, 0, Math.PI * 2);\n ctx.fill();\n ctx.stroke();\n\n // Draw rotation icon\n renderRotationHandleIcon(ctx, x, y, zoom);\n\n ctx.restore();\n}\n","/**\n * TransformHandles - Manages rendering and interaction with transform handles\n * Provides consistent resize and rotation behavior across all text elements\n */\n\nimport { calculateResizeHandles, calculateRotationHandlePosition, getCursorForWorldPosition, hitTestCircle } from './GeometryUtils.js';\nimport type { BaseElement } from './BaseElement.js';\nimport type { HandleInfo, Point } from '../types/index.js';\nimport { theme } from '../theme.js';\nimport { getThemeAccentColor } from '../constants.js';\nimport {\n HANDLE_RADIUS as BASE_HANDLE_RADIUS,\n ROTATION_ICON_SCALE,\n} from '../components/CanvasEditor/renderers/renderingConstants.js';\nimport { CORNER_HANDLE_HIT_RADIUS } from '../constants.js';\n\nconst HANDLE_SIZE = BASE_HANDLE_RADIUS * 2;\n\nexport class TransformHandles {\n resizeHandles: HandleInfo[];\n rotationHandle: Point | null;\n elementRotation: number;\n rotationAnchor: Point;\n\n constructor() {\n this.resizeHandles = [];\n this.rotationHandle = null;\n this.elementRotation = 0;\n this.rotationAnchor = { x: 0, y: 0 };\n }\n\n /**\n * Update handle positions for an element\n * @param element - The element to update handles for\n * @param zoom - Current zoom level (used to maintain consistent rotation handle distance in screen space)\n */\n update(element: BaseElement, zoom: number = 1.0): void {\n // For crop mode, use getBoundingBox() to get the original image bounds\n // For normal mode, use getVisualBoundingBox() to get the visible bounds\n const isCropping = 'isCropping' in element && element.isCropping;\n const bbox = isCropping ? element.getBoundingBox() : element.getVisualBoundingBox();\n const rotationAnchor = element.getRotationAnchor();\n const enabledAnchors = element.getEnabledAnchors();\n\n // Store rotation info for rendering\n this.elementRotation = element.rotation;\n this.rotationAnchor = rotationAnchor;\n\n // Apply visual padding to expand the bounding box for handle positioning\n // This places handles on the padded selection border\n const padding = theme.canvas.selectionPadding;\n const paddedBbox = {\n x: bbox.x - padding,\n y: bbox.y - padding,\n width: bbox.width + padding * 2,\n height: bbox.height + padding * 2,\n };\n\n // Calculate all resize handles using padded bbox\n // This places handles at corners of the padded selection box\n const allHandles = calculateResizeHandles(paddedBbox, element.rotation, rotationAnchor);\n\n // Filter to only enabled anchors\n // Also hide side handles when element is rotated (they look awkward sticking out)\n const isRotated = Math.abs(element.rotation % 360) > 0.1;\n this.resizeHandles = allHandles.filter((handle) => {\n if (!enabledAnchors.includes(handle.anchor)) return false;\n // Hide side handles when rotated\n if (isRotated && handle.anchor.includes('middle')) return false;\n return true;\n });\n\n // Calculate rotation handle using padded bbox with zoom-adjusted distance\n // Scale distance inversely with zoom to maintain consistent screen space distance\n // At zoom=0.33, use 70/0.33 = 212px world distance → 70px screen distance\n this.rotationHandle = calculateRotationHandlePosition(\n paddedBbox,\n element.rotation,\n rotationAnchor,\n zoom\n );\n }\n\n /**\n * Render all handles\n * @param renderRotationInCanvas - If false, skip canvas rendering of rotation handle (for React-based rendering)\n */\n render(\n ctx: CanvasRenderingContext2D,\n showRotationHandle: boolean = true,\n rotationAngle: number = 0,\n isRotating: boolean = false,\n isHoveringRotation: boolean = false,\n hoveredResizeHandle: HandleInfo | null = null,\n isResizing: boolean = false,\n activeResizeHandle: HandleInfo | null = null,\n zoom: number = 1.0,\n renderRotationInCanvas: boolean = true\n ): void {\n // Render resize handles\n this.resizeHandles.forEach((handle) => {\n // When resizing, only show the active handle\n if (isResizing && activeResizeHandle) {\n if (handle.anchor !== activeResizeHandle.anchor) {\n return; // Skip non-active handles\n }\n }\n\n const isHovered = !!(hoveredResizeHandle && hoveredResizeHandle.anchor === handle.anchor);\n const isActive = !!(isResizing && activeResizeHandle && activeResizeHandle.anchor === handle.anchor);\n this.renderResizeHandle(ctx, handle, isHovered, isActive, zoom);\n });\n\n // Render rotation handle only if using canvas rendering (not React component)\n if (renderRotationInCanvas && showRotationHandle && this.rotationHandle && !isRotating && !isResizing) {\n this.renderRotationHandle(ctx, this.rotationHandle, isHoveringRotation, zoom);\n }\n\n // Show tooltip with angle when rotating (only if using canvas rendering)\n if (renderRotationInCanvas && showRotationHandle && this.rotationHandle && isRotating) {\n this.renderRotationTooltip(ctx, this.rotationHandle, rotationAngle, zoom);\n }\n }\n\n /**\n * Render a single resize handle\n * Corner handles are circles, side handles are rounded rectangles\n */\n renderResizeHandle(\n ctx: CanvasRenderingContext2D,\n handle: HandleInfo,\n isHovered: boolean = false,\n isActive: boolean = false,\n zoom: number = 1.0\n ): void {\n ctx.save();\n\n // Scale handle position by zoom (convert world to screen coordinates)\n const handleX = handle.x * zoom;\n const handleY = handle.y * zoom;\n\n // Check if this is a side handle (middle-left, middle-right, etc.)\n const isSideHandle = handle.anchor.includes('middle');\n\n if (isSideHandle) {\n // Determine if this is a top/bottom or left/right handle\n const isTopBottom = handle.anchor === 'middle-top' || handle.anchor === 'middle-bottom';\n\n // Rotate side handles to align with element rotation\n ctx.translate(handleX, handleY);\n // Use negative angle for clockwise rotation (matching UI convention)\n ctx.rotate((-this.elementRotation * Math.PI) / 180);\n\n // For top/bottom handles, rotate an additional 90 degrees\n if (isTopBottom) {\n ctx.rotate(Math.PI / 2);\n }\n\n ctx.translate(-handleX, -handleY);\n\n // Draw rounded rectangle for side handles (taller than wide)\n const width = HANDLE_SIZE * 0.6; // Narrower\n const height = HANDLE_SIZE * 1.4; // Taller\n const radius = width / 2; // Fully rounded top and bottom (pill shape)\n const x = handleX - width / 2;\n const y = handleY - height / 2;\n\n // White fill with theme accent border\n ctx.fillStyle = '#fff';\n ctx.strokeStyle = getThemeAccentColor(ctx.canvas);\n ctx.lineWidth = isHovered || isActive ? 2.5 : 2;\n\n ctx.beginPath();\n ctx.roundRect(x, y, width, height, radius);\n ctx.fill();\n ctx.stroke();\n } else {\n // White fill with theme accent border\n ctx.fillStyle = '#fff';\n ctx.strokeStyle = getThemeAccentColor(ctx.canvas);\n ctx.lineWidth = isHovered || isActive ? 2.5 : 2;\n\n // Draw circle for corner handles - use shared constant for consistency\n ctx.beginPath();\n ctx.arc(handleX, handleY, BASE_HANDLE_RADIUS, 0, Math.PI * 2);\n ctx.fill();\n ctx.stroke();\n }\n\n ctx.restore();\n }\n\n /**\n * Render the rotation handle (with refresh-ccw icon)\n * Works in screen space (positions multiplied by zoom, fixed pixel sizes)\n */\n renderRotationHandle(ctx: CanvasRenderingContext2D, handle: Point, isHovering: boolean = false, zoom: number = 1.0): void {\n ctx.save();\n\n // Scale handle position by zoom (convert world to screen coordinates)\n const handleX = handle.x * zoom;\n const handleY = handle.y * zoom;\n\n const visualRadius = BASE_HANDLE_RADIUS * 2.67; // 16px for rotation handle\n\n // Circle background - white fill with theme accent border\n ctx.fillStyle = '#fff';\n ctx.strokeStyle = getThemeAccentColor(ctx.canvas);\n ctx.lineWidth = isHovering ? 2.5 : 2;\n\n ctx.beginPath();\n ctx.arc(handleX, handleY, visualRadius, 0, Math.PI * 2);\n ctx.fill();\n ctx.stroke();\n\n // Draw refresh-ccw icon (Lucide icon)\n const scale = ROTATION_ICON_SCALE;\n ctx.translate(handleX, handleY);\n ctx.scale(scale, scale);\n\n ctx.strokeStyle = '#000';\n ctx.lineWidth = 1.5; // Consistent line width for clean appearance\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n\n // Center the icon (24x24 viewBox)\n ctx.translate(-12, -12);\n\n // Path 1: M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8\n ctx.beginPath();\n ctx.moveTo(21, 12);\n ctx.arc(12, 12, 9, 0, -Math.PI / 2, true);\n ctx.bezierCurveTo(3.5, 4.5, 4, 6, 3, 8);\n ctx.stroke();\n\n // Path 2: M3 3v5h5 (arrow at top-left)\n ctx.beginPath();\n ctx.moveTo(3, 3);\n ctx.lineTo(3, 8);\n ctx.lineTo(8, 8);\n ctx.stroke();\n\n // Path 3: M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16\n ctx.beginPath();\n ctx.moveTo(3, 12);\n ctx.arc(12, 12, 9, Math.PI, Math.PI / 2, true);\n ctx.bezierCurveTo(20.5, 19.5, 20, 18, 21, 16);\n ctx.stroke();\n\n // Path 4: M16 16h5v5 (arrow at bottom-right)\n ctx.beginPath();\n ctx.moveTo(16, 16);\n ctx.lineTo(21, 16);\n ctx.lineTo(21, 21);\n ctx.stroke();\n\n ctx.restore();\n }\n\n /**\n * Render rotation tooltip (angle display while rotating)\n */\n renderRotationTooltip(ctx: CanvasRenderingContext2D, handle: Point, angle: number, zoom: number = 1.0): void {\n ctx.save();\n\n const angleText = `${Math.round(angle)}°`;\n const tooltipWidth = angleText.length * 8 + 12;\n const tooltipHeight = 22;\n // Scale handle position by zoom (convert world to screen coordinates)\n const tooltipX = handle.x * zoom;\n const tooltipY = handle.y * zoom - 35;\n\n // Shadow\n ctx.shadowColor = 'rgba(0,0,0,0.3)';\n ctx.shadowBlur = 3;\n ctx.shadowOffsetY = 1;\n\n // Background\n ctx.fillStyle = '#333';\n ctx.beginPath();\n ctx.roundRect(tooltipX - tooltipWidth / 2, tooltipY, tooltipWidth, tooltipHeight, 3);\n ctx.fill();\n\n // Reset shadow\n ctx.shadowColor = 'transparent';\n ctx.shadowBlur = 0;\n ctx.shadowOffsetY = 0;\n\n // Text\n ctx.fillStyle = '#fff';\n ctx.font = '12px Arial';\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(angleText, tooltipX, tooltipY + tooltipHeight / 2);\n\n ctx.restore();\n }\n\n /**\n * Test if a point hits any resize handle\n * @param zoom - Current zoom level (radius scales inversely with zoom)\n */\n hitTestResize(x: number, y: number, zoom: number = 1.0): HandleInfo | null {\n // Hit test radius needs to scale inversely with zoom\n // Use larger CORNER_HANDLE_HIT_RADIUS for comfortable touch interaction\n const radius = CORNER_HANDLE_HIT_RADIUS / zoom;\n\n for (const handle of this.resizeHandles) {\n if (hitTestCircle(x, y, handle.x, handle.y, radius)) {\n return handle;\n }\n }\n return null;\n }\n\n /**\n * Test if a point hits the rotation handle\n * @param zoom - Current zoom level (radius scales inversely with zoom)\n */\n hitTestRotation(x: number, y: number, zoom: number = 1.0): boolean {\n if (!this.rotationHandle) return false;\n // Hit test radius needs to scale inversely with zoom\n const radius = (BASE_HANDLE_RADIUS * 2.67 + 2) / zoom; // 16px + 2px padding\n return hitTestCircle(x, y, this.rotationHandle.x, this.rotationHandle.y, radius);\n }\n\n /**\n * Get rotation handle position (for React-based rendering)\n */\n getRotationHandlePosition(): Point | null {\n return this.rotationHandle;\n }\n\n /**\n * Get cursor style for a resize handle, adjusted for the element's current rotation.\n * @param handle - The resize handle to get cursor for\n * @param rotation - Element rotation in degrees\n */\n getCursor(handle: HandleInfo, rotation: number): string {\n const handleType = handle.anchor.includes('middle') ? 'edge' : 'corner';\n return getCursorForWorldPosition(\n handle.x,\n handle.y,\n this.rotationAnchor.x,\n this.rotationAnchor.y,\n handleType,\n rotation,\n handle.anchor\n );\n }\n}\n\nexport default TransformHandles;\n","/**\n * ResizeHandler - Encapsulates complex resize logic\n * Handles fixed-corner resize with rotation for all transform types\n */\n\nimport { Transform } from './Transform.js';\nimport type { ImageElement } from './ImageElement.js';\nimport type { TextElement } from './TextElement.js';\nimport type { Point, ResizeAnchor, TransformStartData } from '../types/index.js';\n\ninterface HandleInfo {\n anchor: ResizeAnchor;\n type?: string;\n cursor?: string;\n x?: number;\n y?: number;\n}\n\nexport class ResizeHandler {\n /**\n * Calculate new dimensions and position for a resize operation\n */\n static calculateResize({\n element,\n handle,\n dx,\n dy,\n startData,\n snapSystem,\n }: {\n element: TextElement;\n handle: HandleInfo;\n dx: number;\n dy: number;\n startData: TransformStartData;\n snapSystem?: { activeSnaps?: { x?: unknown; y?: unknown } } | null;\n }): { element: TextElement; isFrozen?: boolean; unfrozenDimensions?: { width: number; height: number } } {\n // Transform delta to local coordinates (inverse transform: world → local)\n const transform = new Transform(element);\n const localDelta = transform.worldDeltaToLocal(dx, dy);\n const localDx = localDelta.dx;\n const localDy = localDelta.dy;\n\n // Use visual bbox dimensions since handles are positioned there\n const startVisualWidth = startData.visualWidth ?? startData.width ?? 0;\n const startVisualHeight = startData.visualHeight ?? startData.height ?? 0;\n const startBboxWidth = startData.width ?? 0;\n const startBboxHeight = startData.height ?? 0;\n\n let newVisualWidth = startVisualWidth;\n let newVisualHeight = startVisualHeight;\n\n // Calculate new visual dimensions based on which handle is being dragged\n // For corner handles, ONLY use the perpendicular axis to that edge\n if (handle.anchor.includes('right')) {\n // Right side: only use local X-axis movement\n newVisualWidth = startVisualWidth + localDx;\n } else if (handle.anchor.includes('left')) {\n // Left side: only use local X-axis movement (inverted)\n newVisualWidth = startVisualWidth - localDx;\n }\n\n // Only update height for handles that should change height (not horizontal side handles)\n // For corner handles, ONLY use the perpendicular axis to that edge\n if (handle.anchor !== 'middle-left' && handle.anchor !== 'middle-right') {\n if (handle.anchor.includes('bottom')) {\n // Bottom side: only use local Y-axis movement\n newVisualHeight = startVisualHeight + localDy;\n } else if (handle.anchor.includes('top')) {\n // Top side: only use local Y-axis movement (inverted)\n newVisualHeight = startVisualHeight - localDy;\n }\n }\n\n // Calculate new dimensions based on handle type\n let newWidth, newHeight;\n\n // Special handling for side handles: use direct drag distance instead of scale\n // This is critical because visual bbox != actual bbox for elements with transformData.width\n if (handle.anchor === 'middle-left' || handle.anchor === 'middle-right') {\n // For horizontal side handles: apply width change directly to bbox width\n const widthChange = newVisualWidth - startVisualWidth;\n newWidth = startBboxWidth + widthChange;\n newHeight = startBboxHeight; // Keep original bbox height\n } else if (handle.anchor === 'middle-top' || handle.anchor === 'middle-bottom') {\n // For vertical side handles: apply height change directly to bbox height\n const heightChange = newVisualHeight - startVisualHeight;\n newWidth = startBboxWidth; // Keep original bbox width\n newHeight = startBboxHeight + heightChange;\n } else {\n // For corner handles: use scale factors\n const scaleX = newVisualWidth / startVisualWidth;\n const scaleY = newVisualHeight / startVisualHeight;\n newWidth = startBboxWidth * scaleX;\n newHeight = startBboxHeight * scaleY;\n }\n\n // Save unfrozen dimensions BEFORE applying freeze\n const unfrozenVisualWidth = newVisualWidth;\n const unfrozenVisualHeight = newVisualHeight;\n\n // Freeze dimensions for images when sticky snap is active to maintain aspect ratio\n let freezeApplied = false;\n if (snapSystem && element.transformType === 'image') {\n const anchor = handle.anchor ?? '';\n const isCornerResizeHandle =\n anchor === 'top-left' || anchor === 'top-right' || anchor === 'bottom-left' || anchor === 'bottom-right';\n const isCropHandle = anchor.startsWith('crop-');\n\n if (isCornerResizeHandle && !isCropHandle) {\n const snapX = snapSystem.activeSnaps?.x;\n const snapY = snapSystem.activeSnaps?.y;\n const hasActiveSnap = Boolean(snapX) || Boolean(snapY);\n\n if (hasActiveSnap) {\n const frozenState = [snapX, snapY].find((state): state is { frozenWidth?: number; frozenHeight?: number } =>\n typeof state === 'object' && state !== null && 'frozenWidth' in state\n );\n\n if (frozenState) {\n const imageElement = element as unknown as ImageElement;\n const imageStartTransform = startData.transformData as unknown as ImageElement['transformData'] | undefined;\n\n const targetWidth = frozenState.frozenWidth ?? imageElement.transformData?.width ?? startBboxWidth;\n\n const aspectRatio =\n imageElement.imageAspectRatio ||\n (imageStartTransform?.width && imageStartTransform?.height\n ? imageStartTransform.width / imageStartTransform.height\n : null);\n\n let targetHeight = frozenState.frozenHeight;\n if (targetHeight === undefined || targetHeight === null) {\n if (aspectRatio && aspectRatio !== 0) {\n targetHeight = targetWidth / aspectRatio;\n } else if (imageElement.transformData?.height) {\n targetHeight = imageElement.transformData.height;\n } else {\n targetHeight = startBboxHeight;\n }\n }\n\n const widthScaleFromStart = startBboxWidth !== 0 ? targetWidth / startBboxWidth : 1;\n const heightScaleFromStart = startBboxHeight !== 0 ? targetHeight / startBboxHeight : 1;\n\n newVisualWidth = startVisualWidth * widthScaleFromStart;\n newVisualHeight = startVisualHeight * heightScaleFromStart;\n\n newWidth = targetWidth;\n newHeight = targetHeight;\n freezeApplied = true;\n }\n }\n }\n }\n\n // Clamp to minimum sizes\n newWidth = Math.max(50, newWidth);\n newHeight = Math.max(40, newHeight);\n\n // Clone the element for updates\n const updatedElement = element.clone();\n\n\n // Apply the resize to the element\n // For ImageElement in crop mode, this returns false if resize is frozen at bounds\n const resizeApplied = updatedElement.resize(handle.anchor, newWidth, newHeight, startData);\n\n // Scale stroke width proportionally for corner resizes (all element types)\n // Side handles don't scale stroke — only changing one axis shouldn't affect uniform stroke width\n const isCorner = !handle.anchor.includes('middle');\n if (isCorner && updatedElement.stroke?.enabled && startData.strokeWidth !== undefined && startData.strokeWidth > 0) {\n const startW = startData.width ?? 1;\n const startH = startData.height ?? 1;\n // Geometric mean: standard for scaling a 1D property under 2D transformation\n // Avoids the upward bias of arithmetic mean on asymmetric resizes\n const strokeScale = Math.sqrt((newWidth / startW) * (newHeight / startH));\n // Round to tenths to minimize drift across multiple resize operations\n const scaledStrokeWidth = Math.max(1, Math.round(startData.strokeWidth * strokeScale * 10) / 10);\n updatedElement.stroke = { ...updatedElement.stroke, width: scaledStrokeWidth };\n }\n\n const isFrozen = freezeApplied || resizeApplied === false;\n\n // Calculate fixed corner position adjustment (only for corner handles)\n // Side handles are handled by the element's resize() method\n // Skip position adjustment for images in crop mode and groups - they handle positioning internally\n const isCornerHandle = handle.anchor.includes('top') || handle.anchor.includes('bottom');\n const isCroppingImage = 'isCropping' in updatedElement && updatedElement.isCropping;\n const isGroup = 'isGroup' in updatedElement && updatedElement.isGroup;\n if (isCornerHandle && !isCroppingImage && !isGroup) {\n const rotationAnchor = element.getRotationAnchor();\n const positionOffset = this._calculateFixedCornerOffset({\n element,\n updatedElement,\n handle,\n rotationAnchor,\n });\n\n // Apply position adjustment\n updatedElement.x += positionOffset.x;\n updatedElement.y += positionOffset.y;\n }\n\n return {\n element: updatedElement,\n isFrozen,\n // For snap system to detect break-free: dimensions before freeze was applied\n unfrozenDimensions: freezeApplied ? { width: unfrozenVisualWidth, height: unfrozenVisualHeight } : undefined,\n };\n }\n\n /**\n * Calculate position offset to keep opposite corner fixed\n * Uses visual bounding box and rotation anchor for proper rotated resize\n * @private\n */\n static _calculateFixedCornerOffset({\n element,\n updatedElement,\n handle,\n rotationAnchor,\n }: {\n element: TextElement;\n updatedElement: TextElement;\n handle: HandleInfo;\n rotationAnchor: Point;\n }): Point {\n // Create transforms for both the start and updated elements\n const startTransform = new Transform(element);\n\n // Get precomputed cos/sin for forward transform (local → world)\n const { cos: cosRot, sin: sinRot } = startTransform.getRenderingCosSin();\n\n // Use VISUAL BOUNDING BOX dimensions for fixed corner calculations\n //\n // Critical: Handles are positioned at getVisualBoundingBox() corners (see TransformHandles.js:41)\n // We MUST use the same bbox here to ensure the math aligns correctly. If we used getBoundingBox()\n // instead, there would be a mismatch when visual bbox != actual bbox (e.g., multi-line text),\n // causing the corner to slip away from the mouse cursor during resize.\n const startBbox = element.getVisualBoundingBox();\n const newBbox = updatedElement.getVisualBoundingBox();\n\n // Get rotation anchors (center points around which rotation happens)\n const startRotationAnchor = rotationAnchor; // Passed in from before resize\n const newRotationAnchor = updatedElement.getRotationAnchor();\n\n // Determine which corner should stay fixed (in local coords)\n const fixedLocal = this._getFixedLocalCoords(handle.anchor, startBbox.width, startBbox.height);\n\n // Calculate the fixed corner's position in world coordinates\n // Step 1: Position relative to bbox top-left\n const localX = fixedLocal.x;\n const localY = fixedLocal.y;\n\n // Step 2: Bbox position relative to rotation anchor\n const bboxOffsetX = startBbox.x - startRotationAnchor.x;\n const bboxOffsetY = startBbox.y - startRotationAnchor.y;\n\n // Step 3: Corner position relative to rotation anchor (in local space)\n const cornerRelX = bboxOffsetX + localX;\n const cornerRelY = bboxOffsetY + localY;\n\n // Step 4: Rotate this position around rotation anchor\n const rotatedX = cornerRelX * cosRot - cornerRelY * sinRot;\n const rotatedY = cornerRelX * sinRot + cornerRelY * cosRot;\n\n // Step 5: Transform to world coordinates\n const fixedWorldX = startRotationAnchor.x + rotatedX;\n const fixedWorldY = startRotationAnchor.y + rotatedY;\n\n // Now calculate where that corner is after resize\n const newFixedLocal = this._getFixedLocalCoords(handle.anchor, newBbox.width, newBbox.height);\n\n // Repeat the process with new dimensions\n const newLocalX = newFixedLocal.x;\n const newLocalY = newFixedLocal.y;\n\n const newBboxOffsetX = newBbox.x - newRotationAnchor.x;\n const newBboxOffsetY = newBbox.y - newRotationAnchor.y;\n\n const newCornerRelX = newBboxOffsetX + newLocalX;\n const newCornerRelY = newBboxOffsetY + newLocalY;\n\n const newRotatedX = newCornerRelX * cosRot - newCornerRelY * sinRot;\n const newRotatedY = newCornerRelX * sinRot + newCornerRelY * cosRot;\n\n const currentFixedWorldX = newRotationAnchor.x + newRotatedX;\n const currentFixedWorldY = newRotationAnchor.y + newRotatedY;\n\n // Return offset to keep fixed corner in place\n return {\n x: fixedWorldX - currentFixedWorldX,\n y: fixedWorldY - currentFixedWorldY,\n };\n }\n\n /**\n * Get local coordinates of the fixed point for a given handle\n * @private\n */\n static _getFixedLocalCoords(anchor: ResizeAnchor, width: number, height: number): Point {\n const coords = { x: 0, y: 0 };\n\n switch (anchor) {\n case 'top-left':\n coords.x = width;\n coords.y = height;\n break;\n case 'top-right':\n coords.x = 0;\n coords.y = height;\n break;\n case 'bottom-left':\n coords.x = width;\n coords.y = 0;\n break;\n case 'bottom-right':\n coords.x = 0;\n coords.y = 0;\n break;\n case 'middle-left':\n coords.x = width;\n coords.y = height / 2;\n break;\n case 'middle-right':\n coords.x = 0;\n coords.y = height / 2;\n break;\n case 'middle-top':\n coords.x = width / 2;\n coords.y = height;\n break;\n case 'middle-bottom':\n coords.x = width / 2;\n coords.y = 0;\n break;\n }\n\n return coords;\n }\n}\n\nexport default ResizeHandler;\n","/**\n * ResizePipeline - Unified resize pipeline with hook points\n *\n * Provides a single entry point for resize operations with\n * hooks for snapping and other modifications.\n */\n\nimport { ResizeHandler } from './ResizeHandler.js';\nimport type { BaseElement } from './BaseElement.js';\nimport type { TextElement } from './TextElement.js';\nimport type { TransformStartData, ResizeAnchor } from '../types/index.js';\n\ntype HookStage = 'beforeResize' | 'afterResize' | 'beforePositionUpdate' | 'afterPositionUpdate';\n\ninterface HandleInfo {\n anchor: ResizeAnchor;\n type?: string;\n cursor?: string;\n x?: number;\n y?: number;\n}\n\ninterface ResizeParams {\n element: TextElement;\n handle: HandleInfo;\n dx: number;\n dy: number;\n startData: TransformStartData;\n allElements?: BaseElement[] | null;\n snapSystem?: { activeSnaps?: { x?: unknown; y?: unknown } } | null;\n}\n\ninterface ResizeResult {\n element: TextElement;\n isFrozen?: boolean;\n unfrozenDimensions?: { width: number; height: number };\n handle?: HandleInfo;\n startData?: TransformStartData;\n}\n\ninterface PositionUpdateData {\n position: { x: number; y: number };\n element: BaseElement;\n}\n\ntype HookFn<T> = (data: T) => T | undefined;\n\nexport class ResizePipeline {\n hooks: Record<HookStage, HookFn<unknown>[]>;\n\n constructor() {\n this.hooks = {\n beforeResize: [],\n afterResize: [],\n beforePositionUpdate: [],\n afterPositionUpdate: [],\n };\n }\n\n /**\n * Add a hook at a specific stage\n * @param stage - 'beforeResize', 'afterResize', 'beforePositionUpdate', 'afterPositionUpdate'\n * @param fn - Hook function\n */\n addHook(stage: HookStage, fn: HookFn<unknown>): void {\n if (!this.hooks[stage]) {\n throw new Error(`Invalid hook stage: ${stage}`);\n }\n this.hooks[stage].push(fn);\n }\n\n /**\n * Remove a hook\n */\n removeHook(stage: HookStage, fn: HookFn<unknown>): void {\n if (!this.hooks[stage]) return;\n const index = this.hooks[stage].indexOf(fn);\n if (index !== -1) {\n this.hooks[stage].splice(index, 1);\n }\n }\n\n /**\n * Clear all hooks at a stage\n */\n clearHooks(stage: HookStage): void {\n if (!this.hooks[stage]) return;\n this.hooks[stage] = [];\n }\n\n /**\n * Clear all hooks\n */\n clearAllHooks(): void {\n (Object.keys(this.hooks) as HookStage[]).forEach((stage) => {\n this.hooks[stage] = [];\n });\n }\n\n /**\n * Run hooks at a specific stage\n */\n runHooks<T>(stage: HookStage, data: T): T {\n if (!this.hooks[stage]) return data;\n\n return this.hooks[stage].reduce((acc: T, fn: HookFn<unknown>) => {\n const result = fn(acc);\n return result !== undefined ? (result as T) : acc;\n }, data);\n }\n\n /**\n * Execute resize with hooks\n */\n executeResize(params: ResizeParams): ResizeResult {\n\n // Run before hooks\n const beforeParams = this.runHooks('beforeResize', params);\n\n\n // Calculate new dimensions using ResizeHandler\n // Pass snapSystem so it can check for frozen dimensions\n let result: ResizeResult = ResizeHandler.calculateResize({\n ...beforeParams,\n snapSystem: params.snapSystem,\n });\n\n\n // Pass handle info and startData to afterResize hooks (needed for snapping)\n result.handle = params.handle;\n result.startData = params.startData;\n\n // Run after resize hooks (SNAPPING ADJUSTS ELEMENT HERE)\n result = this.runHooks('afterResize', result);\n\n return result;\n }\n\n /**\n * Execute position update with hooks\n * Used for drag operations\n */\n executePositionUpdate(position: { x: number; y: number }, element: BaseElement): { x: number; y: number } {\n // Run before hooks\n const beforeData = this.runHooks<PositionUpdateData>('beforePositionUpdate', { position, element });\n\n // Run after hooks (SNAPPING GOES HERE)\n const afterData = this.runHooks<PositionUpdateData>('afterPositionUpdate', beforeData);\n\n return afterData.position;\n }\n}\n\n/**\n * Singleton instance for the application\n * This allows hooking into resize operations globally\n */\nexport const globalResizePipeline = new ResizePipeline();\n","/**\n * InteractionStateMachine - Manages canvas interaction states\n *\n * Provides clean state transitions for drag/resize/rotate operations\n * and injection points for snapping functionality.\n */\n\nimport { calculateAngle } from './GeometryUtils.js';\nimport type { BaseElement } from './BaseElement.js';\nimport type { GroupElement } from './GroupElement.js';\nimport type { CustomTransform } from '../transforms/CustomTransform.js';\nimport type { ResizePipeline } from './ResizePipeline.js';\nimport type { ResizeAnchor, BoundingBox, Point, InteractionMode, ImageTransformData, TransformStartData } from '../types/index.js';\n\ntype State = 'IDLE' | 'DRAGGING' | 'RESIZING' | 'ROTATING' | 'PINCHING';\n\ninterface HandleInfo {\n anchor: ResizeAnchor;\n type?: string;\n cursor?: string;\n x?: number;\n y?: number;\n}\n\ninterface RotationStartData {\n startAngle: number;\n currentRotation: number;\n elementData: TransformStartData;\n}\n\ninterface PinchStartData {\n /** Snapshot of the element's transform at gesture start. */\n elementData: TransformStartData;\n /** Midpoint of the two pointers in world coords at gesture start. */\n startCentroid: Point;\n /** Distance between the two pointers at gesture start, in world units. */\n startDistance: number;\n /** Angle of the vector from p1 → p2 at gesture start, in radians. */\n startAngle: number;\n}\n\n/**\n * Output of `updatePinch` — the raw gesture state. PinchHandler\n * consumes this together with the element snapshot in\n * `getPinchContext()` to produce the next element.\n */\nexport interface PinchGesture {\n /** Distance ratio (current / start). 1.0 = no scale change. */\n scale: number;\n /** Angle delta in radians (current - start). 0 = no rotate change. */\n rotationDelta: number;\n /** Gesture centroid at start (world coords). */\n startCentroid: Point;\n /** Gesture centroid right now (world coords). */\n currentCentroid: Point;\n}\n\ninterface InteractionContextData {\n mode: InteractionMode | null;\n dragMode?: string | null;\n startX: number;\n startY: number;\n startData: TransformStartData | RotationStartData | null;\n activeHandle: HandleInfo | null;\n element: BaseElement | null;\n}\n\ninterface SnapHandler {\n snapPosition?: (position: Point, element: BaseElement, allElements: BaseElement[] | null) => Point;\n snapRotation?: (rotation: number, element: BaseElement) => number;\n}\n\nexport interface ActiveChildContext {\n group: GroupElement;\n childId: string;\n}\n\nexport interface CropGroupContext {\n group: BaseElement;\n childIndex: number;\n}\n\nexport class InteractionStateMachine {\n state: State;\n context: InteractionContextData;\n snapHandler: SnapHandler | null;\n resizePipeline: ResizePipeline | null;\n /** Context for interactions targeting a child within a group */\n _activeChildContext?: ActiveChildContext;\n /** Context for crop mode interactions on group children */\n _cropGroupContext?: CropGroupContext;\n\n constructor(resizePipeline: ResizePipeline | null = null) {\n this.state = 'IDLE';\n this.context = {\n mode: null,\n startX: 0,\n startY: 0,\n startData: null,\n activeHandle: null,\n element: null,\n };\n\n // Hook for snapping logic\n this.snapHandler = null;\n\n // Resize pipeline for hooking into resize operations\n this.resizePipeline = resizePipeline;\n }\n\n /**\n * Set snap handler for position/dimension snapping\n */\n setSnapHandler(handler: SnapHandler | null): void {\n this.snapHandler = handler;\n }\n\n /**\n * Set resize pipeline for hooking into resize operations\n */\n setResizePipeline(pipeline: ResizePipeline | null): void {\n this.resizePipeline = pipeline;\n }\n\n /**\n * Get current state\n */\n getState(): State {\n return this.state;\n }\n\n /**\n * Get current context\n */\n getContext(): InteractionContextData {\n return this.context;\n }\n\n /**\n * Check if currently interacting\n */\n isInteracting(): boolean {\n return this.state !== 'IDLE';\n }\n\n /**\n * Start drag operation\n */\n startDrag(element: BaseElement, mouseX: number, mouseY: number, dragMode: string | null = null): void {\n this.state = 'DRAGGING';\n\n // Get base start data\n const baseStartData = element.getTransformStartData();\n\n // For crop image drag, also store crop values\n let startData: TransformStartData = baseStartData;\n if (dragMode === 'crop-image-drag' && element.transformData) {\n const imageTransformData = element.transformData as ImageTransformData;\n startData = {\n ...baseStartData,\n cropX: imageTransformData.cropX,\n cropY: imageTransformData.cropY,\n cropWidth: imageTransformData.cropWidth,\n cropHeight: imageTransformData.cropHeight,\n };\n }\n\n this.context = {\n mode: 'drag',\n dragMode: dragMode, // 'crop-image-drag' for moving image within crop, null for normal drag\n startX: mouseX,\n startY: mouseY,\n startData: startData,\n activeHandle: null,\n element: element,\n };\n }\n\n /**\n * Get drag mode\n */\n getDragMode(): string | null | undefined {\n return this.context.dragMode;\n }\n\n /**\n * Reset drag start position (for incremental dragging)\n */\n resetDragStart(mouseX: number, mouseY: number): void {\n if (this.state !== 'DRAGGING') return;\n this.context.startX = mouseX;\n this.context.startY = mouseY;\n }\n\n /**\n * Update drag operation\n * Returns updated position { x, y }\n */\n updateDrag(mouseX: number, mouseY: number, allElements: BaseElement[] | null = null): Point {\n if (this.state !== 'DRAGGING') {\n throw new Error('Cannot update drag: not in DRAGGING state');\n }\n\n const dx = mouseX - this.context.startX;\n const dy = mouseY - this.context.startY;\n\n const dragStartData = this.context.startData as TransformStartData;\n let newX = dragStartData.x + dx;\n let newY = dragStartData.y + dy;\n\n // Apply snapping if handler is set\n if (this.snapHandler && this.snapHandler.snapPosition && this.context.element) {\n const snapped = this.snapHandler.snapPosition({ x: newX, y: newY }, this.context.element, allElements);\n newX = snapped.x;\n newY = snapped.y;\n }\n\n return { x: newX, y: newY };\n }\n\n /**\n * Start resize operation\n */\n startResize(\n element: BaseElement,\n handle: HandleInfo,\n mouseX: number,\n mouseY: number,\n bbox: BoundingBox,\n visualBbox: BoundingBox\n ): void {\n this.state = 'RESIZING';\n const startData = element.getTransformStartData();\n\n // Clone the element and set lock on the CLONE to avoid visual jump\n // This prevents modifying the element in the main elements array before drag starts\n const elementForResize = element.clone();\n const isCornerHandle =\n handle.anchor === 'top-left' ||\n handle.anchor === 'top-right' ||\n handle.anchor === 'bottom-left' ||\n handle.anchor === 'bottom-right';\n if (isCornerHandle && startData.lineCount !== undefined && elementForResize.transformType === 'custom') {\n (elementForResize as CustomTransform)._lockedLineCount = startData.lineCount;\n }\n\n this.context = {\n mode: 'resize',\n startX: mouseX,\n startY: mouseY,\n startData: {\n ...startData,\n visualX: visualBbox.x,\n visualY: visualBbox.y,\n visualWidth: visualBbox.width,\n visualHeight: visualBbox.height,\n bboxX: bbox.x,\n bboxY: bbox.y,\n },\n activeHandle: handle,\n element: elementForResize, // Use the cloned element with lock already set\n };\n }\n\n /**\n * Update resize operation\n * Returns { dx, dy } deltas\n */\n updateResize(mouseX: number, mouseY: number): { dx: number; dy: number } {\n if (this.state !== 'RESIZING') {\n throw new Error('Cannot update resize: not in RESIZING state');\n }\n\n const dx = mouseX - this.context.startX;\n const dy = mouseY - this.context.startY;\n\n return { dx, dy };\n }\n\n /**\n * Adjust the resize start position to account for snap corrections\n * This prevents dx/dy from accumulating when a snap is locked\n */\n adjustResizeStart(deltaX: number, deltaY: number): void {\n if (this.state !== 'RESIZING') {\n return;\n }\n\n // Adjust the start position by the snap correction amount\n // This way, future dx/dy calculations will account for the snap\n this.context.startX += deltaX;\n this.context.startY += deltaY;\n }\n\n /**\n * Get resize context for ResizeHandler\n */\n getResizeContext(): { element: BaseElement; handle: HandleInfo; startData: TransformStartData } {\n if (this.state !== 'RESIZING') {\n throw new Error('Cannot get resize context: not in RESIZING state');\n }\n\n return {\n element: this.context.element!,\n handle: this.context.activeHandle!,\n startData: this.context.startData as TransformStartData,\n };\n }\n\n /**\n * Update the element in resize context (to preserve state between frames)\n * This is critical for ImageElement crop mode where _lastValidCropState needs to persist\n */\n updateResizeElement(element: BaseElement): void {\n if (this.state !== 'RESIZING') {\n return;\n }\n this.context.element = element;\n }\n\n /**\n * Reset resize start data to current element state (for unfreezing)\n * This updates startData to use the current element dimensions, preventing jumps\n */\n resetResizeStartData(element: BaseElement, mouseX: number, mouseY: number): void {\n if (this.state !== 'RESIZING') {\n return;\n }\n\n const bbox = element.getBoundingBox();\n const visualBbox = element.getVisualBoundingBox();\n const startData = element.getTransformStartData();\n\n // Update the context with new start data based on current element state\n this.context.startX = mouseX;\n this.context.startY = mouseY;\n this.context.startData = {\n ...startData,\n visualX: visualBbox.x,\n visualY: visualBbox.y,\n visualWidth: visualBbox.width,\n visualHeight: visualBbox.height,\n bboxX: bbox.x,\n bboxY: bbox.y,\n };\n\n }\n\n /**\n * Start rotation operation\n */\n startRotate(element: BaseElement, mouseX: number, mouseY: number, startAngle: number, currentRotation: number): void {\n this.state = 'ROTATING';\n this.context = {\n mode: 'rotate',\n startX: mouseX,\n startY: mouseY,\n startData: {\n startAngle: startAngle,\n currentRotation: currentRotation,\n elementData: element.getTransformStartData(),\n },\n activeHandle: null,\n element: element,\n };\n }\n\n /**\n * Update rotation operation\n * Returns updated rotation angle in degrees\n */\n updateRotate(mouseX: number, mouseY: number, centerX: number, centerY: number): number {\n if (this.state !== 'ROTATING') {\n throw new Error('Cannot update rotate: not in ROTATING state');\n }\n\n const rotStartData = this.context.startData as RotationStartData;\n const angle = calculateAngle(centerX, centerY, mouseX, mouseY);\n const rotationDelta = ((angle - rotStartData.startAngle) * 180) / Math.PI;\n\n // Negate delta so: dragging right (clockwise) = negative rotation, left (counterclockwise) = positive\n let newRotation = rotStartData.currentRotation - rotationDelta;\n\n // Normalize to -180 to +180 range\n // First normalize to 0-360, then shift to -180 to +180\n newRotation = ((newRotation % 360) + 360) % 360;\n if (newRotation > 180) {\n newRotation -= 360;\n }\n\n // Apply rotation snapping if handler is set\n if (this.snapHandler && this.snapHandler.snapRotation && this.context.element) {\n return this.snapHandler.snapRotation(newRotation, this.context.element);\n }\n\n return newRotation;\n }\n\n /**\n * Get rotation context\n */\n getRotationContext(): { startAngle: number; currentRotation: number } {\n if (this.state !== 'ROTATING') {\n throw new Error('Cannot get rotation context: not in ROTATING state');\n }\n\n const rotStartData = this.context.startData as RotationStartData;\n return {\n startAngle: rotStartData.startAngle,\n currentRotation: rotStartData.currentRotation,\n };\n }\n\n /**\n * Start a two-finger pinch gesture on `element`. Captures the\n * gesture's start centroid / distance / angle and the element's\n * full transform snapshot — `updatePinch` then derives the\n * gesture's scale + rotation delta against this snapshot every\n * frame. PinchHandler consumes both via `getPinchContext()` to\n * produce the next element.\n */\n startPinch(element: BaseElement, p1: Point, p2: Point): void {\n this.state = 'PINCHING';\n\n const startCentroid: Point = {\n x: (p1.x + p2.x) / 2,\n y: (p1.y + p2.y) / 2,\n };\n const dx = p2.x - p1.x;\n const dy = p2.y - p1.y;\n const startDistance = Math.hypot(dx, dy);\n const startAngle = Math.atan2(dy, dx);\n\n const startData: PinchStartData = {\n elementData: element.getTransformStartData(),\n startCentroid,\n startDistance,\n startAngle,\n };\n\n this.context = {\n mode: 'pinch',\n startX: startCentroid.x,\n startY: startCentroid.y,\n startData: startData as unknown as TransformStartData, // see InteractionContextData typing\n activeHandle: null,\n element: element.clone(),\n };\n }\n\n /**\n * Update pinch gesture for the latest two-pointer positions.\n * Returns the raw gesture state (scale ratio, rotation delta,\n * centroid positions). The caller (PinchHandler) is responsible\n * for applying this to the element — keeping the math out of the\n * state machine mirrors how `updateResize` returns `{dx, dy}` and\n * lets `ResizeHandler` do the geometry.\n *\n * Scale is clamped to a small floor so degenerate single-finger\n * coalesces (e.g. fingers landing on the same pixel for one\n * frame) don't produce zero / NaN dimensions.\n */\n updatePinch(p1: Point, p2: Point): PinchGesture {\n if (this.state !== 'PINCHING') {\n throw new Error('Cannot update pinch: not in PINCHING state');\n }\n\n const pinchStartData = this.context.startData as unknown as PinchStartData;\n\n const currentCentroid: Point = {\n x: (p1.x + p2.x) / 2,\n y: (p1.y + p2.y) / 2,\n };\n const dx = p2.x - p1.x;\n const dy = p2.y - p1.y;\n const currentDistance = Math.hypot(dx, dy);\n const currentAngle = Math.atan2(dy, dx);\n\n // Floor protects against zero / negative scale producing\n // degenerate geometry. No upper clamp — the existing handle\n // resize has none either, and aggressive pinch-in is a\n // legitimate gesture.\n const safeStartDistance = Math.max(pinchStartData.startDistance, 0.0001);\n const rawScale = currentDistance / safeStartDistance;\n const scale = Math.max(rawScale, 0.05);\n\n const rotationDelta = currentAngle - pinchStartData.startAngle;\n\n return {\n scale,\n rotationDelta,\n startCentroid: pinchStartData.startCentroid,\n currentCentroid,\n };\n }\n\n /**\n * Get the element snapshot + pinch start data (used by\n * PinchHandler.calculatePinch). Mirrors `getResizeContext`.\n */\n getPinchContext(): { element: BaseElement; startData: TransformStartData } {\n if (this.state !== 'PINCHING') {\n throw new Error('Cannot get pinch context: not in PINCHING state');\n }\n\n const pinchStartData = this.context.startData as unknown as PinchStartData;\n return {\n element: this.context.element!,\n startData: pinchStartData.elementData,\n };\n }\n\n /**\n * End current interaction\n */\n end(): void {\n this.state = 'IDLE';\n this.context = {\n mode: null,\n startX: 0,\n startY: 0,\n startData: null,\n activeHandle: null,\n element: null,\n };\n }\n\n /**\n * Cancel current interaction (no commit)\n */\n cancel(): void {\n this.end();\n }\n\n /**\n * Get active handle (for resize mode)\n */\n getActiveHandle(): HandleInfo | null {\n return this.context.activeHandle;\n }\n\n /**\n * Get mode ('drag', 'resize', 'rotate', or null)\n */\n getMode(): InteractionMode | null {\n return this.context.mode;\n }\n}\n","/**\n * RotationAnchorResolver - Consistent rotation anchor resolution\n *\n * Provides a unified way to determine rotation anchors and snap points\n * for all transform types, with clear precedence rules.\n */\n\nimport { RotationUtils } from './RotationUtils.js';\nimport type { BaseElement } from './BaseElement.js';\nimport type { Point } from '../types/index.js';\n\n/** Named anchor points for snap alignment */\ninterface SnapAnchors {\n topLeft: Point;\n topRight: Point;\n bottomLeft: Point;\n bottomRight: Point;\n topCenter: Point;\n rightCenter: Point;\n bottomCenter: Point;\n leftCenter: Point;\n center: Point;\n rotation: Point;\n [key: string]: Point;\n}\n\n/** Edge line represented by start and end points */\ninterface EdgeLine {\n start: Point;\n end: Point;\n}\n\nexport class RotationAnchorResolver {\n /**\n * Resolve rotation anchor with clear precedence:\n * 1. Transform's getRotationAnchor() if overridden\n * 2. Center of visual bounding box (default)\n */\n static resolve(element: BaseElement): Point {\n // All elements should have getRotationAnchor() method\n // This is the single source of truth for rotation\n return element.getRotationAnchor();\n }\n\n /**\n * Get all snap-able anchor points for an element\n * Used for alignment snapping feature\n *\n * Returns 9 standard anchor points plus the rotation anchor:\n * - 4 corners (top-left, top-right, bottom-left, bottom-right)\n * - 4 edge midpoints (top, right, bottom, left)\n * - 1 center\n * - 1 rotation anchor (may be same as center)\n */\n static getSnapAnchors(element: BaseElement): SnapAnchors {\n const bbox = element.getVisualBoundingBox();\n\n // Calculate basic 9 anchor points from visual bbox\n const anchors: SnapAnchors = {\n // Corners\n topLeft: { x: bbox.x, y: bbox.y },\n topRight: { x: bbox.x + bbox.width, y: bbox.y },\n bottomLeft: { x: bbox.x, y: bbox.y + bbox.height },\n bottomRight: { x: bbox.x + bbox.width, y: bbox.y + bbox.height },\n\n // Edge midpoints\n topCenter: { x: bbox.x + bbox.width / 2, y: bbox.y },\n rightCenter: { x: bbox.x + bbox.width, y: bbox.y + bbox.height / 2 },\n bottomCenter: { x: bbox.x + bbox.width / 2, y: bbox.y + bbox.height },\n leftCenter: { x: bbox.x, y: bbox.y + bbox.height / 2 },\n\n // Center\n center: { x: bbox.x + bbox.width / 2, y: bbox.y + bbox.height / 2 },\n\n // Rotation anchor (may differ from center for some transforms)\n rotation: this.resolve(element),\n };\n\n // If element is rotated, transform all anchor points\n if (element.rotation !== 0) {\n const rotationAnchor = anchors.rotation;\n const angleRad = RotationUtils.toRadiansInverse(element.rotation);\n\n // Transform all anchors except rotation anchor itself\n Object.keys(anchors).forEach((key) => {\n if (key === 'rotation') return; // Don't transform rotation anchor\n\n const point = anchors[key];\n const dx = point.x - rotationAnchor.x;\n const dy = point.y - rotationAnchor.y;\n\n // Rotate around rotation anchor\n const rotatedX = dx * Math.cos(angleRad) - dy * Math.sin(angleRad);\n const rotatedY = dx * Math.sin(angleRad) + dy * Math.cos(angleRad);\n\n anchors[key] = {\n x: rotationAnchor.x + rotatedX,\n y: rotationAnchor.y + rotatedY,\n };\n });\n }\n\n return anchors;\n }\n\n /**\n * Get edge lines for an element (for edge-to-edge snapping)\n *\n * Returns 4 lines representing the element's edges:\n * - top: left to right\n * - right: top to bottom\n * - bottom: right to left\n * - left: bottom to top\n */\n static getEdgeLines(element: BaseElement): Record<string, EdgeLine> {\n const anchors = this.getSnapAnchors(element);\n\n return {\n top: {\n start: anchors.topLeft,\n end: anchors.topRight,\n },\n right: {\n start: anchors.topRight,\n end: anchors.bottomRight,\n },\n bottom: {\n start: anchors.bottomRight,\n end: anchors.bottomLeft,\n },\n left: {\n start: anchors.bottomLeft,\n end: anchors.topLeft,\n },\n };\n }\n\n /**\n * Get all anchor types as array (for iteration)\n */\n static getAnchorTypes(): string[] {\n return [\n 'topLeft',\n 'topCenter',\n 'topRight',\n 'rightCenter',\n 'bottomRight',\n 'bottomCenter',\n 'bottomLeft',\n 'leftCenter',\n 'center',\n 'rotation',\n ];\n }\n\n /**\n * Check if an element has a custom rotation anchor\n * (different from center of visual bbox)\n */\n static hasCustomRotationAnchor(element: BaseElement): boolean {\n const bbox = element.getVisualBoundingBox();\n const center = {\n x: bbox.x + bbox.width / 2,\n y: bbox.y + bbox.height / 2,\n };\n\n const rotationAnchor = this.resolve(element);\n\n // Check if rotation anchor differs from center (with small epsilon for float comparison)\n const epsilon = 0.001;\n return Math.abs(rotationAnchor.x - center.x) > epsilon || Math.abs(rotationAnchor.y - center.y) > epsilon;\n }\n}\n","/**\n * SpatialGrid - Grid-based spatial index for O(1) amortized position lookups.\n *\n * Used by AlignmentSnapSystem to avoid scanning all elements when looking\n * for snap candidates. Points are bucketed into grid cells of fixed size.\n * Queries check only the cells that overlap the search radius, giving\n * O(1) amortized lookup when the number of results is small relative to\n * the total item count.\n */\n\n/** An item stored in the spatial grid. */\nexport interface SpatialGridItem {\n x: number;\n y: number;\n id: string;\n}\n\nexport class SpatialGrid {\n private cellSize: number;\n private cells: Map<string, Set<SpatialGridItem>>;\n /** Reverse index: id -> set of cell keys that contain items with that id */\n private idIndex: Map<string, Set<string>>;\n\n constructor(cellSize: number) {\n this.cellSize = cellSize;\n this.cells = new Map();\n this.idIndex = new Map();\n }\n\n /** Clear all entries */\n clear(): void {\n this.cells.clear();\n this.idIndex.clear();\n }\n\n /** Insert a point into the grid */\n insert(item: SpatialGridItem): void {\n const key = this.getCellKey(item.x, item.y);\n let cell = this.cells.get(key);\n if (!cell) {\n cell = new Set();\n this.cells.set(key, cell);\n }\n cell.add(item);\n\n // Update reverse index\n let keys = this.idIndex.get(item.id);\n if (!keys) {\n keys = new Set();\n this.idIndex.set(item.id, keys);\n }\n keys.add(key);\n }\n\n /** Remove all items with matching id */\n removeById(id: string): void {\n const keys = this.idIndex.get(id);\n if (!keys) return;\n\n for (const key of keys) {\n const cell = this.cells.get(key);\n if (!cell) continue;\n for (const item of cell) {\n if (item.id === id) {\n cell.delete(item);\n }\n }\n if (cell.size === 0) {\n this.cells.delete(key);\n }\n }\n this.idIndex.delete(id);\n }\n\n /**\n * Query all items within `radius` of point (x, y), excluding given id.\n *\n * Checks all cells that overlap the axis-aligned bounding box\n * [x - radius, x + radius] x [y - radius, y + radius].\n * Each candidate is then distance-checked against the radius.\n */\n queryNear(\n x: number,\n y: number,\n radius: number,\n excludeId?: string,\n ): SpatialGridItem[] {\n const results: SpatialGridItem[] = [];\n\n const minCellX = Math.floor((x - radius) / this.cellSize);\n const maxCellX = Math.floor((x + radius) / this.cellSize);\n const minCellY = Math.floor((y - radius) / this.cellSize);\n const maxCellY = Math.floor((y + radius) / this.cellSize);\n\n for (let cx = minCellX; cx <= maxCellX; cx++) {\n for (let cy = minCellY; cy <= maxCellY; cy++) {\n const key = `${cx},${cy}`;\n const cell = this.cells.get(key);\n if (!cell) continue;\n\n for (const item of cell) {\n if (excludeId !== undefined && item.id === excludeId) continue;\n const dx = item.x - x;\n const dy = item.y - y;\n if (dx * dx + dy * dy <= radius * radius) {\n results.push(item);\n }\n }\n }\n }\n\n return results;\n }\n\n /**\n * Query all items whose coordinate on `axis` is within `radius` of the\n * given value, excluding a given id. Unlike `queryNear` (Euclidean),\n * this checks only one axis -- essential for alignment snapping where\n * two anchors may be far apart on one axis but perfectly aligned on\n * the other.\n *\n * For axis='x': finds items where |item.x - value| <= radius\n * For axis='y': finds items where |item.y - value| <= radius\n *\n * Scans all cells in the band along the target axis.\n */\n queryNearAxis(\n axis: 'x' | 'y',\n value: number,\n radius: number,\n excludeId?: string,\n ): SpatialGridItem[] {\n const results: SpatialGridItem[] = [];\n\n if (axis === 'x') {\n // We need all cells whose X index is in [floor((value-radius)/cs), floor((value+radius)/cs)]\n const minCellX = Math.floor((value - radius) / this.cellSize);\n const maxCellX = Math.floor((value + radius) / this.cellSize);\n\n for (const [key, cell] of this.cells) {\n const commaIdx = key.indexOf(',');\n const cellX = parseInt(key.substring(0, commaIdx), 10);\n if (cellX < minCellX || cellX > maxCellX) continue;\n\n for (const item of cell) {\n if (excludeId !== undefined && item.id === excludeId) continue;\n if (Math.abs(item.x - value) <= radius) {\n results.push(item);\n }\n }\n }\n } else {\n const minCellY = Math.floor((value - radius) / this.cellSize);\n const maxCellY = Math.floor((value + radius) / this.cellSize);\n\n for (const [key, cell] of this.cells) {\n const commaIdx = key.indexOf(',');\n const cellY = parseInt(key.substring(commaIdx + 1), 10);\n if (cellY < minCellY || cellY > maxCellY) continue;\n\n for (const item of cell) {\n if (excludeId !== undefined && item.id === excludeId) continue;\n if (Math.abs(item.y - value) <= radius) {\n results.push(item);\n }\n }\n }\n }\n\n return results;\n }\n\n /** Rebuild from a full list of items (clears existing data first) */\n rebuild(items: SpatialGridItem[]): void {\n this.clear();\n for (const item of items) {\n this.insert(item);\n }\n }\n\n /** Get the cell key for a given world-space coordinate */\n private getCellKey(x: number, y: number): string {\n return `${Math.floor(x / this.cellSize)},${Math.floor(y / this.cellSize)}`;\n }\n}\n","/**\n * AlignmentSnapSystem - Handles alignment snapping for elements\n *\n * Detects when elements are near alignment with other elements and snaps them\n * to common alignment points (edges, centers, etc.)\n */\n\nimport { RotationUtils } from './RotationUtils.js';\nimport { RotationAnchorResolver } from './RotationAnchorResolver.js';\nimport { isNear } from './GeometryUtils.js';\nimport { theme } from '../theme.js';\nimport { SpatialGrid } from './SpatialGrid.js';\nimport type { SpatialGridItem } from './SpatialGrid.js';\nimport type { BaseElement } from './BaseElement.js';\nimport type { ImageElement } from './ImageElement.js';\nimport type { Point, SnapGuide, ResizeAnchor, AnyTransformData } from '../types/index.js';\n\n// ============================================================================\n// Local Type Definitions for AlignmentSnapSystem\n// ============================================================================\n\n/**\n * Represents an element with the properties accessed during snap operations.\n * Extends BaseElement with optional text/image-specific properties that the\n * snap system reads via duck-typing.\n */\ninterface SnapElement extends BaseElement {\n /** Font size (present on TextElement and its subclasses) */\n fontSize?: number;\n /** Direct width property (present on some element types) */\n width?: number;\n /** Direct height property (present on some element types) */\n height?: number;\n /** Image aspect ratio (present on ImageElement) */\n imageAspectRatio?: number;\n /** Whether the image is in crop mode (present on ImageElement) */\n isCropping?: boolean;\n /** Override clone to return SnapElement */\n clone(): SnapElement;\n}\n\n/** Named anchor points returned by RotationAnchorResolver.getSnapAnchors() */\ntype SnapAnchors = Record<string, Point>;\n\n/** Resize handle info passed through the resize pipeline */\ninterface SnapHandleInfo {\n anchor: ResizeAnchor | string;\n type?: string;\n cursor?: string;\n x?: number;\n y?: number;\n}\n\n/** Start data captured at the beginning of a resize operation */\ninterface ResizeStartData {\n fontSize?: number;\n width: number;\n height: number;\n visualWidth?: number;\n visualHeight?: number;\n x?: number;\n y?: number;\n rotation?: number;\n transformData?: Partial<AnyTransformData>;\n}\n\n/** Result from the resize pipeline, passed into snapDimensions */\ninterface ResizeResult {\n element: SnapElement;\n handle: SnapHandleInfo;\n startData: ResizeStartData | null;\n isFrozen?: boolean;\n unfrozenDimensions?: { width: number; height: number };\n snapAdjustment?: { dx: number; dy: number };\n isInStickySnap?: boolean;\n}\n\n/** A detected snap on either X or Y axis during position snap */\ninterface PositionSnap {\n snapTo: number;\n offset: number;\n distance: number;\n guide: SnapGuideExtended;\n}\n\n/** A detected snap on either X or Y axis during resize snap */\ninterface ResizeSnap {\n distance: number;\n snapTo: number;\n currentValue: number;\n guide: SnapGuideExtended;\n}\n\n/** Extended guide info including movingKey and otherKey for tracking */\ninterface SnapGuideExtended {\n type: string;\n x?: number;\n y?: number;\n y1?: number;\n y2?: number;\n x1?: number;\n x2?: number;\n movingKey: string;\n otherKey: string;\n}\n\n/** Result from _processStickySnap */\ninterface StickySnapResult {\n snapDelta: number;\n bestSnap: ResizeSnap | null;\n}\n\ninterface SnapState {\n snapTo: number;\n anchorKey: string;\n frozenFontSize: number;\n frozenWidth: number;\n frozenHeight: number;\n frozenPosition: Point;\n isPositionBasedSnap: boolean;\n}\n\ninterface AlignmentSnapSystemOptions {\n snapThreshold?: number;\n enabled?: boolean;\n showGuides?: boolean;\n stickyThreshold?: number;\n}\n\n// ============================================================================\n// Helper Functions for accessing optional transform data properties\n// ============================================================================\n\n/** Safely get width from transform data (only some transform types have it) */\nfunction getTransformDataWidth(element: SnapElement): number | undefined {\n const td = element.transformData;\n if (td && 'width' in td && typeof (td as { width?: number }).width === 'number') {\n return (td as { width: number }).width;\n }\n return undefined;\n}\n\n/** Safely get height from transform data (only some transform types have it) */\nfunction getTransformDataHeight(element: SnapElement): number | undefined {\n const td = element.transformData;\n if (td && 'height' in td && typeof (td as { height?: number }).height === 'number') {\n return (td as { height: number }).height;\n }\n return undefined;\n}\n\n/** Safely set width on transform data */\nfunction setTransformDataWidth(element: SnapElement, value: number): boolean {\n const td = element.transformData;\n if (td && 'width' in td) {\n (td as { width: number }).width = value;\n return true;\n }\n return false;\n}\n\n/** Safely set height on transform data */\nfunction setTransformDataHeight(element: SnapElement, value: number): boolean {\n const td = element.transformData;\n if (td && 'height' in td) {\n (td as { height: number }).height = value;\n return true;\n }\n return false;\n}\n\nexport class AlignmentSnapSystem {\n snapThreshold: number;\n enabled: boolean;\n showGuides: boolean;\n guides: SnapGuide[];\n activeSnaps: {\n x: SnapState | null;\n y: SnapState | null;\n };\n stickyThreshold: number;\n\n /** Spatial grid for O(1) amortized snap candidate lookup */\n private _spatialGrid: SpatialGrid;\n /** Map from grid item compound key to anchor metadata */\n private _anchorMetadata: Map<string, { elementId: string; anchorKey: string; point: Point }>;\n\n /**\n * ADR-0060: extra rectangular regions injected as snap targets in\n * addition to the canvas elements. Used by spread-mode focused-piece\n * editing so dragged elements snap to the FOCUSED PIECE'S center /\n * edges / corners instead of the full artboard. Each rect produces\n * 9 anchor points (corners, edge midpoints, center) — exactly the\n * shape `RotationAnchorResolver.getSnapAnchors` produces for an\n * un-rotated element. The id is used to scope the synthetic\n * \"element id\" so grid-item keys stay unique.\n */\n private _extraSnapRects: Array<{\n id: string;\n x: number;\n y: number;\n width: number;\n height: number;\n }> = [];\n\n constructor(options: AlignmentSnapSystemOptions = {}) {\n this.snapThreshold = options.snapThreshold || theme.canvas.snapThreshold;\n this.enabled = options.enabled !== false;\n this.showGuides = options.showGuides !== false;\n\n // Active snap guides to render\n this.guides = [];\n\n // Sticky snap state - tracks active snaps\n this.activeSnaps = {\n x: null, // { snapTo: number, anchorKey: string, frozenFontSize: number, frozenWidth: number, frozenPosition: {x, y}, isPositionBasedSnap: boolean }\n y: null, // { snapTo: number, anchorKey: string, frozenFontSize: number, frozenWidth: number, frozenPosition: {x, y}, isPositionBasedSnap: boolean }\n };\n\n // Dead zone for sticky snapping (pixels)\n // Once snapped, need to move this far to break free\n this.stickyThreshold = options.stickyThreshold || 15;\n\n // Spatial grid: cell size = 2x snap threshold for good bucket distribution\n this._spatialGrid = new SpatialGrid(Math.max(this.snapThreshold * 2, 16));\n this._anchorMetadata = new Map();\n }\n\n /**\n * Enable or disable snapping\n */\n setEnabled(enabled: boolean): void {\n this.enabled = enabled;\n }\n\n /**\n * Set snap threshold in pixels\n */\n setSnapThreshold(threshold: number): void {\n this.snapThreshold = threshold;\n // Rebuild spatial grid with new cell size proportional to threshold\n this._spatialGrid = new SpatialGrid(Math.max(threshold * 2, 16));\n }\n\n /**\n * Get current snap guides for rendering\n */\n getGuides(): SnapGuide[] {\n return this.guides;\n }\n\n /**\n * Clear all snap guides\n */\n clearGuides(): void {\n this.guides = [];\n }\n\n /**\n * Clear sticky snap state\n */\n clearSnapState(): void {\n this.activeSnaps.x = null;\n this.activeSnaps.y = null;\n }\n\n /**\n * ADR-0060: replace the set of extra snap rects (focused-piece bounds\n * etc.). Pass `[]` to clear. Effective on the next snap operation.\n */\n setExtraSnapRects(\n rects: Array<{ id: string; x: number; y: number; width: number; height: number }>,\n ): void {\n this._extraSnapRects = rects;\n }\n\n /**\n * Populate the spatial grid with anchor points from the given elements.\n * Each element produces ~9 anchor points (corners, edge midpoints, center).\n * The grid enables O(1) amortized lookup of nearby anchors by axis.\n *\n * Also folds in `_extraSnapRects` (set via `setExtraSnapRects`) so\n * a focused piece in spread mode contributes its corners / edges /\n * center as snap targets even though there's no real BaseElement.\n */\n private _populateSpatialGrid(elements: BaseElement[]): void {\n this._spatialGrid.clear();\n this._anchorMetadata.clear();\n\n for (const element of elements) {\n const anchors: SnapAnchors = RotationAnchorResolver.getSnapAnchors(element);\n for (const [anchorKey, point] of Object.entries(anchors) as Array<[string, Point]>) {\n const compoundKey = `${element.id}:${anchorKey}`;\n const gridItem: SpatialGridItem = {\n x: point.x,\n y: point.y,\n id: compoundKey,\n };\n this._spatialGrid.insert(gridItem);\n this._anchorMetadata.set(compoundKey, {\n elementId: element.id,\n anchorKey,\n point,\n });\n }\n }\n\n for (const rect of this._extraSnapRects) {\n const cx = rect.x + rect.width / 2;\n const cy = rect.y + rect.height / 2;\n const x2 = rect.x + rect.width;\n const y2 = rect.y + rect.height;\n const anchors: SnapAnchors = {\n topLeft: { x: rect.x, y: rect.y },\n topRight: { x: x2, y: rect.y },\n bottomLeft: { x: rect.x, y: y2 },\n bottomRight: { x: x2, y: y2 },\n topCenter: { x: cx, y: rect.y },\n rightCenter: { x: x2, y: cy },\n bottomCenter: { x: cx, y: y2 },\n leftCenter: { x: rect.x, y: cy },\n center: { x: cx, y: cy },\n // No `rotation` anchor — extras are un-rotated by definition.\n };\n const elementId = `__snap-rect:${rect.id}`;\n for (const [anchorKey, point] of Object.entries(anchors) as Array<[string, Point]>) {\n const compoundKey = `${elementId}:${anchorKey}`;\n this._spatialGrid.insert({\n x: point.x,\n y: point.y,\n id: compoundKey,\n });\n this._anchorMetadata.set(compoundKey, {\n elementId,\n anchorKey,\n point,\n });\n }\n }\n }\n\n /**\n * Snap a position during drag operation\n */\n snapPosition(position: Point, element: BaseElement, allElements: BaseElement[] | null): Point {\n if (!this.enabled || !allElements) {\n this.clearGuides();\n return position;\n }\n\n // Create a temporary element at the new position for anchor calculation\n const tempElement = element.clone();\n tempElement.x = position.x;\n tempElement.y = position.y;\n\n // Get anchors for the element being moved\n const movingAnchors: SnapAnchors = RotationAnchorResolver.getSnapAnchors(tempElement);\n\n // Get other elements (exclude the one being moved)\n const otherElements = allElements.filter((s) => s.id !== element.id);\n\n // Populate spatial grid with anchor points from other elements\n this._populateSpatialGrid(otherElements);\n\n // Find best snap for X and Y independently\n // Also collect ALL nearby snaps to show multiple guide lines\n let bestSnapX: PositionSnap | null = null;\n let bestSnapY: PositionSnap | null = null;\n let minDistX = Infinity;\n let minDistY = Infinity;\n\n const allXSnaps: PositionSnap[] = []; // Collect all X snaps within threshold\n const allYSnaps: PositionSnap[] = []; // Collect all Y snaps within threshold\n const newGuides: SnapGuideExtended[] = [];\n\n // Use spatial grid to find nearby anchors for each moving anchor\n for (const [movingKey, movingAnchor] of Object.entries(movingAnchors) as Array<[string, Point]>) {\n // Query X-axis candidates: anchors whose X is within snapThreshold\n const xCandidates = this._spatialGrid.queryNearAxis('x', movingAnchor.x, this.snapThreshold);\n for (const candidate of xCandidates) {\n const meta = this._anchorMetadata.get(candidate.id);\n if (!meta) continue;\n const otherAnchor = meta.point;\n const otherKey = meta.anchorKey;\n\n const distX = Math.abs(movingAnchor.x - otherAnchor.x);\n if (distX < this.snapThreshold) {\n const xSnap: PositionSnap = {\n snapTo: otherAnchor.x,\n offset: movingAnchor.x - position.x, // How far anchor is from element position\n distance: distX,\n guide: {\n type: 'vertical',\n x: otherAnchor.x,\n y1: Math.min(movingAnchor.y, otherAnchor.y) - 50,\n y2: Math.max(movingAnchor.y, otherAnchor.y) + 50,\n movingKey,\n otherKey,\n },\n };\n allXSnaps.push(xSnap);\n\n if (distX < minDistX) {\n minDistX = distX;\n bestSnapX = xSnap;\n }\n }\n }\n\n // Query Y-axis candidates: anchors whose Y is within snapThreshold\n const yCandidates = this._spatialGrid.queryNearAxis('y', movingAnchor.y, this.snapThreshold);\n for (const candidate of yCandidates) {\n const meta = this._anchorMetadata.get(candidate.id);\n if (!meta) continue;\n const otherAnchor = meta.point;\n const otherKey = meta.anchorKey;\n\n const distY = Math.abs(movingAnchor.y - otherAnchor.y);\n if (distY < this.snapThreshold) {\n const ySnap: PositionSnap = {\n snapTo: otherAnchor.y,\n offset: movingAnchor.y - position.y,\n distance: distY,\n guide: {\n type: 'horizontal',\n y: otherAnchor.y,\n x1: Math.min(movingAnchor.x, otherAnchor.x) - 50,\n x2: Math.max(movingAnchor.x, otherAnchor.x) + 50,\n movingKey,\n otherKey,\n },\n };\n allYSnaps.push(ySnap);\n\n if (distY < minDistY) {\n minDistY = distY;\n bestSnapY = ySnap;\n }\n }\n }\n }\n\n // Apply snapping\n let snappedX = position.x;\n let snappedY = position.y;\n\n if (bestSnapX) {\n snappedX = bestSnapX.snapTo - bestSnapX.offset;\n }\n\n if (bestSnapY) {\n snappedY = bestSnapY.snapTo - bestSnapY.offset;\n }\n\n // Show guides for ALL snaps that are within threshold (to show multiple aligned edges)\n // Deduplicate by snap position to avoid showing the same line multiple times\n const xSnapsByPosition = new Map<number, PositionSnap>();\n const ySnapsByPosition = new Map<number, PositionSnap>();\n\n for (const xSnap of allXSnaps) {\n const existing = xSnapsByPosition.get(xSnap.snapTo);\n if (!existing || xSnap.distance < existing.distance) {\n xSnapsByPosition.set(xSnap.snapTo, xSnap);\n }\n }\n\n for (const ySnap of allYSnaps) {\n const existing = ySnapsByPosition.get(ySnap.snapTo);\n if (!existing || ySnap.distance < existing.distance) {\n ySnapsByPosition.set(ySnap.snapTo, ySnap);\n }\n }\n\n // Add all unique snap guides\n for (const xSnap of xSnapsByPosition.values()) {\n newGuides.push(xSnap.guide);\n }\n\n for (const ySnap of ySnapsByPosition.values()) {\n newGuides.push(ySnap.guide);\n }\n\n this.guides = newGuides as SnapGuide[];\n\n return { x: snappedX, y: snappedY };\n }\n\n /**\n * Calculate what the frozen font size should be at the snap point\n * This ensures consistent font size regardless of which side of threshold you enter from\n */\n _calculateFrozenFontSizeAtSnap(element: SnapElement, snapDeltaX: number, _snapDeltaY: number, handle: SnapHandleInfo | null, startData: ResizeStartData | null): number {\n if (!startData) return element.fontSize ?? 0;\n\n // Only corner handles affect font size\n const anchorStr = handle?.anchor ? String(handle.anchor) : '';\n const isCornerHandle = handle && (anchorStr.includes('top') || anchorStr.includes('bottom'));\n if (!isCornerHandle) {\n return startData.fontSize ?? 0; // Side handles keep original font size\n }\n\n // Calculate what the width will be at the snap point\n const currentWidth = getTransformDataWidth(element) ?? element.width ?? startData.width;\n\n // For right-side resize, positive snapDeltaX means we're adding width\n // For left-side resize, negative snapDeltaX means we're adding width\n let targetWidthAtSnap = currentWidth;\n\n if (anchorStr.includes('right') && snapDeltaX !== 0) {\n targetWidthAtSnap = currentWidth + snapDeltaX;\n } else if (anchorStr.includes('left') && snapDeltaX !== 0) {\n targetWidthAtSnap = currentWidth - snapDeltaX;\n }\n\n // Calculate scale factor at snap point relative to start\n const scale = targetWidthAtSnap / startData.width;\n\n // Calculate frozen font size at snap point\n const frozenFontSize = (startData.fontSize ?? 0) * scale;\n\n\n return frozenFontSize;\n }\n\n /**\n * Check if this will be a position-based snap (vs dimension-based)\n * Position-based snaps adjust position to align edges when dimensions can't be modified\n * Used for text elements where height is derived, not settable\n */\n _isPositionBasedSnap(element: SnapElement, axis: 'x' | 'y'): boolean {\n if (axis === 'x') {\n // X-axis snaps modify width, which is always settable for text elements\n return false;\n } else if (axis === 'y') {\n // Y-axis snaps need to modify height\n // For text elements, height is derived (not settable), so we use position-based snap\n // Check if height property exists as a settable value\n const hasSettableHeight =\n (getTransformDataHeight(element) !== undefined) ||\n (element.height !== undefined && typeof element.height === 'number');\n return !hasSettableHeight;\n }\n return false;\n }\n\n /**\n * Determine which snap keys to use based on the resize handle\n */\n _getSnapKeysForHandle(handle: SnapHandleInfo | null): string[] {\n if (!handle || !handle.anchor) {\n // Fallback to corner anchors only (no center points for resize)\n return ['topLeft', 'topRight', 'bottomLeft', 'bottomRight'];\n }\n\n // For corner handles, only check the corner being moved\n // This prevents unnecessary snap freezing when the fixed corner happens to align with other elements\n if (handle.anchor === 'top-left' || handle.anchor === 'topLeft') {\n return ['topLeft'];\n } else if (handle.anchor === 'top-right' || handle.anchor === 'topRight') {\n return ['topRight'];\n } else if (handle.anchor === 'bottom-left' || handle.anchor === 'bottomLeft') {\n return ['bottomLeft'];\n } else if (handle.anchor === 'bottom-right' || handle.anchor === 'bottomRight') {\n return ['bottomRight'];\n }\n\n // For edge handles, only check the two corners on that edge\n if (handle.anchor.includes('left')) {\n return ['topLeft', 'bottomLeft'];\n } else if (handle.anchor.includes('right')) {\n return ['topRight', 'bottomRight'];\n } else if (handle.anchor.includes('top')) {\n return ['topLeft', 'topRight'];\n } else if (handle.anchor.includes('bottom')) {\n return ['bottomLeft', 'bottomRight'];\n }\n\n return [];\n }\n\n /**\n * Get all possible target snap keys (corners only, no center points)\n */\n _getTargetSnapKeys(): string[] {\n return ['topLeft', 'topRight', 'bottomLeft', 'bottomRight'];\n }\n\n /**\n * Find all X-axis snaps within threshold.\n * Uses the spatial grid (pre-populated via _populateSpatialGrid) for\n * O(1) amortized lookup instead of scanning all other elements.\n */\n _findXSnaps(movingAnchors: SnapAnchors, _otherElements: BaseElement[], snapKeys: string[], targetSnapKeys: string[]): ResizeSnap[] {\n const xSnaps: ResizeSnap[] = [];\n const targetSnapKeysSet = new Set(targetSnapKeys);\n\n for (const movingKey of snapKeys) {\n const movingAnchor = movingAnchors[movingKey];\n if (!movingAnchor) continue;\n\n // Query the spatial grid for anchors whose X is within threshold\n const candidates = this._spatialGrid.queryNearAxis('x', movingAnchor.x, this.snapThreshold);\n\n for (const candidate of candidates) {\n const meta = this._anchorMetadata.get(candidate.id);\n if (!meta) continue;\n\n // Only consider target snap keys (corners for resize)\n if (!targetSnapKeysSet.has(meta.anchorKey)) continue;\n\n const otherAnchor = meta.point;\n const otherKey = meta.anchorKey;\n\n const distX = Math.abs(movingAnchor.x - otherAnchor.x);\n if (distX < this.snapThreshold) {\n xSnaps.push({\n distance: distX,\n snapTo: otherAnchor.x,\n currentValue: movingAnchor.x,\n guide: {\n type: 'vertical',\n x: otherAnchor.x,\n y1: Math.min(movingAnchor.y, otherAnchor.y) - 50,\n y2: Math.max(movingAnchor.y, otherAnchor.y) + 50,\n movingKey,\n otherKey,\n },\n });\n }\n }\n }\n\n return xSnaps;\n }\n\n /**\n * Find all Y-axis snaps within threshold.\n * Uses the spatial grid (pre-populated via _populateSpatialGrid) for\n * O(1) amortized lookup instead of scanning all other elements.\n */\n _findYSnaps(movingAnchors: SnapAnchors, _otherElements: BaseElement[], snapKeys: string[], targetSnapKeys: string[]): ResizeSnap[] {\n const ySnaps: ResizeSnap[] = [];\n const targetSnapKeysSet = new Set(targetSnapKeys);\n\n for (const movingKey of snapKeys) {\n const movingAnchor = movingAnchors[movingKey];\n if (!movingAnchor) continue;\n\n // Query the spatial grid for anchors whose Y is within threshold\n const candidates = this._spatialGrid.queryNearAxis('y', movingAnchor.y, this.snapThreshold);\n\n for (const candidate of candidates) {\n const meta = this._anchorMetadata.get(candidate.id);\n if (!meta) continue;\n\n // Only consider target snap keys (corners for resize)\n if (!targetSnapKeysSet.has(meta.anchorKey)) continue;\n\n const otherAnchor = meta.point;\n const otherKey = meta.anchorKey;\n\n const distY = Math.abs(movingAnchor.y - otherAnchor.y);\n if (distY < this.snapThreshold) {\n ySnaps.push({\n distance: distY,\n snapTo: otherAnchor.y,\n currentValue: movingAnchor.y,\n guide: {\n type: 'horizontal',\n y: otherAnchor.y,\n x1: Math.min(movingAnchor.x, otherAnchor.x) - 50,\n x2: Math.max(movingAnchor.x, otherAnchor.x) + 50,\n movingKey,\n otherKey,\n },\n });\n }\n }\n }\n\n return ySnaps;\n }\n\n /**\n * Sort snaps by distance and prioritize based on handle anchor\n */\n _prioritizeSnaps(snaps: ResizeSnap[], handle: SnapHandleInfo | null, axis: 'x' | 'y'): void {\n if (!handle || !handle.anchor) {\n snaps.sort((a, b) => a.distance - b.distance);\n return;\n }\n\n if (axis === 'x') {\n if (handle.anchor.includes('left')) {\n snaps.sort((a, b) => {\n const aPriority = a.guide.movingKey.includes('left') ? -1000 : 0;\n const bPriority = b.guide.movingKey.includes('left') ? -1000 : 0;\n return a.distance + aPriority - (b.distance + bPriority);\n });\n } else if (handle.anchor.includes('right')) {\n snaps.sort((a, b) => {\n const aPriority = a.guide.movingKey.includes('right') ? -1000 : 0;\n const bPriority = b.guide.movingKey.includes('right') ? -1000 : 0;\n return a.distance + aPriority - (b.distance + bPriority);\n });\n } else {\n snaps.sort((a, b) => a.distance - b.distance);\n }\n } else if (axis === 'y') {\n if (handle.anchor.includes('top')) {\n snaps.sort((a, b) => {\n const aPriority = a.guide.movingKey.includes('top') ? -1000 : 0;\n const bPriority = b.guide.movingKey.includes('top') ? -1000 : 0;\n return a.distance + aPriority - (b.distance + bPriority);\n });\n } else if (handle.anchor.includes('bottom')) {\n snaps.sort((a, b) => {\n const aPriority = a.guide.movingKey.includes('bottom') ? -1000 : 0;\n const bPriority = b.guide.movingKey.includes('bottom') ? -1000 : 0;\n return a.distance + aPriority - (b.distance + bPriority);\n });\n } else {\n snaps.sort((a, b) => a.distance - b.distance);\n }\n }\n }\n\n /**\n * Create a guide visualization for an active snap\n */\n _createGuideForSnap(snap: SnapState, anchorKey: string, movingAnchors: SnapAnchors, otherElements: BaseElement[], axis: 'x' | 'y'): ResizeSnap | null {\n const trackedAnchor = movingAnchors[anchorKey];\n if (!trackedAnchor) return null;\n\n if (axis === 'x') {\n // Find min/max Y from all elements on this snap line\n // Include ALL anchor points of moving element for full guide extent\n let minY = Infinity;\n let maxY = -Infinity;\n\n // Check all anchor points of the moving element\n for (const anchor of Object.values(movingAnchors)) {\n if (Math.abs(anchor.x - snap.snapTo) < 5) {\n minY = Math.min(minY, anchor.y);\n maxY = Math.max(maxY, anchor.y);\n }\n }\n\n // Check all anchor points of other elements\n for (const otherElement of otherElements) {\n const otherAnchors: SnapAnchors = RotationAnchorResolver.getSnapAnchors(otherElement) as SnapAnchors;\n for (const anchor of Object.values(otherAnchors)) {\n if (Math.abs(anchor.x - snap.snapTo) < 5) {\n minY = Math.min(minY, anchor.y);\n maxY = Math.max(maxY, anchor.y);\n }\n }\n }\n\n // Fallback if no anchors found\n if (!isFinite(minY) || !isFinite(maxY)) {\n minY = trackedAnchor.y;\n maxY = trackedAnchor.y;\n }\n\n return {\n snapTo: snap.snapTo,\n currentValue: trackedAnchor.x,\n distance: 0,\n guide: {\n type: 'vertical',\n x: snap.snapTo,\n y1: minY - 50,\n y2: maxY + 50,\n movingKey: anchorKey,\n otherKey: 'snap',\n },\n };\n } else if (axis === 'y') {\n // Find min/max X from all elements on this snap line\n // Include ALL anchor points of moving element for full guide extent\n let minX = Infinity;\n let maxX = -Infinity;\n\n // Check all anchor points of the moving element\n for (const anchor of Object.values(movingAnchors)) {\n if (Math.abs(anchor.y - snap.snapTo) < 5) {\n minX = Math.min(minX, anchor.x);\n maxX = Math.max(maxX, anchor.x);\n }\n }\n\n // Check all anchor points of other elements\n for (const otherElement of otherElements) {\n const otherAnchors: SnapAnchors = RotationAnchorResolver.getSnapAnchors(otherElement) as SnapAnchors;\n for (const anchor of Object.values(otherAnchors)) {\n if (Math.abs(anchor.y - snap.snapTo) < 5) {\n minX = Math.min(minX, anchor.x);\n maxX = Math.max(maxX, anchor.x);\n }\n }\n }\n\n // Fallback if no anchors found\n if (!isFinite(minX) || !isFinite(maxX)) {\n minX = trackedAnchor.x;\n maxX = trackedAnchor.x;\n }\n\n return {\n snapTo: snap.snapTo,\n currentValue: trackedAnchor.y,\n distance: 0,\n guide: {\n type: 'horizontal',\n y: snap.snapTo,\n x1: minX - 50,\n x2: maxX + 50,\n movingKey: anchorKey,\n otherKey: 'snap',\n },\n };\n }\n\n return null;\n }\n\n /**\n * Calculate aspect-ratio-aware sticky threshold for a given axis\n * Wide elements move faster horizontally, tall elements move faster vertically\n * Adjust threshold to compensate for this velocity difference\n */\n _getAdjustedStickyThreshold(axis: 'x' | 'y', element: SnapElement): number {\n const bbox = element.getVisualBoundingBox();\n const aspectRatio = bbox.width / bbox.height;\n\n // Base threshold\n let adjustedThreshold = this.stickyThreshold;\n\n if (axis === 'x') {\n // For wide elements (aspect > 1), horizontal edges move faster\n // Reduce X threshold proportionally to aspect ratio\n if (aspectRatio > 1) {\n adjustedThreshold = this.stickyThreshold / Math.sqrt(aspectRatio);\n }\n } else if (axis === 'y') {\n // For tall elements (aspect < 1), vertical edges move faster\n // Reduce Y threshold proportionally to inverse aspect ratio\n if (aspectRatio < 1) {\n adjustedThreshold = this.stickyThreshold * Math.sqrt(aspectRatio);\n }\n }\n\n return adjustedThreshold;\n }\n\n /**\n * Calculate what dimensions should be when edge reaches snap point\n * This ensures frozen dimensions match the geometry at the snap position\n *\n * The key insight: When a snap is first detected (edge within 5px of guideline),\n * we need to freeze dimensions at what they WILL BE when the edge reaches the snap point,\n * not what they ARE at the moment of detection. This prevents visual \"jumps\" during\n * corner resize because position compensation expects dimensions to match the snap point.\n *\n * @param element - The element being resized\n * @param axis - 'x' or 'y' axis of the snap\n * @param snapDelta - Distance to move to reach snap point (can be positive or negative)\n * @param anchorKey - Which anchor is snapping (e.g., 'topRight', 'bottomLeft', etc.)\n * @param handle - The resize handle being dragged\n * @returns {Object} { frozenWidth, frozenHeight } - Dimensions at snap point\n */\n _calculateFrozenDimensionsAtSnap(\n element: SnapElement,\n axis: 'x' | 'y',\n snapDelta: number,\n anchorKey: string,\n _handle: SnapHandleInfo | null\n ): { frozenWidth: number; frozenHeight: number } {\n const currentWidth = getTransformDataWidth(element) ?? element.width ?? 0;\n const currentHeight = getTransformDataHeight(element) ?? element.height ?? 0;\n\n let finalWidth = currentWidth;\n let finalHeight = currentHeight;\n\n const isImageElement = element.transformType === 'image';\n const aspectRatio = isImageElement\n ? element.imageAspectRatio || (currentWidth && currentHeight ? currentWidth / currentHeight : null)\n : null;\n\n // Determine which edge is snapping based on anchorKey (case-insensitive check)\n const anchorLower = anchorKey.toLowerCase();\n\n if (axis === 'x') {\n if (anchorLower.includes('right')) {\n // Right edge snapping: width increases by snapDelta\n finalWidth = currentWidth + snapDelta;\n if (isImageElement && aspectRatio) {\n finalHeight = finalWidth / aspectRatio;\n }\n } else if (anchorLower.includes('left')) {\n // Left edge snapping: width changes by -snapDelta\n finalWidth = currentWidth - snapDelta;\n if (isImageElement && aspectRatio) {\n finalHeight = finalWidth / aspectRatio;\n }\n }\n } else if (axis === 'y') {\n if (anchorLower.includes('bottom')) {\n // Bottom edge snapping: height increases by snapDelta\n finalHeight = currentHeight + snapDelta;\n if (isImageElement && aspectRatio) {\n finalWidth = finalHeight * aspectRatio;\n }\n } else if (anchorLower.includes('top')) {\n // Top edge snapping: height changes by -snapDelta\n finalHeight = currentHeight - snapDelta;\n if (isImageElement && aspectRatio) {\n finalWidth = finalHeight * aspectRatio;\n }\n }\n }\n\n\n return { frozenWidth: finalWidth, frozenHeight: finalHeight };\n }\n\n /**\n * Process sticky snapping for a single axis\n * @returns {Object} { snapDelta, bestSnap }\n */\n _processStickySnap(\n axis: 'x' | 'y',\n movingAnchors: SnapAnchors,\n snaps: ResizeSnap[],\n element: SnapElement,\n handle: SnapHandleInfo | null,\n startData: ResizeStartData | null,\n otherElements: BaseElement[]\n ): StickySnapResult {\n const activeSnap = this.activeSnaps[axis];\n let snapDelta = 0;\n let bestSnap: ResizeSnap | null = null;\n\n if (activeSnap) {\n // We have an active snap - check if we're still within sticky range\n const trackedAnchor = movingAnchors[activeSnap.anchorKey];\n\n if (trackedAnchor) {\n const currentValue = axis === 'x' ? trackedAnchor.x : trackedAnchor.y;\n const rawDistance = Math.abs(currentValue - activeSnap.snapTo);\n\n // Use aspect-ratio-aware threshold\n const adjustedThreshold = this._getAdjustedStickyThreshold(axis, element);\n\n if (axis === 'x') {\n } else if (axis === 'y') {\n // Bounding box available for debugging if needed\n }\n\n if (rawDistance < adjustedThreshold) {\n // Still within sticky range - keep locked to snap point\n snapDelta = activeSnap.snapTo - currentValue;\n\n // For text elements with Y-axis snap, height is derived (not settable)\n // Don't recalculate frozen font size - keep it completely locked\n // This prevents feedback loop: fontSize change → height change → edge moves → new snapDelta\n const shouldRecalculateFontSize = !(axis === 'y' && activeSnap.isPositionBasedSnap);\n\n if (shouldRecalculateFontSize) {\n // Recalculate frozen font size based on current snap delta\n const recalculatedFontSize = this._calculateFrozenFontSizeAtSnap(\n element,\n axis === 'x' ? snapDelta : 0,\n axis === 'y' ? snapDelta : 0,\n handle,\n startData\n );\n\n if (recalculatedFontSize !== activeSnap.frozenFontSize) {\n activeSnap.frozenFontSize = recalculatedFontSize;\n }\n }\n\n if (axis === 'x') {\n }\n\n // Create guide for visualization\n bestSnap = this._createGuideForSnap(activeSnap, activeSnap.anchorKey, movingAnchors, otherElements, axis);\n } else {\n // Moved too far - break free from snap\n const previousSnap = activeSnap;\n this.activeSnaps[axis] = null;\n\n // Check if we should start a new snap (use base snapThreshold for initial snap detection)\n if (snaps.length > 0 && snaps[0].distance < this.snapThreshold) {\n bestSnap = snaps[0];\n snapDelta = bestSnap.snapTo - bestSnap.currentValue;\n\n // If re-entering the EXACT SAME snap (same anchor AND same snap position), preserve frozen state\n // Otherwise, calculate fresh frozen state for the new snap\n const shouldPreserveFreeze =\n previousSnap &&\n previousSnap.anchorKey === bestSnap.guide.movingKey &&\n Math.abs(previousSnap.snapTo - bestSnap.snapTo) < 1; // Same snap line (within 1px)\n\n const frozenFontSize = shouldPreserveFreeze\n ? previousSnap.frozenFontSize\n : this._calculateFrozenFontSizeAtSnap(\n element,\n axis === 'x' ? snapDelta : 0,\n axis === 'y' ? snapDelta : 0,\n handle,\n startData\n );\n\n const isPositionBased = this._isPositionBasedSnap(element, axis);\n // For position-based snaps, frozen position must include the snap delta\n // because we achieve alignment by adjusting position, not dimensions\n let frozenPos;\n if (shouldPreserveFreeze) {\n frozenPos = previousSnap.frozenPosition;\n } else if (isPositionBased) {\n // Include snap adjustment in frozen position for position-based snaps\n frozenPos = {\n x: axis === 'x' ? element.x + snapDelta : element.x,\n y: axis === 'y' ? element.y + snapDelta : element.y,\n };\n } else {\n frozenPos = { x: element.x, y: element.y };\n }\n\n // Calculate frozen dimensions at snap point (not at threshold entry)\n // This prevents visual \"jumps\" during corner resize\n const frozenDimensions = this._calculateFrozenDimensionsAtSnap(\n element,\n axis as 'x' | 'y',\n snapDelta,\n bestSnap.guide.movingKey,\n handle\n );\n\n this.activeSnaps[axis] = {\n snapTo: bestSnap.snapTo,\n anchorKey: bestSnap.guide.movingKey,\n frozenFontSize,\n frozenWidth: frozenDimensions.frozenWidth,\n frozenHeight: frozenDimensions.frozenHeight,\n frozenPosition: frozenPos,\n isPositionBasedSnap: isPositionBased,\n };\n }\n }\n } else {\n // Anchor no longer exists - clear the snap\n this.activeSnaps[axis] = null;\n }\n } else if (snaps.length > 0 && snaps[0].distance < this.snapThreshold) {\n // New snap - enter sticky snap mode\n bestSnap = snaps[0];\n snapDelta = bestSnap.snapTo - bestSnap.currentValue;\n\n // Check if we're re-entering the EXACT SAME snap (same anchor AND same snap position)\n const existingSnap = this.activeSnaps[axis];\n const shouldPreserveFreeze =\n existingSnap &&\n existingSnap.anchorKey === bestSnap.guide.movingKey &&\n Math.abs(existingSnap.snapTo - bestSnap.snapTo) < 1; // Same snap line (within 1px)\n\n const frozenFontSize = shouldPreserveFreeze\n ? existingSnap.frozenFontSize\n : this._calculateFrozenFontSizeAtSnap(\n element,\n axis === 'x' ? snapDelta : 0,\n axis === 'y' ? snapDelta : 0,\n handle,\n startData\n );\n\n const isPositionBased = this._isPositionBasedSnap(element, axis);\n // For position-based snaps, frozen position must include the snap delta\n // because we achieve alignment by adjusting position, not dimensions\n let frozenPos;\n if (shouldPreserveFreeze) {\n frozenPos = existingSnap.frozenPosition;\n } else if (isPositionBased) {\n // Include snap adjustment in frozen position for position-based snaps\n frozenPos = {\n x: axis === 'x' ? element.x + snapDelta : element.x,\n y: axis === 'y' ? element.y + snapDelta : element.y,\n };\n } else {\n frozenPos = { x: element.x, y: element.y };\n }\n\n // Calculate frozen dimensions at snap point (not at threshold entry)\n // This prevents visual \"jumps\" during corner resize\n const frozenDimensions = this._calculateFrozenDimensionsAtSnap(\n element,\n axis as 'x' | 'y',\n snapDelta,\n bestSnap.guide.movingKey,\n handle\n );\n\n this.activeSnaps[axis] = {\n snapTo: bestSnap.snapTo,\n anchorKey: bestSnap.guide.movingKey,\n frozenFontSize,\n frozenWidth: frozenDimensions.frozenWidth,\n frozenHeight: frozenDimensions.frozenHeight,\n frozenPosition: frozenPos,\n isPositionBasedSnap: isPositionBased,\n };\n }\n\n return { snapDelta, bestSnap };\n }\n\n /**\n * Adjust element for right edge resize\n */\n _adjustElementForRightResize(element: SnapElement, snapDeltaX: number, handle: SnapHandleInfo): void {\n // Store visual bbox before adjustment\n const beforeVisualBbox = element.getVisualBoundingBox();\n\n // For images, dimensions will be frozen later, so don't adjust width here\n const isImageElement = element.transformType === 'image';\n\n // Adjust width in transformData (skip for images as dimensions are frozen)\n if (!isImageElement) {\n const tdWidth = getTransformDataWidth(element);\n if (tdWidth !== undefined) {\n setTransformDataWidth(element, tdWidth + snapDeltaX);\n } else if (element.width !== undefined) {\n element.width += snapDeltaX;\n }\n }\n\n // For SIDE handles (middle-right), adjust position for center-based elements\n // The center shifts by half the width change to keep the left edge fixed\n if (handle.anchor === 'middle-right') {\n const rotationRad = RotationUtils.toRadiansInverse(element.rotation);\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n element.x += (snapDeltaX / 2) * cos;\n element.y += (snapDeltaX / 2) * sin;\n\n } else {\n // For CORNER handles, we need to adjust position to keep the opposite (left) edge fixed\n // This is because the width change affects the visual bbox, which affects positioning\n\n // For images, skip compensation since width won't change (frozen dimensions)\n if (!isImageElement) {\n // Get the new visual bbox after width adjustment\n const afterVisualBbox = element.getVisualBoundingBox();\n\n // Calculate how much the left edge moved due to the width change\n const leftEdgeShift = afterVisualBbox.x - beforeVisualBbox.x;\n\n // Compensate by adjusting position to keep left edge in place\n if (leftEdgeShift !== 0) {\n element.x -= leftEdgeShift;\n\n }\n } else {\n }\n }\n\n }\n\n /**\n * Adjust element for left edge resize\n */\n _adjustElementForLeftResize(element: SnapElement, snapDeltaX: number, handle: SnapHandleInfo): void {\n // Store visual bbox before adjustment\n const beforeVisualBbox = element.getVisualBoundingBox();\n\n // For images, dimensions will be frozen later, so don't adjust width here\n // This prevents position compensation from seeing a width change that will be undone\n const isImageElement = element.transformType === 'image';\n\n // Adjust width first (skip for images as dimensions are frozen)\n if (!isImageElement) {\n const tdWidth = getTransformDataWidth(element);\n if (tdWidth !== undefined) {\n setTransformDataWidth(element, tdWidth - snapDeltaX);\n } else if (element.width !== undefined) {\n element.width -= snapDeltaX;\n }\n }\n\n // For SIDE handles (middle-left), adjust position for center-based elements\n // The center shifts by half the width change to keep the right edge fixed\n // When left edge moves right (positive snapDeltaX), width decreases, center moves right\n // When left edge moves left (negative snapDeltaX), width increases, center moves left\n if (handle.anchor === 'middle-left') {\n const rotationRad = RotationUtils.toRadiansInverse(element.rotation);\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Width change is -snapDeltaX, so center shift is -snapDeltaX/2\n // But we want the center to move in the SAME direction as the left edge\n // So we add +snapDeltaX/2\n element.x += (snapDeltaX / 2) * cos;\n element.y += (snapDeltaX / 2) * sin;\n\n } else {\n // For CORNER handles, we need to adjust position to keep the opposite (right) edge fixed\n // This is because the width change affects the visual bbox, which affects positioning\n\n // First apply the direct position adjustment for the snap\n element.x += snapDeltaX;\n\n // For non-images, also compensate position to keep opposite edge fixed\n // For images, dimensions will be frozen so width won't actually change,\n // making the compensation unnecessary (and causing incorrect positioning)\n if (!isImageElement) {\n // Get the new visual bbox after width and position adjustment\n const afterVisualBbox = element.getVisualBoundingBox();\n\n // Calculate how much the right edge moved due to the width change\n const rightEdgeShift =\n afterVisualBbox.x + afterVisualBbox.width - (beforeVisualBbox.x + beforeVisualBbox.width);\n\n // Compensate by adjusting position to keep right edge in place\n if (rightEdgeShift !== 0) {\n element.x -= rightEdgeShift;\n\n }\n } else {\n }\n }\n\n }\n\n /**\n * Adjust element for bottom edge resize\n */\n _adjustElementForBottomResize(element: SnapElement, snapDeltaY: number, handle: SnapHandleInfo): void {\n // Store visual bbox before adjustment\n const beforeVisualBbox = element.getVisualBoundingBox();\n\n // For images, dimensions will be frozen later, so don't adjust height here\n const isImageElement = element.transformType === 'image';\n\n // Adjust height in transformData (skip for images as dimensions are frozen)\n if (!isImageElement) {\n const tdHeight = getTransformDataHeight(element);\n if (tdHeight !== undefined) {\n setTransformDataHeight(element, tdHeight + snapDeltaY);\n } else if (element.height !== undefined) {\n element.height += snapDeltaY;\n }\n }\n\n // Get bbox after height adjustment to check if height is settable\n const afterVisualBbox = element.getVisualBoundingBox();\n const heightChanged = Math.abs(afterVisualBbox.height - beforeVisualBbox.height) > 0.01;\n\n // For SIDE handles (middle-bottom), adjust position for center-based elements\n // The center shifts by half the height change to keep the top edge fixed\n if (handle.anchor === 'middle-bottom') {\n const rotationRad = RotationUtils.toRadiansInverse(element.rotation);\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n // Height increases downward, so we move in the perpendicular direction\n element.x -= (snapDeltaY / 2) * sin;\n element.y += (snapDeltaY / 2) * cos;\n\n } else {\n // For CORNER handles with settable height, compensate position to keep top edge fixed\n // For images, dimensions will be frozen so height won't change,\n // making the compensation unnecessary\n if (!isImageElement && heightChanged) {\n const topEdgeShift = afterVisualBbox.y - beforeVisualBbox.y;\n\n if (topEdgeShift !== 0) {\n element.y -= topEdgeShift;\n\n }\n } else if (isImageElement) {\n } else {\n // For CORNER handles with derived height (like text elements),\n // adjust position directly to align the bottom edge\n element.y += snapDeltaY;\n\n }\n }\n\n }\n\n /**\n * Adjust element for top edge resize\n */\n _adjustElementForTopResize(element: SnapElement, snapDeltaY: number, handle: SnapHandleInfo): void {\n // Store visual bbox before adjustment\n const beforeVisualBbox = element.getVisualBoundingBox();\n\n // For images, dimensions will be frozen later, so don't adjust height here\n const isImageElement = element.transformType === 'image';\n\n // Adjust height in transformData (skip for images as dimensions are frozen)\n if (!isImageElement) {\n const tdHeight = getTransformDataHeight(element);\n if (tdHeight !== undefined) {\n setTransformDataHeight(element, tdHeight - snapDeltaY);\n } else if (element.height !== undefined) {\n element.height -= snapDeltaY;\n }\n }\n\n // Get bbox after height adjustment to check if height is settable\n const afterVisualBbox = element.getVisualBoundingBox();\n const heightChanged = Math.abs(afterVisualBbox.height - beforeVisualBbox.height) > 0.01;\n\n // For SIDE handles (middle-top), adjust position for center-based elements\n // The center shifts by half the height change to keep the bottom edge fixed\n if (handle.anchor === 'middle-top') {\n const rotationRad = RotationUtils.toRadiansInverse(element.rotation);\n const cos = Math.cos(rotationRad);\n const sin = Math.sin(rotationRad);\n\n // Height change is -snapDeltaY, center shift follows top edge movement\n element.x -= (snapDeltaY / 2) * sin;\n element.y += (snapDeltaY / 2) * cos;\n\n } else {\n // For CORNER handles, adjust position based on whether height is settable\n\n // For images, height will be frozen (dimension won't change)\n // so we only apply direct position adjustment, not compensation\n const shouldCompensate = !isImageElement && heightChanged;\n\n // First apply the direct position adjustment for the snap\n element.y += snapDeltaY;\n\n if (shouldCompensate) {\n // For CORNER handles with settable height, compensate to keep bottom edge fixed\n // Get the new visual bbox after height and position adjustment\n const newVisualBbox = element.getVisualBoundingBox();\n\n // Calculate how much the bottom edge moved due to the height change\n const bottomEdgeShift = newVisualBbox.y + newVisualBbox.height - (beforeVisualBbox.y + beforeVisualBbox.height);\n\n // Compensate by adjusting position to keep bottom edge in place\n if (bottomEdgeShift !== 0) {\n element.y -= bottomEdgeShift;\n\n }\n } else if (isImageElement) {\n } else {\n // For text elements with derived height\n }\n }\n\n }\n\n /**\n * Snap dimensions during resize operation\n *\n * @param {object} result - Resize result AFTER ResizeHandler processes it\n * @param {array} allElements - All elements in the scene\n * @param {object} startData - Original data from when resize started (for font size preservation)\n * @returns {object} Modified resize result with adjusted element\n */\n snapDimensions(result: ResizeResult, allElements: BaseElement[] | null, startData: ResizeStartData | null = null): ResizeResult {\n if (!this.enabled || !allElements) {\n this.clearGuides();\n return result;\n }\n\n const { element } = result;\n const handle = result.handle;\n\n // Get anchors for the resized element\n // For images with frozen dimensions, we need to use unfrozen dimensions for distance checking\n // Otherwise the snap can never be broken because the anchors don't move\n let movingAnchors: SnapAnchors;\n const isImageElement = element.transformType === 'image';\n const hasActiveSnap = this.activeSnaps.x !== null || this.activeSnaps.y !== null;\n\n if (isImageElement && hasActiveSnap && result.isFrozen && result.unfrozenDimensions) {\n // Use unfrozen dimensions from ResizeHandler for anchor calculation\n // This allows detecting when user is trying to break free from snap\n const tempElement = element.clone();\n\n if (tempElement.transformData) {\n // Use the visual dimensions that would exist without freeze\n const visualBbox = element.getVisualBoundingBox();\n const frozenVisualWidth = visualBbox.width;\n const frozenVisualHeight = visualBbox.height;\n\n // Calculate scale from frozen to unfrozen\n const startBboxWidth = startData?.width ?? element.width ?? 0;\n const startBboxHeight = startData?.height ?? element.height ?? 0;\n const startVisualWidth = startData?.visualWidth ?? frozenVisualWidth;\n const startVisualHeight = startData?.visualHeight ?? frozenVisualHeight;\n\n // Unfrozen dimensions are in visual space\n const scaleX = result.unfrozenDimensions.width / startVisualWidth;\n const scaleY = result.unfrozenDimensions.height / startVisualHeight;\n\n // Apply to bbox dimensions\n setTransformDataWidth(tempElement, startBboxWidth * scaleX);\n setTransformDataHeight(tempElement, startBboxHeight * scaleY);\n\n }\n\n movingAnchors = RotationAnchorResolver.getSnapAnchors(tempElement) as SnapAnchors;\n } else {\n movingAnchors = RotationAnchorResolver.getSnapAnchors(element) as SnapAnchors;\n }\n\n // Get other elements (exclude the one being resized)\n const otherElements = allElements.filter((s) => s.id !== element.id);\n\n if (otherElements.length === 0) {\n this.clearGuides();\n return result;\n }\n\n // Populate spatial grid with anchor points from other elements\n this._populateSpatialGrid(otherElements);\n\n // Determine which anchors to check based on handle position\n const snapKeys = this._getSnapKeysForHandle(handle);\n const targetSnapKeys = this._getTargetSnapKeys();\n\n // Find all snaps within threshold (uses spatial grid internally)\n const xSnaps = this._findXSnaps(movingAnchors, otherElements, snapKeys, targetSnapKeys);\n const ySnaps = this._findYSnaps(movingAnchors, otherElements, snapKeys, targetSnapKeys);\n\n // Prioritize snaps based on handle position\n this._prioritizeSnaps(xSnaps, handle, 'x');\n this._prioritizeSnaps(ySnaps, handle, 'y');\n\n // Process sticky snapping for both axes\n const xSnapResult = this._processStickySnap('x', movingAnchors, xSnaps, element, handle, startData, otherElements);\n const ySnapResult = this._processStickySnap('y', movingAnchors, ySnaps, element, handle, startData, otherElements);\n\n const snapDeltaX = xSnapResult.snapDelta;\n const snapDeltaY = ySnapResult.snapDelta;\n const bestSnapX = xSnapResult.bestSnap;\n const bestSnapY = ySnapResult.bestSnap;\n\n // Set guides for active snaps\n const newGuides: SnapGuideExtended[] = [];\n if (bestSnapX) newGuides.push(bestSnapX.guide);\n if (bestSnapY) newGuides.push(bestSnapY.guide);\n\n this.guides = newGuides as SnapGuide[];\n\n if (newGuides.length > 0) {\n }\n\n // For images, ResizeHandler already applied frozen dimensions during calculateResize\n // So we should NOT adjust anything here - just return the element as-is\n // Adjusting here would cause a \"double adjustment\" and visual jump\n // isImageElement already declared above\n const isCornerResize =\n handle &&\n (handle.anchor === 'top-left' ||\n handle.anchor === 'top-right' ||\n handle.anchor === 'bottom-left' ||\n handle.anchor === 'bottom-right');\n\n if (isImageElement && isCornerResize && (snapDeltaX !== 0 || snapDeltaY !== 0)) {\n return result;\n }\n\n // If we have snaps, adjust the element position to achieve snapping\n if (snapDeltaX !== 0 || snapDeltaY !== 0) {\n\n // Clone the element to apply adjustments\n const snappedElement = element.clone();\n\n // IMPORTANT: Preserve frozen state from when sticky snap was first activated\n // During sticky snap, we freeze fontSize, width (for Y snaps), and position (x,y) to prevent micro-adjustments\n const frozenState = this.activeSnaps.x || this.activeSnaps.y;\n const preservedFontSize = frozenState?.frozenFontSize ?? element.fontSize ?? 0;\n const preservedWidth = frozenState?.frozenWidth ?? getTransformDataWidth(element) ?? element.width ?? 0;\n const preservedHeight = frozenState?.frozenHeight ?? getTransformDataHeight(element) ?? element.height ?? 0;\n const preservedPosition = frozenState?.frozenPosition ?? { x: element.x, y: element.y };\n\n\n // Skip position adjustments for image elements in crop mode resizing the frame\n // The crop center position is carefully calculated in ImageElement.resize() and must not be modified\n const isImageCropMode = snappedElement.isCropping && !String(handle.anchor).startsWith('crop-');\n\n if (!isImageCropMode) {\n // Apply element adjustments based on which edge is actually snapped (not which handle is dragged)\n // This fixes the bug where dragging center-right handle on a left-edge-snapped element\n // would incorrectly apply right edge adjustment instead of left edge adjustment\n if (handle && handle.anchor) {\n // Horizontal adjustments - determine which edge is snapped from the active snap anchor key\n if (snapDeltaX !== 0) {\n const xSnapAnchor = this.activeSnaps.x?.anchorKey;\n const xSnapAnchorLower = xSnapAnchor?.toLowerCase();\n if (xSnapAnchorLower && xSnapAnchorLower.includes('right')) {\n this._adjustElementForRightResize(snappedElement, snapDeltaX, handle);\n } else if (xSnapAnchorLower && xSnapAnchorLower.includes('left')) {\n this._adjustElementForLeftResize(snappedElement, snapDeltaX, handle);\n } else {\n snappedElement.x += snapDeltaX;\n }\n }\n\n // Vertical adjustments - determine which edge is snapped from the active snap anchor key\n if (snapDeltaY !== 0) {\n const ySnapAnchor = this.activeSnaps.y?.anchorKey;\n const ySnapAnchorLower = ySnapAnchor?.toLowerCase();\n if (ySnapAnchorLower && ySnapAnchorLower.includes('bottom')) {\n this._adjustElementForBottomResize(snappedElement, snapDeltaY, handle);\n } else if (ySnapAnchorLower && ySnapAnchorLower.includes('top')) {\n this._adjustElementForTopResize(snappedElement, snapDeltaY, handle);\n } else {\n snappedElement.y += snapDeltaY;\n }\n }\n } else {\n // Fallback: just move position\n snappedElement.x += snapDeltaX;\n snappedElement.y += snapDeltaY;\n }\n }\n\n // Restore the frozen state to prevent micro-adjustments\n // During sticky snap, we completely freeze fontSize\n // But we must preserve position adjustments made by snap compensation\n\n if (snappedElement.fontSize !== undefined) {\n snappedElement.fontSize = preservedFontSize;\n }\n\n // Width/Height freeze logic for images and elements with aspect ratios\n // For images: freeze BOTH dimensions when either axis snaps to maintain aspect ratio\n // For text: Only freeze width for position-based Y snaps (width affects text wrapping)\n const isImageElement2 = snappedElement.transformType === 'image';\n const hasActiveSnap2 = this.activeSnaps.x !== null || this.activeSnaps.y !== null;\n\n if (isImageElement2 && hasActiveSnap2) {\n // For images with active snap: Freeze BOTH dimensions to maintain aspect ratio\n if (getTransformDataWidth(snappedElement) !== undefined) {\n // Update frozen dimensions if they've changed significantly (when snap is first activated)\n // This ensures frozen dimensions match the snapped dimensions, not the pre-snap dimensions\n const activeSnapState = this.activeSnaps.x || this.activeSnaps.y;\n let finalWidth = preservedWidth;\n let finalHeight = preservedHeight;\n\n if (activeSnapState) {\n // Use the frozen dimensions that were calculated at snap point\n // DO NOT update them based on current dimensions - they were already calculated correctly\n // in _calculateFrozenDimensionsAtSnap to match the geometry at the snap point\n finalWidth = activeSnapState.frozenWidth ?? finalWidth;\n finalHeight = activeSnapState.frozenHeight ?? finalHeight;\n\n }\n\n const tdWidth = getTransformDataWidth(element);\n const tdHeight = getTransformDataHeight(element);\n const imageAspectRatio =\n (snappedElement as ImageElement).imageAspectRatio ||\n (tdWidth && tdHeight\n ? tdWidth / tdHeight\n : null);\n\n if (imageAspectRatio && imageAspectRatio !== 0) {\n finalHeight = finalWidth / imageAspectRatio;\n }\n\n // Apply the frozen dimensions to prevent aspect ratio drift\n setTransformDataWidth(snappedElement, finalWidth);\n setTransformDataHeight(snappedElement, finalHeight);\n\n if (activeSnapState) {\n activeSnapState.frozenWidth = finalWidth;\n activeSnapState.frozenHeight = finalHeight;\n }\n\n }\n } else {\n // For text elements: Only freeze width for position-based Y snaps\n // For Y snaps, width affects derived height (text wrapping), so must freeze\n // For X snaps, we're actively adjusting width to align edges, so don't freeze\n const yIsPositionBasedSnap = this.activeSnaps.y?.isPositionBasedSnap || false;\n if (yIsPositionBasedSnap) {\n if (getTransformDataWidth(snappedElement) !== undefined) {\n setTransformDataWidth(snappedElement, preservedWidth);\n } else if (snappedElement.width !== undefined) {\n snappedElement.width = preservedWidth;\n }\n }\n }\n\n // Position freeze logic:\n // 1. If no snap: restore frozen position\n // 2. If position-based snap: restore frozen position (includes snap adjustment)\n // 3. If dimension-based snap: keep compensated position (for edge alignment)\n // 4. EXCEPTION: Side handles NEVER freeze (center-based, must update continuously)\n // 5. EXCEPTION: Image crop mode NEVER freezes position (crop center calculated precisely in resize)\n const isHorizontalSideHandle = handle && (handle.anchor === 'middle-left' || handle.anchor === 'middle-right');\n const isVerticalSideHandle = handle && (handle.anchor === 'middle-top' || handle.anchor === 'middle-bottom');\n\n const xIsPositionBasedSnap = this.activeSnaps.x?.isPositionBasedSnap || false;\n const yIsPositionBasedSnap = this.activeSnaps.y?.isPositionBasedSnap || false;\n\n // Freeze X: no snap OR position-based snap (frozen position includes alignment)\n // BUT skip for image crop mode - the crop center is precisely calculated in ImageElement.resize()\n if (\n (snapDeltaX === 0 || xIsPositionBasedSnap) &&\n !isHorizontalSideHandle &&\n !isVerticalSideHandle &&\n !isImageCropMode\n ) {\n snappedElement.x = preservedPosition.x;\n }\n\n // Freeze Y: no snap OR position-based snap (frozen position includes alignment)\n // BUT skip for image crop mode - the crop center is precisely calculated in ImageElement.resize()\n if (\n (snapDeltaY === 0 || yIsPositionBasedSnap) &&\n !isVerticalSideHandle &&\n !isHorizontalSideHandle &&\n !isImageCropMode\n ) {\n snappedElement.y = preservedPosition.y;\n }\n\n\n return {\n ...result,\n element: snappedElement,\n snapAdjustment: { dx: snapDeltaX, dy: snapDeltaY },\n // Flag to indicate we're in sticky snap mode (used to freeze font size)\n isInStickySnap: true,\n };\n }\n\n return result;\n }\n\n /**\n * Snap rotation (for future implementation)\n */\n snapRotation(rotation: number, _element: BaseElement, _allElements?: BaseElement[]): number {\n if (!this.enabled) return rotation;\n\n // Common rotation angles to snap to\n const commonAngles = [0, 45, 90, 135, 180, 225, 270, 315, 360];\n const snapAngle = 5; // degrees\n\n for (const angle of commonAngles) {\n if (isNear(rotation, angle, snapAngle)) {\n return angle;\n }\n }\n\n return rotation;\n }\n}\n","/**\n * SpacingSystem - Detects and displays spacing between elements during drag operations\n *\n * Shows red measurement lines with distance labels when elements are close to each other\n * but not snapped. Provides visual feedback for consistent spacing.\n */\n\nimport { SPACING_SNAP_THRESHOLD } from '../constants.js';\nimport type { BaseElement } from './BaseElement.js';\nimport type { SpacingIndicator } from '../types/index.js';\n\ninterface SpacingSystemOptions {\n detectionThreshold?: number;\n minDistance?: number;\n enabled?: boolean;\n showLabels?: boolean;\n}\n\nexport class SpacingSystem {\n detectionThreshold: number;\n minDistance: number;\n enabled: boolean;\n showLabels: boolean;\n indicators: SpacingIndicator[];\n lastSnapTarget: { x: number; y: number } | null = null;\n\n constructor(options: SpacingSystemOptions = {}) {\n this.detectionThreshold = options.detectionThreshold || 100; // Show spacing within 50px\n this.minDistance = options.minDistance || 10; // Don't show spacing if closer than 10px (snap zone)\n this.enabled = options.enabled !== false;\n this.showLabels = options.showLabels !== false;\n this.indicators = [];\n }\n\n /**\n * Enable or disable spacing detection\n */\n setEnabled(enabled: boolean): void {\n this.enabled = enabled;\n }\n\n /**\n * Set detection threshold in pixels\n */\n setDetectionThreshold(threshold: number): void {\n this.detectionThreshold = threshold;\n }\n\n /**\n * Get current spacing indicators for rendering\n */\n getIndicators(): SpacingIndicator[] {\n return this.indicators;\n }\n\n /**\n * Clear all spacing indicators\n */\n clearIndicators(): void {\n this.indicators = [];\n this.lastSnapTarget = null;\n }\n\n /**\n * Snap position to match consistent spacing patterns\n * Returns adjusted position if spacing snap is detected, otherwise returns original position\n */\n snapToSpacing(\n position: { x: number; y: number },\n movingElement: BaseElement,\n allElements: BaseElement[] | null\n ): { x: number; y: number } {\n if (!this.enabled || !allElements) {\n return position;\n }\n\n const snapThreshold = SPACING_SNAP_THRESHOLD;\n\n // Get other elements (exclude the one being moved)\n const otherElements = allElements.filter((s) => s.id !== movingElement.id);\n if (otherElements.length === 0) {\n return position;\n }\n\n // Create a temporary element at the new position\n const tempElement = movingElement.clone();\n tempElement.x = position.x;\n tempElement.y = position.y;\n const movingBBox = tempElement.getVisualBoundingBox();\n\n // Calculate offset between element position and visual bounding box\n // This is important for elements where the visual bbox doesn't start at (x, y)\n const visualOffsetX = movingBBox.x - position.x;\n const visualOffsetY = movingBBox.y - position.y;\n\n let snapX = position.x;\n let snapY = position.y;\n let didSnapX = false;\n let didSnapY = false;\n\n // Find elements in horizontal alignment (for vertical spacing snap)\n const horizontallyAligned = otherElements.filter((s) => {\n const bbox = s.getVisualBoundingBox();\n const horizontalOverlap = !(movingBBox.x + movingBBox.width < bbox.x || movingBBox.x > bbox.x + bbox.width);\n return horizontalOverlap;\n });\n\n // Sort by y position\n horizontallyAligned.sort((a, b) => {\n const bboxA = a.getVisualBoundingBox();\n const bboxB = b.getVisualBoundingBox();\n return bboxA.y - bboxB.y;\n });\n\n // Check for consistent vertical spacing pattern\n if (horizontallyAligned.length >= 1) {\n\n // Find the closest element above and below the moving element first\n let closestAbove: BaseElement | null = null;\n let closestBelow: BaseElement | null = null;\n\n for (const element of horizontallyAligned) {\n const bbox = element.getVisualBoundingBox();\n\n // Element is above moving element\n if (bbox.y + bbox.height <= movingBBox.y) {\n if (!closestAbove || bbox.y > closestAbove.getVisualBoundingBox().y) {\n closestAbove = element;\n }\n }\n // Element is below moving element\n else if (bbox.y >= movingBBox.y + movingBBox.height) {\n if (!closestBelow || bbox.y < closestBelow.getVisualBoundingBox().y) {\n closestBelow = element;\n }\n }\n }\n\n // Collect all gaps between adjacent elements\n const gaps: number[] = [];\n for (let i = 0; i < horizontallyAligned.length - 1; i++) {\n const bbox1 = horizontallyAligned[i].getVisualBoundingBox();\n const bbox2 = horizontallyAligned[i + 1].getVisualBoundingBox();\n const gap = bbox2.y - (bbox1.y + bbox1.height);\n gaps.push(gap);\n }\n\n\n // Try to find a spacing pattern to snap to\n // Priority: use the gap from the closest adjacent element if it appears elsewhere\n let targetGap: number | null = null;\n\n if (closestAbove) {\n const aboveBBox = closestAbove.getVisualBoundingBox();\n const currentGap = movingBBox.y - (aboveBBox.y + aboveBBox.height);\n\n // Check if this gap matches any existing gap in the layout\n for (const gap of gaps) {\n if (Math.abs(currentGap - gap) <= snapThreshold) {\n targetGap = gap;\n break;\n }\n }\n }\n\n if (!targetGap && closestBelow) {\n const belowBBox = closestBelow.getVisualBoundingBox();\n const currentGap = belowBBox.y - (movingBBox.y + movingBBox.height);\n\n // Check if this gap matches any existing gap in the layout\n for (const gap of gaps) {\n if (Math.abs(currentGap - gap) <= snapThreshold) {\n targetGap = gap;\n break;\n }\n }\n }\n\n if (targetGap !== null && targetGap > 0) {\n // Check if moving element should snap between closestAbove and closestBelow\n if (closestAbove) {\n const aboveBBox = closestAbove.getVisualBoundingBox();\n const currentGap = movingBBox.y - (aboveBBox.y + aboveBBox.height);\n const gapDiff = Math.abs(currentGap - targetGap);\n\n\n if (gapDiff <= snapThreshold) {\n // Snap to match the pattern gap\n // targetVisualY is where the visual bbox top should be\n const targetVisualY = aboveBBox.y + aboveBBox.height + targetGap;\n // Convert from visual bbox Y to element Y by subtracting the offset\n const targetElementY = targetVisualY - visualOffsetY;\n snapY = targetElementY;\n didSnapY = true;\n }\n }\n\n if (!didSnapY && closestBelow) {\n const belowBBox = closestBelow.getVisualBoundingBox();\n const currentGap = belowBBox.y - (movingBBox.y + movingBBox.height);\n const gapDiff = Math.abs(currentGap - targetGap);\n\n\n if (gapDiff <= snapThreshold) {\n // Snap to match the pattern gap\n // targetVisualY is where the visual bbox top should be\n const targetVisualY = belowBBox.y - movingBBox.height - targetGap;\n // Convert from visual bbox Y to element Y by subtracting the offset\n const targetElementY = targetVisualY - visualOffsetY;\n snapY = targetElementY;\n didSnapY = true;\n }\n }\n }\n }\n\n // Find elements in vertical alignment (for horizontal spacing snap)\n const verticallyAligned = otherElements.filter((s) => {\n const bbox = s.getVisualBoundingBox();\n const verticalOverlap = !(movingBBox.y + movingBBox.height < bbox.y || movingBBox.y > bbox.y + bbox.height);\n return verticalOverlap;\n });\n\n // Sort by x position\n verticallyAligned.sort((a, b) => {\n const bboxA = a.getVisualBoundingBox();\n const bboxB = b.getVisualBoundingBox();\n return bboxA.x - bboxB.x;\n });\n\n // Check for consistent horizontal spacing pattern\n if (verticallyAligned.length >= 1) {\n // Find the closest element left and right of the moving element first\n let closestLeft: BaseElement | null = null;\n let closestRight: BaseElement | null = null;\n\n for (const element of verticallyAligned) {\n const bbox = element.getVisualBoundingBox();\n\n // Element is to the left of moving element\n if (bbox.x + bbox.width <= movingBBox.x) {\n if (!closestLeft || bbox.x > closestLeft.getVisualBoundingBox().x) {\n closestLeft = element;\n }\n }\n // Element is to the right of moving element\n else if (bbox.x >= movingBBox.x + movingBBox.width) {\n if (!closestRight || bbox.x < closestRight.getVisualBoundingBox().x) {\n closestRight = element;\n }\n }\n }\n\n // Collect all gaps between adjacent elements\n const gaps: number[] = [];\n for (let i = 0; i < verticallyAligned.length - 1; i++) {\n const bbox1 = verticallyAligned[i].getVisualBoundingBox();\n const bbox2 = verticallyAligned[i + 1].getVisualBoundingBox();\n const gap = bbox2.x - (bbox1.x + bbox1.width);\n gaps.push(gap);\n }\n\n // Try to find a spacing pattern to snap to\n let targetGap: number | null = null;\n\n if (closestLeft) {\n const leftBBox = closestLeft.getVisualBoundingBox();\n const currentGap = movingBBox.x - (leftBBox.x + leftBBox.width);\n\n // Check if this gap matches any existing gap in the layout\n for (const gap of gaps) {\n if (Math.abs(currentGap - gap) <= snapThreshold) {\n targetGap = gap;\n break;\n }\n }\n }\n\n if (!targetGap && closestRight) {\n const rightBBox = closestRight.getVisualBoundingBox();\n const currentGap = rightBBox.x - (movingBBox.x + movingBBox.width);\n\n // Check if this gap matches any existing gap in the layout\n for (const gap of gaps) {\n if (Math.abs(currentGap - gap) <= snapThreshold) {\n targetGap = gap;\n break;\n }\n }\n }\n\n if (targetGap !== null && targetGap > 0) {\n // Check if moving element should snap between closestLeft and closestRight\n if (closestLeft) {\n const leftBBox = closestLeft.getVisualBoundingBox();\n const currentGap = movingBBox.x - (leftBBox.x + leftBBox.width);\n const gapDiff = Math.abs(currentGap - targetGap);\n\n if (gapDiff <= snapThreshold) {\n // Snap to match the pattern gap\n // targetVisualX is where the visual bbox left should be\n const targetVisualX = leftBBox.x + leftBBox.width + targetGap;\n // Convert from visual bbox X to element X by subtracting the offset\n const targetElementX = targetVisualX - visualOffsetX;\n snapX = targetElementX;\n didSnapX = true;\n }\n }\n\n if (!didSnapX && closestRight) {\n const rightBBox = closestRight.getVisualBoundingBox();\n const currentGap = rightBBox.x - (movingBBox.x + movingBBox.width);\n const gapDiff = Math.abs(currentGap - targetGap);\n\n if (gapDiff <= snapThreshold) {\n // Snap to match the pattern gap\n // targetVisualX is where the visual bbox left should be\n const targetVisualX = rightBBox.x - movingBBox.width - targetGap;\n // Convert from visual bbox X to element X by subtracting the offset\n const targetElementX = targetVisualX - visualOffsetX;\n snapX = targetElementX;\n didSnapX = true;\n }\n }\n }\n }\n\n this.lastSnapTarget = didSnapX || didSnapY ? { x: snapX, y: snapY } : null;\n\n if (didSnapX || didSnapY) {\n }\n\n return { x: snapX, y: snapY };\n }\n\n /**\n * Detect spacing between a moving element and other elements\n * Returns spacing indicators for the closest elements in each direction\n * Also detects consistent spacing patterns (e.g., evenly spaced rows/columns)\n * @param showAllMeasurements - When true (Alt key held), shows all spacing measurements, not just to moving element\n */\n detectSpacing(\n movingElement: BaseElement,\n allElements: BaseElement[] | null,\n showAllMeasurements: boolean = false\n ): SpacingIndicator[] {\n if (!this.enabled || !allElements) {\n this.clearIndicators();\n return [];\n }\n\n // Get other elements (exclude the one being moved)\n const otherElements = allElements.filter((s) => s.id !== movingElement.id);\n\n if (otherElements.length === 0) {\n this.clearIndicators();\n return [];\n }\n\n // Get the bounding box of the moving element\n const movingBBox = movingElement.getVisualBoundingBox();\n\n // Track all nearby elements in each direction (not just closest)\n const leftElements: Array<{ element: BaseElement; distance: number }> = [];\n const rightElements: Array<{ element: BaseElement; distance: number }> = [];\n const topElements: Array<{ element: BaseElement; distance: number }> = [];\n const bottomElements: Array<{ element: BaseElement; distance: number }> = [];\n\n // Find the closest element in each direction\n for (const otherElement of otherElements) {\n const otherBBox = otherElement.getVisualBoundingBox();\n\n // Calculate distances between edges\n // Left: distance from moving element's left edge to other element's right edge\n const distanceToLeft = movingBBox.x - (otherBBox.x + otherBBox.width);\n // Right: distance from moving element's right edge to other element's left edge\n const distanceToRight = otherBBox.x - (movingBBox.x + movingBBox.width);\n // Top: distance from moving element's top edge to other element's bottom edge\n const distanceToTop = movingBBox.y - (otherBBox.y + otherBBox.height);\n // Bottom: distance from moving element's bottom edge to other element's top edge\n const distanceToBottom = otherBBox.y - (movingBBox.y + movingBBox.height);\n\n // Check if elements overlap vertically (for horizontal spacing)\n const verticalOverlap = !(\n movingBBox.y + movingBBox.height < otherBBox.y || movingBBox.y > otherBBox.y + otherBBox.height\n );\n\n // Check if elements overlap horizontally (for vertical spacing)\n const horizontalOverlap = !(\n movingBBox.x + movingBBox.width < otherBBox.x || movingBBox.x > otherBBox.x + otherBBox.width\n );\n\n // Left spacing (other element is to the left)\n // Collect all nearby elements, not just the closest\n if (distanceToLeft >= this.minDistance && distanceToLeft < this.detectionThreshold && verticalOverlap) {\n leftElements.push({ element: otherElement, distance: distanceToLeft });\n }\n\n // Right spacing (other element is to the right)\n if (distanceToRight >= this.minDistance && distanceToRight < this.detectionThreshold && verticalOverlap) {\n rightElements.push({ element: otherElement, distance: distanceToRight });\n }\n\n // Top spacing (other element is above)\n if (distanceToTop >= this.minDistance && distanceToTop < this.detectionThreshold && horizontalOverlap) {\n topElements.push({ element: otherElement, distance: distanceToTop });\n }\n\n // Bottom spacing (other element is below)\n if (distanceToBottom >= this.minDistance && distanceToBottom < this.detectionThreshold && horizontalOverlap) {\n bottomElements.push({ element: otherElement, distance: distanceToBottom });\n }\n }\n\n // Sort elements by distance in each direction\n leftElements.sort((a, b) => a.distance - b.distance);\n rightElements.sort((a, b) => a.distance - b.distance);\n topElements.sort((a, b) => a.distance - b.distance);\n bottomElements.sort((a, b) => a.distance - b.distance);\n\n const newIndicators: SpacingIndicator[] = [];\n\n // If Alt key is pressed, ONLY show all measurements (not regular spacing detection)\n if (showAllMeasurements) {\n const allMeasurements = this.detectAllMeasurements(movingElement, allElements);\n newIndicators.push(...allMeasurements);\n } else {\n // Regular spacing detection (only when Alt is NOT pressed)\n // Detect consistent spacing patterns and create indicators\n // Horizontal spacing (left and right)\n if (leftElements.length > 0) {\n const indicators = this.detectConsistentSpacing(movingElement, movingBBox, leftElements, 'left', otherElements);\n newIndicators.push(...indicators);\n }\n\n if (rightElements.length > 0) {\n const indicators = this.detectConsistentSpacing(\n movingElement,\n movingBBox,\n rightElements,\n 'right',\n otherElements\n );\n newIndicators.push(...indicators);\n }\n\n // Vertical spacing (top and bottom)\n if (topElements.length > 0) {\n const indicators = this.detectConsistentSpacing(movingElement, movingBBox, topElements, 'top', otherElements);\n newIndicators.push(...indicators);\n }\n\n if (bottomElements.length > 0) {\n const indicators = this.detectConsistentSpacing(\n movingElement,\n movingBBox,\n bottomElements,\n 'bottom',\n otherElements\n );\n newIndicators.push(...indicators);\n }\n }\n\n this.indicators = newIndicators;\n return newIndicators;\n }\n\n /**\n * Detect spacing from hovered element to ALL nearby elements (when Alt key is held)\n * Shows measurements from the hovered element to other elements within detection threshold\n */\n private detectAllMeasurements(hoveredElement: BaseElement, allElements: BaseElement[]): SpacingIndicator[] {\n const indicators: SpacingIndicator[] = [];\n const hoveredBBox = hoveredElement.getVisualBoundingBox();\n\n // Get other elements\n const otherElements = allElements.filter((s) => s.id !== hoveredElement.id);\n\n // Show measurements FROM the hovered element TO all nearby elements\n // For Alt+hover, use a much larger detection threshold (3x normal) to show more measurements\n const altHoverThreshold = this.detectionThreshold * 3; // 300px instead of 100px\n\n // Track horizontal and vertical measurement counts separately to alternate offsets\n let horizontalCount = 0;\n let verticalCount = 0;\n\n for (const element of otherElements) {\n const elementBBox = element.getVisualBoundingBox();\n\n // Calculate distance from hovered element to this element\n const distanceToElement = Math.min(\n Math.abs(hoveredBBox.x - (elementBBox.x + elementBBox.width)), // left\n Math.abs(elementBBox.x - (hoveredBBox.x + hoveredBBox.width)), // right\n Math.abs(hoveredBBox.y - (elementBBox.y + elementBBox.height)), // top\n Math.abs(elementBBox.y - (hoveredBBox.y + hoveredBBox.height)) // bottom\n );\n\n\n if (distanceToElement > altHoverThreshold) {\n continue; // Skip elements that are too far away\n }\n\n // Calculate horizontal gap\n let horizontalGap = 0;\n let showHorizontal = false;\n let hx1 = 0,\n hx2 = 0,\n hy = 0;\n\n // Offset to prevent overlapping with vertical measurements\n // Alternate up/down for each horizontal measurement\n const horizontalOffsetMultiplier = horizontalCount % 2 === 0 ? 1 : -1;\n const horizontalOffset = -30 * horizontalOffsetMultiplier; // Alternate: -30px or +30px\n\n // Element is to the right (gap between right edge of hovered and left edge of element)\n if (elementBBox.x >= hoveredBBox.x + hoveredBBox.width) {\n horizontalGap = elementBBox.x - (hoveredBBox.x + hoveredBBox.width);\n if (horizontalGap >= this.minDistance) {\n showHorizontal = true;\n hx1 = hoveredBBox.x + hoveredBBox.width;\n hx2 = elementBBox.x;\n // Use the vertical center between the two elements to spread them out\n hy = (hoveredBBox.y + hoveredBBox.height / 2 + elementBBox.y + elementBBox.height / 2) / 2 + horizontalOffset;\n }\n }\n // Element is to the left (gap between left edge of hovered and right edge of element)\n else if (hoveredBBox.x >= elementBBox.x + elementBBox.width) {\n horizontalGap = hoveredBBox.x - (elementBBox.x + elementBBox.width);\n if (horizontalGap >= this.minDistance) {\n showHorizontal = true;\n hx1 = elementBBox.x + elementBBox.width;\n hx2 = hoveredBBox.x;\n // Use the vertical center between the two elements to spread them out\n hy = (hoveredBBox.y + hoveredBBox.height / 2 + elementBBox.y + elementBBox.height / 2) / 2 + horizontalOffset;\n }\n } else {\n }\n\n if (showHorizontal) {\n indicators.push({\n type: 'horizontal',\n distance: Math.round(horizontalGap),\n x1: hx1,\n x2: hx2,\n y: hy,\n labelX: (hx1 + hx2) / 2,\n labelY: hy,\n isAltHover: true,\n });\n horizontalCount++; // Increment for next horizontal measurement\n }\n\n // Calculate vertical gap\n let verticalGap = 0;\n let showVertical = false;\n let vx = 0,\n vy1 = 0,\n vy2 = 0;\n\n // Offset to prevent overlapping with horizontal measurements\n // Alternate left/right for each vertical measurement\n const verticalOffsetMultiplier = verticalCount % 2 === 0 ? 1 : -1;\n const verticalOffset = 30 * verticalOffsetMultiplier; // Alternate: +30px or -30px\n\n // Element is below (gap between bottom edge of hovered and top edge of element)\n if (elementBBox.y >= hoveredBBox.y + hoveredBBox.height) {\n verticalGap = elementBBox.y - (hoveredBBox.y + hoveredBBox.height);\n if (verticalGap >= this.minDistance) {\n showVertical = true;\n // Use the horizontal center between the two elements to spread them out\n vx = (hoveredBBox.x + hoveredBBox.width / 2 + elementBBox.x + elementBBox.width / 2) / 2 + verticalOffset;\n vy1 = hoveredBBox.y + hoveredBBox.height;\n vy2 = elementBBox.y;\n }\n }\n // Element is above (gap between top edge of hovered and bottom edge of element)\n else if (hoveredBBox.y >= elementBBox.y + elementBBox.height) {\n verticalGap = hoveredBBox.y - (elementBBox.y + elementBBox.height);\n if (verticalGap >= this.minDistance) {\n showVertical = true;\n // Use the horizontal center between the two elements to spread them out\n vx = (hoveredBBox.x + hoveredBBox.width / 2 + elementBBox.x + elementBBox.width / 2) / 2 + verticalOffset;\n vy1 = elementBBox.y + elementBBox.height;\n vy2 = hoveredBBox.y;\n }\n }\n\n if (showVertical) {\n indicators.push({\n type: 'vertical',\n distance: Math.round(verticalGap),\n x: vx,\n y1: vy1,\n y2: vy2,\n labelX: vx,\n labelY: (vy1 + vy2) / 2,\n isAltHover: true,\n });\n verticalCount++; // Increment for next vertical measurement\n }\n }\n\n return indicators;\n }\n\n /**\n * Detect consistent spacing patterns (e.g., evenly spaced rows/columns)\n * and create indicators for all matching spacings\n */\n private detectConsistentSpacing(\n movingElement: BaseElement,\n movingBBox: { x: number; y: number; width: number; height: number },\n nearbyElements: Array<{ element: BaseElement; distance: number }>,\n direction: 'left' | 'right' | 'top' | 'bottom',\n allElements: BaseElement[]\n ): SpacingIndicator[] {\n const indicators: SpacingIndicator[] = [];\n const spacingTolerance = 16; // Consider spacings within 16px as \"matching\"\n\n // Get the closest element\n const closest = nearbyElements[0];\n const closestDistance = closest.distance;\n const closestBBox = closest.element.getVisualBoundingBox();\n\n // Check if this spacing would snap (for visual indicator)\n const wouldSnap = this.checkIfWouldSnap(movingBBox, closestDistance, direction, allElements);\n\n // ALWAYS add indicator for the moving element to the closest element first\n // Mark it as snap target if it would snap\n this.addIndicatorForDirection(indicators, movingBBox, closestBBox, closestDistance, direction, wouldSnap);\n\n if (direction === 'left' || direction === 'right') {\n // Horizontal spacing - look for elements in a row with consistent spacing\n // Find other elements that align vertically and have similar spacing between them\n\n // Get all elements in the same horizontal band\n const alignedElements = allElements.filter((s) => {\n if (s.id === movingElement.id) return false;\n const bbox = s.getVisualBoundingBox();\n const verticalOverlap = !(movingBBox.y + movingBBox.height < bbox.y || movingBBox.y > bbox.y + bbox.height);\n return verticalOverlap;\n });\n\n // Sort by x position\n alignedElements.sort((a, b) => {\n const bboxA = a.getVisualBoundingBox();\n const bboxB = b.getVisualBoundingBox();\n return bboxA.x - bboxB.x;\n });\n\n // Find patterns of consistent spacing\n if (direction === 'left') {\n // Look for elements to the left with consistent spacing\n for (let i = 0; i < alignedElements.length - 1; i++) {\n const element1 = alignedElements[i];\n const element2 = alignedElements[i + 1];\n const bbox1 = element1.getVisualBoundingBox();\n const bbox2 = element2.getVisualBoundingBox();\n\n const gap = bbox2.x - (bbox1.x + bbox1.width);\n\n // Check if this gap matches our target spacing\n if (Math.abs(gap - closestDistance) <= spacingTolerance && gap >= this.minDistance) {\n // Found a matching spacing - show indicator\n const y1 = Math.max(bbox1.y, bbox2.y);\n const y2 = Math.min(bbox1.y + bbox1.height, bbox2.y + bbox2.height);\n const y = (y1 + y2) / 2;\n\n indicators.push({\n type: 'horizontal',\n distance: Math.round(gap),\n x1: bbox1.x + bbox1.width,\n x2: bbox2.x,\n y,\n labelX: (bbox1.x + bbox1.width + bbox2.x) / 2,\n labelY: y,\n });\n }\n }\n } else {\n // direction === 'right'\n // Look for elements to the right with consistent spacing\n for (let i = 0; i < alignedElements.length - 1; i++) {\n const element1 = alignedElements[i];\n const element2 = alignedElements[i + 1];\n const bbox1 = element1.getVisualBoundingBox();\n const bbox2 = element2.getVisualBoundingBox();\n\n const gap = bbox2.x - (bbox1.x + bbox1.width);\n\n if (Math.abs(gap - closestDistance) <= spacingTolerance && gap >= this.minDistance) {\n const y1 = Math.max(bbox1.y, bbox2.y);\n const y2 = Math.min(bbox1.y + bbox1.height, bbox2.y + bbox2.height);\n const y = (y1 + y2) / 2;\n\n indicators.push({\n type: 'horizontal',\n distance: Math.round(gap),\n x1: bbox1.x + bbox1.width,\n x2: bbox2.x,\n y,\n labelX: (bbox1.x + bbox1.width + bbox2.x) / 2,\n labelY: y,\n });\n }\n }\n }\n } else {\n // Vertical spacing - look for elements in a column with consistent spacing\n\n // Get all elements in the same vertical band\n const alignedElements = allElements.filter((s) => {\n if (s.id === movingElement.id) return false;\n const bbox = s.getVisualBoundingBox();\n const horizontalOverlap = !(movingBBox.x + movingBBox.width < bbox.x || movingBBox.x > bbox.x + bbox.width);\n return horizontalOverlap;\n });\n\n // Sort by y position\n alignedElements.sort((a, b) => {\n const bboxA = a.getVisualBoundingBox();\n const bboxB = b.getVisualBoundingBox();\n return bboxA.y - bboxB.y;\n });\n\n // Find patterns of consistent spacing\n if (direction === 'top') {\n // Look for elements above with consistent spacing\n for (let i = 0; i < alignedElements.length - 1; i++) {\n const element1 = alignedElements[i];\n const element2 = alignedElements[i + 1];\n const bbox1 = element1.getVisualBoundingBox();\n const bbox2 = element2.getVisualBoundingBox();\n\n const gap = bbox2.y - (bbox1.y + bbox1.height);\n\n if (Math.abs(gap - closestDistance) <= spacingTolerance && gap >= this.minDistance) {\n const x1 = Math.max(bbox1.x, bbox2.x);\n const x2 = Math.min(bbox1.x + bbox1.width, bbox2.x + bbox2.width);\n const x = (x1 + x2) / 2;\n\n indicators.push({\n type: 'vertical',\n distance: Math.round(gap),\n x,\n y1: bbox1.y + bbox1.height,\n y2: bbox2.y,\n labelX: x,\n labelY: (bbox1.y + bbox1.height + bbox2.y) / 2,\n });\n }\n }\n } else {\n // direction === 'bottom'\n // Look for elements below with consistent spacing\n for (let i = 0; i < alignedElements.length - 1; i++) {\n const element1 = alignedElements[i];\n const element2 = alignedElements[i + 1];\n const bbox1 = element1.getVisualBoundingBox();\n const bbox2 = element2.getVisualBoundingBox();\n\n const gap = bbox2.y - (bbox1.y + bbox1.height);\n\n if (Math.abs(gap - closestDistance) <= spacingTolerance && gap >= this.minDistance) {\n const x1 = Math.max(bbox1.x, bbox2.x);\n const x2 = Math.min(bbox1.x + bbox1.width, bbox2.x + bbox2.width);\n const x = (x1 + x2) / 2;\n\n indicators.push({\n type: 'vertical',\n distance: Math.round(gap),\n x,\n y1: bbox1.y + bbox1.height,\n y2: bbox2.y,\n labelX: x,\n labelY: (bbox1.y + bbox1.height + bbox2.y) / 2,\n });\n }\n }\n }\n }\n\n return indicators;\n }\n\n /**\n * Check if spacing would snap to a pattern\n */\n private checkIfWouldSnap(\n movingBBox: { x: number; y: number; width: number; height: number },\n currentDistance: number,\n direction: 'left' | 'right' | 'top' | 'bottom',\n allElements: BaseElement[]\n ): boolean {\n const snapThreshold = SPACING_SNAP_THRESHOLD;\n\n\n if (direction === 'top' || direction === 'bottom') {\n // Check vertical spacing pattern\n const horizontallyAligned = allElements.filter((s) => {\n const bbox = s.getVisualBoundingBox();\n const horizontalOverlap = !(movingBBox.x + movingBBox.width < bbox.x || movingBBox.x > bbox.x + bbox.width);\n return horizontalOverlap;\n });\n\n\n // Need at least 1 other element to establish a pattern\n if (horizontallyAligned.length >= 1) {\n // Sort by y position\n horizontallyAligned.sort((a, b) => {\n const bboxA = a.getVisualBoundingBox();\n const bboxB = b.getVisualBoundingBox();\n return bboxA.y - bboxB.y;\n });\n\n const gaps: number[] = [];\n for (let i = 0; i < horizontallyAligned.length - 1; i++) {\n const bbox1 = horizontallyAligned[i].getVisualBoundingBox();\n const bbox2 = horizontallyAligned[i + 1].getVisualBoundingBox();\n const gap = bbox2.y - (bbox1.y + bbox1.height);\n gaps.push(gap);\n }\n\n // If we have at least 1 gap, we can establish a pattern\n if (gaps.length > 0) {\n const avgGap = gaps.reduce((a, b) => a + b, 0) / gaps.length;\n const gapDiff = Math.abs(currentDistance - avgGap);\n const wouldSnap = gapDiff <= snapThreshold;\n return wouldSnap;\n }\n }\n return false;\n } else {\n // Check horizontal spacing pattern\n const verticallyAligned = allElements.filter((s) => {\n const bbox = s.getVisualBoundingBox();\n const verticalOverlap = !(movingBBox.y + movingBBox.height < bbox.y || movingBBox.y > bbox.y + bbox.height);\n return verticalOverlap;\n });\n\n // Need at least 1 other element to establish a pattern\n if (verticallyAligned.length >= 1) {\n // Sort by x position\n verticallyAligned.sort((a, b) => {\n const bboxA = a.getVisualBoundingBox();\n const bboxB = b.getVisualBoundingBox();\n return bboxA.x - bboxB.x;\n });\n\n const gaps: number[] = [];\n for (let i = 0; i < verticallyAligned.length - 1; i++) {\n const bbox1 = verticallyAligned[i].getVisualBoundingBox();\n const bbox2 = verticallyAligned[i + 1].getVisualBoundingBox();\n const gap = bbox2.x - (bbox1.x + bbox1.width);\n gaps.push(gap);\n }\n\n // If we have at least 1 gap, we can establish a pattern\n if (gaps.length > 0) {\n const avgGap = gaps.reduce((a, b) => a + b, 0) / gaps.length;\n const gapDiff = Math.abs(currentDistance - avgGap);\n const wouldSnap = gapDiff <= snapThreshold;\n return wouldSnap;\n }\n }\n return false;\n }\n\n return false;\n }\n\n /**\n * Helper method to add a spacing indicator in a specific direction\n */\n private addIndicatorForDirection(\n indicators: SpacingIndicator[],\n movingBBox: { x: number; y: number; width: number; height: number },\n otherBBox: { x: number; y: number; width: number; height: number },\n distance: number,\n direction: 'left' | 'right' | 'top' | 'bottom',\n isSnapTarget: boolean = false\n ): void {\n if (direction === 'left') {\n const y1 = Math.max(movingBBox.y, otherBBox.y);\n const y2 = Math.min(movingBBox.y + movingBBox.height, otherBBox.y + otherBBox.height);\n const y = (y1 + y2) / 2;\n\n indicators.push({\n type: 'horizontal',\n distance: Math.round(distance),\n x1: otherBBox.x + otherBBox.width,\n x2: movingBBox.x,\n y,\n labelX: (otherBBox.x + otherBBox.width + movingBBox.x) / 2,\n labelY: y,\n isSnapTarget,\n });\n } else if (direction === 'right') {\n const y1 = Math.max(movingBBox.y, otherBBox.y);\n const y2 = Math.min(movingBBox.y + movingBBox.height, otherBBox.y + otherBBox.height);\n const y = (y1 + y2) / 2;\n\n indicators.push({\n type: 'horizontal',\n distance: Math.round(distance),\n x1: movingBBox.x + movingBBox.width,\n x2: otherBBox.x,\n y,\n labelX: (movingBBox.x + movingBBox.width + otherBBox.x) / 2,\n labelY: y,\n isSnapTarget,\n });\n } else if (direction === 'top') {\n const x1 = Math.max(movingBBox.x, otherBBox.x);\n const x2 = Math.min(movingBBox.x + movingBBox.width, otherBBox.x + otherBBox.width);\n const x = (x1 + x2) / 2;\n\n indicators.push({\n type: 'vertical',\n distance: Math.round(distance),\n x,\n y1: otherBBox.y + otherBBox.height,\n y2: movingBBox.y,\n labelX: x,\n labelY: (otherBBox.y + otherBBox.height + movingBBox.y) / 2,\n isSnapTarget,\n });\n } else if (direction === 'bottom') {\n const x1 = Math.max(movingBBox.x, otherBBox.x);\n const x2 = Math.min(movingBBox.x + movingBBox.width, otherBBox.x + otherBBox.width);\n const x = (x1 + x2) / 2;\n\n indicators.push({\n type: 'vertical',\n distance: Math.round(distance),\n x,\n y1: movingBBox.y + movingBBox.height,\n y2: otherBBox.y,\n labelX: x,\n labelY: (movingBBox.y + movingBBox.height + otherBBox.y) / 2,\n isSnapTarget,\n });\n }\n }\n}\n","/**\n * ArtboardRenderer - Renders artboards on the canvas\n * Handles artboard background, border, label, and selection state\n */\n\nimport { ArtboardElement } from './ArtboardElement.js';\nimport { TextureManager } from '../utils/TextureManager.js';\nimport { createLogger } from '../utils/logger.js';\nimport {\n getThemeAccentColor,\n getThemeArtboardBorderColor,\n getThemeArtboardLabelColor,\n} from '../constants.js';\n\nconst logger = createLogger('ArtboardRenderer');\n\nexport interface ArtboardRenderOptions {\n showLabel?: boolean;\n showBorder?: boolean;\n borderColor?: string;\n borderWidth?: number;\n labelFontSize?: number;\n labelColor?: string;\n labelPadding?: number;\n selectedBorderColor?: string;\n selectedBorderWidth?: number;\n showPreviewBackground?: boolean; // Enable preview background rendering (never exported)\n borderRadius?: number; // Override border radius for artboard corners (in pixels)\n}\n\nconst DEFAULT_OPTIONS: Required<ArtboardRenderOptions> = {\n showLabel: true,\n showBorder: true,\n borderColor: '', // '' = use getThemeArtboardBorderColor() at render time\n borderWidth: 1,\n labelFontSize: 14,\n labelColor: '', // '' = use getThemeArtboardLabelColor() at render time\n labelPadding: 8,\n selectedBorderColor: '', // '' = use getThemeAccentColor() at render time\n selectedBorderWidth: 2,\n showPreviewBackground: true, // Show preview by default in editor\n borderRadius: 0, // Default to no border radius (sharp corners)\n};\n\nexport class ArtboardRenderer {\n private options: Required<ArtboardRenderOptions>;\n\n constructor(options: ArtboardRenderOptions = {}) {\n this.options = { ...DEFAULT_OPTIONS, ...options };\n }\n\n /**\n * Render an artboard\n */\n render(ctx: CanvasRenderingContext2D, artboard: ArtboardElement, isSelected: boolean = false): void {\n\n ctx.save();\n\n // Render background\n this.renderBackground(ctx, artboard);\n\n // Render border\n if (this.options.showBorder) {\n this.renderBorder(ctx, artboard, isSelected);\n }\n\n // Render label\n if (this.options.showLabel) {\n this.renderLabel(ctx, artboard, isSelected);\n }\n\n ctx.restore();\n }\n\n /**\n * Render artboard background\n * Optionally renders preview background color for t-shirt design preview\n * Preview background is NEVER exported - only shown in editor\n * Background is clipped to the artboard's clipShape\n */\n private renderBackground(ctx: CanvasRenderingContext2D, artboard: ArtboardElement): void {\n // Save context before clipping\n ctx.save();\n\n // Apply clip path for background rendering (respects clipShape)\n this.applyClipPath(ctx, artboard);\n\n // Render preview background if enabled and set (for t-shirt color preview)\n if (\n this.options.showPreviewBackground &&\n artboard.previewBackgroundColor &&\n artboard.previewBackgroundColor !== 'transparent'\n ) {\n ctx.fillStyle = artboard.previewBackgroundColor;\n ctx.fillRect(artboard.x, artboard.y, artboard.width, artboard.height);\n }\n\n // Render actual artboard background on top\n if (artboard.backgroundType === 'texture' && artboard.backgroundTexture) {\n // Render texture background\n this.renderTextureBackground(ctx, artboard);\n } else if (artboard.backgroundType === 'transparent' || artboard.backgroundColor === 'transparent') {\n // Draw checkerboard pattern for transparent background\n this.renderCheckerboard(ctx, artboard.x, artboard.y, artboard.width, artboard.height);\n } else {\n // Render solid color background\n ctx.fillStyle = artboard.backgroundColor;\n ctx.fillRect(artboard.x, artboard.y, artboard.width, artboard.height);\n }\n\n // Restore context after background\n ctx.restore();\n }\n\n /**\n * Render texture background\n * Renders preset color as base layer, then texture on top\n */\n private renderTextureBackground(\n ctx: CanvasRenderingContext2D,\n artboard: ArtboardElement\n ): void {\n const texture = TextureManager.getTexture(artboard.backgroundTexture!);\n\n // First, render the preset color as the base layer\n const presetColor = TextureManager.getTexturePresetColor(artboard.backgroundTexture!);\n ctx.fillStyle = presetColor;\n ctx.fillRect(artboard.x, artboard.y, artboard.width, artboard.height);\n\n if (!texture || !texture.complete) {\n // Texture not loaded yet - load it asynchronously\n TextureManager.loadTexture(artboard.backgroundTexture!).catch((err) => {\n logger.error('Failed to load texture:', err);\n });\n\n // Preset color already rendered above, just return\n return;\n }\n\n\n // Save context\n ctx.save();\n\n // Create clipping path\n ctx.beginPath();\n ctx.rect(artboard.x, artboard.y, artboard.width, artboard.height);\n ctx.clip();\n\n // Draw texture on top of the preset color\n ctx.drawImage(texture, artboard.x, artboard.y, artboard.width, artboard.height);\n\n // Restore context\n ctx.restore();\n }\n\n /**\n * Render a checkerboard pattern for transparent backgrounds\n */\n private renderCheckerboard(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n width: number,\n height: number\n ): void {\n const squareSize = 16;\n const lightColor = '#ffffff';\n const darkColor = '#d0d0d0';\n\n // Save context\n ctx.save();\n\n // Create clipping path\n ctx.beginPath();\n ctx.rect(x, y, width, height);\n ctx.clip();\n\n // Fill entire area with light color first\n ctx.fillStyle = lightColor;\n ctx.fillRect(x, y, width, height);\n\n // Draw only dark squares on top (every other square)\n ctx.fillStyle = darkColor;\n for (let row = 0; row < Math.ceil(height / squareSize); row++) {\n for (let col = 0; col < Math.ceil(width / squareSize); col++) {\n // Only draw dark squares (when row + col is odd)\n if ((row + col) % 2 === 1) {\n ctx.fillRect(x + col * squareSize, y + row * squareSize, squareSize, squareSize);\n }\n }\n }\n\n // Restore context\n ctx.restore();\n }\n\n /**\n * Render artboard border (respects clipShape and borderRadius option)\n */\n private renderBorder(ctx: CanvasRenderingContext2D, artboard: ArtboardElement, isSelected: boolean): void {\n const { x, y, width, height, clipShape } = artboard;\n\n const borderColor = this.options.borderColor || getThemeArtboardBorderColor();\n const selectedBorderColor = this.options.selectedBorderColor || getThemeAccentColor();\n ctx.strokeStyle = isSelected ? selectedBorderColor : borderColor;\n ctx.lineWidth = isSelected ? this.options.selectedBorderWidth : this.options.borderWidth;\n\n ctx.beginPath();\n\n // Draw border following the clip shape or borderRadius option\n if (!clipShape || clipShape === 'rectangle') {\n if (this.options.borderRadius > 0) {\n const radius = Math.min(this.options.borderRadius, Math.min(width, height) / 2);\n this.roundedRect(ctx, x, y, width, height, radius);\n } else {\n ctx.rect(x, y, width, height);\n }\n } else if (clipShape === 'circle') {\n const centerX = x + width / 2;\n const centerY = y + height / 2;\n const radius = Math.min(width, height) / 2;\n ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);\n } else if (typeof clipShape === 'object' && clipShape.type === 'rounded') {\n const radius = Math.min(clipShape.radius, Math.min(width, height) / 2);\n this.roundedRect(ctx, x, y, width, height, radius);\n } else if (typeof clipShape === 'object' && clipShape.type === 'path') {\n // For custom paths, stroke the path\n try {\n const path = new Path2D(clipShape.d);\n ctx.save();\n ctx.translate(x, y);\n ctx.stroke(path);\n ctx.restore();\n return; // Path2D stroke handles it\n } catch (e) {\n logger.warn('Invalid SVG path for border, falling back to rectangle:', e);\n ctx.rect(x, y, width, height);\n }\n }\n\n ctx.stroke();\n }\n\n /**\n * Render artboard label (name + dimensions)\n */\n private renderLabel(ctx: CanvasRenderingContext2D, artboard: ArtboardElement, isSelected: boolean): void {\n const fontSize = this.options.labelFontSize;\n const padding = this.options.labelPadding;\n\n // Label text\n const labelText = `${artboard.name} (${Math.round(artboard.width)} × ${Math.round(artboard.height)})`;\n\n // Label position (above artboard)\n const labelX = artboard.x;\n const labelY = artboard.y - padding;\n\n // Render label text\n ctx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif`;\n const selectedBorderColor = this.options.selectedBorderColor || getThemeAccentColor();\n const labelColor = this.options.labelColor || getThemeArtboardLabelColor();\n ctx.fillStyle = isSelected ? selectedBorderColor : labelColor;\n ctx.textAlign = 'left';\n ctx.textBaseline = 'bottom';\n\n ctx.fillText(labelText, labelX, labelY);\n }\n\n /**\n * Render multiple artboards\n */\n renderMultiple(ctx: CanvasRenderingContext2D, artboards: ArtboardElement[], selectedId: string | null): void {\n // Render artboards in order (bottom to top)\n artboards.forEach((artboard) => {\n const isSelected = artboard.id === selectedId;\n this.render(ctx, artboard, isSelected);\n });\n }\n\n /**\n * Clip rendering to artboard bounds (respects clipShape)\n * Use this to clip element rendering to stay within artboard\n */\n clipToArtboard(ctx: CanvasRenderingContext2D, artboard: ArtboardElement): void {\n ctx.save();\n this.applyClipPath(ctx, artboard);\n }\n\n /**\n * Apply clip path based on artboard's clipShape\n * This creates the clip region for the given artboard\n * If borderRadius option is set and no special clipShape, uses rounded rectangle\n */\n applyClipPath(ctx: CanvasRenderingContext2D, artboard: ArtboardElement): void {\n const { x, y, width, height, clipShape } = artboard;\n\n // Debug: log borderRadius value\n if (this.options.borderRadius > 0) {\n }\n\n ctx.beginPath();\n\n // No clip shape or 'rectangle' = use borderRadius from options if set, otherwise standard rect\n if (!clipShape || clipShape === 'rectangle') {\n if (this.options.borderRadius > 0) {\n const radius = Math.min(this.options.borderRadius, Math.min(width, height) / 2);\n this.roundedRect(ctx, x, y, width, height, radius);\n } else {\n ctx.rect(x, y, width, height);\n }\n } else if (clipShape === 'circle') {\n // Circle inscribed in artboard\n const centerX = x + width / 2;\n const centerY = y + height / 2;\n const radius = Math.min(width, height) / 2;\n ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);\n } else if (typeof clipShape === 'object' && clipShape.type === 'rounded') {\n // Rounded rectangle\n const radius = Math.min(clipShape.radius, Math.min(width, height) / 2);\n this.roundedRect(ctx, x, y, width, height, radius);\n } else if (typeof clipShape === 'object' && clipShape.type === 'path') {\n // Custom SVG path - use Path2D for parsing\n try {\n const path = new Path2D(clipShape.d);\n // Translate the path to artboard position\n ctx.translate(x, y);\n ctx.clip(path);\n ctx.translate(-x, -y);\n return; // Path2D handles the clip itself\n } catch (e) {\n logger.warn('Invalid SVG path, falling back to rectangle:', e);\n ctx.rect(x, y, width, height);\n }\n }\n\n ctx.clip();\n }\n\n /**\n * Draw a rounded rectangle path\n * Helper method for rounded clip shapes\n */\n private roundedRect(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n width: number,\n height: number,\n radius: number\n ): void {\n // Use built-in roundRect if available\n if (typeof ctx.roundRect === 'function') {\n ctx.roundRect(x, y, width, height, radius);\n } else {\n // Fallback for older browsers\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 }\n }\n\n /**\n * Restore after clipping\n */\n restoreClip(ctx: CanvasRenderingContext2D): void {\n ctx.restore();\n }\n\n /**\n * Render artboard outline (for dragging/resizing preview)\n */\n renderOutline(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n width: number,\n height: number,\n isDragging: boolean = false\n ): void {\n ctx.save();\n\n // Dashed outline\n ctx.setLineDash([5, 5]);\n ctx.strokeStyle = isDragging ? '#0066cc' : '#999';\n ctx.lineWidth = 1;\n\n ctx.strokeRect(x, y, width, height);\n\n ctx.restore();\n }\n\n /**\n * Render resize handles for artboard\n */\n renderResizeHandles(ctx: CanvasRenderingContext2D, artboard: ArtboardElement): void {\n const handleSize = 8;\n const { x, y, width, height } = artboard;\n\n // Define handle positions (corners only for artboards)\n const handles = [\n { x: x, y: y }, // top-left\n { x: x + width, y: y }, // top-right\n { x: x, y: y + height }, // bottom-left\n { x: x + width, y: y + height }, // bottom-right\n ];\n\n ctx.save();\n\n handles.forEach((handle) => {\n // White fill with blue border\n ctx.fillStyle = '#fff';\n ctx.strokeStyle = '#0066cc';\n ctx.lineWidth = 2;\n\n ctx.fillRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);\n ctx.strokeRect(handle.x - handleSize / 2, handle.y - handleSize / 2, handleSize, handleSize);\n });\n\n ctx.restore();\n }\n\n /**\n * Update options\n */\n setOptions(options: Partial<ArtboardRenderOptions>): void {\n this.options = { ...this.options, ...options };\n }\n\n /**\n * Get current options\n */\n getOptions(): ArtboardRenderOptions {\n return { ...this.options };\n }\n\n /**\n * Create export-safe renderer\n * Returns a new renderer instance with preview background disabled\n * Use this when exporting to ensure preview background is never included\n */\n static createForExport(options: ArtboardRenderOptions = {}): ArtboardRenderer {\n return new ArtboardRenderer({\n ...options,\n showPreviewBackground: false, // CRITICAL: Never show preview in exports\n });\n }\n\n /**\n * Check if preview background would be rendered for given artboard\n */\n willRenderPreviewBackground(artboard: ArtboardElement): boolean {\n return !!(\n this.options.showPreviewBackground &&\n artboard.previewBackgroundColor &&\n artboard.previewBackgroundColor !== 'transparent'\n );\n }\n}\n\nexport default ArtboardRenderer;\n","/**\n * SelectionRenderer - Selection box, transform handles, and resize info rendering\n *\n * Renders the visual indicators for element selection state:\n * - Selection bounding box around the selected element\n * - Position/size tooltip shown during resize operations\n * - Transform handles (corner, edge, rotation) via TransformHandles delegation\n *\n * Extracted from CanvasRenderer.ts as part of the rendering decomposition.\n * All methods are static to match the original CanvasRenderer pattern.\n */\n\nimport { theme } from '../theme.js';\nimport { RotationUtils } from './RotationUtils.js';\nimport type { BaseElement } from './BaseElement.js';\nimport type { TransformHandles } from './TransformHandles.js';\nimport type { InteractionStateMachine } from './InteractionStateMachine.js';\nimport type { HandleInfo } from '../types/index.js';\n\nimport {\n getThemeAccentColor,\n getThemeTooltipBackground,\n getThemeTooltipForeground,\n getThemeTooltipShadowColor,\n} from '../constants.js';\n\ninterface HoverState {\n type: 'element' | 'resize-handle' | 'rotation-handle' | null;\n data?: BaseElement | HandleInfo | null;\n}\n\nexport class SelectionRenderer {\n /**\n * Render selection box layer\n */\n static renderSelectionLayer(\n ctx: CanvasRenderingContext2D,\n selectedElement: BaseElement | null,\n stateMachine: InteractionStateMachine,\n zoom: number = 1.0\n ): void {\n if (!selectedElement) return;\n\n // Skip rendering selection for hidden elements\n if (selectedElement.visible === false) return;\n\n // Hide selection border while dragging\n const isDragging = stateMachine && stateMachine.getMode() === 'drag';\n if (isDragging) return;\n\n // Skip rendering selection for GroupElement - it renders its own outline\n if ('isGroup' in selectedElement && selectedElement.isGroup) return;\n\n const visualBbox = selectedElement.getVisualBoundingBox();\n const rotationAnchor = selectedElement.getRotationAnchor();\n\n ctx.save();\n\n // Draw bounding box\n ctx.strokeStyle = getThemeAccentColor(ctx.canvas);\n ctx.lineWidth = 2;\n\n // Transform to match element rotation around its rotation anchor (scaled by zoom)\n ctx.translate(rotationAnchor.x * zoom, rotationAnchor.y * zoom);\n ctx.rotate(RotationUtils.toRadians(selectedElement.rotation));\n ctx.translate(-rotationAnchor.x * zoom, -rotationAnchor.y * zoom);\n\n // Apply visual padding to the selection box (doesn't affect snapping/alignment)\n const padding = theme.canvas.selectionPadding;\n ctx.strokeRect(\n (visualBbox.x - padding) * zoom,\n (visualBbox.y - padding) * zoom,\n (visualBbox.width + padding * 2) * zoom,\n (visualBbox.height + padding * 2) * zoom\n );\n\n ctx.restore();\n }\n\n /**\n * Render position and size info below the selected element\n * Only shows dimensions (width x height) while resizing\n */\n static renderPositionSizeInfo(\n ctx: CanvasRenderingContext2D,\n selectedElement: BaseElement | null,\n transformHandles: TransformHandles | null,\n stateMachine: InteractionStateMachine | null,\n zoom: number = 1.0\n ): void {\n if (!selectedElement || !transformHandles?.rotationHandle) return;\n\n // Only show while resizing\n if (stateMachine?.getMode() !== 'resize') return;\n\n const visualBbox = selectedElement.getVisualBoundingBox();\n\n // Get dimensions only\n const width = Math.round(visualBbox.width);\n const height = Math.round(visualBbox.height);\n\n // Format the info text (dimensions only)\n const infoText = `${width} \\u00d7 ${height}`;\n\n // Calculate position: centered horizontally, just above the rotation handle (scaled by zoom)\n const rotationHandle = transformHandles.rotationHandle;\n const tooltipX = rotationHandle.x * zoom;\n const tooltipY = rotationHandle.y * zoom - 20; // Position above rotation handle\n\n ctx.save();\n\n // Set smaller font\n ctx.font = '10px system-ui, -apple-system, sans-serif';\n const textMetrics = ctx.measureText(infoText);\n const tooltipWidth = textMetrics.width + 12; // Smaller padding\n const tooltipHeight = 18; // Smaller height\n\n // Shadow for depth\n ctx.shadowColor = getThemeTooltipShadowColor(ctx.canvas as unknown as HTMLElement);\n ctx.shadowBlur = 4;\n ctx.shadowOffsetY = 1;\n\n // Background\n ctx.fillStyle = getThemeTooltipBackground(ctx.canvas as unknown as HTMLElement);\n ctx.beginPath();\n ctx.roundRect(\n tooltipX - tooltipWidth / 2,\n tooltipY - tooltipHeight / 2,\n tooltipWidth,\n tooltipHeight,\n 3 // Smaller border radius\n );\n ctx.fill();\n\n // Reset shadow\n ctx.shadowColor = 'transparent';\n ctx.shadowBlur = 0;\n ctx.shadowOffsetY = 0;\n\n // Text - use alphabetic baseline for better vertical centering\n ctx.fillStyle = getThemeTooltipForeground(ctx.canvas as unknown as HTMLElement);\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(infoText, tooltipX, tooltipY + 0.5); // Slight offset for optical centering\n\n ctx.restore();\n }\n\n /**\n * Render transform handles layer\n */\n static renderHandlesLayer(\n ctx: CanvasRenderingContext2D,\n selectedElement: BaseElement | null,\n transformHandles: TransformHandles,\n currentRotation: number,\n isRotating: boolean,\n hoverState: HoverState,\n stateMachine: InteractionStateMachine,\n zoom: number = 1.0,\n showRotationHandle: boolean = true\n ): void {\n if (!selectedElement) return;\n\n // Skip rendering handles for hidden elements\n if (selectedElement.visible === false) return;\n\n // Hide handles while dragging\n const isDragging = stateMachine.getMode() === 'drag';\n if (isDragging) return;\n\n const isHoveringRotation = hoverState.type === 'rotation-handle';\n const hoveredResizeHandle = hoverState.type === 'resize-handle' ? (hoverState.data as HandleInfo) : null;\n const isResizing = stateMachine.getMode() === 'resize';\n const activeResizeHandle = isResizing ? (stateMachine.getActiveHandle() as HandleInfo) : null;\n\n transformHandles.render(\n ctx,\n showRotationHandle,\n currentRotation,\n isRotating,\n isHoveringRotation,\n hoveredResizeHandle,\n isResizing,\n activeResizeHandle,\n zoom,\n true // Render rotation handle on canvas (not React)\n );\n }\n}\n","/**\n * HoverRenderer - Hover outline and ring effect rendering\n *\n * Renders visual feedback when hovering over elements:\n * - Element-type-specific outlines (shape, image, group, text)\n * - Hover ring effect for straight text (offscreen canvas compositing)\n * - Hover ring effect for transformed text (circle, wave, arch, etc.)\n * - Character position calculations for each transform type\n *\n * ============================================================================\n * IMPORTANT: This is the HOVER RING EFFECT system\n * ============================================================================\n * - Colored ring/outline that appears when hovering over elements\n * - Uses offscreen canvas compositing with gap effect for text\n * - This is NOT the same as element stroke (StrokeRenderer.ts)\n * - This is NOT the same as selection box (Transform classes)\n *\n * Extracted from CanvasRenderer.ts as part of the rendering decomposition.\n * All methods are static to match the original CanvasRenderer pattern.\n */\n\nimport { RotationUtils } from './RotationUtils.js';\nimport type { BaseElement } from './BaseElement.js';\nimport type { TextElement } from './TextElement.js';\nimport type { CharacterStyle, CircleTransformData, WaveTransformData, FlagTransformData, ArchTransformData, AscendTransformData, LeanTransformData } from '../types/index.js';\n\nimport {\n getThemeAccentHoverColor,\n HORIZONTAL_PADDING,\n WAVE_SKEW_FACTOR,\n} from '../constants.js';\nimport { getFontMetrics, buildFontString, calculateVisualBoundsWithSpaceCollapsing } from './TextMetrics.js';\nimport { splitRichTextIntoLines, wrapRichTextSpans, applySpaceLayoutRules } from '../rendering/canvas-renderer.js';\n\ninterface HoverState {\n type: 'element' | 'resize-handle' | 'rotation-handle' | null;\n data?: BaseElement | null;\n startTime?: number;\n}\n\nexport class HoverRenderer {\n /**\n * Render hover highlights layer with fade-in animation\n * Outlines the actual element shape instead of a rectangular border\n */\n static renderHoverLayer(\n ctx: CanvasRenderingContext2D,\n hoverState: HoverState,\n selectedId: string | null,\n _isAltKeyPressed: boolean = false,\n zoom: number = 1.0,\n artboardBackgroundColor?: string\n ): void {\n if (hoverState.type !== 'element' || !hoverState.data) return;\n if (hoverState.data.id === selectedId) return; // Don't hover selected element\n\n const element = hoverState.data;\n\n ctx.save();\n\n // Calculate fade-in animation (0 to 0.5 opacity over 150ms)\n const FADE_DURATION = 150; // milliseconds\n const TARGET_OPACITY = 0.5;\n let opacity = TARGET_OPACITY;\n\n if (hoverState.startTime) {\n const elapsed = performance.now() - hoverState.startTime;\n const progress = Math.min(elapsed / FADE_DURATION, 1.0);\n opacity = progress * TARGET_OPACITY;\n }\n\n // Use theme hover accent color with animated opacity\n ctx.strokeStyle = getThemeAccentHoverColor(ctx.canvas);\n ctx.globalAlpha = opacity;\n ctx.lineWidth = 3; // Slightly thicker for visibility\n\n // Render element-specific outline based on type\n this.renderElementOutline(ctx, element, zoom, artboardBackgroundColor);\n\n ctx.restore();\n }\n\n /**\n * Render outline for a specific element type\n */\n private static renderElementOutline(\n ctx: CanvasRenderingContext2D,\n element: BaseElement,\n zoom: number = 1.0,\n artboardBackgroundColor?: string\n ): void {\n const rotationAnchor = element.getRotationAnchor();\n\n // Apply element rotation\n ctx.translate(rotationAnchor.x * zoom, rotationAnchor.y * zoom);\n ctx.rotate(RotationUtils.toRadians(element.rotation));\n ctx.translate(-rotationAnchor.x * zoom, -rotationAnchor.y * zoom);\n\n // Handle different element types\n if (element.transformType === 'shape') {\n this.renderShapeOutline(ctx, element, zoom);\n } else if (element.transformType === 'image') {\n this.renderImageOutline(ctx, element, zoom);\n } else if (element.transformType === 'group') {\n this.renderGroupOutline(ctx, element, zoom);\n } else {\n // Text elements (custom, circle, arch, wave, etc.)\n this.renderTextOutline(ctx, element as TextElement, zoom, artboardBackgroundColor);\n }\n }\n\n /**\n * Render outline for shape elements (star, rectangle, circle, etc.)\n * Creates a ring effect with a gap between the element and the stroke\n */\n private static renderShapeOutline(ctx: CanvasRenderingContext2D, element: BaseElement, zoom: number): void {\n const transformData = element.transformData as Record<string, number | string | undefined>;\n const width = (transformData.width as number) * zoom;\n const height = (transformData.height as number) * zoom;\n\n // Ring effect: gap = 2px, stroke = 2px\n // Offset to center of stroke = gap + stroke_width/2 = 2 + 1 = 3px\n const strokeWidth = 2;\n const gap = strokeWidth; // Gap equals stroke width\n const offset = gap + strokeWidth / 2;\n\n ctx.save();\n ctx.translate(element.x * zoom, element.y * zoom);\n\n ctx.beginPath();\n\n switch (transformData.shapeType as string) {\n case 'rectangle': {\n const borderRadius = (transformData.borderRadius as number) || 0;\n const x = -(width + offset * 2) / 2;\n const y = -(height + offset * 2) / 2;\n const w = width + offset * 2;\n const h = height + offset * 2;\n if (borderRadius > 0) {\n const radius = Math.min((borderRadius / 100) * Math.min(w, h), w / 2, h / 2);\n ctx.roundRect(x, y, w, h, radius);\n } else {\n ctx.rect(x, y, w, h);\n }\n break;\n }\n\n case 'circle': {\n const radius = Math.min(width, height) / 2 + offset;\n ctx.arc(0, 0, radius, 0, Math.PI * 2);\n break;\n }\n\n case 'ellipse': {\n const radiusX = ((transformData.radiusX as number) ?? width / 2) + offset;\n const radiusY = ((transformData.radiusY as number) ?? height / 2) + offset;\n ctx.ellipse(0, 0, radiusX, radiusY, 0, 0, Math.PI * 2);\n break;\n }\n\n case 'triangle': {\n const halfWidth = width / 2;\n const halfHeight = height / 2;\n\n // Original vertices\n const triVerts = [\n { x: 0, y: -halfHeight }, // top\n { x: halfWidth, y: halfHeight }, // bottom-right\n { x: -halfWidth, y: halfHeight }, // bottom-left\n ];\n\n // Compute outward unit normals for each edge (counterclockwise winding, y-down)\n const triNormals = triVerts.map((v, i) => {\n const next = triVerts[(i + 1) % 3];\n const dx = next.x - v.x;\n const dy = next.y - v.y;\n const len = Math.sqrt(dx * dx + dy * dy);\n return { x: dy / len, y: -dx / len };\n });\n\n // Offset each vertex along bisector of its two adjacent edge normals\n const offsetVerts = triVerts.map((_v, i) => {\n const n1 = triNormals[(i + 2) % 3]; // previous edge normal\n const n2 = triNormals[i]; // current edge normal\n const bx = n1.x + n2.x;\n const by = n1.y + n2.y;\n const bLen = Math.sqrt(bx * bx + by * by);\n const cosHalf = (bx * n1.x + by * n1.y) / bLen;\n const dist = offset / cosHalf;\n return {\n x: _v.x + (bx / bLen) * dist,\n y: _v.y + (by / bLen) * dist,\n };\n });\n\n ctx.moveTo(offsetVerts[0].x, offsetVerts[0].y);\n ctx.lineTo(offsetVerts[1].x, offsetVerts[1].y);\n ctx.lineTo(offsetVerts[2].x, offsetVerts[2].y);\n ctx.closePath();\n break;\n }\n\n case 'star': {\n const points = (transformData.points as number) ?? 5;\n const innerRadius = (transformData.innerRadius as number) ?? 0.4;\n const outerRadius = Math.min(width, height) / 2 + offset;\n const innerR = outerRadius * innerRadius;\n\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 : innerR;\n const x = r * Math.cos(angle);\n const y = r * Math.sin(angle);\n if (i === 0) {\n ctx.moveTo(x, y);\n } else {\n ctx.lineTo(x, y);\n }\n }\n ctx.closePath();\n break;\n }\n\n case 'polygon': {\n const sides = (transformData.sides as number) ?? 5;\n const radius = Math.min(width, height) / 2 + offset;\n for (let i = 0; i < sides; i++) {\n const angle = (i * 2 * Math.PI) / sides - Math.PI / 2;\n const x = radius * Math.cos(angle);\n const y = radius * Math.sin(angle);\n if (i === 0) {\n ctx.moveTo(x, y);\n } else {\n ctx.lineTo(x, y);\n }\n }\n ctx.closePath();\n break;\n }\n\n case 'line': {\n const halfWidth = width / 2 + offset;\n ctx.moveTo(-halfWidth, 0);\n ctx.lineTo(halfWidth, 0);\n break;\n }\n }\n\n ctx.stroke();\n ctx.restore();\n }\n\n /**\n * Render outline for image elements\n * Creates a ring effect with a gap between the element and the stroke\n */\n private static renderImageOutline(ctx: CanvasRenderingContext2D, element: BaseElement, zoom: number): void {\n // Use visual bounding box which respects crop settings\n const visualBbox = element.getVisualBoundingBox();\n const width = visualBbox.width * zoom;\n const height = visualBbox.height * zoom;\n const borderRadius = (element.transformData as Record<string, unknown>)?.borderRadius as number || 0;\n\n // Ring effect: gap = 2px, stroke = 2px\n // Offset to center of stroke = gap + stroke_width/2 = 2 + 1 = 3px\n const strokeWidth = 2;\n const gap = strokeWidth; // Gap equals stroke width\n const offset = gap + strokeWidth / 2;\n\n ctx.save();\n // Use element.x, element.y which is the center of the visible (cropped) area\n ctx.translate(element.x * zoom, element.y * zoom);\n\n const x = -(width + offset * 2) / 2;\n const y = -(height + offset * 2) / 2;\n const w = width + offset * 2;\n const h = height + offset * 2;\n\n ctx.beginPath();\n if (borderRadius > 0) {\n const radius = Math.min((borderRadius / 100) * Math.min(w, h), w / 2, h / 2);\n ctx.roundRect(x, y, w, h, radius);\n } else {\n ctx.rect(x, y, w, h);\n }\n ctx.stroke();\n\n ctx.restore();\n }\n\n /**\n * Render outline for group elements\n * Creates a ring effect with a gap between the element and the stroke\n */\n private static renderGroupOutline(ctx: CanvasRenderingContext2D, element: BaseElement, zoom: number): void {\n const visualBbox = element.getVisualBoundingBox();\n\n // Ring effect: gap = 2px, stroke = 2px\n // Offset to center of stroke = gap + stroke_width/2 = 2 + 1 = 3px\n const strokeWidth = 2;\n const gap = strokeWidth; // Gap equals stroke width\n const offset = gap + strokeWidth / 2;\n\n const x = visualBbox.x * zoom - offset;\n const y = visualBbox.y * zoom - offset;\n const width = visualBbox.width * zoom + offset * 2;\n const height = visualBbox.height * zoom + offset * 2;\n\n ctx.save();\n ctx.setLineDash([5, 5]); // Dashed outline for groups\n ctx.beginPath();\n ctx.roundRect(x, y, width, height, 8);\n ctx.stroke();\n ctx.restore();\n }\n\n /**\n * Render outline for text elements\n * Creates a ring effect with a gap between text and stroke (ring behind text fill)\n * Supports all transform types: custom, circle, wave, arch, etc.\n */\n private static renderTextOutline(\n ctx: CanvasRenderingContext2D,\n element: TextElement,\n zoom: number,\n _artboardBackgroundColor?: string\n ): void {\n // Delegate to transform-specific hover rendering based on type\n const transformType = element.transformType;\n\n if (\n transformType === 'circle' ||\n transformType === 'wave' ||\n transformType === 'arch' ||\n transformType === 'lean' ||\n transformType === 'ascend' ||\n transformType === 'flag'\n ) {\n // Use transform-specific hover ring effect (follows curved paths)\n this.renderTransformHoverRingEffect(ctx, element as TextElement, zoom);\n } else {\n // Straight text hover ring effect (for 'custom'/straight text)\n this.renderHoverRingEffect(ctx, element, zoom);\n }\n }\n\n /**\n * HOVER RING EFFECT for straight/custom text\n *\n * This is the colored ring that appears when hovering over text elements.\n * Uses offscreen canvas compositing to create a ring with a transparent gap.\n * Supports per-character fonts via rich text spans.\n *\n * NOTE: This is NOT the same as:\n * - Element stroke (StrokeRenderer.ts - only when stroke.enabled = true)\n * - Selection box (dashed rectangle in element.render())\n */\n private static renderHoverRingEffect(ctx: CanvasRenderingContext2D, element: TextElement, zoom: number): void {\n ctx.save();\n\n // Position and scale exactly like the text rendering\n ctx.translate(element.x * zoom, element.y * zoom);\n if (zoom !== 1.0) {\n ctx.scale(zoom, zoom);\n }\n\n // Use the exact same font setup as renderTextFillOnly\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\n // Calculate Y position using the EXACT same logic as renderTextFillOnly\n // Support multi-line text with proper line spacing\n const fontMetrics = getFontMetrics(fontSize, fontFamily, bold, italic);\n const lineSpacing = fontSize * 1.2; // Match LINE_HEIGHT_MULTIPLIER from constants.ts\n\n // Ring effect using offscreen canvas for true transparency in gap\n // Render complete effect isolated, then composite onto main canvas\n // Divide by zoom to keep ring visually constant in CSS pixels —\n // the ctx.scale(zoom) above will scale these back to screen size\n const ringWidth = 4 / zoom;\n const gap = 4 / zoom;\n\n // Create offscreen canvas for isolated hover effect rendering\n const tempCanvas = document.createElement('canvas');\n tempCanvas.width = ctx.canvas.width;\n tempCanvas.height = ctx.canvas.height;\n const tempCtx = tempCanvas.getContext('2d');\n if (!tempCtx) {\n ctx.restore();\n return;\n }\n\n // Copy transform to temp context\n tempCtx.setTransform(ctx.getTransform());\n tempCtx.textBaseline = 'alphabetic';\n tempCtx.lineJoin = 'round';\n\n // Check if element has rich text (per-character formatting)\n const richText = element.getRichText ? element.getRichText() : null;\n\n if (richText) {\n // Use rich text spans for per-character font support\n const explicitLines = splitRichTextIntoLines(richText);\n\n // Build wrapped lines with paragraph metadata\n const wrappedLines: Array<{ text: string; style: CharacterStyle }[]> = [];\n const paragraphMetadata: Array<{ isParagraphStart: boolean; isParagraphEnd: boolean }> = [];\n\n explicitLines.forEach((lineSpans) => {\n if (element.transformType === 'custom' && element.transformData) {\n const availableWidth = (element.transformData as Record<string, number>).width - HORIZONTAL_PADDING * 2;\n const wrapped = wrapRichTextSpans(lineSpans, availableWidth, {\n fontSize,\n fontFamily,\n bold,\n italic,\n });\n wrapped.forEach((wLine, wIndex) => {\n wrappedLines.push(wLine);\n // Each explicit-newline paragraph independently tracks its first/last\n // wrapped line. Previously this only marked the global first/last lines,\n // causing space stripping on intermediate paragraphs.\n paragraphMetadata.push({\n isParagraphStart: wIndex === 0,\n isParagraphEnd: wIndex === wrapped.length - 1,\n });\n });\n } else {\n // Single (unwrapped) line is both start and end of its paragraph\n wrappedLines.push(lineSpans);\n paragraphMetadata.push({\n isParagraphStart: true,\n isParagraphEnd: true,\n });\n }\n });\n\n // Calculate total height\n let totalHeight: number;\n if (wrappedLines.length === 0) {\n totalHeight = 0;\n } else if (wrappedLines.length === 1) {\n totalHeight = fontMetrics.height;\n } else {\n totalHeight = (wrappedLines.length - 1) * lineSpacing + fontMetrics.height;\n }\n const topLeftY = -totalHeight / 2;\n\n // Render each line\n wrappedLines.forEach((lineSpans, lineIndex) => {\n const isParagraphStart = paragraphMetadata[lineIndex].isParagraphStart;\n const isParagraphEnd = paragraphMetadata[lineIndex].isParagraphEnd;\n\n // Calculate line width using per-span fonts\n let lineWidth = 0;\n const fullLineText = lineSpans.map((s) => s.text).join('');\n const leadingSpacesCount = fullLineText.match(/^ +/)?.[0].length || 0;\n const trailingSpacesCount = fullLineText.match(/ +$/)?.[0].length || 0;\n\n let charIdx = 0;\n lineSpans.forEach((span) => {\n const spanFontSize = span.style.fontSize !== undefined ? span.style.fontSize : fontSize;\n const spanFontFamily = span.style.fontFamily !== undefined ? span.style.fontFamily : fontFamily;\n const spanBold = span.style.bold !== undefined ? span.style.bold : bold;\n const spanItalic = span.style.italic !== undefined ? span.style.italic : italic;\n\n // Apply visibility rules\n let visibleText = span.text;\n const spanStart = charIdx;\n const spanEnd = charIdx + span.text.length;\n\n if (!isParagraphStart && leadingSpacesCount > 0 && spanStart < leadingSpacesCount) {\n const hiddenChars = Math.min(leadingSpacesCount - spanStart, span.text.length);\n visibleText = visibleText.substring(hiddenChars);\n }\n if (!isParagraphEnd && trailingSpacesCount > 0) {\n const trailingStart = fullLineText.length - trailingSpacesCount;\n if (spanEnd > trailingStart) {\n const leadingTrim = !isParagraphStart && spanStart < leadingSpacesCount\n ? Math.min(leadingSpacesCount - spanStart, span.text.length)\n : 0;\n const visibleChars = Math.max(0, trailingStart - spanStart - leadingTrim);\n visibleText = visibleText.substring(0, visibleChars);\n }\n }\n\n if (visibleText.length > 0) {\n ctx.save();\n ctx.font = buildFontString(spanFontSize, spanFontFamily, spanBold, spanItalic);\n lineWidth += ctx.measureText(visibleText).width;\n ctx.restore();\n }\n\n charIdx += span.text.length;\n });\n\n // Calculate starting X position based on alignment.\n //\n // Must match the fill renderer (rich-text-renderer.ts\n // `renderRichTextFillOnly` ~line 495): left/right align to the\n // wrap-box EDGES, not to x=0. Anchoring left/right at x=0 (the\n // element center) is what made the hover ring drift ~half the\n // box width away from the text on non-center alignment.\n const maxWidth =\n element.transformType === 'custom' && element.transformData\n ? (element.transformData as Record<string, number>).width - HORIZONTAL_PADDING * 2\n : undefined;\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 for this line\n const baselineY = topLeftY + fontMetrics.ascent + lineIndex * lineSpacing;\n\n // Render each span with ring effect\n charIdx = 0;\n lineSpans.forEach((span) => {\n const spanFontSize = span.style.fontSize !== undefined ? span.style.fontSize : fontSize;\n const spanFontFamily = span.style.fontFamily !== undefined ? span.style.fontFamily : fontFamily;\n // Note: spanColor is not used directly here but is available for ring color customization\n void (span.style.color !== undefined ? span.style.color : element.color || '#333333');\n const spanBold = span.style.bold !== undefined ? span.style.bold : bold;\n const spanItalic = span.style.italic !== undefined ? span.style.italic : italic;\n\n // Apply visibility rules\n let visibleText = span.text;\n const spanStart = charIdx;\n const spanEnd = charIdx + span.text.length;\n\n if (!isParagraphStart && leadingSpacesCount > 0 && spanStart < leadingSpacesCount) {\n const hiddenChars = Math.min(leadingSpacesCount - spanStart, span.text.length);\n visibleText = visibleText.substring(hiddenChars);\n }\n if (!isParagraphEnd && trailingSpacesCount > 0) {\n const trailingStart = fullLineText.length - trailingSpacesCount;\n if (spanEnd > trailingStart) {\n const leadingTrim = !isParagraphStart && spanStart < leadingSpacesCount\n ? Math.min(leadingSpacesCount - spanStart, span.text.length)\n : 0;\n const visibleChars = Math.max(0, trailingStart - spanStart - leadingTrim);\n visibleText = visibleText.substring(0, visibleChars);\n }\n }\n\n charIdx += span.text.length;\n\n if (visibleText.length > 0) {\n // Set font for this span\n tempCtx.font = buildFontString(spanFontSize, spanFontFamily, spanBold, spanItalic);\n tempCtx.textAlign = 'left';\n\n // Step 1: Draw outer stroke on offscreen canvas\n tempCtx.globalCompositeOperation = 'source-over';\n tempCtx.strokeStyle = ctx.strokeStyle; // Use current accent color\n tempCtx.globalAlpha = Math.min(ctx.globalAlpha * 1.6, 0.8);\n tempCtx.lineWidth = (gap + ringWidth) * 2;\n tempCtx.strokeText(visibleText, currentX, baselineY);\n\n // Step 2: Cut out gap area using destination-out\n // This cuts out the gap stroke AND the text shape from the ring\n // The text fill is NOT rendered here - it comes from the original element render\n // This prevents text appearing darker on hover (double-render issue)\n tempCtx.globalCompositeOperation = 'destination-out';\n tempCtx.globalAlpha = 1.0;\n tempCtx.lineWidth = gap * 2;\n tempCtx.strokeText(visibleText, currentX, baselineY);\n tempCtx.fillText(visibleText, currentX, baselineY);\n\n // Note: We intentionally do NOT draw text fill here\n // The original element.render() already drew the text\n // We only composite the ring effect on top\n\n // Advance X position\n currentX += tempCtx.measureText(visibleText).width;\n }\n });\n });\n } else {\n // Fallback: no rich text, use plain text rendering with element-level font\n ctx.font = buildFontString(fontSize, fontFamily, bold, italic);\n tempCtx.font = ctx.font;\n tempCtx.textAlign = textAlign as CanvasTextAlign;\n\n // Get wrapped lines\n let textLines: string[];\n let totalHeight: number;\n\n if (element.transformType === 'custom' && element.transformData) {\n const availableWidth = (element.transformData as Record<string, number>).width - HORIZONTAL_PADDING * 2;\n const { lines } = calculateVisualBoundsWithSpaceCollapsing(\n element.text,\n availableWidth,\n fontSize,\n fontFamily,\n bold,\n italic,\n undefined,\n true\n );\n textLines = lines;\n } else {\n textLines = element.text.split('\\n');\n const hasExplicitNewlines = element.text.includes('\\n');\n textLines = applySpaceLayoutRules(textLines, hasExplicitNewlines);\n }\n\n if (textLines.length === 0) {\n totalHeight = 0;\n } else if (textLines.length === 1) {\n totalHeight = fontMetrics.height;\n } else {\n totalHeight = (textLines.length - 1) * lineSpacing + fontMetrics.height;\n }\n\n const topLeftY = -totalHeight / 2;\n const hasExplicitNewlines = element.text.includes('\\n');\n\n // X anchor for each line, matching the plain-text fill renderer\n // (text-renderer.ts `renderCustomTransformNormal` ~line 641): with\n // `tempCtx.textAlign` set, left/right anchor to the wrap-box edges,\n // center anchors at x=0. `box` is the full container width.\n const box =\n element.transformType === 'custom' && element.transformData\n ? (element.transformData as Record<string, number>).width\n : 0;\n const topLeftX = -box / 2;\n let xPos = topLeftX + HORIZONTAL_PADDING; // left\n if (textAlign === 'center') {\n xPos = topLeftX + box / 2;\n } else if (textAlign === 'right') {\n xPos = topLeftX + box - HORIZONTAL_PADDING;\n }\n\n textLines.forEach((line, lineIndex) => {\n const yPos = topLeftY + fontMetrics.ascent + lineIndex * lineSpacing;\n // With explicit newlines, each line is its own paragraph (both start and end).\n // With wrapping only, first line is paragraph start, last is paragraph end.\n const isParagraphStart = hasExplicitNewlines || lineIndex === 0;\n const isParagraphEnd = hasExplicitNewlines || lineIndex === textLines.length - 1;\n\n let visibleText = line;\n if (!isParagraphStart) {\n visibleText = visibleText.replace(/^ +/, '');\n }\n if (!isParagraphEnd) {\n visibleText = visibleText.replace(/ +$/, '');\n }\n\n // Step 1: Draw outer stroke\n tempCtx.globalCompositeOperation = 'source-over';\n tempCtx.strokeStyle = ctx.strokeStyle;\n tempCtx.globalAlpha = Math.min(ctx.globalAlpha * 1.6, 0.8);\n tempCtx.lineWidth = (gap + ringWidth) * 2;\n tempCtx.lineJoin = 'round';\n tempCtx.strokeText(visibleText, xPos, yPos);\n\n // Step 2: Cut out gap area\n // This cuts out the gap stroke AND the text shape from the ring\n // The text fill is NOT rendered here - it comes from the original element render\n // This prevents text appearing darker on hover (double-render issue)\n tempCtx.globalCompositeOperation = 'destination-out';\n tempCtx.globalAlpha = 1.0;\n tempCtx.lineWidth = gap * 2;\n tempCtx.strokeText(visibleText, xPos, yPos);\n tempCtx.fillText(visibleText, xPos, yPos);\n\n // Note: We intentionally do NOT draw text fill here\n // The original element.render() already drew the text\n // We only composite the ring effect on top\n });\n }\n\n // Draw complete hover effect from offscreen canvas to main canvas\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.drawImage(tempCanvas, 0, 0);\n\n ctx.restore();\n }\n\n /**\n * HOVER RING EFFECT for transformed text (circle, wave, arch, lean, ascend, flag)\n *\n * This is the colored ring that appears when hovering over curved/transformed text.\n * Renders text character-by-character along the transform path with ring effect.\n * Uses offscreen canvas compositing to create a ring with a transparent gap.\n *\n * NOTE: This is NOT the same as:\n * - Element stroke (StrokeRenderer.ts - only when stroke.enabled = true)\n * - Selection box (dashed outline shown by each transform's render() method)\n */\n private static renderTransformHoverRingEffect(ctx: CanvasRenderingContext2D, element: TextElement, zoom: number): void {\n ctx.save();\n\n // Position and scale\n ctx.translate(element.x * zoom, element.y * zoom);\n if (zoom !== 1.0) {\n ctx.scale(zoom, zoom);\n }\n\n // Apply element rotation\n ctx.rotate((-element.rotation * Math.PI) / 180);\n\n // Ring effect settings — divide by zoom for zoom-invariant visual size\n const ringWidth = 4 / zoom;\n const gap = 4 / zoom;\n\n // Create offscreen canvas for isolated hover effect rendering\n const tempCanvas = document.createElement('canvas');\n tempCanvas.width = ctx.canvas.width;\n tempCanvas.height = ctx.canvas.height;\n const tempCtx = tempCanvas.getContext('2d');\n if (!tempCtx) {\n ctx.restore();\n return;\n }\n\n // Copy current transform to temp context\n tempCtx.setTransform(ctx.getTransform());\n\n // Render the transform with hover effect using a helper\n // Pass the current stroke style (color) from the main context\n this.renderTransformWithHoverEffect(tempCtx, element, ringWidth, gap, ctx.strokeStyle as string);\n\n // Draw complete hover effect from offscreen canvas to main canvas\n ctx.setTransform(1, 0, 0, 1, 0, 0); // Reset transform for drawImage\n ctx.drawImage(tempCanvas, 0, 0);\n\n ctx.restore();\n }\n\n /**\n * Helper to render text along a transform path with hover effect\n */\n private static renderTransformWithHoverEffect(\n ctx: CanvasRenderingContext2D,\n element: TextElement,\n ringWidth: number,\n gap: number,\n color: string\n ): void {\n const textEl = element;\n const fontSize = textEl.fontSize;\n const fontFamily = textEl.fontFamily;\n const bold = textEl.bold || false;\n const italic = textEl.italic || false;\n\n // Set up font\n const weight = bold ? 'bold' : 'normal';\n const style = italic ? 'italic' : 'normal';\n ctx.font = `${style} ${weight} ${fontSize}px ${fontFamily}`;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n\n // Lean transform uses a global horizontal skew on the whole text block,\n // not per-character positioning. Handle it specially.\n if (textEl.transformType === 'lean') {\n const leanData = textEl.transformData as LeanTransformData;\n const skewAngle = (-leanData.leanAmount * Math.PI) / 4;\n ctx.transform(1, 0, Math.tan(skewAngle), 1, 0, 0);\n\n // Draw the entire text as one block with ring effect\n ctx.globalCompositeOperation = 'source-over';\n ctx.strokeStyle = color;\n ctx.globalAlpha = 0.8;\n ctx.lineWidth = (gap + ringWidth) * 2;\n ctx.lineJoin = 'round';\n ctx.strokeText(textEl.text, 0, 0);\n\n ctx.globalCompositeOperation = 'destination-out';\n ctx.globalAlpha = 1.0;\n ctx.lineWidth = gap * 2;\n ctx.strokeText(textEl.text, 0, 0);\n ctx.fillText(textEl.text, 0, 0);\n return;\n }\n\n // Get character positions based on transform type\n const chars = textEl.text.split('');\n const charWidths = chars.map((char: string) => ctx.measureText(char).width);\n\n // Calculate positions for each character based on transform type\n const positions = this.calculateCharPositions(element, chars, charWidths);\n\n // Two-pass rendering: draw ALL ring strokes first, then cut out ALL gaps+text.\n // This prevents one character's ring from overlapping an adjacent character's text.\n\n // Helper to apply position transform (translate + rotate + skew)\n const applyCharTransform = (pos: { x: number; y: number; angle: number; skewY?: number }) => {\n ctx.translate(pos.x, pos.y);\n if (pos.angle) ctx.rotate(pos.angle);\n if (pos.skewY) ctx.transform(1, pos.skewY, 0, 1, 0, 0);\n };\n\n // Pass 1: Draw all outer ring strokes\n ctx.globalCompositeOperation = 'source-over';\n ctx.strokeStyle = color;\n ctx.globalAlpha = 0.8;\n ctx.lineWidth = (gap + ringWidth) * 2;\n ctx.lineJoin = 'round';\n positions.forEach((pos, i) => {\n ctx.save();\n applyCharTransform(pos);\n ctx.strokeText(chars[i], 0, 0);\n ctx.restore();\n });\n\n // Pass 2: Cut out gap and text shape from the ring\n ctx.globalCompositeOperation = 'destination-out';\n ctx.globalAlpha = 1.0;\n ctx.lineWidth = gap * 2;\n positions.forEach((pos, i) => {\n ctx.save();\n applyCharTransform(pos);\n ctx.strokeText(chars[i], 0, 0);\n ctx.fillText(chars[i], 0, 0);\n ctx.restore();\n });\n }\n\n /**\n * Calculate character positions for different transform types\n *\n * Also used by EditModeRenderer for cursor positioning on transformed text.\n */\n static calculateCharPositions(\n element: TextElement,\n chars: string[],\n charWidths: number[]\n ): Array<{ x: number; y: number; angle: number; skewY?: number }> {\n const transformType = element.transformType;\n const transformData = element.transformData;\n\n if (transformType === 'circle') {\n return this.calculateCirclePositions(transformData as CircleTransformData, chars, charWidths);\n } else if (transformType === 'wave') {\n return this.calculateWavePositions(element, chars, charWidths, transformData as WaveTransformData);\n } else if (transformType === 'arch') {\n return this.calculateArchPositions(element, chars, charWidths, transformData as ArchTransformData);\n } else if (transformType === 'lean') {\n return this.calculateLeanPositions(chars, charWidths);\n } else if (transformType === 'ascend') {\n return this.calculateAscendPositions(element, chars, charWidths, transformData as AscendTransformData);\n } else if (transformType === 'flag') {\n return this.calculateFlagPositions(element, chars, charWidths, transformData as FlagTransformData);\n }\n\n // Default: straight line\n return chars.map(() => ({ x: 0, y: 0, angle: 0 }));\n }\n\n /**\n * Calculate positions for circle transform\n */\n private static calculateCirclePositions(\n transformData: CircleTransformData,\n chars: string[],\n charWidths: number[]\n ): Array<{ x: number; y: number; angle: number }> {\n const positions: Array<{ x: number; y: number; angle: number }> = [];\n const totalWidth = charWidths.reduce((sum, w) => sum + w, 0);\n const radius = transformData.radius * transformData.scale;\n\n if (transformData.reverse) {\n // Bottom text: start from right side and go left\n let currentAngle = Math.PI / 2 + totalWidth / (2 * radius);\n\n chars.forEach((_, i) => {\n const charWidth = charWidths[i];\n const angleStep = charWidth / radius;\n\n const x = Math.cos(currentAngle - angleStep / 2) * radius;\n const y = Math.sin(currentAngle - angleStep / 2) * radius;\n const angle = currentAngle - angleStep / 2 - Math.PI / 2;\n\n positions.push({ x, y, angle });\n currentAngle -= angleStep;\n });\n } else {\n // Top text: start from left side and go right\n let currentAngle = -Math.PI / 2 - totalWidth / (2 * radius);\n\n chars.forEach((_, i) => {\n const charWidth = charWidths[i];\n const angleStep = charWidth / radius;\n\n const x = Math.cos(currentAngle + angleStep / 2) * radius;\n const y = Math.sin(currentAngle + angleStep / 2) * radius;\n const angle = currentAngle + angleStep / 2 + Math.PI / 2;\n\n positions.push({ x, y, angle });\n currentAngle += angleStep;\n });\n }\n\n return positions;\n }\n\n /**\n * Calculate positions for wave transform\n */\n private static calculateWavePositions(\n element: TextElement,\n chars: string[],\n charWidths: number[],\n transformData: WaveTransformData\n ): Array<{ x: number; y: number; angle: number; skewY?: number }> {\n const positions: Array<{ x: number; y: number; angle: number; skewY?: number }> = [];\n const totalWidth = charWidths.reduce((sum, w) => sum + w, 0);\n let currentX = -totalWidth / 2;\n\n chars.forEach((_, i) => {\n const charWidth = charWidths[i];\n const x = currentX + charWidth / 2;\n const normalizedX = x / (transformData.width / 2);\n\n // Calculate y position on sine wave (matches transform-renderer.ts)\n const y = transformData.amplitude * element.fontSize * Math.sin(transformData.frequency * Math.PI * normalizedX);\n\n // Calculate slope for vertical skew (matches transform-renderer.ts)\n const slope =\n transformData.amplitude *\n transformData.frequency *\n Math.PI *\n Math.cos(transformData.frequency * Math.PI * normalizedX);\n\n positions.push({ x, y, angle: 0, skewY: slope * WAVE_SKEW_FACTOR });\n currentX += charWidth;\n });\n\n return positions;\n }\n\n /**\n * Calculate positions for arch transform\n */\n private static calculateArchPositions(\n element: TextElement,\n chars: string[],\n charWidths: number[],\n transformData: ArchTransformData\n ): Array<{ x: number; y: number; angle: number; skewY?: number }> {\n const positions: Array<{ x: number; y: number; angle: number; skewY?: number }> = [];\n const totalWidth = charWidths.reduce((sum, w) => sum + w, 0);\n const radius = transformData.width / 2;\n let currentX = -totalWidth / 2;\n\n chars.forEach((_, i) => {\n const charWidth = charWidths[i];\n const x = currentX + charWidth / 2;\n const normalizedX = x / radius;\n\n // Parabolic curve (matches transform-renderer.ts)\n const y = (Math.pow(normalizedX, 2) - 1) * transformData.archHeight * element.fontSize;\n\n // Slope for vertical skew (matches transform-renderer.ts)\n const slope = 2 * normalizedX * transformData.archHeight;\n const skewFactor = 0.3;\n\n positions.push({ x, y, angle: 0, skewY: slope * skewFactor });\n currentX += charWidth;\n });\n\n return positions;\n }\n\n /**\n * Calculate positions for lean transform\n */\n private static calculateLeanPositions(\n chars: string[],\n charWidths: number[]\n ): Array<{ x: number; y: number; angle: number }> {\n const positions: Array<{ x: number; y: number; angle: number }> = [];\n const totalWidth = charWidths.reduce((sum, w) => sum + w, 0);\n let currentX = -totalWidth / 2;\n\n chars.forEach((_, i) => {\n const charWidth = charWidths[i];\n const x = currentX + charWidth / 2;\n\n // Lean uses skew transform, characters stay on baseline\n positions.push({ x, y: 0, angle: 0 });\n currentX += charWidth;\n });\n\n return positions;\n }\n\n /**\n * Calculate positions for ascend transform\n */\n private static calculateAscendPositions(\n _element: TextElement,\n chars: string[],\n charWidths: number[],\n transformData: AscendTransformData\n ): Array<{ x: number; y: number; angle: number; skewY?: number }> {\n const positions: Array<{ x: number; y: number; angle: number; skewY?: number }> = [];\n const totalWidth = charWidths.reduce((sum, w) => sum + w, 0);\n const angleRad = (transformData.ascendAngle * Math.PI) / 180;\n const slope = Math.tan(angleRad);\n let currentX = -totalWidth / 2;\n\n chars.forEach((_, i) => {\n const charWidth = charWidths[i];\n const x = currentX + charWidth / 2;\n\n // Diagonal line (matches transform-renderer.ts)\n const y = x * slope;\n\n // Skew factor 1.0 for ascend (matches transform-renderer.ts)\n const skewFactor = 1.0;\n\n positions.push({ x, y, angle: 0, skewY: slope * skewFactor });\n currentX += charWidth;\n });\n\n return positions;\n }\n\n /**\n * Calculate positions for flag transform\n */\n private static calculateFlagPositions(\n element: TextElement,\n chars: string[],\n charWidths: number[],\n transformData: FlagTransformData\n ): Array<{ x: number; y: number; angle: number; skewY?: number }> {\n const positions: Array<{ x: number; y: number; angle: number; skewY?: number }> = [];\n const totalWidth = charWidths.reduce((sum, w) => sum + w, 0);\n let currentX = -totalWidth / 2;\n\n chars.forEach((_, i) => {\n const charWidth = charWidths[i];\n const x = currentX + charWidth / 2;\n const normalizedX = x / (transformData.width / 2);\n\n // Flag effect: amplitude increases from center to edges (matches transform-renderer.ts)\n const distanceFromCenter = Math.abs(normalizedX);\n const flagAmplitude = transformData.amplitude * distanceFromCenter;\n\n const y = flagAmplitude * element.fontSize * Math.sin(transformData.frequency * Math.PI * normalizedX);\n\n // Slope for skew (matches transform-renderer.ts)\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 const skewFactor = 0.3;\n\n positions.push({ x, y, angle: 0, skewY: slope * skewFactor });\n currentX += charWidth;\n });\n\n return positions;\n }\n}\n","/**\n * InteractionFeedbackRenderer - Snap guides, spacing indicators, and distance labels\n *\n * Renders visual feedback during user interactions:\n * - Snap guide lines (alignment snapping during drag/resize)\n * - Spacing indicator lines with end caps (distance between elements)\n * - Distance labels with colored background\n *\n * Extracted from CanvasRenderer.ts as part of the rendering decomposition.\n * All methods are static to match the original CanvasRenderer pattern.\n */\n\nimport { theme } from '../theme.js';\nimport type { SnapGuide, SpacingIndicator } from '../types/index.js';\n\nimport {\n getThemeAccentColor,\n getThemeSpacingColor,\n SPACING_LABEL_TEXT_COLOR,\n SPACING_LABEL_BORDER_RADIUS,\n} from '../constants.js';\n\nexport class InteractionFeedbackRenderer {\n /**\n * Render snap guides layer\n */\n static renderSnapGuidesLayer(ctx: CanvasRenderingContext2D, snapGuides: SnapGuide[] = []): void {\n if (snapGuides.length === 0) return;\n\n ctx.save();\n ctx.strokeStyle = getThemeAccentColor(ctx.canvas);\n ctx.lineWidth = theme.canvas.snapGuideWidth;\n ctx.setLineDash(theme.canvas.snapGuideDash);\n\n snapGuides.forEach((guide) => {\n ctx.beginPath();\n\n if (guide.type === 'vertical') {\n // Vertical line\n ctx.moveTo(guide.x ?? 0, guide.y1 ?? 0);\n ctx.lineTo(guide.x ?? 0, guide.y2 ?? 0);\n } else if (guide.type === 'horizontal') {\n // Horizontal line\n ctx.moveTo(guide.x1 ?? 0, guide.y ?? 0);\n ctx.lineTo(guide.x2 ?? 0, guide.y ?? 0);\n } else {\n // Fallback for generic lines\n ctx.moveTo(guide.x1 ?? 0, guide.y1 ?? 0);\n ctx.lineTo(guide.x2 ?? 0, guide.y2 ?? 0);\n }\n\n ctx.stroke();\n });\n\n ctx.restore();\n }\n\n /**\n * Render spacing indicators layer\n */\n static renderSpacingIndicatorsLayer(ctx: CanvasRenderingContext2D, spacingIndicators: SpacingIndicator[] = []): void {\n if (spacingIndicators.length === 0) return;\n\n ctx.save();\n ctx.setLineDash([]); // Solid lines for spacing indicators\n\n spacingIndicators.forEach((indicator) => {\n // Use theme-specific color for all spacing indicators (regular, snap targets, and Alt+hover)\n const spacingColor = getThemeSpacingColor(ctx.canvas);\n ctx.strokeStyle = spacingColor;\n ctx.fillStyle = spacingColor;\n ctx.lineWidth = theme.canvas.spacingIndicatorWidth;\n ctx.beginPath();\n\n if (indicator.type === 'horizontal') {\n // Horizontal spacing line\n const x1 = indicator.x1 ?? 0;\n const x2 = indicator.x2 ?? 0;\n const y = indicator.y ?? 0;\n\n // Draw the main line\n ctx.moveTo(x1, y);\n ctx.lineTo(x2, y);\n ctx.stroke();\n\n // Draw end caps (perpendicular lines at each end)\n const capLength = 6;\n ctx.beginPath();\n ctx.moveTo(x1, y - capLength);\n ctx.lineTo(x1, y + capLength);\n ctx.stroke();\n\n ctx.beginPath();\n ctx.moveTo(x2, y - capLength);\n ctx.lineTo(x2, y + capLength);\n ctx.stroke();\n\n // Draw distance label\n const labelX = indicator.labelX;\n const labelY = indicator.labelY;\n this.renderDistanceLabel(ctx, labelX, labelY, indicator.distance);\n } else if (indicator.type === 'vertical') {\n // Vertical spacing line\n const x = indicator.x ?? 0;\n const y1 = indicator.y1 ?? 0;\n const y2 = indicator.y2 ?? 0;\n\n // Draw the main line\n ctx.moveTo(x, y1);\n ctx.lineTo(x, y2);\n ctx.stroke();\n\n // Draw end caps (perpendicular lines at each end)\n const capLength = 6;\n ctx.beginPath();\n ctx.moveTo(x - capLength, y1);\n ctx.lineTo(x + capLength, y1);\n ctx.stroke();\n\n ctx.beginPath();\n ctx.moveTo(x - capLength, y2);\n ctx.lineTo(x + capLength, y2);\n ctx.stroke();\n\n // Draw distance label\n const labelX = indicator.labelX;\n const labelY = indicator.labelY;\n this.renderDistanceLabel(ctx, labelX, labelY, indicator.distance);\n }\n });\n\n ctx.restore();\n }\n\n /**\n * Render a distance label with background\n */\n static renderDistanceLabel(ctx: CanvasRenderingContext2D, x: number, y: number, distance: number): void {\n const text = `${distance}`;\n ctx.font = `600 ${theme.canvas.spacingLabelFontSize}px ${theme.canvas.spacingLabelFontFamily}`;\n\n // Measure text\n const metrics = ctx.measureText(text);\n const textWidth = metrics.width;\n const textHeight = theme.canvas.spacingLabelFontSize;\n\n // Background padding\n const padding = 4;\n const bgWidth = textWidth + padding * 2;\n const bgHeight = textHeight + padding * 2;\n\n // Draw background with rounded corners\n ctx.fillStyle = getThemeSpacingColor(ctx.canvas);\n ctx.beginPath();\n ctx.roundRect(x - bgWidth / 2, y - bgHeight / 2, bgWidth, bgHeight, SPACING_LABEL_BORDER_RADIUS);\n ctx.fill();\n\n // Draw text\n ctx.fillStyle = SPACING_LABEL_TEXT_COLOR;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'middle';\n ctx.fillText(text, x, y);\n }\n}\n","/**\n * EditModeRenderer - Text editing cursor, selection highlight, and crop UI rendering\n *\n * Renders visual elements for active editing states:\n * - Text cursor (blinking vertical line) for straight and transformed text\n * - Text selection highlighting with proper kerning\n * - iOS-style selection handles for touch devices\n * - Crop box overlay, grid lines, and crop handles\n *\n * Extracted from CanvasRenderer.ts as part of the rendering decomposition.\n * All methods are static to match the original CanvasRenderer pattern.\n */\n\nimport type React from 'react';\nimport { RotationUtils } from './RotationUtils.js';\nimport {\n CORNER_HANDLE_VISUAL_RADIUS,\n EDGE_HANDLE_VISUAL_LENGTH,\n} from '../constants.js';\nimport type { TextElement } from './TextElement.js';\nimport type { BaseElement } from './BaseElement.js';\nimport type { TransformHandles } from './TransformHandles.js';\nimport { RichText } from '../types/index.js';\nimport type { CharacterStyle, CustomTransformData } from '../types/index.js';\nimport { ImageElement } from './ImageElement.js';\nimport { HoverRenderer } from './HoverRenderer.js';\n\nimport {\n getThemeAccentColor,\n getThemeTextSelectionColor,\n HORIZONTAL_PADDING,\n} from '../constants.js';\nimport { getFontMetrics } from './TextMetrics.js';\nimport { splitRichTextIntoLines, wrapRichTextSpans, renderRichTextFillOnly } from '../rendering/canvas-renderer.js';\n\nexport class EditModeRenderer {\n /**\n * Render edit mode features: cursor and text selection highlight\n * This renders on the main canvas at world coordinates (same as the element)\n */\n static renderEditModeLayer(\n ctx: CanvasRenderingContext2D,\n element: BaseElement | null,\n editModeData: {\n cursorPosition: number;\n selectionStart: number;\n selectionEnd: number;\n editText: string;\n editRichText: RichText | null;\n cursorOpacity: number;\n isTouchDevice?: boolean;\n selectionHandlePositionsRef?: React.MutableRefObject<{\n start: { center: { x: number; y: number }; textEdge: { x: number; y: number } } | null;\n end: { center: { x: number; y: number }; textEdge: { x: number; y: number } } | null;\n }>;\n },\n zoom: number\n ): void {\n if (!element || element.transformType === 'image' || element.transformType === 'group') return;\n\n // After filtering out 'image' and 'group', the remaining elements are text-based\n const textElement = element as TextElement;\n\n const { cursorPosition, selectionStart, selectionEnd, editText, editRichText, cursorOpacity, isTouchDevice, selectionHandlePositionsRef } = editModeData;\n\n // Store zoom for use in selection handle rendering (zoom-invariant tap zones)\n const currentZoom = zoom || 1.0;\n\n // Delegate to transform-specific rendering for non-custom transforms\n const transformType = textElement.transformType;\n if (\n transformType === 'circle' ||\n transformType === 'wave' ||\n transformType === 'arch' ||\n transformType === 'lean' ||\n transformType === 'ascend' ||\n transformType === 'flag'\n ) {\n this.renderTransformEditModeLayer(ctx, textElement, editModeData, currentZoom);\n return;\n }\n\n // Continue with straight text cursor rendering for custom transform\n\n // Get element properties\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 underline = textElement.underline || false;\n const strikethrough = textElement.strikethrough || false;\n const textAlign = textElement.textAlign || 'center';\n\n ctx.save();\n\n // Apply element's transform (position and rotation)\n ctx.translate(textElement.x, textElement.y);\n // Use negative angle for clockwise rotation (matching UI convention)\n // Convert from degrees to radians\n ctx.rotate((-textElement.rotation * Math.PI) / 180);\n\n // Use getFontMetrics to match rendering exactly (uses 'ÁÉÍgjpqy' sample)\n const fontMetrics = getFontMetrics(fontSize, fontFamily, bold, italic);\n const lineBoxHeight = fontMetrics.height;\n\n // Calculate total text height to center vertically (match renderRichTextFillOnly)\n const lineSpacing = fontSize * 1.2; // Match LINE_HEIGHT_MULTIPLIER from constants.ts\n\n // CRITICAL: Use the exact same wrapping logic as rendering\n // Get available width for wrapping (if element is CustomTransform)\n let wrappedLines: Array<{ text: string; style: CharacterStyle }[]>;\n let paragraphMetadata: Array<{ isParagraphStart: boolean; isParagraphEnd: boolean }>;\n\n // Inner box width used for alignment anchoring (mirror renderRichTextFillOnly):\n // defined only when the element has an explicit box, otherwise undefined so\n // left/right alignment falls back to anchor-at-zero.\n let maxWidth: number | undefined;\n\n if (editRichText && textElement.transformType === 'custom' && (textElement.transformData as CustomTransformData).width) {\n // Use rich text wrapping (same as renderRichTextFillOnly)\n const boxWidth = (textElement.transformData as CustomTransformData).width - HORIZONTAL_PADDING * 2;\n maxWidth = boxWidth;\n const explicitLines = splitRichTextIntoLines(editRichText);\n wrappedLines = [];\n paragraphMetadata = [];\n\n explicitLines.forEach((lineSpans) => {\n const wrapped = wrapRichTextSpans(lineSpans, boxWidth, {\n fontSize: textElement.fontSize,\n fontFamily: textElement.fontFamily,\n bold: textElement.bold,\n italic: textElement.italic,\n });\n wrapped.forEach((wrappedLine, idx) => {\n wrappedLines.push(wrappedLine);\n paragraphMetadata.push({\n isParagraphStart: idx === 0,\n isParagraphEnd: idx === wrapped.length - 1,\n });\n });\n });\n } else {\n // Fallback: no wrapping, just split by newlines\n const textLines = editText.split('\\n');\n wrappedLines = textLines.map((line) => [\n {\n text: line,\n style: { fontSize, fontFamily, bold, italic },\n },\n ]);\n paragraphMetadata = wrappedLines.map(() => ({\n isParagraphStart: true, // Each line is its own paragraph\n isParagraphEnd: true,\n }));\n }\n\n // Match rendering formula: lineHeight * (1 + (n-1) * lineSpacing)\n // Note: lineSpacing here is the full line height (fontSize * 1.2), not just the gap\n const totalHeight =\n wrappedLines.length > 1 ? fontMetrics.height + (wrappedLines.length - 1) * lineSpacing : fontMetrics.height;\n\n // Start Y from top of vertically-centered text block (match renderRichTextFillOnly line 613)\n let currentY = -totalHeight / 2;\n\n // Track cursor position and character positions for selection\n let cursorX = 0;\n let cursorY = currentY;\n let cursorHeight = lineBoxHeight;\n\n interface CharPosition {\n x: number;\n y: number;\n width: number;\n height: number;\n }\n const charPositions: CharPosition[] = [];\n\n // CRITICAL: Process wrapped lines directly (not character-by-character from editText)\n // This ensures cursor positions match the actual rendered layout\n let charIndex = 0; // Track position in original editText\n\n wrappedLines.forEach((lineSpans, lineIndex) => {\n const isParagraphStart = paragraphMetadata[lineIndex].isParagraphStart;\n const isParagraphEnd = paragraphMetadata[lineIndex].isParagraphEnd;\n\n // Build line characters with measurements\n const currentLineChars: Array<{ char: string; width: number; fontSize: number; index: number }> = [];\n\n lineSpans.forEach((span) => {\n const chars = Array.from(span.text);\n chars.forEach((char) => {\n // Get character style\n let charFontSize = fontSize;\n let charFontFamily = fontFamily;\n let charBold = bold;\n let charItalic = italic;\n\n if (editRichText && charIndex < editText.length) {\n const charStyle = editRichText.getStyleAt(charIndex);\n charFontSize = charStyle.fontSize || fontSize;\n charFontFamily = charStyle.fontFamily || fontFamily;\n charBold = charStyle.bold !== undefined ? charStyle.bold : bold;\n charItalic = charStyle.italic !== undefined ? charStyle.italic : italic;\n }\n\n // Measure character width\n ctx.save();\n const fontWeight = charBold ? 'bold' : 'normal';\n const fontStyle = charItalic ? 'italic' : 'normal';\n ctx.font = `${fontStyle} ${fontWeight} ${charFontSize}px \"${charFontFamily}\", Arial, sans-serif`;\n const charWidth = ctx.measureText(char).width;\n ctx.restore();\n\n currentLineChars.push({ char, width: charWidth, fontSize: charFontSize, index: charIndex });\n charIndex++;\n });\n });\n\n // Process this wrapped line\n {\n const isFirstLine = lineIndex === 0;\n const isLastLine = lineIndex === wrappedLines.length - 1;\n\n const fullLineText = currentLineChars.map((c) => c.char).join('');\n\n // Calculate leading/trailing space counts for layout rules\n const leadingSpacesCount = fullLineText.match(/^ +/)?.[0].length || 0;\n const trailingSpacesCount = fullLineText.match(/ +$/)?.[0].length || 0;\n\n // Calculate line width using per-character widths (respecting space layout rules)\n let lineWidth = 0;\n for (let j = 0; j < currentLineChars.length; j++) {\n // Skip leading spaces if not first line\n if (!isFirstLine && j < leadingSpacesCount) continue;\n // Skip trailing spaces if not last line\n if (!isLastLine && j >= currentLineChars.length - trailingSpacesCount) continue;\n lineWidth += currentLineChars[j].width;\n }\n\n // Calculate starting X based on alignment (match renderRichTextFillOnly).\n // Anchor to the bounding-box edges, not x=0 — that way the cursor +\n // characters land where the rendered glyphs actually appear.\n let lineX: number;\n if (textAlign === 'center') {\n lineX = -lineWidth / 2;\n } else if (textAlign === 'right') {\n lineX = maxWidth !== undefined ? maxWidth / 2 - lineWidth : -lineWidth;\n } else {\n // left\n lineX = maxWidth !== undefined ? -maxWidth / 2 : 0;\n }\n\n // Position all characters in this line (cursor can navigate through hidden spaces)\n let currentX = lineX;\n\n for (let j = 0; j < currentLineChars.length; j++) {\n const charData = currentLineChars[j];\n\n // Store character position (cursor can navigate to hidden spaces)\n charPositions[charData.index] = {\n x: currentX,\n y: currentY,\n width: charData.width, // All characters keep their width for cursor navigation\n height: charData.fontSize,\n };\n\n // Always advance X (so cursor can move through hidden spaces)\n currentX += charData.width;\n }\n\n // Calculate cursor position with proper kerning support\n // Cursor position N = after character N-1, before character N\n if (currentLineChars.length > 0) {\n const firstCharIndex = currentLineChars[0].index;\n const lastCharIndex = currentLineChars[currentLineChars.length - 1].index;\n\n // Check if cursor is on this line\n // CRITICAL: Don't render cursor at lastCharIndex + 1 if the next line starts there\n // This prevents duplicate cursors at wrap boundaries\n const nextLineExists = lineIndex < wrappedLines.length - 1;\n const nextLineStartsAtEndOfThisLine = nextLineExists && lastCharIndex + 1 === charIndex;\n\n const maxAllowedCursor = nextLineStartsAtEndOfThisLine ? lastCharIndex : lastCharIndex + 1;\n\n if (cursorPosition >= firstCharIndex && cursorPosition <= maxAllowedCursor) {\n if (cursorPosition === firstCharIndex) {\n // Cursor before first character on this line\n cursorX = lineX;\n cursorY = currentY;\n cursorHeight = lineBoxHeight;\n } else {\n // Cursor after some character - sum per-character widths up to cursor\n // Apply space layout rules to match text rendering\n let visibleTextWidth = 0;\n for (let k = 0; k < currentLineChars.length && currentLineChars[k].index < cursorPosition; k++) {\n const c = currentLineChars[k];\n const isSpace = c.char === ' ';\n let isVisible = true;\n if (isSpace) {\n const isLeadingSpace = k < leadingSpacesCount;\n const isTrailingSpace = k >= currentLineChars.length - trailingSpacesCount;\n const leadingHidden = !isParagraphStart && leadingSpacesCount > 0;\n const trailingHidden = !isParagraphEnd && trailingSpacesCount > 0;\n isVisible = !((isLeadingSpace && leadingHidden) || (isTrailingSpace && trailingHidden));\n }\n if (isVisible) {\n visibleTextWidth += c.width;\n }\n }\n\n cursorX = lineX + visibleTextWidth;\n cursorY = currentY;\n cursorHeight = lineBoxHeight;\n }\n }\n }\n\n // Advance to next line (match renderRichTextFillOnly exactly)\n if (lineIndex < wrappedLines.length - 1) {\n // Cumulative advancement: currentY += spacing\n currentY += lineSpacing;\n }\n }\n });\n\n // Render selection highlight (using kerning-aware positioning to match cursor)\n const hasSelection = selectionStart !== selectionEnd;\n\n // Track selection handle positions for iOS-style handles\n let startHandlePos: { x: number; y: number } | null = null;\n let endHandlePos: { x: number; y: number } | null = null;\n\n if (hasSelection) {\n const minSel = Math.min(selectionStart, selectionEnd);\n const maxSel = Math.max(selectionStart, selectionEnd);\n\n ctx.save();\n ctx.fillStyle = getThemeTextSelectionColor(ctx.canvas);\n\n // Re-process wrapped lines to render selection with proper kerning\n let selCharIndex = 0;\n let selCurrentY = -totalHeight / 2;\n\n wrappedLines.forEach((lineSpans, lineIndex) => {\n const isParagraphStart = paragraphMetadata[lineIndex].isParagraphStart;\n const isParagraphEnd = paragraphMetadata[lineIndex].isParagraphEnd;\n const isFirstLine = lineIndex === 0;\n const isLastLine = lineIndex === wrappedLines.length - 1;\n\n // Build line characters with per-character widths and styles\n const lineChars: Array<{ char: string; index: number; width: number; style: CharacterStyle }> = [];\n lineSpans.forEach((span) => {\n Array.from(span.text).forEach((char) => {\n // Get per-character style\n const charStyle = span.style || {};\n const charFontSize = charStyle.fontSize !== undefined ? charStyle.fontSize : fontSize;\n const charFontFamily = charStyle.fontFamily !== undefined ? charStyle.fontFamily : fontFamily;\n const charBold = charStyle.bold !== undefined ? charStyle.bold : bold;\n const charItalic = charStyle.italic !== undefined ? charStyle.italic : italic;\n\n // Measure character width with its specific font\n ctx.save();\n const fontWeight = charBold ? 'bold' : 'normal';\n const fontStyleStr = charItalic ? 'italic' : 'normal';\n ctx.font = `${fontStyleStr} ${fontWeight} ${charFontSize}px \"${charFontFamily}\", Arial, sans-serif`;\n const charWidth = ctx.measureText(char).width;\n ctx.restore();\n\n lineChars.push({ char, index: selCharIndex, width: charWidth, style: charStyle });\n selCharIndex++;\n });\n });\n\n if (lineChars.length > 0) {\n const firstCharIndex = lineChars[0].index;\n const lastCharIndex = lineChars[lineChars.length - 1].index;\n\n // Check if this line has any selected characters\n if (maxSel > firstCharIndex && minSel <= lastCharIndex) {\n ctx.save();\n\n const fullLineText = lineChars.map((c) => c.char).join('');\n\n // Calculate leading/trailing spaces\n const leadingSpacesCount = fullLineText.match(/^ +/)?.[0].length || 0;\n const trailingSpacesCount = fullLineText.match(/ +$/)?.[0].length || 0;\n\n // Calculate line width using per-character widths (respecting space layout rules)\n let lineWidth = 0;\n for (let i = 0; i < lineChars.length; i++) {\n // Skip leading spaces if not first line\n if (!isFirstLine && i < leadingSpacesCount) continue;\n // Skip trailing spaces if not last line\n if (!isLastLine && i >= lineChars.length - trailingSpacesCount) continue;\n lineWidth += lineChars[i].width;\n }\n\n let lineX = 0;\n if (textAlign === 'center') {\n lineX = -lineWidth / 2;\n } else if (textAlign === 'right') {\n lineX = -lineWidth;\n }\n\n // Calculate selection boundaries on this line\n const lineSelStart = Math.max(minSel, firstCharIndex);\n const lineSelEnd = Math.min(maxSel, lastCharIndex + 1);\n\n // Measure width up to selection start using per-character widths\n let xBeforeSelection = 0;\n for (let i = 0; i < lineChars.length && lineChars[i].index < lineSelStart; i++) {\n const c = lineChars[i];\n const isLeadingSpace = i < leadingSpacesCount;\n const isTrailingSpace = i >= lineChars.length - trailingSpacesCount;\n const leadingHidden = !isParagraphStart && leadingSpacesCount > 0;\n const trailingHidden = !isParagraphEnd && trailingSpacesCount > 0;\n const isVisible = !((isLeadingSpace && leadingHidden) || (isTrailingSpace && trailingHidden));\n if (isVisible) {\n xBeforeSelection += c.width;\n }\n }\n\n // Measure width up to selection end using per-character widths\n let xBeforeSelectionEnd = 0;\n for (let i = 0; i < lineChars.length && lineChars[i].index < lineSelEnd; i++) {\n const c = lineChars[i];\n const isLeadingSpace = i < leadingSpacesCount;\n const isTrailingSpace = i >= lineChars.length - trailingSpacesCount;\n const leadingHidden = !isParagraphStart && leadingSpacesCount > 0;\n const trailingHidden = !isParagraphEnd && trailingSpacesCount > 0;\n const isVisible = !((isLeadingSpace && leadingHidden) || (isTrailingSpace && trailingHidden));\n if (isVisible) {\n xBeforeSelectionEnd += c.width;\n }\n }\n\n // Draw selection rectangle\n const selectionX = lineX + xBeforeSelection;\n const selectionWidth = xBeforeSelectionEnd - xBeforeSelection;\n ctx.fillRect(selectionX, selCurrentY, selectionWidth, lineBoxHeight);\n\n // Track handle positions (first occurrence becomes start, last becomes end)\n if (startHandlePos === null) {\n // First line with selection - start handle at top-left\n startHandlePos = { x: selectionX, y: selCurrentY };\n }\n // Always update end handle - last line with selection, at bottom-right\n endHandlePos = { x: selectionX + selectionWidth, y: selCurrentY + lineBoxHeight };\n\n ctx.restore();\n }\n }\n\n // Advance to next line\n if (lineIndex < wrappedLines.length - 1) {\n selCurrentY += lineSpacing;\n }\n });\n ctx.restore();\n\n // Re-render text ON TOP of selection so text isn't tinted\n // This ensures selection appears BEHIND the text characters\n if (editRichText) {\n ctx.save();\n // Render the text content again (it will draw on top of selection)\n const customData = textElement.transformType === 'custom' ? textElement.transformData as CustomTransformData : null;\n const maxWidth = customData?.width\n ? customData.width - HORIZONTAL_PADDING * 2\n : undefined;\n renderRichTextFillOnly(ctx, editRichText, {\n fontSize,\n fontFamily,\n color: textElement.color || '#000000',\n bold,\n italic,\n underline,\n strikethrough,\n textAlign,\n }, maxWidth);\n ctx.restore();\n }\n }\n\n // Render iOS-style selection handles for touch devices\n if (hasSelection && isTouchDevice && startHandlePos && endHandlePos) {\n // Type assertions needed because TypeScript doesn't track assignments across if blocks\n const startPos = startHandlePos as { x: number; y: number };\n const endPos = endHandlePos as { x: number; y: number };\n\n // ZOOM-INVARIANT sizing: divide by zoom to maintain constant screen-space size\n // Visual handle size - keep small and elegant\n const handleRadius = 6 / currentZoom; // ~6px on screen (small circle)\n const stemWidth = 2 / currentZoom; // ~2px on screen\n\n ctx.save();\n const accentColor = getThemeAccentColor(ctx.canvas);\n ctx.fillStyle = accentColor;\n ctx.strokeStyle = accentColor;\n ctx.lineWidth = stemWidth;\n ctx.lineCap = 'round';\n\n // Calculate handle center positions (where the circles are drawn)\n // Circle at top of start bookend (above the vertical line)\n const startHandleCenter = { x: startPos.x, y: startPos.y - handleRadius };\n // Circle at bottom of end bookend (below the vertical line)\n const endHandleCenter = { x: endPos.x, y: endPos.y + handleRadius };\n\n // Start handle (circle ABOVE selection, at top-left)\n // Vertical line spans full text height (top to bottom)\n ctx.beginPath();\n ctx.moveTo(startPos.x, startPos.y);\n ctx.lineTo(startPos.x, startPos.y + lineBoxHeight);\n ctx.stroke();\n // Circle at top of stem (with white border for visibility)\n ctx.beginPath();\n ctx.arc(startHandleCenter.x, startHandleCenter.y, handleRadius, 0, Math.PI * 2);\n ctx.fill();\n ctx.strokeStyle = 'white';\n ctx.lineWidth = 2 / currentZoom;\n ctx.stroke();\n\n // End handle (circle BELOW selection, at bottom-right)\n // Vertical line spans full text height (top to bottom)\n ctx.strokeStyle = accentColor;\n ctx.lineWidth = stemWidth;\n ctx.beginPath();\n ctx.moveTo(endPos.x, endPos.y - lineBoxHeight);\n ctx.lineTo(endPos.x, endPos.y);\n ctx.stroke();\n // Circle at bottom of stem (with white border for visibility)\n ctx.beginPath();\n ctx.arc(endHandleCenter.x, endHandleCenter.y, handleRadius, 0, Math.PI * 2);\n ctx.fill();\n ctx.strokeStyle = 'white';\n ctx.lineWidth = 2 / currentZoom;\n ctx.stroke();\n\n ctx.restore();\n\n // Store handle positions for hit testing (in element-local coordinates)\n // Includes both handle center (for hit testing) and text edge (for coordinate offset during drag)\n if (selectionHandlePositionsRef) {\n selectionHandlePositionsRef.current = {\n start: {\n center: startHandleCenter,\n textEdge: startPos, // Original selection edge (top-left of selection on first line)\n },\n end: {\n center: endHandleCenter,\n textEdge: endPos, // Original selection edge (bottom-right of selection on last line)\n },\n };\n }\n } else if (selectionHandlePositionsRef) {\n // Clear handle positions when no selection or not touch device\n selectionHandlePositionsRef.current = { start: null, end: null };\n }\n\n // Render cursor (blinking vertical line) - only if no selection\n // Use zoom-invariant line width so cursor appears same size at all zoom levels\n if (!hasSelection && cursorOpacity > 0) {\n ctx.save();\n ctx.globalAlpha = cursorOpacity;\n ctx.strokeStyle = getThemeAccentColor(ctx.canvas);\n ctx.lineWidth = 3 / currentZoom; // Zoom-invariant: always 3px on screen\n ctx.lineCap = 'round'; // Rounded ends for smoother appearance\n ctx.beginPath();\n ctx.moveTo(cursorX, cursorY);\n ctx.lineTo(cursorX, cursorY + cursorHeight);\n ctx.stroke();\n ctx.restore();\n }\n\n ctx.restore();\n }\n\n /**\n * Render edit mode features for transformed text (circle, wave, arch, etc.)\n * Renders cursor along the transform path\n */\n private static renderTransformEditModeLayer(\n ctx: CanvasRenderingContext2D,\n element: TextElement,\n editModeData: {\n cursorPosition: number;\n selectionStart: number;\n selectionEnd: number;\n editText: string;\n editRichText: RichText | null;\n cursorOpacity: number;\n },\n zoom: number = 1.0\n ): void {\n const { cursorPosition, cursorOpacity } = editModeData;\n const fontSize = element.fontSize || 24;\n const fontFamily = element.fontFamily || 'Arial';\n const bold = element.bold || false;\n const italic = element.italic || false;\n\n ctx.save();\n\n // Apply element's transform (position and rotation)\n ctx.translate(element.x, element.y);\n ctx.rotate((-element.rotation * Math.PI) / 180);\n\n // Set up font to measure characters\n const weight = bold ? 'bold' : 'normal';\n const style = italic ? 'italic' : 'normal';\n ctx.font = `${style} ${weight} ${fontSize}px ${fontFamily}`;\n\n // Get character positions along the transform path\n const chars = Array.from(element.text);\n const charWidths = chars.map((char) => ctx.measureText(char).width);\n const positions = HoverRenderer.calculateCharPositions(element, chars, charWidths);\n\n // Find cursor position\n if (cursorPosition >= 0 && cursorPosition <= chars.length && cursorOpacity > 0) {\n let cursorPos: { x: number; y: number; angle: number };\n\n if (cursorPosition === 0) {\n // Cursor before first character\n if (positions.length > 0) {\n const firstPos = positions[0];\n const charWidth = charWidths[0];\n // Position cursor half a character width before the first character\n cursorPos = {\n x: firstPos.x - Math.cos(firstPos.angle) * (charWidth / 2),\n y: firstPos.y - Math.sin(firstPos.angle) * (charWidth / 2),\n angle: firstPos.angle,\n };\n } else {\n // No characters, cursor at origin\n cursorPos = { x: 0, y: 0, angle: 0 };\n }\n } else if (cursorPosition >= positions.length) {\n // Cursor after last character\n const lastPos = positions[positions.length - 1];\n const lastCharWidth = charWidths[charWidths.length - 1];\n cursorPos = {\n x: lastPos.x + Math.cos(lastPos.angle) * (lastCharWidth / 2),\n y: lastPos.y + Math.sin(lastPos.angle) * (lastCharWidth / 2),\n angle: lastPos.angle,\n };\n } else {\n // Cursor between characters - use position of character at cursor\n const charPos = positions[cursorPosition];\n const charWidth = charWidths[cursorPosition];\n cursorPos = {\n x: charPos.x - Math.cos(charPos.angle) * (charWidth / 2),\n y: charPos.y - Math.sin(charPos.angle) * (charWidth / 2),\n angle: charPos.angle,\n };\n }\n\n // Draw cursor at calculated position with rotation\n // Use zoom-invariant line width so cursor appears same size at all zoom levels\n ctx.save();\n ctx.translate(cursorPos.x, cursorPos.y);\n ctx.rotate(cursorPos.angle);\n\n ctx.globalAlpha = cursorOpacity;\n ctx.strokeStyle = getThemeAccentColor(ctx.canvas);\n ctx.lineWidth = 2 / zoom; // Zoom-invariant: always 2px on screen\n\n const cursorHeight = fontSize * 1.2;\n ctx.beginPath();\n ctx.moveTo(0, -cursorHeight / 2);\n ctx.lineTo(0, cursorHeight / 2);\n ctx.stroke();\n\n ctx.restore();\n }\n\n ctx.restore();\n }\n\n /**\n * Render crop box layer (for images in crop mode)\n * Shows both image resize handles and crop handles\n */\n static renderCropBoxLayer(\n ctx: CanvasRenderingContext2D,\n selectedElement: BaseElement,\n transformHandles: TransformHandles,\n zoom: number = 1.0\n ): void {\n if (!(selectedElement instanceof ImageElement) || !selectedElement.isCropping) return;\n const imgElement = selectedElement as ImageElement;\n\n const cropBox = imgElement.getCropBoxBounds();\n if (!cropBox) return;\n\n // Crop handle style constants - divide by zoom to maintain consistent screen-space size\n // Since we apply ctx.scale(zoom) below, these need to be in world-space (larger when zoomed out)\n const EDGE_HANDLE_THICKNESS = 4 / zoom; // Thickness of edge rectangles\n const CROP_BOX_LINE_WIDTH = 2 / zoom; // Width of crop box outline\n const CORNER_HANDLE_RADIUS_SCALED = CORNER_HANDLE_VISUAL_RADIUS / zoom; // Circular corner radius (visual)\n const EDGE_HANDLE_LENGTH_SCALED = EDGE_HANDLE_VISUAL_LENGTH / zoom; // Edge handle length (visual)\n const GRID_LINE_WIDTH = 1 / zoom; // Grid line width\n\n ctx.save();\n\n // Transform to the visible area center (this.x, this.y), scaled by zoom\n ctx.translate(selectedElement.x * zoom, selectedElement.y * zoom);\n ctx.rotate(RotationUtils.toRadians(selectedElement.rotation));\n\n // Apply flip transformations AND zoom to match where the image is actually rendered\n // This is necessary so the dark overlay and handles align with the flipped image\n const flipH = imgElement.transformData.flipHorizontal ? -1 : 1;\n const flipV = imgElement.transformData.flipVertical ? -1 : 1;\n ctx.scale(flipH * zoom, flipV * zoom);\n\n // Calculate where the full image frame is relative to the visible crop center\n const cropWidth = imgElement.transformData.width * imgElement.transformData.cropWidth;\n const cropHeight = imgElement.transformData.height * imgElement.transformData.cropHeight;\n\n // Offset from crop center to frame center in local space\n const localOffsetX =\n imgElement.transformData.width / 2 -\n (imgElement.transformData.cropX * imgElement.transformData.width + cropWidth / 2);\n const localOffsetY =\n imgElement.transformData.height / 2 -\n (imgElement.transformData.cropY * imgElement.transformData.height + cropHeight / 2);\n\n // Image bounds in local coordinates (full frame)\n const halfWidth = imgElement.transformData.width / 2;\n const halfHeight = imgElement.transformData.height / 2;\n const imageBounds = {\n x: localOffsetX - halfWidth,\n y: localOffsetY - halfHeight,\n width: imgElement.transformData.width,\n height: imgElement.transformData.height,\n };\n\n // Darken area outside crop box\n ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';\n\n // Top\n if (cropBox.y > imageBounds.y) {\n ctx.fillRect(imageBounds.x, imageBounds.y, imageBounds.width, cropBox.y - imageBounds.y);\n }\n\n // Bottom\n const cropBottom = cropBox.y + cropBox.height;\n const imageBottom = imageBounds.y + imageBounds.height;\n if (cropBottom < imageBottom) {\n ctx.fillRect(imageBounds.x, cropBottom, imageBounds.width, imageBottom - cropBottom);\n }\n\n // Left\n if (cropBox.x > imageBounds.x) {\n ctx.fillRect(imageBounds.x, cropBox.y, cropBox.x - imageBounds.x, cropBox.height);\n }\n\n // Right\n const cropRight = cropBox.x + cropBox.width;\n const imageRight = imageBounds.x + imageBounds.width;\n if (cropRight < imageRight) {\n ctx.fillRect(cropRight, cropBox.y, imageRight - cropRight, cropBox.height);\n }\n\n // Draw crop box outline with the accent token, resolved against the canvas's\n // scope so .sc-root overrides apply (ADR-0085 §5 class-2 theming)\n ctx.strokeStyle = getThemeAccentColor(ctx.canvas);\n ctx.lineWidth = CROP_BOX_LINE_WIDTH;\n ctx.strokeRect(cropBox.x, cropBox.y, cropBox.width, cropBox.height);\n\n // Draw grid lines (rule of thirds)\n ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';\n ctx.lineWidth = GRID_LINE_WIDTH;\n\n // Vertical lines\n const thirdWidth = cropBox.width / 3;\n ctx.beginPath();\n ctx.moveTo(cropBox.x + thirdWidth, cropBox.y);\n ctx.lineTo(cropBox.x + thirdWidth, cropBox.y + cropBox.height);\n ctx.moveTo(cropBox.x + 2 * thirdWidth, cropBox.y);\n ctx.lineTo(cropBox.x + 2 * thirdWidth, cropBox.y + cropBox.height);\n ctx.stroke();\n\n // Horizontal lines\n const thirdHeight = cropBox.height / 3;\n ctx.beginPath();\n ctx.moveTo(cropBox.x, cropBox.y + thirdHeight);\n ctx.lineTo(cropBox.x + cropBox.width, cropBox.y + thirdHeight);\n ctx.moveTo(cropBox.x, cropBox.y + 2 * thirdHeight);\n ctx.lineTo(cropBox.x + cropBox.width, cropBox.y + 2 * thirdHeight);\n ctx.stroke();\n\n // Draw corner and edge handles for crop box\n\n // Corner handles (circular with white fill and gray border)\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 + cropBox.width, y: cropBox.y + cropBox.height }, // bottom-right\n { x: cropBox.x, y: cropBox.y + cropBox.height }, // bottom-left\n ];\n\n corners.forEach((corner) => {\n // Draw gray border\n ctx.beginPath();\n ctx.arc(corner.x, corner.y, CORNER_HANDLE_RADIUS_SCALED + 1 / zoom, 0, Math.PI * 2);\n ctx.fillStyle = '#bbb';\n ctx.fill();\n\n // Draw white fill\n ctx.beginPath();\n ctx.arc(corner.x, corner.y, CORNER_HANDLE_RADIUS_SCALED, 0, Math.PI * 2);\n ctx.fillStyle = '#fff';\n ctx.fill();\n });\n\n // Edge handles (rectangles - wide for top/bottom, tall for left/right at crop box edges)\n const edges = [\n { x: cropBox.x + cropBox.width / 2, y: cropBox.y, orientation: 'horizontal' }, // top\n { x: cropBox.x + cropBox.width / 2, y: cropBox.y + cropBox.height, orientation: 'horizontal' }, // bottom\n { x: cropBox.x, y: cropBox.y + cropBox.height / 2, orientation: 'vertical' }, // left\n { x: cropBox.x + cropBox.width, y: cropBox.y + cropBox.height / 2, orientation: 'vertical' }, // right\n ];\n\n edges.forEach((edge) => {\n let width, height;\n if (edge.orientation === 'horizontal') {\n // Top/bottom: wide rectangles\n width = EDGE_HANDLE_LENGTH_SCALED;\n height = EDGE_HANDLE_THICKNESS;\n } else {\n // Left/right: tall rectangles\n width = EDGE_HANDLE_THICKNESS;\n height = EDGE_HANDLE_LENGTH_SCALED;\n }\n\n const x = edge.x - width / 2;\n const y = edge.y - height / 2;\n\n // Fill with white (sharp corners)\n ctx.fillStyle = '#fff';\n ctx.fillRect(x, y, width, height);\n\n // Stroke with gray border\n ctx.strokeStyle = '#bbb';\n ctx.lineWidth = GRID_LINE_WIDTH;\n ctx.strokeRect(x, y, width, height);\n });\n\n ctx.restore();\n\n // Render image resize handles (white circles) LAST in world coordinates\n // These are drawn last so they appear on top of everything\n // NOTE: We render these OUTSIDE the transformed context (after restore)\n // so we use world coordinates directly with zoom applied\n if (transformHandles && transformHandles.resizeHandles) {\n // Only render corner handles for images\n const cornerHandles = transformHandles.resizeHandles.filter(\n (h) =>\n h.anchor === 'top-left' ||\n h.anchor === 'top-right' ||\n h.anchor === 'bottom-left' ||\n h.anchor === 'bottom-right'\n );\n\n // Get crop box corners in world coordinates to check for overlap\n const cropCorners = imgElement.getCropBoxWorldCorners();\n\n // Match the exact style from TransformHandles.js\n const handleRadius = 6; // HANDLE_SIZE / 2 = 12 / 2 = 6\n const minDistance = 20; // Hide image handle if crop handle is within 20px\n\n cornerHandles.forEach((handle) => {\n // Check if any crop corner is too close to this image corner\n let tooClose = false;\n if (cropCorners) {\n for (const cropCorner of cropCorners) {\n const dx = handle.x - cropCorner.x;\n const dy = handle.y - cropCorner.y;\n const distance = Math.sqrt(dx * dx + dy * dy);\n if (distance < minDistance) {\n tooClose = true;\n break;\n }\n }\n }\n\n // Only render if not too close to a crop handle\n if (!tooClose) {\n ctx.save();\n // Ensure fully opaque rendering\n ctx.globalAlpha = 1.0;\n ctx.beginPath();\n // Apply zoom to convert world coordinates to screen coordinates\n ctx.arc(handle.x * zoom, handle.y * zoom, handleRadius, 0, 2 * Math.PI);\n // Match the exact style from TransformHandles - white fill with theme accent border\n ctx.fillStyle = '#fff';\n ctx.fill();\n ctx.strokeStyle = getThemeAccentColor(ctx.canvas);\n ctx.lineWidth = 2;\n ctx.stroke();\n ctx.restore();\n }\n });\n }\n }\n}\n","/**\n * CanvasRenderer - Layered canvas rendering system (facade)\n *\n * Main entry point for canvas rendering. Delegates to focused renderer modules:\n * - SelectionRenderer: Selection boxes, transform handles, resize info\n * - HoverRenderer: Hover outlines, ring effects, character position calculations\n * - InteractionFeedbackRenderer: Snap guides, spacing indicators, distance labels\n * - EditModeRenderer: Text cursor, selection highlighting, crop UI\n *\n * This file retains:\n * - Element layer rendering (knockout, clipping, overflow dimming)\n * - The main render() orchestration method\n * - All public static method signatures (delegating to sub-renderers)\n *\n * ============================================================================\n * IMPORTANT: THREE SEPARATE VISUAL FEEDBACK SYSTEMS (DO NOT CONFUSE!)\n * ============================================================================\n *\n * 1. **HOVER RING EFFECT** (HoverRenderer.ts)\n * - Colored ring/outline that appears when hovering over text elements\n * - Rendered by: HoverRenderer.renderHoverLayer()\n * - Uses: Offscreen canvas compositing with gap effect\n * - Example: Hover over \" Foo Bar\" text to see cyan/accent colored ring\n *\n * 2. **ELEMENT STROKE EFFECT** (StrokeRenderer.ts)\n * - Actual stroke property on elements (element.stroke.enabled = true)\n * - Rendered by: StrokeRenderer.renderTextStroke()\n * - Location: src/rendering/StrokeRenderer.ts\n * - Only renders when user explicitly enables stroke in effects panel\n * - Example: Text with a black outline configured in effects panel\n *\n * 3. **SELECTION BOX** (Transform classes)\n * - Dashed rectangle around selected/hovered elements\n * - Rendered by: element.render(ctx, isSelected, isHovered) in each transform class\n * - Location: CustomTransform.ts, CircleTransform.ts, etc.\n * - Shows bounding box of element\n * - Example: Dashed green rectangle when you select text\n *\n * These are COMPLETELY SEPARATE systems. During the \" Foo Bar\" bug fix,\n * we fixed #1 (hover ring effect), NOT #2 (element stroke).\n */\n\n// Element painting goes through the paint registry (P1 element/renderer\n// split); installing the painters here guarantees they are registered before\n// any element.render() this module drives. The EXPLICIT top-level call (not a\n// bare side-effect import) is load-bearing: bundlers honoring the package's\n// `sideEffects` allowlist prune bare imports of modules outside it, and both\n// webpack (Next dev) and the Vite lib build dropped the registration that way\n// — shipping an editor that painted shapes but no text.\nimport { installElementPainters } from '../rendering/element-painters.js';\ninstallElementPainters();\nimport {\n PREVIEW_ELEMENT_OPACITY,\n} from '../constants.js';\nimport type { BaseElement } from './BaseElement.js';\nimport type { TransformHandles } from './TransformHandles.js';\nimport type { InteractionStateMachine, ActiveChildContext } from './InteractionStateMachine.js';\nimport { isGroupElement, isImageElement } from '../types/guards.js';\nimport { RichText } from '../types/index.js';\nimport type { SnapGuide, SpacingIndicator, AnyElementConfig, ArtboardDistressTexture, CanvasError, ClipShape } from '../types/index.js';\nimport { renderWithCompositing } from '../rendering/CompositingRenderer.js';\nimport type { CompositeOp } from '../rendering/CompositingRenderer.js';\nimport { renderWithMasks } from '../rendering/MaskRenderer.js';\nimport { applyArtboardDistressTexture, applyElementDistressTexture } from '../rendering/DistressTextureRenderer.js';\nimport { applyArtboardImageMask } from '../rendering/MaskRenderer.js';\nimport { createLogger } from '../utils/logger.js';\n\n// Focused renderer modules\nimport { SelectionRenderer } from './SelectionRenderer.js';\nimport { HoverRenderer } from './HoverRenderer.js';\nimport { InteractionFeedbackRenderer } from './InteractionFeedbackRenderer.js';\nimport { EditModeRenderer } from './EditModeRenderer.js';\n\nconst renderLogger = createLogger('CanvasRenderer');\n\ninterface HoverState {\n type: 'element' | 'resize-handle' | 'rotation-handle' | null;\n data?: BaseElement | null;\n}\n\nexport class CanvasRenderer {\n // =========================================================================\n // Pooled Offscreen Canvases (avoid per-frame allocation)\n // =========================================================================\n\n /** Pooled offscreen canvas for clipping content rendering */\n private static _contentCanvas: HTMLCanvasElement | null = null;\n private static _contentCanvasSize: { width: number; height: number } = { width: 0, height: 0 };\n\n /** Pooled offscreen canvas for clipping mask rendering */\n private static _maskCanvas: HTMLCanvasElement | null = null;\n private static _maskCanvasSize: { width: number; height: number } = { width: 0, height: 0 };\n\n /**\n * Get a pooled offscreen canvas for clipping content, only recreating\n * when dimensions change. Eliminates per-frame canvas allocation.\n */\n private static _getContentCanvas(width: number, height: number): HTMLCanvasElement {\n if (\n !this._contentCanvas ||\n this._contentCanvasSize.width !== width ||\n this._contentCanvasSize.height !== height\n ) {\n this._contentCanvas = document.createElement('canvas');\n this._contentCanvas.width = width;\n this._contentCanvas.height = height;\n this._contentCanvasSize = { width, height };\n }\n return this._contentCanvas;\n }\n\n /**\n * Get a pooled offscreen canvas for clipping masks, only recreating\n * when dimensions change. Eliminates per-frame canvas allocation.\n */\n private static _getMaskCanvas(width: number, height: number): HTMLCanvasElement {\n if (\n !this._maskCanvas ||\n this._maskCanvasSize.width !== width ||\n this._maskCanvasSize.height !== height\n ) {\n this._maskCanvas = document.createElement('canvas');\n this._maskCanvas.width = width;\n this._maskCanvas.height = height;\n this._maskCanvasSize = { width, height };\n }\n return this._maskCanvas;\n }\n\n // =========================================================================\n // Per-frame allocation cache (avoid toJSON() / Map / Set on every frame)\n // =========================================================================\n\n /**\n * Cached element JSON configs keyed on the elements array reference.\n * toJSON() calls are expensive at 60fps with 20+ elements.\n * Safe to cache because knockout/mask metadata (id, blendMode, knockoutParts, masks)\n * only changes on React state updates, which always produce a new array reference.\n * Interactive operations (drag, resize) mutate element position in-place on the\n * same instances without changing the array reference.\n */\n private static _cachedElementsRef: BaseElement[] | null = null;\n private static _cachedElementConfigs: AnyElementConfig[] | null = null;\n private static _cachedElementMap: Map<string, BaseElement> | null = null;\n\n /** Reusable Set for tracking knockout-rendered IDs — avoids per-frame allocation. */\n private static readonly _knockoutRenderedIds: Set<string> = new Set<string>();\n /**\n * Optional callback for render errors. Set by SnowconeCanvas to forward\n * rendering errors to the onError prop. When set, individual element render\n * failures are caught and reported instead of crashing the entire canvas.\n */\n static onRenderError: ((error: CanvasError) => void) | null = null;\n\n /**\n * Safely render an element, catching errors so one element's failure\n * doesn't crash the entire canvas render loop.\n */\n private static safeRender(\n ctx: CanvasRenderingContext2D,\n element: BaseElement,\n isSelected: boolean,\n isHovered: boolean,\n ): void {\n try {\n element.render(ctx, isSelected, isHovered);\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n renderLogger.error(`Failed to render element ${element.id}:`, err);\n if (CanvasRenderer.onRenderError) {\n CanvasRenderer.onRenderError({\n category: 'render',\n message: `Failed to render element: ${err.message}`,\n originalError: err,\n elementId: element.id,\n recoverable: true,\n });\n }\n }\n }\n\n // =========================================================================\n // Element Layer Rendering (kept here - tightly coupled knockout/clipping)\n // =========================================================================\n\n /**\n * Helper function to render an element (using actual element instance, not config)\n * Used by knockout rendering system\n */\n private static renderElement(\n ctx: CanvasRenderingContext2D,\n element: BaseElement,\n isPreview: boolean = true\n ): void {\n // Apply subtle opacity for preview mode\n if (isPreview) {\n ctx.save();\n ctx.globalAlpha = PREVIEW_ELEMENT_OPACITY;\n }\n\n // Render the element (no selection/hover highlights for knockout rendering)\n element.render(ctx, false, false);\n\n if (isPreview) {\n ctx.restore();\n }\n }\n\n /**\n * Apply artboard clip path to the context, respecting clipShape.\n * After calling this, subsequent drawing will be clipped to the artboard shape.\n * Caller must call ctx.beginPath() before this if needed.\n */\n private static applyArtboardClipPath(\n ctx: CanvasRenderingContext2D,\n artboard: { x: number; y: number; width: number; height: number; borderRadius?: number; clipShape?: ClipShape }\n ): void {\n const { x, y, width, height, clipShape } = artboard;\n const borderRadius = artboard.borderRadius || 0;\n\n ctx.beginPath();\n\n if (!clipShape || clipShape === 'rectangle') {\n if (borderRadius > 0) {\n ctx.roundRect(x, y, width, height, borderRadius);\n } else {\n ctx.rect(x, y, width, height);\n }\n } else if (clipShape === 'circle') {\n const centerX = x + width / 2;\n const centerY = y + height / 2;\n const radius = Math.min(width, height) / 2;\n ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);\n } else if (typeof clipShape === 'object' && clipShape.type === 'rounded') {\n const radius = Math.min(clipShape.radius, Math.min(width, height) / 2);\n ctx.roundRect(x, y, width, height, radius);\n } else if (typeof clipShape === 'object' && clipShape.type === 'path') {\n try {\n const path = new Path2D(clipShape.d);\n ctx.translate(x, y);\n ctx.clip(path);\n ctx.translate(-x, -y);\n return; // Path2D handles clip directly\n } catch (e) {\n // Invalid path - fall back to rectangle\n ctx.rect(x, y, width, height);\n }\n } else if (typeof clipShape === 'object' && clipShape.type === 'composite-path') {\n // ADR-0054 multi-piece spreads: union of piece-local boundary paths,\n // each translated (and optionally 90°/180°/270° rotated) into the\n // artboard frame. Path2D.addPath(path, DOMMatrix) composes; Canvas's\n // clip() respects the resulting compound region.\n try {\n const composite = new Path2D();\n for (const piece of clipShape.pieces) {\n const sub = new Path2D(piece.d);\n const rot = piece.rotation ?? 0;\n const matrix = new DOMMatrix();\n matrix.translateSelf(piece.x, piece.y);\n if (rot !== 0) {\n // Rotate around the piece's *unrotated* center so the\n // post-rotation bbox lands flush at (piece.x, piece.y).\n const cx = (piece.baseWidth ?? 0) / 2;\n const cy = (piece.baseHeight ?? 0) / 2;\n // After rotation, the piece's visual bbox top-left can move;\n // compensate so rotated piece sits at (piece.x, piece.y).\n const swap = rot === 90 || rot === 270;\n const visW = swap ? (piece.baseHeight ?? 0) : (piece.baseWidth ?? 0);\n const visH = swap ? (piece.baseWidth ?? 0) : (piece.baseHeight ?? 0);\n matrix.translateSelf(visW / 2, visH / 2);\n matrix.rotateSelf(rot);\n matrix.translateSelf(-cx, -cy);\n }\n composite.addPath(sub, matrix);\n }\n ctx.translate(x, y);\n ctx.clip(composite);\n ctx.translate(-x, -y);\n return;\n } catch (e) {\n ctx.rect(x, y, width, height);\n }\n }\n\n ctx.clip();\n }\n\n /**\n * Render an element with overflow dimming - parts outside the artboard render at 30% opacity\n * Uses two-pass rendering: first at 30% opacity (overflow), then clipped to artboard at 100% (inside)\n */\n private static renderElementWithOverflowDimming(\n ctx: CanvasRenderingContext2D,\n element: BaseElement,\n artboard: { x: number; y: number; width: number; height: number; borderRadius?: number; clipShape?: ClipShape },\n isSelected: boolean,\n isHovered: boolean,\n isPreview: boolean\n ): void {\n // Save the current transform for later restoration\n const savedTransform = ctx.getTransform();\n const borderRadius = artboard.borderRadius || 0;\n\n // Pass 1: Render at 30% opacity with blur ONLY in overflow area (outside artboard)\n // Use an inverted clip to render only outside the artboard bounds\n ctx.save();\n ctx.globalAlpha = 0.3 * (isPreview ? PREVIEW_ELEMENT_OPACITY : 1.0);\n // `blur(4px)` is intentionally unscaled by devicePixelRatio so it\n // matches PieceGuideRenderer's die-cut backdrop blur (also 4px,\n // unscaled) — the two passes should read as the same blur strength\n // on the same display. See PieceGuideRenderer.ts around `drawDieCut`\n // for the matching convention.\n ctx.filter = 'blur(4px)';\n\n // Create inverted clip: cover entire canvas, then subtract artboard\n // Using evenodd fill rule, the artboard \"hole\" will be excluded from the clip\n ctx.beginPath();\n // Outer rectangle must cover the entire VISIBLE world area (accounting for zoom/pan)\n // Get the inverse of the current transform to convert canvas coords to world coords\n // Note: Use actual canvas pixel dimensions - the transform already includes DPR scaling\n const transform = ctx.getTransform();\n const inverseTransform = transform.inverse();\n const screenWidth = ctx.canvas.width;\n const screenHeight = ctx.canvas.height;\n\n // Transform canvas corners to world coordinates\n const topLeft = new DOMPoint(0, 0).matrixTransform(inverseTransform);\n const bottomRight = new DOMPoint(screenWidth, screenHeight).matrixTransform(inverseTransform);\n\n // Add generous padding to ensure complete coverage even with slight transform errors\n const padding = Math.max(bottomRight.x - topLeft.x, bottomRight.y - topLeft.y);\n const worldMinX = topLeft.x - padding;\n const worldMinY = topLeft.y - padding;\n const worldMaxX = bottomRight.x + padding;\n const worldMaxY = bottomRight.y + padding;\n\n // Inner shape (will be \"cut out\" by evenodd) — what to *exclude*\n // from the dim halo. For ADR-0054 multi-piece spreads with\n // path-kind boundaries, this is the union of piece silhouettes\n // so the halo fills the whole viewport except inside silhouettes.\n // Without this, the area *between* silhouettes and the artboard's\n // bounding rect is left fully transparent, the parent's bg shows\n // through unmodified, and the user sees a sharp \"bright frame\"\n // around the silhouettes that contrasts with the dim halo region\n // (same parent bg + 30% artwork composite). The composite path\n // makes the whole \"outside silhouettes\" area read uniformly.\n //\n // For non-composite-path artboards, the artboard rect — rounded\n // by `borderRadius` for phone case corner wedges — is still the\n // right cutout. The historical comment chain on this block (sharp-\n // rect vs rounded) applies there.\n //\n // Build everything as a single Path2D and `ctx.clip(path,\n // 'evenodd')` so the outer rect + Path2D silhouettes can compose\n // (ctx.beginPath() + ctx.rect() + Path2D would not).\n const compositeShape =\n typeof artboard.clipShape === 'object' &&\n artboard.clipShape !== null &&\n artboard.clipShape.type === 'composite-path'\n ? artboard.clipShape\n : null;\n if (compositeShape) {\n const compoundPath = new Path2D();\n compoundPath.rect(worldMinX, worldMinY, worldMaxX - worldMinX, worldMaxY - worldMinY);\n for (const piece of compositeShape.pieces) {\n const sub = new Path2D(piece.d);\n const rot = piece.rotation ?? 0;\n const matrix = new DOMMatrix();\n matrix.translateSelf(artboard.x + piece.x, artboard.y + piece.y);\n if (rot !== 0) {\n const cx = (piece.baseWidth ?? 0) / 2;\n const cy = (piece.baseHeight ?? 0) / 2;\n const swap = rot === 90 || rot === 270;\n const visW = swap ? (piece.baseHeight ?? 0) : (piece.baseWidth ?? 0);\n const visH = swap ? (piece.baseWidth ?? 0) : (piece.baseHeight ?? 0);\n matrix.translateSelf(visW / 2, visH / 2);\n matrix.rotateSelf(rot);\n matrix.translateSelf(-cx, -cy);\n }\n compoundPath.addPath(sub, matrix);\n }\n ctx.clip(compoundPath, 'evenodd');\n } else {\n ctx.rect(worldMinX, worldMinY, worldMaxX - worldMinX, worldMaxY - worldMinY);\n if (borderRadius > 0) {\n ctx.roundRect(artboard.x, artboard.y, artboard.width, artboard.height, borderRadius);\n } else {\n ctx.rect(artboard.x, artboard.y, artboard.width, artboard.height);\n }\n ctx.clip('evenodd');\n }\n\n element.render(ctx, false, false); // Don't show selection/hover for the dimmed version\n ctx.restore();\n\n // Pass 2: Clip to artboard shape and render at 100% opacity\n ctx.save();\n this.applyArtboardClipPath(ctx, artboard);\n\n if (isPreview) {\n ctx.globalAlpha = PREVIEW_ELEMENT_OPACITY;\n }\n element.render(ctx, isSelected, isHovered);\n ctx.restore();\n\n // Restore transform (clip modifies it)\n ctx.setTransform(savedTransform);\n }\n\n /**\n * Render all elements layer\n * @param showOverflowDimmed When true, element parts outside the artboard render at 30% opacity\n * @param suppressOverflowDim When true, skip the 30% overflow pass even if `showOverflowDimmed`\n * is enabled. Used for piece-guide rendering (ADR-0054) where the boundary's custom path\n * clipShape is the visible design edge — overflow beyond the rectangular artboard reads as\n * a coloured halo rather than useful UX. Selected/hovered elements fall through to the\n * clipped-only branch which respects `clipShape` correctly.\n */\n static renderElementsLayer(\n ctx: CanvasRenderingContext2D,\n elements: BaseElement[],\n selectedId: string | null,\n hoverState?: HoverState,\n stateMachine?: InteractionStateMachine,\n artboardBackgroundColor?: string,\n artboard?: { x: number; y: number; width: number; height: number; borderRadius?: number; clipShape?: ClipShape },\n isPreview: boolean = true,\n showOverflowDimmed: boolean = false,\n suppressOverflowDim: boolean = false\n ): void {\n const isDragging = stateMachine != null && stateMachine.getMode() === 'drag';\n const activeChildContext = stateMachine?._activeChildContext;\n\n // Cache element JSON configs keyed on array reference to eliminate per-frame toJSON() calls.\n // Rebuilds only when the elements array reference changes (React state update).\n if (elements !== CanvasRenderer._cachedElementsRef) {\n CanvasRenderer._cachedElementConfigs = elements.map((e) => e.toJSON()) as AnyElementConfig[];\n CanvasRenderer._cachedElementMap = new Map(elements.map((el) => [el.id, el]));\n CanvasRenderer._cachedElementsRef = elements;\n }\n const allElementConfigs = CanvasRenderer._cachedElementConfigs!;\n const elementMap = CanvasRenderer._cachedElementMap!;\n\n // Reuse Set across frames to avoid per-frame allocation; clear before use.\n const knockoutRenderedIds = CanvasRenderer._knockoutRenderedIds;\n knockoutRenderedIds.clear();\n\n // First pass: render compositing elements (knockout and clip)\n elements.forEach((element, index) => {\n if (element.blendMode === 'knockout' || element.blendMode === 'clip') {\n const elementConfig = allElementConfigs[index];\n const compositeOp: CompositeOp = element.blendMode === 'knockout' ? 'destination-out' : 'destination-in';\n\n // Get affected elements using the same logic as CompositingRenderer\n const scope = elementConfig.knockoutParts?.scope || 'group';\n const compositingIndex = allElementConfigs.findIndex((el) => el.id === elementConfig.id);\n\n // Check if any compositing parts are actually enabled\n const hasCompositingParts = elementConfig.knockoutParts?.fill || elementConfig.knockoutParts?.stroke;\n\n // Get affected elements\n const affectedElements =\n scope === 'artboard' && compositingIndex !== -1 ? allElementConfigs.slice(0, compositingIndex) : [];\n\n // For artboard scope, use compositing even with 0 affected elements\n // This allows knockout to reveal the artboard background color\n const shouldUseCompositing = hasCompositingParts && (scope === 'artboard' || affectedElements.length > 0);\n\n if (shouldUseCompositing) {\n // Mark affected elements as rendered\n affectedElements.forEach((el) => el.id && knockoutRenderedIds.add(el.id));\n\n // Render with compositing using actual element instances\n renderWithCompositing(\n ctx,\n elementConfig as AnyElementConfig,\n allElementConfigs as AnyElementConfig[],\n (ctx, config) => {\n const actualElement = config.id && elementMap.get(config.id);\n if (actualElement) {\n this.renderElement(ctx as CanvasRenderingContext2D, actualElement, isPreview);\n }\n },\n artboardBackgroundColor,\n artboard,\n compositeOp\n );\n\n knockoutRenderedIds.add(element.id);\n }\n }\n });\n\n // Second pass: render normal (non-knockout) elements with clipping support\n // Check if any elements have clipping enabled\n const hasClipping = elements.some((el) => !knockoutRenderedIds.has(el.id) && el.isClipping);\n\n if (!hasClipping) {\n // No clipping - render normally\n elements.forEach((element) => {\n // Skip if already rendered by knockout\n if (knockoutRenderedIds.has(element.id)) return;\n\n // Skip invisible elements\n if (element.visible === false) return;\n\n const isSelected = element.id === selectedId;\n const isHovered = hoverState?.type === 'element' && hoverState?.data?.id === element.id;\n\n // Don't show group as selected when dragging a child within it (hides the group outline)\n const isDraggingChildInThisGroup =\n isDragging && activeChildContext && isGroupElement(element) && element.id === selectedId;\n const shouldShowAsSelected = isSelected && !isDraggingChildInThisGroup;\n\n // Check if element has custom texture distress effect\n const elementConfig = allElementConfigs[elements.indexOf(element)];\n const hasCustomDistress = elementConfig?.distressEffect?.enabled &&\n elementConfig.distressEffect.style === 'custom' &&\n elementConfig.distressEffect.textureUrl;\n\n // Render element with masks applied if present\n if (elementConfig && elementConfig.masks && elementConfig.masks.length > 0) {\n // Render with masks (overflow dimming not supported with masks for now)\n renderWithMasks(ctx, elementConfig as AnyElementConfig, (maskCtx) => {\n const renderContent = (renderCtx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D) => {\n if (isPreview) {\n renderCtx.save();\n (renderCtx as CanvasRenderingContext2D).globalAlpha = PREVIEW_ELEMENT_OPACITY;\n }\n this.safeRender(renderCtx as CanvasRenderingContext2D, element, shouldShowAsSelected, isHovered);\n if (isPreview) {\n (renderCtx as CanvasRenderingContext2D).restore();\n }\n };\n\n // Apply distress inside masks if element has both\n if (hasCustomDistress) {\n const bbox = element.getVisualBoundingBox();\n applyElementDistressTexture(maskCtx, elementConfig!.distressEffect!, bbox, renderContent);\n } else {\n renderContent(maskCtx);\n }\n });\n } else if (hasCustomDistress) {\n // Per-element custom texture distress\n // Use visual bounding box (actual rendered pixel extent) not math bounding box\n // (resize handles). Transforms like arch/circle/wave extend text beyond math bounds.\n const bbox = element.getVisualBoundingBox();\n applyElementDistressTexture(ctx, elementConfig!.distressEffect!, bbox, (offCtx) => {\n if (isPreview) {\n offCtx.save();\n (offCtx as CanvasRenderingContext2D).globalAlpha = PREVIEW_ELEMENT_OPACITY;\n }\n this.safeRender(offCtx as CanvasRenderingContext2D, element, shouldShowAsSelected, isHovered);\n if (isPreview) {\n offCtx.restore();\n }\n });\n } else if (showOverflowDimmed && !suppressOverflowDim && artboard && shouldShowAsSelected) {\n // Selected only: Render with overflow dimming - parts outside artboard at 30% opacity.\n // Hover used to trigger this too, but the halo on every mouseover read as noisy\n // distraction (artwork peeked past the piece edges any time the cursor moved over\n // a layer). The signal is reserved for explicit selection now — once the user has\n // committed via tap/click, they want to see how far their element extends.\n // Skipped when `suppressOverflowDim` is set (piece-guide contexts) so selected\n // elements fall through to the clipped-only branch and don't produce the 30%+blur\n // halo.\n this.renderElementWithOverflowDimming(ctx, element, artboard, shouldShowAsSelected, isHovered, isPreview);\n } else if (showOverflowDimmed && artboard) {\n // Not selected/hovered: Clip to artboard shape (hide overflow completely)\n ctx.save();\n this.applyArtboardClipPath(ctx, artboard);\n if (isPreview) {\n ctx.globalAlpha = PREVIEW_ELEMENT_OPACITY;\n }\n this.safeRender(ctx, element, shouldShowAsSelected, isHovered);\n ctx.restore();\n } else {\n // Normal rendering without masks or overflow handling\n if (isPreview) {\n ctx.save();\n ctx.globalAlpha = PREVIEW_ELEMENT_OPACITY;\n }\n this.safeRender(ctx, element, shouldShowAsSelected, isHovered);\n if (isPreview) {\n ctx.restore();\n }\n }\n });\n } else {\n // Has clipping - use offscreen canvas compositing\n this._renderWithClipping(\n ctx,\n elements,\n knockoutRenderedIds,\n selectedId,\n hoverState,\n isDragging,\n activeChildContext,\n allElementConfigs as AnyElementConfig[],\n isPreview\n );\n }\n }\n\n /**\n * Render elements with clipping mask support at artboard level\n * Elements with isClipping=true clip all elements below them\n */\n private static _renderWithClipping(\n ctx: CanvasRenderingContext2D,\n elements: BaseElement[],\n knockoutRenderedIds: Set<string>,\n selectedId: string | null,\n hoverState: HoverState | undefined,\n isDragging: boolean | undefined,\n activeChildContext: ActiveChildContext | undefined,\n allElementConfigs: AnyElementConfig[],\n isPreview: boolean = true\n ): void {\n const canvas = ctx.canvas;\n const width = canvas.width;\n const height = canvas.height;\n const transform = ctx.getTransform();\n\n // Group elements into clipping segments\n const segments: {\n content: BaseElement[];\n mask?: BaseElement;\n contentConfigs: AnyElementConfig[];\n maskConfig?: AnyElementConfig;\n }[] = [];\n let currentContent: BaseElement[] = [];\n let currentContentConfigs: AnyElementConfig[] = [];\n\n // Iterate through elements (bottom to top in visual order)\n for (let i = 0; i < elements.length; i++) {\n const element = elements[i];\n const elementConfig = allElementConfigs[i];\n\n // Skip if already rendered by knockout\n if (knockoutRenderedIds.has(element.id)) continue;\n\n // Skip invisible elements\n if (element.visible === false) continue;\n\n if (element.isClipping) {\n // This element clips everything we've collected so far\n if (currentContent.length > 0) {\n segments.push({\n content: currentContent,\n mask: element,\n contentConfigs: currentContentConfigs,\n maskConfig: elementConfig,\n });\n currentContent = [];\n currentContentConfigs = [];\n } else {\n // Clipping mask with no content below it - just render the mask normally\n segments.push({\n content: [element],\n contentConfigs: [elementConfig],\n });\n }\n } else {\n currentContent.push(element);\n currentContentConfigs.push(elementConfig);\n }\n }\n\n // Add remaining content without clipping\n if (currentContent.length > 0) {\n segments.push({\n content: currentContent,\n contentConfigs: currentContentConfigs,\n });\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((element, idx) => {\n const isSelected = element.id === selectedId;\n const isHovered = hoverState?.type === 'element' && hoverState?.data?.id === element.id;\n const isDraggingChildInThisGroup =\n isDragging && activeChildContext && isGroupElement(element) && element.id === selectedId;\n const shouldShowAsSelected = isSelected && !isDraggingChildInThisGroup;\n\n const elementConfig = segment.contentConfigs[idx];\n if (elementConfig && elementConfig.masks && elementConfig.masks.length > 0) {\n renderWithMasks(ctx, elementConfig as AnyElementConfig, (maskCtx) => {\n if (isPreview) {\n maskCtx.save();\n maskCtx.globalAlpha = PREVIEW_ELEMENT_OPACITY;\n }\n element.render(maskCtx as CanvasRenderingContext2D, shouldShowAsSelected, isHovered);\n if (isPreview) {\n maskCtx.restore();\n }\n });\n } else {\n if (isPreview) {\n ctx.save();\n ctx.globalAlpha = PREVIEW_ELEMENT_OPACITY;\n }\n element.render(ctx, shouldShowAsSelected, isHovered);\n if (isPreview) {\n ctx.restore();\n }\n }\n });\n } else {\n // Render with clipping mask\n\n const contentCanvas = this._getContentCanvas(width, height);\n const contentCtx = contentCanvas.getContext('2d');\n if (!contentCtx) continue;\n\n // Clear the pooled canvas before use\n contentCtx.setTransform(1, 0, 0, 1, 0, 0);\n contentCtx.clearRect(0, 0, width, height);\n contentCtx.globalCompositeOperation = 'source-over';\n\n contentCtx.setTransform(transform);\n\n // Render content\n segment.content.forEach((element, idx) => {\n const isSelected = element.id === selectedId;\n const isHovered = hoverState?.type === 'element' && hoverState?.data?.id === element.id;\n const isDraggingChildInThisGroup =\n isDragging && activeChildContext && isGroupElement(element) && element.id === selectedId;\n const shouldShowAsSelected = isSelected && !isDraggingChildInThisGroup;\n\n\n const elementConfig = segment.contentConfigs[idx];\n if (elementConfig && elementConfig.masks && elementConfig.masks.length > 0) {\n renderWithMasks(contentCtx, elementConfig as AnyElementConfig, (maskCtx) => {\n if (isPreview) {\n maskCtx.save();\n maskCtx.globalAlpha = PREVIEW_ELEMENT_OPACITY;\n }\n element.render(maskCtx as CanvasRenderingContext2D, shouldShowAsSelected, isHovered);\n if (isPreview) {\n maskCtx.restore();\n }\n });\n } else {\n if (isPreview) {\n contentCtx.save();\n contentCtx.globalAlpha = PREVIEW_ELEMENT_OPACITY;\n }\n element.render(contentCtx, shouldShowAsSelected, isHovered);\n if (isPreview) {\n contentCtx.restore();\n }\n }\n });\n\n // Create mask (using pooled canvas)\n const maskCanvas = this._getMaskCanvas(width, height);\n const maskCtx = maskCanvas.getContext('2d');\n if (!maskCtx) continue;\n\n // Clear the pooled canvas before use\n maskCtx.setTransform(1, 0, 0, 1, 0, 0);\n maskCtx.clearRect(0, 0, width, height);\n maskCtx.globalCompositeOperation = 'source-over';\n\n maskCtx.setTransform(transform);\n\n const maskIsSelected = segment.mask.id === selectedId;\n const maskIsHovered = hoverState?.type === 'element' && hoverState?.data?.id === segment.mask.id;\n\n if (segment.maskConfig && segment.maskConfig.masks && segment.maskConfig.masks.length > 0) {\n renderWithMasks(maskCtx, segment.maskConfig as AnyElementConfig, (innerMaskCtx) => {\n if (isPreview) {\n innerMaskCtx.save();\n innerMaskCtx.globalAlpha = PREVIEW_ELEMENT_OPACITY;\n }\n segment.mask!.render(innerMaskCtx as CanvasRenderingContext2D, maskIsSelected, maskIsHovered);\n if (isPreview) {\n innerMaskCtx.restore();\n }\n });\n } else {\n if (isPreview) {\n maskCtx.save();\n maskCtx.globalAlpha = PREVIEW_ELEMENT_OPACITY;\n }\n segment.mask.render(maskCtx, maskIsSelected, maskIsHovered);\n if (isPreview) {\n maskCtx.restore();\n }\n }\n\n // Apply mask using destination-in\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.save();\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n ctx.drawImage(contentCanvas, 0, 0);\n ctx.restore();\n }\n }\n }\n\n // =========================================================================\n // Delegated Methods (preserve public API, delegate to focused renderers)\n // =========================================================================\n\n /**\n * Render snap guides layer\n * @see InteractionFeedbackRenderer.renderSnapGuidesLayer\n */\n static renderSnapGuidesLayer(ctx: CanvasRenderingContext2D, snapGuides: SnapGuide[] = []): void {\n InteractionFeedbackRenderer.renderSnapGuidesLayer(ctx, snapGuides);\n }\n\n /**\n * Render spacing indicators layer\n * @see InteractionFeedbackRenderer.renderSpacingIndicatorsLayer\n */\n static renderSpacingIndicatorsLayer(ctx: CanvasRenderingContext2D, spacingIndicators: SpacingIndicator[] = []): void {\n InteractionFeedbackRenderer.renderSpacingIndicatorsLayer(ctx, spacingIndicators);\n }\n\n /**\n * Render a distance label with background\n * @see InteractionFeedbackRenderer.renderDistanceLabel\n */\n static renderDistanceLabel(ctx: CanvasRenderingContext2D, x: number, y: number, distance: number): void {\n InteractionFeedbackRenderer.renderDistanceLabel(ctx, x, y, distance);\n }\n\n /**\n * Render selection box layer\n * @see SelectionRenderer.renderSelectionLayer\n */\n static renderSelectionLayer(\n ctx: CanvasRenderingContext2D,\n selectedElement: BaseElement | null,\n stateMachine: InteractionStateMachine,\n zoom: number = 1.0\n ): void {\n SelectionRenderer.renderSelectionLayer(ctx, selectedElement, stateMachine, zoom);\n }\n\n /**\n * Render position and size info below the selected element\n * @see SelectionRenderer.renderPositionSizeInfo\n */\n static renderPositionSizeInfo(\n ctx: CanvasRenderingContext2D,\n selectedElement: BaseElement | null,\n transformHandles: TransformHandles | null,\n stateMachine: InteractionStateMachine | null,\n zoom: number = 1.0\n ): void {\n SelectionRenderer.renderPositionSizeInfo(ctx, selectedElement, transformHandles, stateMachine, zoom);\n }\n\n /**\n * Render transform handles layer\n * @see SelectionRenderer.renderHandlesLayer\n */\n static renderHandlesLayer(\n ctx: CanvasRenderingContext2D,\n selectedElement: BaseElement | null,\n transformHandles: TransformHandles,\n currentRotation: number,\n isRotating: boolean,\n hoverState: HoverState,\n stateMachine: InteractionStateMachine,\n zoom: number = 1.0,\n showRotationHandle: boolean = true\n ): void {\n SelectionRenderer.renderHandlesLayer(\n ctx, selectedElement, transformHandles, currentRotation,\n isRotating, hoverState, stateMachine, zoom, showRotationHandle\n );\n }\n\n /**\n * Render hover highlights layer with fade-in animation\n * @see HoverRenderer.renderHoverLayer\n */\n static renderHoverLayer(\n ctx: CanvasRenderingContext2D,\n hoverState: HoverState,\n selectedId: string | null,\n _isAltKeyPressed: boolean = false,\n zoom: number = 1.0,\n artboardBackgroundColor?: string\n ): void {\n HoverRenderer.renderHoverLayer(ctx, hoverState, selectedId, _isAltKeyPressed, zoom, artboardBackgroundColor);\n }\n\n /**\n * Render crop box layer (for images in crop mode)\n * @see EditModeRenderer.renderCropBoxLayer\n */\n static renderCropBoxLayer(\n ctx: CanvasRenderingContext2D,\n selectedElement: BaseElement,\n transformHandles: TransformHandles,\n zoom: number = 1.0\n ): void {\n EditModeRenderer.renderCropBoxLayer(ctx, selectedElement, transformHandles, zoom);\n }\n\n // =========================================================================\n // Main Render Orchestration\n // =========================================================================\n\n /**\n * Complete rendering pipeline\n * Renders all layers in correct z-order\n */\n static render({\n ctx,\n elements,\n selectedId,\n selectedElement,\n // Chrome-related fields (transformHandles, currentRotation,\n // isRotating, hideHandles, isAltKeyPressed, showRotationHandle)\n // are accepted for backwards compatibility with the public type\n // signature exposed via `api/internals.ts`, but are no longer\n // consumed here — single-selection chrome is now drawn by\n // `renderInteractionChromeLayer` after piece guides. See that\n // method for the actual chrome rendering.\n hoverState,\n isEditing,\n stateMachine,\n snapGuides = [],\n spacingIndicators = [],\n artboardBackgroundColor,\n artboard,\n isPreview = true,\n zoom = 1.0,\n paddingOffset = { x: 0, y: 0 },\n showOverflowDimmed = false,\n suppressOverflowDim = false,\n distressTexture,\n imageMask,\n editModeData,\n }: {\n ctx: CanvasRenderingContext2D;\n elements: BaseElement[];\n selectedId: string | null;\n selectedElement: BaseElement | null;\n transformHandles: TransformHandles | null;\n currentRotation: number;\n isRotating: boolean;\n hoverState: HoverState;\n isEditing: boolean;\n stateMachine: InteractionStateMachine | null;\n snapGuides?: SnapGuide[];\n spacingIndicators?: SpacingIndicator[];\n hideHandles?: boolean;\n isAltKeyPressed?: boolean;\n artboardBackgroundColor?: string;\n artboard?: { x: number; y: number; width: number; height: number; borderRadius?: number; clipShape?: ClipShape };\n isPreview?: boolean;\n zoom?: number;\n paddingOffset?: { x: number; y: number };\n /** When true, element parts outside the artboard render at 30% opacity */\n showOverflowDimmed?: boolean;\n /**\n * When true, skip the 30% overflow-dim pass even if `showOverflowDimmed` is on.\n * Used by ADR-0054 piece-guide rendering: the boundary's custom path clipShape is\n * the visible design edge, so the rectangular-artboard overflow halo reads as\n * coloured noise rather than useful UX. Selected elements fall through to the\n * clipped-only branch, which respects `clipShape` correctly.\n */\n suppressOverflowDim?: boolean;\n /** Whether to show the rotation handle (defaults to true, set false for embedded mode) */\n showRotationHandle?: boolean;\n /** Artboard-level distress texture (applied after elements, before UI layers) */\n distressTexture?: ArtboardDistressTexture;\n /** Artboard-level image mask (applied after distress texture, before UI layers) */\n imageMask?: import('../types/index.js').ArtboardImageMask;\n editModeData?: {\n cursorPosition: number;\n selectionStart: number;\n selectionEnd: number;\n editText: string;\n editRichText: RichText | null;\n cursorOpacity: number;\n };\n }): void {\n // Layer 1: Elements (rendered with zoom applied)\n this.renderElementsLayer(\n ctx,\n elements,\n selectedId,\n hoverState,\n stateMachine ?? undefined,\n artboardBackgroundColor,\n artboard,\n isPreview,\n showOverflowDimmed,\n suppressOverflowDim\n );\n\n // Layer 1.25: Artboard-level distress texture (after elements, before UI)\n if (distressTexture?.enabled && artboard) {\n applyArtboardDistressTexture(ctx, distressTexture, artboard, artboardBackgroundColor);\n }\n\n // Layer 1.3: Artboard-level image mask (after distress, before UI)\n if (imageMask?.enabled && artboard) {\n applyArtboardImageMask(ctx, imageMask, artboard, artboardBackgroundColor);\n }\n\n // Layer 1.5: Edit mode cursor and selection (rendered with zoom applied, same as elements)\n if (isEditing && editModeData && selectedElement) {\n EditModeRenderer.renderEditModeLayer(ctx, selectedElement, editModeData, zoom);\n }\n\n // For UI elements (guides, handles, etc.), we need to render at native size\n // even when zoom is applied. We do this by:\n // 1. Saving the current transform (which has DPR + zoom)\n // 2. Resetting to DPR-only transform\n // 3. Scaling all UI positions by zoom to compensate\n // 4. Restoring the original transform\n if (zoom !== 1.0) {\n ctx.save();\n\n // Read the *effective* DPR from the current ctx transform —\n // matches what the render loop applied (which may be less than\n // `window.devicePixelRatio` when the buffer cap kicked in for\n // high-zoom). Using window.dpr here would drift the snap\n // guides relative to the elements by `dpr / effectiveDpr`.\n const transform = ctx.getTransform();\n const effectiveDpr = transform.a / zoom;\n\n // Reset transform to effective-DPR-only (removes zoom), but\n // preserve padding offset (in world coords, scaled to screen).\n const offsetX = paddingOffset.x * zoom * effectiveDpr;\n const offsetY = paddingOffset.y * zoom * effectiveDpr;\n ctx.setTransform(effectiveDpr, 0, 0, effectiveDpr, offsetX, offsetY);\n }\n\n // Transform snap guides positions by zoom\n const zoomedSnapGuides = snapGuides.map((guide) => ({\n ...guide,\n x: guide.x !== undefined ? guide.x * zoom : undefined,\n y: guide.y !== undefined ? guide.y * zoom : undefined,\n x1: guide.x1 !== undefined ? guide.x1 * zoom : undefined,\n y1: guide.y1 !== undefined ? guide.y1 * zoom : undefined,\n x2: guide.x2 !== undefined ? guide.x2 * zoom : undefined,\n y2: guide.y2 !== undefined ? guide.y2 * zoom : undefined,\n }));\n\n // Transform spacing indicators by zoom\n const zoomedSpacingIndicators = spacingIndicators.map((indicator) => ({\n ...indicator,\n x: indicator.x !== undefined ? indicator.x * zoom : undefined,\n y: indicator.y !== undefined ? indicator.y * zoom : undefined,\n x1: indicator.x1 !== undefined ? indicator.x1 * zoom : undefined,\n y1: indicator.y1 !== undefined ? indicator.y1 * zoom : undefined,\n x2: indicator.x2 !== undefined ? indicator.x2 * zoom : undefined,\n y2: indicator.y2 !== undefined ? indicator.y2 * zoom : undefined,\n labelX: indicator.labelX * zoom,\n labelY: indicator.labelY * zoom,\n }));\n\n // Layer 2: Snap guides (renders on top of elements)\n InteractionFeedbackRenderer.renderSnapGuidesLayer(ctx, zoomedSnapGuides);\n\n // Layer 2.5: Spacing indicators (renders after snap guides)\n InteractionFeedbackRenderer.renderSpacingIndicatorsLayer(ctx, zoomedSpacingIndicators);\n\n // Single-selection interaction chrome (selection bbox + handles +\n // position/size info + hover outline + crop-mode chrome) used to\n // render here as Layers 3–6. They've been hoisted out to\n // `renderInteractionChromeLayer`, which `useCanvasRenderLoop`\n // calls *after* `renderPieceGuides` so the partial die-cut\n // knockout (alpha 0.7, intentionally dims artwork to 30% inside\n // the cutout) doesn't also dim selection chrome that the user\n // needs at full opacity for resize/drag feedback.\n\n // Restore original transform (with zoom) if we modified it\n if (zoom !== 1.0) {\n ctx.restore();\n }\n }\n\n /**\n * Single-selection interaction chrome: bbox, handles, position/size\n * info, hover outline, and the crop-mode alt branch. Hoisted out of\n * `render()` so the master render loop can call it *after*\n * `renderPieceGuides`. Otherwise the piece-guide partial die-cut\n * knockout (`destination-out` at alpha 0.7) erases 70% of the\n * chrome's alpha along with the artwork it's intentionally dimming.\n *\n * Renders at native screen scale: identical transform manipulation\n * to the original Layer 2 / 3–6 block in `render()` so chrome stays\n * crisp regardless of canvas zoom.\n */\n static renderInteractionChromeLayer({\n ctx,\n selectedElement,\n transformHandles,\n stateMachine,\n currentRotation,\n isRotating,\n hoverState,\n selectedId,\n hideHandles = false,\n isEditing,\n isAltKeyPressed = false,\n artboardBackgroundColor,\n hoverClipArtboard,\n showRotationHandle = true,\n zoom = 1.0,\n paddingOffset = { x: 0, y: 0 },\n }: {\n ctx: CanvasRenderingContext2D;\n selectedElement: BaseElement | null;\n transformHandles: TransformHandles | null;\n stateMachine: InteractionStateMachine | null;\n currentRotation: number;\n isRotating: boolean;\n hoverState: HoverState;\n selectedId: string | null;\n hideHandles?: boolean;\n isEditing: boolean;\n isAltKeyPressed?: boolean;\n artboardBackgroundColor?: string;\n /**\n * Optional clip rect (in world coords) confining the hover outline\n * to the visible artboard / piece bounds. Selection chrome ignores\n * this — once the user commits to a selection they want to see the\n * full extent of the element. The hover outline though was reading\n * as noisy when the cursor moved over an element that extended past\n * the piece edge, since the hover ring was drawn into the overflow\n * region on top of the dimmed surrounding canvas.\n */\n hoverClipArtboard?: {\n x: number;\n y: number;\n width: number;\n height: number;\n borderRadius?: number;\n clipShape?: ClipShape;\n };\n showRotationHandle?: boolean;\n zoom?: number;\n paddingOffset?: { x: number; y: number };\n }): void {\n // Same transform manipulation as the parent render() does for\n // its UI layers: reset to DPR-only so chrome paints at native\n // screen size, then restore. Caller-supplied coordinates are\n // pre-multiplied by zoom inside the individual chrome renderers.\n //\n // We read the *effective* DPR from the current ctx transform\n // (which the render loop set as `effectiveDpr × zoom`). This\n // matters when the render loop has capped the canvas backing\n // buffer at a max dimension (so `effectiveDpr < window.dpr`):\n // using `window.devicePixelRatio` here would mismatch the\n // element-render scale and the chrome would drift away from\n // the element by `dpr / effectiveDpr` as zoom grows past the\n // cap threshold.\n if (zoom !== 1.0) {\n ctx.save();\n const transform = ctx.getTransform();\n const effectiveDpr = transform.a / zoom;\n const offsetX = paddingOffset.x * zoom * effectiveDpr;\n const offsetY = paddingOffset.y * zoom * effectiveDpr;\n ctx.setTransform(effectiveDpr, 0, 0, effectiveDpr, offsetX, offsetY);\n }\n\n const isCropping =\n selectedElement && isImageElement(selectedElement) && selectedElement.isCropping;\n\n if (!isCropping) {\n // Layer 3: Selection box (hidden while dragging or during keyboard nudging)\n if (!hideHandles && stateMachine) {\n SelectionRenderer.renderSelectionLayer(ctx, selectedElement, stateMachine, zoom);\n }\n\n // Layer 4: Position and size info (above rotation handle, only while resizing)\n if (transformHandles && stateMachine) {\n SelectionRenderer.renderPositionSizeInfo(ctx, selectedElement, transformHandles, stateMachine, zoom);\n }\n\n // Layer 5: Transform handles (hidden during keyboard nudging)\n if (!hideHandles && transformHandles && stateMachine) {\n SelectionRenderer.renderHandlesLayer(\n ctx,\n selectedElement,\n transformHandles,\n currentRotation,\n isRotating,\n hoverState,\n stateMachine,\n zoom,\n showRotationHandle\n );\n }\n\n // Layer 6: Hover highlights (only when not editing). Wrapped in\n // an artboard clip so the hover ring doesn't bleed into the\n // overflow region (outside the piece shape) — that was reading\n // as noisy on every mouseover, especially on piece-guide\n // products where the visible piece is smaller than the element's\n // bounding rect. Selection chrome above is intentionally NOT\n // clipped: once the user commits via tap/click, they want to\n // see how far the element extends.\n if (!isEditing) {\n if (hoverClipArtboard) {\n ctx.save();\n // Build the clip path manually here (rather than via the\n // private `applyArtboardClipPath` helper) so we don't leak\n // `clip()` calls into the parent transform space — the\n // chrome layer's transform is `effectiveDpr × identity`\n // (no zoom), so we multiply world coords by `zoom` to land\n // in physical pixels.\n const cb = hoverClipArtboard;\n const cx = cb.x * zoom;\n const cy = cb.y * zoom;\n const cw = cb.width * zoom;\n const ch = cb.height * zoom;\n const radius = (cb.borderRadius ?? 0) * zoom;\n ctx.beginPath();\n if (radius > 0) {\n ctx.roundRect(cx, cy, cw, ch, radius);\n } else {\n ctx.rect(cx, cy, cw, ch);\n }\n ctx.clip();\n }\n HoverRenderer.renderHoverLayer(ctx, hoverState, selectedId, isAltKeyPressed, zoom, artboardBackgroundColor);\n if (hoverClipArtboard) {\n ctx.restore();\n }\n }\n } else {\n // In crop mode: render crop box with both image and crop handles\n if (transformHandles) {\n EditModeRenderer.renderCropBoxLayer(ctx, selectedElement, transformHandles, zoom);\n }\n }\n\n if (zoom !== 1.0) {\n ctx.restore();\n }\n }\n}\n"],"names":["theme","SELECTION_LINE_WIDTH","HANDLE_RADIUS","ROTATION_HANDLE_RADIUS","DASH_SIZE","DASH_GAP","ROTATION_ICON_SCALE","getLineWidth","zoom","getHandleRadius","getRotationHandleRadius","getDashPattern","renderRotationHandleIcon","ctx","x","y","scale","renderRotationHandle","accentColor","isHovering","rotationHandleRadius","HANDLE_SIZE","BASE_HANDLE_RADIUS","TransformHandles","element","bbox","rotationAnchor","enabledAnchors","padding","paddedBbox","allHandles","calculateResizeHandles","isRotated","handle","calculateRotationHandlePosition","showRotationHandle","rotationAngle","isRotating","isHoveringRotation","hoveredResizeHandle","isResizing","activeResizeHandle","renderRotationInCanvas","isHovered","isActive","handleX","handleY","isTopBottom","width","height","radius","getThemeAccentColor","visualRadius","angle","angleText","tooltipWidth","tooltipHeight","tooltipX","tooltipY","CORNER_HANDLE_HIT_RADIUS","hitTestCircle","rotation","handleType","getCursorForWorldPosition","ResizeHandler","dx","dy","startData","snapSystem","localDelta","Transform","localDx","localDy","startVisualWidth","startVisualHeight","startBboxWidth","startBboxHeight","newVisualWidth","newVisualHeight","newWidth","newHeight","widthChange","heightChange","scaleX","scaleY","unfrozenVisualWidth","unfrozenVisualHeight","freezeApplied","anchor","isCornerResizeHandle","isCropHandle","snapX","_a","snapY","_b","frozenState","state","imageElement","imageStartTransform","targetWidth","_c","aspectRatio","targetHeight","_d","widthScaleFromStart","heightScaleFromStart","updatedElement","resizeApplied","_e","startW","startH","strokeScale","scaledStrokeWidth","isFrozen","isCornerHandle","isCroppingImage","isGroup","positionOffset","startTransform","cosRot","sinRot","startBbox","newBbox","startRotationAnchor","newRotationAnchor","fixedLocal","localX","localY","bboxOffsetX","bboxOffsetY","cornerRelX","cornerRelY","rotatedX","rotatedY","fixedWorldX","fixedWorldY","newFixedLocal","newLocalX","newLocalY","newBboxOffsetX","newBboxOffsetY","newCornerRelX","newCornerRelY","newRotatedX","newRotatedY","currentFixedWorldX","currentFixedWorldY","coords","ResizePipeline","stage","fn","index","data","acc","result","params","beforeParams","position","beforeData","globalResizePipeline","InteractionStateMachine","resizePipeline","handler","pipeline","mouseX","mouseY","dragMode","baseStartData","imageTransformData","allElements","dragStartData","newX","newY","snapped","visualBbox","elementForResize","deltaX","deltaY","startAngle","currentRotation","centerX","centerY","rotStartData","rotationDelta","calculateAngle","newRotation","p1","p2","startCentroid","startDistance","pinchStartData","currentCentroid","currentDistance","currentAngle","safeStartDistance","rawScale","RotationAnchorResolver","anchors","angleRad","RotationUtils","key","point","center","epsilon","SpatialGrid","cellSize","item","cell","keys","id","excludeId","results","minCellX","maxCellX","minCellY","maxCellY","cx","cy","axis","value","commaIdx","cellX","cellY","items","getTransformDataWidth","td","getTransformDataHeight","setTransformDataWidth","setTransformDataHeight","AlignmentSnapSystem","options","enabled","threshold","rects","elements","anchorKey","compoundKey","gridItem","rect","x2","y2","elementId","tempElement","movingAnchors","otherElements","s","bestSnapX","bestSnapY","minDistX","minDistY","allXSnaps","allYSnaps","newGuides","movingKey","movingAnchor","xCandidates","candidate","meta","otherAnchor","otherKey","distX","xSnap","yCandidates","distY","ySnap","snappedX","snappedY","xSnapsByPosition","ySnapsByPosition","existing","snapDeltaX","_snapDeltaY","anchorStr","currentWidth","targetWidthAtSnap","_otherElements","snapKeys","targetSnapKeys","xSnaps","targetSnapKeysSet","candidates","ySnaps","snaps","a","b","aPriority","bPriority","snap","trackedAnchor","minY","maxY","otherElement","otherAnchors","minX","maxX","adjustedThreshold","snapDelta","_handle","currentHeight","finalWidth","finalHeight","isImageElement","anchorLower","activeSnap","bestSnap","currentValue","rawDistance","recalculatedFontSize","previousSnap","shouldPreserveFreeze","frozenFontSize","isPositionBased","frozenPos","frozenDimensions","existingSnap","beforeVisualBbox","tdWidth","rotationRad","cos","sin","leftEdgeShift","afterVisualBbox","rightEdgeShift","snapDeltaY","tdHeight","heightChanged","topEdgeShift","shouldCompensate","newVisualBbox","bottomEdgeShift","hasActiveSnap","frozenVisualWidth","frozenVisualHeight","xSnapResult","ySnapResult","isCornerResize","snappedElement","preservedFontSize","preservedWidth","preservedHeight","preservedPosition","isImageCropMode","xSnapAnchor","xSnapAnchorLower","ySnapAnchor","ySnapAnchorLower","isImageElement2","hasActiveSnap2","activeSnapState","imageAspectRatio","isHorizontalSideHandle","isVerticalSideHandle","xIsPositionBasedSnap","yIsPositionBasedSnap","_element","_allElements","commonAngles","snapAngle","isNear","SpacingSystem","movingElement","snapThreshold","SPACING_SNAP_THRESHOLD","movingBBox","visualOffsetX","visualOffsetY","didSnapX","didSnapY","horizontallyAligned","bboxA","bboxB","closestAbove","closestBelow","gaps","i","bbox1","gap","targetGap","aboveBBox","currentGap","belowBBox","verticallyAligned","closestLeft","closestRight","leftBBox","rightBBox","showAllMeasurements","leftElements","rightElements","topElements","bottomElements","otherBBox","distanceToLeft","distanceToRight","distanceToTop","distanceToBottom","verticalOverlap","horizontalOverlap","newIndicators","allMeasurements","indicators","hoveredElement","hoveredBBox","altHoverThreshold","horizontalCount","verticalCount","elementBBox","horizontalGap","showHorizontal","hx1","hx2","hy","horizontalOffset","verticalGap","showVertical","vx","vy1","vy2","verticalOffset","nearbyElements","direction","closest","closestDistance","closestBBox","wouldSnap","alignedElements","element1","element2","bbox2","y1","x1","avgGap","distance","isSnapTarget","logger","createLogger","DEFAULT_OPTIONS","ArtboardRenderer","artboard","isSelected","texture","TextureManager","presetColor","err","lightColor","darkColor","row","col","clipShape","borderColor","getThemeArtboardBorderColor","selectedBorderColor","path","e","fontSize","labelText","labelX","labelY","labelColor","getThemeArtboardLabelColor","artboards","selectedId","isDragging","handles","SelectionRenderer","selectedElement","stateMachine","transformHandles","infoText","rotationHandle","getThemeTooltipShadowColor","getThemeTooltipBackground","getThemeTooltipForeground","hoverState","HoverRenderer","_isAltKeyPressed","artboardBackgroundColor","FADE_DURATION","TARGET_OPACITY","opacity","elapsed","getThemeAccentHoverColor","transformData","strokeWidth","offset","borderRadius","w","h","radiusX","radiusY","halfWidth","halfHeight","triVerts","triNormals","v","next","len","offsetVerts","_v","n1","n2","bx","by","bLen","cosHalf","dist","points","innerRadius","outerRadius","innerR","r","sides","_artboardBackgroundColor","transformType","fontFamily","bold","italic","textAlign","fontMetrics","getFontMetrics","lineSpacing","ringWidth","tempCanvas","tempCtx","richText","explicitLines","splitRichTextIntoLines","wrappedLines","paragraphMetadata","lineSpans","availableWidth","HORIZONTAL_PADDING","wrapped","wrapRichTextSpans","wLine","wIndex","totalHeight","topLeftY","lineIndex","isParagraphStart","isParagraphEnd","lineWidth","fullLineText","leadingSpacesCount","trailingSpacesCount","charIdx","span","spanFontSize","spanFontFamily","spanBold","spanItalic","visibleText","spanStart","spanEnd","hiddenChars","trailingStart","leadingTrim","visibleChars","buildFontString","maxWidth","currentX","baselineY","textLines","lines","calculateVisualBoundsWithSpaceCollapsing","hasExplicitNewlines","applySpaceLayoutRules","box","topLeftX","xPos","line","yPos","color","textEl","weight","style","skewAngle","chars","charWidths","char","positions","applyCharTransform","pos","totalWidth","sum","_","angleStep","charWidth","normalizedX","slope","WAVE_SKEW_FACTOR","distanceFromCenter","flagAmplitude","cosComponent","sinComponent","InteractionFeedbackRenderer","snapGuides","guide","spacingIndicators","indicator","spacingColor","getThemeSpacingColor","capLength","text","textWidth","textHeight","bgWidth","bgHeight","SPACING_LABEL_BORDER_RADIUS","SPACING_LABEL_TEXT_COLOR","EditModeRenderer","editModeData","textElement","cursorPosition","selectionStart","selectionEnd","editText","editRichText","cursorOpacity","isTouchDevice","selectionHandlePositionsRef","currentZoom","underline","strikethrough","lineBoxHeight","boxWidth","wrappedLine","idx","currentY","cursorX","cursorY","cursorHeight","charPositions","charIndex","currentLineChars","charFontSize","charFontFamily","charBold","charItalic","charStyle","fontWeight","fontStyle","isFirstLine","isLastLine","c","j","lineX","charData","firstCharIndex","lastCharIndex","maxAllowedCursor","visibleTextWidth","k","isSpace","isVisible","isLeadingSpace","isTrailingSpace","leadingHidden","trailingHidden","hasSelection","startHandlePos","endHandlePos","minSel","maxSel","getThemeTextSelectionColor","selCharIndex","selCurrentY","lineChars","fontStyleStr","lineSelStart","lineSelEnd","xBeforeSelection","xBeforeSelectionEnd","selectionX","selectionWidth","customData","renderRichTextFillOnly","startPos","endPos","handleRadius","stemWidth","startHandleCenter","endHandleCenter","cursorPos","firstPos","lastPos","lastCharWidth","charPos","ImageElement","imgElement","cropBox","EDGE_HANDLE_THICKNESS","CROP_BOX_LINE_WIDTH","CORNER_HANDLE_RADIUS_SCALED","CORNER_HANDLE_VISUAL_RADIUS","EDGE_HANDLE_LENGTH_SCALED","EDGE_HANDLE_VISUAL_LENGTH","GRID_LINE_WIDTH","flipH","flipV","cropWidth","cropHeight","localOffsetX","localOffsetY","imageBounds","cropBottom","imageBottom","cropRight","imageRight","thirdWidth","thirdHeight","corner","edge","cornerHandles","cropCorners","minDistance","tooClose","cropCorner","installElementPainters","renderLogger","_CanvasRenderer","error","isPreview","PREVIEW_ELEMENT_OPACITY","composite","piece","sub","rot","matrix","swap","visW","visH","savedTransform","inverseTransform","screenWidth","screenHeight","topLeft","bottomRight","worldMinX","worldMinY","worldMaxX","worldMaxY","compositeShape","compoundPath","showOverflowDimmed","suppressOverflowDim","activeChildContext","el","allElementConfigs","elementMap","knockoutRenderedIds","elementConfig","compositeOp","scope","compositingIndex","hasCompositingParts","affectedElements","renderWithCompositing","config","actualElement","isDraggingChildInThisGroup","isGroupElement","shouldShowAsSelected","hasCustomDistress","renderWithMasks","maskCtx","renderContent","renderCtx","applyElementDistressTexture","offCtx","canvas","transform","segments","currentContent","currentContentConfigs","segment","contentCanvas","contentCtx","maskCanvas","maskIsSelected","maskIsHovered","innerMaskCtx","isEditing","paddingOffset","distressTexture","imageMask","applyArtboardDistressTexture","applyArtboardImageMask","effectiveDpr","offsetX","offsetY","zoomedSnapGuides","zoomedSpacingIndicators","hideHandles","isAltKeyPressed","hoverClipArtboard","cb","cw","ch","CanvasRenderer"],"mappings":"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"}