"""PDF Export Module Converts markdown stage outputs into beautifully formatted PDF documents. Uses markdown2 for markdown parsing and WeasyPrint for PDF generation. """ import logging from pathlib import Path from typing import Optional from datetime import datetime import tempfile # Import PDF generation libraries try: import markdown2 from weasyprint import HTML, CSS PDF_AVAILABLE = True except ImportError: PDF_AVAILABLE = False logging.warning("PDF export libraries not available. Install with: pip install markdown2 weasyprint") logger = logging.getLogger(__name__) # CSS styling for beautiful PDFs PDF_STYLES = """ @page { size: A4; margin: 2cm; } body { font-family: 'Helvetica', 'Arial', sans-serif; font-size: 11pt; line-height: 1.6; color: #333; } h1 { color: #2c3e50; font-size: 24pt; border-bottom: 3px solid #3498db; padding-bottom: 10px; margin-top: 20px; } h2 { color: #34495e; font-size: 18pt; margin-top: 18px; border-bottom: 2px solid #ecf0f1; padding-bottom: 6px; } h3 { color: #555; font-size: 14pt; margin-top: 14px; } h4 { color: #666; font-size: 12pt; margin-top: 12px; } code { background-color: #f4f4f4; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', monospace; font-size: 10pt; } pre { background-color: #f8f8f8; padding: 15px; border-left: 4px solid #3498db; overflow-x: auto; border-radius: 4px; } pre code { background-color: transparent; padding: 0; } blockquote { border-left: 4px solid #95a5a6; padding-left: 15px; margin-left: 0; font-style: italic; color: #555; } ul, ol { margin-left: 20px; } li { margin-bottom: 6px; } strong { color: #2c3e50; } em { color: #7f8c8d; } hr { border: none; border-top: 2px solid #ecf0f1; margin: 20px 0; } /* Footer */ @page { @bottom-center { content: "Page " counter(page) " of " counter(pages); font-size: 9pt; color: #7f8c8d; } } """ def generate_stage_pdf( stage_num: int, markdown_content: str, output_path: Optional[Path] = None, brand_name: str = "Unknown Brand" ) -> Path: """Generate PDF from markdown content for a specific stage. Args: stage_num: Stage number (0-6) markdown_content: Markdown-formatted stage output output_path: Optional output path (defaults to temp file) brand_name: Brand name for PDF header Returns: Path to generated PDF file Raises: ImportError: If PDF generation libraries not installed Exception: If PDF generation fails """ if not PDF_AVAILABLE: raise ImportError( "PDF export libraries not available. " "Install with: pip install markdown2 weasyprint" ) logger.info(f"Generating PDF for Stage {stage_num}") try: # CRITICAL: Ensure markdown_content is a string, not dict/JSON if not isinstance(markdown_content, str): logger.error(f"markdown_content is not a string (type={type(markdown_content)})") # Convert to JSON string as fallback import json markdown_content = f"```json\n{json.dumps(markdown_content, indent=2)}\n```" # Convert markdown to HTML html_content = markdown2.markdown( markdown_content, extras=["fenced-code-blocks", "tables", "break-on-newline"] ) # Add header and metadata timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") stage_names = { 0: "Brand Context", 1: "Extraction", 2: "Signals", 3: "Insights", 4: "Ideation", 5: "Opportunity Cards", 6: "Executive Summary" } stage_name = stage_names.get(stage_num, f"Stage {stage_num}") full_html = f"""
Stage {stage_num}: {stage_name}
Generated: {timestamp}
Complete Pipeline Analysis
Generated: {timestamp}