texcomp / app.py
Samuel
update
7578e01
from flask import Flask, request, jsonify, send_file, render_template_string
from flask_cors import CORS
import os
import subprocess
import tempfile
import shutil
from pathlib import Path
app = Flask(__name__)
CORS(app)
def read_file(filepath):
"""Read index.html content"""
try:
with open(filepath, 'r', encoding='utf-8') as f:
return f.read()
except FileNotFoundError:
return "<h1>Frontend not found</h1><p>Please ensure index.html is in the same directory as app.py</p>"
@app.route('/')
def index():
"""Serve the frontend HTML"""
html_content = read_file('index.html')
return render_template_string(html_content)
@app.route('/docs')
def docs():
"""API documentation"""
docs_html = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>LaTeX Compiler API Documentation</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-50 p-8">
<div class="max-w-4xl mx-auto bg-white rounded-lg shadow-lg p-8">
<h1 class="text-3xl font-bold mb-6 text-gray-800">LaTeX Compiler API Documentation</h1>
<div class="mb-8">
<h2 class="text-2xl font-semibold mb-4 text-gray-700">Endpoints</h2>
<div class="mb-6 p-4 bg-blue-50 rounded-lg">
<h3 class="text-xl font-semibold mb-2 text-blue-900">POST /compile</h3>
<p class="mb-3 text-gray-700">Compile LaTeX code to PDF</p>
<h4 class="font-semibold mb-2">Request Methods:</h4>
<ul class="list-disc ml-6 mb-3 space-y-2">
<li><strong>File Upload:</strong> multipart/form-data with 'file' field (.tex file)</li>
<li><strong>JSON:</strong> application/json with {"code": "LaTeX source code"}</li>
</ul>
<h4 class="font-semibold mb-2">Success Response:</h4>
<pre class="bg-gray-800 text-green-400 p-3 rounded overflow-x-auto">Content-Type: application/pdf
PDF file binary data</pre>
<h4 class="font-semibold mb-2 mt-3">Error Response:</h4>
<pre class="bg-gray-800 text-red-400 p-3 rounded overflow-x-auto">{
"error": "Compilation failed",
"log": "LaTeX error log details..."
}</pre>
</div>
<div class="mb-6 p-4 bg-green-50 rounded-lg">
<h3 class="text-xl font-semibold mb-2 text-green-900">GET /</h3>
<p class="text-gray-700">Serve the web frontend interface</p>
</div>
<div class="p-4 bg-purple-50 rounded-lg">
<h3 class="text-xl font-semibold mb-2 text-purple-900">GET /docs</h3>
<p class="text-gray-700">Display this API documentation</p>
</div>
</div>
<div class="mb-8">
<h2 class="text-2xl font-semibold mb-4 text-gray-700">Example Usage</h2>
<h3 class="text-lg font-semibold mb-2">cURL - JSON Request:</h3>
<pre class="bg-gray-800 text-white p-4 rounded overflow-x-auto mb-4">curl -X POST http://localhost:7860/compile \\
-H "Content-Type: application/json" \\
-d '{"code": "\\\\documentclass{article}\\\\begin{document}Hello\\\\end{document}"}' \\
--output output.pdf</pre>
<h3 class="text-lg font-semibold mb-2">cURL - File Upload:</h3>
<pre class="bg-gray-800 text-white p-4 rounded overflow-x-auto mb-4">curl -X POST http://localhost:7860/compile \\
-F "file=@document.tex" \\
--output output.pdf</pre>
<h3 class="text-lg font-semibold mb-2">Python Example:</h3>
<pre class="bg-gray-800 text-white p-4 rounded overflow-x-auto">import requests
latex_code = r"""
\\documentclass{article}
\\begin{document}
Hello from Python!
\\end{document}
"""
response = requests.post(
'http://localhost:7860/compile',
json={'code': latex_code}
)
if response.status_code == 200:
with open('output.pdf', 'wb') as f:
f.write(response.content)
else:
print(response.json())</pre>
</div>
<div class="mt-8 p-4 bg-yellow-50 rounded-lg">
<h3 class="font-semibold text-yellow-900 mb-2">⚠️ Notes</h3>
<ul class="list-disc ml-6 text-gray-700 space-y-1">
<li>Maximum file size recommended: 10MB</li>
<li>Compilation timeout: 30 seconds</li>
<li>Temporary files are automatically cleaned up</li>
<li>Only pdflatex engine is supported</li>
</ul>
</div>
<div class="mt-6 text-center">
<a href="/" class="text-blue-600 hover:text-blue-800 underline">← Back to Compiler</a>
</div>
</div>
</body>
</html>
'''
return docs_html
@app.route('/compile', methods=['POST'])
def compile_latex():
"""Compile LaTeX code to PDF"""
temp_dir = None
try:
# Create temporary directory
temp_dir = tempfile.mkdtemp()
tex_file = os.path.join(temp_dir, 'document.tex')
pdf_file = os.path.join(temp_dir, 'document.pdf')
# Get LaTeX code from request
latex_code = None
if request.is_json:
data = request.get_json()
latex_code = data.get('code', '')
elif 'file' in request.files:
file = request.files['file']
if file.filename == '':
return jsonify({'error': 'No file selected'}), 400
latex_code = file.read().decode('utf-8')
else:
return jsonify({'error': 'No LaTeX code provided. Send JSON with "code" field or upload .tex file'}), 400
if not latex_code:
return jsonify({'error': 'Empty LaTeX code'}), 400
# Write LaTeX code to file
with open(tex_file, 'w', encoding='utf-8') as f:
f.write(latex_code)
# Detect which engine to use based on packages
engine = 'pdflatex'
if '\\usepackage{fontspec}' in latex_code or '\\setmainfont' in latex_code:
engine = 'xelatex'
# Compile with appropriate LaTeX engine
result = subprocess.run(
[engine, '-interaction=nonstopmode', '-output-directory', temp_dir, tex_file],
capture_output=True,
text=True,
timeout=30
)
# Check if PDF was created
if os.path.exists(pdf_file):
return send_file(pdf_file, mimetype='application/pdf', as_attachment=True, download_name='compiled.pdf')
else:
# Compilation failed, return error log
log_file = os.path.join(temp_dir, 'document.log')
error_log = ''
if os.path.exists(log_file):
with open(log_file, 'r', encoding='utf-8', errors='ignore') as f:
error_log = f.read()
return jsonify({
'error': f'Compilation failed with {engine}',
'log': error_log or result.stderr or result.stdout
}), 400
except subprocess.TimeoutExpired:
return jsonify({'error': 'Compilation timeout (30s limit exceeded)'}), 408
except Exception as e:
return jsonify({'error': f'Server error: {str(e)}'}), 500
finally:
# Clean up temporary files
if temp_dir and os.path.exists(temp_dir):
try:
shutil.rmtree(temp_dir)
except Exception:
pass
if __name__ == '__main__':
app.run(host='0.0.0.0', port=7860, debug=False)