Latex_builder / app.py
euler314's picture
Update app.py
e6742d7 verified
raw
history blame
17.1 kB
import streamlit as st
import subprocess
import tempfile
import base64
from pathlib import Path
import os
import shutil
import time
# Set page configuration
st.set_page_config(page_title="LaTeX Editor & Compiler", page_icon="πŸ“", layout="wide")
# Check if pdflatex is available
def is_pdflatex_installed():
return shutil.which("pdflatex") is not None
# Function to convert LaTeX to PDF
def latex_to_pdf(latex_code):
# Check if pdflatex is installed
if not is_pdflatex_installed():
st.error("pdflatex not found. Debug info:")
st.code(f"PATH: {os.environ.get('PATH')}")
result = subprocess.run(["which", "pdflatex"], capture_output=True, text=True)
st.code(f"which pdflatex: {result.stdout} {result.stderr}")
return None, "", "Error: pdflatex is not installed or not in PATH."
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
tex_file = temp_path / "document.tex"
pdf_file = temp_path / "document.pdf"
# Write LaTeX code to file
with open(tex_file, "w") as f:
f.write(latex_code)
try:
# Run pdflatex to compile the LaTeX file
process = subprocess.run(
["pdflatex", "-interaction=nonstopmode", "-output-directory", temp_dir, str(tex_file)],
capture_output=True,
text=True
)
# Check if PDF was created
if pdf_file.exists():
with open(pdf_file, "rb") as file:
pdf_data = file.read()
return pdf_data, process.stdout, process.stderr
else:
return None, process.stdout, process.stderr
except Exception as e:
return None, "", str(e)
# Function to create download link for PDF
def get_download_link(pdf_data, filename="document.pdf"):
b64_pdf = base64.b64encode(pdf_data).decode()
return f'<a href="data:application/pdf;base64,{b64_pdf}" download="{filename}" class="download-button">Download PDF</a>'
# Convert PDF to images for preview (safer than embedding PDF)
def pdf_preview(pdf_data):
# Save PDF to a temporary file
if not pdf_data:
return st.error("No PDF data to preview")
st.warning("PDF preview is not available due to browser security restrictions.")
st.info("Please download the PDF using the button below to view it.")
# Provide download button with more prominent styling
st.markdown(
f"""
<div style="text-align: center; margin: 30px 0;">
{get_download_link(pdf_data)}
</div>
""",
unsafe_allow_html=True
)
# LaTeX package reference
latex_packages = {
"Document": {
"\\usepackage{geometry}": "Page layout customization",
"\\usepackage{fancyhdr}": "Custom headers and footers",
"\\usepackage{titlesec}": "Title formatting",
"\\usepackage{hyperref}": "Hyperlinks and PDF metadata"
},
"Math": {
"\\usepackage{amsmath}": "Enhanced math formatting",
"\\usepackage{amssymb}": "Mathematical symbols",
"\\usepackage{mathtools}": "Extensions to amsmath",
"\\usepackage{physics}": "Physics notation"
},
"Graphics": {
"\\usepackage{graphicx}": "Include images",
"\\usepackage{tikz}": "Create vector graphics",
"\\usepackage{pgfplots}": "Create plots",
"\\usepackage{float}": "Better figure placement"
},
"Tables": {
"\\usepackage{tabularx}": "Flexible tables",
"\\usepackage{booktabs}": "Professional tables",
"\\usepackage{colortbl}": "Colored tables",
"\\usepackage{multirow}": "Multi-row cells"
},
"Content": {
"\\usepackage{listings}": "Code syntax highlighting",
"\\usepackage{minted}": "Advanced code highlighting",
"\\usepackage{biblatex}": "Bibliography management",
"\\usepackage{xcolor}": "Color support"
}
}
# LaTeX commands reference
latex_commands = {
"Document Structure": {
"\\documentclass{article}": "Specifies the type of document",
"\\begin{document}": "Starts the document content",
"\\end{document}": "Ends the document content",
"\\title{...}": "Sets the document title",
"\\author{...}": "Sets the document author",
"\\date{...}": "Sets the document date",
"\\maketitle": "Prints the title, author, and date"
},
"Sections": {
"\\section{...}": "Creates a section",
"\\subsection{...}": "Creates a subsection",
"\\subsubsection{...}": "Creates a subsubsection",
"\\paragraph{...}": "Creates a paragraph heading",
"\\tableofcontents": "Generates a table of contents"
},
"Text Formatting": {
"\\textbf{...}": "Bold text",
"\\textit{...}": "Italic text",
"\\underline{...}": "Underlined text",
"\\emph{...}": "Emphasized text",
"\\texttt{...}": "Typewriter text",
"\\textsc{...}": "Small caps text",
"\\textsf{...}": "Sans-serif text",
"\\color{red}{...}": "Colored text (requires xcolor)"
},
"Math": {
"$...$": "Inline math mode",
"$$...$$": "Display math mode",
"\\begin{equation}...\\end{equation}": "Numbered equation",
"\\begin{align}...\\end{align}": "Aligned equations",
"\\frac{num}{denom}": "Fraction",
"\\dfrac{num}{denom}": "Display fraction",
"\\sqrt{...}": "Square root",
"\\sqrt[n]{...}": "nth root",
"\\sum_{lower}^{upper}": "Summation",
"\\prod_{lower}^{upper}": "Product",
"\\int_{lower}^{upper}": "Integral",
"\\lim_{x \\to value}": "Limit",
"\\vec{...}": "Vector",
"\\overline{...}": "Overline",
"\\hat{...}": "Hat accent",
"\\partial": "Partial derivative"
},
"Lists": {
"\\begin{itemize}...\\end{itemize}": "Bulleted list",
"\\begin{enumerate}...\\end{enumerate}": "Numbered list",
"\\begin{description}...\\end{description}": "Description list",
"\\item": "List item",
"\\item[custom]": "Custom label item"
},
"Tables": {
"\\begin{table}...\\end{table}": "Table environment",
"\\begin{tabular}{cols}...\\end{tabular}": "Create a table",
"\\hline": "Horizontal line in table",
"\\cline{i-j}": "Partial horizontal line",
"cell1 & cell2 & cell3 \\\\": "Table row",
"\\multicolumn{n}{align}{content}": "Span multiple columns"
},
"Figures": {
"\\begin{figure}...\\end{figure}": "Figure environment",
"\\includegraphics[width=0.8\\textwidth]{filename}": "Include an image",
"\\caption{...}": "Add a caption to a figure or table",
"\\label{...}": "Add a label for cross-referencing",
"\\ref{...}": "Reference a labeled item"
},
"Citations": {
"\\cite{key}": "Citation",
"\\bibliography{file}": "Bibliography source",
"\\bibliographystyle{style}": "Bibliography style"
}
}
# Default LaTeX template
default_template = r"""\documentclass{article}
\usepackage[utf8]{inputenc}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{graphicx}
\usepackage{hyperref}
\title{LaTeX Document}
\author{Your Name}
\date{\today}
\begin{document}
\maketitle
\section{Introduction}
Your introduction here. Insert some text to demonstrate LaTeX.
\section{Mathematical Expressions}
\subsection{Equations}
The famous Einstein's equation:
\begin{equation}
E = mc^2
\end{equation}
The quadratic formula:
\begin{equation}
x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
\end{equation}
\subsection{Calculus}
An integral example:
\begin{equation}
\int_{0}^{\pi} \sin(x) \, dx = 2
\end{equation}
\section{Lists and Items}
\subsection{Bullet Points}
\begin{itemize}
\item First item
\item Second item
\item Third item
\end{itemize}
\subsection{Numbered List}
\begin{enumerate}
\item First step
\item Second step
\item Third step
\end{enumerate}
\section{Tables}
\begin{table}[h]
\centering
\begin{tabular}{|c|c|c|}
\hline
Cell 1 & Cell 2 & Cell 3 \\
\hline
Data 1 & Data 2 & Data 3 \\
\hline
\end{tabular}
\caption{A simple table}
\label{tab:simple}
\end{table}
\section{Conclusion}
Your conclusion here.
\end{document}
"""
# Add custom CSS
st.markdown("""
<style>
.editor-container {
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
background-color: #f8f9fa;
}
.download-button {
display: inline-block;
padding: 0.7em 1.4em;
background-color: #4CAF50;
color: white !important;
text-align: center;
text-decoration: none;
font-size: 18px;
border-radius: 4px;
transition: background-color 0.3s;
margin-top: 10px;
font-weight: bold;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.download-button:hover {
background-color: #45a049;
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
}
.stTextArea textarea {
font-family: 'Courier New', Courier, monospace !important;
font-size: 14px !important;
line-height: 1.5 !important;
}
.latex-command {
background-color: #f1f1f1;
padding: 2px 4px;
border-radius: 3px;
font-family: monospace;
cursor: pointer;
}
.reference-tabs .stTabs {
background-color: #f5f5f5;
border-radius: 5px;
padding: 10px;
}
.stMarkdown h4 {
margin-top: 0.5rem !important;
margin-bottom: 0.5rem !important;
}
.command-category {
margin-bottom: 15px !important;
}
</style>
""", unsafe_allow_html=True)
# JavaScript for copying commands (optional, may not work in all environments)
st.markdown("""
<script>
document.addEventListener('DOMContentLoaded', (event) => {
// Add click handlers to copy LaTeX commands
document.querySelectorAll('.latex-command').forEach(element => {
element.addEventListener('click', function() {
const textToCopy = this.textContent;
const textArea = document.querySelector('.stTextArea textarea');
if (textArea) {
const start = textArea.selectionStart;
const end = textArea.selectionEnd;
const value = textArea.value;
textArea.value = value.substring(0, start) + textToCopy + value.substring(end);
textArea.selectionStart = textArea.selectionEnd = start + textToCopy.length;
textArea.focus();
}
});
});
});
</script>
""", unsafe_allow_html=True)
# Main application
def main():
st.title("LaTeX Editor & PDF Compiler")
# Display installation status
if not is_pdflatex_installed():
st.warning("⚠️ LaTeX is not installed correctly. The PDF compilation feature will not work.")
st.info("For Hugging Face Spaces, make sure you have a packages.txt file with the necessary LaTeX packages.")
# Show packages.txt content suggestion
with st.expander("Required packages.txt content"):
st.code("""texlive
texlive-latex-extra
texlive-fonts-recommended
texlive-science""", language="text")
# Create layout with sidebar
col1, col2 = st.columns([3, 2])
with col1:
st.subheader("LaTeX Editor")
# Initialize session state
if 'latex_code' not in st.session_state:
st.session_state.latex_code = default_template
# LaTeX editor
latex_code = st.text_area(
"Edit your LaTeX document:",
value=st.session_state.latex_code,
height=500,
key="latex_editor"
)
st.session_state.latex_code = latex_code
# Control buttons
col1_1, col1_2, col1_3 = st.columns(3)
with col1_1:
if st.button("Compile PDF", use_container_width=True):
st.session_state.compile_clicked = True
with col1_2:
if st.button("Load Template", use_container_width=True):
st.session_state.latex_code = default_template
st.rerun()
with col1_3:
if st.button("Clear Editor", use_container_width=True):
st.session_state.latex_code = ""
st.rerun()
with col2:
st.subheader("PDF Output")
# PDF compilation and download
if 'compile_clicked' in st.session_state and st.session_state.compile_clicked:
with st.spinner("Compiling LaTeX to PDF..."):
pdf_data, stdout, stderr = latex_to_pdf(latex_code)
if pdf_data:
st.session_state.pdf_data = pdf_data
st.success("PDF compiled successfully!")
pdf_preview(pdf_data)
st.session_state.compile_clicked = False
else:
st.error("Compilation Error")
with st.expander("Error Details"):
st.text(stdout)
st.text(stderr)
st.session_state.compile_clicked = False
# Display previous PDF if available
elif 'pdf_data' in st.session_state and st.session_state.pdf_data:
pdf_preview(st.session_state.pdf_data)
else:
st.info("Compile your LaTeX document to generate a PDF for download")
# LaTeX Reference Sidebar
st.sidebar.title("LaTeX Reference")
# Search functionality
search_query = st.sidebar.text_input("Search commands or packages", "")
if search_query:
st.sidebar.subheader("Search Results")
found = False
# Search commands
for category, commands in latex_commands.items():
filtered_commands = {cmd: desc for cmd, desc in commands.items()
if search_query.lower() in cmd.lower() or search_query.lower() in desc.lower()}
if filtered_commands:
found = True
with st.sidebar.expander(f"{category} ({len(filtered_commands)} results)"):
for cmd, desc in filtered_commands.items():
cmd_display = cmd.replace("\\", "\\\\") # Escape backslashes for display
st.markdown(f"<div class='latex-command' title='Click to copy'>{cmd}</div> - {desc}", unsafe_allow_html=True)
if st.button(f"Insert '{cmd}'", key=f"btn_{cmd}"):
# Insert at cursor not supported directly, but we can append
st.session_state.latex_code += f"\n{cmd}"
st.rerun()
# Search packages
for category, packages in latex_packages.items():
filtered_packages = {pkg: desc for pkg, desc in packages.items()
if search_query.lower() in pkg.lower() or search_query.lower() in desc.lower()}
if filtered_packages:
found = True
with st.sidebar.expander(f"Packages: {category} ({len(filtered_packages)} results)"):
for pkg, desc in filtered_packages.items():
pkg_display = pkg.replace("\\", "\\\\") # Escape backslashes for display
st.markdown(f"<div class='latex-command' title='Click to copy'>{pkg}</div> - {desc}", unsafe_allow_html=True)
if st.button(f"Insert '{pkg}'", key=f"btn_{pkg}"):
st.session_state.latex_code += f"\n{pkg}"
st.rerun()
if not found:
st.sidebar.info("No matching commands or packages found")
else:
# Display full reference when not searching
tabs = st.sidebar.tabs(["Commands", "Packages"])
with tabs[0]:
for category, commands in latex_commands.items():
with st.expander(category):
for cmd, desc in commands.items():
st.markdown(f"<div class='latex-command'>{cmd}</div> - {desc}", unsafe_allow_html=True)
if st.button(f"Insert '{cmd}'", key=f"btn_{cmd}"):
st.session_state.latex_code += f"\n{cmd}"
st.rerun()
with tabs[1]:
for category, packages in latex_packages.items():
with st.expander(category):
for pkg, desc in packages.items():
st.markdown(f"<div class='latex-command'>{pkg}</div> - {desc}", unsafe_allow_html=True)
if st.button(f"Insert '{pkg}'", key=f"btn_{pkg}"):
st.session_state.latex_code += f"\n{pkg}"
st.rerun()
if __name__ == "__main__":
main()