#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Timestamp: 2026-01-27
# File: src/scitex_writer/_mcp/content.py
"""Content compilation for LaTeX snippets and sections.
Provides quick compilation of raw LaTeX content with color mode support.
"""
from __future__ import annotations
import re
import subprocess
import tempfile
from pathlib import Path
from typing import Literal, Optional
from .utils import resolve_project_path
def compile_content(
latex_content: str,
project_dir: Optional[str] = None,
color_mode: Literal["light", "dark", "sepia", "paper"] = "light",
section_name: str = "content",
timeout: int = 60,
keep_aux: bool = False,
) -> dict:
"""Compile raw LaTeX content to PDF.
Creates a standalone document from the provided LaTeX content and compiles
it to PDF. Supports various color modes for comfortable viewing and can
link to project bibliography for citation rendering.
Args:
latex_content: Raw LaTeX content to compile. Can be:
- A complete document (with \\documentclass)
- Document body only (will be wrapped automatically)
project_dir: Optional path to scitex-writer project for bibliography.
color_mode: Color theme for output:
- 'light': Default white background
- 'dark': Dark gray background with light text
- 'sepia': Warm cream background for comfortable reading
- 'paper': Pure white, optimized for printing
section_name: Name for the output (used in filename).
timeout: Compilation timeout in seconds.
keep_aux: Keep auxiliary files (.aux, .log, etc.) after compilation.
Returns:
Dict with success status, output_pdf path, log, and any errors.
"""
try:
temp_dir = Path(tempfile.mkdtemp(prefix=f"scitex_content_{section_name}_"))
tex_file = temp_dir / f"{section_name}.tex"
pdf_file = temp_dir / f"{section_name}.pdf"
is_complete_document = "\\documentclass" in latex_content
if is_complete_document:
final_content = _inject_color_mode(latex_content, color_mode)
else:
final_content = _create_content_document(
latex_content, color_mode, project_dir
)
tex_file.write_text(final_content, encoding="utf-8")
if project_dir:
_setup_bibliography(temp_dir, project_dir)
cmd = [
"latexmk",
"-pdf",
"-interaction=nonstopmode",
"-halt-on-error",
f"-jobname={section_name}",
str(tex_file),
]
result = subprocess.run(
cmd,
cwd=str(temp_dir),
capture_output=True,
text=True,
timeout=timeout,
)
log_content = _read_log_file(temp_dir, section_name)
if not keep_aux:
_cleanup_aux_files(temp_dir, section_name)
if result.returncode == 0 and pdf_file.exists():
return {
"success": True,
"output_pdf": str(pdf_file),
"temp_dir": str(temp_dir),
"color_mode": color_mode,
"log": log_content,
"message": f"Content compiled successfully: {section_name}",
}
else:
return {
"success": False,
"output_pdf": None,
"temp_dir": str(temp_dir),
"color_mode": color_mode,
"log": log_content,
"stdout": result.stdout[-2000:]
if len(result.stdout) > 2000
else result.stdout,
"stderr": result.stderr[-2000:]
if len(result.stderr) > 2000
else result.stderr,
"error": f"Compilation failed with exit code {result.returncode}",
}
except subprocess.TimeoutExpired:
return {
"success": False,
"error": f"Content compilation timed out after {timeout} seconds",
}
except Exception as e:
return {
"success": False,
"error": str(e),
}
def _setup_bibliography(temp_dir: Path, project_dir: str) -> None:
"""Set up bibliography files in temp directory."""
project_path = resolve_project_path(project_dir)
bib_dir = project_path / "00_shared" / "bib_files"
if bib_dir.exists():
for bib_file in bib_dir.glob("*.bib"):
link_path = temp_dir / bib_file.name
if not link_path.exists():
link_path.symlink_to(bib_file)
def _read_log_file(temp_dir: Path, section_name: str) -> str:
"""Read and truncate log file content."""
log_file = temp_dir / f"{section_name}.log"
if log_file.exists():
content = log_file.read_text(encoding="utf-8", errors="replace")
return content[-5000:] if len(content) > 5000 else content
return ""
def _cleanup_aux_files(temp_dir: Path, section_name: str) -> None:
"""Remove auxiliary files from compilation."""
aux_extensions = [
".aux",
".log",
".fls",
".fdb_latexmk",
".synctex.gz",
".out",
".bbl",
".blg",
".toc",
".lof",
".lot",
]
for ext in aux_extensions:
aux_file = temp_dir / f"{section_name}{ext}"
if aux_file.exists():
aux_file.unlink()
def _inject_color_mode(latex_content: str, color_mode: str) -> str:
"""Inject color mode styling into an existing LaTeX document."""
color_commands = _get_color_commands(color_mode)
if not color_commands:
return latex_content
begin_doc_pattern = r"(\\begin\{document\})"
match = re.search(begin_doc_pattern, latex_content)
if match:
insert_pos = match.end()
return (
latex_content[:insert_pos]
+ "\n"
+ color_commands
+ "\n"
+ latex_content[insert_pos:]
)
else:
return color_commands + "\n" + latex_content
def _get_color_commands(color_mode: str) -> str:
"""Get LaTeX color commands for the specified color mode."""
color_configs = {
"light": "",
"dark": """% Dark mode styling
\\pagecolor{black!95!white}
\\color{white}
\\makeatletter
\\@ifpackageloaded{hyperref}{%
\\hypersetup{
colorlinks=true,
linkcolor=cyan!80!white,
citecolor=green!70!white,
urlcolor=blue!60!white,
}%
}{}
\\makeatother""",
"sepia": """% Sepia mode styling
\\pagecolor{Sepia!15!white}
\\color{black!85!Sepia}
\\makeatletter
\\@ifpackageloaded{hyperref}{%
\\hypersetup{
colorlinks=true,
linkcolor=brown!70!black,
citecolor=olive!70!black,
urlcolor=teal!70!black,
}%
}{}
\\makeatother""",
"paper": "",
}
return color_configs.get(color_mode, "")
def _create_content_document(
body_content: str, color_mode: str, project_dir: Optional[str]
) -> str:
"""Create a complete LaTeX document wrapping the body content."""
color_commands = _get_color_commands(color_mode)
bib_line = "\\bibliography{bibliography}" if project_dir else ""
return f"""\\documentclass[11pt]{{article}}
% Essential packages
\\usepackage[english]{{babel}}
\\usepackage[T1]{{fontenc}}
\\usepackage[utf8]{{inputenc}}
\\usepackage[table,svgnames]{{xcolor}}
\\usepackage{{amsmath, amssymb}}
\\usepackage{{graphicx}}
\\usepackage{{booktabs}}
\\usepackage{{hyperref}}
\\usepackage[numbers]{{natbib}}
\\usepackage{{geometry}}
\\geometry{{margin=1in}}
\\usepackage{{pagecolor}}
\\begin{{document}}
{color_commands}
{body_content}
{bib_line}
\\end{{document}}
"""
__all__ = ["compile_content"]
# EOF