@hypen-space/web 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-2s02mkzs.js +32 -0
- package/dist/chunk-2s02mkzs.js.map +9 -0
- package/dist/src/canvas/accessibility.js +152 -0
- package/dist/src/canvas/accessibility.js.map +10 -0
- package/dist/src/canvas/events.js +198 -0
- package/dist/src/canvas/events.js.map +10 -0
- package/dist/src/canvas/index.js +28 -0
- package/dist/src/canvas/index.js.map +9 -0
- package/dist/src/canvas/input.js +132 -0
- package/dist/src/canvas/input.js.map +10 -0
- package/dist/src/canvas/layout.js +309 -0
- package/dist/src/canvas/layout.js.map +10 -0
- package/dist/src/canvas/paint.js +878 -0
- package/dist/src/canvas/paint.js.map +10 -0
- package/dist/src/canvas/renderer.js +276 -0
- package/dist/src/canvas/renderer.js.map +10 -0
- package/dist/src/canvas/text.js +118 -0
- package/dist/src/canvas/text.js.map +10 -0
- package/dist/src/canvas/types.js +2 -0
- package/dist/src/canvas/types.js.map +9 -0
- package/dist/src/canvas/utils.js +139 -0
- package/dist/src/canvas/utils.js.map +10 -0
- package/dist/src/dom/applicators/advanced-layout.js +111 -0
- package/dist/src/dom/applicators/advanced-layout.js.map +10 -0
- package/dist/src/dom/applicators/background.js +54 -0
- package/dist/src/dom/applicators/background.js.map +10 -0
- package/dist/src/dom/applicators/border.js +33 -0
- package/dist/src/dom/applicators/border.js.map +10 -0
- package/dist/src/dom/applicators/color.js +36 -0
- package/dist/src/dom/applicators/color.js.map +10 -0
- package/dist/src/dom/applicators/display.js +57 -0
- package/dist/src/dom/applicators/display.js.map +10 -0
- package/dist/src/dom/applicators/effects.js +89 -0
- package/dist/src/dom/applicators/effects.js.map +10 -0
- package/dist/src/dom/applicators/events.js +518 -0
- package/dist/src/dom/applicators/events.js.map +10 -0
- package/dist/src/dom/applicators/font.js +39 -0
- package/dist/src/dom/applicators/font.js.map +10 -0
- package/dist/src/dom/applicators/index.js +296 -0
- package/dist/src/dom/applicators/index.js.map +10 -0
- package/dist/src/dom/applicators/layout.js +86 -0
- package/dist/src/dom/applicators/layout.js.map +10 -0
- package/dist/src/dom/applicators/margin.js +32 -0
- package/dist/src/dom/applicators/margin.js.map +10 -0
- package/dist/src/dom/applicators/padding.js +35 -0
- package/dist/src/dom/applicators/padding.js.map +10 -0
- package/dist/src/dom/applicators/size.js +42 -0
- package/dist/src/dom/applicators/size.js.map +10 -0
- package/dist/src/dom/applicators/transform.js +92 -0
- package/dist/src/dom/applicators/transform.js.map +10 -0
- package/dist/src/dom/applicators/transition.js +66 -0
- package/dist/src/dom/applicators/transition.js.map +10 -0
- package/dist/src/dom/applicators/typography.js +87 -0
- package/dist/src/dom/applicators/typography.js.map +10 -0
- package/dist/src/dom/canvas/index.js +50 -0
- package/dist/src/dom/canvas/index.js.map +10 -0
- package/dist/src/dom/components/audio.js +48 -0
- package/dist/src/dom/components/audio.js.map +10 -0
- package/dist/src/dom/components/avatar.js +58 -0
- package/dist/src/dom/components/avatar.js.map +10 -0
- package/dist/src/dom/components/badge.js +55 -0
- package/dist/src/dom/components/badge.js.map +10 -0
- package/dist/src/dom/components/button.js +29 -0
- package/dist/src/dom/components/button.js.map +10 -0
- package/dist/src/dom/components/card.js +33 -0
- package/dist/src/dom/components/card.js.map +10 -0
- package/dist/src/dom/components/center.js +32 -0
- package/dist/src/dom/components/center.js.map +10 -0
- package/dist/src/dom/components/checkbox.js +54 -0
- package/dist/src/dom/components/checkbox.js.map +10 -0
- package/dist/src/dom/components/column.js +31 -0
- package/dist/src/dom/components/column.js.map +10 -0
- package/dist/src/dom/components/container.js +29 -0
- package/dist/src/dom/components/container.js.map +10 -0
- package/dist/src/dom/components/divider.js +45 -0
- package/dist/src/dom/components/divider.js.map +10 -0
- package/dist/src/dom/components/grid.js +44 -0
- package/dist/src/dom/components/grid.js.map +10 -0
- package/dist/src/dom/components/heading.js +47 -0
- package/dist/src/dom/components/heading.js.map +10 -0
- package/dist/src/dom/components/image.js +39 -0
- package/dist/src/dom/components/image.js.map +10 -0
- package/dist/src/dom/components/index.js +217 -0
- package/dist/src/dom/components/index.js.map +10 -0
- package/dist/src/dom/components/input.js +41 -0
- package/dist/src/dom/components/input.js.map +10 -0
- package/dist/src/dom/components/link.js +42 -0
- package/dist/src/dom/components/link.js.map +10 -0
- package/dist/src/dom/components/list.js +42 -0
- package/dist/src/dom/components/list.js.map +10 -0
- package/dist/src/dom/components/paragraph.js +35 -0
- package/dist/src/dom/components/paragraph.js.map +10 -0
- package/dist/src/dom/components/progressbar.js +57 -0
- package/dist/src/dom/components/progressbar.js.map +10 -0
- package/dist/src/dom/components/route.js +44 -0
- package/dist/src/dom/components/route.js.map +10 -0
- package/dist/src/dom/components/router.js +33 -0
- package/dist/src/dom/components/router.js.map +10 -0
- package/dist/src/dom/components/row.js +31 -0
- package/dist/src/dom/components/row.js.map +10 -0
- package/dist/src/dom/components/select.js +57 -0
- package/dist/src/dom/components/select.js.map +10 -0
- package/dist/src/dom/components/slider.js +48 -0
- package/dist/src/dom/components/slider.js.map +10 -0
- package/dist/src/dom/components/spacer.js +30 -0
- package/dist/src/dom/components/spacer.js.map +10 -0
- package/dist/src/dom/components/spinner.js +65 -0
- package/dist/src/dom/components/spinner.js.map +10 -0
- package/dist/src/dom/components/stack.js +45 -0
- package/dist/src/dom/components/stack.js.map +10 -0
- package/dist/src/dom/components/switch.js +83 -0
- package/dist/src/dom/components/switch.js.map +10 -0
- package/dist/src/dom/components/text.js +37 -0
- package/dist/src/dom/components/text.js.map +10 -0
- package/dist/src/dom/components/textarea.js +51 -0
- package/dist/src/dom/components/textarea.js.map +10 -0
- package/dist/src/dom/components/video.js +51 -0
- package/dist/src/dom/components/video.js.map +10 -0
- package/dist/src/dom/debug.js +170 -0
- package/dist/src/dom/debug.js.map +10 -0
- package/dist/src/dom/events.js +112 -0
- package/dist/src/dom/events.js.map +10 -0
- package/dist/src/dom/index.js +73 -0
- package/dist/src/dom/index.js.map +9 -0
- package/dist/src/dom/renderer.js +277 -0
- package/dist/src/dom/renderer.js.map +10 -0
- package/dist/src/index.js +89 -0
- package/dist/src/index.js.map +9 -0
- package/package.json +84 -0
- package/src/canvas/QUICKSTART.md +421 -0
- package/src/canvas/README.md +376 -0
- package/src/canvas/accessibility.ts +218 -0
- package/src/canvas/events.ts +307 -0
- package/src/canvas/index.ts +35 -0
- package/src/canvas/input.ts +210 -0
- package/src/canvas/layout.ts +401 -0
- package/src/canvas/paint.ts +1321 -0
- package/src/canvas/renderer.ts +422 -0
- package/src/canvas/text.ts +182 -0
- package/src/canvas/types.ts +137 -0
- package/src/canvas/utils.ts +218 -0
- package/src/dom/README.md +265 -0
- package/src/dom/applicators/advanced-layout.ts +128 -0
- package/src/dom/applicators/background.ts +50 -0
- package/src/dom/applicators/border.ts +19 -0
- package/src/dom/applicators/color.ts +23 -0
- package/src/dom/applicators/display.ts +54 -0
- package/src/dom/applicators/effects.ts +97 -0
- package/src/dom/applicators/events.ts +689 -0
- package/src/dom/applicators/font.ts +27 -0
- package/src/dom/applicators/index.ts +354 -0
- package/src/dom/applicators/layout.ts +92 -0
- package/src/dom/applicators/margin.ts +18 -0
- package/src/dom/applicators/padding.ts +18 -0
- package/src/dom/applicators/size.ts +31 -0
- package/src/dom/applicators/transform.ts +93 -0
- package/src/dom/applicators/transition.ts +65 -0
- package/src/dom/applicators/typography.ts +91 -0
- package/src/dom/canvas/index.ts +60 -0
- package/src/dom/components/audio.ts +45 -0
- package/src/dom/components/avatar.ts +49 -0
- package/src/dom/components/badge.ts +45 -0
- package/src/dom/components/button.ts +13 -0
- package/src/dom/components/card.ts +19 -0
- package/src/dom/components/center.ts +16 -0
- package/src/dom/components/checkbox.ts +54 -0
- package/src/dom/components/column.ts +15 -0
- package/src/dom/components/container.ts +13 -0
- package/src/dom/components/divider.ts +37 -0
- package/src/dom/components/grid.ts +40 -0
- package/src/dom/components/heading.ts +41 -0
- package/src/dom/components/image.ts +27 -0
- package/src/dom/components/index.ts +115 -0
- package/src/dom/components/input.ts +29 -0
- package/src/dom/components/link.ts +35 -0
- package/src/dom/components/list.ts +30 -0
- package/src/dom/components/paragraph.ts +23 -0
- package/src/dom/components/progressbar.ts +51 -0
- package/src/dom/components/route.ts +37 -0
- package/src/dom/components/router.ts +22 -0
- package/src/dom/components/row.ts +15 -0
- package/src/dom/components/select.ts +56 -0
- package/src/dom/components/slider.ts +45 -0
- package/src/dom/components/spacer.ts +16 -0
- package/src/dom/components/spinner.ts +60 -0
- package/src/dom/components/stack.ts +34 -0
- package/src/dom/components/switch.ts +86 -0
- package/src/dom/components/text.ts +24 -0
- package/src/dom/components/textarea.ts +50 -0
- package/src/dom/components/video.ts +50 -0
- package/src/dom/debug.ts +247 -0
- package/src/dom/events.ts +168 -0
- package/src/dom/index.ts +11 -0
- package/src/dom/renderer.ts +327 -0
- package/src/index.ts +56 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/canvas/paint.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Paint System\n *\n * Drawing virtual nodes to canvas\n */\n\nimport type { VirtualNode, PainterFunction } from \"./types.js\";\nimport { renderText } from \"./text.js\";\n\n/**\n * Custom painters registry\n */\nconst customPainters = new Map<string, PainterFunction>();\n\n/**\n * Register a custom painter for a component type\n */\nexport function registerPainter(type: string, painter: PainterFunction): void {\n customPainters.set(type.toLowerCase(), painter);\n}\n\n/**\n * Paint a virtual node and its children\n */\nexport function paintNode(ctx: CanvasRenderingContext2D, node: VirtualNode): void {\n if (!node.visible || !node.layout) return;\n\n ctx.save();\n\n // Apply transforms\n applyTransforms(ctx, node);\n\n // Apply opacity\n if (node.opacity < 1) {\n ctx.globalAlpha = node.opacity;\n }\n\n // Check for custom painter\n const customPainter = customPainters.get(node.type.toLowerCase());\n if (customPainter) {\n customPainter(ctx, node);\n ctx.restore();\n return;\n }\n\n // Default painting based on type\n switch (node.type.toLowerCase()) {\n case \"column\":\n case \"row\":\n case \"stack\":\n paintContainer(ctx, node);\n break;\n case \"text\":\n paintText(ctx, node);\n break;\n case \"button\":\n paintButton(ctx, node);\n break;\n case \"input\":\n paintInput(ctx, node);\n break;\n case \"image\":\n paintImage(ctx, node);\n break;\n case \"spacer\":\n // Spacer is invisible, just takes up space\n break;\n case \"divider\":\n case \"separator\":\n paintDivider(ctx, node);\n break;\n case \"checkbox\":\n paintCheckbox(ctx, node);\n break;\n case \"radio\":\n paintRadio(ctx, node);\n break;\n case \"switch\":\n case \"toggle\":\n paintSwitch(ctx, node);\n break;\n case \"slider\":\n paintSlider(ctx, node);\n break;\n case \"progress\":\n case \"progressbar\":\n paintProgress(ctx, node);\n break;\n case \"spinner\":\n case \"loading\":\n paintSpinner(ctx, node);\n break;\n case \"card\":\n paintCard(ctx, node);\n break;\n case \"badge\":\n paintBadge(ctx, node);\n break;\n case \"avatar\":\n paintAvatar(ctx, node);\n break;\n case \"icon\":\n paintIcon(ctx, node);\n break;\n case \"link\":\n paintLink(ctx, node);\n break;\n case \"container\":\n case \"box\":\n paintContainer(ctx, node);\n break;\n default:\n paintContainer(ctx, node);\n }\n\n ctx.restore();\n\n // Paint children\n for (const child of node.children) {\n paintNode(ctx, child);\n }\n\n // Restore context if overflow clipping was applied\n if ((node as any)._needsRestore) {\n ctx.restore();\n delete (node as any)._needsRestore;\n }\n}\n\n/**\n * Paint a container (Column, Row, Stack, Box)\n */\nfunction paintContainer(ctx: CanvasRenderingContext2D, node: VirtualNode): void {\n const layout = node.layout!;\n const props = node.props;\n\n const x = layout.x;\n const y = layout.y;\n const width = layout.width;\n const height = layout.height;\n const radius = layout.border.radius;\n\n // Apply shadow if specified\n const shadow = props.shadow || props.boxShadow;\n if (shadow) {\n applyShadow(ctx, shadow);\n }\n\n // Draw background\n const backgroundColor = props.backgroundColor || props.background;\n if (backgroundColor) {\n // Support for gradients\n if (typeof backgroundColor === \"string\" && backgroundColor.includes(\"gradient\")) {\n ctx.fillStyle = parseGradient(ctx, backgroundColor, x, y, width, height);\n } else {\n ctx.fillStyle = backgroundColor;\n }\n\n if (radius > 0) {\n drawRoundedRect(ctx, x, y, width, height, radius);\n ctx.fill();\n } else {\n ctx.fillRect(x, y, width, height);\n }\n }\n\n // Reset shadow for border\n if (shadow) {\n ctx.shadowColor = \"transparent\";\n ctx.shadowBlur = 0;\n ctx.shadowOffsetX = 0;\n ctx.shadowOffsetY = 0;\n }\n\n // Draw border\n if (layout.border.width > 0 && layout.border.color !== \"transparent\") {\n ctx.strokeStyle = layout.border.color;\n ctx.lineWidth = layout.border.width;\n if (radius > 0) {\n drawRoundedRect(ctx, x, y, width, height, radius);\n ctx.stroke();\n } else {\n ctx.strokeRect(x, y, width, height);\n }\n }\n\n // Apply overflow clipping for children\n const overflow = props.overflow || \"visible\";\n if (overflow === \"hidden\" || overflow === \"scroll\" || overflow === \"auto\") {\n ctx.save();\n ctx.beginPath();\n if (radius > 0) {\n drawRoundedRect(ctx, x, y, width, height, radius);\n } else {\n ctx.rect(x, y, width, height);\n }\n ctx.clip();\n\n // Mark that we need to restore later\n (node as any)._needsRestore = true;\n }\n}\n\n/**\n * Paint text node\n */\nfunction paintText(ctx: CanvasRenderingContext2D, node: VirtualNode): void {\n const layout = node.layout!;\n const props = node.props;\n\n let text = String(props[0] || props.text || \"\");\n const color = props.color || \"#000000\";\n const fontSize = parseFloat(props.fontSize) || 16;\n const fontWeight = props.fontWeight || \"normal\";\n const fontFamily = props.fontFamily || \"system-ui, sans-serif\";\n const textAlign = props.textAlign || \"left\";\n const lineHeight = parseFloat(props.lineHeight) || fontSize * 1.2;\n const textDecoration = props.textDecoration || \"none\";\n const textTransform = props.textTransform || \"none\";\n const letterSpacing = parseFloat(props.letterSpacing) || 0;\n\n // Apply text transform\n if (textTransform === \"uppercase\") {\n text = text.toUpperCase();\n } else if (textTransform === \"lowercase\") {\n text = text.toLowerCase();\n } else if (textTransform === \"capitalize\") {\n text = text.replace(/\\b\\w/g, (char) => char.toUpperCase());\n }\n\n // Apply text shadow if specified\n const textShadow = props.textShadow || props.shadow;\n if (textShadow) {\n applyShadow(ctx, textShadow);\n }\n\n // Handle letter spacing\n const x = layout.x + layout.contentX;\n const y = layout.y + layout.contentY;\n\n if (letterSpacing !== 0) {\n // Manual letter spacing\n ctx.fillStyle = color;\n ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;\n ctx.textAlign = \"left\";\n ctx.textBaseline = \"top\";\n\n let currentX = x;\n for (let i = 0; i < text.length; i++) {\n ctx.fillText(text[i], currentX, y);\n currentX += ctx.measureText(text[i]).width;\n // Add letter spacing after each character except the last\n if (i < text.length - 1) {\n currentX += letterSpacing;\n }\n }\n\n // Text decoration with letter spacing (now correct width)\n if (textDecoration !== \"none\") {\n applyTextDecoration(ctx, textDecoration, color, x, y, currentX - x, fontSize);\n }\n } else {\n // Normal text rendering\n renderText(\n ctx,\n text,\n x,\n y,\n layout.contentWidth,\n layout.contentHeight,\n {\n color,\n fontSize,\n fontWeight,\n fontFamily,\n textAlign: textAlign as any,\n verticalAlign: \"top\",\n lineHeight,\n }\n );\n\n // Text decoration (need to set font again for measurement)\n if (textDecoration !== \"none\") {\n ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;\n const textWidth = ctx.measureText(text).width;\n applyTextDecoration(ctx, textDecoration, color, x, y, textWidth, fontSize);\n }\n }\n\n // Reset shadow\n if (textShadow) {\n ctx.shadowColor = \"transparent\";\n ctx.shadowBlur = 0;\n ctx.shadowOffsetX = 0;\n ctx.shadowOffsetY = 0;\n }\n}\n\n/**\n * Paint button node\n */\nfunction paintButton(ctx: CanvasRenderingContext2D, node: VirtualNode): void {\n const layout = node.layout!;\n const props = node.props;\n\n const x = layout.x;\n const y = layout.y;\n const width = layout.width;\n const height = layout.height;\n const radius = layout.border.radius || 4;\n\n // Apply shadow if specified\n const shadow = props.shadow || props.boxShadow;\n if (shadow) {\n applyShadow(ctx, shadow);\n }\n\n // Background color based on state\n let backgroundColor = props.backgroundColor || \"#007bff\";\n if (node.hovered) {\n backgroundColor = props.hoverColor || \"#0056b3\";\n }\n if (node.focused) {\n backgroundColor = props.focusColor || \"#004085\";\n }\n\n // Support gradients for button background\n if (typeof backgroundColor === \"string\" && backgroundColor.includes(\"gradient\")) {\n ctx.fillStyle = parseGradient(ctx, backgroundColor, x, y, width, height);\n } else {\n ctx.fillStyle = backgroundColor;\n }\n\n // Draw button background\n drawRoundedRect(ctx, x, y, width, height, radius);\n ctx.fill();\n\n // Reset shadow for border\n if (shadow) {\n ctx.shadowColor = \"transparent\";\n ctx.shadowBlur = 0;\n ctx.shadowOffsetX = 0;\n ctx.shadowOffsetY = 0;\n }\n\n // Draw border\n if (layout.border.width > 0) {\n ctx.strokeStyle = layout.border.color;\n ctx.lineWidth = layout.border.width;\n drawRoundedRect(ctx, x, y, width, height, radius);\n ctx.stroke();\n }\n\n // Paint children (typically Text)\n for (const child of node.children) {\n paintNode(ctx, child);\n }\n}\n\n/**\n * Paint input node\n */\nfunction paintInput(ctx: CanvasRenderingContext2D, node: VirtualNode): void {\n const layout = node.layout!;\n const props = node.props;\n\n const x = layout.x;\n const y = layout.y;\n const width = layout.width;\n const height = layout.height;\n const radius = layout.border.radius || 4;\n\n // Background\n ctx.fillStyle = props.backgroundColor || \"#ffffff\";\n drawRoundedRect(ctx, x, y, width, height, radius);\n ctx.fill();\n\n // Border (thicker if focused)\n const borderColor = node.focused ? \"#007bff\" : (layout.border.color || \"#cccccc\");\n const borderWidth = node.focused ? 2 : (layout.border.width || 1);\n ctx.strokeStyle = borderColor;\n ctx.lineWidth = borderWidth;\n drawRoundedRect(ctx, x, y, width, height, radius);\n ctx.stroke();\n\n // Input value text\n const value = props.value || \"\";\n const placeholder = props.placeholder || \"\";\n const text = value || placeholder;\n const textColor = value ? (props.color || \"#000000\") : \"#999999\";\n\n if (text) {\n const fontSize = parseFloat(props.fontSize) || 16;\n const fontWeight = props.fontWeight || \"normal\";\n const fontFamily = props.fontFamily || \"system-ui, sans-serif\";\n const lineHeight = parseFloat(props.lineHeight) || fontSize * 1.2;\n\n renderText(\n ctx,\n text,\n layout.x + layout.contentX,\n layout.y + layout.contentY,\n layout.contentWidth,\n layout.contentHeight,\n {\n color: textColor,\n fontSize,\n fontWeight,\n fontFamily,\n textAlign: \"left\",\n verticalAlign: \"middle\",\n lineHeight,\n }\n );\n }\n}\n\n/**\n * Paint image node\n */\nfunction paintImage(ctx: CanvasRenderingContext2D, node: VirtualNode): void {\n const layout = node.layout!;\n const props = node.props;\n\n const src = props.src || props[0];\n if (!src) return;\n\n // TODO: Load and cache images\n // For now, just draw a placeholder\n const x = layout.x;\n const y = layout.y;\n const width = layout.width;\n const height = layout.height;\n\n ctx.fillStyle = \"#e0e0e0\";\n ctx.fillRect(x, y, width, height);\n\n ctx.strokeStyle = \"#999999\";\n ctx.lineWidth = 1;\n ctx.strokeRect(x, y, width, height);\n\n // Draw \"IMG\" text\n ctx.fillStyle = \"#666666\";\n ctx.font = \"14px sans-serif\";\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(\"IMG\", x + width / 2, y + height / 2);\n}\n\n/**\n * Draw rounded rectangle path\n */\nfunction drawRoundedRect(\n ctx: CanvasRenderingContext2D,\n x: number,\n y: number,\n width: number,\n height: number,\n radius: number\n): void {\n if (radius <= 0) {\n ctx.rect(x, y, width, height);\n return;\n }\n\n ctx.beginPath();\n ctx.moveTo(x + radius, y);\n ctx.lineTo(x + width - radius, y);\n ctx.arcTo(x + width, y, x + width, y + radius, radius);\n ctx.lineTo(x + width, y + height - radius);\n ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius);\n ctx.lineTo(x + radius, y + height);\n ctx.arcTo(x, y + height, x, y + height - radius, radius);\n ctx.lineTo(x, y + radius);\n ctx.arcTo(x, y, x + radius, y, radius);\n ctx.closePath();\n}\n\n/**\n * Apply shadow to canvas context\n * Supports both simple and CSS-like shadow syntax:\n * - Simple: \"2 2 4 rgba(0,0,0,0.3)\"\n * - CSS-like: \"2px 2px 4px rgba(0,0,0,0.3)\"\n * - Named: {offsetX: 2, offsetY: 2, blur: 4, color: \"rgba(0,0,0,0.3)\"}\n */\nfunction applyShadow(ctx: CanvasRenderingContext2D, shadow: any): void {\n if (typeof shadow === \"string\") {\n // Parse CSS-like shadow string: \"offsetX offsetY blur color\"\n const parts = shadow.trim().split(/\\s+/);\n if (parts.length >= 3) {\n const offsetX = parseFloat(parts[0]);\n const offsetY = parseFloat(parts[1]);\n const blur = parseFloat(parts[2]);\n const color = parts.slice(3).join(\" \") || \"rgba(0,0,0,0.3)\";\n\n ctx.shadowOffsetX = offsetX;\n ctx.shadowOffsetY = offsetY;\n ctx.shadowBlur = blur;\n ctx.shadowColor = color;\n }\n } else if (typeof shadow === \"object\") {\n // Object syntax\n ctx.shadowOffsetX = shadow.offsetX || 0;\n ctx.shadowOffsetY = shadow.offsetY || 0;\n ctx.shadowBlur = shadow.blur || 0;\n ctx.shadowColor = shadow.color || \"rgba(0,0,0,0.3)\";\n }\n}\n\n/**\n * Parse gradient string and create canvas gradient\n * Supports:\n * - linear-gradient(direction, color1, color2, ...)\n * - radial-gradient(color1, color2, ...)\n */\nfunction parseGradient(\n ctx: CanvasRenderingContext2D,\n gradientStr: string,\n x: number,\n y: number,\n width: number,\n height: number\n): CanvasGradient | string {\n // Simple gradient parsing\n if (gradientStr.startsWith(\"linear-gradient\")) {\n // Extract content between parentheses\n const match = gradientStr.match(/linear-gradient\\((.*)\\)/);\n if (!match) return gradientStr;\n\n const parts = match[1].split(\",\").map((s) => s.trim());\n\n // Determine direction (default to bottom)\n let x0 = x, y0 = y, x1 = x, y1 = y + height;\n let colorStart = 0;\n\n if (parts[0].includes(\"deg\") || parts[0].includes(\"to \")) {\n colorStart = 1;\n const direction = parts[0];\n\n if (direction.includes(\"to right\") || direction === \"90deg\") {\n x1 = x + width;\n y1 = y;\n } else if (direction.includes(\"to left\") || direction === \"270deg\") {\n x0 = x + width;\n x1 = x;\n y0 = y;\n y1 = y;\n } else if (direction.includes(\"to top\") || direction === \"0deg\") {\n y0 = y + height;\n y1 = y;\n }\n // Default is \"to bottom\" which we already set\n }\n\n const gradient = ctx.createLinearGradient(x0, y0, x1, y1);\n\n // Add color stops\n const colors = parts.slice(colorStart);\n colors.forEach((color, i) => {\n const stop = i / (colors.length - 1);\n gradient.addColorStop(stop, color.trim());\n });\n\n return gradient;\n } else if (gradientStr.startsWith(\"radial-gradient\")) {\n // Extract content between parentheses\n const match = gradientStr.match(/radial-gradient\\((.*)\\)/);\n if (!match) return gradientStr;\n\n const parts = match[1].split(\",\").map((s) => s.trim());\n\n // Create radial gradient from center\n const centerX = x + width / 2;\n const centerY = y + height / 2;\n const radius = Math.max(width, height) / 2;\n\n const gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, radius);\n\n // Add color stops\n parts.forEach((color, i) => {\n const stop = i / (parts.length - 1);\n gradient.addColorStop(stop, color.trim());\n });\n\n return gradient;\n }\n\n return gradientStr;\n}\n\n/**\n * Apply CSS-like transforms to canvas context\n * Supports:\n * - translate(x, y)\n * - rotate(angle)\n * - scale(x, y)\n * - skew(x, y)\n * - transform: \"translate(10, 20) rotate(45) scale(1.5)\"\n */\nfunction applyTransforms(ctx: CanvasRenderingContext2D, node: VirtualNode): void {\n const props = node.props;\n const layout = node.layout!;\n\n // Get transform origin (default to center)\n const originX = parseFloat(props.transformOriginX) || 0.5;\n const originY = parseFloat(props.transformOriginY) || 0.5;\n\n const centerX = layout.x + layout.width * originX;\n const centerY = layout.y + layout.height * originY;\n\n // Check for individual transform properties\n const translateX = parseFloat(props.translateX) || 0;\n const translateY = parseFloat(props.translateY) || 0;\n const rotate = parseFloat(props.rotate) || 0; // in degrees\n const scaleX = parseFloat(props.scaleX) || parseFloat(props.scale) || 1;\n const scaleY = parseFloat(props.scaleY) || parseFloat(props.scale) || 1;\n const skewX = parseFloat(props.skewX) || parseFloat(props.skew) || 0; // in degrees\n const skewY = parseFloat(props.skewY) || 0; // in degrees\n\n // Apply transforms in order: translate to origin, scale, rotate, skew, translate back, translate by offset\n if (translateX !== 0 || translateY !== 0 || rotate !== 0 || scaleX !== 1 || scaleY !== 1 || skewX !== 0 || skewY !== 0) {\n // Move to transform origin\n ctx.translate(centerX, centerY);\n\n // Apply scale\n if (scaleX !== 1 || scaleY !== 1) {\n ctx.scale(scaleX, scaleY);\n }\n\n // Apply rotation (convert degrees to radians)\n if (rotate !== 0) {\n ctx.rotate((rotate * Math.PI) / 180);\n }\n\n // Apply skew using transform matrix\n if (skewX !== 0 || skewY !== 0) {\n const skewXRad = (skewX * Math.PI) / 180;\n const skewYRad = (skewY * Math.PI) / 180;\n ctx.transform(1, Math.tan(skewYRad), Math.tan(skewXRad), 1, 0, 0);\n }\n\n // Move back from origin and apply translation\n ctx.translate(-centerX + translateX, -centerY + translateY);\n }\n\n // Also support compound transform string (optional, for future extensibility)\n if (props.transform && typeof props.transform === \"string\") {\n parseTransformString(ctx, props.transform, centerX, centerY);\n }\n}\n\n/**\n * Parse and apply a CSS-like transform string\n */\nfunction parseTransformString(\n ctx: CanvasRenderingContext2D,\n transformStr: string,\n originX: number,\n originY: number\n): void {\n // Simple transform parsing - matches translate(), rotate(), scale()\n const transforms = transformStr.match(/(\\w+)\\(([^)]+)\\)/g);\n if (!transforms) return;\n\n ctx.translate(originX, originY);\n\n for (const transform of transforms) {\n const match = transform.match(/(\\w+)\\(([^)]+)\\)/);\n if (!match) continue;\n\n const [, func, args] = match;\n const values = args.split(\",\").map((v) => parseFloat(v.trim()));\n\n switch (func.toLowerCase()) {\n case \"translate\":\n ctx.translate(values[0] || 0, values[1] || 0);\n break;\n case \"rotate\":\n ctx.rotate((values[0] * Math.PI) / 180);\n break;\n case \"scale\":\n ctx.scale(values[0] || 1, values[1] || values[0] || 1);\n break;\n }\n }\n\n ctx.translate(-originX, -originY);\n}\n\n/**\n * Paint divider/separator node\n */\nfunction paintDivider(ctx: CanvasRenderingContext2D, node: VirtualNode): void {\n const layout = node.layout!;\n const props = node.props;\n\n const orientation = props.orientation || \"horizontal\";\n const color = props.color || props.backgroundColor || \"#e0e0e0\";\n const thickness = parseFloat(props.thickness) || 1;\n\n ctx.strokeStyle = color;\n ctx.lineWidth = thickness;\n ctx.beginPath();\n\n if (orientation === \"vertical\") {\n const x = layout.x + layout.width / 2;\n ctx.moveTo(x, layout.y);\n ctx.lineTo(x, layout.y + layout.height);\n } else {\n const y = layout.y + layout.height / 2;\n ctx.moveTo(layout.x, y);\n ctx.lineTo(layout.x + layout.width, y);\n }\n\n ctx.stroke();\n}\n\n/**\n * Paint checkbox node\n */\nfunction paintCheckbox(ctx: CanvasRenderingContext2D, node: VirtualNode): void {\n const layout = node.layout!;\n const props = node.props;\n\n const size = Math.min(layout.width, layout.height);\n const x = layout.x;\n const y = layout.y;\n\n // Properly parse boolean values (handle string \"false\")\n const checkedValue = props.checked !== undefined ? props.checked : props.value;\n const checked = checkedValue === true || checkedValue === \"true\" || (checkedValue !== false && checkedValue !== \"false\" && !!checkedValue);\n\n const radius = parseFloat(props.borderRadius) || 2;\n\n // Background\n const bgColor = checked ? (props.checkedColor || \"#007bff\") : (props.backgroundColor || \"#ffffff\");\n ctx.fillStyle = bgColor;\n drawRoundedRect(ctx, x, y, size, size, radius);\n ctx.fill();\n\n // Border\n const borderColor = checked ? (props.checkedColor || \"#007bff\") : (props.borderColor || \"#cccccc\");\n ctx.strokeStyle = borderColor;\n ctx.lineWidth = node.focused ? 2 : 1;\n drawRoundedRect(ctx, x, y, size, size, radius);\n ctx.stroke();\n\n // Checkmark\n if (checked) {\n ctx.strokeStyle = props.checkColor || \"#ffffff\";\n ctx.lineWidth = 2;\n ctx.lineCap = \"round\";\n ctx.lineJoin = \"round\";\n\n const padding = size * 0.25;\n ctx.beginPath();\n ctx.moveTo(x + padding, y + size / 2);\n ctx.lineTo(x + size * 0.4, y + size - padding);\n ctx.lineTo(x + size - padding, y + padding);\n ctx.stroke();\n }\n}\n\n/**\n * Paint radio button node\n */\nfunction paintRadio(ctx: CanvasRenderingContext2D, node: VirtualNode): void {\n const layout = node.layout!;\n const props = node.props;\n\n const size = Math.min(layout.width, layout.height);\n const centerX = layout.x + size / 2;\n const centerY = layout.y + size / 2;\n const radius = size / 2;\n\n // Properly parse boolean values (handle string \"false\")\n const checkedValue = props.checked !== undefined ? props.checked : props.value;\n const checked = checkedValue === true || checkedValue === \"true\" || (checkedValue !== false && checkedValue !== \"false\" && !!checkedValue);\n\n // Outer circle background\n const bgColor = props.backgroundColor || \"#ffffff\";\n ctx.fillStyle = bgColor;\n ctx.beginPath();\n ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);\n ctx.fill();\n\n // Outer circle border\n const borderColor = checked ? (props.checkedColor || \"#007bff\") : (props.borderColor || \"#cccccc\");\n ctx.strokeStyle = borderColor;\n ctx.lineWidth = node.focused ? 2 : 1;\n ctx.beginPath();\n ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);\n ctx.stroke();\n\n // Inner filled circle when checked\n if (checked) {\n ctx.fillStyle = props.checkedColor || \"#007bff\";\n ctx.beginPath();\n ctx.arc(centerX, centerY, radius * 0.5, 0, Math.PI * 2);\n ctx.fill();\n }\n}\n\n/**\n * Paint switch/toggle node\n */\nfunction paintSwitch(ctx: CanvasRenderingContext2D, node: VirtualNode): void {\n const layout = node.layout!;\n const props = node.props;\n\n const width = layout.width;\n const height = layout.height;\n const x = layout.x;\n const y = layout.y;\n\n // Properly parse boolean values (handle string \"false\")\n const checkedValue = props.checked !== undefined ? props.checked : props.value;\n const checked = checkedValue === true || checkedValue === \"true\" || (checkedValue !== false && checkedValue !== \"false\" && !!checkedValue);\n\n const radius = height / 2;\n\n // Track background\n const trackColor = checked ? (props.checkedColor || \"#4caf50\") : (props.backgroundColor || \"#cccccc\");\n ctx.fillStyle = trackColor;\n drawRoundedRect(ctx, x, y, width, height, radius);\n ctx.fill();\n\n // Thumb (circle)\n const thumbRadius = radius * 0.8;\n const thumbX = checked ? (x + width - radius) : (x + radius);\n const thumbY = y + radius;\n\n ctx.fillStyle = props.thumbColor || \"#ffffff\";\n ctx.beginPath();\n ctx.arc(thumbX, thumbY, thumbRadius, 0, Math.PI * 2);\n ctx.fill();\n\n // Thumb shadow\n if (props.shadow !== false) {\n ctx.shadowColor = \"rgba(0,0,0,0.2)\";\n ctx.shadowBlur = 2;\n ctx.shadowOffsetY = 1;\n ctx.beginPath();\n ctx.arc(thumbX, thumbY, thumbRadius, 0, Math.PI * 2);\n ctx.fill();\n ctx.shadowColor = \"transparent\";\n ctx.shadowBlur = 0;\n ctx.shadowOffsetY = 0;\n }\n}\n\n/**\n * Paint slider node\n */\nfunction paintSlider(ctx: CanvasRenderingContext2D, node: VirtualNode): void {\n const layout = node.layout!;\n const props = node.props;\n\n const width = layout.width;\n const height = layout.height;\n const x = layout.x;\n const y = layout.y;\n\n const min = parseFloat(props.min) || 0;\n const max = parseFloat(props.max) || 100;\n const value = parseFloat(props.value) || min;\n const percentage = (value - min) / (max - min);\n\n const trackHeight = parseFloat(props.trackHeight) || 4;\n const thumbSize = parseFloat(props.thumbSize) || 16;\n const trackY = y + (height - trackHeight) / 2;\n\n // Track background\n ctx.fillStyle = props.trackColor || \"#e0e0e0\";\n drawRoundedRect(ctx, x, trackY, width, trackHeight, trackHeight / 2);\n ctx.fill();\n\n // Track fill\n const fillWidth = width * percentage;\n const fillColor = props.fillColor || props.color || \"#007bff\";\n\n // Support gradient fills\n if (typeof fillColor === \"string\" && fillColor.includes(\"gradient\")) {\n ctx.fillStyle = parseGradient(ctx, fillColor, x, trackY, fillWidth, trackHeight);\n } else {\n ctx.fillStyle = fillColor;\n }\n\n drawRoundedRect(ctx, x, trackY, fillWidth, trackHeight, trackHeight / 2);\n ctx.fill();\n\n // Thumb\n const thumbX = x + fillWidth;\n const thumbY = y + height / 2;\n\n ctx.fillStyle = props.thumbColor || \"#007bff\";\n ctx.beginPath();\n ctx.arc(thumbX, thumbY, thumbSize / 2, 0, Math.PI * 2);\n ctx.fill();\n\n // Thumb border\n ctx.strokeStyle = \"#ffffff\";\n ctx.lineWidth = 2;\n ctx.beginPath();\n ctx.arc(thumbX, thumbY, thumbSize / 2, 0, Math.PI * 2);\n ctx.stroke();\n}\n\n/**\n * Paint progress bar node\n */\nfunction paintProgress(ctx: CanvasRenderingContext2D, node: VirtualNode): void {\n const layout = node.layout!;\n const props = node.props;\n\n const width = layout.width;\n const height = layout.height;\n const x = layout.x;\n const y = layout.y;\n\n const min = parseFloat(props.min) || 0;\n const max = parseFloat(props.max) || 100;\n const value = parseFloat(props.value) || 0;\n const percentage = Math.min(Math.max((value - min) / (max - min), 0), 1);\n const radius = layout.border.radius || height / 2;\n\n // Background\n ctx.fillStyle = props.backgroundColor || \"#e0e0e0\";\n drawRoundedRect(ctx, x, y, width, height, radius);\n ctx.fill();\n\n // Progress fill\n if (percentage > 0) {\n const fillWidth = width * percentage;\n\n // Support gradient fills\n const fillColor = props.fillColor || props.color || \"#007bff\";\n if (typeof fillColor === \"string\" && fillColor.includes(\"gradient\")) {\n ctx.fillStyle = parseGradient(ctx, fillColor, x, y, fillWidth, height);\n } else {\n ctx.fillStyle = fillColor;\n }\n\n drawRoundedRect(ctx, x, y, fillWidth, height, radius);\n ctx.fill();\n }\n\n // Optional text label\n if (props.showLabel) {\n const label = props.label || `${Math.round(percentage * 100)}%`;\n ctx.fillStyle = props.labelColor || \"#ffffff\";\n ctx.font = `${props.fontSize || 12}px ${props.fontFamily || \"sans-serif\"}`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(label, x + width / 2, y + height / 2);\n }\n}\n\n/**\n * Paint spinner/loading node\n */\nfunction paintSpinner(ctx: CanvasRenderingContext2D, node: VirtualNode): void {\n const layout = node.layout!;\n const props = node.props;\n\n const size = Math.min(layout.width, layout.height);\n const centerX = layout.x + size / 2;\n const centerY = layout.y + size / 2;\n const radius = size / 2 - 4;\n const thickness = parseFloat(props.thickness) || 4;\n const color = props.color || \"#007bff\";\n\n // Use timestamp for animation if available\n const rotation = (Date.now() / 1000) * Math.PI; // Rotate based on time\n\n ctx.strokeStyle = color;\n ctx.lineWidth = thickness;\n ctx.lineCap = \"round\";\n\n // Draw circular arc\n ctx.beginPath();\n ctx.arc(centerX, centerY, radius, rotation, rotation + Math.PI * 1.5);\n ctx.stroke();\n\n // Fade out effect\n ctx.globalAlpha = 0.3;\n ctx.beginPath();\n ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);\n ctx.stroke();\n ctx.globalAlpha = 1;\n}\n\n/**\n * Paint card node\n */\nfunction paintCard(ctx: CanvasRenderingContext2D, node: VirtualNode): void {\n const layout = node.layout!;\n const props = node.props;\n\n const x = layout.x;\n const y = layout.y;\n const width = layout.width;\n const height = layout.height;\n const radius = layout.border.radius || 8;\n\n // Default card shadow\n const shadow = props.shadow || props.boxShadow || \"0 2 8 rgba(0,0,0,0.1)\";\n applyShadow(ctx, shadow);\n\n // Background\n const backgroundColor = props.backgroundColor || \"#ffffff\";\n if (typeof backgroundColor === \"string\" && backgroundColor.includes(\"gradient\")) {\n ctx.fillStyle = parseGradient(ctx, backgroundColor, x, y, width, height);\n } else {\n ctx.fillStyle = backgroundColor;\n }\n\n drawRoundedRect(ctx, x, y, width, height, radius);\n ctx.fill();\n\n // Reset shadow\n ctx.shadowColor = \"transparent\";\n ctx.shadowBlur = 0;\n ctx.shadowOffsetX = 0;\n ctx.shadowOffsetY = 0;\n\n // Border\n if (layout.border.width > 0) {\n ctx.strokeStyle = layout.border.color;\n ctx.lineWidth = layout.border.width;\n drawRoundedRect(ctx, x, y, width, height, radius);\n ctx.stroke();\n }\n\n // Paint children\n for (const child of node.children) {\n paintNode(ctx, child);\n }\n}\n\n/**\n * Paint badge node\n */\nfunction paintBadge(ctx: CanvasRenderingContext2D, node: VirtualNode): void {\n const layout = node.layout!;\n const props = node.props;\n\n const x = layout.x;\n const y = layout.y;\n const width = layout.width;\n const height = layout.height;\n const radius = layout.border.radius || height / 2;\n\n // Background\n const backgroundColor = props.backgroundColor || \"#dc3545\";\n ctx.fillStyle = backgroundColor;\n drawRoundedRect(ctx, x, y, width, height, radius);\n ctx.fill();\n\n // Text content\n const text = String(props[0] || props.text || \"\");\n if (text) {\n ctx.fillStyle = props.color || \"#ffffff\";\n ctx.font = `${props.fontWeight || \"bold\"} ${props.fontSize || 10}px ${props.fontFamily || \"sans-serif\"}`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(text, x + width / 2, y + height / 2);\n }\n}\n\n/**\n * Paint avatar node\n */\nfunction paintAvatar(ctx: CanvasRenderingContext2D, node: VirtualNode): void {\n const layout = node.layout!;\n const props = node.props;\n\n const size = Math.min(layout.width, layout.height);\n const centerX = layout.x + size / 2;\n const centerY = layout.y + size / 2;\n const radius = size / 2;\n\n // Clip to circle\n ctx.save();\n ctx.beginPath();\n ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);\n ctx.clip();\n\n // Background\n const backgroundColor = props.backgroundColor || \"#cccccc\";\n ctx.fillStyle = backgroundColor;\n ctx.beginPath();\n ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);\n ctx.fill();\n\n // Text initials if provided\n const text = String(props[0] || props.text || props.initials || \"\");\n if (text) {\n ctx.fillStyle = props.color || \"#ffffff\";\n ctx.font = `${props.fontWeight || \"bold\"} ${props.fontSize || size / 2.5}px ${props.fontFamily || \"sans-serif\"}`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(text, centerX, centerY);\n }\n\n ctx.restore();\n\n // Border\n if (layout.border.width > 0) {\n ctx.strokeStyle = layout.border.color;\n ctx.lineWidth = layout.border.width;\n ctx.beginPath();\n ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);\n ctx.stroke();\n }\n}\n\n/**\n * Paint icon node (simple placeholder - real icons would need SVG path support)\n */\nfunction paintIcon(ctx: CanvasRenderingContext2D, node: VirtualNode): void {\n const layout = node.layout!;\n const props = node.props;\n\n const size = Math.min(layout.width, layout.height);\n const x = layout.x;\n const y = layout.y;\n const color = props.color || \"#000000\";\n const iconName = props.icon || props.name || \"circle\";\n\n ctx.fillStyle = color;\n ctx.strokeStyle = color;\n ctx.lineWidth = 2;\n\n // Simple icon shapes\n switch (iconName.toLowerCase()) {\n case \"circle\":\n ctx.beginPath();\n ctx.arc(x + size / 2, y + size / 2, size / 3, 0, Math.PI * 2);\n ctx.fill();\n break;\n\n case \"square\":\n const padding = size * 0.2;\n ctx.fillRect(x + padding, y + padding, size - padding * 2, size - padding * 2);\n break;\n\n case \"star\":\n drawStar(ctx, x + size / 2, y + size / 2, 5, size / 3, size / 6);\n ctx.fill();\n break;\n\n case \"check\":\n case \"checkmark\":\n const p = size * 0.2;\n ctx.beginPath();\n ctx.moveTo(x + p, y + size / 2);\n ctx.lineTo(x + size * 0.4, y + size - p);\n ctx.lineTo(x + size - p, y + p);\n ctx.stroke();\n break;\n\n case \"x\":\n case \"close\":\n const pd = size * 0.2;\n ctx.beginPath();\n ctx.moveTo(x + pd, y + pd);\n ctx.lineTo(x + size - pd, y + size - pd);\n ctx.moveTo(x + size - pd, y + pd);\n ctx.lineTo(x + pd, y + size - pd);\n ctx.stroke();\n break;\n\n default:\n // Default: filled circle\n ctx.beginPath();\n ctx.arc(x + size / 2, y + size / 2, size / 3, 0, Math.PI * 2);\n ctx.fill();\n }\n}\n\n/**\n * Paint link node\n */\nfunction paintLink(ctx: CanvasRenderingContext2D, node: VirtualNode): void {\n const layout = node.layout!;\n const props = node.props;\n\n const text = String(props[0] || props.text || \"\");\n const color = node.hovered ? (props.hoverColor || \"#0056b3\") : (props.color || \"#007bff\");\n const fontSize = parseFloat(props.fontSize) || 16;\n const fontWeight = props.fontWeight || \"normal\";\n const fontFamily = props.fontFamily || \"system-ui, sans-serif\";\n const textDecoration = props.textDecoration !== undefined ? props.textDecoration : \"underline\";\n\n ctx.fillStyle = color;\n ctx.font = `${fontWeight} ${fontSize}px ${fontFamily}`;\n ctx.textAlign = \"left\";\n ctx.textBaseline = \"top\";\n\n const x = layout.x + layout.contentX;\n const y = layout.y + layout.contentY;\n\n ctx.fillText(text, x, y);\n\n // Underline\n if (textDecoration === \"underline\") {\n const textWidth = ctx.measureText(text).width;\n ctx.strokeStyle = color;\n ctx.lineWidth = 1;\n ctx.beginPath();\n ctx.moveTo(x, y + fontSize + 2);\n ctx.lineTo(x + textWidth, y + fontSize + 2);\n ctx.stroke();\n }\n}\n\n/**\n * Apply text decoration (underline, line-through, etc.)\n */\nfunction applyTextDecoration(\n ctx: CanvasRenderingContext2D,\n decoration: string,\n color: string,\n x: number,\n y: number,\n width: number,\n fontSize: number\n): void {\n ctx.strokeStyle = color;\n ctx.lineWidth = Math.max(1, fontSize / 16);\n\n ctx.beginPath();\n\n if (decoration === \"underline\") {\n const underlineY = y + fontSize + 2;\n ctx.moveTo(x, underlineY);\n ctx.lineTo(x + width, underlineY);\n } else if (decoration === \"line-through\" || decoration === \"strikethrough\") {\n const lineThroughY = y + fontSize / 2;\n ctx.moveTo(x, lineThroughY);\n ctx.lineTo(x + width, lineThroughY);\n } else if (decoration === \"overline\") {\n ctx.moveTo(x, y);\n ctx.lineTo(x + width, y);\n }\n\n ctx.stroke();\n}\n\n/**\n * Apply overflow clipping\n */\nfunction applyOverflowClip(ctx: CanvasRenderingContext2D, node: VirtualNode): void {\n const props = node.props;\n const overflow = props.overflow || \"visible\";\n\n if (overflow === \"hidden\" || overflow === \"scroll\" || overflow === \"auto\") {\n const layout = node.layout!;\n const x = layout.x;\n const y = layout.y;\n const width = layout.width;\n const height = layout.height;\n const radius = layout.border.radius;\n\n ctx.save();\n ctx.beginPath();\n\n if (radius > 0) {\n drawRoundedRect(ctx, x, y, width, height, radius);\n } else {\n ctx.rect(x, y, width, height);\n }\n\n ctx.clip();\n }\n}\n\n/**\n * Helper: Draw a star shape\n */\nfunction drawStar(\n ctx: CanvasRenderingContext2D,\n cx: number,\n cy: number,\n spikes: number,\n outerRadius: number,\n innerRadius: number\n): void {\n let rot = (Math.PI / 2) * 3;\n let x = cx;\n let y = cy;\n const step = Math.PI / spikes;\n\n ctx.beginPath();\n ctx.moveTo(cx, cy - outerRadius);\n\n for (let i = 0; i < spikes; i++) {\n x = cx + Math.cos(rot) * outerRadius;\n y = cy + Math.sin(rot) * outerRadius;\n ctx.lineTo(x, y);\n rot += step;\n\n x = cx + Math.cos(rot) * innerRadius;\n y = cy + Math.sin(rot) * innerRadius;\n ctx.lineTo(x, y);\n rot += step;\n }\n\n ctx.lineTo(cx, cy - outerRadius);\n ctx.closePath();\n}\n\n\n\n\n\n\n\n\n\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "0E042DAE3A9F443064756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import {
|
|
2
|
+
paintNode,
|
|
3
|
+
registerPainter
|
|
4
|
+
} from "./paint.js";
|
|
5
|
+
import {
|
|
6
|
+
computeLayout
|
|
7
|
+
} from "./layout.js";
|
|
8
|
+
import {
|
|
9
|
+
CanvasEventManager
|
|
10
|
+
} from "./events.js";
|
|
11
|
+
import {
|
|
12
|
+
AccessibilityLayer
|
|
13
|
+
} from "./accessibility.js";
|
|
14
|
+
import"./text.js";
|
|
15
|
+
import {
|
|
16
|
+
InputOverlay
|
|
17
|
+
} from "./input.js";
|
|
18
|
+
import"./utils.js";
|
|
19
|
+
import"../../chunk-2s02mkzs.js";
|
|
20
|
+
|
|
21
|
+
// src/canvas/renderer.ts
|
|
22
|
+
var DEFAULT_OPTIONS = {
|
|
23
|
+
devicePixelRatio: typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1,
|
|
24
|
+
backgroundColor: "#ffffff",
|
|
25
|
+
enableAccessibility: true,
|
|
26
|
+
enableHitTesting: true,
|
|
27
|
+
enableInputOverlay: true,
|
|
28
|
+
enableDirtyRects: false,
|
|
29
|
+
enableLayerCaching: false,
|
|
30
|
+
maxLayerCacheSize: 10,
|
|
31
|
+
showLayoutBounds: false,
|
|
32
|
+
showDirtyRects: false,
|
|
33
|
+
logPerformance: false
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
class CanvasRenderer {
|
|
37
|
+
canvas;
|
|
38
|
+
ctx;
|
|
39
|
+
engine;
|
|
40
|
+
options;
|
|
41
|
+
rootNode = null;
|
|
42
|
+
nodes = new Map;
|
|
43
|
+
eventManager;
|
|
44
|
+
inputOverlay;
|
|
45
|
+
accessibilityLayer;
|
|
46
|
+
rafId = null;
|
|
47
|
+
needsRedraw = false;
|
|
48
|
+
frameCount = 0;
|
|
49
|
+
lastFrameTime = 0;
|
|
50
|
+
constructor(canvas, engine, options) {
|
|
51
|
+
this.canvas = canvas;
|
|
52
|
+
this.engine = engine;
|
|
53
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
54
|
+
const ctx = canvas.getContext("2d");
|
|
55
|
+
if (!ctx) {
|
|
56
|
+
throw new Error("Failed to get 2D context from canvas");
|
|
57
|
+
}
|
|
58
|
+
this.ctx = ctx;
|
|
59
|
+
this.setupHiDPI();
|
|
60
|
+
this.eventManager = new CanvasEventManager(canvas, engine);
|
|
61
|
+
this.inputOverlay = new InputOverlay(canvas.parentElement || (typeof document !== "undefined" ? document.body : null));
|
|
62
|
+
this.accessibilityLayer = new AccessibilityLayer(canvas.parentElement || (typeof document !== "undefined" ? document.body : null), this.options.enableAccessibility || false);
|
|
63
|
+
this.canvas.addEventListener("hypen:redraw", () => this.scheduleRedraw());
|
|
64
|
+
}
|
|
65
|
+
setupHiDPI() {
|
|
66
|
+
const dpr = this.options.devicePixelRatio || 1;
|
|
67
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
68
|
+
this.canvas.width = rect.width * dpr;
|
|
69
|
+
this.canvas.height = rect.height * dpr;
|
|
70
|
+
this.ctx.scale(dpr, dpr);
|
|
71
|
+
this.canvas.style.width = `${rect.width}px`;
|
|
72
|
+
this.canvas.style.height = `${rect.height}px`;
|
|
73
|
+
}
|
|
74
|
+
applyPatches(patches) {
|
|
75
|
+
for (const patch of patches) {
|
|
76
|
+
this.applyPatch(patch);
|
|
77
|
+
}
|
|
78
|
+
if (this.rootNode) {
|
|
79
|
+
this.accessibilityLayer.syncTree(this.rootNode);
|
|
80
|
+
}
|
|
81
|
+
this.scheduleRedraw();
|
|
82
|
+
}
|
|
83
|
+
applyPatch(patch) {
|
|
84
|
+
switch (patch.type) {
|
|
85
|
+
case "create":
|
|
86
|
+
this.onCreate(patch.id, patch.element_type, patch.props || {});
|
|
87
|
+
break;
|
|
88
|
+
case "setProp":
|
|
89
|
+
this.onSetProp(patch.id, patch.name, patch.value);
|
|
90
|
+
break;
|
|
91
|
+
case "setText":
|
|
92
|
+
this.onSetText(patch.id, patch.text);
|
|
93
|
+
break;
|
|
94
|
+
case "insert":
|
|
95
|
+
this.onInsert(patch.parent_id, patch.id, patch.before_id);
|
|
96
|
+
break;
|
|
97
|
+
case "move":
|
|
98
|
+
this.onMove(patch.parent_id, patch.id, patch.before_id);
|
|
99
|
+
break;
|
|
100
|
+
case "remove":
|
|
101
|
+
this.onRemove(patch.id);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
onCreate(id, elementType, props) {
|
|
106
|
+
const node = {
|
|
107
|
+
id,
|
|
108
|
+
type: elementType,
|
|
109
|
+
props: props instanceof Map ? Object.fromEntries(props) : props,
|
|
110
|
+
children: [],
|
|
111
|
+
parent: null,
|
|
112
|
+
visible: true,
|
|
113
|
+
opacity: parseFloat(props.opacity) || 1,
|
|
114
|
+
clickable: elementType === "button" || !!props.onclick,
|
|
115
|
+
hoverable: true,
|
|
116
|
+
focusable: elementType === "input" || elementType === "textarea" || elementType === "button",
|
|
117
|
+
focused: false,
|
|
118
|
+
hovered: false
|
|
119
|
+
};
|
|
120
|
+
this.nodes.set(id, node);
|
|
121
|
+
}
|
|
122
|
+
onSetProp(id, name, value) {
|
|
123
|
+
const node = this.nodes.get(id);
|
|
124
|
+
if (!node)
|
|
125
|
+
return;
|
|
126
|
+
node.props[name] = value;
|
|
127
|
+
if (name === "visible") {
|
|
128
|
+
node.visible = !!value;
|
|
129
|
+
}
|
|
130
|
+
if (name === "opacity") {
|
|
131
|
+
node.opacity = parseFloat(value) || 1;
|
|
132
|
+
}
|
|
133
|
+
this.accessibilityLayer.updateNode(node);
|
|
134
|
+
}
|
|
135
|
+
onSetText(id, text) {
|
|
136
|
+
const node = this.nodes.get(id);
|
|
137
|
+
if (!node)
|
|
138
|
+
return;
|
|
139
|
+
node.props[0] = text;
|
|
140
|
+
this.accessibilityLayer.updateNode(node);
|
|
141
|
+
}
|
|
142
|
+
onInsert(parentId, id, beforeId) {
|
|
143
|
+
const child = this.nodes.get(id);
|
|
144
|
+
if (!child)
|
|
145
|
+
return;
|
|
146
|
+
if (parentId === "root" && id === "root") {
|
|
147
|
+
this.rootNode = child;
|
|
148
|
+
this.eventManager.setRootNode(child);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const parent = this.nodes.get(parentId);
|
|
152
|
+
if (!parent) {
|
|
153
|
+
if (parentId === "root") {
|
|
154
|
+
this.rootNode = child;
|
|
155
|
+
this.eventManager.setRootNode(child);
|
|
156
|
+
}
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
child.parent = parent;
|
|
160
|
+
if (beforeId) {
|
|
161
|
+
const beforeIndex = parent.children.findIndex((c) => c.id === beforeId);
|
|
162
|
+
if (beforeIndex >= 0) {
|
|
163
|
+
parent.children.splice(beforeIndex, 0, child);
|
|
164
|
+
} else {
|
|
165
|
+
parent.children.push(child);
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
parent.children.push(child);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
onMove(parentId, id, beforeId) {
|
|
172
|
+
const node = this.nodes.get(id);
|
|
173
|
+
if (!node || !node.parent)
|
|
174
|
+
return;
|
|
175
|
+
const oldParent = node.parent;
|
|
176
|
+
const oldIndex = oldParent.children.indexOf(node);
|
|
177
|
+
if (oldIndex >= 0) {
|
|
178
|
+
oldParent.children.splice(oldIndex, 1);
|
|
179
|
+
}
|
|
180
|
+
this.onInsert(parentId, id, beforeId);
|
|
181
|
+
}
|
|
182
|
+
onRemove(id) {
|
|
183
|
+
const node = this.nodes.get(id);
|
|
184
|
+
if (!node)
|
|
185
|
+
return;
|
|
186
|
+
if (node.parent) {
|
|
187
|
+
const index = node.parent.children.indexOf(node);
|
|
188
|
+
if (index >= 0) {
|
|
189
|
+
node.parent.children.splice(index, 1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (this.rootNode === node) {
|
|
193
|
+
this.rootNode = null;
|
|
194
|
+
this.eventManager.setRootNode(null);
|
|
195
|
+
}
|
|
196
|
+
this.nodes.delete(id);
|
|
197
|
+
}
|
|
198
|
+
scheduleRedraw() {
|
|
199
|
+
if (this.rafId !== null)
|
|
200
|
+
return;
|
|
201
|
+
if (typeof requestAnimationFrame !== "undefined") {
|
|
202
|
+
this.rafId = requestAnimationFrame(() => {
|
|
203
|
+
this.render();
|
|
204
|
+
this.rafId = null;
|
|
205
|
+
});
|
|
206
|
+
} else {
|
|
207
|
+
this.render();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
render() {
|
|
211
|
+
const startTime = performance.now();
|
|
212
|
+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
213
|
+
if (this.options.backgroundColor) {
|
|
214
|
+
this.ctx.fillStyle = this.options.backgroundColor;
|
|
215
|
+
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
|
216
|
+
}
|
|
217
|
+
if (this.rootNode) {
|
|
218
|
+
const dpr = this.options.devicePixelRatio || 1;
|
|
219
|
+
computeLayout(this.ctx, this.rootNode, this.canvas.width / dpr, this.canvas.height / dpr);
|
|
220
|
+
paintNode(this.ctx, this.rootNode);
|
|
221
|
+
if (this.options.showLayoutBounds) {
|
|
222
|
+
this.drawLayoutBounds(this.rootNode);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (this.options.logPerformance) {
|
|
226
|
+
const elapsed = performance.now() - startTime;
|
|
227
|
+
this.frameCount++;
|
|
228
|
+
if (performance.now() - this.lastFrameTime > 1000) {
|
|
229
|
+
console.log(`Canvas FPS: ${this.frameCount}, Last frame: ${elapsed.toFixed(2)}ms`);
|
|
230
|
+
this.frameCount = 0;
|
|
231
|
+
this.lastFrameTime = performance.now();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
drawLayoutBounds(node) {
|
|
236
|
+
if (!node.layout)
|
|
237
|
+
return;
|
|
238
|
+
const layout = node.layout;
|
|
239
|
+
this.ctx.strokeStyle = "#ff0000";
|
|
240
|
+
this.ctx.lineWidth = 1;
|
|
241
|
+
this.ctx.strokeRect(layout.x, layout.y, layout.width, layout.height);
|
|
242
|
+
for (const child of node.children) {
|
|
243
|
+
this.drawLayoutBounds(child);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
getNode(id) {
|
|
247
|
+
return this.nodes.get(id);
|
|
248
|
+
}
|
|
249
|
+
clear() {
|
|
250
|
+
this.rootNode = null;
|
|
251
|
+
this.nodes.clear();
|
|
252
|
+
this.eventManager.setRootNode(null);
|
|
253
|
+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
254
|
+
}
|
|
255
|
+
registerPainter(type, painter) {
|
|
256
|
+
registerPainter(type, painter);
|
|
257
|
+
}
|
|
258
|
+
setOptions(options) {
|
|
259
|
+
this.options = { ...this.options, ...options };
|
|
260
|
+
this.accessibilityLayer.setEnabled(this.options.enableAccessibility || false);
|
|
261
|
+
}
|
|
262
|
+
destroy() {
|
|
263
|
+
if (this.rafId !== null) {
|
|
264
|
+
cancelAnimationFrame(this.rafId);
|
|
265
|
+
}
|
|
266
|
+
this.eventManager.destroy();
|
|
267
|
+
this.accessibilityLayer.destroy();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
export {
|
|
271
|
+
CanvasRenderer
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
export { CanvasRenderer };
|
|
275
|
+
|
|
276
|
+
//# debugId=5517243AFD76B28664756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/canvas/renderer.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Canvas Renderer\n *\n * Main renderer class that orchestrates layout, painting, and events\n */\n\nimport type { Renderer, Patch } from \"@hypen/core\";\n\n// Interface for the engine that canvas renderer needs\ninterface IEngine {\n dispatchAction(name: string, payload?: any): void;\n}\nimport type {\n VirtualNode,\n CanvasRendererOptions,\n PainterFunction,\n LayoutFunction,\n} from \"./types.js\";\nimport { computeLayout } from \"./layout.js\";\nimport { paintNode, registerPainter } from \"./paint.js\";\nimport { CanvasEventManager } from \"./events.js\";\nimport { InputOverlay } from \"./input.js\";\nimport { AccessibilityLayer } from \"./accessibility.js\";\nimport { findNodeById } from \"./utils.js\";\n\nconst DEFAULT_OPTIONS: CanvasRendererOptions = {\n devicePixelRatio: typeof window !== \"undefined\" ? window.devicePixelRatio || 1 : 1,\n backgroundColor: \"#ffffff\",\n enableAccessibility: true,\n enableHitTesting: true,\n enableInputOverlay: true,\n enableDirtyRects: false,\n enableLayerCaching: false,\n maxLayerCacheSize: 10,\n showLayoutBounds: false,\n showDirtyRects: false,\n logPerformance: false,\n};\n\n/**\n * Canvas Renderer\n */\nexport class CanvasRenderer implements Renderer {\n private canvas: HTMLCanvasElement;\n private ctx: CanvasRenderingContext2D;\n private engine: IEngine;\n private options: CanvasRendererOptions;\n \n private rootNode: VirtualNode | null = null;\n private nodes = new Map<string, VirtualNode>();\n \n private eventManager: CanvasEventManager;\n private inputOverlay: InputOverlay;\n private accessibilityLayer: AccessibilityLayer;\n \n private rafId: number | null = null;\n private needsRedraw = false;\n \n private frameCount = 0;\n private lastFrameTime = 0;\n\n constructor(canvas: HTMLCanvasElement, engine: IEngine, options?: Partial<CanvasRendererOptions>) {\n this.canvas = canvas;\n this.engine = engine;\n this.options = { ...DEFAULT_OPTIONS, ...options };\n\n // Get context\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) {\n throw new Error(\"Failed to get 2D context from canvas\");\n }\n this.ctx = ctx;\n\n // Setup HiDPI\n this.setupHiDPI();\n\n // Initialize subsystems\n this.eventManager = new CanvasEventManager(canvas, engine);\n this.inputOverlay = new InputOverlay(\n (canvas as any).parentElement || (typeof document !== \"undefined\" ? document.body : null)\n );\n this.accessibilityLayer = new AccessibilityLayer(\n (canvas as any).parentElement || (typeof document !== \"undefined\" ? document.body : null),\n this.options.enableAccessibility || false\n );\n\n // Listen for redraw requests from event manager\n this.canvas.addEventListener(\"hypen:redraw\", () => this.scheduleRedraw());\n\n // Don't schedule initial render - wait for patches\n }\n\n /**\n * Setup HiDPI rendering\n */\n private setupHiDPI(): void {\n const dpr = this.options.devicePixelRatio || 1;\n const rect = this.canvas.getBoundingClientRect();\n\n this.canvas.width = rect.width * dpr;\n this.canvas.height = rect.height * dpr;\n\n this.ctx.scale(dpr, dpr);\n\n // Update canvas display size\n this.canvas.style.width = `${rect.width}px`;\n this.canvas.style.height = `${rect.height}px`;\n }\n\n /**\n * Apply patches from engine\n */\n applyPatches(patches: Patch[]): void {\n for (const patch of patches) {\n this.applyPatch(patch);\n }\n\n // Update accessibility layer\n if (this.rootNode) {\n this.accessibilityLayer.syncTree(this.rootNode);\n }\n\n // Schedule redraw\n this.scheduleRedraw();\n }\n\n /**\n * Apply single patch\n */\n private applyPatch(patch: Patch): void {\n switch (patch.type) {\n case \"create\":\n this.onCreate(patch.id!, (patch as any).element_type!, patch.props || {});\n break;\n\n case \"setProp\":\n this.onSetProp(patch.id!, patch.name!, patch.value);\n break;\n\n case \"setText\":\n this.onSetText(patch.id!, patch.text!);\n break;\n\n case \"insert\":\n this.onInsert((patch as any).parent_id!, patch.id!, (patch as any).before_id);\n break;\n\n case \"move\":\n this.onMove((patch as any).parent_id!, patch.id!, (patch as any).before_id);\n break;\n\n case \"remove\":\n this.onRemove(patch.id!);\n break;\n }\n }\n\n /**\n * Create new virtual node\n */\n private onCreate(id: string, elementType: string, props: Record<string, any>): void {\n const node: VirtualNode = {\n id,\n type: elementType,\n props: props instanceof Map ? Object.fromEntries(props) : props,\n children: [],\n parent: null,\n visible: true,\n opacity: parseFloat(props.opacity) || 1,\n clickable: elementType === \"button\" || !!props.onclick,\n hoverable: true,\n focusable: elementType === \"input\" || elementType === \"textarea\" || elementType === \"button\",\n focused: false,\n hovered: false,\n };\n\n this.nodes.set(id, node);\n }\n\n /**\n * Set property on node\n */\n private onSetProp(id: string, name: string, value: any): void {\n const node = this.nodes.get(id);\n if (!node) return;\n\n node.props[name] = value;\n\n // Update computed properties\n if (name === \"visible\") {\n node.visible = !!value;\n }\n if (name === \"opacity\") {\n node.opacity = parseFloat(value) || 1;\n }\n\n // Update accessibility\n this.accessibilityLayer.updateNode(node);\n }\n\n /**\n * Set text on node\n */\n private onSetText(id: string, text: string): void {\n const node = this.nodes.get(id);\n if (!node) return;\n\n node.props[0] = text;\n\n // Update accessibility\n this.accessibilityLayer.updateNode(node);\n }\n\n /**\n * Insert node into tree\n */\n private onInsert(parentId: string, id: string, beforeId?: string): void {\n const child = this.nodes.get(id);\n if (!child) return;\n\n // Check if this is setting the root node (parent_id === id === \"root\" or similar)\n if (parentId === \"root\" && id === \"root\") {\n this.rootNode = child;\n this.eventManager.setRootNode(child);\n return;\n }\n\n // Otherwise find the parent node\n const parent = this.nodes.get(parentId);\n if (!parent) {\n // If parent is \"root\", this might be the first real root node\n if (parentId === \"root\") {\n this.rootNode = child;\n this.eventManager.setRootNode(child);\n }\n return;\n }\n\n // Insert child into parent\n child.parent = parent;\n\n if (beforeId) {\n const beforeIndex = parent.children.findIndex((c) => c.id === beforeId);\n if (beforeIndex >= 0) {\n parent.children.splice(beforeIndex, 0, child);\n } else {\n parent.children.push(child);\n }\n } else {\n parent.children.push(child);\n }\n }\n\n /**\n * Move node in tree\n */\n private onMove(parentId: string, id: string, beforeId?: string): void {\n // Remove from old parent\n const node = this.nodes.get(id);\n if (!node || !node.parent) return;\n\n const oldParent = node.parent;\n const oldIndex = oldParent.children.indexOf(node);\n if (oldIndex >= 0) {\n oldParent.children.splice(oldIndex, 1);\n }\n\n // Insert into new location\n this.onInsert(parentId, id, beforeId);\n }\n\n /**\n * Remove node from tree\n */\n private onRemove(id: string): void {\n const node = this.nodes.get(id);\n if (!node) return;\n\n // Remove from parent\n if (node.parent) {\n const index = node.parent.children.indexOf(node);\n if (index >= 0) {\n node.parent.children.splice(index, 1);\n }\n }\n\n // Remove from root\n if (this.rootNode === node) {\n this.rootNode = null;\n this.eventManager.setRootNode(null);\n }\n\n // Remove from nodes map\n this.nodes.delete(id);\n }\n\n /**\n * Schedule redraw\n */\n private scheduleRedraw(): void {\n if (this.rafId !== null) return;\n\n // Use requestAnimationFrame if available (browser), otherwise render immediately (tests)\n if (typeof requestAnimationFrame !== \"undefined\") {\n this.rafId = requestAnimationFrame(() => {\n this.render();\n this.rafId = null;\n });\n } else {\n // In non-browser environments (tests), render immediately\n this.render();\n }\n }\n\n /**\n * Main render function\n */\n private render(): void {\n const startTime = performance.now();\n\n // Clear canvas\n this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n\n // Draw background\n if (this.options.backgroundColor) {\n this.ctx.fillStyle = this.options.backgroundColor;\n this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);\n }\n\n // Layout and paint\n if (this.rootNode) {\n // Compute layout\n const dpr = this.options.devicePixelRatio || 1;\n computeLayout(\n this.ctx,\n this.rootNode,\n this.canvas.width / dpr,\n this.canvas.height / dpr\n );\n\n // Paint\n paintNode(this.ctx, this.rootNode);\n\n // Debug: show layout bounds\n if (this.options.showLayoutBounds) {\n this.drawLayoutBounds(this.rootNode);\n }\n }\n\n // Performance logging\n if (this.options.logPerformance) {\n const elapsed = performance.now() - startTime;\n this.frameCount++;\n if (performance.now() - this.lastFrameTime > 1000) {\n console.log(`Canvas FPS: ${this.frameCount}, Last frame: ${elapsed.toFixed(2)}ms`);\n this.frameCount = 0;\n this.lastFrameTime = performance.now();\n }\n }\n }\n\n /**\n * Draw layout bounds for debugging\n */\n private drawLayoutBounds(node: VirtualNode): void {\n if (!node.layout) return;\n\n const layout = node.layout;\n\n this.ctx.strokeStyle = \"#ff0000\";\n this.ctx.lineWidth = 1;\n this.ctx.strokeRect(layout.x, layout.y, layout.width, layout.height);\n\n for (const child of node.children) {\n this.drawLayoutBounds(child);\n }\n }\n\n /**\n * Get node by ID\n */\n getNode(id: string): VirtualNode | undefined {\n return this.nodes.get(id);\n }\n\n /**\n * Clear renderer\n */\n clear(): void {\n this.rootNode = null;\n this.nodes.clear();\n this.eventManager.setRootNode(null);\n this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n }\n\n /**\n * Register custom painter\n */\n registerPainter(type: string, painter: PainterFunction): void {\n registerPainter(type, painter);\n }\n\n /**\n * Set renderer options\n */\n setOptions(options: Partial<CanvasRendererOptions>): void {\n this.options = { ...this.options, ...options };\n this.accessibilityLayer.setEnabled(this.options.enableAccessibility || false);\n }\n\n /**\n * Destroy renderer\n */\n destroy(): void {\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n }\n this.eventManager.destroy();\n this.accessibilityLayer.destroy();\n }\n}\n\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "5517243AFD76B28664756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createFontString
|
|
3
|
+
} from "./utils.js";
|
|
4
|
+
import"../../chunk-2s02mkzs.js";
|
|
5
|
+
|
|
6
|
+
// src/canvas/text.ts
|
|
7
|
+
var textMetricsCache = new Map;
|
|
8
|
+
function getCacheKey(text, fontStyle, maxWidth) {
|
|
9
|
+
return `${text}|${fontStyle.fontSize}|${fontStyle.fontWeight}|${fontStyle.fontFamily}|${maxWidth || "auto"}`;
|
|
10
|
+
}
|
|
11
|
+
function measureText(ctx, text, fontStyle, maxWidth) {
|
|
12
|
+
const cacheKey = getCacheKey(text, fontStyle, maxWidth);
|
|
13
|
+
const cached = textMetricsCache.get(cacheKey);
|
|
14
|
+
if (cached)
|
|
15
|
+
return cached;
|
|
16
|
+
const font = createFontString(fontStyle.fontSize, fontStyle.fontWeight, fontStyle.fontFamily);
|
|
17
|
+
ctx.save();
|
|
18
|
+
ctx.font = font;
|
|
19
|
+
const lineHeight = fontStyle.lineHeight || fontStyle.fontSize * 1.2;
|
|
20
|
+
if (!maxWidth) {
|
|
21
|
+
const metrics = ctx.measureText(text);
|
|
22
|
+
const result2 = {
|
|
23
|
+
width: metrics.width,
|
|
24
|
+
height: lineHeight,
|
|
25
|
+
lines: [text],
|
|
26
|
+
lineHeight
|
|
27
|
+
};
|
|
28
|
+
ctx.restore();
|
|
29
|
+
textMetricsCache.set(cacheKey, result2);
|
|
30
|
+
return result2;
|
|
31
|
+
}
|
|
32
|
+
const lines = wrapText(ctx, text, maxWidth);
|
|
33
|
+
const width = Math.max(...lines.map((line) => ctx.measureText(line).width));
|
|
34
|
+
const height = lines.length * lineHeight;
|
|
35
|
+
const result = {
|
|
36
|
+
width,
|
|
37
|
+
height,
|
|
38
|
+
lines,
|
|
39
|
+
lineHeight
|
|
40
|
+
};
|
|
41
|
+
ctx.restore();
|
|
42
|
+
textMetricsCache.set(cacheKey, result);
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
function wrapText(ctx, text, maxWidth) {
|
|
46
|
+
const lines = [];
|
|
47
|
+
const paragraphs = text.split(`
|
|
48
|
+
`);
|
|
49
|
+
for (const paragraph of paragraphs) {
|
|
50
|
+
const words = paragraph.split(" ");
|
|
51
|
+
let currentLine = "";
|
|
52
|
+
for (const word of words) {
|
|
53
|
+
const testLine = currentLine ? `${currentLine} ${word}` : word;
|
|
54
|
+
const metrics = ctx.measureText(testLine);
|
|
55
|
+
if (metrics.width > maxWidth && currentLine) {
|
|
56
|
+
lines.push(currentLine);
|
|
57
|
+
currentLine = word;
|
|
58
|
+
} else {
|
|
59
|
+
currentLine = testLine;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (currentLine) {
|
|
63
|
+
lines.push(currentLine);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return lines.length > 0 ? lines : [""];
|
|
67
|
+
}
|
|
68
|
+
function renderText(ctx, text, x, y, width, height, style) {
|
|
69
|
+
const font = createFontString(style.fontSize, style.fontWeight, style.fontFamily);
|
|
70
|
+
ctx.save();
|
|
71
|
+
ctx.font = font;
|
|
72
|
+
ctx.fillStyle = style.color;
|
|
73
|
+
ctx.textBaseline = "top";
|
|
74
|
+
const metrics = measureText(ctx, text, style, width);
|
|
75
|
+
let startY = y;
|
|
76
|
+
if (style.verticalAlign === "middle") {
|
|
77
|
+
startY = y + (height - metrics.height) / 2;
|
|
78
|
+
} else if (style.verticalAlign === "bottom") {
|
|
79
|
+
startY = y + height - metrics.height;
|
|
80
|
+
}
|
|
81
|
+
for (let i = 0;i < metrics.lines.length; i++) {
|
|
82
|
+
const line = metrics.lines[i];
|
|
83
|
+
const lineY = startY + i * metrics.lineHeight;
|
|
84
|
+
let lineX = x;
|
|
85
|
+
if (style.textAlign === "center") {
|
|
86
|
+
const lineWidth = ctx.measureText(line).width;
|
|
87
|
+
lineX = x + (width - lineWidth) / 2;
|
|
88
|
+
} else if (style.textAlign === "right") {
|
|
89
|
+
const lineWidth = ctx.measureText(line).width;
|
|
90
|
+
lineX = x + width - lineWidth;
|
|
91
|
+
}
|
|
92
|
+
ctx.fillText(line, lineX, lineY);
|
|
93
|
+
}
|
|
94
|
+
ctx.restore();
|
|
95
|
+
}
|
|
96
|
+
function clearTextCache() {
|
|
97
|
+
textMetricsCache.clear();
|
|
98
|
+
}
|
|
99
|
+
async function loadFont(fontFamily, fontWeight = "normal") {
|
|
100
|
+
if (!("fonts" in document))
|
|
101
|
+
return;
|
|
102
|
+
const font = `${fontWeight} 16px ${fontFamily}`;
|
|
103
|
+
try {
|
|
104
|
+
await document.fonts.load(font);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.warn(`Failed to load font: ${font}`, error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export {
|
|
110
|
+
renderText,
|
|
111
|
+
measureText,
|
|
112
|
+
loadFont,
|
|
113
|
+
clearTextCache
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export { measureText, renderText };
|
|
117
|
+
|
|
118
|
+
//# debugId=70127DAFE309FD0864756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/canvas/text.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Text Rendering System\n *\n * Text measurement, wrapping, and rendering\n */\n\nimport type { FontStyle, TextMetrics, TextStyle } from \"./types.js\";\nimport { createFontString } from \"./utils.js\";\n\n/**\n * Text metrics cache\n */\nconst textMetricsCache = new Map<string, TextMetrics>();\n\n/**\n * Get cache key for text metrics\n */\nfunction getCacheKey(text: string, fontStyle: FontStyle, maxWidth?: number): string {\n return `${text}|${fontStyle.fontSize}|${fontStyle.fontWeight}|${fontStyle.fontFamily}|${maxWidth || \"auto\"}`;\n}\n\n/**\n * Measure text dimensions\n */\nexport function measureText(\n ctx: CanvasRenderingContext2D,\n text: string,\n fontStyle: FontStyle,\n maxWidth?: number\n): TextMetrics {\n const cacheKey = getCacheKey(text, fontStyle, maxWidth);\n const cached = textMetricsCache.get(cacheKey);\n if (cached) return cached;\n\n const font = createFontString(fontStyle.fontSize, fontStyle.fontWeight, fontStyle.fontFamily);\n ctx.save();\n ctx.font = font;\n\n const lineHeight = fontStyle.lineHeight || fontStyle.fontSize * 1.2;\n\n // No wrapping needed\n if (!maxWidth) {\n const metrics = ctx.measureText(text);\n const result: TextMetrics = {\n width: metrics.width,\n height: lineHeight,\n lines: [text],\n lineHeight,\n };\n ctx.restore();\n textMetricsCache.set(cacheKey, result);\n return result;\n }\n\n // Wrap text\n const lines = wrapText(ctx, text, maxWidth);\n const width = Math.max(...lines.map((line) => ctx.measureText(line).width));\n const height = lines.length * lineHeight;\n\n const result: TextMetrics = {\n width,\n height,\n lines,\n lineHeight,\n };\n\n ctx.restore();\n textMetricsCache.set(cacheKey, result);\n return result;\n}\n\n/**\n * Wrap text to fit within maxWidth\n */\nfunction wrapText(ctx: CanvasRenderingContext2D, text: string, maxWidth: number): string[] {\n const lines: string[] = [];\n const paragraphs = text.split(\"\\n\");\n\n for (const paragraph of paragraphs) {\n const words = paragraph.split(\" \");\n let currentLine = \"\";\n\n for (const word of words) {\n const testLine = currentLine ? `${currentLine} ${word}` : word;\n const metrics = ctx.measureText(testLine);\n\n if (metrics.width > maxWidth && currentLine) {\n lines.push(currentLine);\n currentLine = word;\n } else {\n currentLine = testLine;\n }\n }\n\n if (currentLine) {\n lines.push(currentLine);\n }\n }\n\n return lines.length > 0 ? lines : [\"\"];\n}\n\n/**\n * Render text with style\n */\nexport function renderText(\n ctx: CanvasRenderingContext2D,\n text: string,\n x: number,\n y: number,\n width: number,\n height: number,\n style: TextStyle\n): void {\n const font = createFontString(style.fontSize, style.fontWeight, style.fontFamily);\n ctx.save();\n ctx.font = font;\n ctx.fillStyle = style.color;\n ctx.textBaseline = \"top\";\n\n const metrics = measureText(ctx, text, style, width);\n\n // Calculate starting Y based on vertical alignment\n let startY = y;\n if (style.verticalAlign === \"middle\") {\n startY = y + (height - metrics.height) / 2;\n } else if (style.verticalAlign === \"bottom\") {\n startY = y + height - metrics.height;\n }\n\n // Render each line\n for (let i = 0; i < metrics.lines.length; i++) {\n const line = metrics.lines[i];\n const lineY = startY + i * metrics.lineHeight;\n\n // Calculate X based on text alignment\n let lineX = x;\n if (style.textAlign === \"center\") {\n const lineWidth = ctx.measureText(line).width;\n lineX = x + (width - lineWidth) / 2;\n } else if (style.textAlign === \"right\") {\n const lineWidth = ctx.measureText(line).width;\n lineX = x + width - lineWidth;\n }\n\n ctx.fillText(line, lineX, lineY);\n }\n\n ctx.restore();\n}\n\n/**\n * Clear text metrics cache\n */\nexport function clearTextCache(): void {\n textMetricsCache.clear();\n}\n\n/**\n * Preload font to ensure it's available\n */\nexport async function loadFont(fontFamily: string, fontWeight: string | number = \"normal\"): Promise<void> {\n if (!(\"fonts\" in document)) return;\n\n const font = `${fontWeight} 16px ${fontFamily}`;\n try {\n await document.fonts.load(font);\n } catch (error) {\n console.warn(`Failed to load font: ${font}`, error);\n }\n}\n\n\n\n\n\n\n\n\n\n\n\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;;;;;AAYA,IAAM,mBAAmB,IAAI;AAK7B,SAAS,WAAW,CAAC,MAAc,WAAsB,UAA2B;AAAA,EAClF,OAAO,GAAG,QAAQ,UAAU,YAAY,UAAU,cAAc,UAAU,cAAc,YAAY;AAAA;AAM/F,SAAS,WAAW,CACzB,KACA,MACA,WACA,UACa;AAAA,EACb,MAAM,WAAW,YAAY,MAAM,WAAW,QAAQ;AAAA,EACtD,MAAM,SAAS,iBAAiB,IAAI,QAAQ;AAAA,EAC5C,IAAI;AAAA,IAAQ,OAAO;AAAA,EAEnB,MAAM,OAAO,iBAAiB,UAAU,UAAU,UAAU,YAAY,UAAU,UAAU;AAAA,EAC5F,IAAI,KAAK;AAAA,EACT,IAAI,OAAO;AAAA,EAEX,MAAM,aAAa,UAAU,cAAc,UAAU,WAAW;AAAA,EAGhE,IAAI,CAAC,UAAU;AAAA,IACb,MAAM,UAAU,IAAI,YAAY,IAAI;AAAA,IACpC,MAAM,UAAsB;AAAA,MAC1B,OAAO,QAAQ;AAAA,MACf,QAAQ;AAAA,MACR,OAAO,CAAC,IAAI;AAAA,MACZ;AAAA,IACF;AAAA,IACA,IAAI,QAAQ;AAAA,IACZ,iBAAiB,IAAI,UAAU,OAAM;AAAA,IACrC,OAAO;AAAA,EACT;AAAA,EAGA,MAAM,QAAQ,SAAS,KAAK,MAAM,QAAQ;AAAA,EAC1C,MAAM,QAAQ,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC,SAAS,IAAI,YAAY,IAAI,EAAE,KAAK,CAAC;AAAA,EAC1E,MAAM,SAAS,MAAM,SAAS;AAAA,EAE9B,MAAM,SAAsB;AAAA,IAC1B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA,EAEA,IAAI,QAAQ;AAAA,EACZ,iBAAiB,IAAI,UAAU,MAAM;AAAA,EACrC,OAAO;AAAA;AAMT,SAAS,QAAQ,CAAC,KAA+B,MAAc,UAA4B;AAAA,EACzF,MAAM,QAAkB,CAAC;AAAA,EACzB,MAAM,aAAa,KAAK,MAAM;AAAA,CAAI;AAAA,EAElC,WAAW,aAAa,YAAY;AAAA,IAClC,MAAM,QAAQ,UAAU,MAAM,GAAG;AAAA,IACjC,IAAI,cAAc;AAAA,IAElB,WAAW,QAAQ,OAAO;AAAA,MACxB,MAAM,WAAW,cAAc,GAAG,eAAe,SAAS;AAAA,MAC1D,MAAM,UAAU,IAAI,YAAY,QAAQ;AAAA,MAExC,IAAI,QAAQ,QAAQ,YAAY,aAAa;AAAA,QAC3C,MAAM,KAAK,WAAW;AAAA,QACtB,cAAc;AAAA,MAChB,EAAO;AAAA,QACL,cAAc;AAAA;AAAA,IAElB;AAAA,IAEA,IAAI,aAAa;AAAA,MACf,MAAM,KAAK,WAAW;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,OAAO,MAAM,SAAS,IAAI,QAAQ,CAAC,EAAE;AAAA;AAMhC,SAAS,UAAU,CACxB,KACA,MACA,GACA,GACA,OACA,QACA,OACM;AAAA,EACN,MAAM,OAAO,iBAAiB,MAAM,UAAU,MAAM,YAAY,MAAM,UAAU;AAAA,EAChF,IAAI,KAAK;AAAA,EACT,IAAI,OAAO;AAAA,EACX,IAAI,YAAY,MAAM;AAAA,EACtB,IAAI,eAAe;AAAA,EAEnB,MAAM,UAAU,YAAY,KAAK,MAAM,OAAO,KAAK;AAAA,EAGnD,IAAI,SAAS;AAAA,EACb,IAAI,MAAM,kBAAkB,UAAU;AAAA,IACpC,SAAS,KAAK,SAAS,QAAQ,UAAU;AAAA,EAC3C,EAAO,SAAI,MAAM,kBAAkB,UAAU;AAAA,IAC3C,SAAS,IAAI,SAAS,QAAQ;AAAA,EAChC;AAAA,EAGA,SAAS,IAAI,EAAG,IAAI,QAAQ,MAAM,QAAQ,KAAK;AAAA,IAC7C,MAAM,OAAO,QAAQ,MAAM;AAAA,IAC3B,MAAM,QAAQ,SAAS,IAAI,QAAQ;AAAA,IAGnC,IAAI,QAAQ;AAAA,IACZ,IAAI,MAAM,cAAc,UAAU;AAAA,MAChC,MAAM,YAAY,IAAI,YAAY,IAAI,EAAE;AAAA,MACxC,QAAQ,KAAK,QAAQ,aAAa;AAAA,IACpC,EAAO,SAAI,MAAM,cAAc,SAAS;AAAA,MACtC,MAAM,YAAY,IAAI,YAAY,IAAI,EAAE;AAAA,MACxC,QAAQ,IAAI,QAAQ;AAAA,IACtB;AAAA,IAEA,IAAI,SAAS,MAAM,OAAO,KAAK;AAAA,EACjC;AAAA,EAEA,IAAI,QAAQ;AAAA;AAMP,SAAS,cAAc,GAAS;AAAA,EACrC,iBAAiB,MAAM;AAAA;AAMzB,eAAsB,QAAQ,CAAC,YAAoB,aAA8B,UAAyB;AAAA,EACxG,IAAI,EAAE,WAAW;AAAA,IAAW;AAAA,EAE5B,MAAM,OAAO,GAAG,mBAAmB;AAAA,EACnC,IAAI;AAAA,IACF,MAAM,SAAS,MAAM,KAAK,IAAI;AAAA,IAC9B,OAAO,OAAO;AAAA,IACd,QAAQ,KAAK,wBAAwB,QAAQ,KAAK;AAAA;AAAA;",
|
|
8
|
+
"debugId": "70127DAFE309FD0864756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|