""" PDF generation using ReportLab. This module provides functions to create PDF documents directly. """ import os from io import BytesIO from reportlab.lib.pagesizes import letter, A4 from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.units import inch, cm from reportlab.lib.colors import black, grey from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle from reportlab.platypus.flowables import PageBreak from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT from utils import log_error def create_pdf_resume(data, template_type="standard"): """ Create a PDF resume using ReportLab. Args: data (dict): Resume data template_type (str): 'standard' or 'modern' Returns: bytes: PDF bytes """ try: buffer = BytesIO() if template_type == "modern": doc = SimpleDocTemplate(buffer, pagesize=A4, leftMargin=0.5*inch, rightMargin=0.5*inch, topMargin=0.5*inch, bottomMargin=0.5*inch) elements = _create_modern_resume_elements(data) else: doc = SimpleDocTemplate(buffer, pagesize=A4, leftMargin=0.75*inch, rightMargin=0.75*inch, topMargin=0.75*inch, bottomMargin=0.75*inch) elements = _create_standard_resume_elements(data) doc.build(elements) pdf_bytes = buffer.getvalue() buffer.close() return pdf_bytes except Exception as e: log_error(f"PDF creation failed: {str(e)}", e) return None def _create_standard_resume_elements(data): """Create elements for standard resume template.""" styles = getSampleStyleSheet() elements = [] # Custom styles styles.add(ParagraphStyle( name='Name', parent=styles['Heading1'], fontSize=24, spaceAfter=12, alignment=TA_CENTER )) styles.add(ParagraphStyle( name='Contact', parent=styles['Normal'], fontSize=10, spaceAfter=30, alignment=TA_CENTER )) styles.add(ParagraphStyle( name='SectionTitle', parent=styles['Heading2'], fontSize=14, spaceBefore=20, spaceAfter=10, borderWidth=1, borderColor=grey, borderPadding=5 )) styles.add(ParagraphStyle( name='JobTitle', parent=styles['Heading3'], fontSize=12, spaceAfter=2 )) styles.add(ParagraphStyle( name='Company', parent=styles['Normal'], fontSize=11, textColor=grey, spaceAfter=5 )) styles.add(ParagraphStyle( name='Date', parent=styles['Normal'], fontSize=10, textColor=grey, alignment=TA_RIGHT )) # Name elements.append(Paragraph(data.get('name', ''), styles['Name'])) # Contact info contact_info = [] if data.get('email'): contact_info.append(f"Email: {data['email']}") if data.get('phone'): contact_info.append(f"Phone: {data['phone']}") if data.get('linkedin'): contact_info.append(f"LinkedIn: {data['linkedin']}") if data.get('github'): contact_info.append(f"GitHub: {data['github']}") if contact_info: elements.append(Paragraph(" | ".join(contact_info), styles['Contact'])) # Profile Summary if data.get('summary'): elements.append(Paragraph("Profile Summary", styles['SectionTitle'])) elements.append(Paragraph(data['summary'], styles['Normal'])) # Add sections based on order sections_order = data.get('sections_order', []) for section in sections_order: if section == 'work_experience' and data.get('work_experience'): elements.append(Paragraph("Work Experience", styles['SectionTitle'])) for exp in data['work_experience']: elements.append(Paragraph(exp.get('title', ''), styles['JobTitle'])) elements.append(Paragraph(exp.get('organization', ''), styles['Company'])) # Create table for date and remarks date_data = [[ Paragraph(f"{exp.get('start_date', '')} - {exp.get('end_date', 'Present')}", styles['Date']), Paragraph(exp.get('remarks', ''), styles['Normal']) ]] date_table = Table(date_data, colWidths=[2*inch, 4*inch]) date_table.setStyle(TableStyle([ ('VALIGN', (0, 0), (-1, -1), 'TOP'), ])) elements.append(date_table) elements.append(Spacer(1, 10)) elif section == 'projects' and data.get('projects'): elements.append(Paragraph("Projects", styles['SectionTitle'])) for proj in data['projects']: elements.append(Paragraph(proj.get('title', ''), styles['JobTitle'])) elements.append(Paragraph(proj.get('organization', ''), styles['Company'])) date_data = [[ Paragraph(f"{proj.get('start_date', '')} - {proj.get('end_date', 'Present')}", styles['Date']), Paragraph(proj.get('remarks', ''), styles['Normal']) ]] date_table = Table(date_data, colWidths=[2*inch, 4*inch]) date_table.setStyle(TableStyle([ ('VALIGN', (0, 0), (-1, -1), 'TOP'), ])) elements.append(date_table) elements.append(Spacer(1, 10)) elif section == 'education' and data.get('education'): elements.append(Paragraph("Education", styles['SectionTitle'])) for edu in data['education']: elements.append(Paragraph(edu.get('title', ''), styles['JobTitle'])) elements.append(Paragraph(edu.get('organization', ''), styles['Company'])) date_data = [[ Paragraph(f"{edu.get('start_date', '')} - {edu.get('end_date', 'Present')}", styles['Date']), Paragraph(edu.get('remarks', ''), styles['Normal']) ]] date_table = Table(date_data, colWidths=[2*inch, 4*inch]) date_table.setStyle(TableStyle([ ('VALIGN', (0, 0), (-1, -1), 'TOP'), ])) elements.append(date_table) elements.append(Spacer(1, 10)) elif section == 'skills' and data.get('skills'): elements.append(Paragraph("Skills", styles['SectionTitle'])) skills_list = [skill.strip() for skill in data['skills'].split(',')] elements.append(Paragraph(", ".join(skills_list), styles['Normal'])) elif section == 'achievements' and data.get('achievements'): elements.append(Paragraph("Achievements", styles['SectionTitle'])) achievements_list = [achievement.strip() for achievement in data['achievements'].split(',')] elements.append(Paragraph(", ".join(achievements_list), styles['Normal'])) return elements def _create_modern_resume_elements(data): """Create elements for modern resume template (two-column).""" styles = getSampleStyleSheet() elements = [] # Custom styles styles.add(ParagraphStyle( name='Name', parent=styles['Heading1'], fontSize=20, spaceAfter=12, alignment=TA_CENTER )) styles.add(ParagraphStyle( name='Contact', parent=styles['Normal'], fontSize=9, spaceAfter=20, alignment=TA_CENTER )) styles.add(ParagraphStyle( name='SectionTitle', parent=styles['Heading2'], fontSize=12, spaceBefore=15, spaceAfter=8, textColor=black )) styles.add(ParagraphStyle( name='JobTitle', parent=styles['Heading3'], fontSize=11, spaceAfter=2 )) styles.add(ParagraphStyle( name='Company', parent=styles['Normal'], fontSize=10, textColor=grey, spaceAfter=5 )) styles.add(ParagraphStyle( name='Date', parent=styles['Normal'], fontSize=9, textColor=grey, alignment=TA_RIGHT )) # Header (full width) elements.append(Paragraph(data.get('name', ''), styles['Name'])) # Contact info contact_info = [] if data.get('email'): contact_info.append(f"Email: {data['email']}") if data.get('phone'): contact_info.append(f"Phone: {data['phone']}") if data.get('linkedin'): contact_info.append(f"LinkedIn: {data['linkedin']}") if data.get('github'): contact_info.append(f"GitHub: {data['github']}") if contact_info: elements.append(Paragraph(" | ".join(contact_info), styles['Contact'])) # Two-column layout left_col_width = 2.5 * inch right_col_width = 4.5 * inch # Main content (right column) main_elements = [] # Profile Summary if data.get('summary'): main_elements.append(Paragraph("Profile Summary", styles['SectionTitle'])) main_elements.append(Paragraph(data['summary'], styles['Normal'])) # Add sections to main content sections_order = data.get('sections_order', []) for section in sections_order: if section == 'work_experience' and data.get('work_experience'): main_elements.append(Paragraph("Work Experience", styles['SectionTitle'])) for exp in data['work_experience']: main_elements.append(Paragraph(exp.get('title', ''), styles['JobTitle'])) main_elements.append(Paragraph(exp.get('organization', ''), styles['Company'])) main_elements.append(Paragraph( f"{exp.get('start_date', '')} - {exp.get('end_date', 'Present')}", styles['Date'] )) main_elements.append(Paragraph(exp.get('remarks', ''), styles['Normal'])) main_elements.append(Spacer(1, 10)) elif section == 'projects' and data.get('projects'): main_elements.append(Paragraph("Projects", styles['SectionTitle'])) for proj in data['projects']: main_elements.append(Paragraph(proj.get('title', ''), styles['JobTitle'])) main_elements.append(Paragraph(proj.get('organization', ''), styles['Company'])) main_elements.append(Paragraph( f"{proj.get('start_date', '')} - {proj.get('end_date', 'Present')}", styles['Date'] )) main_elements.append(Paragraph(proj.get('remarks', ''), styles['Normal'])) main_elements.append(Spacer(1, 10)) elif section == 'education' and data.get('education'): main_elements.append(Paragraph("Education", styles['SectionTitle'])) for edu in data['education']: main_elements.append(Paragraph(edu.get('title', ''), styles['JobTitle'])) main_elements.append(Paragraph(edu.get('organization', ''), styles['Company'])) main_elements.append(Paragraph( f"{edu.get('start_date', '')} - {edu.get('end_date', 'Present')}", styles['Date'] )) main_elements.append(Paragraph(edu.get('remarks', ''), styles['Normal'])) main_elements.append(Spacer(1, 10)) # Sidebar (left column) sidebar_elements = [] # Skills if data.get('skills'): sidebar_elements.append(Paragraph("Skills", styles['SectionTitle'])) skills_list = [skill.strip() for skill in data['skills'].split(',')] for skill in skills_list: sidebar_elements.append(Paragraph(f"• {skill}", styles['Normal'])) sidebar_elements.append(Spacer(1, 10)) # Achievements if data.get('achievements'): sidebar_elements.append(Paragraph("Achievements", styles['SectionTitle'])) achievements_list = [achievement.strip() for achievement in data['achievements'].split(',')] for achievement in achievements_list: sidebar_elements.append(Paragraph(f"• {achievement}", styles['Normal'])) # Create two-column table all_elements = [] max_len = max(len(main_elements), len(sidebar_elements)) for i in range(max_len): left = sidebar_elements[i] if i < len(sidebar_elements) else Spacer(1, 1) right = main_elements[i] if i < len(main_elements) else Spacer(1, 1) all_elements.append([left, right]) if all_elements: col_table = Table(all_elements, colWidths=[left_col_width, right_col_width]) col_table.setStyle(TableStyle([ ('VALIGN', (0, 0), (-1, -1), 'TOP'), ])) elements.append(col_table) return elements