""" đŸĻŠ Sisi PDF API - Universal HTML to PDF Converter Perfect support for Chinese (Simplified & Traditional), Emoji, SVG, and modern CSS Powered by Playwright + Chromium 🔒 Protected with API Key Authentication """ from fastapi import FastAPI, HTTPException, Header, Depends from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import Response from pydantic import BaseModel from playwright.async_api import async_playwright from typing import Optional import logging import base64 import asyncio import os logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # 🔐 API Key Configuration API_KEY = os.environ.get("API_KEY", "sisi_default_key_change_me") def verify_api_key(x_api_key: Optional[str] = Header(None)): """Verify API key from request header""" if x_api_key != API_KEY: raise HTTPException( status_code=403, detail="🔒 Invalid or missing API key. Please contact SisiTheFox for access." ) return x_api_key app = FastAPI( title="Sisi PDF API", description="Universal HTML to PDF converter with perfect Chinese, Emoji, and SVG support", version="1.0.0" ) # CORS middleware for all origins app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) class PDFRequest(BaseModel): """PDF generation request""" html_content: str # Full HTML content to convert filename: Optional[str] = "document.pdf" page_size: Optional[str] = "A4" # A4, Letter, A3, etc. margin_top: Optional[str] = "20mm" margin_bottom: Optional[str] = "20mm" margin_left: Optional[str] = "20mm" margin_right: Optional[str] = "20mm" print_background: Optional[bool] = True landscape: Optional[bool] = False @app.get("/") def root(): """Health check""" return { "status": "online", "service": "Sisi PDF API", "version": "1.0.0", "features": [ "Perfect Chinese support (Simplified & Traditional)", "Colorful emoji rendering", "SVG graphics (100% accurate)", "Modern CSS (Flexbox, Grid, Gradients)", "Powered by Chromium" ] } @app.get("/health") @app.head("/health") def health_check(): """Health check endpoint for monitoring""" return { "status": "healthy", "service": "Sisi PDF API", "version": "1.0.0" } @app.post("/generate-pdf") async def generate_pdf(request: PDFRequest, api_key: str = Depends(verify_api_key)): """ Generate PDF from HTML content 🔒 Requires API Key authentication Perfect support for: - Chinese characters (įŽ€äŊ“ä¸­æ–‡ã€įšéĢ”ä¸­æ–‡) - Emoji (đŸĻŠđŸ’•âœ¨đŸŽ‰) - SVG graphics - Modern CSS3 """ try: logger.info(f"📄 Generating PDF: {request.filename}") async with async_playwright() as p: # Launch Chromium browser browser = await p.chromium.launch( headless=True, args=[ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu' ] ) # Create new page page = await browser.new_page() # Set content await page.set_content(request.html_content, wait_until='networkidle') # Generate PDF pdf_bytes = await page.pdf( format=request.page_size, print_background=request.print_background, landscape=request.landscape, margin={ 'top': request.margin_top, 'bottom': request.margin_bottom, 'left': request.margin_left, 'right': request.margin_right } ) await browser.close() logger.info(f"✅ PDF generated: {len(pdf_bytes)} bytes") return Response( content=pdf_bytes, media_type="application/pdf", headers={ "Content-Disposition": f'attachment; filename="{request.filename}"' } ) except Exception as e: logger.error(f"❌ PDF generation error: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) @app.post("/generate-pdf-base64") async def generate_pdf_base64(request: PDFRequest, api_key: str = Depends(verify_api_key)): """ Generate PDF and return as base64 string 🔒 Requires API Key authentication Useful for embedding in JSON responses """ try: logger.info(f"📄 Generating PDF (base64): {request.filename}") async with async_playwright() as p: browser = await p.chromium.launch( headless=True, args=[ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu' ] ) page = await browser.new_page() await page.set_content(request.html_content, wait_until='networkidle') pdf_bytes = await page.pdf( format=request.page_size, print_background=request.print_background, landscape=request.landscape, margin={ 'top': request.margin_top, 'bottom': request.margin_bottom, 'left': request.margin_left, 'right': request.margin_right } ) await browser.close() # Convert to base64 pdf_base64 = base64.b64encode(pdf_bytes).decode('utf-8') logger.info(f"✅ PDF generated (base64): {len(pdf_bytes)} bytes") return { "success": True, "filename": request.filename, "pdf_base64": pdf_base64, "size_bytes": len(pdf_bytes) } except Exception as e: logger.error(f"❌ PDF generation error: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=7860)