""" 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)")