faj-cli
Version:
FAJ - A powerful CLI resume builder with AI enhancement and multi-format export
388 lines • 14.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.generatePDFResume = generatePDFResume;
const ResumeTemplates_1 = require("./ResumeTemplates");
// A4: 595 x 842 points (8.27 x 11.69 inches)
// Letter: 612 x 792 points (8.5 x 11 inches)
const PAGE_SIZES = {
A4: { width: 595, height: 842 },
Letter: { width: 612, height: 792 }
};
async function generatePDFResume(content, options = {}) {
const PDFDocument = require('pdfkit');
// Get theme configuration
const theme = ResumeTemplates_1.themes[options.theme || 'modern'] || ResumeTemplates_1.themes.modern;
const config = theme.pdfConfig || {};
// Set up page configuration for A4 by default
const pageSize = options.pageSize || 'A4';
const margins = options.margins || config.margins || {
top: 50,
bottom: 50,
left: 50,
right: 50
};
// Create PDF with proper page setup
const doc = new PDFDocument({
size: pageSize,
margins: margins,
bufferPages: true, // Enable page buffering for better control
info: {
Title: `${content.basicInfo?.name || 'Resume'} - Resume`,
Author: content.basicInfo?.name || 'Anonymous',
Subject: 'Professional Resume',
Keywords: 'resume, cv, professional'
}
});
// Color definitions based on theme
const colors = config.colors || {
primary: '#2c3e50',
secondary: '#34495e',
accent: '#3498db',
text: '#333333',
lightText: '#666666',
border: '#e0e0e0'
};
// Font settings
const fonts = {
regular: 'Helvetica',
bold: 'Helvetica-Bold',
italic: 'Helvetica-Oblique',
boldItalic: 'Helvetica-BoldOblique'
};
// Build PDF content
const chunks = [];
doc.on('data', (chunk) => chunks.push(chunk));
// Track vertical position for page management
let currentY = doc.y;
const pageHeight = PAGE_SIZES[pageSize].height;
const pageWidth = PAGE_SIZES[pageSize].width - margins.left - margins.right;
// Helper function to check if we need a new page
const checkPageBreak = (requiredSpace) => {
if (doc.y + requiredSpace > pageHeight - margins.bottom) {
doc.addPage();
currentY = margins.top;
return true;
}
return false;
};
// Modern theme header with gradient effect
if (options.theme === 'modern') {
// Create gradient-like effect with rectangles
doc.rect(0, 0, PAGE_SIZES[pageSize].width, 120)
.fill(colors.primary);
// Add subtle pattern
doc.rect(0, 120, PAGE_SIZES[pageSize].width, 2)
.fill(colors.accent);
}
// Professional theme header
if (options.theme === 'professional') {
doc.rect(0, 0, PAGE_SIZES[pageSize].width, 100)
.fill('#fafafa');
doc.moveTo(margins.left, 100)
.lineTo(PAGE_SIZES[pageSize].width - margins.right, 100)
.strokeColor(colors.primary)
.lineWidth(2)
.stroke();
}
// Name and Contact Information
if (content.basicInfo) {
const headerY = options.theme === 'modern' ? 40 : 30;
// Name
doc.font(fonts.bold)
.fontSize(28)
.fillColor(options.theme === 'modern' ? 'white' : colors.primary)
.text(content.basicInfo.name, 0, headerY, {
align: 'center',
width: PAGE_SIZES[pageSize].width
});
// Contact info
const contactInfo = [];
if (content.basicInfo.email)
contactInfo.push(content.basicInfo.email);
if (content.basicInfo.phone)
contactInfo.push(content.basicInfo.phone);
if (content.basicInfo.location)
contactInfo.push(content.basicInfo.location);
if (contactInfo.length > 0) {
doc.font(fonts.regular)
.fontSize(10)
.fillColor(options.theme === 'modern' ? 'white' : colors.lightText)
.text(contactInfo.join(' • '), 0, headerY + 35, {
align: 'center',
width: PAGE_SIZES[pageSize].width
});
}
// Links
const links = [];
if (content.basicInfo.github)
links.push(content.basicInfo.github);
if (content.basicInfo.linkedin)
links.push(content.basicInfo.linkedin);
if (links.length > 0) {
doc.fontSize(9)
.text(links.join(' • '), 0, headerY + 50, {
align: 'center',
width: PAGE_SIZES[pageSize].width
});
}
doc.moveDown(2);
}
// Move to content area
doc.fillColor(colors.text);
currentY = options.theme === 'modern' ? 140 : 120;
doc.y = currentY;
// Professional Summary
if (content.content?.summary) {
checkPageBreak(100);
// Section title
doc.font(fonts.bold)
.fontSize(14)
.fillColor(colors.primary)
.text('PROFESSIONAL SUMMARY', margins.left);
// Section underline
doc.moveTo(margins.left, doc.y)
.lineTo(pageWidth + margins.left, doc.y)
.strokeColor(colors.primary)
.lineWidth(1)
.stroke();
doc.moveDown(0.5);
// Summary text
doc.font(fonts.regular)
.fontSize(10)
.fillColor(colors.text)
.text(content.content.summary, margins.left, doc.y, {
align: 'justify',
width: pageWidth
});
doc.moveDown(1.2);
}
// Work Experience
if (content.content?.experience && content.content.experience.length > 0) {
checkPageBreak(60);
doc.font(fonts.bold)
.fontSize(14)
.fillColor(colors.primary)
.text('WORK EXPERIENCE', margins.left);
doc.moveTo(margins.left, doc.y)
.lineTo(pageWidth + margins.left, doc.y)
.strokeColor(colors.primary)
.lineWidth(1)
.stroke();
doc.moveDown(0.5);
for (const exp of content.content.experience) {
checkPageBreak(80);
// Position and company on same line
doc.font(fonts.bold)
.fontSize(11)
.fillColor(colors.text)
.text(exp.title, margins.left, doc.y, {
continued: true,
width: pageWidth * 0.6
});
// Date aligned to right
const dateText = `${exp.startDate} - ${exp.current ? 'Present' : exp.endDate}`;
doc.font(fonts.italic)
.fontSize(9)
.fillColor(colors.lightText)
.text(dateText, margins.left + pageWidth * 0.6, doc.y, {
align: 'right',
width: pageWidth * 0.4
});
// Company name
doc.font(fonts.italic)
.fontSize(10)
.fillColor(colors.secondary)
.text(exp.company, margins.left);
// Description
if (exp.description) {
doc.font(fonts.regular)
.fontSize(9.5)
.fillColor(colors.text)
.text(exp.description, margins.left, doc.y, {
align: 'justify',
width: pageWidth
});
}
// Highlights with bullets
if (exp.highlights && exp.highlights.length > 0) {
doc.moveDown(0.3);
for (const highlight of exp.highlights) {
checkPageBreak(20);
// Bullet point
doc.font(fonts.regular)
.fontSize(9)
.fillColor(colors.accent)
.text('•', margins.left + 10, doc.y, { continued: true });
// Highlight text
doc.fillColor(colors.text)
.text(` ${highlight}`, margins.left + 20, doc.y, {
width: pageWidth - 20
});
}
}
doc.moveDown(0.8);
}
}
// Projects
if (content.content?.projects && content.content.projects.length > 0) {
checkPageBreak(60);
doc.font(fonts.bold)
.fontSize(14)
.fillColor(colors.primary)
.text('PROJECTS', margins.left);
doc.moveTo(margins.left, doc.y)
.lineTo(pageWidth + margins.left, doc.y)
.strokeColor(colors.primary)
.lineWidth(1)
.stroke();
doc.moveDown(0.5);
for (const proj of content.content.projects) {
checkPageBreak(60);
// Project name
doc.font(fonts.bold)
.fontSize(11)
.fillColor(colors.text)
.text(proj.name, margins.left);
// Role
if (proj.role) {
doc.font(fonts.italic)
.fontSize(9)
.fillColor(colors.secondary)
.text(proj.role, margins.left);
}
// Description
if (proj.description) {
doc.font(fonts.regular)
.fontSize(9.5)
.fillColor(colors.text)
.text(proj.description, margins.left, doc.y, {
align: 'justify',
width: pageWidth
});
}
// Highlights
if (proj.highlights && proj.highlights.length > 0) {
doc.moveDown(0.3);
for (const highlight of proj.highlights) {
checkPageBreak(20);
doc.font(fonts.regular)
.fontSize(9)
.fillColor(colors.accent)
.text('•', margins.left + 10, doc.y, { continued: true });
doc.fillColor(colors.text)
.text(` ${highlight}`, margins.left + 20, doc.y, {
width: pageWidth - 20
});
}
}
doc.moveDown(0.8);
}
}
// Skills
if (content.content?.skills && content.content.skills.length > 0) {
checkPageBreak(60);
doc.font(fonts.bold)
.fontSize(14)
.fillColor(colors.primary)
.text('TECHNICAL SKILLS', margins.left);
doc.moveTo(margins.left, doc.y)
.lineTo(pageWidth + margins.left, doc.y)
.strokeColor(colors.primary)
.lineWidth(1)
.stroke();
doc.moveDown(0.5);
// Group skills by category
const skillsByCategory = {};
content.content.skills.forEach((skill) => {
const category = skill.category || 'Other';
if (!skillsByCategory[category]) {
skillsByCategory[category] = [];
}
skillsByCategory[category].push(skill);
});
const categoryLabels = {
'programming_languages': 'Programming Languages',
'frameworks': 'Frameworks & Libraries',
'databases': 'Databases',
'tools': 'Tools & Technologies',
'cloud': 'Cloud & DevOps',
'soft_skills': 'Soft Skills',
'Other': 'Other Skills'
};
for (const [category, skills] of Object.entries(skillsByCategory)) {
checkPageBreak(20);
doc.font(fonts.bold)
.fontSize(9)
.fillColor(colors.secondary)
.text(`${categoryLabels[category] || category}: `, margins.left, doc.y, {
continued: true
});
doc.font(fonts.regular)
.fillColor(colors.text)
.text(skills.map((s) => s.name).join(', '), {
width: pageWidth - 100
});
doc.moveDown(0.3);
}
doc.moveDown(0.5);
}
// Education
if (content.content?.education && content.content.education.length > 0) {
checkPageBreak(60);
doc.font(fonts.bold)
.fontSize(14)
.fillColor(colors.primary)
.text('EDUCATION', margins.left);
doc.moveTo(margins.left, doc.y)
.lineTo(pageWidth + margins.left, doc.y)
.strokeColor(colors.primary)
.lineWidth(1)
.stroke();
doc.moveDown(0.5);
for (const edu of content.content.education) {
checkPageBreak(40);
// Degree and field
doc.font(fonts.bold)
.fontSize(11)
.fillColor(colors.text)
.text(`${edu.degree}(${edu.field})`, margins.left);
// Institution
doc.font(fonts.regular)
.fontSize(10)
.fillColor(colors.secondary)
.text(edu.institution, margins.left);
// Date and GPA on same line
const eduInfo = [`${edu.startDate} - ${edu.endDate}`];
if (edu.gpa)
eduInfo.push(`GPA: ${edu.gpa}`);
doc.font(fonts.italic)
.fontSize(9)
.fillColor(colors.lightText)
.text(eduInfo.join(' | '), margins.left);
doc.moveDown(0.6);
}
}
// Page numbers
const pages = doc.bufferedPageRange();
for (let i = pages.start; i < pages.start + pages.count; i++) {
doc.switchToPage(i);
// Footer
doc.font(fonts.italic)
.fontSize(8)
.fillColor(colors.lightText)
.text(`Page ${i + 1} of ${pages.count}`, margins.left, pageHeight - 30, {
align: 'center',
width: pageWidth
});
}
// Finalize PDF
doc.end();
return new Promise((resolve, reject) => {
doc.on('end', () => {
const pdfBuffer = Buffer.concat(chunks);
resolve(pdfBuffer);
});
doc.on('error', reject);
});
}
//# sourceMappingURL=PDFTemplates.js.map