First commit
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
"""Generate PDF: Scanned letter FROM client (simulated scan effect)."""
|
||||
|
||||
import io
|
||||
from pathlib import Path
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from reportlab.lib.units import mm
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.lib.colors import HexColor
|
||||
|
||||
try:
|
||||
from PIL import Image, ImageFilter, ImageEnhance
|
||||
import random as _random
|
||||
|
||||
HAS_PIL = True
|
||||
except ImportError:
|
||||
HAS_PIL = False
|
||||
|
||||
|
||||
def _create_clean_pdf(
|
||||
buffer, from_name, from_company, from_address, to_name, to_firm, body, date
|
||||
):
|
||||
"""Create a clean PDF letter."""
|
||||
c = canvas.Canvas(buffer, pagesize=A4)
|
||||
w, h = A4
|
||||
|
||||
# Company header
|
||||
c.setFont("Helvetica-Bold", 14)
|
||||
c.drawString(50, h - 60, from_company)
|
||||
c.setFont("Helvetica", 9)
|
||||
y = h - 78
|
||||
for line in from_address.split(","):
|
||||
c.drawString(50, y, line.strip())
|
||||
y -= 14
|
||||
|
||||
# Date
|
||||
y -= 20
|
||||
c.setFont("Helvetica", 11)
|
||||
c.drawString(50, y, date)
|
||||
|
||||
# Recipient
|
||||
y -= 30
|
||||
c.drawString(50, y, f"Mr. {to_name}")
|
||||
y -= 16
|
||||
c.drawString(50, y, to_firm)
|
||||
y -= 16
|
||||
c.drawString(50, y, "Suites 3203-3207, 32/F, Edinburgh Tower, The Landmark")
|
||||
y -= 16
|
||||
c.drawString(50, y, "15 Queen's Road Central, Central, Hong Kong")
|
||||
|
||||
# Body
|
||||
y -= 35
|
||||
c.setFont("Helvetica", 11)
|
||||
for para in body.split("\n\n"):
|
||||
for line_text in _wrap_text(para.strip(), 85):
|
||||
if y < 80:
|
||||
c.showPage()
|
||||
y = h - 60
|
||||
c.setFont("Helvetica", 11)
|
||||
c.drawString(50, y, line_text)
|
||||
y -= 16
|
||||
y -= 10
|
||||
|
||||
# Signature area
|
||||
y -= 20
|
||||
c.drawString(50, y, "Yours sincerely,")
|
||||
y -= 40
|
||||
# Simulate a signature squiggle
|
||||
c.setStrokeColor(HexColor("#1a1a8a"))
|
||||
c.setLineWidth(1.5)
|
||||
import random
|
||||
|
||||
random.seed(hash(from_name))
|
||||
sx = 50
|
||||
sy = y + 10
|
||||
c.line(sx, sy, sx + 30, sy + 8)
|
||||
c.line(sx + 30, sy + 8, sx + 50, sy - 5)
|
||||
c.line(sx + 50, sy - 5, sx + 80, sy + 3)
|
||||
c.setStrokeColor(HexColor("#000000"))
|
||||
|
||||
y -= 10
|
||||
c.setFont("Helvetica-Bold", 11)
|
||||
c.drawString(50, y, from_name)
|
||||
y -= 16
|
||||
c.setFont("Helvetica", 10)
|
||||
c.drawString(50, y, from_company)
|
||||
|
||||
c.save()
|
||||
|
||||
|
||||
def _wrap_text(text, max_chars):
|
||||
"""Simple word-wrap."""
|
||||
words = text.split()
|
||||
lines = []
|
||||
current = ""
|
||||
for word in words:
|
||||
if len(current) + len(word) + 1 > max_chars:
|
||||
lines.append(current)
|
||||
current = word
|
||||
else:
|
||||
current = f"{current} {word}" if current else word
|
||||
if current:
|
||||
lines.append(current)
|
||||
return lines
|
||||
|
||||
|
||||
def generate(
|
||||
output_path: Path,
|
||||
from_name: str,
|
||||
from_company: str,
|
||||
from_address: str,
|
||||
to_name: str,
|
||||
to_firm: str,
|
||||
body: str,
|
||||
date: str,
|
||||
):
|
||||
"""Generate a scanned-looking PDF letter."""
|
||||
# First create a clean PDF in memory
|
||||
buf = io.BytesIO()
|
||||
_create_clean_pdf(
|
||||
buf, from_name, from_company, from_address, to_name, to_firm, body, date
|
||||
)
|
||||
buf.seek(0)
|
||||
|
||||
if HAS_PIL:
|
||||
# Convert to image, degrade slightly, save back as PDF
|
||||
try:
|
||||
from pdf2image import convert_from_bytes
|
||||
|
||||
images = convert_from_bytes(buf.read(), dpi=150)
|
||||
if images:
|
||||
img = images[0]
|
||||
# Add slight grey tint (paper simulation)
|
||||
enhancer = ImageEnhance.Brightness(img)
|
||||
img = enhancer.enhance(0.95)
|
||||
# Slight blur for scan effect
|
||||
img = img.filter(ImageFilter.GaussianBlur(radius=0.3))
|
||||
# Save as PDF
|
||||
img.save(str(output_path), "PDF", resolution=150)
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Fallback: save clean PDF as-is
|
||||
output_path.write_bytes(buf.getvalue())
|
||||
Reference in New Issue
Block a user