""" Flyer builder module - assembles the final adoption flyer. """ from PIL import Image, ImageDraw, ImageFont import textwrap import os from pathlib import Path def build_flyer(image, bio_text, flyer_data): """ Build the final adoption flyer with image, text, and shelter info. Args: image: PIL Image of the composited animal photo bio_text: str, the generated bio flyer_data: dict with animal and shelter information Returns: tuple: (flyer_image, png_path, pdf_path) """ # Create flyer canvas (8.5" x 11" at 300 DPI = 2550 x 3300, but we'll use 1200x1600 for digital) canvas_width = 1200 canvas_height = 1600 # Create white canvas canvas = Image.new('RGB', (canvas_width, canvas_height), 'white') draw = ImageDraw.Draw(canvas) # Define layout areas header_height = 120 photo_top = header_height + 20 photo_height = 700 details_top = photo_top + photo_height + 30 bio_top = details_top + 120 footer_top = canvas_height - 100 # Try to load fonts, fall back to default if not available try: # These are common system fonts title_font = _get_font(size=60, bold=True) heading_font = _get_font(size=28, bold=True) body_font = _get_font(size=22) detail_font = _get_font(size=20) footer_font = _get_font(size=18) except Exception as e: print(f"Font loading issue: {e}") title_font = heading_font = body_font = detail_font = footer_font = None # 1. Header - "ADOPT ME!" header_text = "🐾 ADOPT ME! 🐾" _draw_centered_text(draw, header_text, canvas_width // 2, 60, title_font, fill='#2E86AB') # 2. Animal Photo (centered) photo_width = 900 photo = image.copy() photo.thumbnail((photo_width, photo_height), Image.Resampling.LANCZOS) photo_x = (canvas_width - photo.width) // 2 photo_y = photo_top + (photo_height - photo.height) // 2 # Add subtle border around photo border_padding = 10 draw.rectangle( [photo_x - border_padding, photo_y - border_padding, photo_x + photo.width + border_padding, photo_y + photo.height + border_padding], outline='#cccccc', width=3 ) canvas.paste(photo, (photo_x, photo_y)) # 3. Animal Details (name, breed, age, gender) name = flyer_data.get("name", "Unknown") breed = flyer_data.get("breed", "Mixed Breed") age = flyer_data.get("age", "Unknown") gender = flyer_data.get("gender", "") species = flyer_data.get("species", "Pet") # Name (large and centered) _draw_centered_text(draw, name, canvas_width // 2, details_top, heading_font, fill='#333333') # Details line details = f"{species} • {breed} • {age} • {gender}" _draw_centered_text(draw, details, canvas_width // 2, details_top + 40, detail_font, fill='#666666') # 4. Bio Text (wrapped) bio_y = bio_top bio_margin = 80 bio_width = canvas_width - (bio_margin * 2) wrapped_bio = textwrap.fill(bio_text, width=60) for line in wrapped_bio.split('\n'): if bio_y > footer_top - 50: # Don't overflow into footer break _draw_centered_text(draw, line, canvas_width // 2, bio_y, body_font, fill='#444444') bio_y += 35 # 5. Footer - Shelter Contact Info draw.line([(100, footer_top - 30), (canvas_width - 100, footer_top - 30)], fill='#cccccc', width=2) shelter_name = flyer_data.get("shelter_name", "Animal Shelter") shelter_phone = flyer_data.get("shelter_phone", "") shelter_website = flyer_data.get("shelter_website", "") _draw_centered_text(draw, shelter_name, canvas_width // 2, footer_top, footer_font, fill='#2E86AB') contact_text = f"📞 {shelter_phone} • 🌐 {shelter_website}" _draw_centered_text(draw, contact_text, canvas_width // 2, footer_top + 35, footer_font, fill='#666666') # Save outputs output_dir = Path(__file__).parent.parent / "output" output_dir.mkdir(exist_ok=True) # Generate filename from animal name safe_name = "".join(c for c in name if c.isalnum() or c in (' ', '-', '_')).strip() safe_name = safe_name.replace(' ', '_') png_path = output_dir / f"{safe_name}_flyer.png" pdf_path = output_dir / f"{safe_name}_flyer.pdf" # Save PNG canvas.save(str(png_path), 'PNG', quality=95) print(f"Saved PNG to {png_path}") # Save PDF using reportlab try: _save_as_pdf(canvas, str(pdf_path)) print(f"Saved PDF to {pdf_path}") except Exception as e: print(f"PDF generation failed: {e}") pdf_path = None return canvas, str(png_path), str(pdf_path) if pdf_path else None def _get_font(size=20, bold=False): """ Try to load a system font, fall back to default if unavailable. Args: size: int, font size bold: bool, whether to use bold variant Returns: ImageFont object """ # Common font paths by OS font_options = [ # Windows "C:/Windows/Fonts/arial.ttf", "C:/Windows/Fonts/arialbd.ttf" if bold else "C:/Windows/Fonts/arial.ttf", # macOS "/System/Library/Fonts/Helvetica.ttc", "/Library/Fonts/Arial.ttf", # Linux "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" if bold else "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", ] for font_path in font_options: if os.path.exists(font_path): try: return ImageFont.truetype(font_path, size) except Exception: continue # Fall back to default font try: return ImageFont.load_default() except Exception: return None def _draw_centered_text(draw, text, x, y, font, fill='black'): """ Draw text centered at the given x, y position. Args: draw: ImageDraw object text: str, text to draw x: int, center x position y: int, top y position font: ImageFont object fill: color for text """ if font: bbox = draw.textbbox((0, 0), text, font=font) text_width = bbox[2] - bbox[0] text_x = x - text_width // 2 draw.text((text_x, y), text, font=font, fill=fill) else: # Fallback without font (approximation) text_width = len(text) * 8 # Rough approximation text_x = x - text_width // 2 draw.text((text_x, y), text, fill=fill) def _save_as_pdf(image, pdf_path): """ Save the flyer image as a PDF. Args: image: PIL Image pdf_path: str, output path for PDF """ from reportlab.pdfgen import canvas as pdf_canvas from reportlab.lib.pagesizes import letter from reportlab.lib.units import inch import io # Create PDF c = pdf_canvas.Canvas(pdf_path, pagesize=letter) width, height = letter # Convert PIL image to bytes img_buffer = io.BytesIO() image.save(img_buffer, format='PNG') img_buffer.seek(0) # Calculate dimensions to fit letter size while maintaining aspect ratio img_width, img_height = image.size aspect = img_width / img_height target_width = width - (1 * inch) target_height = target_width / aspect if target_height > height - (1 * inch): target_height = height - (1 * inch) target_width = target_height * aspect # Center on page x = (width - target_width) / 2 y = (height - target_height) / 2 # Draw image c.drawImage(img_buffer, x, y, width=target_width, height=target_height) # Save PDF c.save()