""" Sistema de gestión de MathJax local con descarga automática """ import os import zipfile import logging from pathlib import Path from urllib.request import urlretrieve from urllib.error import URLError class MathJaxManager: """Gestor de MathJax local con descarga automática""" MATHJAX_VERSION = "3.2.2" MATHJAX_URL = f"https://github.com/mathjax/MathJax/archive/{MATHJAX_VERSION}.zip" LOCAL_DIR = Path(".mathjax") def __init__(self): self.logger = logging.getLogger(__name__) self.mathjax_dir = self.LOCAL_DIR / f"MathJax-{self.MATHJAX_VERSION}" self.es5_dir = self.mathjax_dir / "es5" def get_mathjax_url(self) -> str: """ Obtiene la URL de MathJax (local si está disponible, CDN si no) """ if self.is_local_available(): # Construir URL local relativa para file:// local_url = f"file:///{self.es5_dir.absolute().as_posix()}/tex-svg.js" self.logger.debug(f"Usando MathJax local: {local_url}") return local_url else: # Descargar automáticamente si no existe if self.download_mathjax(): local_url = f"file:///{self.es5_dir.absolute().as_posix()}/tex-svg.js" self.logger.info(f"MathJax descargado y configurado localmente: {local_url}") return local_url else: # Fallback a CDN cdn_url = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js" self.logger.warning(f"Fallback a CDN: {cdn_url}") return cdn_url def is_local_available(self) -> bool: """Verifica si MathJax está disponible localmente""" tex_svg_file = self.es5_dir / "tex-svg.js" return tex_svg_file.exists() and tex_svg_file.is_file() def download_mathjax(self) -> bool: """ Descarga MathJax automáticamente si no está disponible Returns: True si la descarga fue exitosa, False si falló """ try: self.logger.info(f"Descargando MathJax {self.MATHJAX_VERSION}...") # Crear directorio si no existe self.LOCAL_DIR.mkdir(exist_ok=True) # Archivo temporal para la descarga zip_path = self.LOCAL_DIR / f"mathjax-{self.MATHJAX_VERSION}.zip" # Descargar archivo ZIP self.logger.debug(f"Descargando desde: {self.MATHJAX_URL}") urlretrieve(self.MATHJAX_URL, zip_path) # Extraer archivo self.logger.debug(f"Extrayendo en: {self.LOCAL_DIR}") with zipfile.ZipFile(zip_path, 'r') as zip_ref: zip_ref.extractall(self.LOCAL_DIR) # Limpiar archivo ZIP zip_path.unlink() # Verificar que se extrajo correctamente if self.is_local_available(): self.logger.info(f"✅ MathJax {self.MATHJAX_VERSION} descargado correctamente") return True else: self.logger.error("❌ Error: MathJax no se extrajo correctamente") return False except URLError as e: self.logger.error(f"❌ Error de red descargando MathJax: {e}") return False except zipfile.BadZipFile as e: self.logger.error(f"❌ Error: archivo ZIP corrupto: {e}") # Limpiar archivo corrupto if zip_path.exists(): zip_path.unlink() return False except Exception as e: self.logger.error(f"❌ Error inesperado descargando MathJax: {e}") return False def clean_local_mathjax(self): """ Limpia la instalación local de MathJax (Para forzar redownload en la siguiente inicialización) """ try: if self.LOCAL_DIR.exists(): import shutil shutil.rmtree(self.LOCAL_DIR) self.logger.info("🗑️ MathJax local limpiado") except Exception as e: self.logger.error(f"Error limpiando MathJax local: {e}") def get_status_info(self) -> dict: """Obtiene información del estado de MathJax""" return { "local_available": self.is_local_available(), "local_dir": str(self.LOCAL_DIR.absolute()), "mathjax_dir": str(self.mathjax_dir.absolute()) if self.mathjax_dir.exists() else None, "version": self.MATHJAX_VERSION, "tex_svg_file": str(self.es5_dir / "tex-svg.js") if self.is_local_available() else None }