"""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())