| 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) |