Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -5,6 +5,7 @@ import base64
|
|
5 |
from pathlib import Path
|
6 |
import os
|
7 |
import shutil
|
|
|
8 |
|
9 |
# Set page configuration
|
10 |
st.set_page_config(page_title="LaTeX Editor & Compiler", page_icon="📝", layout="wide")
|
@@ -21,8 +22,6 @@ def latex_to_pdf(latex_code):
|
|
21 |
st.code(f"PATH: {os.environ.get('PATH')}")
|
22 |
result = subprocess.run(["which", "pdflatex"], capture_output=True, text=True)
|
23 |
st.code(f"which pdflatex: {result.stdout} {result.stderr}")
|
24 |
-
result = subprocess.run(["ls", "-la", "/usr/bin/pdflatex"], capture_output=True, text=True)
|
25 |
-
st.code(f"ls -la /usr/bin/pdflatex: {result.stdout} {result.stderr}")
|
26 |
return None, "", "Error: pdflatex is not installed or not in PATH."
|
27 |
|
28 |
with tempfile.TemporaryDirectory() as temp_dir:
|
@@ -52,24 +51,30 @@ def latex_to_pdf(latex_code):
|
|
52 |
except Exception as e:
|
53 |
return None, "", str(e)
|
54 |
|
55 |
-
# Function to display PDF
|
56 |
-
def display_pdf(pdf_data):
|
57 |
-
base64_pdf = base64.b64encode(pdf_data).decode('utf-8')
|
58 |
-
pdf_display = f"""
|
59 |
-
<iframe
|
60 |
-
src="data:application/pdf;base64,{base64_pdf}"
|
61 |
-
width="100%"
|
62 |
-
height="600"
|
63 |
-
style="border: none;"
|
64 |
-
></iframe>
|
65 |
-
"""
|
66 |
-
st.markdown(pdf_display, unsafe_allow_html=True)
|
67 |
-
|
68 |
# Function to create download link for PDF
|
69 |
def get_download_link(pdf_data, filename="document.pdf"):
|
70 |
b64_pdf = base64.b64encode(pdf_data).decode()
|
71 |
return f'<a href="data:application/pdf;base64,{b64_pdf}" download="{filename}" class="download-button">Download PDF</a>'
|
72 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
# LaTeX package reference
|
74 |
latex_packages = {
|
75 |
"Document": {
|
@@ -262,18 +267,21 @@ st.markdown("""
|
|
262 |
}
|
263 |
.download-button {
|
264 |
display: inline-block;
|
265 |
-
padding: 0.
|
266 |
background-color: #4CAF50;
|
267 |
color: white !important;
|
268 |
text-align: center;
|
269 |
text-decoration: none;
|
270 |
-
font-size:
|
271 |
border-radius: 4px;
|
272 |
transition: background-color 0.3s;
|
273 |
margin-top: 10px;
|
|
|
|
|
274 |
}
|
275 |
.download-button:hover {
|
276 |
background-color: #45a049;
|
|
|
277 |
}
|
278 |
.stTextArea textarea {
|
279 |
font-family: 'Courier New', Courier, monospace !important;
|
@@ -302,6 +310,29 @@ st.markdown("""
|
|
302 |
</style>
|
303 |
""", unsafe_allow_html=True)
|
304 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
305 |
# Main application
|
306 |
def main():
|
307 |
st.title("LaTeX Editor & PDF Compiler")
|
@@ -310,8 +341,13 @@ def main():
|
|
310 |
if not is_pdflatex_installed():
|
311 |
st.warning("⚠️ LaTeX is not installed correctly. The PDF compilation feature will not work.")
|
312 |
st.info("For Hugging Face Spaces, make sure you have a packages.txt file with the necessary LaTeX packages.")
|
313 |
-
|
314 |
-
|
|
|
|
|
|
|
|
|
|
|
315 |
|
316 |
# Create layout with sidebar
|
317 |
col1, col2 = st.columns([3, 2])
|
@@ -350,19 +386,17 @@ def main():
|
|
350 |
st.rerun()
|
351 |
|
352 |
with col2:
|
353 |
-
st.subheader("PDF
|
354 |
|
355 |
-
# PDF
|
356 |
if 'compile_clicked' in st.session_state and st.session_state.compile_clicked:
|
357 |
with st.spinner("Compiling LaTeX to PDF..."):
|
358 |
pdf_data, stdout, stderr = latex_to_pdf(latex_code)
|
359 |
|
360 |
if pdf_data:
|
361 |
st.session_state.pdf_data = pdf_data
|
362 |
-
|
363 |
-
|
364 |
-
# Download button
|
365 |
-
st.markdown(get_download_link(pdf_data), unsafe_allow_html=True)
|
366 |
st.session_state.compile_clicked = False
|
367 |
else:
|
368 |
st.error("Compilation Error")
|
@@ -373,10 +407,9 @@ def main():
|
|
373 |
|
374 |
# Display previous PDF if available
|
375 |
elif 'pdf_data' in st.session_state and st.session_state.pdf_data:
|
376 |
-
|
377 |
-
st.markdown(get_download_link(st.session_state.pdf_data), unsafe_allow_html=True)
|
378 |
else:
|
379 |
-
st.info("Compile your LaTeX document to
|
380 |
|
381 |
# LaTeX Reference Sidebar
|
382 |
st.sidebar.title("LaTeX Reference")
|
@@ -397,7 +430,12 @@ def main():
|
|
397 |
found = True
|
398 |
with st.sidebar.expander(f"{category} ({len(filtered_commands)} results)"):
|
399 |
for cmd, desc in filtered_commands.items():
|
400 |
-
|
|
|
|
|
|
|
|
|
|
|
401 |
|
402 |
# Search packages
|
403 |
for category, packages in latex_packages.items():
|
@@ -408,7 +446,11 @@ def main():
|
|
408 |
found = True
|
409 |
with st.sidebar.expander(f"Packages: {category} ({len(filtered_packages)} results)"):
|
410 |
for pkg, desc in filtered_packages.items():
|
411 |
-
|
|
|
|
|
|
|
|
|
412 |
|
413 |
if not found:
|
414 |
st.sidebar.info("No matching commands or packages found")
|
@@ -422,12 +464,18 @@ def main():
|
|
422 |
with st.expander(category):
|
423 |
for cmd, desc in commands.items():
|
424 |
st.markdown(f"<div class='latex-command'>{cmd}</div> - {desc}", unsafe_allow_html=True)
|
|
|
|
|
|
|
425 |
|
426 |
with tabs[1]:
|
427 |
for category, packages in latex_packages.items():
|
428 |
with st.expander(category):
|
429 |
for pkg, desc in packages.items():
|
430 |
st.markdown(f"<div class='latex-command'>{pkg}</div> - {desc}", unsafe_allow_html=True)
|
|
|
|
|
|
|
431 |
|
432 |
if __name__ == "__main__":
|
433 |
main()
|
|
|
5 |
from pathlib import Path
|
6 |
import os
|
7 |
import shutil
|
8 |
+
import time
|
9 |
|
10 |
# Set page configuration
|
11 |
st.set_page_config(page_title="LaTeX Editor & Compiler", page_icon="📝", layout="wide")
|
|
|
22 |
st.code(f"PATH: {os.environ.get('PATH')}")
|
23 |
result = subprocess.run(["which", "pdflatex"], capture_output=True, text=True)
|
24 |
st.code(f"which pdflatex: {result.stdout} {result.stderr}")
|
|
|
|
|
25 |
return None, "", "Error: pdflatex is not installed or not in PATH."
|
26 |
|
27 |
with tempfile.TemporaryDirectory() as temp_dir:
|
|
|
51 |
except Exception as e:
|
52 |
return None, "", str(e)
|
53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
54 |
# Function to create download link for PDF
|
55 |
def get_download_link(pdf_data, filename="document.pdf"):
|
56 |
b64_pdf = base64.b64encode(pdf_data).decode()
|
57 |
return f'<a href="data:application/pdf;base64,{b64_pdf}" download="{filename}" class="download-button">Download PDF</a>'
|
58 |
|
59 |
+
# Convert PDF to images for preview (safer than embedding PDF)
|
60 |
+
def pdf_preview(pdf_data):
|
61 |
+
# Save PDF to a temporary file
|
62 |
+
if not pdf_data:
|
63 |
+
return st.error("No PDF data to preview")
|
64 |
+
|
65 |
+
st.warning("PDF preview is not available due to browser security restrictions.")
|
66 |
+
st.info("Please download the PDF using the button below to view it.")
|
67 |
+
|
68 |
+
# Provide download button with more prominent styling
|
69 |
+
st.markdown(
|
70 |
+
f"""
|
71 |
+
<div style="text-align: center; margin: 30px 0;">
|
72 |
+
{get_download_link(pdf_data)}
|
73 |
+
</div>
|
74 |
+
""",
|
75 |
+
unsafe_allow_html=True
|
76 |
+
)
|
77 |
+
|
78 |
# LaTeX package reference
|
79 |
latex_packages = {
|
80 |
"Document": {
|
|
|
267 |
}
|
268 |
.download-button {
|
269 |
display: inline-block;
|
270 |
+
padding: 0.7em 1.4em;
|
271 |
background-color: #4CAF50;
|
272 |
color: white !important;
|
273 |
text-align: center;
|
274 |
text-decoration: none;
|
275 |
+
font-size: 18px;
|
276 |
border-radius: 4px;
|
277 |
transition: background-color 0.3s;
|
278 |
margin-top: 10px;
|
279 |
+
font-weight: bold;
|
280 |
+
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
281 |
}
|
282 |
.download-button:hover {
|
283 |
background-color: #45a049;
|
284 |
+
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
|
285 |
}
|
286 |
.stTextArea textarea {
|
287 |
font-family: 'Courier New', Courier, monospace !important;
|
|
|
310 |
</style>
|
311 |
""", unsafe_allow_html=True)
|
312 |
|
313 |
+
# JavaScript for copying commands (optional, may not work in all environments)
|
314 |
+
st.markdown("""
|
315 |
+
<script>
|
316 |
+
document.addEventListener('DOMContentLoaded', (event) => {
|
317 |
+
// Add click handlers to copy LaTeX commands
|
318 |
+
document.querySelectorAll('.latex-command').forEach(element => {
|
319 |
+
element.addEventListener('click', function() {
|
320 |
+
const textToCopy = this.textContent;
|
321 |
+
const textArea = document.querySelector('.stTextArea textarea');
|
322 |
+
if (textArea) {
|
323 |
+
const start = textArea.selectionStart;
|
324 |
+
const end = textArea.selectionEnd;
|
325 |
+
const value = textArea.value;
|
326 |
+
textArea.value = value.substring(0, start) + textToCopy + value.substring(end);
|
327 |
+
textArea.selectionStart = textArea.selectionEnd = start + textToCopy.length;
|
328 |
+
textArea.focus();
|
329 |
+
}
|
330 |
+
});
|
331 |
+
});
|
332 |
+
});
|
333 |
+
</script>
|
334 |
+
""", unsafe_allow_html=True)
|
335 |
+
|
336 |
# Main application
|
337 |
def main():
|
338 |
st.title("LaTeX Editor & PDF Compiler")
|
|
|
341 |
if not is_pdflatex_installed():
|
342 |
st.warning("⚠️ LaTeX is not installed correctly. The PDF compilation feature will not work.")
|
343 |
st.info("For Hugging Face Spaces, make sure you have a packages.txt file with the necessary LaTeX packages.")
|
344 |
+
|
345 |
+
# Show packages.txt content suggestion
|
346 |
+
with st.expander("Required packages.txt content"):
|
347 |
+
st.code("""texlive
|
348 |
+
texlive-latex-extra
|
349 |
+
texlive-fonts-recommended
|
350 |
+
texlive-science""", language="text")
|
351 |
|
352 |
# Create layout with sidebar
|
353 |
col1, col2 = st.columns([3, 2])
|
|
|
386 |
st.rerun()
|
387 |
|
388 |
with col2:
|
389 |
+
st.subheader("PDF Output")
|
390 |
|
391 |
+
# PDF compilation and download
|
392 |
if 'compile_clicked' in st.session_state and st.session_state.compile_clicked:
|
393 |
with st.spinner("Compiling LaTeX to PDF..."):
|
394 |
pdf_data, stdout, stderr = latex_to_pdf(latex_code)
|
395 |
|
396 |
if pdf_data:
|
397 |
st.session_state.pdf_data = pdf_data
|
398 |
+
st.success("PDF compiled successfully!")
|
399 |
+
pdf_preview(pdf_data)
|
|
|
|
|
400 |
st.session_state.compile_clicked = False
|
401 |
else:
|
402 |
st.error("Compilation Error")
|
|
|
407 |
|
408 |
# Display previous PDF if available
|
409 |
elif 'pdf_data' in st.session_state and st.session_state.pdf_data:
|
410 |
+
pdf_preview(st.session_state.pdf_data)
|
|
|
411 |
else:
|
412 |
+
st.info("Compile your LaTeX document to generate a PDF for download")
|
413 |
|
414 |
# LaTeX Reference Sidebar
|
415 |
st.sidebar.title("LaTeX Reference")
|
|
|
430 |
found = True
|
431 |
with st.sidebar.expander(f"{category} ({len(filtered_commands)} results)"):
|
432 |
for cmd, desc in filtered_commands.items():
|
433 |
+
cmd_display = cmd.replace("\\", "\\\\") # Escape backslashes for display
|
434 |
+
st.markdown(f"<div class='latex-command' title='Click to copy'>{cmd}</div> - {desc}", unsafe_allow_html=True)
|
435 |
+
if st.button(f"Insert '{cmd}'", key=f"btn_{cmd}"):
|
436 |
+
# Insert at cursor not supported directly, but we can append
|
437 |
+
st.session_state.latex_code += f"\n{cmd}"
|
438 |
+
st.rerun()
|
439 |
|
440 |
# Search packages
|
441 |
for category, packages in latex_packages.items():
|
|
|
446 |
found = True
|
447 |
with st.sidebar.expander(f"Packages: {category} ({len(filtered_packages)} results)"):
|
448 |
for pkg, desc in filtered_packages.items():
|
449 |
+
pkg_display = pkg.replace("\\", "\\\\") # Escape backslashes for display
|
450 |
+
st.markdown(f"<div class='latex-command' title='Click to copy'>{pkg}</div> - {desc}", unsafe_allow_html=True)
|
451 |
+
if st.button(f"Insert '{pkg}'", key=f"btn_{pkg}"):
|
452 |
+
st.session_state.latex_code += f"\n{pkg}"
|
453 |
+
st.rerun()
|
454 |
|
455 |
if not found:
|
456 |
st.sidebar.info("No matching commands or packages found")
|
|
|
464 |
with st.expander(category):
|
465 |
for cmd, desc in commands.items():
|
466 |
st.markdown(f"<div class='latex-command'>{cmd}</div> - {desc}", unsafe_allow_html=True)
|
467 |
+
if st.button(f"Insert '{cmd}'", key=f"btn_{cmd}"):
|
468 |
+
st.session_state.latex_code += f"\n{cmd}"
|
469 |
+
st.rerun()
|
470 |
|
471 |
with tabs[1]:
|
472 |
for category, packages in latex_packages.items():
|
473 |
with st.expander(category):
|
474 |
for pkg, desc in packages.items():
|
475 |
st.markdown(f"<div class='latex-command'>{pkg}</div> - {desc}", unsafe_allow_html=True)
|
476 |
+
if st.button(f"Insert '{pkg}'", key=f"btn_{pkg}"):
|
477 |
+
st.session_state.latex_code += f"\n{pkg}"
|
478 |
+
st.rerun()
|
479 |
|
480 |
if __name__ == "__main__":
|
481 |
main()
|