""" Sistema de Panel LaTeX para la Calculadora MAV CAS Híbrida """ from PySide6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QFrame, QTextBrowser ) from PySide6.QtCore import Qt, QTimer from PySide6.QtGui import QFont from PySide6.QtWebEngineWidgets import QWebEngineView import logging from typing import List, Dict, Optional from .evaluation import EvaluationResult import sympy class LatexPanel(QWidget): """Panel LaTeX con WebEngine o fallback a HTML""" def __init__(self, parent=None): super().__init__(parent) self.equations = [] self._webview_available = False self._mathjax_ready = False self._pending_equations = [] self._parent_calculator = parent self._setup_ui() # Timer para verificar si MathJax está listo self._mathjax_check_timer = QTimer() self._mathjax_check_timer.timeout.connect(self._check_mathjax_ready) self._mathjax_check_timer.start(500) # Verificar cada 500ms def _setup_ui(self): """Configura la UI del panel""" layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) # Header header = QFrame() header.setFixedHeight(30) header.setStyleSheet("background-color: #1a1a1a; border-bottom: 1px solid #3c3c3c;") header_layout = QHBoxLayout(header) header_layout.setContentsMargins(10, 0, 10, 0) title = QLabel("📐 Ecuaciones LaTeX") title.setStyleSheet("color: #80c7f7; font-weight: bold;") header_layout.addWidget(title) header_layout.addStretch() layout.addWidget(header) # Intentar crear WebEngineView try: self.webview = QWebEngineView() self.webview.setContextMenuPolicy(Qt.NoContextMenu) self._setup_webview() layout.addWidget(self.webview) self._webview_available = True logging.debug("✅ WebEngineView disponible para LaTeX") except Exception as e: logging.warning(f"⚠️ WebEngineView no disponible: {e}") # Fallback a QTextBrowser self.text_browser = QTextBrowser() self.text_browser.setOpenExternalLinks(False) self._setup_text_browser() layout.addWidget(self.text_browser) self._webview_available = False def _setup_webview(self): """Configura WebEngineView con MathJax""" html_content = self._generate_mathjax_html() self.webview.setHtml(html_content) def _setup_text_browser(self): """Configura el browser de texto como fallback""" self.text_browser.setStyleSheet(""" QTextBrowser { background-color: #1a1a1a; color: #d4d4d4; border: none; font-family: 'Consolas'; font-size: 11px; padding: 10px; } """) self.text_browser.setHtml("""
📐 Panel de Ecuaciones
Las ecuaciones aparecerán aquí
""") def _generate_mathjax_html(self): """Genera HTML base con MathJax""" return """
Panel de Ecuaciones LaTeX
""" def _check_mathjax_ready(self): """Verifica si MathJax está listo y renderiza ecuaciones pendientes""" if not self._webview_available: return # Verificar si MathJax está listo self.webview.page().runJavaScript( "window.mathJaxReady || false;", self._on_mathjax_ready_check ) def _on_mathjax_ready_check(self, ready): """Callback cuando se verifica el estado de MathJax""" if ready and not self._mathjax_ready: self._mathjax_ready = True self._mathjax_check_timer.stop() logging.debug("✅ MathJax listo, procesando ecuaciones pendientes") # Renderizar ecuaciones pendientes for eq in self._pending_equations: self._add_equation_to_webview(eq['type'], eq['content']) self._pending_equations.clear() # Trigger initial render si hay un calculador padre if self._parent_calculator and hasattr(self._parent_calculator, '_trigger_initial_latex_render'): self._parent_calculator._trigger_initial_latex_render() def _add_equation_to_webview(self, eq_type: str, content: str): """Añade una ecuación directamente al webview""" if self._webview_available: escaped_content = content.replace('\\', '\\\\').replace("'", "\\'").replace('"', '\\"') js_code = f"addEquation('{eq_type}', '{escaped_content}');" self.webview.page().runJavaScript(js_code) def add_equation(self, eq_type: str, content: str): """Añade una ecuación al panel""" self.equations.append({'type': eq_type, 'content': content}) if self._webview_available: if self._mathjax_ready: # MathJax está listo, renderizar inmediatamente self._add_equation_to_webview(eq_type, content) else: # MathJax no está listo, guardar para después self._pending_equations.append({'type': eq_type, 'content': content}) else: # Actualizar HTML en text browser self._update_text_browser() def clear_equations(self): """Limpia todas las ecuaciones""" self.equations.clear() self._pending_equations.clear() if self._webview_available: # Usar JavaScript para limpiar dinámicamente si MathJax está listo if self._mathjax_ready: self.webview.page().runJavaScript("clearEquations();") else: # Si MathJax no está listo, recargar HTML base limpio html_content = self._generate_mathjax_html() self.webview.setHtml(html_content) else: self._setup_text_browser() # Reset al estado inicial def _update_text_browser(self): """Actualiza el contenido del text browser (fallback)""" html_parts = [""" """] for eq in self.equations: eq_type = eq['type'] content = eq['content'] css_class = eq_type if eq_type == 'comment': html_parts.append(f'
{content}
') else: # Para ecuaciones matemáticas, mostrar en formato de código html_parts.append(f'
{content}
') self.text_browser.setHtml(''.join(html_parts)) class LatexProcessor: """Procesador de contenido LaTeX""" @staticmethod def generate_complete_latex_content(result: EvaluationResult) -> str: """Genera contenido LaTeX completo incluyendo toda la información del output""" try: # Para comentarios, usar el texto directamente if result.result_type == "comment": return result.output or "" latex_parts = [] # PARTE 1: Contenido principal (con LaTeX de SymPy si es posible) main_content = "" if result.actual_result_object is not None: try: # Intentar generar LaTeX de SymPy para el objeto matemático main_content = sympy.latex(result.actual_result_object) # Para asignaciones, necesitamos agregar el lado izquierdo if result.is_assignment and result.input_line: # Extraer la variable del lado izquierdo if '=' in result.input_line: left_side = result.input_line.split('=')[0].strip() # Limpiar posibles símbolos LaTeX del lado izquierdo left_side = left_side.replace('$$', '').strip() main_content = f"{left_side} = {main_content}" except Exception: # Si falla el LaTeX de SymPy, usar el output textual main_content = result.output or "" else: main_content = result.output or "" latex_parts.append(main_content) # PARTE 2: Aproximación numérica (si está disponible en el output) if result.output and "≈" in result.output: approx_parts = result.output.split("≈", 1) if len(approx_parts) == 2: approx_value = approx_parts[1].strip() # Extraer solo la parte numérica antes del indicador de tipo if ";" in approx_value: approx_value = approx_value.split(";")[0].strip() # Intentar convertir la aproximación a LaTeX si es una ecuación try: if "Eq(" in approx_value: # Es una ecuación, intentar parserarla para LaTeX approx_obj = eval(approx_value, {'Eq': sympy.Eq, 'sqrt': sympy.sqrt}) approx_latex = sympy.latex(approx_obj) latex_parts.append(f"\\approx {approx_latex}") else: # Es un valor numérico simple latex_parts.append(f"\\approx {approx_value}") except: # Si falla, usar la aproximación como texto latex_parts.append(f"\\approx {approx_value}") # PARTE 3: Indicador de tipo (si está en el output) if result.output and "[" in result.output and "]" in result.output: # Extraer el indicador de tipo (ej: [=], [Equality], etc.) parts = result.output.split("[") if len(parts) >= 2: type_part = "[" + parts[-1] # Tomar el último indicador if "]" in type_part: type_indicator = type_part.split("]")[0] + "]" latex_parts.append(f"\\quad \\text{{{type_indicator}}}") # Combinar todas las partes complete_latex = " ".join(latex_parts) # Limpiar caracteres problemáticos para MathJax complete_latex = complete_latex.replace("__", "_{").replace("**", "^") # Agregar llaves de cierre para subíndices import re complete_latex = re.sub(r'_\{(\w+)', r'_{\1}', complete_latex) return complete_latex except Exception as e: logging.error(f"Error generando LaTeX completo: {e}") # Fallback al output original return result.output or "" @staticmethod def should_add_to_latex(result: EvaluationResult) -> tuple[bool, str]: """Determina si un resultado debe agregarse al panel LaTeX y qué tipo usar""" try: if result.result_type == "comment": return True, "comment" elif result.is_assignment: return True, "assignment" elif result.is_equation: return True, "equation" elif result.success and result.output: # Agregar si tiene contenido matemático math_indicators = ['=', '+', '-', '*', '/', '^', 'sqrt', 'sin', 'cos', 'tan', 'log', 'exp'] if any(indicator in result.output for indicator in math_indicators): return True, "symbolic" elif result.actual_result_object is not None and isinstance(result.actual_result_object, sympy.Basic): return True, "symbolic" return False, "" except Exception: return False, ""