shelter / src /flyer_builder.py
CalamityJon's picture
Upload 32 files
0453188 verified
Raw
History Blame Contribute Delete
8.07 kB
"""
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()