Spaces:
Configuration error
Configuration error
| """ | |
| 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 |