ABDALLALSWAITI commited on
Commit
62a0d03
·
verified ·
1 Parent(s): 75b2025

Rename src/api_app.py to api.py

Browse files
Files changed (2) hide show
  1. api.py +385 -0
  2. src/api_app.py +0 -558
api.py ADDED
@@ -0,0 +1,385 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, File, UploadFile, Form, HTTPException
2
+ from fastapi.responses import Response, JSONResponse
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ import tempfile
5
+ import shutil
6
+ import os
7
+ import subprocess
8
+ import base64
9
+ from pathlib import Path
10
+
11
+ app = FastAPI(
12
+ title="HTML to PDF API",
13
+ description="Convert HTML to PDF using Puppeteer",
14
+ version="1.0.0"
15
+ )
16
+
17
+ # Enable CORS
18
+ app.add_middleware(
19
+ CORSMiddleware,
20
+ allow_origins=["*"],
21
+ allow_credentials=True,
22
+ allow_methods=["*"],
23
+ allow_headers=["*"],
24
+ )
25
+
26
+ def convert_html_to_pdf(html_content: str, aspect_ratio: str):
27
+ """Convert HTML content to PDF"""
28
+ temp_dir = None
29
+ try:
30
+ temp_dir = tempfile.mkdtemp()
31
+
32
+ # Style injection for better PDF rendering
33
+ style_injection = """
34
+ <style>
35
+ @page { margin: 0; }
36
+ * {
37
+ -webkit-print-color-adjust: exact !important;
38
+ print-color-adjust: exact !important;
39
+ color-adjust: exact !important;
40
+ }
41
+ body {
42
+ -webkit-print-color-adjust: exact !important;
43
+ print-color-adjust: exact !important;
44
+ }
45
+ </style>
46
+ """
47
+
48
+ if '</head>' in html_content:
49
+ html_content = html_content.replace('</head>', style_injection + '</head>')
50
+ elif '<body' in html_content:
51
+ html_content = html_content.replace('<body', style_injection + '<body', 1)
52
+ else:
53
+ html_content = style_injection + html_content
54
+
55
+ # Save HTML to temp file
56
+ html_file = os.path.join(temp_dir, "input.html")
57
+ with open(html_file, 'w', encoding='utf-8') as f:
58
+ f.write(html_content)
59
+
60
+ # Get puppeteer script path
61
+ script_dir = os.path.dirname(os.path.abspath(__file__))
62
+ puppeteer_script = os.path.join(script_dir, 'puppeteer_pdf.js')
63
+
64
+ # Run conversion
65
+ result = subprocess.run(
66
+ ['node', puppeteer_script, html_file, aspect_ratio],
67
+ capture_output=True,
68
+ text=True,
69
+ timeout=60,
70
+ cwd=script_dir
71
+ )
72
+
73
+ if result.returncode != 0:
74
+ raise Exception(f"PDF conversion failed: {result.stderr}")
75
+
76
+ pdf_file = html_file.replace('.html', '.pdf')
77
+
78
+ if not os.path.exists(pdf_file):
79
+ raise Exception("PDF file was not generated")
80
+
81
+ with open(pdf_file, 'rb') as f:
82
+ pdf_bytes = f.read()
83
+
84
+ shutil.rmtree(temp_dir, ignore_errors=True)
85
+ return pdf_bytes
86
+
87
+ except Exception as e:
88
+ if temp_dir:
89
+ shutil.rmtree(temp_dir, ignore_errors=True)
90
+ raise e
91
+
92
+ @app.get("/")
93
+ async def root():
94
+ """API root endpoint"""
95
+ return {
96
+ "message": "HTML to PDF Conversion API",
97
+ "version": "1.0.0",
98
+ "endpoints": {
99
+ "POST /convert": "Convert HTML to PDF (file upload)",
100
+ "POST /convert-text": "Convert HTML text to PDF",
101
+ "GET /health": "Health check"
102
+ }
103
+ }
104
+
105
+ @app.get("/health")
106
+ async def health_check():
107
+ """Health check endpoint"""
108
+ return {"status": "healthy", "service": "html-to-pdf-api"}
109
+
110
+ @app.post("/convert")
111
+ async def convert_file(
112
+ file: UploadFile = File(...),
113
+ aspect_ratio: str = Form(default="9:16")
114
+ ):
115
+ """
116
+ Convert uploaded HTML file to PDF
117
+
118
+ - **file**: HTML file to convert
119
+ - **aspect_ratio**: Page orientation (16:9, 1:1, or 9:16)
120
+ """
121
+ if not file.filename.lower().endswith(('.html', '.htm')):
122
+ raise HTTPException(status_code=400, detail="File must be HTML (.html or .htm)")
123
+
124
+ if aspect_ratio not in ["16:9", "1:1", "9:16"]:
125
+ raise HTTPException(status_code=400, detail="Invalid aspect ratio. Use: 16:9, 1:1, or 9:16")
126
+
127
+ try:
128
+ # Read HTML content
129
+ content = await file.read()
130
+ try:
131
+ html_content = content.decode('utf-8')
132
+ except UnicodeDecodeError:
133
+ html_content = content.decode('latin-1')
134
+
135
+ # Convert to PDF
136
+ pdf_bytes = convert_html_to_pdf(html_content, aspect_ratio)
137
+
138
+ # Return PDF file
139
+ return Response(
140
+ content=pdf_bytes,
141
+ media_type="application/pdf",
142
+ headers={
143
+ "Content-Disposition": f"attachment; filename={file.filename.replace('.html', '.pdf').replace('.htm', '.pdf')}"
144
+ }
145
+ )
146
+
147
+ except Exception as e:
148
+ raise HTTPException(status_code=500, detail=f"Conversion failed: {str(e)}")
149
+
150
+ @app.post("/convert-text")
151
+ async def convert_text(
152
+ html: str = Form(...),
153
+ aspect_ratio: str = Form(default="9:16"),
154
+ return_base64: bool = Form(default=False)
155
+ ):
156
+ """
157
+ Convert HTML text to PDF
158
+
159
+ - **html**: HTML content as string
160
+ - **aspect_ratio**: Page orientation (16:9, 1:1, or 9:16)
161
+ - **return_base64**: If true, returns base64 encoded PDF in JSON
162
+ """
163
+ if aspect_ratio not in ["16:9", "1:1", "9:16"]:
164
+ raise HTTPException(status_code=400, detail="Invalid aspect ratio. Use: 16:9, 1:1, or 9:16")
165
+
166
+ try:
167
+ # Convert to PDF
168
+ pdf_bytes = convert_html_to_pdf(html, aspect_ratio)
169
+
170
+ if return_base64:
171
+ # Return as JSON with base64 encoded PDF
172
+ pdf_base64 = base64.b64encode(pdf_bytes).decode('utf-8')
173
+ return JSONResponse(content={
174
+ "success": True,
175
+ "pdf_base64": pdf_base64,
176
+ "size_bytes": len(pdf_bytes)
177
+ })
178
+ else:
179
+ # Return PDF file directly
180
+ return Response(
181
+ content=pdf_bytes,
182
+ media_type="application/pdf",
183
+ headers={
184
+ "Content-Disposition": "attachment; filename=converted.pdf"
185
+ }
186
+ )
187
+
188
+ except Exception as e:
189
+ raise HTTPException(status_code=500, detail=f"Conversion failed: {str(e)}")
190
+
191
+ if __name__ == "__main__":
192
+ import uvicorn
193
+ uvicorn.run(app, host="0.0.0.0", port=7860)from fastapi import FastAPI, File, UploadFile, Form, HTTPException
194
+ from fastapi.responses import Response, JSONResponse
195
+ from fastapi.middleware.cors import CORSMiddleware
196
+ import tempfile
197
+ import shutil
198
+ import os
199
+ import subprocess
200
+ import base64
201
+ from pathlib import Path
202
+
203
+ app = FastAPI(
204
+ title="HTML to PDF API",
205
+ description="Convert HTML to PDF using Puppeteer",
206
+ version="1.0.0"
207
+ )
208
+
209
+ # Enable CORS
210
+ app.add_middleware(
211
+ CORSMiddleware,
212
+ allow_origins=["*"],
213
+ allow_credentials=True,
214
+ allow_methods=["*"],
215
+ allow_headers=["*"],
216
+ )
217
+
218
+ def convert_html_to_pdf(html_content: str, aspect_ratio: str):
219
+ """Convert HTML content to PDF"""
220
+ temp_dir = None
221
+ try:
222
+ temp_dir = tempfile.mkdtemp()
223
+
224
+ # Style injection for better PDF rendering
225
+ style_injection = """
226
+ <style>
227
+ @page { margin: 0; }
228
+ * {
229
+ -webkit-print-color-adjust: exact !important;
230
+ print-color-adjust: exact !important;
231
+ color-adjust: exact !important;
232
+ }
233
+ body {
234
+ -webkit-print-color-adjust: exact !important;
235
+ print-color-adjust: exact !important;
236
+ }
237
+ </style>
238
+ """
239
+
240
+ if '</head>' in html_content:
241
+ html_content = html_content.replace('</head>', style_injection + '</head>')
242
+ elif '<body' in html_content:
243
+ html_content = html_content.replace('<body', style_injection + '<body', 1)
244
+ else:
245
+ html_content = style_injection + html_content
246
+
247
+ # Save HTML to temp file
248
+ html_file = os.path.join(temp_dir, "input.html")
249
+ with open(html_file, 'w', encoding='utf-8') as f:
250
+ f.write(html_content)
251
+
252
+ # Get puppeteer script path
253
+ script_dir = os.path.dirname(os.path.abspath(__file__))
254
+ puppeteer_script = os.path.join(script_dir, 'puppeteer_pdf.js')
255
+
256
+ # Run conversion
257
+ result = subprocess.run(
258
+ ['node', puppeteer_script, html_file, aspect_ratio],
259
+ capture_output=True,
260
+ text=True,
261
+ timeout=60,
262
+ cwd=script_dir
263
+ )
264
+
265
+ if result.returncode != 0:
266
+ raise Exception(f"PDF conversion failed: {result.stderr}")
267
+
268
+ pdf_file = html_file.replace('.html', '.pdf')
269
+
270
+ if not os.path.exists(pdf_file):
271
+ raise Exception("PDF file was not generated")
272
+
273
+ with open(pdf_file, 'rb') as f:
274
+ pdf_bytes = f.read()
275
+
276
+ shutil.rmtree(temp_dir, ignore_errors=True)
277
+ return pdf_bytes
278
+
279
+ except Exception as e:
280
+ if temp_dir:
281
+ shutil.rmtree(temp_dir, ignore_errors=True)
282
+ raise e
283
+
284
+ @app.get("/")
285
+ async def root():
286
+ """API root endpoint"""
287
+ return {
288
+ "message": "HTML to PDF Conversion API",
289
+ "version": "1.0.0",
290
+ "endpoints": {
291
+ "POST /convert": "Convert HTML to PDF (file upload)",
292
+ "POST /convert-text": "Convert HTML text to PDF",
293
+ "GET /health": "Health check"
294
+ }
295
+ }
296
+
297
+ @app.get("/health")
298
+ async def health_check():
299
+ """Health check endpoint"""
300
+ return {"status": "healthy", "service": "html-to-pdf-api"}
301
+
302
+ @app.post("/convert")
303
+ async def convert_file(
304
+ file: UploadFile = File(...),
305
+ aspect_ratio: str = Form(default="9:16")
306
+ ):
307
+ """
308
+ Convert uploaded HTML file to PDF
309
+
310
+ - **file**: HTML file to convert
311
+ - **aspect_ratio**: Page orientation (16:9, 1:1, or 9:16)
312
+ """
313
+ if not file.filename.lower().endswith(('.html', '.htm')):
314
+ raise HTTPException(status_code=400, detail="File must be HTML (.html or .htm)")
315
+
316
+ if aspect_ratio not in ["16:9", "1:1", "9:16"]:
317
+ raise HTTPException(status_code=400, detail="Invalid aspect ratio. Use: 16:9, 1:1, or 9:16")
318
+
319
+ try:
320
+ # Read HTML content
321
+ content = await file.read()
322
+ try:
323
+ html_content = content.decode('utf-8')
324
+ except UnicodeDecodeError:
325
+ html_content = content.decode('latin-1')
326
+
327
+ # Convert to PDF
328
+ pdf_bytes = convert_html_to_pdf(html_content, aspect_ratio)
329
+
330
+ # Return PDF file
331
+ return Response(
332
+ content=pdf_bytes,
333
+ media_type="application/pdf",
334
+ headers={
335
+ "Content-Disposition": f"attachment; filename={file.filename.replace('.html', '.pdf').replace('.htm', '.pdf')}"
336
+ }
337
+ )
338
+
339
+ except Exception as e:
340
+ raise HTTPException(status_code=500, detail=f"Conversion failed: {str(e)}")
341
+
342
+ @app.post("/convert-text")
343
+ async def convert_text(
344
+ html: str = Form(...),
345
+ aspect_ratio: str = Form(default="9:16"),
346
+ return_base64: bool = Form(default=False)
347
+ ):
348
+ """
349
+ Convert HTML text to PDF
350
+
351
+ - **html**: HTML content as string
352
+ - **aspect_ratio**: Page orientation (16:9, 1:1, or 9:16)
353
+ - **return_base64**: If true, returns base64 encoded PDF in JSON
354
+ """
355
+ if aspect_ratio not in ["16:9", "1:1", "9:16"]:
356
+ raise HTTPException(status_code=400, detail="Invalid aspect ratio. Use: 16:9, 1:1, or 9:16")
357
+
358
+ try:
359
+ # Convert to PDF
360
+ pdf_bytes = convert_html_to_pdf(html, aspect_ratio)
361
+
362
+ if return_base64:
363
+ # Return as JSON with base64 encoded PDF
364
+ pdf_base64 = base64.b64encode(pdf_bytes).decode('utf-8')
365
+ return JSONResponse(content={
366
+ "success": True,
367
+ "pdf_base64": pdf_base64,
368
+ "size_bytes": len(pdf_bytes)
369
+ })
370
+ else:
371
+ # Return PDF file directly
372
+ return Response(
373
+ content=pdf_bytes,
374
+ media_type="application/pdf",
375
+ headers={
376
+ "Content-Disposition": "attachment; filename=converted.pdf"
377
+ }
378
+ )
379
+
380
+ except Exception as e:
381
+ raise HTTPException(status_code=500, detail=f"Conversion failed: {str(e)}")
382
+
383
+ if __name__ == "__main__":
384
+ import uvicorn
385
+ uvicorn.run(app, host="0.0.0.0", port=7860)
src/api_app.py DELETED
@@ -1,558 +0,0 @@
1
- import streamlit as st
2
- from fastapi import FastAPI, HTTPException
3
- from fastapi.responses import Response
4
- from fastapi.middleware.cors import CORSMiddleware
5
- from pydantic import BaseModel
6
- from typing import Optional
7
- import subprocess
8
- import os
9
- import tempfile
10
- import shutil
11
- import base64
12
- import re
13
- import uvicorn
14
- from threading import Thread
15
-
16
- # ==================== FastAPI Setup ====================
17
- api = FastAPI(
18
- title="HTML to PDF Converter API",
19
- description="Convert HTML to PDF using Puppeteer",
20
- version="1.0.0"
21
- )
22
-
23
- api.add_middleware(
24
- CORSMiddleware,
25
- allow_origins=["*"],
26
- allow_credentials=True,
27
- allow_methods=["*"],
28
- allow_headers=["*"],
29
- )
30
-
31
- class HTMLRequest(BaseModel):
32
- html: str
33
- aspect_ratio: Optional[str] = "auto"
34
- return_base64: Optional[bool] = False
35
-
36
- # ==================== Shared Functions ====================
37
- def detect_aspect_ratio(html_content):
38
- """Detect aspect ratio from HTML content"""
39
- viewport_match = re.search(r'<meta[^>]*viewport[^>]*content=["\']([^"\']*)["\']', html_content, re.IGNORECASE)
40
- if viewport_match:
41
- viewport = viewport_match.group(1).lower()
42
- if 'width=device-width' in viewport or 'width=100%' in viewport:
43
- if 'orientation=portrait' in viewport:
44
- return "9:16"
45
- elif 'orientation=landscape' in viewport:
46
- return "16:9"
47
-
48
- aspect_match = re.search(r'aspect-ratio\s*:\s*(\d+)\s*/\s*(\d+)', html_content, re.IGNORECASE)
49
- if aspect_match:
50
- width = int(aspect_match.group(1))
51
- height = int(aspect_match.group(2))
52
- ratio = width / height
53
- if ratio > 1.5:
54
- return "16:9"
55
- elif ratio < 0.7:
56
- return "9:16"
57
- else:
58
- return "1:1"
59
-
60
- if any(keyword in html_content.lower() for keyword in ['reveal.js', 'impress.js', 'slide', 'presentation']):
61
- return "16:9"
62
-
63
- body_match = re.search(r'<body[^>]*style=["\']([^"\']*)["\']', html_content, re.IGNORECASE)
64
- if body_match:
65
- style = body_match.group(1).lower()
66
- if 'width' in style and 'height' in style:
67
- width_match = re.search(r'width\s*:\s*(\d+)', style)
68
- height_match = re.search(r'height\s*:\s*(\d+)', style)
69
- if width_match and height_match:
70
- w = int(width_match.group(1))
71
- h = int(height_match.group(1))
72
- ratio = w / h
73
- if ratio > 1.5:
74
- return "16:9"
75
- elif ratio < 0.7:
76
- return "9:16"
77
-
78
- return "9:16"
79
-
80
- def convert_html_to_pdf(html_content, aspect_ratio):
81
- """Convert HTML content to PDF using Puppeteer"""
82
- temp_dir = None
83
- try:
84
- temp_dir = tempfile.mkdtemp()
85
-
86
- if aspect_ratio == "auto":
87
- aspect_ratio = detect_aspect_ratio(html_content)
88
-
89
- if aspect_ratio not in ["16:9", "1:1", "9:16"]:
90
- aspect_ratio = "9:16"
91
-
92
- style_injection = """
93
- <style>
94
- @page {
95
- margin: 0;
96
- }
97
- * {
98
- -webkit-print-color-adjust: exact !important;
99
- print-color-adjust: exact !important;
100
- color-adjust: exact !important;
101
- }
102
- body {
103
- -webkit-print-color-adjust: exact !important;
104
- print-color-adjust: exact !important;
105
- }
106
- </style>
107
- """
108
-
109
- if '</head>' in html_content:
110
- html_content = html_content.replace('</head>', style_injection + '</head>')
111
- elif '<body' in html_content:
112
- html_content = html_content.replace('<body', style_injection + '<body', 1)
113
- else:
114
- html_content = style_injection + html_content
115
-
116
- html_file = os.path.join(temp_dir, "input.html")
117
- with open(html_file, 'w', encoding='utf-8') as f:
118
- f.write(html_content)
119
-
120
- script_dir = os.path.dirname(os.path.abspath(__file__))
121
- puppeteer_script = os.path.join(os.path.dirname(script_dir), 'puppeteer_pdf.js')
122
-
123
- result = subprocess.run(
124
- ['node', puppeteer_script, html_file, aspect_ratio],
125
- capture_output=True,
126
- text=True,
127
- timeout=60,
128
- cwd=os.path.dirname(script_dir)
129
- )
130
-
131
- if result.returncode != 0:
132
- return None, f"PDF conversion failed: {result.stderr}"
133
-
134
- pdf_file = html_file.replace('.html', '.pdf')
135
-
136
- if not os.path.exists(pdf_file):
137
- return None, "PDF file was not generated"
138
-
139
- with open(pdf_file, 'rb') as f:
140
- pdf_bytes = f.read()
141
-
142
- shutil.rmtree(temp_dir, ignore_errors=True)
143
-
144
- return pdf_bytes, None
145
-
146
- except subprocess.TimeoutExpired:
147
- if temp_dir:
148
- shutil.rmtree(temp_dir, ignore_errors=True)
149
- return None, "Error: PDF conversion timed out (60 seconds)"
150
- except Exception as e:
151
- if temp_dir:
152
- shutil.rmtree(temp_dir, ignore_errors=True)
153
- return None, f"Error: {str(e)}"
154
-
155
- # ==================== FastAPI Endpoints ====================
156
- @api.get("/")
157
- async def root():
158
- return {
159
- "message": "HTML to PDF Converter API",
160
- "version": "1.0.0",
161
- "endpoints": {
162
- "POST /api/convert": "Convert HTML to PDF",
163
- "GET /api/health": "Health check"
164
- },
165
- "docs": "/docs"
166
- }
167
-
168
- @api.get("/api/health")
169
- async def health():
170
- return {"status": "healthy", "service": "html-to-pdf"}
171
-
172
- @api.post("/api/convert")
173
- async def convert_html(request: HTMLRequest):
174
- """Convert HTML to PDF via API"""
175
- try:
176
- pdf_bytes, error = convert_html_to_pdf(request.html, request.aspect_ratio)
177
-
178
- if error:
179
- raise HTTPException(status_code=500, detail=error)
180
-
181
- if request.return_base64:
182
- pdf_base64 = base64.b64encode(pdf_bytes).decode('utf-8')
183
- return {
184
- "success": True,
185
- "pdf_base64": pdf_base64,
186
- "size": len(pdf_bytes),
187
- "aspect_ratio": request.aspect_ratio
188
- }
189
- else:
190
- return Response(
191
- content=pdf_bytes,
192
- media_type="application/pdf",
193
- headers={
194
- "Content-Disposition": "attachment; filename=converted.pdf"
195
- }
196
- )
197
- except Exception as e:
198
- raise HTTPException(status_code=500, detail=str(e))
199
-
200
- # ==================== Streamlit UI ====================
201
- def render_html_preview(html_content):
202
- b64 = base64.b64encode(html_content.encode()).decode()
203
- iframe_html = f'<iframe src="data:text/html;base64,{b64}" width="100%" height="600" style="border: 2px solid #ddd; border-radius: 5px;"></iframe>'
204
- return iframe_html
205
-
206
- def render_pdf_preview(pdf_bytes):
207
- b64 = base64.b64encode(pdf_bytes).decode()
208
- pdf_display = f'<iframe src="data:application/pdf;base64,{b64}" width="100%" height="600" style="border: 2px solid #ddd; border-radius: 5px;" type="application/pdf"></iframe>'
209
- return pdf_display
210
-
211
- # Start FastAPI in background
212
- def run_api():
213
- uvicorn.run(api, host="0.0.0.0", port=7860, log_level="info")
214
-
215
- # Start API server in a separate thread
216
- if 'api_started' not in st.session_state:
217
- api_thread = Thread(target=run_api, daemon=True)
218
- api_thread.start()
219
- st.session_state.api_started = True
220
-
221
- # Streamlit Configuration
222
- st.set_page_config(
223
- page_title="HTML to PDF Converter",
224
- page_icon="📄",
225
- layout="wide"
226
- )
227
-
228
- # Page header
229
- st.title("📄 HTML to PDF Converter")
230
- st.markdown("""
231
- Convert HTML files or HTML code to PDF using Puppeteer with automatic aspect ratio detection.
232
- Preserves styles, fonts, colors, and layout.
233
- """)
234
-
235
- # API Info Banner
236
- st.info("""
237
- 🚀 **API Mode Enabled!** You can now use this service via REST API:
238
- - **Endpoint**: `POST /api/convert`
239
- - **Docs**: [/docs](/docs) for interactive API documentation
240
- - **Health Check**: `GET /api/health`
241
- """)
242
-
243
- # Create tabs
244
- tab1, tab2, tab3 = st.tabs(["📤 Upload HTML File", "📝 Paste HTML Code", "🔌 API Documentation"])
245
-
246
- # Tab 1: Upload HTML File
247
- with tab1:
248
- uploaded_file = st.file_uploader(
249
- "Choose an HTML file",
250
- type=['html', 'htm'],
251
- key="file_uploader",
252
- help="Upload an HTML file (max 200MB)",
253
- accept_multiple_files=False
254
- )
255
-
256
- if uploaded_file is not None:
257
- st.success(f"✅ File uploaded: {uploaded_file.name} ({uploaded_file.size:,} bytes)")
258
-
259
- uploaded_file.seek(0)
260
- try:
261
- html_content = uploaded_file.getvalue().decode('utf-8')
262
- except UnicodeDecodeError:
263
- uploaded_file.seek(0)
264
- html_content = uploaded_file.getvalue().decode('latin-1')
265
-
266
- detected_ratio = detect_aspect_ratio(html_content)
267
-
268
- col1, col2 = st.columns([1, 1])
269
-
270
- with col1:
271
- st.subheader("⚙️ Settings")
272
-
273
- auto_detect = st.checkbox("Auto-detect aspect ratio", value=True, key="auto_detect_file")
274
-
275
- if auto_detect:
276
- aspect_ratio_file = detected_ratio
277
- st.info(f"🔍 Detected: **{detected_ratio}**")
278
- else:
279
- aspect_ratio_file = st.radio(
280
- "Aspect Ratio",
281
- options=["16:9", "1:1", "9:16"],
282
- index=["16:9", "1:1", "9:16"].index(detected_ratio),
283
- key="aspect_file",
284
- help="Select the page orientation and dimensions"
285
- )
286
-
287
- st.markdown(f"""
288
- **Selected: {aspect_ratio_file}**
289
- - 16:9 = Landscape (297mm × 210mm)
290
- - 1:1 = Square (210mm × 210mm)
291
- - 9:16 = Portrait (210mm × 297mm)
292
- """)
293
-
294
- convert_file_btn = st.button("🔄 Convert to PDF", key="convert_file", type="primary", use_container_width=True)
295
-
296
- with col2:
297
- st.subheader("👁️ HTML Preview")
298
- with st.expander("Show HTML Preview", expanded=False):
299
- st.components.v1.html(render_html_preview(html_content), height=600, scrolling=True)
300
-
301
- if convert_file_btn:
302
- with st.spinner("Converting HTML to PDF..."):
303
- pdf_bytes, error = convert_html_to_pdf(html_content, aspect_ratio_file)
304
-
305
- if error:
306
- st.error(f"❌ {error}")
307
- with st.expander("Show error details"):
308
- st.code(error)
309
- else:
310
- st.success("✅ PDF generated successfully!")
311
-
312
- col_a, col_b = st.columns([1, 1])
313
-
314
- with col_a:
315
- output_filename = uploaded_file.name.replace('.html', '.pdf').replace('.htm', '.pdf')
316
- if not output_filename.endswith('.pdf'):
317
- output_filename += '.pdf'
318
-
319
- st.download_button(
320
- label="⬇️ Download PDF",
321
- data=pdf_bytes,
322
- file_name=output_filename,
323
- mime="application/pdf",
324
- use_container_width=True,
325
- key="download_file_pdf"
326
- )
327
-
328
- with col_b:
329
- st.info(f"📦 Size: {len(pdf_bytes):,} bytes")
330
-
331
- st.subheader("📄 PDF Preview")
332
- st.components.v1.html(render_pdf_preview(pdf_bytes), height=600, scrolling=True)
333
-
334
- # Tab 2: Paste HTML Code
335
- with tab2:
336
- col1, col2 = st.columns([1, 1])
337
-
338
- with col1:
339
- html_code = st.text_area(
340
- "HTML Content",
341
- value="""<!DOCTYPE html>
342
- <html>
343
- <head>
344
- <title>Sample Document</title>
345
- <style>
346
- body {
347
- font-family: Arial, sans-serif;
348
- margin: 40px;
349
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
350
- color: white;
351
- }
352
- h1 {
353
- font-size: 48px;
354
- margin-bottom: 20px;
355
- text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
356
- }
357
- p {
358
- font-size: 18px;
359
- line-height: 1.6;
360
- }
361
- .box {
362
- background: rgba(255,255,255,0.1);
363
- padding: 20px;
364
- border-radius: 10px;
365
- margin-top: 20px;
366
- }
367
- </style>
368
- </head>
369
- <body>
370
- <h1>Hello, PDF World! 🌍</h1>
371
- <p>This is a sample HTML document converted to PDF.</p>
372
- <div class="box">
373
- <p>✨ Styles, colors, and gradients are preserved!</p>
374
- </div>
375
- </body>
376
- </html>""",
377
- height=400,
378
- key="html_code"
379
- )
380
-
381
- if html_code and html_code.strip():
382
- detected_ratio_text = detect_aspect_ratio(html_code)
383
-
384
- auto_detect_text = st.checkbox("Auto-detect aspect ratio", value=True, key="auto_detect_text")
385
-
386
- if auto_detect_text:
387
- aspect_ratio_text = detected_ratio_text
388
- st.info(f"🔍 Detected: **{detected_ratio_text}**")
389
- else:
390
- aspect_ratio_text = st.radio(
391
- "Aspect Ratio",
392
- options=["16:9", "1:1", "9:16"],
393
- index=["16:9", "1:1", "9:16"].index(detected_ratio_text),
394
- key="aspect_text",
395
- help="Select the page orientation and dimensions"
396
- )
397
-
398
- convert_text_btn = st.button("🔄 Convert to PDF", key="convert_text", type="primary", use_container_width=True)
399
- else:
400
- convert_text_btn = False
401
-
402
- with col2:
403
- if html_code and html_code.strip():
404
- st.subheader("👁️ HTML Preview")
405
- with st.expander("Show HTML Preview", expanded=False):
406
- st.components.v1.html(render_html_preview(html_code), height=600, scrolling=True)
407
-
408
- if convert_text_btn and html_code and html_code.strip():
409
- with st.spinner("Converting HTML to PDF..."):
410
- pdf_bytes, error = convert_html_to_pdf(html_code, aspect_ratio_text)
411
-
412
- if error:
413
- st.error(f"❌ {error}")
414
- with st.expander("Show error details"):
415
- st.code(error)
416
- else:
417
- st.success("✅ PDF generated successfully!")
418
-
419
- col_a, col_b = st.columns([1, 1])
420
-
421
- with col_a:
422
- st.download_button(
423
- label="⬇️ Download PDF",
424
- data=pdf_bytes,
425
- file_name="converted.pdf",
426
- mime="application/pdf",
427
- use_container_width=True,
428
- key="download_text_pdf"
429
- )
430
-
431
- with col_b:
432
- st.info(f"📦 Size: {len(pdf_bytes):,} bytes")
433
-
434
- st.subheader("📄 PDF Preview")
435
- st.components.v1.html(render_pdf_preview(pdf_bytes), height=600, scrolling=True)
436
-
437
- # Tab 3: API Documentation
438
- with tab3:
439
- st.header("🔌 API Documentation")
440
-
441
- st.markdown("""
442
- ### Base URL
443
- ```
444
- https://your-space-name.hf.space
445
- ```
446
-
447
- ### Endpoints
448
-
449
- #### 1. Health Check
450
- ```http
451
- GET /api/health
452
- ```
453
-
454
- **Response:**
455
- ```json
456
- {
457
- "status": "healthy",
458
- "service": "html-to-pdf"
459
- }
460
- ```
461
-
462
- #### 2. Convert HTML to PDF
463
- ```http
464
- POST /api/convert
465
- Content-Type: application/json
466
- ```
467
-
468
- **Request Body:**
469
- ```json
470
- {
471
- "html": "<html><body><h1>Hello World</h1></body></html>",
472
- "aspect_ratio": "auto",
473
- "return_base64": false
474
- }
475
- ```
476
-
477
- **Parameters:**
478
- - `html` (required): HTML content as string
479
- - `aspect_ratio` (optional): "auto", "16:9", "1:1", or "9:16" (default: "auto")
480
- - `return_base64` (optional): Return PDF as base64 string (default: false)
481
-
482
- **Response (return_base64=false):**
483
- - Returns PDF file directly with `Content-Type: application/pdf`
484
-
485
- **Response (return_base64=true):**
486
- ```json
487
- {
488
- "success": true,
489
- "pdf_base64": "JVBERi0xLjQKJeLjz9...",
490
- "size": 12345,
491
- "aspect_ratio": "9:16"
492
- }
493
- ```
494
-
495
- ### Example Usage
496
-
497
- #### Python
498
- ```python
499
- import requests
500
-
501
- url = "https://your-space-name.hf.space/api/convert"
502
-
503
- payload = {
504
- "html": "<html><body><h1>Test PDF</h1></body></html>",
505
- "aspect_ratio": "auto",
506
- "return_base64": false
507
- }
508
-
509
- response = requests.post(url, json=payload)
510
-
511
- # Save PDF
512
- with open("output.pdf", "wb") as f:
513
- f.write(response.content)
514
- ```
515
-
516
- #### JavaScript
517
- ```javascript
518
- const response = await fetch('https://your-space-name.hf.space/api/convert', {
519
- method: 'POST',
520
- headers: {
521
- 'Content-Type': 'application/json',
522
- },
523
- body: JSON.stringify({
524
- html: '<html><body><h1>Test PDF</h1></body></html>',
525
- aspect_ratio: 'auto',
526
- return_base64: false
527
- })
528
- });
529
-
530
- const blob = await response.blob();
531
- // Download or use the PDF blob
532
- ```
533
-
534
- #### cURL
535
- ```bash
536
- curl -X POST "https://your-space-name.hf.space/api/convert" \\
537
- -H "Content-Type: application/json" \\
538
- -d '{"html":"<html><body><h1>Test</h1></body></html>","aspect_ratio":"auto"}' \\
539
- --output output.pdf
540
- ```
541
- """)
542
-
543
- # Footer
544
- st.markdown("---")
545
- st.markdown("""
546
- ### 💡 Tips:
547
- - **Auto-detection** analyzes your HTML to suggest the best aspect ratio
548
- - **16:9** - Best for presentations and landscape documents (297mm × 210mm)
549
- - **1:1** - Square format (210mm × 210mm)
550
- - **9:16** - Portrait format, standard A4 (210mm × 297mm)
551
- - All CSS styles, colors, gradients, and fonts are preserved
552
- - Use inline CSS or `<style>` tags for best results
553
- - External resources should use absolute URLs
554
-
555
- ### 🔗 API Access
556
- - **Interactive Docs**: Visit `/docs` for Swagger UI
557
- - **Alternative Docs**: Visit `/redoc` for ReDoc UI
558
- """)