r""" Visitor for (style attributes) MathML elements. The element is a wrapper that applies style attributes to its children: - mathvariant: Font variants (bold, italic, script, fraktur, etc.) - mathcolor: Text color - displaystyle: Display vs inline math mode This visitor is a transparent container that wraps converted children with appropriate LaTeX style commands. """ from lxml import etree as ET from ..core.interfaces import Visitor, Strategy from ..utils import local_name as _local class MstyleVisitor(Visitor): r""" Handles (style attributes) MathML elements. Applies style attributes to child content through LaTeX wrappers. Examples: Bold text: x → \mathbf{x} Colored text: f → \textcolor{red}{f} Script font: L → \mathscr{L} Fraktur font: g → \mathfrak{g} Double-struck (blackboard bold): R → \mathbb{R} Normal (upright) text: x → \mathrm{x} Display style (affects fractions): ... → (context passed to children) """ def __init__(self, strategy: Strategy): """Initialize the Mstyle visitor.""" super().__init__(strategy) self._converter_context = None def set_converter_context(self, converter): """Set converter context for color suppression checking.""" self._converter_context = converter def visit(self, node: ET.Element) -> str: r""" Convert element to LaTeX. Args: node: The element to convert Returns: LaTeX string with style wrappers applied to children Raises: ValueError: If node is not an element """ if not self.can_handle(node): raise ValueError(f"MstyleVisitor cannot handle element '{node.tag}'") # Get style attributes mathvariant = node.get("mathvariant", "") displaystyle = node.get("displaystyle", "") mathcolor = node.get("mathcolor", "") # Convert all children content = self._convert_children(node) # Apply font variants content = self._apply_mathvariant(content, mathvariant) # Apply color (if not black, which is default) content = self._apply_color(content, mathcolor) # Display style is contextual and doesn't produce direct LaTeX output # It would be handled by context/strategy if needed # (In the original implementation, it affects fraction rendering) return content def _convert_children(self, node: ET.Element) -> str: """ Convert all child elements to LaTeX. Args: node: The element Returns: LaTeX string with all children converted """ parts = [] for child in node: parts.append(self._convert_child(child)) return ' '.join(parts).strip() def _convert_child(self, child: ET.Element) -> str: """Convert a child element to LaTeX.""" tag = _local(child.tag) # Import visitors as needed to avoid circular imports if tag == 'mi': from .mi_visitor import MiVisitor return MiVisitor(self.strategy).visit(child) elif tag == 'mo': from .mo_visitor import MoVisitor return MoVisitor(self.strategy).visit(child) elif tag == 'mn': from .mn_visitor import MnVisitor return MnVisitor(self.strategy).visit(child) elif tag == 'mfrac': from .mfrac_visitor import MfracVisitor return MfracVisitor(self.strategy).visit(child) elif tag == 'msup': from .msup_visitor import MsupVisitor return MsupVisitor(self.strategy).visit(child) elif tag == 'msub': from .msub_visitor import MsubVisitor return MsubVisitor(self.strategy).visit(child) elif tag == 'mrow': from .mrow_visitor import MrowVisitor return MrowVisitor(self.strategy).visit(child) elif tag == 'msqrt': from .msqrt_visitor import MsqrtVisitor return MsqrtVisitor(self.strategy).visit(child) elif tag == 'mroot': from .mroot_visitor import MrootVisitor return MrootVisitor(self.strategy).visit(child) elif tag == 'msubsup': from .msubsup_visitor import MsubsupVisitor return MsubsupVisitor(self.strategy).visit(child) elif tag == 'munder': from .munder_visitor import MunderVisitor return MunderVisitor(self.strategy).visit(child) elif tag == 'mover': from .mover_visitor import MoverVisitor return MoverVisitor(self.strategy).visit(child) elif tag == 'munderover': from .munderover_visitor import MunderoverVisitor return MunderoverVisitor(self.strategy).visit(child) elif tag == 'mfenced': from .mfenced_visitor import MfencedVisitor return MfencedVisitor(self.strategy).visit(child) elif tag == 'mtable': from .mtable_visitor import MtableVisitor return MtableVisitor(self.strategy).visit(child) elif tag == 'mtext': from .mtext_visitor import MtextVisitor return MtextVisitor(self.strategy).visit(child) elif tag == 'mstyle': from .mstyle_visitor import MstyleVisitor return MstyleVisitor(self.strategy).visit(child) elif tag == 'mspace': from .mspace_visitor import MspaceVisitor return MspaceVisitor(self.strategy).visit(child) elif tag == 'menclose': from .menclose_visitor import MencloseVisitor return MencloseVisitor(self.strategy).visit(child) elif tag == 'mmultiscripts': from .mmultiscripts_visitor import MmultiscriptsVisitor return MmultiscriptsVisitor(self.strategy).visit(child) elif tag == 'maction': from .maction_visitor import MactionVisitor return MactionVisitor(self.strategy).visit(child) elif tag == 'merror': from .merror_visitor import MerrorVisitor return MerrorVisitor(self.strategy).visit(child) elif tag == 'mpadded': from .mpadded_visitor import MpaddedVisitor return MpaddedVisitor(self.strategy).visit(child) elif tag == 'mphantom': from .mphantom_visitor import MphantomVisitor return MphantomVisitor(self.strategy).visit(child) elif tag == 'ms': from .ms_visitor import MsVisitor return MsVisitor(self.strategy).visit(child) elif tag == 'mglyph': from .mglyph_visitor import MglyphVisitor return MglyphVisitor(self.strategy).visit(child) elif tag == 'maligngroup': from .maligngroup_visitor import MaligngroupVisitor return MaligngroupVisitor(self.strategy).visit(child) elif tag == 'malignmark': from .malignmark_visitor import MalignmarkVisitor return MalignmarkVisitor(self.strategy).visit(child) elif tag == 'mlabeledtr': from .mlabeledtr_visitor import MlabeledtrVisitor return MlabeledtrVisitor(self.strategy).visit(child) else: # Fallback: return text content for unknown elements import sys print(f"[WARNING] Unknown element <{tag}> in mstyle, using text fallback", file=sys.stderr) return _text_content(child) def _apply_mathvariant(self, content: str, mathvariant: str) -> str: """ Apply mathvariant font styling to content. Args: content: The LaTeX content to wrap mathvariant: The mathvariant attribute value Returns: LaTeX content with font variant wrapper """ if not mathvariant or not content: return content # Map mathvariant values to LaTeX commands variant_map = { "bold": "\\mathbf", "bold-italic": "\\boldsymbol", # Bold italic "italic": None, # Italic is default in math mode "normal": "\\mathrm", # Upright/roman text "script": "\\mathscr", "fraktur": "\\mathfrak", "double-struck": "\\mathbb", # Blackboard bold "monospace": "\\mathtt", "sans-serif": "\\mathsf", } latex_cmd = variant_map.get(mathvariant) if latex_cmd: return f"{latex_cmd}{{{content}}}" else: # Italic (default) or unknown variant - no wrapper needed return content def _apply_color(self, content: str, mathcolor: str) -> str: """ Apply color styling to content. Args: content: The LaTeX content to wrap mathcolor: The mathcolor attribute value Returns: LaTeX content with color wrapper """ # Use centralized color handling with RGB support and suppression from ..utils.color_helpers import hex_to_latex_color from ..utils import is_default_color if not mathcolor or not content: return content # Check if suppression is active (uniform color optimization) # Get context from visitor or strategy (strategy is shared across all visitors) context = getattr(self, '_converter_context', None) if context is None and hasattr(self.strategy, '_converter_context'): context = self.strategy._converter_context if context and getattr(context, '_suppress_leaf_colors', False): return content # Skip default black color if is_default_color(mathcolor): return content # Convert hex color to LaTeX format (with RGB mode support) color_model, color_spec = hex_to_latex_color(mathcolor) # Skip if conversion resulted in black if color_spec == 'black': return content # Wrap with appropriate format if color_model: return f"\\textcolor{color_model}{{{color_spec}}}{{{content}}}" else: return f"\\textcolor{{{color_spec}}}{{{content}}}" def can_handle(self, node: ET.Element) -> bool: """Check if this visitor can handle the given node.""" return _local(node.tag) == 'mstyle' @property def element_name(self) -> str: """Get the element name this visitor handles.""" return 'mstyle' def __repr__(self) -> str: """String representation for debugging.""" return f"MstyleVisitor(strategy={self.strategy.name})"