#!/usr/bin/env python3
"""
High-Quality Property PDF Generator using ReportLab
Generates professional property brochures with market analysis and investment insights
"""
import os
import io
import base64
from datetime import datetime
from typing import Dict, Any, List, Optional
from reportlab.lib.pagesizes import A4, letter
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, PageBreak, Table, TableStyle, Image
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch
from reportlab.lib import colors
from reportlab.lib.colors import HexColor
from PIL import Image as PILImage
import tempfile
class PropertyPDFGenerator:
"""High-quality property PDF generator with professional templates"""
def __init__(self):
"""Initialize the PDF generator with professional styles"""
self.styles = getSampleStyleSheet()
self._setup_premium_styles()
def _setup_premium_styles(self):
"""Setup premium styling for professional brochures"""
# Ultra Premium Title Style
self.styles.add(ParagraphStyle(
name='UltraPremiumTitle',
parent=self.styles['Title'],
fontSize=36,
fontName='Helvetica-Bold',
textColor=HexColor('#1a202c'),
spaceAfter=20,
alignment=1, # Center
leading=40
))
# Luxury Collection Title Style
self.styles.add(ParagraphStyle(
name='LuxuryCollectionTitle',
parent=self.styles['Heading1'],
fontSize=24,
fontName='Helvetica-Bold',
textColor=HexColor('#2d3748'),
spaceAfter=15,
alignment=1, # Center
leading=28
))
# Modern Collection Title Style
self.styles.add(ParagraphStyle(
name='ModernCollectionTitle',
parent=self.styles['Heading1'],
fontSize=22,
fontName='Helvetica-Bold',
textColor=HexColor('#4a5568'),
spaceAfter=15,
alignment=1, # Center
leading=26
))
# Dubai Collection Title Style
self.styles.add(ParagraphStyle(
name='DubaiCollectionTitle',
parent=self.styles['Heading1'],
fontSize=26,
fontName='Helvetica-Bold',
textColor=HexColor('#c53030'),
spaceAfter=15,
alignment=1, # Center
leading=30
))
# Premium Subtitle Style
self.styles.add(ParagraphStyle(
name='PremiumSubtitle',
parent=self.styles['Heading2'],
fontSize=18,
fontName='Helvetica-Bold',
textColor=HexColor('#4a5568'),
spaceAfter=12,
alignment=1, # Center
leading=22
))
# Section Header Style
self.styles.add(ParagraphStyle(
name='SectionHeader',
parent=self.styles['Heading3'],
fontSize=16,
fontName='Helvetica-Bold',
textColor=HexColor('#2d3748'),
spaceAfter=10,
spaceBefore=15,
leading=20
))
# Premium Content Text Style
self.styles.add(ParagraphStyle(
name='PremiumContentText',
parent=self.styles['Normal'],
fontSize=12,
fontName='Helvetica',
textColor=HexColor('#4a5568'),
spaceAfter=8,
leading=16
))
# Premium Feature Text Style
self.styles.add(ParagraphStyle(
name='PremiumFeatureText',
parent=self.styles['Normal'],
fontSize=12,
fontName='Helvetica-Bold',
textColor=HexColor('#2d3748'),
spaceAfter=6,
leading=16
))
# Premium Price Display Style
self.styles.add(ParagraphStyle(
name='PremiumPriceDisplay',
parent=self.styles['Normal'],
fontSize=28,
fontName='Helvetica-Bold',
textColor=HexColor('#2c5530'),
spaceAfter=15,
alignment=1, # Center
leading=32
))
# Location Text Style
self.styles.add(ParagraphStyle(
name='LocationText',
parent=self.styles['Normal'],
fontSize=16,
fontName='Helvetica-Bold',
textColor=HexColor('#718096'),
spaceAfter=10,
alignment=1, # Center
leading=20
))
# Amenity Text Style
self.styles.add(ParagraphStyle(
name='AmenityText',
parent=self.styles['Normal'],
fontSize=11,
fontName='Helvetica',
textColor=HexColor('#4a5568'),
spaceAfter=4,
leading=15
))
def _create_professional_header_footer(self, canvas_obj, page_num: int, template_name: str):
"""Create professional header and footer for each page"""
# Header
canvas_obj.setFillColor(HexColor('#1f2937'))
canvas_obj.setFont("Helvetica-Bold", 18)
canvas_obj.drawString(50, A4[1] - 40, "LUXURY REAL ESTATE")
canvas_obj.setFont("Helvetica", 14)
canvas_obj.drawString(50, A4[1] - 60, "Premium Property Brochure")
# Footer
canvas_obj.setFillColor(HexColor('#6b7280'))
canvas_obj.setFont("Helvetica", 12)
canvas_obj.drawCentredString(A4[0]/2, 35, f"Generated on {datetime.now().strftime('%B %d, %Y')}")
canvas_obj.drawCentredString(A4[0]/2, 20, "Luxury Real Estate - Premium Property Solutions")
# Page number
canvas_obj.drawRightString(A4[0] - 50, 20, f"Page {page_num}")
def generate_property_pdf(self, property_data: Dict[str, Any], template_type: str = 'professional-3pager', output_file: str = None) -> str:
"""Generate high-quality property PDF based on template type"""
try:
# Create output file path
if not output_file:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_file = f"property_brochure_{timestamp}.pdf"
# Create PDF document
doc = SimpleDocTemplate(
output_file,
pagesize=A4,
rightMargin=0.75*inch,
leftMargin=0.75*inch,
topMargin=1*inch,
bottomMargin=1*inch
)
# Process images if available
processed_images = []
if property_data.get('images') and len(property_data.get('images', [])) > 0:
processed_images = self.process_images(property_data.get('images', []))
# Build story based on template type
if template_type == 'professional-1pager':
story = self._build_professional_1pager_brochure(property_data, processed_images)
elif template_type == 'professional-3pager':
story = self._build_professional_3pager_brochure(property_data, processed_images)
elif template_type == 'professional-5pager':
story = self._build_professional_5pager_brochure(property_data, processed_images)
elif template_type == 'luxury-villa':
story = self._build_luxury_villa_brochure(property_data, processed_images)
elif template_type == 'dubai-penthouse':
story = self._build_dubai_penthouse_brochure(property_data, processed_images)
elif template_type == 'modern-apartment':
story = self._build_modern_apartment_brochure(property_data, processed_images)
elif template_type == 'custom':
story = self._build_custom_template_brochure(property_data, processed_images)
else:
# Default to professional 3-pager
story = self._build_professional_3pager_brochure(property_data, processed_images)
# Build PDF with header/footer
doc.build(story, onFirstPage=lambda canvas, doc: self._create_professional_header_footer(canvas, 1, template_type),
onLaterPages=lambda canvas, doc: self._create_professional_header_footer(canvas, doc.page, template_type))
return output_file
except Exception as e:
print(f"Error generating PDF: {str(e)}")
raise
def process_images(self, images: List[str]) -> List[Image]:
"""Process and resize images for PDF inclusion with high quality"""
processed_images = []
for img_data in images:
try:
# Remove data URL prefix if present
if img_data.startswith('data:image'):
img_data = img_data.split(',')[1]
# Decode base64 image
img_bytes = base64.b64decode(img_data)
img = PILImage.open(io.BytesIO(img_bytes))
# Convert to RGB if necessary
if img.mode != 'RGB':
img = img.convert('RGB')
# Resize image to fit PDF with high quality
max_width = 6 * inch # Maximum width for luxury brochures
aspect_ratio = img.width / img.height
new_width = min(max_width, img.width)
new_height = new_width / aspect_ratio
# Ensure minimum height for quality
min_height = 2 * inch
if new_height < min_height:
new_height = min_height
new_width = new_height * aspect_ratio
img = img.resize((int(new_width), int(new_height)), PILImage.Resampling.LANCZOS)
# Save to bytes with maximum quality
img_buffer = io.BytesIO()
img.save(img_buffer, format='JPEG', quality=95, optimize=True)
img_buffer.seek(0)
# Create ReportLab Image with proper dimensions
reportlab_img = Image(img_buffer, width=new_width, height=new_height)
processed_images.append(reportlab_img)
except Exception as e:
print(f"Error processing image: {str(e)}")
continue
return processed_images
def _build_professional_1pager_brochure(self, property_data: Dict[str, Any], processed_images: List = None) -> List:
"""Build a professional 1-page brochure with all content and images"""
story = []
# Cover/Header Section
story.append(Paragraph(f"{property_data.get('propertyName', 'Luxury Property')}", self.styles['UltraPremiumTitle']))
story.append(Paragraph(f"{property_data.get('propertyType', 'Premium Property')} • {property_data.get('location', 'Prime Location')}", self.styles['LuxuryCollectionTitle']))
story.append(Spacer(1, 20))
# Price Section
price_color = "#2c5530"
story.append(Paragraph(f"AED {property_data.get('price', 'On Request')}", self.styles['PremiumPriceDisplay']))
story.append(Spacer(1, 15))
# Property Details Table
details_data = [
['Property Type', property_data.get('propertyType', 'N/A')],
['Bedrooms', property_data.get('bedrooms', 'N/A')],
['Bathrooms', property_data.get('bathrooms', 'N/A')],
['Area', f"{property_data.get('area', 'N/A')} sq ft"],
]
# Add market data if available
if property_data.get('marketTrend'):
details_data.append(['Market Trend', property_data.get('marketTrend', 'N/A').title()])
if property_data.get('roiPotential'):
details_data.append(['ROI Potential', f"{property_data.get('roiPotential', 'N/A')}%"])
if property_data.get('avgPricePerSqft'):
details_data.append(['Avg Price/sq ft', f"AED {property_data.get('avgPricePerSqft', 'N/A')}"])
if property_data.get('marketDemand'):
details_data.append(['Market Demand', property_data.get('marketDemand', 'N/A').title()])
details_table = Table(details_data, colWidths=[3*inch, 3*inch])
details_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (0, -1), HexColor('#f8f9fa')),
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 12),
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('GRID', (0, 0), (-1, -1), 1, HexColor('#dee2e6')),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LEFTPADDING', (0, 0), (-1, -1), 12),
('RIGHTPADDING', (0, 0), (-1, -1), 12),
('TOPPADDING', (0, 0), (-1, -1), 8),
('BOTTOMPADDING', (0, 0), (-1, -1), 8),
]))
story.append(details_table)
story.append(Spacer(1, 20))
# Description
if property_data.get('description'):
story.append(Paragraph('Property Description', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('description', ''), self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Investment highlights
if property_data.get('investmentHighlights'):
story.append(Paragraph('Investment Highlights', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('investmentHighlights', ''), self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Location Advantages
if property_data.get('locationAdvantages'):
story.append(Paragraph('Location Advantages', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('locationAdvantages', ''), self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Content Modules
if property_data.get('contentModules'):
story.append(Paragraph('Included Research & Analysis', self.styles['SectionHeader']))
for module in property_data.get('contentModules', []):
module_name = module.replace('-', ' ').title()
story.append(Paragraph(f"✓ {module_name}", self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# All Property Images with Room Names (2x3 grid for 1-pager)
if processed_images and len(processed_images) > 0:
story.append(Paragraph('Property Gallery', self.styles['SectionHeader']))
story.append(Spacer(1, 15))
image_names = property_data.get('imageNames', [])
# Create 2x3 grid for images with room names
for i in range(0, min(len(processed_images), 6), 2): # Max 6 images on 1 page
row_images = processed_images[i:i+2]
row_names = image_names[i:i+2] if len(image_names) > i else []
# Create table with images and names
row_data = []
for j, img in enumerate(row_images):
room_name = row_names[j] if j < len(row_names) else f'Room {i+j+1}'
row_data.append([img, Paragraph(f"{room_name}", self.styles['PremiumContentText'])])
# Pad row to 2 columns if needed
while len(row_data) < 2:
row_data.append(['', ''])
row_table = Table(row_data, colWidths=[3*inch, 3*inch])
row_table.setStyle(TableStyle([
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('LEFTPADDING', (0, 0), (-1, -1), 5),
('RIGHTPADDING', (0, 0), (-1, -1), 5),
]))
story.append(row_table)
story.append(Spacer(1, 10))
story.append(Spacer(1, 15))
# All Amenities
if property_data.get('amenities'):
story.append(Paragraph('Premium Amenities', self.styles['SectionHeader']))
# Show all amenities in a compact format
amenities_text = ' • '.join(property_data.get('amenities', []))
story.append(Paragraph(amenities_text, self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Additional Content
if property_data.get('additionalContent'):
story.append(Paragraph('Additional Information', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('additionalContent', ''), self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Layout and Customization info
customization_info = []
if property_data.get('layout'):
customization_info.append(f"Layout: {property_data.get('layout', 'Default').title()}")
if property_data.get('headerStyle'):
customization_info.append(f"Header: {property_data.get('headerStyle', 'Modern').title()}")
if property_data.get('colorScheme'):
customization_info.append(f"Colors: {property_data.get('colorScheme', 'Blue').title()}")
if property_data.get('fontStyle'):
customization_info.append(f"Font: {property_data.get('fontStyle', 'Sans-Serif').title()}")
if customization_info:
story.append(Paragraph('Document Customization', self.styles['SectionHeader']))
story.append(Paragraph(' • '.join(customization_info), self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Contact Information
story.append(Paragraph('Contact Information', self.styles['SectionHeader']))
story.append(Paragraph("📧 Email: info@luxuryrealestate.com • 📞 Phone: +971 4 XXX XXXX • 🌐 www.luxuryrealestate.com", self.styles['PremiumContentText']))
return story
def _build_professional_3pager_brochure(self, property_data: Dict[str, Any], processed_images: List = None) -> List:
"""Build a comprehensive 3-page professional brochure with market analysis"""
story = []
# PAGE 1: Cover & Overview
story.append(Paragraph(f"{property_data.get('propertyName', 'Premium Property')}", self.styles['UltraPremiumTitle']))
story.append(Paragraph(f"{property_data.get('propertyType', 'Luxury Property')} in {property_data.get('location', 'Prime Location')}", self.styles['LuxuryCollectionTitle']))
story.append(Spacer(1, 30))
# Add main property image if available
if processed_images and len(processed_images) > 0:
story.append(processed_images[0])
story.append(Spacer(1, 20))
# Executive Summary
story.append(Paragraph('Executive Summary', self.styles['SectionHeader']))
exec_summary = f"This exceptional {property_data.get('propertyType', 'property')} represents a unique investment opportunity in {property_data.get('location', 'a prime location')}. "
if property_data.get('roiPotential'):
exec_summary += f"With an expected ROI of {property_data.get('roiPotential')}%, this property offers excellent investment potential. "
exec_summary += property_data.get('description', 'A premium property with outstanding features and amenities.')
story.append(Paragraph(exec_summary, self.styles['PremiumContentText']))
story.append(Spacer(1, 20))
# Key Features Table
features_data = [
['Price', f"AED {property_data.get('price', 'On Request')}"],
['Property Type', property_data.get('propertyType', 'N/A')],
['Location', property_data.get('location', 'N/A')],
['Bedrooms', property_data.get('bedrooms', 'N/A')],
['Bathrooms', property_data.get('bathrooms', 'N/A')],
['Area', f"{property_data.get('area', 'N/A')} sq ft"],
]
features_table = Table(features_data, colWidths=[2.5*inch, 3.5*inch])
features_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (0, -1), HexColor('#e3f2fd')),
('BACKGROUND', (0, 0), (0, 0), HexColor('#1976d2')),
('TEXTCOLOR', (0, 0), (0, 0), colors.white),
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 11),
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('GRID', (0, 0), (-1, -1), 1, HexColor('#1976d2')),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LEFTPADDING', (0, 0), (-1, -1), 12),
('RIGHTPADDING', (0, 0), (-1, -1), 12),
('TOPPADDING', (0, 0), (-1, -1), 10),
('BOTTOMPADDING', (0, 0), (-1, -1), 10),
]))
story.append(features_table)
story.append(Spacer(1, 30))
# Description
if property_data.get('description'):
story.append(Paragraph('Property Description', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('description', ''), self.styles['PremiumContentText']))
story.append(Spacer(1, 20))
# Basic Amenities on Page 1
if property_data.get('amenities'):
story.append(Paragraph('Key Amenities', self.styles['SectionHeader']))
# Show first 6 amenities on page 1
key_amenities = property_data.get('amenities', [])[:6]
for amenity in key_amenities:
story.append(Paragraph(f"• {amenity}", self.styles['PremiumContentText']))
# PAGE BREAK TO START PAGE 2
story.append(PageBreak())
# PAGE 2: Market Analysis & Investment Details
story.append(Paragraph('Market Analysis & Investment Overview', self.styles['UltraPremiumTitle']))
story.append(Spacer(1, 30))
# Add more property images if available (2x2 grid with room names)
if processed_images and len(processed_images) > 1:
story.append(Paragraph('Property Gallery', self.styles['SectionHeader']))
story.append(Spacer(1, 15))
# Create 2x2 image grid for page 2 with room names
remaining_images = processed_images[1:5] # Take next 4 images
image_names = property_data.get('imageNames', [])
if len(remaining_images) >= 2:
# First row
row1_images = remaining_images[:2]
row1_names = image_names[1:3] if len(image_names) > 1 else ['Room 2', 'Room 3']
# Create table with images and names
row1_data = []
for i, img in enumerate(row1_images):
room_name = row1_names[i] if i < len(row1_names) else f'Room {i+2}'
row1_data.append([img, Paragraph(f"{room_name}", self.styles['PremiumContentText'])])
row1_table = Table(row1_data, colWidths=[2.5*inch, 2.5*inch])
row1_table.setStyle(TableStyle([
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('LEFTPADDING', (0, 0), (-1, -1), 5),
('RIGHTPADDING', (0, 0), (-1, -1), 5),
]))
story.append(row1_table)
story.append(Spacer(1, 15))
# Second row if we have more images
if len(remaining_images) >= 4:
row2_images = remaining_images[2:4]
row2_names = image_names[3:5] if len(image_names) > 3 else ['Room 4', 'Room 5']
row2_data = []
for i, img in enumerate(row2_images):
room_name = row2_names[i] if i < len(row2_names) else f'Room {i+4}'
row2_data.append([img, Paragraph(f"{room_name}", self.styles['PremiumContentText'])])
row2_table = Table(row2_data, colWidths=[2.5*inch, 2.5*inch])
row2_table.setStyle(TableStyle([
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('LEFTPADDING', (0, 0), (-1, -1), 5),
('RIGHTPADDING', (0, 0), (-1, -1), 5),
]))
story.append(row2_table)
story.append(Spacer(1, 20))
# Market Data Section
if any([property_data.get('marketTrend'), property_data.get('roiPotential'), property_data.get('avgPricePerSqft')]):
story.append(Paragraph('Market Intelligence', self.styles['SectionHeader']))
story.append(Spacer(1, 15))
market_data = []
if property_data.get('marketTrend'):
market_data.append(['Market Trend', property_data.get('marketTrend', 'N/A').title()])
if property_data.get('roiPotential'):
market_data.append(['ROI Potential', f"{property_data.get('roiPotential', 'N/A')}%"])
if property_data.get('avgPricePerSqft'):
market_data.append(['Avg Price/sq ft', f"AED {property_data.get('avgPricePerSqft', 'N/A')}"])
if property_data.get('marketDemand'):
market_data.append(['Market Demand', property_data.get('marketDemand', 'N/A').title()])
market_table = Table(market_data, colWidths=[3*inch, 3*inch])
market_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (0, -1), HexColor('#e8f5e8')),
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 12),
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('GRID', (0, 0), (-1, -1), 1, HexColor('#388e3c')),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LEFTPADDING', (0, 0), (-1, -1), 12),
('RIGHTPADDING', (0, 0), (-1, -1), 12),
('TOPPADDING', (0, 0), (-1, -1), 10),
('BOTTOMPADDING', (0, 0), (-1, -1), 10),
]))
story.append(market_table)
story.append(Spacer(1, 25))
# Investment Analysis
if any([property_data.get('investmentType'), property_data.get('rentalYield'), property_data.get('investmentHighlights')]):
story.append(Paragraph('Investment Analysis', self.styles['SectionHeader']))
story.append(Spacer(1, 15))
if property_data.get('investmentType'):
story.append(Paragraph(f"Investment Strategy: {property_data.get('investmentType', '').replace('-', ' ').title()}", self.styles['PremiumContentText']))
story.append(Spacer(1, 10))
if property_data.get('rentalYield'):
story.append(Paragraph(f"Expected Rental Yield: {property_data.get('rentalYield')}% annually", self.styles['PremiumContentText']))
story.append(Spacer(1, 10))
if property_data.get('investmentHighlights'):
story.append(Paragraph('Investment Highlights:', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('investmentHighlights', ''), self.styles['PremiumContentText']))
story.append(Spacer(1, 25))
# Location Advantages
if property_data.get('locationAdvantages'):
story.append(Paragraph('Location Advantages', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('locationAdvantages', ''), self.styles['PremiumContentText']))
story.append(Spacer(1, 20))
# Content Modules on Page 2
if property_data.get('contentModules'):
story.append(Paragraph('Included Research & Analysis', self.styles['SectionHeader']))
for module in property_data.get('contentModules', []):
module_name = module.replace('-', ' ').title()
story.append(Paragraph(f"✓ {module_name}", self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# PAGE BREAK TO START PAGE 3
story.append(PageBreak())
# PAGE 3: Complete Amenities & Additional Information
story.append(Paragraph('Complete Property Features & Amenities', self.styles['UltraPremiumTitle']))
story.append(Spacer(1, 30))
# Add remaining images if available (4x4 grid for page 3 with room names)
if processed_images and len(processed_images) > 5:
story.append(Paragraph('Additional Property Views', self.styles['SectionHeader']))
story.append(Spacer(1, 15))
# Create 4x4 grid for remaining images with room names
remaining_images = processed_images[5:13] # Take next 8 images
remaining_names = image_names[5:13] if len(image_names) > 5 else []
if remaining_images:
# Process images in rows of 4 with room names
for i in range(0, len(remaining_images), 4):
row_images = remaining_images[i:i+4]
row_names = remaining_names[i:i+4] if len(remaining_names) > i else []
# Create table with images and names
row_data = []
for j, img in enumerate(row_images):
room_name = row_names[j] if j < len(row_names) else f'Room {i+j+6}'
row_data.append([img, Paragraph(f"{room_name}", self.styles['PremiumContentText'])])
# Pad row to 4 columns if needed
while len(row_data) < 4:
row_data.append(['', ''])
row_table = Table(row_data, colWidths=[1.5*inch, 1.5*inch, 1.5*inch, 1.5*inch])
row_table.setStyle(TableStyle([
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('LEFTPADDING', (0, 0), (-1, -1), 3),
('RIGHTPADDING', (0, 0), (-1, -1), 3),
]))
story.append(row_table)
story.append(Spacer(1, 10))
story.append(Spacer(1, 20))
# All Amenities
if property_data.get('amenities'):
story.append(Paragraph('Complete Amenities List', self.styles['SectionHeader']))
story.append(Spacer(1, 15))
# Create a comprehensive amenities table
amenities_list = property_data.get('amenities', [])
if amenities_list:
# Split amenities into two columns for better layout
mid_point = len(amenities_list) // 2
col1_amenities = amenities_list[:mid_point]
col2_amenities = amenities_list[mid_point:]
# Create table data
amenities_data = []
max_rows = max(len(col1_amenities), len(col2_amenities))
for i in range(max_rows):
col1_item = f"• {col1_amenities[i]}" if i < len(col1_amenities) else ""
col2_item = f"• {col2_amenities[i]}" if i < len(col2_amenities) else ""
amenities_data.append([col1_item, col2_item])
amenities_table = Table(amenities_data, colWidths=[3*inch, 3*inch])
amenities_table.setStyle(TableStyle([
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 11),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('LEFTPADDING', (0, 0), (-1, -1), 5),
('RIGHTPADDING', (0, 0), (-1, -1), 5),
('TOPPADDING', (0, 0), (-1, -1), 5),
('BOTTOMPADDING', (0, 0), (-1, -1), 5),
]))
story.append(amenities_table)
story.append(Spacer(1, 25))
# Additional Content
if property_data.get('additionalContent'):
story.append(Paragraph('Additional Information', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('additionalContent', ''), self.styles['PremiumContentText']))
story.append(Spacer(1, 20))
# Layout information if custom template
if property_data.get('layout'):
story.append(Paragraph('Layout Configuration', self.styles['SectionHeader']))
story.append(Paragraph(f"Selected Layout: {property_data.get('layout', 'Default').title()}", self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Customization details
customization_info = []
if property_data.get('headerStyle'):
customization_info.append(f"Header Style: {property_data.get('headerStyle', 'Modern').title()}")
if property_data.get('colorScheme'):
customization_info.append(f"Color Scheme: {property_data.get('colorScheme', 'Blue').title()}")
if property_data.get('fontStyle'):
customization_info.append(f"Font Style: {property_data.get('fontStyle', 'Sans-Serif').title()}")
if customization_info:
story.append(Paragraph('Document Customization', self.styles['SectionHeader']))
for info in customization_info:
story.append(Paragraph(f"• {info}", self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# PAGE BREAK TO START PAGE 5
story.append(PageBreak())
# PAGE 5: Final Images & Contact Information
story.append(Paragraph('Final Property Views & Contact', self.styles['UltraPremiumTitle']))
story.append(Spacer(1, 30))
# Add remaining images if available (3x3 grid for page 5 with room names)
if processed_images and len(processed_images) > 11:
story.append(Paragraph('Final Property Gallery', self.styles['SectionHeader']))
story.append(Spacer(1, 15))
# Create 3x3 grid for remaining images with room names
remaining_images = processed_images[11:20] # Take next 9 images
remaining_names = image_names[11:20] if len(image_names) > 11 else []
if remaining_images:
# Process images in rows of 3 with room names
for i in range(0, len(remaining_images), 3):
row_images = remaining_images[i:i+3]
row_names = remaining_names[i:i+3] if len(remaining_names) > i else []
# Create table with images and names
row_data = []
for j, img in enumerate(row_images):
room_name = row_names[j] if j < len(row_names) else f'Room {i+j+12}'
row_data.append([img, Paragraph(f"{room_name}", self.styles['PremiumContentText'])])
# Pad row to 3 columns if needed
while len(row_data) < 3:
row_data.append(['', ''])
row_table = Table(row_data, colWidths=[2*inch, 2*inch, 2*inch])
row_table.setStyle(TableStyle([
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('LEFTPADDING', (0, 0), (-1, -1), 3),
('RIGHTPADDING', (0, 0), (-1, -1), 3),
]))
story.append(row_table)
story.append(Spacer(1, 10))
story.append(Spacer(1, 20))
# Investment Summary
story.append(Paragraph('Investment Summary', self.styles['SectionHeader']))
story.append(Spacer(1, 15))
investment_summary = f"This {property_data.get('propertyType', 'property')} in {property_data.get('location', 'prime location')} represents an excellent investment opportunity. "
if property_data.get('roiPotential'):
investment_summary += f"With an expected ROI of {property_data.get('roiPotential')}%, "
if property_data.get('rentalYield'):
investment_summary += f"and a rental yield of {property_data.get('rentalYield')}%, "
investment_summary += "this property offers strong potential for both capital appreciation and rental income."
story.append(Paragraph(investment_summary, self.styles['PremiumContentText']))
story.append(Spacer(1, 20))
# Contact Information / Footer
story.append(Paragraph('Contact Information', self.styles['SectionHeader']))
story.append(Paragraph("For more information about this property or to schedule a viewing, please contact our property specialists.", self.styles['PremiumContentText']))
story.append(Spacer(1, 10))
story.append(Paragraph("📧 Email: info@luxuryrealestate.com", self.styles['PremiumContentText']))
story.append(Paragraph("📞 Phone: +971 4 XXX XXXX", self.styles['PremiumContentText']))
story.append(Paragraph("🌐 Website: www.luxuryrealestate.com", self.styles['PremiumContentText']))
story.append(Spacer(1, 10))
story.append(Paragraph("Thank you for considering this exceptional property investment opportunity.", self.styles['PremiumContentText']))
return story
def _build_luxury_villa_brochure(self, property_data: Dict[str, Any], processed_images: List = None) -> List:
"""Build a luxury villa brochure with detailed information and images"""
story = []
# Cover/Header Section
story.append(Paragraph(f"{property_data.get('propertyName', 'Luxury Villa')}", self.styles['UltraPremiumTitle']))
story.append(Paragraph(f"{property_data.get('propertyType', 'Luxury Villa')} • {property_data.get('location', 'Prime Location')}", self.styles['DubaiCollectionTitle']))
story.append(Spacer(1, 20))
# Price Section
price_color = "#c53030"
story.append(Paragraph(f"AED {property_data.get('price', 'On Request')}", self.styles['PremiumPriceDisplay']))
story.append(Spacer(1, 15))
# Add main property image if available
if processed_images and len(processed_images) > 0:
story.append(processed_images[0])
story.append(Spacer(1, 20))
# Property Details Table
details_data = [
['Property Type', property_data.get('propertyType', 'N/A')],
['Bedrooms', property_data.get('bedrooms', 'N/A')],
['Bathrooms', property_data.get('bathrooms', 'N/A')],
['Area', f"{property_data.get('area', 'N/A')} sq ft"],
]
# Add market data if available
if property_data.get('marketTrend'):
details_data.append(['Market Trend', property_data.get('marketTrend', 'N/A').title()])
if property_data.get('roiPotential'):
details_data.append(['ROI Potential', f"{property_data.get('roiPotential', 'N/A')}%"])
if property_data.get('avgPricePerSqft'):
details_data.append(['Avg Price/sq ft', f"AED {property_data.get('avgPricePerSqft', 'N/A')}"])
if property_data.get('marketDemand'):
details_data.append(['Market Demand', property_data.get('marketDemand', 'N/A').title()])
details_table = Table(details_data, colWidths=[3*inch, 3*inch])
details_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (0, -1), HexColor('#f8f9fa')),
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 12),
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('GRID', (0, 0), (-1, -1), 1, HexColor('#dee2e6')),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LEFTPADDING', (0, 0), (-1, -1), 12),
('RIGHTPADDING', (0, 0), (-1, -1), 12),
('TOPPADDING', (0, 0), (-1, -1), 8),
('BOTTOMPADDING', (0, 0), (-1, -1), 8),
]))
story.append(details_table)
story.append(Spacer(1, 20))
# Description
if property_data.get('description'):
story.append(Paragraph('Property Description', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('description', ''), self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Investment highlights
if property_data.get('investmentHighlights'):
story.append(Paragraph('Investment Highlights', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('investmentHighlights', ''), self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Location Advantages
if property_data.get('locationAdvantages'):
story.append(Paragraph('Location Advantages', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('locationAdvantages', ''), self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Content Modules
if property_data.get('contentModules'):
story.append(Paragraph('Included Research & Analysis', self.styles['SectionHeader']))
for module in property_data.get('contentModules', []):
module_name = module.replace('-', ' ').title()
story.append(Paragraph(f"✓ {module_name}", self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Property Images with Room Names (2x3 grid)
if processed_images and len(processed_images) > 1:
story.append(Paragraph('Property Gallery', self.styles['SectionHeader']))
story.append(Spacer(1, 15))
image_names = property_data.get('imageNames', [])
# Create 2x3 grid for images with room names
for i in range(1, min(len(processed_images), 7), 2): # Skip first image, take next 6
row_images = processed_images[i:i+2]
row_names = image_names[i:i+2] if len(image_names) > i else []
# Create table with images and names
row_data = []
for j, img in enumerate(row_images):
room_name = row_names[j] if j < len(row_names) else f'Room {i+j}'
row_data.append([img, Paragraph(f"{room_name}", self.styles['PremiumContentText'])])
# Pad row to 2 columns if needed
while len(row_data) < 2:
row_data.append(['', ''])
row_table = Table(row_data, colWidths=[3*inch, 3*inch])
row_table.setStyle(TableStyle([
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('LEFTPADDING', (0, 0), (-1, -1), 5),
('RIGHTPADDING', (0, 0), (-1, -1), 5),
]))
story.append(row_table)
story.append(Spacer(1, 10))
story.append(Spacer(1, 15))
# All Amenities
if property_data.get('amenities'):
story.append(Paragraph('Premium Amenities', self.styles['SectionHeader']))
# Show all amenities in a compact format
amenities_text = ' • '.join(property_data.get('amenities', []))
story.append(Paragraph(amenities_text, self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Additional Content
if property_data.get('additionalContent'):
story.append(Paragraph('Additional Information', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('additionalContent', ''), self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Layout and Customization info
customization_info = []
if property_data.get('layout'):
customization_info.append(f"Layout: {property_data.get('layout', 'Default').title()}")
if property_data.get('headerStyle'):
customization_info.append(f"Header: {property_data.get('headerStyle', 'Modern').title()}")
if property_data.get('colorScheme'):
customization_info.append(f"Colors: {property_data.get('colorScheme', 'Blue').title()}")
if property_data.get('fontStyle'):
customization_info.append(f"Font: {property_data.get('fontStyle', 'Sans-Serif').title()}")
if customization_info:
story.append(Paragraph('Document Customization', self.styles['SectionHeader']))
story.append(Paragraph(' • '.join(customization_info), self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Contact Information
story.append(Paragraph('Contact Information', self.styles['SectionHeader']))
story.append(Paragraph("📧 Email: info@luxuryrealestate.com • 📞 Phone: +971 4 XXX XXXX • 🌐 www.luxuryrealestate.com", self.styles['PremiumContentText']))
return story
def _build_professional_5pager_brochure(self, property_data: Dict[str, Any], processed_images: List = None) -> List:
"""Build a professional 5-page brochure with comprehensive property details"""
story = []
# PAGE 1: Cover & Executive Summary
story.append(Paragraph(f"{property_data.get('propertyName', 'Premium Property')}", self.styles['UltraPremiumTitle']))
story.append(Paragraph(f"{property_data.get('propertyType', 'Luxury Property')} in {property_data.get('location', 'Prime Location')}", self.styles['LuxuryCollectionTitle']))
story.append(Spacer(1, 30))
# Add main property image if available
if processed_images and len(processed_images) > 0:
story.append(processed_images[0])
story.append(Spacer(1, 20))
# Executive Summary
story.append(Paragraph('Executive Summary', self.styles['SectionHeader']))
exec_summary = f"This exceptional {property_data.get('propertyType', 'property')} represents a unique investment opportunity in {property_data.get('location', 'a prime location')}. "
if property_data.get('roiPotential'):
exec_summary += f"With an expected ROI of {property_data.get('roiPotential')}%, this property offers excellent investment potential. "
exec_summary += property_data.get('description', 'A premium property with outstanding features and amenities.')
story.append(Paragraph(exec_summary, self.styles['PremiumContentText']))
story.append(Spacer(1, 20))
# Key Features Table
features_data = [
['Price', f"AED {property_data.get('price', 'On Request')}"],
['Property Type', property_data.get('propertyType', 'N/A')],
['Location', property_data.get('location', 'N/A')],
['Bedrooms', property_data.get('bedrooms', 'N/A')],
['Bathrooms', property_data.get('bathrooms', 'N/A')],
['Area', f"{property_data.get('area', 'N/A')} sq ft"],
]
features_table = Table(features_data, colWidths=[2.5*inch, 3.5*inch])
features_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (0, -1), HexColor('#e3f2fd')),
('BACKGROUND', (0, 0), (0, 0), HexColor('#1976d2')),
('TEXTCOLOR', (0, 0), (0, 0), colors.white),
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 11),
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('GRID', (0, 0), (-1, -1), 1, HexColor('#1976d2')),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LEFTPADDING', (0, 0), (-1, -1), 12),
('RIGHTPADDING', (0, 0), (-1, -1), 12),
('TOPPADDING', (0, 0), (-1, -1), 10),
('BOTTOMPADDING', (0, 0), (-1, -1), 10),
]))
story.append(features_table)
story.append(Spacer(1, 30))
# Description
if property_data.get('description'):
story.append(Paragraph('Property Description', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('description', ''), self.styles['PremiumContentText']))
story.append(Spacer(1, 20))
# Basic Amenities on Page 1
if property_data.get('amenities'):
story.append(Paragraph('Key Amenities', self.styles['SectionHeader']))
# Show first 8 amenities on page 1
key_amenities = property_data.get('amenities', [])[:8]
for amenity in key_amenities:
story.append(Paragraph(f"• {amenity}", self.styles['PremiumContentText']))
# PAGE BREAK TO START PAGE 2
story.append(PageBreak())
# PAGE 2: Market Analysis & Investment Details
story.append(Paragraph('Market Analysis & Investment Overview', self.styles['UltraPremiumTitle']))
story.append(Spacer(1, 30))
# Add property images (2x2 grid with room names)
if processed_images and len(processed_images) > 1:
story.append(Paragraph('Property Gallery - Main Views', self.styles['SectionHeader']))
story.append(Spacer(1, 15))
# Create 2x2 image grid for page 2 with room names
remaining_images = processed_images[1:5] # Take next 4 images
image_names = property_data.get('imageNames', [])
if len(remaining_images) >= 2:
# First row
row1_images = remaining_images[:2]
row1_names = image_names[1:3] if len(image_names) > 1 else ['Room 2', 'Room 3']
row1_data = []
for i, img in enumerate(row1_images):
room_name = row1_names[i] if i < len(row1_names) else f'Room {i+2}'
row1_data.append([img, Paragraph(f"{room_name}", self.styles['PremiumContentText'])])
row1_table = Table(row1_data, colWidths=[2.5*inch, 2.5*inch])
row1_table.setStyle(TableStyle([
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('LEFTPADDING', (0, 0), (-1, -1), 5),
('RIGHTPADDING', (0, 0), (-1, -1), 5),
]))
story.append(row1_table)
story.append(Spacer(1, 15))
# Second row if we have more images
if len(remaining_images) >= 4:
row2_images = remaining_images[2:4]
row2_names = image_names[3:5] if len(image_names) > 3 else ['Room 4', 'Room 5']
row2_data = []
for i, img in enumerate(row2_images):
room_name = row2_names[i] if i < len(row2_names) else f'Room {i+4}'
row2_data.append([img, Paragraph(f"{room_name}", self.styles['PremiumContentText'])])
row2_table = Table(row2_data, colWidths=[2.5*inch, 2.5*inch])
row2_table.setStyle(TableStyle([
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('LEFTPADDING', (0, 0), (-1, -1), 5),
('RIGHTPADDING', (0, 0), (-1, -1), 5),
]))
story.append(row2_table)
story.append(Spacer(1, 20))
# Market Data Section
if any([property_data.get('marketTrend'), property_data.get('roiPotential'), property_data.get('avgPricePerSqft')]):
story.append(Paragraph('Market Intelligence', self.styles['SectionHeader']))
story.append(Spacer(1, 15))
market_data = []
if property_data.get('marketTrend'):
market_data.append(['Market Trend', property_data.get('marketTrend', 'N/A').title()])
if property_data.get('roiPotential'):
market_data.append(['ROI Potential', f"{property_data.get('roiPotential', 'N/A')}%"])
if property_data.get('avgPricePerSqft'):
market_data.append(['Avg Price/sq ft', f"AED {property_data.get('avgPricePerSqft', 'N/A')}"])
if property_data.get('marketDemand'):
market_data.append(['Market Demand', property_data.get('marketDemand', 'N/A').title()])
market_table = Table(market_data, colWidths=[3*inch, 3*inch])
market_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (0, -1), HexColor('#e8f5e8')),
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 12),
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('GRID', (0, 0), (-1, -1), 1, HexColor('#388e3c')),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LEFTPADDING', (0, 0), (-1, -1), 12),
('RIGHTPADDING', (0, 0), (-1, -1), 12),
('TOPPADDING', (0, 0), (-1, -1), 10),
('BOTTOMPADDING', (0, 0), (-1, -1), 10),
]))
story.append(market_table)
story.append(Spacer(1, 25))
# Investment Analysis
if any([property_data.get('investmentType'), property_data.get('rentalYield'), property_data.get('investmentHighlights')]):
story.append(Paragraph('Investment Analysis', self.styles['SectionHeader']))
story.append(Spacer(1, 15))
if property_data.get('investmentType'):
story.append(Paragraph(f"Investment Strategy: {property_data.get('investmentType', '').replace('-', ' ').title()}", self.styles['PremiumContentText']))
story.append(Spacer(1, 10))
if property_data.get('rentalYield'):
story.append(Paragraph(f"Expected Rental Yield: {property_data.get('rentalYield')}% annually", self.styles['PremiumContentText']))
story.append(Spacer(1, 10))
if property_data.get('investmentHighlights'):
story.append(Paragraph('Investment Highlights:', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('investmentHighlights', ''), self.styles['PremiumContentText']))
story.append(Spacer(1, 25))
# Location Advantages
if property_data.get('locationAdvantages'):
story.append(Paragraph('Location Advantages', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('locationAdvantages', ''), self.styles['PremiumContentText']))
story.append(Spacer(1, 20))
# Content Modules on Page 2
if property_data.get('contentModules'):
story.append(Paragraph('Included Research & Analysis', self.styles['SectionHeader']))
for module in property_data.get('contentModules', []):
module_name = module.replace('-', ' ').title()
story.append(Paragraph(f"✓ {module_name}", self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# PAGE BREAK TO START PAGE 3
story.append(PageBreak())
# PAGE 3: Additional Images & Detailed Features
story.append(Paragraph('Property Features & Additional Views', self.styles['UltraPremiumTitle']))
story.append(Spacer(1, 30))
# Add more property images (2x3 grid for page 3 with room names)
if processed_images and len(processed_images) > 5:
story.append(Paragraph('Additional Property Views', self.styles['SectionHeader']))
story.append(Spacer(1, 15))
# Create 2x3 grid for remaining images with room names
remaining_images = processed_images[5:11] # Take next 6 images
remaining_names = image_names[5:11] if len(image_names) > 5 else []
if remaining_images:
# Process images in rows of 2 with room names
for i in range(0, len(remaining_images), 2):
row_images = remaining_images[i:i+2]
row_names = remaining_names[i:i+2] if len(remaining_names) > i else []
# Create table with images and names
row_data = []
for j, img in enumerate(row_images):
room_name = row_names[j] if j < len(row_names) else f'Room {i+j+6}'
row_data.append([img, Paragraph(f"{room_name}", self.styles['PremiumContentText'])])
# Pad row to 2 columns if needed
while len(row_data) < 2:
row_data.append(['', ''])
row_table = Table(row_data, colWidths=[3*inch, 3*inch])
row_table.setStyle(TableStyle([
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('LEFTPADDING', (0, 0), (-1, -1), 5),
('RIGHTPADDING', (0, 0), (-1, -1), 5),
]))
story.append(row_table)
story.append(Spacer(1, 10))
story.append(Spacer(1, 20))
# Detailed Property Features
story.append(Paragraph('Detailed Property Features', self.styles['SectionHeader']))
story.append(Spacer(1, 15))
# Property Specifications
specs_data = [
['Property Type', property_data.get('propertyType', 'N/A')],
['Location', property_data.get('location', 'N/A')],
['Bedrooms', property_data.get('bedrooms', 'N/A')],
['Bathrooms', property_data.get('bathrooms', 'N/A')],
['Area', f"{property_data.get('area', 'N/A')} sq ft"],
['Price', f"AED {property_data.get('price', 'On Request')}"],
]
specs_table = Table(specs_data, colWidths=[3*inch, 3*inch])
specs_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (0, -1), HexColor('#f0f4ff')),
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 12),
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('GRID', (0, 0), (-1, -1), 1, HexColor('#3b82f6')),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LEFTPADDING', (0, 0), (-1, -1), 12),
('RIGHTPADDING', (0, 0), (-1, -1), 12),
('TOPPADDING', (0, 0), (-1, -1), 10),
('BOTTOMPADDING', (0, 0), (-1, -1), 10),
]))
story.append(specs_table)
story.append(Spacer(1, 25))
# PAGE BREAK TO START PAGE 4
story.append(PageBreak())
# PAGE 4: Complete Amenities & Additional Information
story.append(Paragraph('Complete Amenities & Features', self.styles['UltraPremiumTitle']))
story.append(Spacer(1, 30))
# All Amenities in organized format
if property_data.get('amenities'):
story.append(Paragraph('Complete Amenities List', self.styles['SectionHeader']))
story.append(Spacer(1, 15))
# Create a comprehensive amenities table
amenities_list = property_data.get('amenities', [])
if amenities_list:
# Split amenities into two columns for better layout
mid_point = len(amenities_list) // 2
col1_amenities = amenities_list[:mid_point]
col2_amenities = amenities_list[mid_point:]
# Create table data
amenities_data = []
max_rows = max(len(col1_amenities), len(col2_amenities))
for i in range(max_rows):
col1_item = f"• {col1_amenities[i]}" if i < len(col1_amenities) else ""
col2_item = f"• {col2_amenities[i]}" if i < len(col2_amenities) else ""
amenities_data.append([col1_item, col2_item])
amenities_table = Table(amenities_data, colWidths=[3*inch, 3*inch])
amenities_table.setStyle(TableStyle([
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 11),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('LEFTPADDING', (0, 0), (-1, -1), 5),
('RIGHTPADDING', (0, 0), (-1, -1), 5),
('TOPPADDING', (0, 0), (-1, -1), 5),
('BOTTOMPADDING', (0, 0), (-1, -1), 5),
]))
story.append(amenities_table)
story.append(Spacer(1, 25))
# Additional Content
if property_data.get('additionalContent'):
story.append(Paragraph('Additional Information', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('additionalContent', ''), self.styles['PremiumContentText']))
story.append(Spacer(1, 20))
# Layout information if custom template
if property_data.get('layout'):
story.append(Paragraph('Layout Configuration', self.styles['SectionHeader']))
story.append(Paragraph(f"Selected Layout: {property_data.get('layout', 'Default').title()}", self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Customization details
customization_info = []
if property_data.get('headerStyle'):
customization_info.append(f"Header Style: {property_data.get('headerStyle', 'Modern').title()}")
if property_data.get('colorScheme'):
customization_info.append(f"Color Scheme: {property_data.get('colorScheme', 'Blue').title()}")
if property_data.get('fontStyle'):
customization_info.append(f"Font Style: {property_data.get('fontStyle', 'Sans-Serif').title()}")
if customization_info:
story.append(Paragraph('Document Customization', self.styles['SectionHeader']))
for info in customization_info:
story.append(Paragraph(f"• {info}", self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# PAGE BREAK TO START PAGE 5
story.append(PageBreak())
# PAGE 5: Final Images & Contact Information
story.append(Paragraph('Final Property Views & Contact', self.styles['UltraPremiumTitle']))
story.append(Spacer(1, 30))
# Add remaining images if available (3x3 grid for page 5 with room names)
if processed_images and len(processed_images) > 11:
story.append(Paragraph('Final Property Gallery', self.styles['SectionHeader']))
story.append(Spacer(1, 15))
# Create 3x3 grid for remaining images with room names
remaining_images = processed_images[11:20] # Take next 9 images
remaining_names = image_names[11:20] if len(image_names) > 11 else []
if remaining_images:
# Process images in rows of 3 with room names
for i in range(0, len(remaining_images), 3):
row_images = remaining_images[i:i+3]
row_names = remaining_names[i:i+3] if len(remaining_names) > i else []
# Create table with images and names
row_data = []
for j, img in enumerate(row_images):
room_name = row_names[j] if j < len(row_names) else f'Room {i+j+12}'
row_data.append([img, Paragraph(f"{room_name}", self.styles['PremiumContentText'])])
# Pad row to 3 columns if needed
while len(row_data) < 3:
row_data.append(['', ''])
row_table = Table(row_data, colWidths=[2*inch, 2*inch, 2*inch])
row_table.setStyle(TableStyle([
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('ALIGN', (0, 0), (-1, -1), 'CENTER'),
('LEFTPADDING', (0, 0), (-1, -1), 3),
('RIGHTPADDING', (0, 0), (-1, -1), 3),
]))
story.append(row_table)
story.append(Spacer(1, 10))
story.append(Spacer(1, 20))
# Investment Summary
story.append(Paragraph('Investment Summary', self.styles['SectionHeader']))
story.append(Spacer(1, 15))
investment_summary = f"This {property_data.get('propertyType', 'property')} in {property_data.get('location', 'prime location')} represents an excellent investment opportunity. "
if property_data.get('roiPotential'):
investment_summary += f"With an expected ROI of {property_data.get('roiPotential')}%, "
if property_data.get('rentalYield'):
investment_summary += f"and a rental yield of {property_data.get('rentalYield')}%, "
investment_summary += "this property offers strong potential for both capital appreciation and rental income."
story.append(Paragraph(investment_summary, self.styles['PremiumContentText']))
story.append(Spacer(1, 20))
# Contact Information / Footer
story.append(Paragraph('Contact Information', self.styles['SectionHeader']))
story.append(Paragraph("For more information about this property or to schedule a viewing, please contact our property specialists.", self.styles['PremiumContentText']))
story.append(Spacer(1, 10))
story.append(Paragraph("📧 Email: info@luxuryrealestate.com", self.styles['PremiumContentText']))
story.append(Paragraph("📞 Phone: +971 4 XXX XXXX", self.styles['PremiumContentText']))
story.append(Paragraph("🌐 Website: www.luxuryrealestate.com", self.styles['PremiumContentText']))
story.append(Spacer(1, 10))
story.append(Paragraph("Thank you for considering this exceptional property investment opportunity.", self.styles['PremiumContentText']))
return story
def _build_dubai_penthouse_brochure(self, property_data: Dict[str, Any], processed_images: List = None) -> List:
"""Build a Dubai penthouse brochure with detailed information"""
story = []
# Cover/Header Section
story.append(Paragraph(f"{property_data.get('propertyName', 'Dubai Penthouse')}", self.styles['UltraPremiumTitle']))
story.append(Paragraph(f"{property_data.get('propertyType', 'Dubai Penthouse')} • {property_data.get('location', 'Prime Location')}", self.styles['DubaiCollectionTitle']))
story.append(Spacer(1, 20))
# Price Section
price_color = "#c53030"
story.append(Paragraph(f"AED {property_data.get('price', 'On Request')}", self.styles['PremiumPriceDisplay']))
story.append(Spacer(1, 15))
# Property Details Table
details_data = [
['Property Type', property_data.get('propertyType', 'N/A')],
['Bedrooms', property_data.get('bedrooms', 'N/A')],
['Bathrooms', property_data.get('bathrooms', 'N/A')],
['Area', f"{property_data.get('area', 'N/A')} sq ft"],
]
# Add market data if available
if property_data.get('marketTrend'):
details_data.append(['Market Trend', property_data.get('marketTrend', 'N/A').title()])
if property_data.get('roiPotential'):
details_data.append(['ROI Potential', f"{property_data.get('roiPotential', 'N/A')}%"])
details_table = Table(details_data, colWidths=[3*inch, 3*inch])
details_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (0, -1), HexColor('#f8f9fa')),
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 12),
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('GRID', (0, 0), (-1, -1), 1, HexColor('#dee2e6')),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LEFTPADDING', (0, 0), (-1, -1), 12),
('RIGHTPADDING', (0, 0), (-1, -1), 12),
('TOPPADDING', (0, 0), (-1, -1), 8),
('BOTTOMPADDING', (0, 0), (-1, -1), 8),
]))
story.append(details_table)
story.append(Spacer(1, 20))
# Description
if property_data.get('description'):
story.append(Paragraph('Property Description', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('description', ''), self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Amenities
if property_data.get('amenities'):
story.append(Paragraph('Premium Amenities', self.styles['SectionHeader']))
amenities_text = ' • '.join(property_data.get('amenities', []))
story.append(Paragraph(amenities_text, self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Investment highlights
if property_data.get('investmentHighlights'):
story.append(Paragraph('Investment Highlights', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('investmentHighlights', ''), self.styles['PremiumContentText']))
return story
def _build_modern_apartment_brochure(self, property_data: Dict[str, Any], processed_images: List = None) -> List:
"""Build a modern apartment brochure with detailed information"""
story = []
# Cover/Header Section
story.append(Paragraph(f"{property_data.get('propertyName', 'Modern Apartment')}", self.styles['UltraPremiumTitle']))
story.append(Paragraph(f"{property_data.get('propertyType', 'Modern Apartment')} • {property_data.get('location', 'Prime Location')}", self.styles['ModernCollectionTitle']))
story.append(Spacer(1, 20))
# Price Section
price_color = "#4a5568"
story.append(Paragraph(f"AED {property_data.get('price', 'On Request')}", self.styles['PremiumPriceDisplay']))
story.append(Spacer(1, 15))
# Property Details Table
details_data = [
['Property Type', property_data.get('propertyType', 'N/A')],
['Bedrooms', property_data.get('bedrooms', 'N/A')],
['Bathrooms', property_data.get('bathrooms', 'N/A')],
['Area', f"{property_data.get('area', 'N/A')} sq ft"],
]
# Add market data if available
if property_data.get('marketTrend'):
details_data.append(['Market Trend', property_data.get('marketTrend', 'N/A').title()])
if property_data.get('roiPotential'):
details_data.append(['ROI Potential', f"{property_data.get('roiPotential', 'N/A')}%"])
details_table = Table(details_data, colWidths=[3*inch, 3*inch])
details_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (0, -1), HexColor('#f8f9fa')),
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 12),
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('GRID', (0, 0), (-1, -1), 1, HexColor('#dee2e6')),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LEFTPADDING', (0, 0), (-1, -1), 12),
('RIGHTPADDING', (0, 0), (-1, -1), 12),
('TOPPADDING', (0, 0), (-1, -1), 8),
('BOTTOMPADDING', (0, 0), (-1, -1), 8),
]))
story.append(details_table)
story.append(Spacer(1, 20))
# Description
if property_data.get('description'):
story.append(Paragraph('Property Description', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('description', ''), self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Amenities
if property_data.get('amenities'):
story.append(Paragraph('Premium Amenities', self.styles['SectionHeader']))
amenities_text = ' • '.join(property_data.get('amenities', []))
story.append(Paragraph(amenities_text, self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Investment highlights
if property_data.get('investmentHighlights'):
story.append(Paragraph('Investment Highlights', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('investmentHighlights', ''), self.styles['PremiumContentText']))
return story
def _build_custom_template_brochure(self, property_data: Dict[str, Any], processed_images: List = None) -> List:
"""Build a custom template brochure based on property data"""
story = []
# Cover/Header Section
story.append(Paragraph(f"{property_data.get('propertyName', 'Luxury Property')}", self.styles['UltraPremiumTitle']))
story.append(Paragraph(f"{property_data.get('propertyType', 'Premium Property')} • {property_data.get('location', 'Prime Location')}", self.styles['LuxuryCollectionTitle']))
story.append(Spacer(1, 20))
# Price Section
price_color = "#2c5530"
story.append(Paragraph(f"AED {property_data.get('price', 'On Request')}", self.styles['PremiumPriceDisplay']))
story.append(Spacer(1, 15))
# Property Details Table
details_data = [
['Property Type', property_data.get('propertyType', 'N/A')],
['Bedrooms', property_data.get('bedrooms', 'N/A')],
['Bathrooms', property_data.get('bathrooms', 'N/A')],
['Area', f"{property_data.get('area', 'N/A')} sq ft"],
]
# Add market data if available
if property_data.get('marketTrend'):
details_data.append(['Market Trend', property_data.get('marketTrend', 'N/A').title()])
if property_data.get('roiPotential'):
details_data.append(['ROI Potential', f"{property_data.get('roiPotential', 'N/A')}%"])
details_table = Table(details_data, colWidths=[3*inch, 3*inch])
details_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (0, -1), HexColor('#f8f9fa')),
('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
('FONTSIZE', (0, 0), (-1, -1), 12),
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('GRID', (0, 0), (-1, -1), 1, HexColor('#dee2e6')),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
('LEFTPADDING', (0, 0), (-1, -1), 12),
('RIGHTPADDING', (0, 0), (-1, -1), 12),
('TOPPADDING', (0, 0), (-1, -1), 8),
('BOTTOMPADDING', (0, 0), (-1, -1), 8),
]))
story.append(details_table)
story.append(Spacer(1, 20))
# Description
if property_data.get('description'):
story.append(Paragraph('Property Description', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('description', ''), self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Amenities
if property_data.get('amenities'):
story.append(Paragraph('Premium Amenities', self.styles['SectionHeader']))
amenities_text = ' • '.join(property_data.get('amenities', []))
story.append(Paragraph(amenities_text, self.styles['PremiumContentText']))
story.append(Spacer(1, 15))
# Investment highlights
if property_data.get('investmentHighlights'):
story.append(Paragraph('Investment Highlights', self.styles['SectionHeader']))
story.append(Paragraph(property_data.get('investmentHighlights', ''), self.styles['PremiumContentText']))
return story
def main():
"""Test the PDF generator with sample data"""
generator = PropertyPDFGenerator()
# Sample property data
sample_data = {
'propertyName': 'Luxury Marina Villa',
'propertyType': 'Villa',
'location': 'Dubai Marina',
'price': '5,500,000',
'bedrooms': '5',
'bathrooms': '6',
'area': '4,200',
'description': 'Stunning luxury villa with panoramic marina views, premium finishes, and exclusive amenities. This exceptional property offers the finest in luxury living.',
'amenities': ['Private Pool', 'Gym', 'Security', 'Garden', 'Garage', 'Smart Home', 'Concierge', 'Spa'],
'images': [],
'imageNames': []
}
# Generate luxury villa PDF
output_file = 'luxury_property_brochure.pdf'
try:
result = generator.generate_property_pdf(sample_data, 'luxury-villa', output_file)
print(f"Luxury villa PDF generated: {result}")
except Exception as e:
print(f"Error: {str(e)}")
# Generate modern apartment PDF
output_file = 'modern_property_brochure.pdf'
try:
result = generator.generate_property_pdf(sample_data, 'modern-apartment', output_file)
print(f"Modern apartment PDF generated: {result}")
except Exception as e:
print(f"Error: {str(e)}")
if __name__ == "__main__":
main()