| | 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: |
| | |
| | temp_dir = tempfile.mkdtemp() |
| | tex_file = os.path.join(temp_dir, 'document.tex') |
| | pdf_file = os.path.join(temp_dir, 'document.pdf') |
| | |
| | |
| | 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 |
| | |
| | |
| | with open(tex_file, 'w', encoding='utf-8') as f: |
| | f.write(latex_code) |
| | |
| | |
| | engine = 'pdflatex' |
| | if '\\usepackage{fontspec}' in latex_code or '\\setmainfont' in latex_code: |
| | engine = 'xelatex' |
| | |
| | |
| | result = subprocess.run( |
| | [engine, '-interaction=nonstopmode', '-output-directory', temp_dir, tex_file], |
| | capture_output=True, |
| | text=True, |
| | timeout=30 |
| | ) |
| | |
| | |
| | if os.path.exists(pdf_file): |
| | return send_file(pdf_file, mimetype='application/pdf', as_attachment=True, download_name='compiled.pdf') |
| | else: |
| | |
| | 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: |
| | |
| | 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) |