import re import requests import io import logging logger = logging.getLogger(__name__) def generate_latex(markdown_text: str) -> str: """Converts basic markdown with MathJax into a raw LaTeX document string.""" tex = markdown_text # Protect math blocks temporarily so regex doesn't mess up math internals math_blocks = [] def save_math(match): math_blocks.append(match.group(0)) return f"__MATH_{len(math_blocks)-1}__" tex = re.sub(r'\$\$.*?\$\$', save_math, tex, flags=re.DOTALL) tex = re.sub(r'\$.*?\$', save_math, tex) # Markdown -> LaTeX conversions tex = re.sub(r'\*\*(.*?)\*\*', r'\\textbf{\1}', tex) tex = re.sub(r'\*(.*?)\*', r'\\textit{\1}', tex) tex = re.sub(r'^### (.*)$', r'\\subsubsection*{\1}', tex, flags=re.MULTILINE) tex = re.sub(r'^## (.*)$', r'\\subsection*{\1}', tex, flags=re.MULTILINE) tex = re.sub(r'^# (.*)$', r'\\section*{\1}', tex, flags=re.MULTILINE) # Simple list handling tex = re.sub(r'^- (.*)$', r'\\item \1', tex, flags=re.MULTILINE) if "\\item" in tex: # Extremely naive list wrapping tex = re.sub(r'((\\item.*\n?)+)', r'\\begin{itemize}\n\1\\end{itemize}\n', tex) # Replace empty lines with double escape for spacing if needed # Actually, empty lines in LaTeX are good for paragraph breaks. # Restore math blocks for i, block in enumerate(math_blocks): tex = tex.replace(f"__MATH_{i}__", block) preamble = r"""\documentclass[12pt,a4paper]{article} \usepackage[utf8]{inputenc} \usepackage[T1]{fontenc} \usepackage{amsmath, amssymb, amsfonts} \usepackage{geometry} \geometry{a4paper, margin=1in} \usepackage{hyperref} \usepackage{xcolor} \title{MathMinds Generated Report} \author{MathMinds AI Assistant} \date{\today} \begin{document} \maketitle """ return preamble + tex + "\n\n\\end{document}" def compile_pdf(tex_content: str) -> bytes: """Compiles a complete LaTeX string to a PDF using public latexonline.cc API.""" try: import urllib.parse encoded_tex = urllib.parse.quote(tex_content) url = f"https://latexonline.cc/compile?text={encoded_tex}" # If the encoded URL is massive, the server might reject the GET request. # Fallback to the POST /data endpoint with a multipart file upload. if len(url) > 8000: logger.info("URL too long, falling back to POST /data multipart") r = requests.post( "https://latexonline.cc/data?command=pdflatex", files={"file": ("document.tex", tex_content)}, timeout=45 ) else: r = requests.get(url, timeout=45) if r.status_code == 200: return r.content else: logger.error(f"LaTeX compile failed: {r.status_code} - {r.text[:200]}") except Exception as e: logger.error(f"LaTeX API exception: {e}") return b""