Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -85,136 +85,6 @@ def render_pdf_preview(pdf_data):
|
|
85 |
st.error(f"Error rendering PDF preview: {str(e)}")
|
86 |
return None
|
87 |
|
88 |
-
# LaTeX package reference
|
89 |
-
latex_packages = {
|
90 |
-
"Document": {
|
91 |
-
"\\usepackage{geometry}": "Page layout customization",
|
92 |
-
"\\usepackage{fancyhdr}": "Custom headers and footers",
|
93 |
-
"\\usepackage{titlesec}": "Title formatting",
|
94 |
-
"\\usepackage{hyperref}": "Hyperlinks and PDF metadata"
|
95 |
-
},
|
96 |
-
"Math": {
|
97 |
-
"\\usepackage{amsmath}": "Enhanced math formatting",
|
98 |
-
"\\usepackage{amssymb}": "Mathematical symbols",
|
99 |
-
"\\usepackage{mathtools}": "Extensions to amsmath",
|
100 |
-
"\\usepackage{physics}": "Physics notation"
|
101 |
-
},
|
102 |
-
"Graphics": {
|
103 |
-
"\\usepackage{graphicx}": "Include images",
|
104 |
-
"\\usepackage{tikz}": "Create vector graphics",
|
105 |
-
"\\usepackage{pgfplots}": "Create plots",
|
106 |
-
"\\usepackage{float}": "Better figure placement"
|
107 |
-
},
|
108 |
-
"Tables": {
|
109 |
-
"\\usepackage{tabularx}": "Flexible tables",
|
110 |
-
"\\usepackage{booktabs}": "Professional tables",
|
111 |
-
"\\usepackage{colortbl}": "Colored tables",
|
112 |
-
"\\usepackage{multirow}": "Multi-row cells"
|
113 |
-
},
|
114 |
-
"Content": {
|
115 |
-
"\\usepackage{listings}": "Code syntax highlighting",
|
116 |
-
"\\usepackage{minted}": "Advanced code highlighting",
|
117 |
-
"\\usepackage{biblatex}": "Bibliography management",
|
118 |
-
"\\usepackage{xcolor}": "Color support"
|
119 |
-
}
|
120 |
-
}
|
121 |
-
|
122 |
-
# LaTeX commands reference
|
123 |
-
latex_commands = {
|
124 |
-
"Document Structure": {
|
125 |
-
"\\documentclass{article}": "Specifies the type of document",
|
126 |
-
"\\begin{document}": "Starts the document content",
|
127 |
-
"\\end{document}": "Ends the document content",
|
128 |
-
"\\title{...}": "Sets the document title",
|
129 |
-
"\\author{...}": "Sets the document author",
|
130 |
-
"\\date{...}": "Sets the document date",
|
131 |
-
"\\maketitle": "Prints the title, author, and date"
|
132 |
-
},
|
133 |
-
"Sections": {
|
134 |
-
"\\section{...}": "Creates a section",
|
135 |
-
"\\subsection{...}": "Creates a subsection",
|
136 |
-
"\\subsubsection{...}": "Creates a subsubsection",
|
137 |
-
"\\paragraph{...}": "Creates a paragraph heading",
|
138 |
-
"\\tableofcontents": "Generates a table of contents"
|
139 |
-
},
|
140 |
-
"Text Formatting": {
|
141 |
-
"\\textbf{...}": "Bold text",
|
142 |
-
"\\textit{...}": "Italic text",
|
143 |
-
"\\underline{...}": "Underlined text",
|
144 |
-
"\\emph{...}": "Emphasized text",
|
145 |
-
"\\texttt{...}": "Typewriter text",
|
146 |
-
"\\textsc{...}": "Small caps text",
|
147 |
-
"\\textsf{...}": "Sans-serif text",
|
148 |
-
"\\color{red}{...}": "Colored text (requires xcolor)"
|
149 |
-
},
|
150 |
-
"Math": {
|
151 |
-
"$...$": "Inline math mode",
|
152 |
-
"$$...$$": "Display math mode",
|
153 |
-
"\\begin{equation}...\\end{equation}": "Numbered equation",
|
154 |
-
"\\begin{align}...\\end{align}": "Aligned equations",
|
155 |
-
"\\frac{num}{denom}": "Fraction",
|
156 |
-
"\\dfrac{num}{denom}": "Display fraction",
|
157 |
-
"\\sqrt{...}": "Square root",
|
158 |
-
"\\sqrt[n]{...}": "nth root",
|
159 |
-
"\\sum_{lower}^{upper}": "Summation",
|
160 |
-
"\\prod_{lower}^{upper}": "Product",
|
161 |
-
"\\int_{lower}^{upper}": "Integral",
|
162 |
-
"\\lim_{x \\to value}": "Limit",
|
163 |
-
"\\vec{...}": "Vector",
|
164 |
-
"\\overline{...}": "Overline",
|
165 |
-
"\\hat{...}": "Hat accent",
|
166 |
-
"\\partial": "Partial derivative"
|
167 |
-
},
|
168 |
-
"Lists": {
|
169 |
-
"\\begin{itemize}...\\end{itemize}": "Bulleted list",
|
170 |
-
"\\begin{enumerate}...\\end{enumerate}": "Numbered list",
|
171 |
-
"\\begin{description}...\\end{description}": "Description list",
|
172 |
-
"\\item": "List item",
|
173 |
-
"\\item[custom]": "Custom label item"
|
174 |
-
},
|
175 |
-
"Tables": {
|
176 |
-
"\\begin{table}...\\end{table}": "Table environment",
|
177 |
-
"\\begin{tabular}{cols}...\\end{tabular}": "Create a table",
|
178 |
-
"\\hline": "Horizontal line in table",
|
179 |
-
"\\cline{i-j}": "Partial horizontal line",
|
180 |
-
"cell1 & cell2 & cell3 \\\\": "Table row",
|
181 |
-
"\\multicolumn{n}{align}{content}": "Span multiple columns"
|
182 |
-
},
|
183 |
-
"Figures": {
|
184 |
-
"\\begin{figure}...\\end{figure}": "Figure environment",
|
185 |
-
"\\includegraphics[width=0.8\\textwidth]{filename}": "Include an image",
|
186 |
-
"\\caption{...}": "Add a caption to a figure or table",
|
187 |
-
"\\label{...}": "Add a label for cross-referencing",
|
188 |
-
"\\ref{...}": "Reference a labeled item"
|
189 |
-
},
|
190 |
-
"Citations": {
|
191 |
-
"\\cite{key}": "Citation",
|
192 |
-
"\\bibliography{file}": "Bibliography source",
|
193 |
-
"\\bibliographystyle{style}": "Bibliography style"
|
194 |
-
}
|
195 |
-
}
|
196 |
-
|
197 |
-
# Flatten commands and shortcuts dictionary for autocomplete
|
198 |
-
flat_commands = {}
|
199 |
-
for category, commands in latex_commands.items():
|
200 |
-
for cmd, desc in commands.items():
|
201 |
-
cmd_parts = cmd.split("{")[0].strip() # Get just the command part
|
202 |
-
flat_commands[cmd_parts] = {"full": cmd, "desc": desc, "category": category}
|
203 |
-
|
204 |
-
# Add common math shortcuts
|
205 |
-
math_shortcuts = {
|
206 |
-
"\\fr": "\\frac{}{}",
|
207 |
-
"\\eq": "\\begin{equation}\n\n\\end{equation}",
|
208 |
-
"\\al": "\\begin{align}\n\n\\end{align}",
|
209 |
-
"\\it": "\\item ",
|
210 |
-
"\\bf": "\\textbf{}",
|
211 |
-
"\\sq": "\\sqrt{}",
|
212 |
-
"\\vec": "\\vec{}",
|
213 |
-
"\\int": "\\int_{}^{}",
|
214 |
-
"\\sum": "\\sum_{}^{}",
|
215 |
-
"\\lim": "\\lim_{\\to }"
|
216 |
-
}
|
217 |
-
|
218 |
# Default LaTeX template
|
219 |
default_template = r"""\documentclass{article}
|
220 |
\usepackage[utf8]{inputenc}
|
@@ -287,252 +157,239 @@ Your conclusion here.
|
|
287 |
\end{document}
|
288 |
"""
|
289 |
|
290 |
-
# Add
|
291 |
st.markdown("""
|
292 |
<style>
|
293 |
-
/*
|
294 |
-
.editor
|
295 |
-
|
296 |
-
border-radius: 5px;
|
297 |
-
padding: 10px;
|
298 |
-
background-color: #f8f9fa;
|
299 |
-
}
|
300 |
-
.stTextArea textarea {
|
301 |
-
font-family: 'Courier New', Courier, monospace !important;
|
302 |
font-size: 14px !important;
|
303 |
line-height: 1.5 !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
304 |
}
|
305 |
|
306 |
/* Download button styling */
|
307 |
.download-button {
|
308 |
display: inline-block;
|
309 |
padding: 0.7em 1.4em;
|
310 |
-
background-color: #
|
311 |
color: white !important;
|
312 |
text-align: center;
|
313 |
text-decoration: none;
|
314 |
-
font-size:
|
315 |
-
border-radius:
|
316 |
transition: background-color 0.3s;
|
317 |
margin-top: 10px;
|
318 |
-
font-weight:
|
319 |
-
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
320 |
}
|
321 |
.download-button:hover {
|
322 |
-
background-color: #
|
323 |
-
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
|
324 |
}
|
325 |
|
326 |
-
/*
|
327 |
-
.
|
328 |
-
background-color: #
|
|
|
|
|
|
|
|
|
|
|
|
|
329 |
}
|
330 |
|
331 |
-
/*
|
332 |
-
.
|
333 |
-
background-color: #
|
334 |
-
|
335 |
-
|
336 |
-
font-family: '
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
}
|
343 |
-
.latex-command:hover {
|
344 |
-
background-color: #d0d7de;
|
345 |
-
border-color: #adb5bd;
|
346 |
}
|
347 |
|
348 |
-
/*
|
349 |
-
.
|
350 |
-
|
351 |
-
|
352 |
-
|
|
|
353 |
}
|
354 |
|
355 |
-
/*
|
356 |
-
.
|
357 |
-
|
358 |
-
color: #
|
359 |
-
|
360 |
-
margin-bottom: 8px;
|
361 |
}
|
362 |
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
background-color: #e9ecef;
|
368 |
-
border-radius: 4px;
|
369 |
}
|
370 |
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
border
|
375 |
-
padding: 8px;
|
376 |
-
border: 1px solid #dee2e6;
|
377 |
}
|
378 |
|
379 |
-
/*
|
380 |
-
|
381 |
-
|
382 |
-
|
383 |
-
|
384 |
-
|
385 |
-
|
386 |
-
margin-left: 8px;
|
387 |
-
cursor: pointer;
|
388 |
-
font-size: 12px;
|
389 |
-
}
|
390 |
-
.insert-button:hover {
|
391 |
-
background-color: #0069d9;
|
392 |
}
|
393 |
|
394 |
-
|
395 |
-
|
396 |
-
background-color: #f8f9fa;
|
397 |
-
border: 1px solid #dee2e6;
|
398 |
-
border-radius: 4px;
|
399 |
-
padding: 10px;
|
400 |
-
margin-top: 5px;
|
401 |
}
|
402 |
|
403 |
-
|
404 |
-
|
405 |
-
background-color: #e9ecef;
|
406 |
-
padding: 2px 6px;
|
407 |
-
border-radius: 3px;
|
408 |
-
font-family: monospace;
|
409 |
-
margin-right: 10px;
|
410 |
}
|
411 |
|
412 |
-
|
413 |
-
|
414 |
-
font-style: italic;
|
415 |
-
color: #6c757d;
|
416 |
-
font-size: 0.85em;
|
417 |
}
|
418 |
</style>
|
419 |
""", unsafe_allow_html=True)
|
420 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
421 |
# Main application
|
422 |
def main():
|
423 |
-
|
|
|
424 |
|
425 |
# Initialize session state
|
426 |
if 'latex_code' not in st.session_state:
|
427 |
st.session_state.latex_code = default_template
|
428 |
if 'show_preview' not in st.session_state:
|
429 |
st.session_state.show_preview = False
|
430 |
-
if 'last_word' not in st.session_state:
|
431 |
-
st.session_state.last_word = ""
|
432 |
|
433 |
# Display installation status
|
434 |
if not is_pdflatex_installed():
|
435 |
st.warning("⚠️ LaTeX is not installed correctly. The PDF compilation feature will not work.")
|
436 |
-
st.info("For Hugging Face Spaces, make sure you have a packages.txt file with the necessary LaTeX packages.")
|
437 |
|
438 |
-
# Create layout
|
439 |
col1, col2 = st.columns([3, 2])
|
440 |
|
441 |
with col1:
|
442 |
-
|
443 |
-
|
444 |
-
|
445 |
-
|
446 |
-
|
447 |
-
# Detect what's being typed for autocompletion
|
448 |
-
current_text = st.session_state.latex_code
|
449 |
-
if '\\' in current_text:
|
450 |
-
# Find the last command being typed
|
451 |
-
lines = current_text.split('\n')
|
452 |
-
for line in reversed(lines):
|
453 |
-
if '\\' in line:
|
454 |
-
last_slash_pos = line.rfind('\\')
|
455 |
-
command_start = line[last_slash_pos:]
|
456 |
-
# Extract the command without arguments
|
457 |
-
command_parts = command_start.split('{')[0].split(' ')[0].strip()
|
458 |
-
if command_parts and command_parts != st.session_state.last_word:
|
459 |
-
st.session_state.last_word = command_parts
|
460 |
-
|
461 |
-
# Show hints for matching commands
|
462 |
-
matching_commands = []
|
463 |
-
|
464 |
-
# Check for shortcut matches
|
465 |
-
shortcut_matches = []
|
466 |
-
for shortcut, expansion in math_shortcuts.items():
|
467 |
-
if shortcut.startswith(command_parts):
|
468 |
-
shortcut_matches.append((shortcut, expansion))
|
469 |
-
|
470 |
-
# Check for command matches
|
471 |
-
command_matches = []
|
472 |
-
for cmd, info in flat_commands.items():
|
473 |
-
if cmd.startswith(command_parts):
|
474 |
-
command_matches.append((cmd, info))
|
475 |
-
|
476 |
-
# Combine shortcuts and commands
|
477 |
-
matches = shortcut_matches + command_matches[:5] # Limit to top 5 commands
|
478 |
-
|
479 |
-
if matches:
|
480 |
-
hint_html = '<div class="hint-box"><p><strong>Suggestions:</strong></p>'
|
481 |
-
for match, info in matches:
|
482 |
-
if isinstance(info, dict):
|
483 |
-
hint_html += f'<div><span class="shortcut-span">{match}</span> {info["desc"]}</div>'
|
484 |
-
else:
|
485 |
-
hint_html += f'<div><span class="shortcut-span">{match}</span> expands to <code>{info}</code></div>'
|
486 |
-
hint_html += '<p class="shortcut-tip">Use the sidebar to insert full commands</p></div>'
|
487 |
-
hint_container.markdown(hint_html, unsafe_allow_html=True)
|
488 |
-
break
|
489 |
-
|
490 |
-
# LaTeX editor
|
491 |
-
latex_code = st.text_area(
|
492 |
-
"Edit your LaTeX document:",
|
493 |
-
value=st.session_state.latex_code,
|
494 |
-
height=500,
|
495 |
-
key="latex_editor"
|
496 |
-
)
|
497 |
st.session_state.latex_code = latex_code
|
498 |
|
499 |
-
# Control buttons
|
500 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
501 |
|
502 |
-
|
503 |
-
|
504 |
-
|
505 |
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
st.rerun()
|
510 |
|
511 |
-
|
512 |
-
|
513 |
-
|
514 |
-
st.rerun()
|
515 |
-
|
516 |
-
# Display available shortcuts
|
517 |
-
with st.expander("LaTeX Shortcuts"):
|
518 |
-
st.markdown("### Quick Shortcuts")
|
519 |
-
for shortcut, expansion in math_shortcuts.items():
|
520 |
-
st.markdown(f"<span class='shortcut-span'>{shortcut}</span> → <code>{expansion}</code>", unsafe_allow_html=True)
|
521 |
|
522 |
with col2:
|
523 |
-
st.
|
524 |
|
525 |
# PDF compilation
|
526 |
if 'compile_clicked' in st.session_state and st.session_state.compile_clicked:
|
527 |
-
with st.spinner("Compiling
|
528 |
pdf_data, stdout, stderr = latex_to_pdf(latex_code)
|
529 |
|
530 |
if pdf_data:
|
531 |
st.session_state.pdf_data = pdf_data
|
532 |
-
st.success("
|
533 |
|
534 |
# Toggle button for preview
|
535 |
-
if st.button("
|
536 |
st.session_state.show_preview = not st.session_state.show_preview
|
537 |
|
538 |
# Download button always available
|
@@ -540,25 +397,32 @@ def main():
|
|
540 |
|
541 |
# Optional preview
|
542 |
if st.session_state.show_preview:
|
|
|
543 |
preview_images = render_pdf_preview(pdf_data)
|
544 |
if preview_images:
|
545 |
-
st.write("PDF Preview (First Pages):")
|
546 |
for i, img in enumerate(preview_images):
|
547 |
st.image(img, caption=f"Page {i+1}", use_container_width=True,
|
548 |
output_format="PNG")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
549 |
|
550 |
st.session_state.compile_clicked = False
|
551 |
else:
|
552 |
-
st.error("Compilation
|
553 |
-
|
554 |
-
|
555 |
-
|
556 |
st.session_state.compile_clicked = False
|
557 |
|
558 |
# Display previous PDF if available
|
559 |
elif 'pdf_data' in st.session_state and st.session_state.pdf_data:
|
560 |
# Toggle button for preview
|
561 |
-
if st.button("
|
562 |
st.session_state.show_preview = not st.session_state.show_preview
|
563 |
|
564 |
# Download button always available
|
@@ -566,76 +430,15 @@ def main():
|
|
566 |
|
567 |
# Optional preview
|
568 |
if st.session_state.show_preview:
|
|
|
569 |
preview_images = render_pdf_preview(st.session_state.pdf_data)
|
570 |
if preview_images:
|
571 |
-
st.write("PDF Preview (First Pages):")
|
572 |
for i, img in enumerate(preview_images):
|
573 |
st.image(img, caption=f"Page {i+1}", use_container_width=True,
|
574 |
output_format="PNG")
|
575 |
-
else:
|
576 |
-
st.info("Compile your LaTeX document to generate a PDF for download")
|
577 |
-
|
578 |
-
# LaTeX Reference Sidebar
|
579 |
-
st.sidebar.title("LaTeX Reference")
|
580 |
-
|
581 |
-
# Command search
|
582 |
-
quick_search = st.sidebar.text_input("Find LaTeX Commands", "")
|
583 |
-
|
584 |
-
if quick_search:
|
585 |
-
# Find and display matching commands
|
586 |
-
matching_cmds = []
|
587 |
-
for category, commands in latex_commands.items():
|
588 |
-
for cmd, desc in commands.items():
|
589 |
-
cmd_without_backslash = cmd.replace("\\", "").lower()
|
590 |
-
if quick_search.lower() in cmd_without_backslash:
|
591 |
-
matching_cmds.append((cmd, desc, category))
|
592 |
-
|
593 |
-
if matching_cmds:
|
594 |
-
st.sidebar.markdown("### Matching Commands")
|
595 |
-
for cmd, desc, category in matching_cmds[:15]: # Limit to 15 results
|
596 |
-
col1, col2 = st.sidebar.columns([4, 1])
|
597 |
-
with col1:
|
598 |
-
st.markdown(f"<div><span class='latex-command'>{cmd}</span> <small>{category}</small></div>", unsafe_allow_html=True)
|
599 |
-
with col2:
|
600 |
-
if st.button("Insert", key=f"quick_{cmd}"):
|
601 |
-
# Update LaTeX code with the inserted command
|
602 |
-
st.session_state.latex_code += f"\n{cmd}"
|
603 |
-
st.rerun()
|
604 |
-
else:
|
605 |
-
st.sidebar.info("No matching commands found")
|
606 |
-
|
607 |
-
# Regular categories
|
608 |
-
tab1, tab2 = st.sidebar.tabs(["Commands", "Packages"])
|
609 |
-
|
610 |
-
with tab1:
|
611 |
-
for category, commands in latex_commands.items():
|
612 |
-
with st.expander(category, expanded=category=="Math"):
|
613 |
-
st.markdown('<div class="command-list">', unsafe_allow_html=True)
|
614 |
-
for cmd, desc in commands.items():
|
615 |
-
col1, col2 = st.sidebar.columns([4, 1])
|
616 |
-
with col1:
|
617 |
-
st.markdown(f"<div><span class='latex-command'>{cmd}</span></div>", unsafe_allow_html=True)
|
618 |
-
with col2:
|
619 |
-
if st.button("Insert", key=f"btn_{cmd}"):
|
620 |
-
# Update LaTeX code with the inserted command
|
621 |
-
st.session_state.latex_code += f"\n{cmd}"
|
622 |
-
st.rerun()
|
623 |
-
st.markdown('</div>', unsafe_allow_html=True)
|
624 |
-
|
625 |
-
with tab2:
|
626 |
-
for category, packages in latex_packages.items():
|
627 |
-
with st.expander(category):
|
628 |
-
st.markdown('<div class="command-list">', unsafe_allow_html=True)
|
629 |
-
for pkg, desc in packages.items():
|
630 |
-
col1, col2 = st.sidebar.columns([4, 1])
|
631 |
-
with col1:
|
632 |
-
st.markdown(f"<div><span class='latex-command'>{pkg}</span></div>", unsafe_allow_html=True)
|
633 |
-
with col2:
|
634 |
-
if st.button("Insert", key=f"btn_{pkg}"):
|
635 |
-
# Update LaTeX code with the inserted command
|
636 |
-
st.session_state.latex_code += f"\n{pkg}"
|
637 |
-
st.rerun()
|
638 |
st.markdown('</div>', unsafe_allow_html=True)
|
|
|
|
|
639 |
|
640 |
if __name__ == "__main__":
|
641 |
main()
|
|
|
85 |
st.error(f"Error rendering PDF preview: {str(e)}")
|
86 |
return None
|
87 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
88 |
# Default LaTeX template
|
89 |
default_template = r"""\documentclass{article}
|
90 |
\usepackage[utf8]{inputenc}
|
|
|
157 |
\end{document}
|
158 |
"""
|
159 |
|
160 |
+
# Add VS Code-like styling
|
161 |
st.markdown("""
|
162 |
<style>
|
163 |
+
/* VS Code-like styling */
|
164 |
+
.vscode-editor textarea {
|
165 |
+
font-family: 'Consolas', 'Monaco', 'Courier New', monospace !important;
|
|
|
|
|
|
|
|
|
|
|
|
|
166 |
font-size: 14px !important;
|
167 |
line-height: 1.5 !important;
|
168 |
+
background-color: #1e1e1e !important;
|
169 |
+
color: #d4d4d4 !important;
|
170 |
+
padding: 10px !important;
|
171 |
+
border-radius: 4px !important;
|
172 |
+
border: 1px solid #252526 !important;
|
173 |
+
}
|
174 |
+
|
175 |
+
/* Editor container styling */
|
176 |
+
.vscode-container {
|
177 |
+
background-color: #1e1e1e;
|
178 |
+
border-radius: 6px;
|
179 |
+
padding: 8px;
|
180 |
+
border: 1px solid #333;
|
181 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
182 |
+
}
|
183 |
+
|
184 |
+
/* Make scrollbars VS Code style */
|
185 |
+
.vscode-editor textarea::-webkit-scrollbar {
|
186 |
+
width: 14px;
|
187 |
+
height: 14px;
|
188 |
+
}
|
189 |
+
|
190 |
+
.vscode-editor textarea::-webkit-scrollbar-thumb {
|
191 |
+
background-color: #424242;
|
192 |
+
border-radius: 7px;
|
193 |
+
border: 3px solid #1e1e1e;
|
194 |
+
}
|
195 |
+
|
196 |
+
.vscode-editor textarea::-webkit-scrollbar-track {
|
197 |
+
background-color: #1e1e1e;
|
198 |
+
}
|
199 |
+
|
200 |
+
/* Button styling */
|
201 |
+
.vscode-button {
|
202 |
+
background-color: #0e639c;
|
203 |
+
color: white;
|
204 |
+
border: none;
|
205 |
+
padding: 8px 12px;
|
206 |
+
border-radius: 2px;
|
207 |
+
cursor: pointer;
|
208 |
+
font-size: 13px;
|
209 |
+
margin-right: 10px;
|
210 |
+
margin-top: 10px;
|
211 |
+
transition: background-color 0.2s;
|
212 |
+
}
|
213 |
+
|
214 |
+
.vscode-button:hover {
|
215 |
+
background-color: #1177bb;
|
216 |
}
|
217 |
|
218 |
/* Download button styling */
|
219 |
.download-button {
|
220 |
display: inline-block;
|
221 |
padding: 0.7em 1.4em;
|
222 |
+
background-color: #3d995e;
|
223 |
color: white !important;
|
224 |
text-align: center;
|
225 |
text-decoration: none;
|
226 |
+
font-size: 14px;
|
227 |
+
border-radius: 2px;
|
228 |
transition: background-color 0.3s;
|
229 |
margin-top: 10px;
|
230 |
+
font-weight: normal;
|
|
|
231 |
}
|
232 |
.download-button:hover {
|
233 |
+
background-color: #4eb772;
|
|
|
234 |
}
|
235 |
|
236 |
+
/* Status bar styling */
|
237 |
+
.status-bar {
|
238 |
+
background-color: #007acc;
|
239 |
+
color: white;
|
240 |
+
padding: 2px 8px;
|
241 |
+
font-size: 12px;
|
242 |
+
border-radius: 2px 2px 0 0;
|
243 |
+
display: flex;
|
244 |
+
justify-content: space-between;
|
245 |
}
|
246 |
|
247 |
+
/* Terminal/output styling */
|
248 |
+
.terminal-output {
|
249 |
+
background-color: #1e1e1e;
|
250 |
+
color: #cccccc;
|
251 |
+
padding: 10px;
|
252 |
+
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
253 |
+
font-size: 12px;
|
254 |
+
border-radius: 0 0 4px 4px;
|
255 |
+
border-top: 1px solid #333;
|
256 |
+
max-height: 200px;
|
257 |
+
overflow-y: auto;
|
|
|
|
|
|
|
|
|
258 |
}
|
259 |
|
260 |
+
/* PDF preview container */
|
261 |
+
.pdf-preview-container {
|
262 |
+
border: 1px solid #333;
|
263 |
+
border-radius: 4px;
|
264 |
+
padding: 15px;
|
265 |
+
background-color: #252526;
|
266 |
}
|
267 |
|
268 |
+
/* Info and error messages */
|
269 |
+
.stInfo {
|
270 |
+
background-color: #063b49;
|
271 |
+
color: #bbbbbb;
|
272 |
+
border: 1px solid #145b6c;
|
|
|
273 |
}
|
274 |
|
275 |
+
.stError {
|
276 |
+
background-color: #5a1d1d;
|
277 |
+
color: #bbbbbb;
|
278 |
+
border: 1px solid #6c2b2b;
|
|
|
|
|
279 |
}
|
280 |
|
281 |
+
.stSuccess {
|
282 |
+
background-color: #143d27;
|
283 |
+
color: #bbbbbb;
|
284 |
+
border: 1px solid #1e5a3a;
|
|
|
|
|
285 |
}
|
286 |
|
287 |
+
/* Hide Streamlit elements */
|
288 |
+
#MainMenu {visibility: hidden;}
|
289 |
+
footer {visibility: hidden;}
|
290 |
+
|
291 |
+
/* Customize the rest of Streamlit UI */
|
292 |
+
.stApp {
|
293 |
+
background-color: #252526;
|
|
|
|
|
|
|
|
|
|
|
|
|
294 |
}
|
295 |
|
296 |
+
h1, h2, h3, h4, h5, h6, p, div {
|
297 |
+
color: #cccccc;
|
|
|
|
|
|
|
|
|
|
|
298 |
}
|
299 |
|
300 |
+
.stTabs [data-baseweb="tab-list"] {
|
301 |
+
background-color: #2d2d2d;
|
|
|
|
|
|
|
|
|
|
|
302 |
}
|
303 |
|
304 |
+
.stTabs [data-baseweb="tab"] {
|
305 |
+
color: #cccccc;
|
|
|
|
|
|
|
306 |
}
|
307 |
</style>
|
308 |
""", unsafe_allow_html=True)
|
309 |
|
310 |
+
# Function to create a VS Code-like editor
|
311 |
+
def vs_code_editor(key, height=500):
|
312 |
+
editor_html = f"""
|
313 |
+
<div class="vscode-container">
|
314 |
+
<div class="status-bar">
|
315 |
+
<span>document.tex - LaTeX</span>
|
316 |
+
<span>UTF-8</span>
|
317 |
+
</div>
|
318 |
+
</div>
|
319 |
+
"""
|
320 |
+
st.markdown(editor_html, unsafe_allow_html=True)
|
321 |
+
|
322 |
+
# Create the actual editor with VS Code styling
|
323 |
+
return st.text_area("", value=st.session_state.get(key, ""),
|
324 |
+
height=height, key=key, label_visibility="collapsed",
|
325 |
+
help="Type your LaTeX code here")
|
326 |
+
|
327 |
# Main application
|
328 |
def main():
|
329 |
+
# Set up a clean, dark theme
|
330 |
+
st.markdown("<h1 style='color: #cccccc; margin-bottom: 20px;'>LaTeX Editor</h1>", unsafe_allow_html=True)
|
331 |
|
332 |
# Initialize session state
|
333 |
if 'latex_code' not in st.session_state:
|
334 |
st.session_state.latex_code = default_template
|
335 |
if 'show_preview' not in st.session_state:
|
336 |
st.session_state.show_preview = False
|
|
|
|
|
337 |
|
338 |
# Display installation status
|
339 |
if not is_pdflatex_installed():
|
340 |
st.warning("⚠️ LaTeX is not installed correctly. The PDF compilation feature will not work.")
|
|
|
341 |
|
342 |
+
# Create layout - full width editor
|
343 |
col1, col2 = st.columns([3, 2])
|
344 |
|
345 |
with col1:
|
346 |
+
# VS Code-like editor with custom class for styling
|
347 |
+
st.markdown('<div class="vscode-editor">', unsafe_allow_html=True)
|
348 |
+
latex_code = vs_code_editor("latex_editor", height=500)
|
349 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
350 |
st.session_state.latex_code = latex_code
|
351 |
|
352 |
+
# Control buttons with VS Code styling
|
353 |
+
st.markdown("""
|
354 |
+
<div style="display: flex; gap: 10px;">
|
355 |
+
<button class="vscode-button" onclick="document.querySelector('[data-testid=\\\"stFormSubmitButton\\\"]').click()">
|
356 |
+
Compile PDF
|
357 |
+
</button>
|
358 |
+
<button class="vscode-button" onclick="document.querySelector('[key=\\\"load_template\\\"]').click()">
|
359 |
+
Load Template
|
360 |
+
</button>
|
361 |
+
<button class="vscode-button" onclick="document.querySelector('[key=\\\"clear_editor\\\"]').click()">
|
362 |
+
Clear Editor
|
363 |
+
</button>
|
364 |
+
</div>
|
365 |
+
""", unsafe_allow_html=True)
|
366 |
|
367 |
+
# Hidden buttons to handle the clicks
|
368 |
+
if st.button("Compile PDF", key="compile", help="Compile LaTeX to PDF"):
|
369 |
+
st.session_state.compile_clicked = True
|
370 |
|
371 |
+
if st.button("Load Template", key="load_template", help="Load default template"):
|
372 |
+
st.session_state.latex_code = default_template
|
373 |
+
st.rerun()
|
|
|
374 |
|
375 |
+
if st.button("Clear Editor", key="clear_editor", help="Clear editor content"):
|
376 |
+
st.session_state.latex_code = ""
|
377 |
+
st.rerun()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
378 |
|
379 |
with col2:
|
380 |
+
st.markdown("<h3 style='color: #cccccc; margin-bottom: 10px;'>Output</h3>", unsafe_allow_html=True)
|
381 |
|
382 |
# PDF compilation
|
383 |
if 'compile_clicked' in st.session_state and st.session_state.compile_clicked:
|
384 |
+
with st.spinner("Compiling..."):
|
385 |
pdf_data, stdout, stderr = latex_to_pdf(latex_code)
|
386 |
|
387 |
if pdf_data:
|
388 |
st.session_state.pdf_data = pdf_data
|
389 |
+
st.success("Compilation successful")
|
390 |
|
391 |
# Toggle button for preview
|
392 |
+
if st.button("Toggle Preview", help="Show or hide the PDF preview"):
|
393 |
st.session_state.show_preview = not st.session_state.show_preview
|
394 |
|
395 |
# Download button always available
|
|
|
397 |
|
398 |
# Optional preview
|
399 |
if st.session_state.show_preview:
|
400 |
+
st.markdown('<div class="pdf-preview-container">', unsafe_allow_html=True)
|
401 |
preview_images = render_pdf_preview(pdf_data)
|
402 |
if preview_images:
|
|
|
403 |
for i, img in enumerate(preview_images):
|
404 |
st.image(img, caption=f"Page {i+1}", use_container_width=True,
|
405 |
output_format="PNG")
|
406 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
407 |
+
|
408 |
+
# Terminal output in collapsible section
|
409 |
+
with st.expander("Terminal Output"):
|
410 |
+
st.markdown('<div class="terminal-output">', unsafe_allow_html=True)
|
411 |
+
st.text(stdout)
|
412 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
413 |
|
414 |
st.session_state.compile_clicked = False
|
415 |
else:
|
416 |
+
st.error("Compilation failed")
|
417 |
+
st.markdown('<div class="terminal-output">', unsafe_allow_html=True)
|
418 |
+
st.text(stderr)
|
419 |
+
st.markdown('</div>', unsafe_allow_html=True)
|
420 |
st.session_state.compile_clicked = False
|
421 |
|
422 |
# Display previous PDF if available
|
423 |
elif 'pdf_data' in st.session_state and st.session_state.pdf_data:
|
424 |
# Toggle button for preview
|
425 |
+
if st.button("Toggle Preview", help="Show or hide the PDF preview"):
|
426 |
st.session_state.show_preview = not st.session_state.show_preview
|
427 |
|
428 |
# Download button always available
|
|
|
430 |
|
431 |
# Optional preview
|
432 |
if st.session_state.show_preview:
|
433 |
+
st.markdown('<div class="pdf-preview-container">', unsafe_allow_html=True)
|
434 |
preview_images = render_pdf_preview(st.session_state.pdf_data)
|
435 |
if preview_images:
|
|
|
436 |
for i, img in enumerate(preview_images):
|
437 |
st.image(img, caption=f"Page {i+1}", use_container_width=True,
|
438 |
output_format="PNG")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
439 |
st.markdown('</div>', unsafe_allow_html=True)
|
440 |
+
else:
|
441 |
+
st.info("Click 'Compile PDF' to generate output")
|
442 |
|
443 |
if __name__ == "__main__":
|
444 |
main()
|