resumebuilder / pdf_generator.py
sakthi07's picture
pushing to hugging face
ab028ac
"""
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