"""
đĻ 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)