Source code for segnomms.svg.composite

"""Composite SVG builder that orchestrates all specialized builders.

This module provides the main InteractiveSVGBuilder that combines functionality
from all specialized builders to maintain backward compatibility.
"""

import xml.etree.ElementTree as ET
from typing import Any, Dict, List, Optional, Union

from ..a11y.accessibility import AccessibilityConfig
from ..core.interfaces import SVGBuilder
from .accessibility import AccessibilityBuilder
from .core import CoreSVGBuilder
from .definitions import DefinitionsBuilder
from .frame_visual import FrameVisualBuilder
from .interactivity import InteractivityBuilder
from .models import GradientConfig


[docs] class InteractiveSVGBuilder(SVGBuilder): """Main SVG builder that composes all specialized builders. This class maintains the same interface as the original monolithic SVGBuilder while delegating to specialized builders internally. """
[docs] def __init__(self, accessibility_config: Optional[AccessibilityConfig] = None): """Initialize the composite SVG builder. Args: accessibility_config: Configuration for accessibility features """ # Initialize all specialized builders self.core_builder = CoreSVGBuilder() self.definitions_builder = DefinitionsBuilder() self.interactivity_builder = InteractivityBuilder() self.frame_visual_builder = FrameVisualBuilder() self.accessibility_builder = AccessibilityBuilder(accessibility_config) # Store accessibility config and enhancer for backward compatibility if accessibility_config is None: from ..a11y.accessibility import create_basic_accessibility self.accessibility_config = create_basic_accessibility() else: self.accessibility_config = accessibility_config self.accessibility_enhancer = self.accessibility_builder.enhancer
# Delegate core SVG methods
[docs] def create_svg_root(self, width: int, height: int, **kwargs: Any) -> ET.Element: """Create the root SVG element with accessibility features. Handles both web accessibility (ARIA attributes) and SVG accessibility (title/desc elements) for complete coverage. """ # Extract and remove title and description to avoid validation errors # These will be handled by the accessibility system below accessibility_title = kwargs.pop("title", None) accessibility_description = kwargs.pop("description", None) # Create the base SVG root element svg = self.core_builder.create_svg_root(width, height, **kwargs) # Apply web accessibility (ARIA attributes, keyboard navigation, etc.) if self.accessibility_config and self.accessibility_config.enabled: if hasattr(self.accessibility_builder, "enhancer"): self.accessibility_builder.enhancer.enhance_root_element( svg, title=accessibility_title, description=accessibility_description, ) # Apply SVG accessibility (native <title> and <desc> elements) # This ensures the SVG is accessible even when standalone if accessibility_title or accessibility_description: self._add_svg_accessibility_elements(svg, accessibility_title, accessibility_description) return svg
[docs] def add_styles( self, svg: ET.Element, interactive: bool = False, animation_config: Optional[Dict[str, Any]] = None, ) -> None: """Add CSS styles to the SVG.""" self.core_builder.add_styles(svg, interactive, animation_config)
[docs] def add_background(self, svg: ET.Element, width: int, height: int, color: str, **kwargs: Any) -> None: """Add a background rectangle to the SVG.""" self.core_builder.add_background(svg, width, height, color, **kwargs)
# Delegate definitions methods
[docs] def add_definitions( self, svg: ET.Element, definitions_or_gradients: Optional[Union[Dict[str, Any], List[GradientConfig]]] = None, patterns: Optional[List[Dict[str, Any]]] = None, filters: Optional[List[Dict[str, Any]]] = None, ) -> ET.Element: """Add definitions section to SVG. Args: svg: SVG element to add definitions to definitions_or_gradients: Either a dictionary containing definitions structure or a list of GradientConfig objects (backward compatibility) patterns: List of pattern definitions (when not using dictionary format) filters: List of filter definitions (when not using dictionary format) """ # Handle dictionary format: {"gradients": {...}, "patterns": {...}, "filters": {...}} if isinstance(definitions_or_gradients, dict): definitions = definitions_or_gradients gradients_data = definitions.get("gradients", {}) patterns_data = definitions.get("patterns", {}) filters_data = definitions.get("filters", {}) # Convert gradients dictionary to list of GradientConfig objects gradients_list = [] if gradients_data: for grad_id, grad_config in gradients_data.items(): if isinstance(grad_config, dict): # Transform test format to GradientConfig format transformed_config = self._transform_gradient_config(grad_id, grad_config) gradients_list.append(transformed_config) # Convert patterns dictionary to list patterns_list = [] if patterns_data: for pattern_id, pattern_config in patterns_data.items(): if isinstance(pattern_config, dict): pattern_config_copy = pattern_config.copy() pattern_config_copy["id"] = pattern_id # Add missing id field patterns_list.append(pattern_config_copy) # Convert filters dictionary to list filters_list = [] if filters_data: for filter_id, filter_config in filters_data.items(): if isinstance(filter_config, dict): filter_config_copy = filter_config.copy() filter_config_copy["id"] = filter_id # Add missing id field filters_list.append(filter_config_copy) return self.definitions_builder.add_definitions(svg, gradients_list, patterns_list, filters_list) else: # Handle original format: individual lists return self.definitions_builder.add_definitions(svg, definitions_or_gradients, patterns, filters)
# Delegate accessibility methods
[docs] def add_svg_accessibility_elements( self, svg: ET.Element, title: Optional[str] = None, description: Optional[str] = None, ) -> None: """Add native SVG accessibility elements (<title> and <desc>). These elements are part of the SVG specification and provide accessibility information that works in all contexts. """ self.accessibility_builder.add_title_and_description(svg, title, description)
[docs] def add_title_and_description( self, svg: ET.Element, title: Optional[str] = None, description: Optional[str] = None, ) -> None: """Add title and description elements for accessibility. DEPRECATED: Use add_svg_accessibility_elements() for clarity. Kept for backward compatibility. """ self.add_svg_accessibility_elements(svg, title, description)
# Private methods def _add_svg_accessibility_elements( self, svg: ET.Element, title: Optional[str], description: Optional[str] ) -> None: """Private method to add SVG accessibility elements. Used internally by create_svg_root to add native SVG accessibility. """ self.add_svg_accessibility_elements(svg, title, description) def _generate_css_styles(self, interactive: bool) -> str: """Generate CSS styles for the SVG.""" return self.core_builder._generate_css_styles(interactive) # Private methods for definitions (maintain interface) def _add_gradient(self, defs: ET.Element, gradient: GradientConfig) -> None: """Add a gradient definition.""" self.definitions_builder._add_gradient(defs, gradient) def _add_pattern(self, defs: ET.Element, pattern: Dict[str, Any]) -> None: """Add a pattern definition.""" self.definitions_builder._add_pattern(defs, pattern) def _add_filter(self, defs: ET.Element, filter_config: Dict[str, Any]) -> None: """Add a filter definition.""" self.definitions_builder._add_filter(defs, filter_config) # Delegate interactivity methods
[docs] def add_javascript(self, svg: ET.Element, script_content: str) -> None: """Add JavaScript code to the SVG document.""" self.interactivity_builder.add_javascript(svg, script_content)
[docs] def add_interaction_handlers(self, svg: ET.Element) -> None: """Add default interaction handlers to the SVG.""" self.interactivity_builder.add_interaction_handlers(svg)
# Delegate frame and visual methods
[docs] def add_frame_definitions( self, svg: ET.Element, frame_config: Any, width: int, height: int, border_pixels: int, ) -> Optional[str]: """Add frame shape definitions to SVG defs section.""" # Convert original parameters to modular format qr_size = min(width, height) - (2 * border_pixels) # Estimate module count from size (this is approximate) module_count = max(21, int(qr_size / 10)) # Reasonable default return self.frame_visual_builder.add_frame_definitions(svg, frame_config, qr_size, module_count)
[docs] def add_quiet_zone_with_style(self, svg: ET.Element, config: Any, width: int, height: int) -> None: """Add styled quiet zone to the SVG.""" # Convert width/height to qr_bounds format for the frame_visual builder qr_bounds = (0, 0, width, height) self.frame_visual_builder.add_quiet_zone_with_style(svg, config, qr_bounds)
[docs] def add_centerpiece_metadata( self, svg: ET.Element, config: Any, bounds: Dict[str, Any], scale: int, border: int, ) -> None: """Add metadata about centerpiece location for overlay applications.""" # Convert original bounds dict to qr_bounds tuple format # bounds dict typically has keys like 'x', 'y', 'width', 'height' x = bounds.get("x", 0) * scale + border * scale y = bounds.get("y", 0) * scale + border * scale width = bounds.get("width", 0) * scale height = bounds.get("height", 0) * scale qr_bounds = (x, y, width, height) self.frame_visual_builder.add_centerpiece_metadata(svg, config, qr_bounds, scale)
# Delegate accessibility structure methods
[docs] def create_layered_structure(self, svg: ET.Element) -> Dict[str, ET.Element]: """Create a semantic layered structure for the SVG.""" return self.accessibility_builder.create_layered_structure(svg)
[docs] def enhance_module_accessibility( self, element: ET.Element, row: int, col: int, module_type: str = "data" ) -> None: """Enhance accessibility for individual QR modules.""" self.accessibility_builder.enhance_module_accessibility(element, row, col, module_type)
[docs] def enhance_pattern_group_accessibility( self, group_element: ET.Element, pattern_type: str, module_count: int ) -> None: """Enhance accessibility for pattern groups.""" self.accessibility_builder.enhance_pattern_group_accessibility( group_element, pattern_type, module_count )
[docs] def get_accessibility_report(self) -> Dict[str, Any]: """Get a report of accessibility features applied.""" return self.accessibility_builder.get_accessibility_report()
[docs] def validate_accessibility(self) -> List[str]: """Validate accessibility compliance.""" return self.accessibility_builder.validate_accessibility()
def _transform_gradient_config(self, grad_id: str, grad_config: Dict[str, Any]) -> Dict[str, Any]: """Transform test format gradient config to GradientConfig format. This method doesn't create a GradientConfig object but returns a dictionary that bypasses the strict model validation and preserves original format. Args: grad_id: Gradient ID grad_config: Dictionary containing gradient configuration in test format Returns: Dictionary that will be passed directly to _add_gradient """ # Instead of transforming to GradientConfig format, create a mock object # that preserves the original test format but adds the required ID result = grad_config.copy() result["gradient_id"] = grad_id result["gradient_type"] = grad_config.get("type", "linear") return result