Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -5,7 +5,9 @@ import base64
|
|
5 |
from pathlib import Path
|
6 |
import os
|
7 |
import shutil
|
8 |
-
import
|
|
|
|
|
9 |
|
10 |
# Set page configuration
|
11 |
st.set_page_config(page_title="LaTeX Editor & Compiler", page_icon="📝", layout="wide")
|
@@ -56,24 +58,32 @@ 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
|
60 |
-
def
|
61 |
-
# Save PDF to a temporary file
|
62 |
if not pdf_data:
|
63 |
-
return
|
64 |
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
77 |
|
78 |
# LaTeX package reference
|
79 |
latex_packages = {
|
@@ -256,15 +266,23 @@ Your conclusion here.
|
|
256 |
\end{document}
|
257 |
"""
|
258 |
|
259 |
-
# Add custom CSS
|
260 |
st.markdown("""
|
261 |
<style>
|
|
|
262 |
.editor-container {
|
263 |
border: 1px solid #ccc;
|
264 |
border-radius: 5px;
|
265 |
padding: 10px;
|
266 |
background-color: #f8f9fa;
|
267 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
268 |
.download-button {
|
269 |
display: inline-block;
|
270 |
padding: 0.7em 1.4em;
|
@@ -283,56 +301,91 @@ st.markdown("""
|
|
283 |
background-color: #45a049;
|
284 |
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
|
285 |
}
|
286 |
-
|
287 |
-
|
288 |
-
|
289 |
-
|
290 |
}
|
|
|
|
|
291 |
.latex-command {
|
292 |
-
background-color: #
|
293 |
-
padding:
|
294 |
-
border-radius:
|
295 |
-
font-family: monospace;
|
|
|
296 |
cursor: pointer;
|
|
|
|
|
|
|
297 |
}
|
298 |
-
.
|
299 |
-
background-color: #
|
300 |
-
border-
|
301 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
302 |
}
|
303 |
-
|
304 |
-
|
305 |
-
|
|
|
|
|
|
|
|
|
306 |
}
|
307 |
-
|
308 |
-
|
|
|
|
|
|
|
309 |
}
|
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")
|
@@ -345,9 +398,13 @@ def main():
|
|
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
|
|
|
|
|
|
|
351 |
|
352 |
# Create layout with sidebar
|
353 |
col1, col2 = st.columns([3, 2])
|
@@ -386,9 +443,9 @@ texlive-science""", language="text")
|
|
386 |
st.rerun()
|
387 |
|
388 |
with col2:
|
389 |
-
st.subheader("PDF
|
390 |
|
391 |
-
# PDF compilation and
|
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)
|
@@ -396,7 +453,18 @@ texlive-science""", language="text")
|
|
396 |
if pdf_data:
|
397 |
st.session_state.pdf_data = pdf_data
|
398 |
st.success("PDF compiled successfully!")
|
399 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
400 |
st.session_state.compile_clicked = False
|
401 |
else:
|
402 |
st.error("Compilation Error")
|
@@ -407,9 +475,17 @@ texlive-science""", language="text")
|
|
407 |
|
408 |
# Display previous PDF if available
|
409 |
elif 'pdf_data' in st.session_state and st.session_state.pdf_data:
|
410 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
411 |
else:
|
412 |
-
st.info("Compile your LaTeX document to
|
413 |
|
414 |
# LaTeX Reference Sidebar
|
415 |
st.sidebar.title("LaTeX Reference")
|
@@ -429,13 +505,16 @@ texlive-science""", language="text")
|
|
429 |
if filtered_commands:
|
430 |
found = True
|
431 |
with st.sidebar.expander(f"{category} ({len(filtered_commands)} results)"):
|
|
|
432 |
for cmd, desc in filtered_commands.items():
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
|
437 |
-
st.
|
438 |
-
|
|
|
|
|
439 |
|
440 |
# Search packages
|
441 |
for category, packages in latex_packages.items():
|
@@ -445,37 +524,51 @@ texlive-science""", language="text")
|
|
445 |
if filtered_packages:
|
446 |
found = True
|
447 |
with st.sidebar.expander(f"Packages: {category} ({len(filtered_packages)} results)"):
|
|
|
448 |
for pkg, desc in filtered_packages.items():
|
449 |
-
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
st.
|
|
|
|
|
|
|
454 |
|
455 |
if not found:
|
456 |
st.sidebar.info("No matching commands or packages found")
|
457 |
|
458 |
else:
|
459 |
# Display full reference when not searching
|
460 |
-
|
461 |
|
462 |
-
with
|
463 |
for category, commands in latex_commands.items():
|
464 |
-
with st.expander(category):
|
|
|
465 |
for cmd, desc in commands.items():
|
466 |
-
st.
|
467 |
-
|
468 |
-
st.
|
469 |
-
|
|
|
|
|
|
|
|
|
470 |
|
471 |
-
with
|
472 |
for category, packages in latex_packages.items():
|
473 |
with st.expander(category):
|
|
|
474 |
for pkg, desc in packages.items():
|
475 |
-
st.
|
476 |
-
|
477 |
-
st.
|
478 |
-
|
|
|
|
|
|
|
|
|
479 |
|
480 |
if __name__ == "__main__":
|
481 |
main()
|
|
|
5 |
from pathlib import Path
|
6 |
import os
|
7 |
import shutil
|
8 |
+
import io
|
9 |
+
from PIL import Image
|
10 |
+
import fitz # PyMuPDF
|
11 |
|
12 |
# Set page configuration
|
13 |
st.set_page_config(page_title="LaTeX Editor & Compiler", page_icon="📝", layout="wide")
|
|
|
58 |
b64_pdf = base64.b64encode(pdf_data).decode()
|
59 |
return f'<a href="data:application/pdf;base64,{b64_pdf}" download="{filename}" class="download-button">Download PDF</a>'
|
60 |
|
61 |
+
# Convert PDF to image for preview
|
62 |
+
def render_pdf_preview(pdf_data):
|
|
|
63 |
if not pdf_data:
|
64 |
+
return None
|
65 |
|
66 |
+
try:
|
67 |
+
# Create a file-like object from the PDF data
|
68 |
+
pdf_stream = io.BytesIO(pdf_data)
|
69 |
+
|
70 |
+
# Open PDF with PyMuPDF (fitz)
|
71 |
+
pdf_document = fitz.open(stream=pdf_stream, filetype="pdf")
|
72 |
+
|
73 |
+
# Render pages as images
|
74 |
+
images = []
|
75 |
+
for page_num in range(min(3, len(pdf_document))): # Preview first 3 pages max
|
76 |
+
page = pdf_document.load_page(page_num)
|
77 |
+
pix = page.get_pixmap(matrix=fitz.Matrix(2, 2)) # Zoom factor 2 for better resolution
|
78 |
+
img_data = pix.tobytes("png")
|
79 |
+
img = Image.open(io.BytesIO(img_data))
|
80 |
+
images.append(img)
|
81 |
+
|
82 |
+
pdf_document.close()
|
83 |
+
return images
|
84 |
+
except Exception as e:
|
85 |
+
st.error(f"Error rendering PDF preview: {str(e)}")
|
86 |
+
return None
|
87 |
|
88 |
# LaTeX package reference
|
89 |
latex_packages = {
|
|
|
266 |
\end{document}
|
267 |
"""
|
268 |
|
269 |
+
# Add custom CSS with improved sidebar styling
|
270 |
st.markdown("""
|
271 |
<style>
|
272 |
+
/* Editor styling */
|
273 |
.editor-container {
|
274 |
border: 1px solid #ccc;
|
275 |
border-radius: 5px;
|
276 |
padding: 10px;
|
277 |
background-color: #f8f9fa;
|
278 |
}
|
279 |
+
.stTextArea textarea {
|
280 |
+
font-family: 'Courier New', Courier, monospace !important;
|
281 |
+
font-size: 14px !important;
|
282 |
+
line-height: 1.5 !important;
|
283 |
+
}
|
284 |
+
|
285 |
+
/* Download button styling */
|
286 |
.download-button {
|
287 |
display: inline-block;
|
288 |
padding: 0.7em 1.4em;
|
|
|
301 |
background-color: #45a049;
|
302 |
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
|
303 |
}
|
304 |
+
|
305 |
+
/* Sidebar styling */
|
306 |
+
.sidebar .sidebar-content {
|
307 |
+
background-color: #f0f2f6;
|
308 |
}
|
309 |
+
|
310 |
+
/* LaTeX command styling */
|
311 |
.latex-command {
|
312 |
+
background-color: #e9ecef;
|
313 |
+
padding: 4px 8px;
|
314 |
+
border-radius: 4px;
|
315 |
+
font-family: 'Courier New', Courier, monospace;
|
316 |
+
color: #1e1e1e;
|
317 |
cursor: pointer;
|
318 |
+
display: inline-block;
|
319 |
+
margin-bottom: 4px;
|
320 |
+
border: 1px solid #ced4da;
|
321 |
}
|
322 |
+
.latex-command:hover {
|
323 |
+
background-color: #d0d7de;
|
324 |
+
border-color: #adb5bd;
|
325 |
+
}
|
326 |
+
|
327 |
+
/* Command description styling */
|
328 |
+
.command-description {
|
329 |
+
color: #495057;
|
330 |
+
padding-left: 8px;
|
331 |
+
display: inline-block;
|
332 |
+
}
|
333 |
+
|
334 |
+
/* Category title styling */
|
335 |
+
.category-title {
|
336 |
+
font-weight: bold;
|
337 |
+
color: #212529;
|
338 |
+
margin-top: 15px;
|
339 |
+
margin-bottom: 8px;
|
340 |
+
}
|
341 |
+
|
342 |
+
/* Expander styling */
|
343 |
+
.streamlit-expanderHeader {
|
344 |
+
font-weight: bold;
|
345 |
+
color: #212529;
|
346 |
+
background-color: #e9ecef;
|
347 |
+
border-radius: 4px;
|
348 |
+
}
|
349 |
+
|
350 |
+
/* Command list container */
|
351 |
+
.command-list {
|
352 |
+
background-color: #f8f9fa;
|
353 |
+
border-radius: 4px;
|
354 |
+
padding: 8px;
|
355 |
+
border: 1px solid #dee2e6;
|
356 |
+
}
|
357 |
+
|
358 |
+
/* Insert button styling */
|
359 |
+
.insert-button {
|
360 |
+
background-color: #007bff;
|
361 |
+
color: white;
|
362 |
+
border: none;
|
363 |
+
border-radius: 4px;
|
364 |
+
padding: 2px 8px;
|
365 |
+
margin-left: 8px;
|
366 |
+
cursor: pointer;
|
367 |
+
font-size: 12px;
|
368 |
+
}
|
369 |
+
.insert-button:hover {
|
370 |
+
background-color: #0069d9;
|
371 |
}
|
372 |
+
|
373 |
+
/* PDF preview container */
|
374 |
+
.pdf-preview-container {
|
375 |
+
border: 1px solid #dee2e6;
|
376 |
+
border-radius: 5px;
|
377 |
+
padding: 15px;
|
378 |
+
background-color: #f8f9fa;
|
379 |
}
|
380 |
+
|
381 |
+
/* PDF page image */
|
382 |
+
.pdf-page-image {
|
383 |
+
border: 1px solid #dee2e6;
|
384 |
+
margin-bottom: 15px;
|
385 |
}
|
386 |
</style>
|
387 |
""", unsafe_allow_html=True)
|
388 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
389 |
# Main application
|
390 |
def main():
|
391 |
st.title("LaTeX Editor & PDF Compiler")
|
|
|
398 |
# Show packages.txt content suggestion
|
399 |
with st.expander("Required packages.txt content"):
|
400 |
st.code("""texlive
|
401 |
+
texlive-latex-base
|
402 |
texlive-latex-extra
|
403 |
texlive-fonts-recommended
|
404 |
+
texlive-science
|
405 |
+
python3-dev
|
406 |
+
python3-pip
|
407 |
+
poppler-utils""", language="text")
|
408 |
|
409 |
# Create layout with sidebar
|
410 |
col1, col2 = st.columns([3, 2])
|
|
|
443 |
st.rerun()
|
444 |
|
445 |
with col2:
|
446 |
+
st.subheader("PDF Preview")
|
447 |
|
448 |
+
# PDF compilation and preview
|
449 |
if 'compile_clicked' in st.session_state and st.session_state.compile_clicked:
|
450 |
with st.spinner("Compiling LaTeX to PDF..."):
|
451 |
pdf_data, stdout, stderr = latex_to_pdf(latex_code)
|
|
|
453 |
if pdf_data:
|
454 |
st.session_state.pdf_data = pdf_data
|
455 |
st.success("PDF compiled successfully!")
|
456 |
+
|
457 |
+
# Render preview
|
458 |
+
preview_images = render_pdf_preview(pdf_data)
|
459 |
+
if preview_images:
|
460 |
+
st.write("PDF Preview (First Pages):")
|
461 |
+
for i, img in enumerate(preview_images):
|
462 |
+
st.image(img, caption=f"Page {i+1}", use_column_width=True,
|
463 |
+
clamp=True, output_format="PNG")
|
464 |
+
|
465 |
+
# Download button
|
466 |
+
st.markdown(get_download_link(pdf_data), unsafe_allow_html=True)
|
467 |
+
|
468 |
st.session_state.compile_clicked = False
|
469 |
else:
|
470 |
st.error("Compilation Error")
|
|
|
475 |
|
476 |
# Display previous PDF if available
|
477 |
elif 'pdf_data' in st.session_state and st.session_state.pdf_data:
|
478 |
+
preview_images = render_pdf_preview(st.session_state.pdf_data)
|
479 |
+
if preview_images:
|
480 |
+
st.write("PDF Preview (First Pages):")
|
481 |
+
for i, img in enumerate(preview_images):
|
482 |
+
st.image(img, caption=f"Page {i+1}", use_column_width=True,
|
483 |
+
clamp=True, output_format="PNG")
|
484 |
+
|
485 |
+
# Download button
|
486 |
+
st.markdown(get_download_link(st.session_state.pdf_data), unsafe_allow_html=True)
|
487 |
else:
|
488 |
+
st.info("Compile your LaTeX document to see the PDF preview")
|
489 |
|
490 |
# LaTeX Reference Sidebar
|
491 |
st.sidebar.title("LaTeX Reference")
|
|
|
505 |
if filtered_commands:
|
506 |
found = True
|
507 |
with st.sidebar.expander(f"{category} ({len(filtered_commands)} results)"):
|
508 |
+
st.markdown('<div class="command-list">', unsafe_allow_html=True)
|
509 |
for cmd, desc in filtered_commands.items():
|
510 |
+
col1, col2 = st.columns([3, 1])
|
511 |
+
with col1:
|
512 |
+
st.markdown(f'<div><span class="latex-command">{cmd}</span> <span class="command-description">{desc}</span></div>', unsafe_allow_html=True)
|
513 |
+
with col2:
|
514 |
+
if st.button("Insert", key=f"btn_{cmd}", help=f"Insert {cmd} into editor"):
|
515 |
+
st.session_state.latex_code += f"\n{cmd}"
|
516 |
+
st.rerun()
|
517 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
518 |
|
519 |
# Search packages
|
520 |
for category, packages in latex_packages.items():
|
|
|
524 |
if filtered_packages:
|
525 |
found = True
|
526 |
with st.sidebar.expander(f"Packages: {category} ({len(filtered_packages)} results)"):
|
527 |
+
st.markdown('<div class="command-list">', unsafe_allow_html=True)
|
528 |
for pkg, desc in filtered_packages.items():
|
529 |
+
col1, col2 = st.columns([3, 1])
|
530 |
+
with col1:
|
531 |
+
st.markdown(f'<div><span class="latex-command">{pkg}</span> <span class="command-description">{desc}</span></div>', unsafe_allow_html=True)
|
532 |
+
with col2:
|
533 |
+
if st.button("Insert", key=f"btn_{pkg}", help=f"Insert {pkg} into editor"):
|
534 |
+
st.session_state.latex_code += f"\n{pkg}"
|
535 |
+
st.rerun()
|
536 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
537 |
|
538 |
if not found:
|
539 |
st.sidebar.info("No matching commands or packages found")
|
540 |
|
541 |
else:
|
542 |
# Display full reference when not searching
|
543 |
+
tab1, tab2 = st.sidebar.tabs(["Commands", "Packages"])
|
544 |
|
545 |
+
with tab1:
|
546 |
for category, commands in latex_commands.items():
|
547 |
+
with st.expander(category, expanded=category=="Math"):
|
548 |
+
st.markdown('<div class="command-list">', unsafe_allow_html=True)
|
549 |
for cmd, desc in commands.items():
|
550 |
+
col1, col2 = st.columns([3, 1])
|
551 |
+
with col1:
|
552 |
+
st.markdown(f'<div><span class="latex-command">{cmd}</span> <span class="command-description">{desc}</span></div>', unsafe_allow_html=True)
|
553 |
+
with col2:
|
554 |
+
if st.button("Insert", key=f"btn_{cmd}", help=f"Insert {cmd} into editor"):
|
555 |
+
st.session_state.latex_code += f"\n{cmd}"
|
556 |
+
st.rerun()
|
557 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
558 |
|
559 |
+
with tab2:
|
560 |
for category, packages in latex_packages.items():
|
561 |
with st.expander(category):
|
562 |
+
st.markdown('<div class="command-list">', unsafe_allow_html=True)
|
563 |
for pkg, desc in packages.items():
|
564 |
+
col1, col2 = st.columns([3, 1])
|
565 |
+
with col1:
|
566 |
+
st.markdown(f'<div><span class="latex-command">{pkg}</span> <span class="command-description">{desc}</span></div>', unsafe_allow_html=True)
|
567 |
+
with col2:
|
568 |
+
if st.button("Insert", key=f"btn_{pkg}", help=f"Insert {pkg} into editor"):
|
569 |
+
st.session_state.latex_code += f"\n{pkg}"
|
570 |
+
st.rerun()
|
571 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
572 |
|
573 |
if __name__ == "__main__":
|
574 |
main()
|