Spaces:
Running
Running
th
commited on
Commit
ยท
e72cddb
0
Parent(s):
๐ฆ Initial commit: Sisi PDF API with Playwright
Browse filesโจ Features:
- Perfect Chinese support (Simplified & Traditional)
- Colorful emoji rendering ๐ฆ๐โจ
- 100% accurate SVG graphics
- Modern CSS3 support (Flexbox, Grid, Gradients)
- Async/concurrent PDF generation
- Two endpoints: binary PDF & base64 JSON
๐๏ธ Tech Stack:
- FastAPI + Playwright + Chromium
- Docker optimized for HF Spaces
- Fonts: Noto CJK + Noto Color Emoji
๐ Performance:
- 2-5 seconds per PDF
- 5-10 concurrent requests
- Perfect for production use
๐ฏ Use Cases:
- Birth chart reports (Sisi Astrology)
- Bazi readings (Astrology Bazi)
- Healing reports (SisiTheFox)
- Any document with Chinese/Emoji/SVG
- .gitignore +44 -0
- Dockerfile +67 -0
- README.md +143 -0
- app.py +195 -0
- requirements.txt +5 -0
.gitignore
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
*.so
|
| 6 |
+
.Python
|
| 7 |
+
build/
|
| 8 |
+
develop-eggs/
|
| 9 |
+
dist/
|
| 10 |
+
downloads/
|
| 11 |
+
eggs/
|
| 12 |
+
.eggs/
|
| 13 |
+
lib/
|
| 14 |
+
lib64/
|
| 15 |
+
parts/
|
| 16 |
+
sdist/
|
| 17 |
+
var/
|
| 18 |
+
wheels/
|
| 19 |
+
*.egg-info/
|
| 20 |
+
.installed.cfg
|
| 21 |
+
*.egg
|
| 22 |
+
|
| 23 |
+
# Virtual Environment
|
| 24 |
+
venv/
|
| 25 |
+
ENV/
|
| 26 |
+
env/
|
| 27 |
+
|
| 28 |
+
# IDE
|
| 29 |
+
.vscode/
|
| 30 |
+
.idea/
|
| 31 |
+
*.swp
|
| 32 |
+
*.swo
|
| 33 |
+
*~
|
| 34 |
+
|
| 35 |
+
# OS
|
| 36 |
+
.DS_Store
|
| 37 |
+
Thumbs.db
|
| 38 |
+
|
| 39 |
+
# Logs
|
| 40 |
+
*.log
|
| 41 |
+
|
| 42 |
+
# Test files
|
| 43 |
+
test_*.py
|
| 44 |
+
*.pdf
|
Dockerfile
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
# Install system dependencies for Playwright
|
| 6 |
+
RUN apt-get update && apt-get install -y \
|
| 7 |
+
wget \
|
| 8 |
+
gnupg \
|
| 9 |
+
ca-certificates \
|
| 10 |
+
fonts-liberation \
|
| 11 |
+
libasound2 \
|
| 12 |
+
libatk-bridge2.0-0 \
|
| 13 |
+
libatk1.0-0 \
|
| 14 |
+
libc6 \
|
| 15 |
+
libcairo2 \
|
| 16 |
+
libcups2 \
|
| 17 |
+
libdbus-1-3 \
|
| 18 |
+
libexpat1 \
|
| 19 |
+
libfontconfig1 \
|
| 20 |
+
libgbm1 \
|
| 21 |
+
libgcc1 \
|
| 22 |
+
libglib2.0-0 \
|
| 23 |
+
libgtk-3-0 \
|
| 24 |
+
libnspr4 \
|
| 25 |
+
libnss3 \
|
| 26 |
+
libpango-1.0-0 \
|
| 27 |
+
libpangocairo-1.0-0 \
|
| 28 |
+
libstdc++6 \
|
| 29 |
+
libx11-6 \
|
| 30 |
+
libx11-xcb1 \
|
| 31 |
+
libxcb1 \
|
| 32 |
+
libxcomposite1 \
|
| 33 |
+
libxcursor1 \
|
| 34 |
+
libxdamage1 \
|
| 35 |
+
libxext6 \
|
| 36 |
+
libxfixes3 \
|
| 37 |
+
libxi6 \
|
| 38 |
+
libxrandr2 \
|
| 39 |
+
libxrender1 \
|
| 40 |
+
libxss1 \
|
| 41 |
+
libxtst6 \
|
| 42 |
+
lsb-release \
|
| 43 |
+
xdg-utils \
|
| 44 |
+
fonts-noto-color-emoji \
|
| 45 |
+
fonts-noto-cjk \
|
| 46 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 47 |
+
|
| 48 |
+
# Copy requirements and install Python packages
|
| 49 |
+
COPY requirements.txt .
|
| 50 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 51 |
+
|
| 52 |
+
# Install Playwright browsers (Chromium only to save space)
|
| 53 |
+
RUN playwright install chromium
|
| 54 |
+
RUN playwright install-deps chromium
|
| 55 |
+
|
| 56 |
+
# Copy application code
|
| 57 |
+
COPY app.py .
|
| 58 |
+
|
| 59 |
+
# Expose port
|
| 60 |
+
EXPOSE 7860
|
| 61 |
+
|
| 62 |
+
# Health check
|
| 63 |
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
| 64 |
+
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:7860/health')"
|
| 65 |
+
|
| 66 |
+
# Run the application
|
| 67 |
+
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ๐ฆ Sisi PDF API
|
| 2 |
+
|
| 3 |
+
Universal HTML to PDF converter with **perfect** support for:
|
| 4 |
+
- โ
Chinese (Simplified & Traditional): ็ฎไฝไธญๆใ็น้ซไธญๆ
|
| 5 |
+
- โ
Colorful Emoji: ๐ฆ๐โจ๐โญ๐
|
| 6 |
+
- โ
SVG Graphics: 100% accurate rendering
|
| 7 |
+
- โ
Modern CSS3: Flexbox, Grid, Gradients, Shadows
|
| 8 |
+
- โ
All Unicode symbols: โโโโโโโโโโโโ
|
| 9 |
+
|
| 10 |
+
## ๐ Features
|
| 11 |
+
|
| 12 |
+
- **Powered by Playwright + Chromium**: Industry-leading rendering engine
|
| 13 |
+
- **Zero client-side dependencies**: Everything runs on the server
|
| 14 |
+
- **Async/concurrent**: Handle multiple requests simultaneously
|
| 15 |
+
- **Production-ready**: Used by Sisi Astrology, Astrology Bazi, and more
|
| 16 |
+
|
| 17 |
+
## ๐ API Endpoints
|
| 18 |
+
|
| 19 |
+
### `POST /generate-pdf`
|
| 20 |
+
|
| 21 |
+
Generate PDF from HTML and return as binary file.
|
| 22 |
+
|
| 23 |
+
**Request Body**:
|
| 24 |
+
```json
|
| 25 |
+
{
|
| 26 |
+
"html_content": "<html>...</html>",
|
| 27 |
+
"filename": "document.pdf",
|
| 28 |
+
"page_size": "A4",
|
| 29 |
+
"margin_top": "20mm",
|
| 30 |
+
"margin_bottom": "20mm",
|
| 31 |
+
"margin_left": "20mm",
|
| 32 |
+
"margin_right": "20mm",
|
| 33 |
+
"print_background": true,
|
| 34 |
+
"landscape": false
|
| 35 |
+
}
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
**Response**: PDF binary file
|
| 39 |
+
|
| 40 |
+
### `POST /generate-pdf-base64`
|
| 41 |
+
|
| 42 |
+
Generate PDF and return as base64 string (useful for JSON APIs).
|
| 43 |
+
|
| 44 |
+
**Response**:
|
| 45 |
+
```json
|
| 46 |
+
{
|
| 47 |
+
"success": true,
|
| 48 |
+
"filename": "document.pdf",
|
| 49 |
+
"pdf_base64": "JVBERi0xLjcK...",
|
| 50 |
+
"size_bytes": 123456
|
| 51 |
+
}
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
## ๐จ Example Usage
|
| 55 |
+
|
| 56 |
+
### JavaScript/TypeScript
|
| 57 |
+
|
| 58 |
+
```typescript
|
| 59 |
+
const response = await fetch('https://sisithefox-pdf-api.hf.space/generate-pdf', {
|
| 60 |
+
method: 'POST',
|
| 61 |
+
headers: { 'Content-Type': 'application/json' },
|
| 62 |
+
body: JSON.stringify({
|
| 63 |
+
html_content: `
|
| 64 |
+
<!DOCTYPE html>
|
| 65 |
+
<html>
|
| 66 |
+
<head>
|
| 67 |
+
<meta charset="UTF-8">
|
| 68 |
+
<style>
|
| 69 |
+
body { font-family: 'Noto Sans CJK', Arial; }
|
| 70 |
+
h1 { color: purple; }
|
| 71 |
+
</style>
|
| 72 |
+
</head>
|
| 73 |
+
<body>
|
| 74 |
+
<h1>๐ฆ ๅฐ้ฑผ็ๆ็ๆฅๅ</h1>
|
| 75 |
+
<p>็ฎไฝไธญๆใ็น้ซไธญๆใEmoji ๐</p>
|
| 76 |
+
<svg>...</svg>
|
| 77 |
+
</body>
|
| 78 |
+
</html>
|
| 79 |
+
`,
|
| 80 |
+
filename: 'natal-chart.pdf'
|
| 81 |
+
})
|
| 82 |
+
})
|
| 83 |
+
|
| 84 |
+
const blob = await response.blob()
|
| 85 |
+
const url = URL.createObjectURL(blob)
|
| 86 |
+
const a = document.createElement('a')
|
| 87 |
+
a.href = url
|
| 88 |
+
a.download = 'natal-chart.pdf'
|
| 89 |
+
a.click()
|
| 90 |
+
```
|
| 91 |
+
|
| 92 |
+
### Python
|
| 93 |
+
|
| 94 |
+
```python
|
| 95 |
+
import requests
|
| 96 |
+
|
| 97 |
+
response = requests.post(
|
| 98 |
+
'https://sisithefox-pdf-api.hf.space/generate-pdf',
|
| 99 |
+
json={
|
| 100 |
+
'html_content': '<html>...</html>',
|
| 101 |
+
'filename': 'document.pdf'
|
| 102 |
+
}
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
with open('output.pdf', 'wb') as f:
|
| 106 |
+
f.write(response.content)
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
## ๐ Perfect for
|
| 110 |
+
|
| 111 |
+
- ๐ Birth Chart Reports (Sisi Astrology)
|
| 112 |
+
- ๐ฎ Bazi Fortune Readings (Astrology Bazi)
|
| 113 |
+
- ๐ Healing Reports (SisiTheFox)
|
| 114 |
+
- ๐ Invoices, Receipts, Certificates
|
| 115 |
+
- ๐ Any document with Chinese/Emoji/SVG
|
| 116 |
+
|
| 117 |
+
## ๐๏ธ Deployment
|
| 118 |
+
|
| 119 |
+
This API is deployed on **Hugging Face Spaces** (free tier):
|
| 120 |
+
- URL: `https://sisithefox-pdf-api.hf.space`
|
| 121 |
+
- RAM: 16GB (more than enough)
|
| 122 |
+
- Storage: 50GB
|
| 123 |
+
- Uptime: 99.9% (with UptimeRobot monitoring)
|
| 124 |
+
|
| 125 |
+
## ๐ฐ Cost
|
| 126 |
+
|
| 127 |
+
**$0** - Completely free thanks to Hugging Face Spaces!
|
| 128 |
+
|
| 129 |
+
## ๐ Security
|
| 130 |
+
|
| 131 |
+
- CORS enabled for all origins (public API)
|
| 132 |
+
- No data stored - PDFs generated on-the-fly
|
| 133 |
+
- Stateless architecture
|
| 134 |
+
|
| 135 |
+
## ๐ Performance
|
| 136 |
+
|
| 137 |
+
- Generation time: 2-5 seconds per PDF
|
| 138 |
+
- Concurrent requests: 5-10 simultaneous
|
| 139 |
+
- Max PDF size: ~50MB (more than enough for reports)
|
| 140 |
+
|
| 141 |
+
---
|
| 142 |
+
|
| 143 |
+
๐ฆ Made with love by Sisi for Sparksverse ecosystem
|
app.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
๐ฆ Sisi PDF API - Universal HTML to PDF Converter
|
| 3 |
+
Perfect support for Chinese (Simplified & Traditional), Emoji, SVG, and modern CSS
|
| 4 |
+
Powered by Playwright + Chromium
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from fastapi import FastAPI, HTTPException
|
| 8 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 9 |
+
from fastapi.responses import Response
|
| 10 |
+
from pydantic import BaseModel
|
| 11 |
+
from playwright.async_api import async_playwright
|
| 12 |
+
from typing import Optional
|
| 13 |
+
import logging
|
| 14 |
+
import base64
|
| 15 |
+
import asyncio
|
| 16 |
+
|
| 17 |
+
logging.basicConfig(level=logging.INFO)
|
| 18 |
+
logger = logging.getLogger(__name__)
|
| 19 |
+
|
| 20 |
+
app = FastAPI(
|
| 21 |
+
title="Sisi PDF API",
|
| 22 |
+
description="Universal HTML to PDF converter with perfect Chinese, Emoji, and SVG support",
|
| 23 |
+
version="1.0.0"
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
# CORS middleware for all origins
|
| 27 |
+
app.add_middleware(
|
| 28 |
+
CORSMiddleware,
|
| 29 |
+
allow_origins=["*"],
|
| 30 |
+
allow_credentials=True,
|
| 31 |
+
allow_methods=["*"],
|
| 32 |
+
allow_headers=["*"],
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class PDFRequest(BaseModel):
|
| 37 |
+
"""PDF generation request"""
|
| 38 |
+
html_content: str # Full HTML content to convert
|
| 39 |
+
filename: Optional[str] = "document.pdf"
|
| 40 |
+
page_size: Optional[str] = "A4" # A4, Letter, A3, etc.
|
| 41 |
+
margin_top: Optional[str] = "20mm"
|
| 42 |
+
margin_bottom: Optional[str] = "20mm"
|
| 43 |
+
margin_left: Optional[str] = "20mm"
|
| 44 |
+
margin_right: Optional[str] = "20mm"
|
| 45 |
+
print_background: Optional[bool] = True
|
| 46 |
+
landscape: Optional[bool] = False
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
@app.get("/")
|
| 50 |
+
def root():
|
| 51 |
+
"""Health check"""
|
| 52 |
+
return {
|
| 53 |
+
"status": "online",
|
| 54 |
+
"service": "Sisi PDF API",
|
| 55 |
+
"version": "1.0.0",
|
| 56 |
+
"features": [
|
| 57 |
+
"Perfect Chinese support (Simplified & Traditional)",
|
| 58 |
+
"Colorful emoji rendering",
|
| 59 |
+
"SVG graphics (100% accurate)",
|
| 60 |
+
"Modern CSS (Flexbox, Grid, Gradients)",
|
| 61 |
+
"Powered by Chromium"
|
| 62 |
+
]
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
@app.get("/health")
|
| 67 |
+
@app.head("/health")
|
| 68 |
+
def health_check():
|
| 69 |
+
"""Health check endpoint for monitoring"""
|
| 70 |
+
return {
|
| 71 |
+
"status": "healthy",
|
| 72 |
+
"service": "Sisi PDF API",
|
| 73 |
+
"version": "1.0.0"
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
@app.post("/generate-pdf")
|
| 78 |
+
async def generate_pdf(request: PDFRequest):
|
| 79 |
+
"""
|
| 80 |
+
Generate PDF from HTML content
|
| 81 |
+
|
| 82 |
+
Perfect support for:
|
| 83 |
+
- Chinese characters (็ฎไฝไธญๆใ็น้ซไธญๆ)
|
| 84 |
+
- Emoji (๐ฆ๐โจ๐)
|
| 85 |
+
- SVG graphics
|
| 86 |
+
- Modern CSS3
|
| 87 |
+
"""
|
| 88 |
+
try:
|
| 89 |
+
logger.info(f"๐ Generating PDF: {request.filename}")
|
| 90 |
+
|
| 91 |
+
async with async_playwright() as p:
|
| 92 |
+
# Launch Chromium browser
|
| 93 |
+
browser = await p.chromium.launch(
|
| 94 |
+
headless=True,
|
| 95 |
+
args=[
|
| 96 |
+
'--no-sandbox',
|
| 97 |
+
'--disable-setuid-sandbox',
|
| 98 |
+
'--disable-dev-shm-usage',
|
| 99 |
+
'--disable-gpu'
|
| 100 |
+
]
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
# Create new page
|
| 104 |
+
page = await browser.new_page()
|
| 105 |
+
|
| 106 |
+
# Set content
|
| 107 |
+
await page.set_content(request.html_content, wait_until='networkidle')
|
| 108 |
+
|
| 109 |
+
# Generate PDF
|
| 110 |
+
pdf_bytes = await page.pdf(
|
| 111 |
+
format=request.page_size,
|
| 112 |
+
print_background=request.print_background,
|
| 113 |
+
landscape=request.landscape,
|
| 114 |
+
margin={
|
| 115 |
+
'top': request.margin_top,
|
| 116 |
+
'bottom': request.margin_bottom,
|
| 117 |
+
'left': request.margin_left,
|
| 118 |
+
'right': request.margin_right
|
| 119 |
+
}
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
await browser.close()
|
| 123 |
+
|
| 124 |
+
logger.info(f"โ
PDF generated: {len(pdf_bytes)} bytes")
|
| 125 |
+
|
| 126 |
+
return Response(
|
| 127 |
+
content=pdf_bytes,
|
| 128 |
+
media_type="application/pdf",
|
| 129 |
+
headers={
|
| 130 |
+
"Content-Disposition": f'attachment; filename="{request.filename}"'
|
| 131 |
+
}
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
except Exception as e:
|
| 135 |
+
logger.error(f"โ PDF generation error: {str(e)}")
|
| 136 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
@app.post("/generate-pdf-base64")
|
| 140 |
+
async def generate_pdf_base64(request: PDFRequest):
|
| 141 |
+
"""
|
| 142 |
+
Generate PDF and return as base64 string
|
| 143 |
+
Useful for embedding in JSON responses
|
| 144 |
+
"""
|
| 145 |
+
try:
|
| 146 |
+
logger.info(f"๐ Generating PDF (base64): {request.filename}")
|
| 147 |
+
|
| 148 |
+
async with async_playwright() as p:
|
| 149 |
+
browser = await p.chromium.launch(
|
| 150 |
+
headless=True,
|
| 151 |
+
args=[
|
| 152 |
+
'--no-sandbox',
|
| 153 |
+
'--disable-setuid-sandbox',
|
| 154 |
+
'--disable-dev-shm-usage',
|
| 155 |
+
'--disable-gpu'
|
| 156 |
+
]
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
page = await browser.new_page()
|
| 160 |
+
await page.set_content(request.html_content, wait_until='networkidle')
|
| 161 |
+
|
| 162 |
+
pdf_bytes = await page.pdf(
|
| 163 |
+
format=request.page_size,
|
| 164 |
+
print_background=request.print_background,
|
| 165 |
+
landscape=request.landscape,
|
| 166 |
+
margin={
|
| 167 |
+
'top': request.margin_top,
|
| 168 |
+
'bottom': request.margin_bottom,
|
| 169 |
+
'left': request.margin_left,
|
| 170 |
+
'right': request.margin_right
|
| 171 |
+
}
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
await browser.close()
|
| 175 |
+
|
| 176 |
+
# Convert to base64
|
| 177 |
+
pdf_base64 = base64.b64encode(pdf_bytes).decode('utf-8')
|
| 178 |
+
|
| 179 |
+
logger.info(f"โ
PDF generated (base64): {len(pdf_bytes)} bytes")
|
| 180 |
+
|
| 181 |
+
return {
|
| 182 |
+
"success": True,
|
| 183 |
+
"filename": request.filename,
|
| 184 |
+
"pdf_base64": pdf_base64,
|
| 185 |
+
"size_bytes": len(pdf_bytes)
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
except Exception as e:
|
| 189 |
+
logger.error(f"โ PDF generation error: {str(e)}")
|
| 190 |
+
raise HTTPException(status_code=500, detail=str(e))
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
if __name__ == "__main__":
|
| 194 |
+
import uvicorn
|
| 195 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.115.5
|
| 2 |
+
uvicorn[standard]==0.32.1
|
| 3 |
+
pydantic==2.10.3
|
| 4 |
+
playwright==1.48.0
|
| 5 |
+
python-multipart==0.0.20
|