758 lines
32 KiB
Python
758 lines
32 KiB
Python
"""
|
||
Prisa Yachts LLC — Bilingual PDF Brochure Generator
|
||
Generates 3 professional PDFs using ReportLab.
|
||
"""
|
||
|
||
import os
|
||
from reportlab.lib.pagesizes import letter, landscape
|
||
from reportlab.lib.units import inch
|
||
from reportlab.lib.colors import Color, HexColor, white, black
|
||
from reportlab.pdfgen import canvas
|
||
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
||
from reportlab.platypus import Paragraph
|
||
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT, TA_JUSTIFY
|
||
from reportlab.pdfbase import pdfmetrics
|
||
from reportlab.pdfbase.ttfonts import TTFont
|
||
from PIL import Image as PILImage
|
||
import textwrap
|
||
|
||
# ── Brand constants ────────────────────────────────────────────────────────────
|
||
NAVY = HexColor('#1B3A6B')
|
||
GOLD = HexColor('#C9A84C')
|
||
WHITE = HexColor('#FFFFFF')
|
||
LIGHT_NAVY = HexColor('#2A5298')
|
||
LIGHT_GRAY = HexColor('#F5F5F5')
|
||
DARK_GRAY = HexColor('#333333')
|
||
|
||
LOGO_PATH = r"D:\Prisa Yachts LLC\WhatsApp Image 2026-03-11 at 22.24.00.jpeg"
|
||
OUTPUT_DIR = r"D:\Proyectos Software\Agente Marketing\prisa-yachts\brochures"
|
||
|
||
TAGLINE = "Safe Command ◇ Luxury Maintenance and Care"
|
||
WEBSITE = "prisayachts.com"
|
||
AREA = "Stuart to Jacksonville, FL"
|
||
|
||
# ── Helper: draw logo or text placeholder ─────────────────────────────────────
|
||
def draw_logo(c, x, y, width, height, centered=True):
|
||
"""Draw the logo image; fall back to a styled text block if unavailable."""
|
||
if os.path.exists(LOGO_PATH):
|
||
try:
|
||
if centered:
|
||
c.drawImage(LOGO_PATH, x - width / 2, y - height,
|
||
width=width, height=height,
|
||
preserveAspectRatio=True, mask='auto')
|
||
else:
|
||
c.drawImage(LOGO_PATH, x, y - height,
|
||
width=width, height=height,
|
||
preserveAspectRatio=True, mask='auto')
|
||
return
|
||
except Exception:
|
||
pass
|
||
# Fallback placeholder
|
||
bx = x - width / 2 if centered else x
|
||
by = y - height
|
||
c.setFillColor(NAVY)
|
||
c.rect(bx, by, width, height, fill=1, stroke=0)
|
||
c.setFillColor(GOLD)
|
||
c.setFont("Helvetica-Bold", 10)
|
||
c.drawCentredString(bx + width / 2, by + height / 2 - 4, "PRISA YACHTS LLC")
|
||
c.setFont("Helvetica", 7)
|
||
c.setFillColor(WHITE)
|
||
c.drawCentredString(bx + width / 2, by + height / 2 - 16, "prisayachts.com")
|
||
|
||
|
||
def draw_gold_line(c, x1, y, x2, thickness=1.0):
|
||
c.setStrokeColor(GOLD)
|
||
c.setLineWidth(thickness)
|
||
c.line(x1, y, x2, y)
|
||
|
||
|
||
def draw_gold_divider(c, x, y, width, thickness=1.0):
|
||
draw_gold_line(c, x, y, x + width, thickness)
|
||
|
||
|
||
# ── Text wrapping helper ───────────────────────────────────────────────────────
|
||
def draw_wrapped_text(c, text, x, y, max_width, font_name, font_size,
|
||
line_height, color=None, align='left'):
|
||
"""Draw text wrapping within max_width, returns final y position."""
|
||
if color:
|
||
c.setFillColor(color)
|
||
c.setFont(font_name, font_size)
|
||
words = text.split()
|
||
lines = []
|
||
current = []
|
||
for word in words:
|
||
test_line = ' '.join(current + [word])
|
||
if c.stringWidth(test_line, font_name, font_size) <= max_width:
|
||
current.append(word)
|
||
else:
|
||
if current:
|
||
lines.append(' '.join(current))
|
||
current = [word]
|
||
if current:
|
||
lines.append(' '.join(current))
|
||
for line in lines:
|
||
if align == 'center':
|
||
c.drawCentredString(x + max_width / 2, y, line)
|
||
elif align == 'right':
|
||
c.drawRightString(x + max_width, y, line)
|
||
else:
|
||
c.drawString(x, y, line)
|
||
y -= line_height
|
||
return y
|
||
|
||
|
||
def draw_section_header(c, text, x, y, width, bg_color=NAVY, text_color=GOLD,
|
||
font_size=11, padding=5):
|
||
"""Draw a colored section header bar."""
|
||
bar_h = font_size + padding * 2
|
||
c.setFillColor(bg_color)
|
||
c.rect(x, y - bar_h, width, bar_h, fill=1, stroke=0)
|
||
c.setFillColor(text_color)
|
||
c.setFont("Helvetica-Bold", font_size)
|
||
c.drawString(x + padding + 2, y - bar_h + padding, text)
|
||
return y - bar_h
|
||
|
||
|
||
def draw_bullet_item(c, text, x, y, max_width, font_name="Helvetica",
|
||
font_size=8.5, line_height=12, bullet_color=GOLD,
|
||
text_color=DARK_GRAY):
|
||
"""Draw a gold diamond bullet followed by wrapped text."""
|
||
bullet = "◇ "
|
||
indent = 14
|
||
c.setFont("Helvetica-Bold", font_size)
|
||
c.setFillColor(bullet_color)
|
||
c.drawString(x, y, "◇")
|
||
y = draw_wrapped_text(c, text, x + indent, y,
|
||
max_width - indent, font_name, font_size,
|
||
line_height, color=text_color)
|
||
return y - 2
|
||
|
||
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
# BROCHURE 1 — General Services (landscape letter, 3 columns)
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
def generate_brochure1():
|
||
path = os.path.join(OUTPUT_DIR, "PrisaYachts_General_Services.pdf")
|
||
pw, ph = landscape(letter) # 792 x 612 pts
|
||
c = canvas.Canvas(path, pagesize=landscape(letter))
|
||
c.setTitle("Prisa Yachts LLC — Professional Marine Services")
|
||
c.setAuthor("Prisa Yachts LLC")
|
||
c.setSubject("General Marine Services Brochure EN/ES")
|
||
|
||
margin = 0.35 * inch
|
||
col_gap = 0.18 * inch
|
||
usable_w = pw - 2 * margin
|
||
left_col_w = usable_w * 0.24
|
||
center_col_w = usable_w * 0.38
|
||
right_col_w = usable_w - left_col_w - center_col_w - 2 * col_gap
|
||
|
||
left_x = margin
|
||
center_x = left_x + left_col_w + col_gap
|
||
right_x = center_x + center_col_w + col_gap
|
||
top_y = ph - margin
|
||
bottom_y = margin
|
||
|
||
# ── Full background white ──
|
||
c.setFillColor(WHITE)
|
||
c.rect(0, 0, pw, ph, fill=1, stroke=0)
|
||
|
||
# ── LEFT COLUMN — navy background ────────────────────────────────────────
|
||
col_h = ph - 2 * margin
|
||
c.setFillColor(NAVY)
|
||
c.rect(left_x, bottom_y, left_col_w, col_h, fill=1, stroke=0)
|
||
|
||
# Logo
|
||
logo_y = top_y - 0.15 * inch
|
||
logo_w = left_col_w - 0.3 * inch
|
||
logo_h = 1.0 * inch
|
||
draw_logo(c, left_x + left_col_w / 2, logo_y, logo_w, logo_h, centered=True)
|
||
cur_y = logo_y - logo_h - 0.1 * inch
|
||
|
||
# Company name
|
||
c.setFillColor(WHITE)
|
||
c.setFont("Helvetica-Bold", 12)
|
||
c.drawCentredString(left_x + left_col_w / 2, cur_y, "Prisa Yachts LLC")
|
||
cur_y -= 0.22 * inch
|
||
|
||
# Tagline (gold italic, wrapped)
|
||
c.setFillColor(GOLD)
|
||
c.setFont("Helvetica-Oblique", 7.5)
|
||
tagline_parts = ["Safe Command ◇", "Luxury Maintenance and Care"]
|
||
for part in tagline_parts:
|
||
c.drawCentredString(left_x + left_col_w / 2, cur_y, part)
|
||
cur_y -= 0.16 * inch
|
||
cur_y -= 0.05 * inch
|
||
|
||
# Service area
|
||
c.setFillColor(WHITE)
|
||
c.setFont("Helvetica", 7.5)
|
||
c.drawCentredString(left_x + left_col_w / 2, cur_y, AREA)
|
||
cur_y -= 0.18 * inch
|
||
|
||
# Gold divider
|
||
pad = 0.15 * inch
|
||
draw_gold_divider(c, left_x + pad, cur_y, left_col_w - 2 * pad, 1.5)
|
||
cur_y -= 0.2 * inch
|
||
|
||
# Contact blocks
|
||
contacts = [
|
||
("Alberto", "(954) 655-4084", "technical@prisayachts.com"),
|
||
("Federico", "(754) 209-3375", "management@prisayachts.com"),
|
||
]
|
||
for name, phone, email in contacts:
|
||
c.setFillColor(GOLD)
|
||
c.setFont("Helvetica-Bold", 8.5)
|
||
c.drawCentredString(left_x + left_col_w / 2, cur_y, name)
|
||
cur_y -= 0.15 * inch
|
||
c.setFillColor(WHITE)
|
||
c.setFont("Helvetica", 7.5)
|
||
c.drawCentredString(left_x + left_col_w / 2, cur_y, phone)
|
||
cur_y -= 0.14 * inch
|
||
c.setFont("Helvetica", 6.8)
|
||
c.drawCentredString(left_x + left_col_w / 2, cur_y, email)
|
||
cur_y -= 0.2 * inch
|
||
|
||
# Gold divider
|
||
draw_gold_divider(c, left_x + pad, cur_y, left_col_w - 2 * pad, 1.0)
|
||
cur_y -= 0.18 * inch
|
||
|
||
# Website
|
||
c.setFillColor(GOLD)
|
||
c.setFont("Helvetica-Bold", 8)
|
||
c.drawCentredString(left_x + left_col_w / 2, cur_y, WEBSITE)
|
||
|
||
# ── CENTER COLUMN — English services ─────────────────────────────────────
|
||
# Column background (very light)
|
||
c.setFillColor(WHITE)
|
||
c.rect(center_x, bottom_y, center_col_w, col_h, fill=1, stroke=0)
|
||
|
||
# Thin navy left border
|
||
c.setFillColor(NAVY)
|
||
c.rect(center_x, bottom_y, 3, col_h, fill=1, stroke=0)
|
||
|
||
cur_y = top_y - 0.15 * inch
|
||
|
||
# Header
|
||
c.setFillColor(NAVY)
|
||
c.setFont("Helvetica-Bold", 14)
|
||
c.drawString(center_x + 0.15 * inch, cur_y, "Professional Marine Services")
|
||
cur_y -= 0.12 * inch
|
||
draw_gold_divider(c, center_x + 0.15 * inch, cur_y,
|
||
center_col_w - 0.2 * inch, 2.0)
|
||
cur_y -= 0.22 * inch
|
||
|
||
en_services = [
|
||
"Marine Electrical (ABYC E-11 Certified)",
|
||
"Lithium Battery Bank Design & Installation",
|
||
"Shore Power & Inverter/Charger Systems",
|
||
"Solar & Alternator Upgrades",
|
||
"Full Rewiring (Tinned Wire, Labeled Circuits)",
|
||
"Teak Deck Recovery & Restoration",
|
||
"Gelcoat Polishing & Oxidation Removal",
|
||
"Washdown System Installation",
|
||
"Anchor Windlass & Bilge Systems",
|
||
"NMEA 2000 / 0183 Integration",
|
||
"Pre-Season Inspection & Annual Service",
|
||
]
|
||
|
||
bx = center_x + 0.15 * inch
|
||
bw = center_col_w - 0.25 * inch
|
||
for svc in en_services:
|
||
cur_y = draw_bullet_item(c, svc, bx, cur_y, bw,
|
||
font_size=8.5, line_height=11.5)
|
||
cur_y -= 1
|
||
|
||
# Footer band
|
||
footer_h = 0.55 * inch
|
||
footer_y = bottom_y
|
||
c.setFillColor(LIGHT_GRAY)
|
||
c.rect(center_x, footer_y, center_col_w, footer_h, fill=1, stroke=0)
|
||
draw_gold_divider(c, center_x, footer_y + footer_h,
|
||
center_col_w, 1.5)
|
||
c.setFillColor(NAVY)
|
||
c.setFont("Helvetica-Oblique", 7)
|
||
footer_text = ("All electrical work performed to ABYC E-11 standards. "
|
||
"Bilingual service EN/ES.")
|
||
c.drawString(center_x + 0.1 * inch, footer_y + 0.28 * inch, footer_text)
|
||
|
||
# ── RIGHT COLUMN — Spanish services ──────────────────────────────────────
|
||
c.setFillColor(WHITE)
|
||
c.rect(right_x, bottom_y, right_col_w, col_h, fill=1, stroke=0)
|
||
|
||
# Thin gold left border
|
||
c.setFillColor(GOLD)
|
||
c.rect(right_x, bottom_y, 3, col_h, fill=1, stroke=0)
|
||
|
||
cur_y = top_y - 0.15 * inch
|
||
|
||
c.setFillColor(NAVY)
|
||
c.setFont("Helvetica-Bold", 14)
|
||
c.drawString(right_x + 0.15 * inch, cur_y, "Servicios Marinos Profesionales")
|
||
cur_y -= 0.12 * inch
|
||
draw_gold_divider(c, right_x + 0.15 * inch, cur_y,
|
||
right_col_w - 0.2 * inch, 2.0)
|
||
cur_y -= 0.22 * inch
|
||
|
||
es_services = [
|
||
"Instalaciones Eléctricas Marinas (Estándar ABYC E-11)",
|
||
"Diseño e Instalación de Banco de Baterías de Litio",
|
||
"Sistemas de Alimentación en Puerto e Inversor/Cargador",
|
||
"Mejoras Solar y de Alternador",
|
||
"Recableado Completo (Cable Estañado, Circuitos Etiquetados)",
|
||
"Recuperación y Restauración de Teca",
|
||
"Pulido de Gelcoat y Eliminación de Oxidación",
|
||
"Instalación de Sistema Washdown",
|
||
"Sistemas de Molinete y Achique",
|
||
"Integración NMEA 2000 / 0183",
|
||
"Inspección Pre-Temporada y Servicio Anual",
|
||
]
|
||
|
||
bx2 = right_x + 0.15 * inch
|
||
bw2 = right_col_w - 0.25 * inch
|
||
for svc in es_services:
|
||
cur_y = draw_bullet_item(c, svc, bx2, cur_y, bw2,
|
||
font_size=8.5, line_height=11.5)
|
||
cur_y -= 1
|
||
|
||
# Footer band
|
||
c.setFillColor(LIGHT_GRAY)
|
||
c.rect(right_x, footer_y, right_col_w, footer_h, fill=1, stroke=0)
|
||
draw_gold_divider(c, right_x, footer_y + footer_h, right_col_w, 1.5)
|
||
c.setFillColor(NAVY)
|
||
c.setFont("Helvetica-Oblique", 7)
|
||
es_footer = ("Todo trabajo eléctrico realizado según estándar ABYC E-11. "
|
||
"Servicio bilingüe EN/ES.")
|
||
c.drawString(right_x + 0.1 * inch, footer_y + 0.28 * inch, es_footer)
|
||
|
||
c.save()
|
||
print(f" [OK] {path}")
|
||
return path
|
||
|
||
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
# BROCHURE 2 — Marine Electrical (portrait letter, 2 columns)
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
def generate_brochure2():
|
||
path = os.path.join(OUTPUT_DIR, "PrisaYachts_Electrical.pdf")
|
||
pw, ph = letter # 612 x 792 pts
|
||
c = canvas.Canvas(path, pagesize=letter)
|
||
c.setTitle("Prisa Yachts LLC — Marine Electrical Services")
|
||
c.setAuthor("Prisa Yachts LLC")
|
||
c.setSubject("Marine Electrical Brochure EN/ES")
|
||
|
||
margin = 0.4 * inch
|
||
col_gap = 0.2 * inch
|
||
usable_w = pw - 2 * margin
|
||
col_w = (usable_w - col_gap) / 2
|
||
|
||
# ── Header band ───────────────────────────────────────────────────────────
|
||
header_h = 1.05 * inch
|
||
header_y = ph - header_h
|
||
c.setFillColor(NAVY)
|
||
c.rect(0, header_y, pw, header_h, fill=1, stroke=0)
|
||
|
||
# Gold bottom border on header
|
||
c.setStrokeColor(GOLD)
|
||
c.setLineWidth(2.5)
|
||
c.line(0, header_y, pw, header_y)
|
||
|
||
# Logo in header (left side)
|
||
draw_logo(c, margin, ph - 0.08 * inch, 1.5 * inch, 0.85 * inch, centered=False)
|
||
|
||
# Title center
|
||
c.setFillColor(WHITE)
|
||
c.setFont("Helvetica-Bold", 18)
|
||
c.drawCentredString(pw / 2, ph - 0.42 * inch, "Marine Electrical Services")
|
||
|
||
# ABYC badge right
|
||
c.setFillColor(GOLD)
|
||
c.setFont("Helvetica-Bold", 9.5)
|
||
c.drawRightString(pw - margin, ph - 0.38 * inch, "ABYC E-11 Certified")
|
||
c.setFillColor(WHITE)
|
||
c.setFont("Helvetica", 8)
|
||
c.drawRightString(pw - margin, ph - 0.55 * inch, "Stuart to Jacksonville, FL")
|
||
|
||
# ── Body columns ─────────────────────────────────────────────────────────
|
||
body_top = header_y - 0.22 * inch
|
||
footer_h = 0.55 * inch
|
||
body_bottom = margin + footer_h + 0.1 * inch
|
||
|
||
left_x = margin
|
||
right_x = margin + col_w + col_gap
|
||
|
||
# Vertical gold divider between columns
|
||
mid_x = margin + col_w + col_gap / 2
|
||
c.setStrokeColor(GOLD)
|
||
c.setLineWidth(1.0)
|
||
c.line(mid_x, body_bottom, mid_x, body_top)
|
||
|
||
def draw_electrical_column(start_x, cur_y, lang='en'):
|
||
cw = col_w - 0.05 * inch
|
||
line_sm = 10.5
|
||
line_md = 12
|
||
|
||
if lang == 'en':
|
||
c.setFillColor(NAVY)
|
||
c.setFont("Helvetica-Bold", 10.5)
|
||
c.drawString(start_x, cur_y, "Expert Marine Electrical — Stuart to Jacksonville")
|
||
cur_y -= 0.18 * inch
|
||
draw_gold_divider(c, start_x, cur_y, cw, 1.5)
|
||
cur_y -= 0.18 * inch
|
||
|
||
sections = [
|
||
("WHY ABYC CERTIFIED WORK MATTERS",
|
||
"Marine electrical failures are the #1 cause of boat fires. Every "
|
||
"installation we do meets or exceeds ABYC E-11 standards: correct wire "
|
||
"gauge, tinned marine-grade wire, properly sized overcurrent protection, "
|
||
"and labeled circuits from stem to stern."),
|
||
("FULL REWIRING",
|
||
"Complete vessel rewiring using marine-grade tinned wire. Correct gauge "
|
||
"per ABYC E-11. Every circuit labeled and documented."),
|
||
("LITHIUM BATTERY BANKS",
|
||
"Custom LiFePO4 battery bank design and installation. BMS integration, "
|
||
"proper charge profiles, and full system documentation."),
|
||
("SOLAR & SHORE POWER",
|
||
"Solar panel installation with MPPT charge controllers. Shore power "
|
||
"systems, isolation transformers, and inverter/charger integration."),
|
||
("SYSTEMS & ELECTRONICS",
|
||
"NMEA 2000 backbone design and installation. Bilge pumps, washdown "
|
||
"systems, anchor windlass, lighting upgrades."),
|
||
("DIAGNOSTICS & INSPECTION",
|
||
"Pre-purchase electrical inspection. Annual safety audit. "
|
||
"Load testing and fault finding."),
|
||
]
|
||
icons = ["⚡", "⚡", "\U0001f50b", "☀️", "\U0001f50c", "\U0001f50d"]
|
||
else:
|
||
c.setFillColor(NAVY)
|
||
c.setFont("Helvetica-Bold", 10.5)
|
||
c.drawString(start_x, cur_y,
|
||
"Instalaciones Eléctricas Marinas — Stuart a Jacksonville")
|
||
cur_y -= 0.18 * inch
|
||
draw_gold_divider(c, start_x, cur_y, cw, 1.5)
|
||
cur_y -= 0.18 * inch
|
||
|
||
sections = [
|
||
("POR QUÉ IMPORTA EL ESTÁNDAR ABYC",
|
||
"Los fallos eléctricos son la causa número 1 de incendios en embarcaciones. "
|
||
"Cada instalación que realizamos cumple o supera el estándar ABYC E-11: "
|
||
"calibre de cable correcto, cable marino estañado, protección contra "
|
||
"sobrecorriente correctamente dimensionada, y circuitos etiquetados de proa a popa."),
|
||
("RECABLEADO COMPLETO",
|
||
"Recableado total de la embarcación con cable estañado de grado marino. "
|
||
"Calibre correcto según ABYC E-11. Cada circuito etiquetado y documentado."),
|
||
("BANCO DE BATERÍAS DE LITIO",
|
||
"Diseño e instalación personalizada de banco LiFePO4. Integración de BMS, "
|
||
"perfiles de carga correctos y documentación completa del sistema."),
|
||
("SOLAR Y ALIMENTACIÓN EN PUERTO",
|
||
"Instalación de paneles solares con controladores MPPT. Sistemas de shore power, "
|
||
"transformadores de aislamiento e integración inversor/cargador."),
|
||
("SISTEMAS Y ELECTRÓNICA",
|
||
"Diseño e instalación de backbone NMEA 2000. Bombas de achique, sistemas "
|
||
"washdown, molinete de ancla, mejoras de iluminación."),
|
||
("DIAGNÓSTICO E INSPECCIÓN",
|
||
"Inspección eléctrica previa a compra. Auditoría de seguridad anual. "
|
||
"Pruebas de carga y localización de fallos."),
|
||
]
|
||
icons = ["⚡", "⚡", "\U0001f50b", "☀️", "\U0001f50c", "\U0001f50d"]
|
||
|
||
for i, (title, body) in enumerate(sections):
|
||
if i == 0:
|
||
# First section: intro paragraph, no icon
|
||
c.setFillColor(NAVY)
|
||
c.setFont("Helvetica-Bold", 8.5)
|
||
c.drawString(start_x, cur_y, title)
|
||
cur_y -= 0.14 * inch
|
||
cur_y = draw_wrapped_text(c, body, start_x, cur_y, cw,
|
||
"Helvetica", 8, line_sm, color=DARK_GRAY)
|
||
cur_y -= 0.12 * inch
|
||
else:
|
||
# Service sections with colored mini-header
|
||
bar_h = 14
|
||
c.setFillColor(NAVY)
|
||
c.rect(start_x, cur_y - bar_h + 3, cw, bar_h, fill=1, stroke=0)
|
||
c.setFillColor(GOLD)
|
||
c.setFont("Helvetica-Bold", 8.5)
|
||
c.drawString(start_x + 4, cur_y - bar_h + 6, title)
|
||
cur_y -= bar_h + 2
|
||
|
||
cur_y = draw_wrapped_text(c, body, start_x + 4, cur_y, cw - 4,
|
||
"Helvetica", 7.8, line_sm, color=DARK_GRAY)
|
||
cur_y -= 0.1 * inch
|
||
|
||
# Quote
|
||
cur_y -= 0.05 * inch
|
||
draw_gold_divider(c, start_x, cur_y, cw, 1.0)
|
||
cur_y -= 0.16 * inch
|
||
if lang == 'en':
|
||
quote = '"Every wire we touch, we own. No shortcuts."'
|
||
else:
|
||
quote = '"Cada cable que tocamos, lo respaldamos. Sin atajos."'
|
||
c.setFillColor(NAVY)
|
||
c.setFont("Helvetica-BoldOblique", 8)
|
||
cur_y = draw_wrapped_text(c, quote, start_x, cur_y, cw,
|
||
"Helvetica-BoldOblique", 8, 11, color=NAVY)
|
||
return cur_y
|
||
|
||
draw_electrical_column(left_x, body_top, 'en')
|
||
draw_electrical_column(right_x, body_top, 'es')
|
||
|
||
# ── Footer ────────────────────────────────────────────────────────────────
|
||
c.setFillColor(NAVY)
|
||
c.rect(0, 0, pw, margin + footer_h, fill=1, stroke=0)
|
||
c.setStrokeColor(GOLD)
|
||
c.setLineWidth(1.5)
|
||
c.line(0, margin + footer_h, pw, margin + footer_h)
|
||
|
||
c.setFillColor(WHITE)
|
||
c.setFont("Helvetica", 8)
|
||
c.drawString(margin, margin + footer_h - 0.18 * inch,
|
||
"Alberto | (954) 655-4084 | technical@prisayachts.com")
|
||
c.setFillColor(GOLD)
|
||
c.setFont("Helvetica-Bold", 8.5)
|
||
c.drawRightString(pw - margin, margin + footer_h - 0.18 * inch, WEBSITE)
|
||
c.setFillColor(WHITE)
|
||
c.setFont("Helvetica", 7)
|
||
c.drawCentredString(pw / 2, margin + 0.15 * inch, AREA)
|
||
|
||
c.save()
|
||
print(f" [OK] {path}")
|
||
return path
|
||
|
||
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
# BROCHURE 3 — Teak & Detailing (portrait letter, 2 columns)
|
||
# ══════════════════════════════════════════════════════════════════════════════
|
||
def generate_brochure3():
|
||
path = os.path.join(OUTPUT_DIR, "PrisaYachts_Teak_Detailing.pdf")
|
||
pw, ph = letter
|
||
c = canvas.Canvas(path, pagesize=letter)
|
||
c.setTitle("Prisa Yachts LLC — Teak Restoration & Marine Detailing")
|
||
c.setAuthor("Prisa Yachts LLC")
|
||
c.setSubject("Teak & Detailing Brochure EN/ES")
|
||
|
||
margin = 0.4 * inch
|
||
col_gap = 0.2 * inch
|
||
usable_w = pw - 2 * margin
|
||
col_w = (usable_w - col_gap) / 2
|
||
|
||
# ── Header band ───────────────────────────────────────────────────────────
|
||
header_h = 1.05 * inch
|
||
header_y = ph - header_h
|
||
c.setFillColor(NAVY)
|
||
c.rect(0, header_y, pw, header_h, fill=1, stroke=0)
|
||
|
||
c.setStrokeColor(GOLD)
|
||
c.setLineWidth(2.5)
|
||
c.line(0, header_y, pw, header_y)
|
||
|
||
draw_logo(c, margin, ph - 0.08 * inch, 1.5 * inch, 0.85 * inch, centered=False)
|
||
|
||
c.setFillColor(WHITE)
|
||
c.setFont("Helvetica-Bold", 16)
|
||
c.drawCentredString(pw / 2, ph - 0.4 * inch, "Teak Restoration & Marine Detailing")
|
||
|
||
c.setFillColor(GOLD)
|
||
c.setFont("Helvetica-Bold", 9)
|
||
c.drawRightString(pw - margin, ph - 0.38 * inch, "Stuart to Jacksonville")
|
||
c.setFillColor(WHITE)
|
||
c.setFont("Helvetica", 8)
|
||
c.drawRightString(pw - margin, ph - 0.55 * inch, "Florida's Saltwater Environment")
|
||
|
||
# ── Body ─────────────────────────────────────────────────────────────────
|
||
body_top = header_y - 0.22 * inch
|
||
footer_h = 0.55 * inch
|
||
body_bottom = margin + footer_h + 0.1 * inch
|
||
|
||
left_x = margin
|
||
right_x = margin + col_w + col_gap
|
||
|
||
# Vertical gold divider
|
||
mid_x = margin + col_w + col_gap / 2
|
||
c.setStrokeColor(GOLD)
|
||
c.setLineWidth(1.0)
|
||
c.line(mid_x, body_bottom, mid_x, body_top)
|
||
|
||
def draw_teak_column(start_x, cur_y, lang='en'):
|
||
cw = col_w - 0.05 * inch
|
||
line_sm = 10.5
|
||
|
||
if lang == 'en':
|
||
subtitle = "Teak & Detailing Specialists — Florida’s Saltwater Environment"
|
||
h2 = "IS YOUR TEAK WORTH SAVING?"
|
||
p1 = ("Gray teak isn’t always dead teak. Before we quote any job, we do an honest "
|
||
"hands-on assessment — and we’ll tell you straight if recovery makes "
|
||
"financial sense or if replacement is the smarter move. Most teak we see in "
|
||
"Florida is recoverable. It just needs the right process.")
|
||
steps_title = "THE RECOVERY PROCESS"
|
||
steps = [
|
||
("Step 1 — CLEAN",
|
||
"Two-part oxalic acid cleaner. Part A removes oxidation and gray surface "
|
||
"cells. Part B neutralizes pH. Clean canvas only — never cover problems."),
|
||
("Step 2 — SAND",
|
||
"80-grit to open the grain. 120-grit to smooth. Always with the grain. "
|
||
"Never cross-grain — water traps are invisible until they rot your plank from inside."),
|
||
("Step 3 — OIL",
|
||
"Penetrating marine teak oil, thin coats, wiped before skinning. "
|
||
"Feeds the wood from inside. Conditions, doesn’t coat."),
|
||
("Step 4 — SEAL",
|
||
"UV-blocking teak sealer on all horizontal surfaces. Florida sun reoxidizes "
|
||
"bare teak in one season. Proper sealing buys 12–18 months."),
|
||
]
|
||
det_title = "DETAILING SERVICES"
|
||
det_items = [
|
||
"Gelcoat oxidation removal (light & heavy cut)",
|
||
"Machine polish & wax — DA and rotary",
|
||
"Hull cleaning & brightwork",
|
||
"Caulking inspection & reseam",
|
||
"Full exterior detail packages",
|
||
]
|
||
quote = '"We’ve restored teak that owners wanted to scrap. Send us a photo before you decide."'
|
||
else:
|
||
subtitle = "Especialistas en Teca y Detailing — Ambiente Marino de Florida"
|
||
h2 = "¿VALE LA PENA SALVAR TU TECA?"
|
||
p1 = ("La teca gris no siempre está muerta. Antes de cotizar cualquier trabajo, "
|
||
"hacemos una evaluación honesta — y te diremos directamente si la "
|
||
"recuperación tiene sentido económico o si el reemplazo es la decisión "
|
||
"más inteligente. La mayoría de la teca que vemos en Florida es recuperable. "
|
||
"Solo necesita el proceso correcto.")
|
||
steps_title = "EL PROCESO DE RECUPERACIÓN"
|
||
steps = [
|
||
("Paso 1 — LIMPIAR",
|
||
"Limpiador de ácido oxálico en dos partes. La parte A elimina la oxidación "
|
||
"y las células grises. La parte B neutraliza el pH. Solo superficie limpia — "
|
||
"nunca cubrir problemas."),
|
||
("Paso 2 — LIJAR",
|
||
"Lija 80 para abrir el grano. Lija 120 para alisar. Siempre a favor de la veta. "
|
||
"Nunca transversal — las trampas de agua son invisibles hasta que pudren la tabla desde adentro."),
|
||
("Paso 3 — ACEITAR",
|
||
"Aceite marino penetrante de teca, capas finas, limpiando antes de que forme película. "
|
||
"Nutre la madera desde adentro. Acondiciona, no recubre."),
|
||
("Paso 4 — SELLAR",
|
||
"Sellador UV en todas las superficies horizontales. El sol de Florida reoxida la teca "
|
||
"sin protección en una temporada. El sellado correcto da 12 a 18 meses."),
|
||
]
|
||
det_title = "SERVICIOS DE DETAILING"
|
||
det_items = [
|
||
"Eliminación de oxidación de gelcoat (corte suave y profundo)",
|
||
"Pulido a máquina y encerado — DA y rotativa",
|
||
"Limpieza de casco y herrajes brillantes",
|
||
"Inspección de sellado y recalafateo",
|
||
"Paquetes completos de detailing exterior",
|
||
]
|
||
quote = '"Hemos restaurado teca que los dueños querían desechar. Mándanos una foto antes de decidir."'
|
||
|
||
# Subtitle
|
||
c.setFillColor(NAVY)
|
||
c.setFont("Helvetica-Bold", 9.5)
|
||
cur_y = draw_wrapped_text(c, subtitle, start_x, cur_y, cw,
|
||
"Helvetica-Bold", 9.5, 12, color=NAVY)
|
||
cur_y -= 0.1 * inch
|
||
draw_gold_divider(c, start_x, cur_y, cw, 1.5)
|
||
cur_y -= 0.16 * inch
|
||
|
||
# H2
|
||
c.setFillColor(NAVY)
|
||
c.setFont("Helvetica-Bold", 8.5)
|
||
c.drawString(start_x, cur_y, h2)
|
||
cur_y -= 0.14 * inch
|
||
|
||
# Intro paragraph
|
||
cur_y = draw_wrapped_text(c, p1, start_x, cur_y, cw,
|
||
"Helvetica", 7.8, line_sm, color=DARK_GRAY)
|
||
cur_y -= 0.15 * inch
|
||
|
||
# Recovery process header
|
||
bar_h = 14
|
||
c.setFillColor(NAVY)
|
||
c.rect(start_x, cur_y - bar_h + 3, cw, bar_h, fill=1, stroke=0)
|
||
c.setFillColor(WHITE)
|
||
c.setFont("Helvetica-Bold", 8.5)
|
||
c.drawString(start_x + 4, cur_y - bar_h + 6, steps_title)
|
||
cur_y -= bar_h + 6
|
||
|
||
# Steps
|
||
for step_title, step_body in steps:
|
||
c.setFillColor(GOLD)
|
||
c.setFont("Helvetica-Bold", 8)
|
||
c.drawString(start_x + 4, cur_y, step_title)
|
||
cur_y -= 0.13 * inch
|
||
cur_y = draw_wrapped_text(c, step_body, start_x + 10, cur_y, cw - 10,
|
||
"Helvetica", 7.5, 10.5, color=DARK_GRAY)
|
||
cur_y -= 0.08 * inch
|
||
|
||
cur_y -= 0.05 * inch
|
||
|
||
# Detailing services header
|
||
c.setFillColor(NAVY)
|
||
c.rect(start_x, cur_y - bar_h + 3, cw, bar_h, fill=1, stroke=0)
|
||
c.setFillColor(WHITE)
|
||
c.setFont("Helvetica-Bold", 8.5)
|
||
c.drawString(start_x + 4, cur_y - bar_h + 6, det_title)
|
||
cur_y -= bar_h + 6
|
||
|
||
for item in det_items:
|
||
cur_y = draw_bullet_item(c, item, start_x + 4, cur_y,
|
||
cw - 4, font_size=7.8, line_height=10.5)
|
||
cur_y -= 1
|
||
|
||
# Quote
|
||
cur_y -= 0.08 * inch
|
||
draw_gold_divider(c, start_x, cur_y, cw, 1.0)
|
||
cur_y -= 0.14 * inch
|
||
cur_y = draw_wrapped_text(c, quote, start_x, cur_y, cw,
|
||
"Helvetica-BoldOblique", 7.8, 11, color=NAVY)
|
||
return cur_y
|
||
|
||
draw_teak_column(left_x, body_top, 'en')
|
||
draw_teak_column(right_x, body_top, 'es')
|
||
|
||
# ── Footer ────────────────────────────────────────────────────────────────
|
||
c.setFillColor(NAVY)
|
||
c.rect(0, 0, pw, margin + footer_h, fill=1, stroke=0)
|
||
c.setStrokeColor(GOLD)
|
||
c.setLineWidth(1.5)
|
||
c.line(0, margin + footer_h, pw, margin + footer_h)
|
||
|
||
c.setFillColor(WHITE)
|
||
c.setFont("Helvetica", 8)
|
||
c.drawString(margin, margin + footer_h - 0.18 * inch,
|
||
"Federico | (754) 209-3375 | management@prisayachts.com")
|
||
c.setFillColor(GOLD)
|
||
c.setFont("Helvetica-Bold", 8.5)
|
||
c.drawRightString(pw - margin, margin + footer_h - 0.18 * inch, WEBSITE)
|
||
c.setFillColor(WHITE)
|
||
c.setFont("Helvetica", 7)
|
||
c.drawCentredString(pw / 2, margin + 0.15 * inch, AREA)
|
||
|
||
c.save()
|
||
print(f" [OK] {path}")
|
||
return path
|
||
|
||
|
||
# ── Main ───────────────────────────────────────────────────────────────────────
|
||
if __name__ == "__main__":
|
||
import sys
|
||
print("Prisa Yachts LLC — Brochure Generator")
|
||
print(f"Output: {OUTPUT_DIR}\n")
|
||
|
||
files = []
|
||
try:
|
||
files.append(generate_brochure1())
|
||
except Exception as e:
|
||
print(f" [ERROR] Brochure 1: {e}", file=sys.stderr)
|
||
|
||
try:
|
||
files.append(generate_brochure2())
|
||
except Exception as e:
|
||
print(f" [ERROR] Brochure 2: {e}", file=sys.stderr)
|
||
|
||
try:
|
||
files.append(generate_brochure3())
|
||
except Exception as e:
|
||
print(f" [ERROR] Brochure 3: {e}", file=sys.stderr)
|
||
|
||
print("\nFile sizes:")
|
||
for f in files:
|
||
size = os.path.getsize(f)
|
||
print(f" {os.path.basename(f)}: {size:,} bytes ({size/1024:.1f} KB)")
|