#!/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()