1561 lines
78 KiB
Python
1561 lines
78 KiB
Python
#!/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"<font size='36'><b>{property_data.get('propertyName', 'Luxury Property')}</b></font>", self.styles['UltraPremiumTitle']))
|
|
story.append(Paragraph(f"<font size='18'>{property_data.get('propertyType', 'Premium Property')} • {property_data.get('location', 'Prime Location')}</font>", self.styles['LuxuryCollectionTitle']))
|
|
story.append(Spacer(1, 20))
|
|
|
|
# Price Section
|
|
price_color = "#2c5530"
|
|
story.append(Paragraph(f"<font size='28' color='{price_color}'><b>AED {property_data.get('price', 'On Request')}</b></font>", 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('<b>Property Description</b>', 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('<b>Investment Highlights</b>', 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('<b>Location Advantages</b>', 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('<b>Included Research & Analysis</b>', 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('<b>Property Gallery</b>', 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"<b>{room_name}</b>", 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('<b>Premium Amenities</b>', 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('<b>Additional Information</b>', 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('<b>Document Customization</b>', self.styles['SectionHeader']))
|
|
story.append(Paragraph(' • '.join(customization_info), self.styles['PremiumContentText']))
|
|
story.append(Spacer(1, 15))
|
|
|
|
# Contact Information
|
|
story.append(Paragraph('<b>Contact Information</b>', 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"<font size='42'><b>{property_data.get('propertyName', 'Premium Property')}</b></font>", self.styles['UltraPremiumTitle']))
|
|
story.append(Paragraph(f"<font size='20'>{property_data.get('propertyType', 'Luxury Property')} in {property_data.get('location', 'Prime Location')}</font>", 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('<b>Executive Summary</b>', 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('<b>Property Description</b>', 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('<b>Key Amenities</b>', 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('<b>Market Analysis & Investment Overview</b>', 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('<b>Property Gallery</b>', 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"<b>{room_name}</b>", 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"<b>{room_name}</b>", 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('<b>Market Intelligence</b>', 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('<b>Investment Analysis</b>', self.styles['SectionHeader']))
|
|
story.append(Spacer(1, 15))
|
|
|
|
if property_data.get('investmentType'):
|
|
story.append(Paragraph(f"<b>Investment Strategy:</b> {property_data.get('investmentType', '').replace('-', ' ').title()}", self.styles['PremiumContentText']))
|
|
story.append(Spacer(1, 10))
|
|
|
|
if property_data.get('rentalYield'):
|
|
story.append(Paragraph(f"<b>Expected Rental Yield:</b> {property_data.get('rentalYield')}% annually", self.styles['PremiumContentText']))
|
|
story.append(Spacer(1, 10))
|
|
|
|
if property_data.get('investmentHighlights'):
|
|
story.append(Paragraph('<b>Investment Highlights:</b>', 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('<b>Location Advantages</b>', 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('<b>Included Research & Analysis</b>', 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('<b>Complete Property Features & Amenities</b>', 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('<b>Additional Property Views</b>', 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"<b>{room_name}</b>", 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('<b>Complete Amenities List</b>', 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('<b>Additional Information</b>', 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('<b>Layout Configuration</b>', 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('<b>Document Customization</b>', 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('<b>Final Property Views & Contact</b>', 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('<b>Final Property Gallery</b>', 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"<b>{room_name}</b>", 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('<b>Investment Summary</b>', 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('<b>Contact Information</b>', 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"<font size='36'><b>{property_data.get('propertyName', 'Luxury Villa')}</b></font>", self.styles['UltraPremiumTitle']))
|
|
story.append(Paragraph(f"<font size='18'>{property_data.get('propertyType', 'Luxury Villa')} • {property_data.get('location', 'Prime Location')}</font>", self.styles['DubaiCollectionTitle']))
|
|
story.append(Spacer(1, 20))
|
|
|
|
# Price Section
|
|
price_color = "#c53030"
|
|
story.append(Paragraph(f"<font size='28' color='{price_color}'><b>AED {property_data.get('price', 'On Request')}</b></font>", 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('<b>Property Description</b>', 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('<b>Investment Highlights</b>', 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('<b>Location Advantages</b>', 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('<b>Included Research & Analysis</b>', 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('<b>Property Gallery</b>', 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"<b>{room_name}</b>", 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('<b>Premium Amenities</b>', 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('<b>Additional Information</b>', 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('<b>Document Customization</b>', self.styles['SectionHeader']))
|
|
story.append(Paragraph(' • '.join(customization_info), self.styles['PremiumContentText']))
|
|
story.append(Spacer(1, 15))
|
|
|
|
# Contact Information
|
|
story.append(Paragraph('<b>Contact Information</b>', 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"<font size='42'><b>{property_data.get('propertyName', 'Premium Property')}</b></font>", self.styles['UltraPremiumTitle']))
|
|
story.append(Paragraph(f"<font size='20'>{property_data.get('propertyType', 'Luxury Property')} in {property_data.get('location', 'Prime Location')}</font>", 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('<b>Executive Summary</b>', 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('<b>Property Description</b>', 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('<b>Key Amenities</b>', 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('<b>Market Analysis & Investment Overview</b>', 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('<b>Property Gallery - Main Views</b>', 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"<b>{room_name}</b>", 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"<b>{room_name}</b>", 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('<b>Market Intelligence</b>', 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('<b>Investment Analysis</b>', self.styles['SectionHeader']))
|
|
story.append(Spacer(1, 15))
|
|
|
|
if property_data.get('investmentType'):
|
|
story.append(Paragraph(f"<b>Investment Strategy:</b> {property_data.get('investmentType', '').replace('-', ' ').title()}", self.styles['PremiumContentText']))
|
|
story.append(Spacer(1, 10))
|
|
|
|
if property_data.get('rentalYield'):
|
|
story.append(Paragraph(f"<b>Expected Rental Yield:</b> {property_data.get('rentalYield')}% annually", self.styles['PremiumContentText']))
|
|
story.append(Spacer(1, 10))
|
|
|
|
if property_data.get('investmentHighlights'):
|
|
story.append(Paragraph('<b>Investment Highlights:</b>', 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('<b>Location Advantages</b>', 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('<b>Included Research & Analysis</b>', 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('<b>Property Features & Additional Views</b>', 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('<b>Additional Property Views</b>', 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"<b>{room_name}</b>", 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('<b>Detailed Property Features</b>', 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('<b>Complete Amenities & Features</b>', self.styles['UltraPremiumTitle']))
|
|
story.append(Spacer(1, 30))
|
|
|
|
# All Amenities in organized format
|
|
if property_data.get('amenities'):
|
|
story.append(Paragraph('<b>Complete Amenities List</b>', 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('<b>Additional Information</b>', 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('<b>Layout Configuration</b>', 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('<b>Document Customization</b>', 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('<b>Final Property Views & Contact</b>', 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('<b>Final Property Gallery</b>', 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"<b>{room_name}</b>", 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('<b>Investment Summary</b>', 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('<b>Contact Information</b>', 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"<font size='36'><b>{property_data.get('propertyName', 'Dubai Penthouse')}</b></font>", self.styles['UltraPremiumTitle']))
|
|
story.append(Paragraph(f"<font size='18'>{property_data.get('propertyType', 'Dubai Penthouse')} • {property_data.get('location', 'Prime Location')}</font>", self.styles['DubaiCollectionTitle']))
|
|
story.append(Spacer(1, 20))
|
|
|
|
# Price Section
|
|
price_color = "#c53030"
|
|
story.append(Paragraph(f"<font size='28' color='{price_color}'><b>AED {property_data.get('price', 'On Request')}</b></font>", 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('<b>Property Description</b>', 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('<b>Premium Amenities</b>', 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('<b>Investment Highlights</b>', 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"<font size='36'><b>{property_data.get('propertyName', 'Modern Apartment')}</b></font>", self.styles['UltraPremiumTitle']))
|
|
story.append(Paragraph(f"<font size='18'>{property_data.get('propertyType', 'Modern Apartment')} • {property_data.get('location', 'Prime Location')}</font>", self.styles['ModernCollectionTitle']))
|
|
story.append(Spacer(1, 20))
|
|
|
|
# Price Section
|
|
price_color = "#4a5568"
|
|
story.append(Paragraph(f"<font size='28' color='{price_color}'><b>AED {property_data.get('price', 'On Request')}</b></font>", 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('<b>Property Description</b>', 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('<b>Premium Amenities</b>', 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('<b>Investment Highlights</b>', 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"<font size='36'><b>{property_data.get('propertyName', 'Luxury Property')}</b></font>", self.styles['UltraPremiumTitle']))
|
|
story.append(Paragraph(f"<font size='18'>{property_data.get('propertyType', 'Premium Property')} • {property_data.get('location', 'Prime Location')}</font>", self.styles['LuxuryCollectionTitle']))
|
|
story.append(Spacer(1, 20))
|
|
|
|
# Price Section
|
|
price_color = "#2c5530"
|
|
story.append(Paragraph(f"<font size='28' color='{price_color}'><b>AED {property_data.get('price', 'On Request')}</b></font>", 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('<b>Property Description</b>', 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('<b>Premium Amenities</b>', 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('<b>Investment Highlights</b>', 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() |