"""Core configuration models.
This module contains the main RenderingConfig class that combines all
other configuration models into a comprehensive configuration system.
"""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Any, Dict, List, Optional
from pydantic import BaseModel, ConfigDict, Field, field_validator
# mypy: disable-error-code=unreachable
if TYPE_CHECKING:
from ...color.palette import PaletteValidationResult
from ...exceptions import (
InvalidColorFormatError,
)
from ..enums import ContourMode, MergeStrategy, ModuleShape, OptimizationLevel
from .advanced import AdvancedQRConfig
# Import all component models
from .geometry import FinderConfig, GeometryConfig
from .phases import Phase1Config, Phase2Config, Phase3Config
from .visual import (
CenterpieceConfig,
FrameConfig,
PatternStyleConfig,
QuietZoneConfig,
StyleConfig,
)
# Set up logger for configuration warnings
logger = logging.getLogger(__name__)
[docs]
class RenderingConfig(BaseModel):
"""Main QR code rendering configuration.
This is the central configuration class that combines all aspects of QR code
generation including geometry, visual styling, advanced features, and processing
phases.
The configuration supports both Pydantic model initialization and a convenient
kwargs-based factory method for backward compatibility.
Attributes:
scale: Size of each module in pixels
border: Quiet zone border thickness in modules
dark: Primary foreground color (hex, rgb, or named)
light: Primary background color (hex, rgb, or named)
safe_mode: Enable safe fallback modes for production
geometry: Module geometry and shape configuration
finder: Finder pattern styling configuration
patterns: Pattern-specific styling configuration
frame: Frame shape and clipping configuration
centerpiece: Central reserve area configuration
quiet_zone: Quiet zone styling configuration
style: General styling and CSS configuration
phase1: Phase 1 processing configuration (enhanced shapes)
phase2: Phase 2 processing configuration (clustering)
phase3: Phase 3 processing configuration (contours)
advanced_qr: Advanced QR features configuration
accessibility: Accessibility features configuration
shape_options: Additional shape-specific parameters
min_contrast_ratio: Minimum WCAG contrast ratio requirement
enable_palette_validation: Validate color accessibility
enforce_wcag_standards: Enforce WCAG AA/AAA compliance
Example:
Creating configuration with model syntax::
config = RenderingConfig(
scale=10,
dark="#1a1a2e",
light="#ffffff",
geometry=GeometryConfig(
shape="rounded",
corner_radius=0.3
),
style=StyleConfig(interactive=True)
)
Creating configuration from kwargs (backward compatible)::
config = RenderingConfig.from_kwargs(
scale=10,
dark="#1a1a2e",
light="#ffffff",
shape="rounded",
corner_radius=0.3,
interactive=True
)
"""
model_config = ConfigDict(validate_default=True, extra="forbid")
# Basic QR code parameters
scale: int = Field(default=1, ge=1, le=100, description="Size of each module in pixels")
border: int = Field(default=4, ge=0, le=20, description="Quiet zone border thickness in modules")
dark: str = Field(default="#000000", description="Primary foreground color (hex, rgb, or named)")
light: str = Field(default="#ffffff", description="Primary background color (hex, rgb, or named)")
safe_mode: bool = Field(
default=False,
description="Force square shapes for critical QR patterns (finder, timing) " "to ensure scannability",
)
# Configuration subsections
geometry: GeometryConfig = Field(
default_factory=GeometryConfig,
description="Module geometry and shape configuration",
)
finder: FinderConfig = Field(
default_factory=FinderConfig, description="Finder pattern styling configuration"
)
patterns: PatternStyleConfig = Field(
default_factory=PatternStyleConfig,
description="Pattern-specific styling configuration",
)
frame: FrameConfig = Field(
default_factory=FrameConfig,
description="Frame shape and clipping configuration",
)
centerpiece: CenterpieceConfig = Field(
default_factory=CenterpieceConfig,
description="Central reserve area configuration",
)
quiet_zone: QuietZoneConfig = Field(
default_factory=QuietZoneConfig, description="Quiet zone styling configuration"
)
style: StyleConfig = Field(
default_factory=StyleConfig, description="General styling and CSS configuration"
)
# Processing phases
phase1: Phase1Config = Field(
default_factory=Phase1Config,
description="Phase 1 processing configuration (enhanced shapes)",
)
phase2: Phase2Config = Field(
default_factory=Phase2Config,
description="Phase 2 processing configuration (clustering)",
)
phase3: Phase3Config = Field(
default_factory=Phase3Config,
description="Phase 3 processing configuration (contours)",
)
# Advanced features
advanced_qr: AdvancedQRConfig = Field(
default_factory=AdvancedQRConfig,
description="Advanced QR features configuration",
)
# Accessibility features (optional, imported dynamically)
accessibility: "AccessibilityConfig" = Field(
default_factory=lambda: AccessibilityConfig(enabled=False),
description="Accessibility features configuration",
)
# Additional shape parameters
shape_options: Optional[Dict[str, Any]] = Field(
default=None, description="Additional shape-specific parameters"
)
# Color validation parameters
min_contrast_ratio: float = Field(
default=4.5,
ge=1.0,
le=21.0,
description="Minimum WCAG contrast ratio requirement",
)
enable_palette_validation: bool = Field(default=False, description="Validate color accessibility")
enforce_wcag_standards: bool = Field(default=False, description="Enforce WCAG AA/AAA compliance")
# Optional metadata for tracking purposes
metadata: Optional[Dict[str, Any]] = Field(
default=None, description="Optional metadata for configuration tracking"
)
[docs]
@field_validator("dark", "light")
@classmethod
def validate_colors(cls, v: str) -> str:
"""Validate color format."""
# Basic validation - more comprehensive validation happens in palette validation
if isinstance(v, str) and len(v.strip()) > 0:
return v.strip()
raise InvalidColorFormatError(
color=str(v),
accepted_formats=["hex (#RRGGBB)", "rgb(r,g,b)", "named colors"],
)
[docs]
@classmethod
def from_kwargs(cls, **kwargs: Any) -> "RenderingConfig":
"""Create RenderingConfig from flat kwargs for backward compatibility.
This factory method allows creating configuration using the traditional
flat parameter approach while internally organizing into the structured
configuration model.
Args:
**kwargs: Flat configuration parameters
Returns:
RenderingConfig instance with organized configuration
Example:
config = RenderingConfig.from_kwargs(
scale=10,
border=4,
dark="#1a1a2e",
light="#ffffff",
shape="rounded",
corner_radius=0.3,
merge="soft",
connectivity="8-way",
interactive=True,
centerpiece_enabled=True,
centerpiece_size=0.15
)
"""
# Start with basic parameters
config_data = {
key: kwargs.get(key, cls.model_fields[key].default)
for key in [
"scale",
"border",
"dark",
"light",
"safe_mode",
"shape_options",
"min_contrast_ratio",
"enable_palette_validation",
"enforce_wcag_standards",
]
if key in kwargs or key in cls.model_fields
}
# Geometry configuration
geometry_data = {}
geometry_mappings = {
"connectivity": "connectivity",
"merge": "merge",
"corner_radius": "corner_radius",
"shape": "shape",
"min_island_modules": "min_island_modules",
}
for kwarg_key, config_key in geometry_mappings.items():
if kwarg_key in kwargs:
geometry_data[config_key] = kwargs[kwarg_key]
if geometry_data:
config_data["geometry"] = geometry_data
# Finder configuration
finder_data = {}
finder_mappings = {
"finder_shape": "shape",
"finder_inner_scale": "inner_scale",
"finder_stroke": "stroke",
}
for kwarg_key, config_key in finder_mappings.items():
if kwarg_key in kwargs:
finder_data[config_key] = kwargs[kwarg_key]
if finder_data:
config_data["finder"] = finder_data
# Phase configurations
for phase_num in [1, 2, 3]:
phase_key = f"phase{phase_num}"
phase_data = {}
# Check for explicit enable/disable
enable_key = f"enable_phase{phase_num}"
if enable_key in kwargs:
phase_data["enabled"] = kwargs[enable_key]
# Phase-specific parameters
phase_prefix = f"phase{phase_num}_"
for key, value in kwargs.items():
if key.startswith(phase_prefix):
param_name = key[len(phase_prefix) :]
phase_data[param_name] = value
if phase_data:
config_data[phase_key] = phase_data
# Frame configuration
frame_data = {}
frame_mappings = {
"frame_shape": "shape",
"frame_corner_radius": "corner_radius",
"frame_clip_mode": "clip_mode",
"frame_custom_path": "custom_path",
"frame_fade_distance": "fade_distance",
"frame_scale_distance": "scale_distance",
}
for kwarg_key, config_key in frame_mappings.items():
if kwarg_key in kwargs:
frame_data[config_key] = kwargs[kwarg_key]
if frame_data:
config_data["frame"] = frame_data
# Centerpiece configuration (with backward compatibility)
centerpiece_data = {}
centerpiece_mappings = {
"centerpiece_enabled": "enabled",
"reserve_center": "enabled", # backward compat
"centerpiece_shape": "shape",
"reserve_shape": "shape", # backward compat
"centerpiece_size": "size",
"reserve_size": "size", # backward compat
"centerpiece_offset_x": "offset_x",
"reserve_offset_x": "offset_x", # backward compat
"centerpiece_offset_y": "offset_y",
"reserve_offset_y": "offset_y", # backward compat
"centerpiece_margin": "margin",
"centerpiece_mode": "mode",
"centerpiece_placement": "placement",
"reserve_margin": "margin", # backward compat
}
for kwarg_key, config_key in centerpiece_mappings.items():
if kwarg_key in kwargs:
centerpiece_data[config_key] = kwargs[kwarg_key]
if centerpiece_data:
config_data["centerpiece"] = centerpiece_data
# Advanced QR features configuration
advanced_qr_data = {}
advanced_qr_mappings = {
"eci_enabled": "eci_enabled",
"qr_eci": "eci_enabled", # Alternative naming
"encoding": "encoding",
"qr_encoding": "encoding", # Alternative naming
"mask_pattern": "mask_pattern",
"qr_mask": "mask_pattern", # Alternative naming
"auto_mask": "auto_mask",
"structured_append": "structured_append",
"multi_symbol": "structured_append", # Alternative naming
"symbol_count": "symbol_count",
"qr_symbol_count": "symbol_count", # Alternative naming
"boost_error": "boost_error",
"qr_boost_error": "boost_error", # Alternative naming
}
for kwarg_key, config_key in advanced_qr_mappings.items():
if kwarg_key in kwargs:
advanced_qr_data[config_key] = kwargs[kwarg_key]
if advanced_qr_data:
config_data["advanced_qr"] = advanced_qr_data
# Pattern styling configuration
pattern_data = {}
pattern_mappings = {
"patterns_enabled": "enabled",
"pattern_finder": "finder",
"pattern_finder_inner": "finder_inner",
"pattern_timing": "timing",
"pattern_alignment": "alignment",
"pattern_format": "format",
"pattern_version": "version",
"pattern_data": "data",
"pattern_finder_color": "finder_color",
"pattern_finder_inner_color": "finder_inner_color",
"pattern_timing_color": "timing_color",
"pattern_alignment_color": "alignment_color",
"pattern_format_color": "format_color",
"pattern_version_color": "version_color",
"pattern_data_color": "data_color",
"pattern_finder_scale": "finder_scale",
"pattern_timing_scale": "timing_scale",
"pattern_alignment_scale": "alignment_scale",
"pattern_finder_effects": "finder_effects",
"pattern_timing_effects": "timing_effects",
"pattern_alignment_effects": "alignment_effects",
}
for kwarg_key, config_key in pattern_mappings.items():
if kwarg_key in kwargs:
pattern_data[config_key] = kwargs[kwarg_key]
if pattern_data:
config_data["patterns"] = pattern_data
# Quiet zone configuration
quiet_zone_data = {}
quiet_zone_mappings = {
"quiet_zone_style": "style",
"quiet_zone_color": "color",
"quiet_zone_gradient": "gradient",
}
for kwarg_key, config_key in quiet_zone_mappings.items():
if kwarg_key in kwargs:
quiet_zone_data[config_key] = kwargs[kwarg_key]
if quiet_zone_data:
config_data["quiet_zone"] = quiet_zone_data
# Style configuration
style_data = {}
style_mappings = {
"interactive": "interactive",
"tooltips": "tooltips",
"custom_styles": "custom_styles",
"css_classes": "css_classes",
"style_css_classes": "css_classes", # Alternative parameter name
}
for kwarg_key, config_key in style_mappings.items():
if kwarg_key in kwargs:
style_data[config_key] = kwargs[kwarg_key]
if style_data:
config_data["style"] = style_data
# Accessibility configuration
accessibility_data = {}
# Handle direct accessibility config object
if "accessibility" in kwargs:
direct_accessibility = kwargs["accessibility"]
if hasattr(direct_accessibility, "model_dump"):
# Pydantic model - use model_dump()
accessibility_data = direct_accessibility.model_dump()
elif hasattr(direct_accessibility, "__dict__"):
# Object with attributes - convert to dict
accessibility_data = direct_accessibility.__dict__.copy()
elif isinstance(direct_accessibility, dict):
# Already a dictionary
accessibility_data = direct_accessibility.copy()
else:
# Unknown type - skip
pass
# Handle prefixed accessibility parameters (flat kwargs format)
accessibility_mappings = {
"accessibility_enabled": "enabled",
"accessibility_id_prefix": "id_prefix",
"accessibility_use_stable_ids": "use_stable_ids",
"accessibility_include_coordinates": "include_coordinates",
"accessibility_enable_aria": "enable_aria",
"accessibility_root_role": "root_role",
"accessibility_module_role": "module_role",
"accessibility_root_label": "root_label",
"accessibility_root_description": "root_description",
"accessibility_include_module_labels": "include_module_labels",
"accessibility_include_pattern_labels": "include_pattern_labels",
"accessibility_optimize_for_screen_readers": "optimize_for_screen_readers",
"accessibility_group_similar_elements": "group_similar_elements",
"accessibility_add_structural_markup": "add_structural_markup",
"accessibility_enable_keyboard_navigation": "enable_keyboard_navigation",
"accessibility_focus_visible_elements": "focus_visible_elements",
"accessibility_target_compliance": "target_compliance",
"accessibility_custom_attributes": "custom_attributes",
}
for kwarg_key, config_key in accessibility_mappings.items():
if kwarg_key in kwargs:
accessibility_data[config_key] = kwargs[kwarg_key]
# Auto-enable accessibility for basic features if any accessibility
# params are provided
if accessibility_data and "enabled" not in accessibility_data:
accessibility_data["enabled"] = True
if accessibility_data:
config_data["accessibility"] = accessibility_data
# Use Pydantic's model_validate for automatic validation and type conversion
try:
config = cls.model_validate(config_data)
except ValueError as e:
# Handle validation errors gracefully by falling back to defaults
import logging
logger = logging.getLogger(__name__)
logger.warning(f"Configuration validation error, using defaults: {e}")
# Try to identify and fix common validation errors
if "geometry.shape" in str(e) and "shape" in kwargs:
logger.warning(f"Invalid shape '{kwargs['shape']}', falling back to 'square'")
config_data["geometry"]["shape"] = "square"
if "frame.shape" in str(e) and "frame_shape" in kwargs:
logger.warning(f"Invalid frame shape '{kwargs['frame_shape']}', " f"falling back to 'square'")
config_data["frame"]["shape"] = "square"
# Retry validation with corrected values
try:
config = cls.model_validate(config_data)
except ValueError:
# If still failing, create a basic default config
logger.warning("Using minimal default configuration due to validation errors")
config = cls()
# Auto-enable phases based on configuration
cls._auto_enable_phases(config, kwargs)
return config
@classmethod
def _auto_enable_phases(cls, config: "RenderingConfig", kwargs: Dict[str, Any]) -> None:
"""Auto-enable phases based on configuration settings."""
# Only auto-enable if not explicitly set in kwargs
enable_phase1 = kwargs.get("enable_phase1")
enable_phase2 = kwargs.get("enable_phase2")
enable_phase3 = kwargs.get("enable_phase3")
# Phase 1: Auto-enable when shape != "square" or corner_radius > 0
if enable_phase1 is None and not config.phase1.enabled:
needs_phase1 = config.geometry.shape != ModuleShape.SQUARE or config.geometry.corner_radius > 0
if needs_phase1:
config.phase1.enabled = True
config.phase1.use_enhanced_shapes = True
config.phase1.roundness = config.geometry.corner_radius
config.phase1.size_ratio = 0.9
# Phase 2: Auto-enable when merge != "none"
if enable_phase2 is None and not config.phase2.enabled:
if config.geometry.merge != MergeStrategy.NONE:
config.phase2.enabled = True
config.phase2.use_cluster_rendering = True
config.phase2.cluster_module_types = ["data"]
config.phase2.min_cluster_size = 3
# Phase 3: Auto-enable when merge == "aggressive"
if enable_phase3 is None and not config.phase3.enabled:
if config.geometry.merge == MergeStrategy.AGGRESSIVE:
config.phase3.enabled = True
config.phase3.use_marching_squares = True
config.phase3.contour_module_types = ["data"]
config.phase3.contour_mode = ContourMode.BEZIER
config.phase3.contour_smoothing = 0.3
config.phase3.bezier_optimization = OptimizationLevel.MEDIUM
# Auto-adjust min_island_modules for decoder compatibility
# Aggressive merge needs higher threshold for reliable decoding
if config.geometry.min_island_modules < 5:
config.geometry.min_island_modules = 5
[docs]
def validate_palette(self) -> "PaletteValidationResult":
"""Validate the color palette using enhanced validation."""
from ...color.palette import PaletteConfig, validate_palette
# Create palette config from current colors
palette_config = PaletteConfig(
dark=self.dark,
light=self.light,
accent_colors=self._get_accent_colors(),
min_contrast_ratio=self.min_contrast_ratio,
enforce_standards=self.enforce_wcag_standards,
validate_accessibility=self.enable_palette_validation,
)
return validate_palette(palette_config)
def _get_accent_colors(self) -> List[str]:
"""Extract accent colors from pattern styling."""
accent_colors = []
if self.patterns.enabled:
# Collect pattern-specific colors
pattern_colors = [
self.patterns.finder_color,
self.patterns.finder_inner_color,
self.patterns.timing_color,
self.patterns.alignment_color,
self.patterns.format_color,
self.patterns.version_color,
self.patterns.data_color,
]
for color in pattern_colors:
if color and color not in accent_colors and color not in [self.dark, self.light]:
accent_colors.append(color)
return accent_colors
[docs]
def get_contrast_ratio(self) -> Optional[float]:
"""Get contrast ratio between primary dark and light colors."""
from ...color.color_analysis import calculate_contrast_ratio
return calculate_contrast_ratio(self.dark, self.light)
[docs]
def meets_contrast_requirements(self) -> bool:
"""Check if primary colors meet minimum contrast requirements."""
ratio = self.get_contrast_ratio()
return ratio is not None and ratio >= self.min_contrast_ratio
[docs]
def to_kwargs(self) -> Dict[str, Any]:
"""Convert configuration back to kwargs format.
Returns flat kwargs dictionary suitable for passing to from_kwargs.
"""
kwargs = {
"scale": self.scale,
"border": self.border,
"dark": self.dark,
"light": self.light,
"safe_mode": self.safe_mode,
# Geometry parameters
"connectivity": self.geometry.connectivity,
"merge": self.geometry.merge,
"corner_radius": self.geometry.corner_radius,
"shape": self.geometry.shape,
"min_island_modules": self.geometry.min_island_modules,
# Finder parameters
"finder_shape": self.finder.shape,
"finder_inner_scale": self.finder.inner_scale,
"finder_stroke": self.finder.stroke,
# Frame parameters
"frame_shape": self.frame.shape,
"frame_corner_radius": self.frame.corner_radius,
"frame_clip_mode": self.frame.clip_mode,
"frame_fade_distance": self.frame.fade_distance,
"frame_scale_distance": self.frame.scale_distance,
# Centerpiece parameters
"centerpiece_enabled": self.centerpiece.enabled,
"centerpiece_shape": self.centerpiece.shape,
"centerpiece_size": self.centerpiece.size,
"centerpiece_offset_x": self.centerpiece.offset_x,
"centerpiece_offset_y": self.centerpiece.offset_y,
"centerpiece_margin": self.centerpiece.margin,
"centerpiece_mode": self.centerpiece.mode.value,
"centerpiece_placement": self.centerpiece.placement.value,
# Advanced QR features parameters
"eci_enabled": self.advanced_qr.eci_enabled,
"encoding": self.advanced_qr.encoding,
"mask_pattern": self.advanced_qr.mask_pattern,
"auto_mask": self.advanced_qr.auto_mask,
"structured_append": self.advanced_qr.structured_append,
"symbol_count": self.advanced_qr.symbol_count,
"boost_error": self.advanced_qr.boost_error,
# Pattern styling parameters
"patterns_enabled": self.patterns.enabled,
"pattern_finder": self.patterns.finder,
"pattern_finder_inner": self.patterns.finder_inner,
"pattern_timing": self.patterns.timing,
"pattern_alignment": self.patterns.alignment,
"pattern_format": self.patterns.format,
"pattern_version": self.patterns.version,
"pattern_data": self.patterns.data,
"pattern_finder_color": self.patterns.finder_color,
"pattern_finder_inner_color": self.patterns.finder_inner_color,
"pattern_timing_color": self.patterns.timing_color,
"pattern_alignment_color": self.patterns.alignment_color,
"pattern_format_color": self.patterns.format_color,
"pattern_version_color": self.patterns.version_color,
"pattern_data_color": self.patterns.data_color,
"pattern_finder_scale": self.patterns.finder_scale,
"pattern_timing_scale": self.patterns.timing_scale,
"pattern_alignment_scale": self.patterns.alignment_scale,
# Quiet zone parameters
"quiet_zone_style": self.quiet_zone.style,
"quiet_zone_color": self.quiet_zone.color,
# Style parameters
"interactive": self.style.interactive,
"tooltips": self.style.tooltips,
}
# Optional values
if self.shape_options:
kwargs["shape_options"] = self.shape_options
if self.frame.custom_path:
kwargs["frame_custom_path"] = self.frame.custom_path
if self.quiet_zone.gradient:
kwargs["quiet_zone_gradient"] = self.quiet_zone.gradient
# Compare with default StyleConfig
default_style = StyleConfig()
if self.style.css_classes != default_style.css_classes:
kwargs["css_classes"] = self.style.css_classes
# Note: custom_styles doesn't exist in StyleConfig
# Phase enables
kwargs["enable_phase1"] = self.phase1.enabled
kwargs["enable_phase2"] = self.phase2.enabled
kwargs["enable_phase3"] = self.phase3.enabled
# Accessibility parameters
if self.accessibility:
kwargs.update(
{
"accessibility_enabled": self.accessibility.enabled,
"accessibility_id_prefix": self.accessibility.id_prefix,
"accessibility_use_stable_ids": self.accessibility.use_stable_ids,
"accessibility_include_coordinates": self.accessibility.include_coordinates, # noqa: E501
"accessibility_enable_aria": self.accessibility.enable_aria,
"accessibility_root_role": self.accessibility.root_role,
"accessibility_module_role": self.accessibility.module_role,
"accessibility_root_label": self.accessibility.root_label, # noqa: E501
"accessibility_root_description": self.accessibility.root_description, # noqa: E501
"accessibility_include_module_labels": self.accessibility.include_module_labels, # noqa: E501
"accessibility_include_pattern_labels": self.accessibility.include_pattern_labels, # noqa: E501
"accessibility_optimize_for_screen_readers": self.accessibility.optimize_for_screen_readers, # noqa: E501
"accessibility_group_similar_elements": self.accessibility.group_similar_elements, # noqa: E501
"accessibility_add_structural_markup": self.accessibility.add_structural_markup, # noqa: E501
"accessibility_enable_keyboard_navigation": self.accessibility.enable_keyboard_navigation, # noqa: E501
"accessibility_focus_visible_elements": self.accessibility.focus_visible_elements, # noqa: E501
"accessibility_target_compliance": self.accessibility.target_compliance, # noqa: E501
"accessibility_custom_attributes": self.accessibility.custom_attributes, # noqa: E501
}
)
return kwargs
[docs]
@classmethod
def json_schema(cls) -> Dict[str, Any]:
"""Generate JSON Schema for the configuration.
Returns:
JSON Schema dictionary for external tools and documentation
"""
return cls.model_json_schema()
[docs]
def to_json(self) -> str:
"""Serialize configuration to JSON string.
Returns:
JSON string representation of the configuration
"""
return self.model_dump_json(indent=2)
[docs]
@classmethod
def from_json(cls, json_str: str) -> "RenderingConfig":
"""Create configuration from JSON string.
Args:
json_str: JSON string representation
Returns:
RenderingConfig instance
"""
return cls.model_validate_json(json_str)
[docs]
class DebugConfig(BaseModel):
"""Debug and development settings."""
model_config = ConfigDict(validate_default=True, extra="forbid")
debug_mode: bool = False
debug_stroke: bool = False
debug_colors: Dict[str, str] = Field(
default_factory=lambda: {
"contour": "red",
"cluster": "blue",
"enhanced": "green",
},
description="Debug colors for different components",
)
save_intermediate_results: bool = False
verbose_logging: bool = False
# Import at end to handle forward references
try:
from ...a11y.accessibility import AccessibilityConfig
# Rebuild model to handle forward reference
RenderingConfig.model_rebuild()
except ImportError:
# Graceful fallback if accessibility module is not available
pass