Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -20,25 +20,30 @@ except ImportError:
|
|
| 20 |
|
| 21 |
# Set page configuration
|
| 22 |
st.set_page_config(
|
| 23 |
-
page_title="Professional LaTeX Editor",
|
| 24 |
page_icon="📝",
|
| 25 |
layout="wide",
|
| 26 |
initial_sidebar_state="collapsed"
|
| 27 |
)
|
| 28 |
|
| 29 |
-
# Check if
|
| 30 |
-
def
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
-
# Function to convert LaTeX to PDF
|
| 34 |
-
def latex_to_pdf(latex_code):
|
| 35 |
-
# Check if
|
| 36 |
-
if not
|
| 37 |
-
st.error("
|
| 38 |
st.code(f"PATH: {os.environ.get('PATH')}")
|
| 39 |
-
result = subprocess.run(["which",
|
| 40 |
-
st.code(f"which
|
| 41 |
-
return None, "", "Error:
|
| 42 |
|
| 43 |
with tempfile.TemporaryDirectory() as temp_dir:
|
| 44 |
temp_path = Path(temp_dir)
|
|
@@ -46,16 +51,20 @@ def latex_to_pdf(latex_code):
|
|
| 46 |
pdf_file = temp_path / "document.pdf"
|
| 47 |
|
| 48 |
# Write LaTeX code to file
|
| 49 |
-
with open(tex_file, "w") as f:
|
| 50 |
f.write(latex_code)
|
| 51 |
|
| 52 |
try:
|
| 53 |
-
#
|
| 54 |
-
|
| 55 |
-
["
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
# Check if PDF was created
|
| 61 |
if pdf_file.exists():
|
|
@@ -198,8 +207,116 @@ def find_environments(latex_code):
|
|
| 198 |
|
| 199 |
return environments
|
| 200 |
|
| 201 |
-
# Default LaTeX template
|
| 202 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
\usepackage[utf8]{inputenc}
|
| 204 |
\usepackage{amsmath}
|
| 205 |
\usepackage{amssymb}
|
|
@@ -265,27 +382,9 @@ An integral example:
|
|
| 265 |
\label{tab:simple}
|
| 266 |
\end{table}
|
| 267 |
|
| 268 |
-
\section{Figures}
|
| 269 |
-
You can include figures using the following syntax:
|
| 270 |
-
|
| 271 |
-
% \begin{figure}[h]
|
| 272 |
-
% \centering
|
| 273 |
-
% \includegraphics[width=0.7\textwidth]{example-image}
|
| 274 |
-
% \caption{Example figure}
|
| 275 |
-
% \label{fig:example}
|
| 276 |
-
% \end{figure}
|
| 277 |
-
|
| 278 |
-
\section{Citations}
|
| 279 |
-
You can cite references using the \verb|\cite{}| command \cite{example}.
|
| 280 |
-
|
| 281 |
-
\section{Cross-References}
|
| 282 |
-
You can reference sections, figures, and tables using the \verb|\ref{}| command.
|
| 283 |
-
For example, see Table~\ref{tab:simple}.
|
| 284 |
-
|
| 285 |
\section{Conclusion}
|
| 286 |
Your conclusion here.
|
| 287 |
|
| 288 |
-
% Sample bibliography entry
|
| 289 |
\begin{thebibliography}{9}
|
| 290 |
\bibitem{example} Author, A. (2023). \textit{Title of the Work}. Publisher.
|
| 291 |
\end{thebibliography}
|
|
@@ -293,7 +392,7 @@ Your conclusion here.
|
|
| 293 |
\end{document}
|
| 294 |
"""
|
| 295 |
|
| 296 |
-
# Enhanced VSCode-like styling
|
| 297 |
st.markdown("""
|
| 298 |
<style>
|
| 299 |
/* Base theming - VS Code inspired */
|
|
@@ -389,7 +488,36 @@ st.markdown("""
|
|
| 389 |
color: #cca700;
|
| 390 |
}
|
| 391 |
|
| 392 |
-
/*
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 393 |
.outline-view {
|
| 394 |
background-color: #252526;
|
| 395 |
border: 1px solid #2d2d2d;
|
|
@@ -414,7 +542,6 @@ st.markdown("""
|
|
| 414 |
margin-left: 5px;
|
| 415 |
}
|
| 416 |
|
| 417 |
-
/* Download button styling */
|
| 418 |
.download-button {
|
| 419 |
display: inline-block;
|
| 420 |
padding: 8px 16px;
|
|
@@ -432,7 +559,6 @@ st.markdown("""
|
|
| 432 |
background-color: #4eb772;
|
| 433 |
}
|
| 434 |
|
| 435 |
-
/* PDF preview area */
|
| 436 |
.preview-container {
|
| 437 |
background-color: #252526;
|
| 438 |
border: 1px solid #2d2d2d;
|
|
@@ -441,7 +567,6 @@ st.markdown("""
|
|
| 441 |
margin-top: 10px;
|
| 442 |
}
|
| 443 |
|
| 444 |
-
/* Control panel */
|
| 445 |
.control-panel {
|
| 446 |
background-color: #252526;
|
| 447 |
border: 1px solid #2d2d2d;
|
|
@@ -455,7 +580,6 @@ st.markdown("""
|
|
| 455 |
gap: 5px;
|
| 456 |
}
|
| 457 |
|
| 458 |
-
/* Messages */
|
| 459 |
.stInfo {
|
| 460 |
background-color: #063b49;
|
| 461 |
color: #bbbbbb;
|
|
@@ -474,11 +598,9 @@ st.markdown("""
|
|
| 474 |
border: 1px solid #1e5a3a;
|
| 475 |
}
|
| 476 |
|
| 477 |
-
/* Hide Streamlit footer and menu */
|
| 478 |
footer, header {display: none !important;}
|
| 479 |
#MainMenu {visibility: hidden;}
|
| 480 |
|
| 481 |
-
/* Custom tabs */
|
| 482 |
.stTabs [data-baseweb="tab-list"] {
|
| 483 |
background-color: #2d2d2d;
|
| 484 |
gap: 0px !important;
|
|
@@ -499,7 +621,6 @@ st.markdown("""
|
|
| 499 |
color: white;
|
| 500 |
}
|
| 501 |
|
| 502 |
-
/* Problems panel */
|
| 503 |
.problems-panel {
|
| 504 |
background-color: #252526;
|
| 505 |
border: 1px solid #2d2d2d;
|
|
@@ -537,7 +658,6 @@ st.markdown("""
|
|
| 537 |
margin-top: 2px;
|
| 538 |
}
|
| 539 |
|
| 540 |
-
/* Document info panel */
|
| 541 |
.document-info {
|
| 542 |
background-color: #252526;
|
| 543 |
border: 1px solid #2d2d2d;
|
|
@@ -564,7 +684,6 @@ st.markdown("""
|
|
| 564 |
text-align: right;
|
| 565 |
}
|
| 566 |
|
| 567 |
-
/* Packages panel */
|
| 568 |
.packages-panel {
|
| 569 |
background-color: #252526;
|
| 570 |
border: 1px solid #2d2d2d;
|
|
@@ -593,7 +712,6 @@ st.markdown("""
|
|
| 593 |
font-family: monospace;
|
| 594 |
}
|
| 595 |
|
| 596 |
-
/* Environment analyzer */
|
| 597 |
.environments-panel {
|
| 598 |
background-color: #252526;
|
| 599 |
border: 1px solid #2d2d2d;
|
|
@@ -620,7 +738,6 @@ st.markdown("""
|
|
| 620 |
font-weight: bold;
|
| 621 |
}
|
| 622 |
|
| 623 |
-
/* Simple keyboard shortcut */
|
| 624 |
.shortcut-item {
|
| 625 |
background-color: #252526;
|
| 626 |
border: 1px solid #2d2d2d;
|
|
@@ -641,7 +758,6 @@ st.markdown("""
|
|
| 641 |
margin-right: 10px;
|
| 642 |
}
|
| 643 |
|
| 644 |
-
/* Monaco editor container */
|
| 645 |
.monaco-editor-container {
|
| 646 |
border: 1px solid #2d2d2d;
|
| 647 |
border-radius: 4px;
|
|
@@ -649,7 +765,6 @@ st.markdown("""
|
|
| 649 |
margin-bottom: 10px;
|
| 650 |
}
|
| 651 |
|
| 652 |
-
/* Make sure Monaco editor has correct background */
|
| 653 |
.monaco-editor, .monaco-editor-background, .monaco-editor .margin {
|
| 654 |
background-color: #1e1e1e !important;
|
| 655 |
}
|
|
@@ -667,17 +782,30 @@ def render_editor_tabs(active_tab="document.tex"):
|
|
| 667 |
"""
|
| 668 |
st.markdown(tab_html, unsafe_allow_html=True)
|
| 669 |
|
| 670 |
-
# VS Code-style status bar
|
| 671 |
-
def render_status_bar():
|
| 672 |
-
status_html = """
|
| 673 |
<div class="status-bar">
|
| 674 |
-
<span>
|
| 675 |
<span>Line: 1, Col: 1</span>
|
| 676 |
<span>UTF-8</span>
|
| 677 |
</div>
|
| 678 |
"""
|
| 679 |
st.markdown(status_html, unsafe_allow_html=True)
|
| 680 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 681 |
# Render a document outline based on section hierarchy
|
| 682 |
def render_document_outline(structure):
|
| 683 |
if not structure:
|
|
@@ -742,7 +870,7 @@ def render_problems(errors, warnings):
|
|
| 742 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 743 |
|
| 744 |
# Render document information panel
|
| 745 |
-
def render_document_info(latex_code):
|
| 746 |
# Ensure latex_code is a string
|
| 747 |
if not isinstance(latex_code, str):
|
| 748 |
try:
|
|
@@ -776,14 +904,19 @@ def render_document_info(latex_code):
|
|
| 776 |
ref_count = len(re.findall(r'\\ref\{', latex_code))
|
| 777 |
cite_count = len(re.findall(r'\\cite\{', latex_code))
|
| 778 |
|
|
|
|
|
|
|
|
|
|
| 779 |
# Render the info panel
|
| 780 |
st.markdown('<div class="document-info">', unsafe_allow_html=True)
|
| 781 |
|
| 782 |
info_rows = [
|
|
|
|
| 783 |
("Document Class", doc_class),
|
| 784 |
("Word Count", word_count),
|
| 785 |
("Character Count", char_count),
|
| 786 |
("Line Count", line_count),
|
|
|
|
| 787 |
("Equations", equation_count),
|
| 788 |
("Align Environments", align_count),
|
| 789 |
("Figures", figure_count),
|
|
@@ -864,7 +997,7 @@ def render_f5_shortcut():
|
|
| 864 |
def main():
|
| 865 |
# Initialize session state
|
| 866 |
if 'latex_code' not in st.session_state:
|
| 867 |
-
st.session_state.latex_code =
|
| 868 |
if 'show_preview' not in st.session_state:
|
| 869 |
st.session_state.show_preview = False
|
| 870 |
if 'last_compiled' not in st.session_state:
|
|
@@ -873,10 +1006,16 @@ def main():
|
|
| 873 |
st.session_state.errors = []
|
| 874 |
if 'warnings' not in st.session_state:
|
| 875 |
st.session_state.warnings = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 876 |
|
| 877 |
-
|
| 878 |
-
|
| 879 |
-
|
| 880 |
|
| 881 |
# Create main layout
|
| 882 |
col1, col2 = st.columns([3, 2])
|
|
@@ -898,7 +1037,7 @@ def main():
|
|
| 898 |
if MONACO_AVAILABLE:
|
| 899 |
try:
|
| 900 |
# Try to use Monaco editor with minimal parameters
|
| 901 |
-
latex_code = st_monaco(st.session_state.latex_code, height=500)
|
| 902 |
if latex_code is not None:
|
| 903 |
st.session_state.latex_code = latex_code
|
| 904 |
except Exception as e:
|
|
@@ -913,18 +1052,22 @@ def main():
|
|
| 913 |
|
| 914 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 915 |
|
| 916 |
-
# Status bar
|
| 917 |
-
render_status_bar()
|
| 918 |
|
| 919 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 920 |
|
|
|
|
|
|
|
|
|
|
| 921 |
# F5 shortcut reminder
|
| 922 |
render_f5_shortcut()
|
| 923 |
|
| 924 |
# Control buttons with VS Code styling
|
| 925 |
st.markdown('<div class="button-group">', unsafe_allow_html=True)
|
| 926 |
compile_btn = st.button("Compile", help="Compile LaTeX to PDF (F5)")
|
| 927 |
-
|
|
|
|
| 928 |
clear_btn = st.button("Clear", help="Clear editor content")
|
| 929 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 930 |
|
|
@@ -933,8 +1076,12 @@ def main():
|
|
| 933 |
st.session_state.compile_clicked = True
|
| 934 |
st.session_state.last_compiled = time.time()
|
| 935 |
|
| 936 |
-
if
|
| 937 |
-
st.session_state.latex_code =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 938 |
st.rerun()
|
| 939 |
|
| 940 |
if clear_btn:
|
|
@@ -948,8 +1095,17 @@ def main():
|
|
| 948 |
col_a, col_b = st.columns(2)
|
| 949 |
|
| 950 |
with col_a:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 951 |
st.checkbox("Auto-compile on save", value=False, key="auto_compile")
|
| 952 |
-
st.checkbox("Use pdflatex", value=True, key="use_pdflatex")
|
| 953 |
st.checkbox("Enable BibTeX", value=False, key="use_bibtex")
|
| 954 |
st.checkbox("Show line numbers", value=True, key="show_line_numbers")
|
| 955 |
|
|
@@ -957,13 +1113,25 @@ def main():
|
|
| 957 |
st.selectbox("Document Class",
|
| 958 |
["article", "report", "book", "letter", "beamer"],
|
| 959 |
index=0, key="doc_class")
|
| 960 |
-
st.selectbox("
|
| 961 |
-
|
| 962 |
-
|
| 963 |
st.selectbox("Editor Theme",
|
| 964 |
["Dark", "Light", "High Contrast"],
|
| 965 |
index=0, key="editor_theme")
|
| 966 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 967 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 968 |
|
| 969 |
with col2:
|
|
@@ -974,7 +1142,7 @@ def main():
|
|
| 974 |
# PDF compilation and output
|
| 975 |
if 'compile_clicked' in st.session_state and st.session_state.compile_clicked:
|
| 976 |
with st.spinner("Compiling..."):
|
| 977 |
-
pdf_data, stdout, stderr = latex_to_pdf(st.session_state.latex_code)
|
| 978 |
|
| 979 |
# Parse errors and warnings
|
| 980 |
errors, warnings = parse_latex_errors(stdout + stderr)
|
|
@@ -983,7 +1151,7 @@ def main():
|
|
| 983 |
|
| 984 |
if pdf_data:
|
| 985 |
st.session_state.pdf_data = pdf_data
|
| 986 |
-
st.success("Compilation successful")
|
| 987 |
|
| 988 |
# Toggle button for preview
|
| 989 |
if st.button("Toggle Preview", help="Show or hide the PDF preview"):
|
|
@@ -995,7 +1163,7 @@ def main():
|
|
| 995 |
# Display compilation info
|
| 996 |
if st.session_state.last_compiled:
|
| 997 |
time_str = time.strftime("%H:%M:%S", time.localtime(st.session_state.last_compiled))
|
| 998 |
-
st.info(f"Last compiled: {time_str}")
|
| 999 |
|
| 1000 |
# Optional preview
|
| 1001 |
if st.session_state.show_preview:
|
|
@@ -1015,7 +1183,7 @@ def main():
|
|
| 1015 |
|
| 1016 |
st.session_state.compile_clicked = False
|
| 1017 |
else:
|
| 1018 |
-
st.error("Compilation failed")
|
| 1019 |
st.markdown('<div class="terminal">', unsafe_allow_html=True)
|
| 1020 |
|
| 1021 |
# Highlight errors in output
|
|
@@ -1042,7 +1210,7 @@ def main():
|
|
| 1042 |
# Display compilation info
|
| 1043 |
if st.session_state.last_compiled:
|
| 1044 |
time_str = time.strftime("%H:%M:%S", time.localtime(st.session_state.last_compiled))
|
| 1045 |
-
st.info(f"Last compiled: {time_str}")
|
| 1046 |
|
| 1047 |
# Optional preview
|
| 1048 |
if st.session_state.show_preview:
|
|
@@ -1066,13 +1234,13 @@ def main():
|
|
| 1066 |
if structure:
|
| 1067 |
render_document_outline(structure)
|
| 1068 |
else:
|
| 1069 |
-
st.info("No document structure detected. Add sections using
|
| 1070 |
|
| 1071 |
with output_tabs[2]:
|
| 1072 |
# Document analysis features
|
| 1073 |
|
| 1074 |
# Document info
|
| 1075 |
-
render_document_info(st.session_state.latex_code)
|
| 1076 |
|
| 1077 |
# Package analysis
|
| 1078 |
packages = analyze_packages(st.session_state.latex_code)
|
|
|
|
| 20 |
|
| 21 |
# Set page configuration
|
| 22 |
st.set_page_config(
|
| 23 |
+
page_title="Professional LaTeX Editor with Chinese Support",
|
| 24 |
page_icon="📝",
|
| 25 |
layout="wide",
|
| 26 |
initial_sidebar_state="collapsed"
|
| 27 |
)
|
| 28 |
|
| 29 |
+
# Check if LaTeX engines are available
|
| 30 |
+
def check_latex_engines():
|
| 31 |
+
engines = {
|
| 32 |
+
'pdflatex': shutil.which("pdflatex") is not None,
|
| 33 |
+
'xelatex': shutil.which("xelatex") is not None,
|
| 34 |
+
'lualatex': shutil.which("lualatex") is not None
|
| 35 |
+
}
|
| 36 |
+
return engines
|
| 37 |
|
| 38 |
+
# Function to convert LaTeX to PDF with engine selection
|
| 39 |
+
def latex_to_pdf(latex_code, engine='xelatex'):
|
| 40 |
+
# Check if the specified engine is installed
|
| 41 |
+
if not shutil.which(engine):
|
| 42 |
+
st.error(f"{engine} not found. Debug info:")
|
| 43 |
st.code(f"PATH: {os.environ.get('PATH')}")
|
| 44 |
+
result = subprocess.run(["which", engine], capture_output=True, text=True)
|
| 45 |
+
st.code(f"which {engine}: {result.stdout} {result.stderr}")
|
| 46 |
+
return None, "", f"Error: {engine} is not installed or not in PATH."
|
| 47 |
|
| 48 |
with tempfile.TemporaryDirectory() as temp_dir:
|
| 49 |
temp_path = Path(temp_dir)
|
|
|
|
| 51 |
pdf_file = temp_path / "document.pdf"
|
| 52 |
|
| 53 |
# Write LaTeX code to file
|
| 54 |
+
with open(tex_file, "w", encoding='utf-8') as f:
|
| 55 |
f.write(latex_code)
|
| 56 |
|
| 57 |
try:
|
| 58 |
+
# Different compilation arguments for different engines
|
| 59 |
+
if engine == 'xelatex':
|
| 60 |
+
cmd = ["xelatex", "-interaction=nonstopmode", "-output-directory", temp_dir, str(tex_file)]
|
| 61 |
+
elif engine == 'lualatex':
|
| 62 |
+
cmd = ["lualatex", "-interaction=nonstopmode", "-output-directory", temp_dir, str(tex_file)]
|
| 63 |
+
else: # pdflatex
|
| 64 |
+
cmd = ["pdflatex", "-interaction=nonstopmode", "-output-directory", temp_dir, str(tex_file)]
|
| 65 |
+
|
| 66 |
+
# Run LaTeX engine to compile the file
|
| 67 |
+
process = subprocess.run(cmd, capture_output=True, text=True)
|
| 68 |
|
| 69 |
# Check if PDF was created
|
| 70 |
if pdf_file.exists():
|
|
|
|
| 207 |
|
| 208 |
return environments
|
| 209 |
|
| 210 |
+
# Default LaTeX template with Chinese support
|
| 211 |
+
default_template_chinese = r"""\documentclass{article}
|
| 212 |
+
\usepackage{xeCJK}
|
| 213 |
+
\usepackage{amsmath}
|
| 214 |
+
\usepackage{amssymb}
|
| 215 |
+
\usepackage{graphicx}
|
| 216 |
+
\usepackage{hyperref}
|
| 217 |
+
\usepackage{xcolor}
|
| 218 |
+
\usepackage{geometry}
|
| 219 |
+
|
| 220 |
+
% Set Chinese fonts (using Noto fonts available in the container)
|
| 221 |
+
\setCJKmainfont{Noto Serif CJK SC}
|
| 222 |
+
\setCJKsansfont{Noto Sans CJK SC}
|
| 223 |
+
\setCJKmonofont{Noto Sans Mono CJK SC}
|
| 224 |
+
|
| 225 |
+
% Page geometry
|
| 226 |
+
\geometry{a4paper, margin=1in}
|
| 227 |
+
|
| 228 |
+
\title{中文LaTeX文档示例}
|
| 229 |
+
\author{作者姓名}
|
| 230 |
+
\date{\today}
|
| 231 |
+
|
| 232 |
+
\begin{document}
|
| 233 |
+
|
| 234 |
+
\maketitle
|
| 235 |
+
|
| 236 |
+
\section{简介}
|
| 237 |
+
这是一个支持中文的LaTeX文档示例。您可以在这里编写中文内容。
|
| 238 |
+
|
| 239 |
+
This document also supports English text seamlessly mixed with Chinese characters.
|
| 240 |
+
|
| 241 |
+
\section{数学表达式}
|
| 242 |
+
\subsection{方程式}
|
| 243 |
+
著名的爱因斯坦质能方程:
|
| 244 |
+
\begin{equation}
|
| 245 |
+
E = mc^2
|
| 246 |
+
\end{equation}
|
| 247 |
+
|
| 248 |
+
二次方程求解公式:
|
| 249 |
+
\begin{equation}
|
| 250 |
+
x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
|
| 251 |
+
\end{equation}
|
| 252 |
+
|
| 253 |
+
\subsection{微积分}
|
| 254 |
+
积分示例:
|
| 255 |
+
\begin{equation}
|
| 256 |
+
\int_{0}^{\pi} \sin(x) \, dx = 2
|
| 257 |
+
\end{equation}
|
| 258 |
+
|
| 259 |
+
\section{列表和项目}
|
| 260 |
+
\subsection{无序列表}
|
| 261 |
+
\begin{itemize}
|
| 262 |
+
\item 第一项包含\textbf{粗体文字}
|
| 263 |
+
\item 第二项包含\textit{斜体文字}
|
| 264 |
+
\item 第三项包含\textcolor{blue}{彩色文字}
|
| 265 |
+
\end{itemize}
|
| 266 |
+
|
| 267 |
+
\subsection{有序列表}
|
| 268 |
+
\begin{enumerate}
|
| 269 |
+
\item 第一步
|
| 270 |
+
\item 第二步
|
| 271 |
+
\item 第三步
|
| 272 |
+
\end{enumerate}
|
| 273 |
+
|
| 274 |
+
\section{表格}
|
| 275 |
+
\begin{table}[h]
|
| 276 |
+
\centering
|
| 277 |
+
\begin{tabular}{|c|c|c|}
|
| 278 |
+
\hline
|
| 279 |
+
姓名 & 年龄 & 城市 \\
|
| 280 |
+
\hline
|
| 281 |
+
张三 & 25 & 北京 \\
|
| 282 |
+
\hline
|
| 283 |
+
李四 & 30 & 上海 \\
|
| 284 |
+
\hline
|
| 285 |
+
\end{tabular}
|
| 286 |
+
\caption{简单表格示例}
|
| 287 |
+
\label{tab:simple}
|
| 288 |
+
\end{table}
|
| 289 |
+
|
| 290 |
+
\section{图形}
|
| 291 |
+
您可以使用以下语法包含图形:
|
| 292 |
+
|
| 293 |
+
% \begin{figure}[h]
|
| 294 |
+
% \centering
|
| 295 |
+
% \includegraphics[width=0.7\textwidth]{example-image}
|
| 296 |
+
% \caption{示例图形}
|
| 297 |
+
% \label{fig:example}
|
| 298 |
+
% \end{figure}
|
| 299 |
+
|
| 300 |
+
\section{引用文献}
|
| 301 |
+
您可以使用 \verb|\cite{}| 命令引用参考文献 \cite{example}。
|
| 302 |
+
|
| 303 |
+
\section{交叉引用}
|
| 304 |
+
您可以使用 \verb|\ref{}| 命令引用章节、图形和表格。
|
| 305 |
+
例如,参见表格~\ref{tab:simple}。
|
| 306 |
+
|
| 307 |
+
\section{结论}
|
| 308 |
+
这里是您的结论。
|
| 309 |
+
|
| 310 |
+
% 示例参考文献条目
|
| 311 |
+
\begin{thebibliography}{9}
|
| 312 |
+
\bibitem{example} 作者姓名 (2023). \textit{书籍标题}. 出版社.
|
| 313 |
+
\end{thebibliography}
|
| 314 |
+
|
| 315 |
+
\end{document}
|
| 316 |
+
"""
|
| 317 |
+
|
| 318 |
+
# Default LaTeX template (English)
|
| 319 |
+
default_template_english = r"""\documentclass{article}
|
| 320 |
\usepackage[utf8]{inputenc}
|
| 321 |
\usepackage{amsmath}
|
| 322 |
\usepackage{amssymb}
|
|
|
|
| 382 |
\label{tab:simple}
|
| 383 |
\end{table}
|
| 384 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 385 |
\section{Conclusion}
|
| 386 |
Your conclusion here.
|
| 387 |
|
|
|
|
| 388 |
\begin{thebibliography}{9}
|
| 389 |
\bibitem{example} Author, A. (2023). \textit{Title of the Work}. Publisher.
|
| 390 |
\end{thebibliography}
|
|
|
|
| 392 |
\end{document}
|
| 393 |
"""
|
| 394 |
|
| 395 |
+
# Enhanced VSCode-like styling (keeping the original styling)
|
| 396 |
st.markdown("""
|
| 397 |
<style>
|
| 398 |
/* Base theming - VS Code inspired */
|
|
|
|
| 488 |
color: #cca700;
|
| 489 |
}
|
| 490 |
|
| 491 |
+
/* Engine status indicators */
|
| 492 |
+
.engine-status {
|
| 493 |
+
display: flex;
|
| 494 |
+
gap: 10px;
|
| 495 |
+
margin: 10px 0;
|
| 496 |
+
padding: 10px;
|
| 497 |
+
background-color: #252526;
|
| 498 |
+
border-radius: 4px;
|
| 499 |
+
}
|
| 500 |
+
|
| 501 |
+
.engine-item {
|
| 502 |
+
display: flex;
|
| 503 |
+
align-items: center;
|
| 504 |
+
gap: 5px;
|
| 505 |
+
padding: 5px 10px;
|
| 506 |
+
border-radius: 3px;
|
| 507 |
+
font-size: 12px;
|
| 508 |
+
}
|
| 509 |
+
|
| 510 |
+
.engine-available {
|
| 511 |
+
background-color: #1e3a1e;
|
| 512 |
+
color: #4caf50;
|
| 513 |
+
}
|
| 514 |
+
|
| 515 |
+
.engine-unavailable {
|
| 516 |
+
background-color: #3a1e1e;
|
| 517 |
+
color: #f44336;
|
| 518 |
+
}
|
| 519 |
+
|
| 520 |
+
/* All other existing styles remain the same */
|
| 521 |
.outline-view {
|
| 522 |
background-color: #252526;
|
| 523 |
border: 1px solid #2d2d2d;
|
|
|
|
| 542 |
margin-left: 5px;
|
| 543 |
}
|
| 544 |
|
|
|
|
| 545 |
.download-button {
|
| 546 |
display: inline-block;
|
| 547 |
padding: 8px 16px;
|
|
|
|
| 559 |
background-color: #4eb772;
|
| 560 |
}
|
| 561 |
|
|
|
|
| 562 |
.preview-container {
|
| 563 |
background-color: #252526;
|
| 564 |
border: 1px solid #2d2d2d;
|
|
|
|
| 567 |
margin-top: 10px;
|
| 568 |
}
|
| 569 |
|
|
|
|
| 570 |
.control-panel {
|
| 571 |
background-color: #252526;
|
| 572 |
border: 1px solid #2d2d2d;
|
|
|
|
| 580 |
gap: 5px;
|
| 581 |
}
|
| 582 |
|
|
|
|
| 583 |
.stInfo {
|
| 584 |
background-color: #063b49;
|
| 585 |
color: #bbbbbb;
|
|
|
|
| 598 |
border: 1px solid #1e5a3a;
|
| 599 |
}
|
| 600 |
|
|
|
|
| 601 |
footer, header {display: none !important;}
|
| 602 |
#MainMenu {visibility: hidden;}
|
| 603 |
|
|
|
|
| 604 |
.stTabs [data-baseweb="tab-list"] {
|
| 605 |
background-color: #2d2d2d;
|
| 606 |
gap: 0px !important;
|
|
|
|
| 621 |
color: white;
|
| 622 |
}
|
| 623 |
|
|
|
|
| 624 |
.problems-panel {
|
| 625 |
background-color: #252526;
|
| 626 |
border: 1px solid #2d2d2d;
|
|
|
|
| 658 |
margin-top: 2px;
|
| 659 |
}
|
| 660 |
|
|
|
|
| 661 |
.document-info {
|
| 662 |
background-color: #252526;
|
| 663 |
border: 1px solid #2d2d2d;
|
|
|
|
| 684 |
text-align: right;
|
| 685 |
}
|
| 686 |
|
|
|
|
| 687 |
.packages-panel {
|
| 688 |
background-color: #252526;
|
| 689 |
border: 1px solid #2d2d2d;
|
|
|
|
| 712 |
font-family: monospace;
|
| 713 |
}
|
| 714 |
|
|
|
|
| 715 |
.environments-panel {
|
| 716 |
background-color: #252526;
|
| 717 |
border: 1px solid #2d2d2d;
|
|
|
|
| 738 |
font-weight: bold;
|
| 739 |
}
|
| 740 |
|
|
|
|
| 741 |
.shortcut-item {
|
| 742 |
background-color: #252526;
|
| 743 |
border: 1px solid #2d2d2d;
|
|
|
|
| 758 |
margin-right: 10px;
|
| 759 |
}
|
| 760 |
|
|
|
|
| 761 |
.monaco-editor-container {
|
| 762 |
border: 1px solid #2d2d2d;
|
| 763 |
border-radius: 4px;
|
|
|
|
| 765 |
margin-bottom: 10px;
|
| 766 |
}
|
| 767 |
|
|
|
|
| 768 |
.monaco-editor, .monaco-editor-background, .monaco-editor .margin {
|
| 769 |
background-color: #1e1e1e !important;
|
| 770 |
}
|
|
|
|
| 782 |
"""
|
| 783 |
st.markdown(tab_html, unsafe_allow_html=True)
|
| 784 |
|
| 785 |
+
# VS Code-style status bar with engine info
|
| 786 |
+
def render_status_bar(engine="XeLaTeX"):
|
| 787 |
+
status_html = f"""
|
| 788 |
<div class="status-bar">
|
| 789 |
+
<span>{engine}</span>
|
| 790 |
<span>Line: 1, Col: 1</span>
|
| 791 |
<span>UTF-8</span>
|
| 792 |
</div>
|
| 793 |
"""
|
| 794 |
st.markdown(status_html, unsafe_allow_html=True)
|
| 795 |
|
| 796 |
+
# Render engine status
|
| 797 |
+
def render_engine_status():
|
| 798 |
+
engines = check_latex_engines()
|
| 799 |
+
|
| 800 |
+
engine_html = '<div class="engine-status">'
|
| 801 |
+
for engine_name, available in engines.items():
|
| 802 |
+
status_class = "engine-available" if available else "engine-unavailable"
|
| 803 |
+
status_icon = "✅" if available else "❌"
|
| 804 |
+
engine_html += f'<div class="engine-item {status_class}">{status_icon} {engine_name}</div>'
|
| 805 |
+
engine_html += '</div>'
|
| 806 |
+
|
| 807 |
+
st.markdown(engine_html, unsafe_allow_html=True)
|
| 808 |
+
|
| 809 |
# Render a document outline based on section hierarchy
|
| 810 |
def render_document_outline(structure):
|
| 811 |
if not structure:
|
|
|
|
| 870 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 871 |
|
| 872 |
# Render document information panel
|
| 873 |
+
def render_document_info(latex_code, engine):
|
| 874 |
# Ensure latex_code is a string
|
| 875 |
if not isinstance(latex_code, str):
|
| 876 |
try:
|
|
|
|
| 904 |
ref_count = len(re.findall(r'\\ref\{', latex_code))
|
| 905 |
cite_count = len(re.findall(r'\\cite\{', latex_code))
|
| 906 |
|
| 907 |
+
# Check for Chinese characters
|
| 908 |
+
chinese_chars = len(re.findall(r'[\u4e00-\u9fff]', latex_code))
|
| 909 |
+
|
| 910 |
# Render the info panel
|
| 911 |
st.markdown('<div class="document-info">', unsafe_allow_html=True)
|
| 912 |
|
| 913 |
info_rows = [
|
| 914 |
+
("LaTeX Engine", engine),
|
| 915 |
("Document Class", doc_class),
|
| 916 |
("Word Count", word_count),
|
| 917 |
("Character Count", char_count),
|
| 918 |
("Line Count", line_count),
|
| 919 |
+
("Chinese Characters", chinese_chars),
|
| 920 |
("Equations", equation_count),
|
| 921 |
("Align Environments", align_count),
|
| 922 |
("Figures", figure_count),
|
|
|
|
| 997 |
def main():
|
| 998 |
# Initialize session state
|
| 999 |
if 'latex_code' not in st.session_state:
|
| 1000 |
+
st.session_state.latex_code = default_template_chinese
|
| 1001 |
if 'show_preview' not in st.session_state:
|
| 1002 |
st.session_state.show_preview = False
|
| 1003 |
if 'last_compiled' not in st.session_state:
|
|
|
|
| 1006 |
st.session_state.errors = []
|
| 1007 |
if 'warnings' not in st.session_state:
|
| 1008 |
st.session_state.warnings = []
|
| 1009 |
+
if 'selected_engine' not in st.session_state:
|
| 1010 |
+
st.session_state.selected_engine = 'xelatex'
|
| 1011 |
+
|
| 1012 |
+
# Check available engines
|
| 1013 |
+
engines = check_latex_engines()
|
| 1014 |
+
available_engines = [engine for engine, available in engines.items() if available]
|
| 1015 |
|
| 1016 |
+
if not available_engines:
|
| 1017 |
+
st.error("⚠️ No LaTeX engines are installed. The compilation feature will not work.")
|
| 1018 |
+
return
|
| 1019 |
|
| 1020 |
# Create main layout
|
| 1021 |
col1, col2 = st.columns([3, 2])
|
|
|
|
| 1037 |
if MONACO_AVAILABLE:
|
| 1038 |
try:
|
| 1039 |
# Try to use Monaco editor with minimal parameters
|
| 1040 |
+
latex_code = st_monaco(st.session_state.latex_code, height=500, language="latex")
|
| 1041 |
if latex_code is not None:
|
| 1042 |
st.session_state.latex_code = latex_code
|
| 1043 |
except Exception as e:
|
|
|
|
| 1052 |
|
| 1053 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 1054 |
|
| 1055 |
+
# Status bar with engine info
|
| 1056 |
+
render_status_bar(st.session_state.selected_engine.upper())
|
| 1057 |
|
| 1058 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 1059 |
|
| 1060 |
+
# Engine status display
|
| 1061 |
+
render_engine_status()
|
| 1062 |
+
|
| 1063 |
# F5 shortcut reminder
|
| 1064 |
render_f5_shortcut()
|
| 1065 |
|
| 1066 |
# Control buttons with VS Code styling
|
| 1067 |
st.markdown('<div class="button-group">', unsafe_allow_html=True)
|
| 1068 |
compile_btn = st.button("Compile", help="Compile LaTeX to PDF (F5)")
|
| 1069 |
+
load_chinese_btn = st.button("Load Chinese Template", help="Load Chinese template")
|
| 1070 |
+
load_english_btn = st.button("Load English Template", help="Load English template")
|
| 1071 |
clear_btn = st.button("Clear", help="Clear editor content")
|
| 1072 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 1073 |
|
|
|
|
| 1076 |
st.session_state.compile_clicked = True
|
| 1077 |
st.session_state.last_compiled = time.time()
|
| 1078 |
|
| 1079 |
+
if load_chinese_btn:
|
| 1080 |
+
st.session_state.latex_code = default_template_chinese
|
| 1081 |
+
st.rerun()
|
| 1082 |
+
|
| 1083 |
+
if load_english_btn:
|
| 1084 |
+
st.session_state.latex_code = default_template_english
|
| 1085 |
st.rerun()
|
| 1086 |
|
| 1087 |
if clear_btn:
|
|
|
|
| 1095 |
col_a, col_b = st.columns(2)
|
| 1096 |
|
| 1097 |
with col_a:
|
| 1098 |
+
# Engine selection
|
| 1099 |
+
if available_engines:
|
| 1100 |
+
selected_engine = st.selectbox(
|
| 1101 |
+
"LaTeX Engine",
|
| 1102 |
+
available_engines,
|
| 1103 |
+
index=available_engines.index(st.session_state.selected_engine) if st.session_state.selected_engine in available_engines else 0,
|
| 1104 |
+
key="engine_select"
|
| 1105 |
+
)
|
| 1106 |
+
st.session_state.selected_engine = selected_engine
|
| 1107 |
+
|
| 1108 |
st.checkbox("Auto-compile on save", value=False, key="auto_compile")
|
|
|
|
| 1109 |
st.checkbox("Enable BibTeX", value=False, key="use_bibtex")
|
| 1110 |
st.checkbox("Show line numbers", value=True, key="show_line_numbers")
|
| 1111 |
|
|
|
|
| 1113 |
st.selectbox("Document Class",
|
| 1114 |
["article", "report", "book", "letter", "beamer"],
|
| 1115 |
index=0, key="doc_class")
|
| 1116 |
+
st.selectbox("Font Selection",
|
| 1117 |
+
["Noto CJK (Chinese)", "WQY (Chinese)", "Latin Modern (English)", "Times (English)"],
|
| 1118 |
+
index=0, key="font_selection")
|
| 1119 |
st.selectbox("Editor Theme",
|
| 1120 |
["Dark", "Light", "High Contrast"],
|
| 1121 |
index=0, key="editor_theme")
|
| 1122 |
|
| 1123 |
+
# Engine information
|
| 1124 |
+
st.markdown("### Engine Information")
|
| 1125 |
+
engine_info = {
|
| 1126 |
+
'pdflatex': 'Traditional LaTeX engine, good for English documents',
|
| 1127 |
+
'xelatex': 'Modern engine with Unicode support, excellent for Chinese and mixed languages',
|
| 1128 |
+
'lualatex': 'Advanced engine with Lua scripting support, good for complex typography'
|
| 1129 |
+
}
|
| 1130 |
+
|
| 1131 |
+
for engine, description in engine_info.items():
|
| 1132 |
+
status = "✅ Available" if engines.get(engine, False) else "❌ Not Available"
|
| 1133 |
+
st.write(f"**{engine.upper()}**: {description} - {status}")
|
| 1134 |
+
|
| 1135 |
st.markdown('</div>', unsafe_allow_html=True)
|
| 1136 |
|
| 1137 |
with col2:
|
|
|
|
| 1142 |
# PDF compilation and output
|
| 1143 |
if 'compile_clicked' in st.session_state and st.session_state.compile_clicked:
|
| 1144 |
with st.spinner("Compiling..."):
|
| 1145 |
+
pdf_data, stdout, stderr = latex_to_pdf(st.session_state.latex_code, st.session_state.selected_engine)
|
| 1146 |
|
| 1147 |
# Parse errors and warnings
|
| 1148 |
errors, warnings = parse_latex_errors(stdout + stderr)
|
|
|
|
| 1151 |
|
| 1152 |
if pdf_data:
|
| 1153 |
st.session_state.pdf_data = pdf_data
|
| 1154 |
+
st.success(f"Compilation successful using {st.session_state.selected_engine.upper()}")
|
| 1155 |
|
| 1156 |
# Toggle button for preview
|
| 1157 |
if st.button("Toggle Preview", help="Show or hide the PDF preview"):
|
|
|
|
| 1163 |
# Display compilation info
|
| 1164 |
if st.session_state.last_compiled:
|
| 1165 |
time_str = time.strftime("%H:%M:%S", time.localtime(st.session_state.last_compiled))
|
| 1166 |
+
st.info(f"Last compiled: {time_str} with {st.session_state.selected_engine.upper()}")
|
| 1167 |
|
| 1168 |
# Optional preview
|
| 1169 |
if st.session_state.show_preview:
|
|
|
|
| 1183 |
|
| 1184 |
st.session_state.compile_clicked = False
|
| 1185 |
else:
|
| 1186 |
+
st.error(f"Compilation failed with {st.session_state.selected_engine.upper()}")
|
| 1187 |
st.markdown('<div class="terminal">', unsafe_allow_html=True)
|
| 1188 |
|
| 1189 |
# Highlight errors in output
|
|
|
|
| 1210 |
# Display compilation info
|
| 1211 |
if st.session_state.last_compiled:
|
| 1212 |
time_str = time.strftime("%H:%M:%S", time.localtime(st.session_state.last_compiled))
|
| 1213 |
+
st.info(f"Last compiled: {time_str} with {st.session_state.selected_engine.upper()}")
|
| 1214 |
|
| 1215 |
# Optional preview
|
| 1216 |
if st.session_state.show_preview:
|
|
|
|
| 1234 |
if structure:
|
| 1235 |
render_document_outline(structure)
|
| 1236 |
else:
|
| 1237 |
+
st.info("No document structure detected. Add sections using \\section{}, \\subsection{}, etc.")
|
| 1238 |
|
| 1239 |
with output_tabs[2]:
|
| 1240 |
# Document analysis features
|
| 1241 |
|
| 1242 |
# Document info
|
| 1243 |
+
render_document_info(st.session_state.latex_code, st.session_state.selected_engine.upper())
|
| 1244 |
|
| 1245 |
# Package analysis
|
| 1246 |
packages = analyze_packages(st.session_state.latex_code)
|