Skip to content

Commit 6651a82

Browse files
committed
generate pdf
1 parent 7297203 commit 6651a82

File tree

1 file changed

+60
-7
lines changed

1 file changed

+60
-7
lines changed

backend/src/services/generatepdf.js

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ const LAYOUT = {
3737
leftColumnX: 50,
3838
leftColumnWidth: 180,
3939
rightColumnX: 240,
40-
rightColumnWidth: 315
40+
rightColumnWidth: 315,
41+
pageHeight: 792
4142
};
4243

4344
async function fetchImageBuffer(url) {
@@ -50,6 +51,14 @@ async function fetchImageBuffer(url) {
5051
}
5152
}
5253

54+
function checkPageBreak(doc, currentY, requiredSpace) {
55+
if (currentY + requiredSpace > LAYOUT.pageHeight - MARGINS.bottom) {
56+
doc.addPage();
57+
return MARGINS.top;
58+
}
59+
return currentY;
60+
}
61+
5362
function addHeader(doc, data) {
5463
const { fullName, email, phone, city, address, nationality, gender } = data.basicDetails;
5564

@@ -100,6 +109,8 @@ function addHeader(doc, data) {
100109
}
101110

102111
function addLeftColumnSection(doc, title, y) {
112+
y = checkPageBreak(doc, y, 30);
113+
103114
doc.fontSize(SIZES.sectionHeading).fillColor(COLORS.primary).font(FONTS.bold)
104115
.text(title.toUpperCase(), LAYOUT.leftColumnX, y);
105116

@@ -170,6 +181,8 @@ function addEducation(doc, data, startY) {
170181
educationEntries.forEach((entry, index) => {
171182
if (index > 0) y += 12;
172183

184+
y = checkPageBreak(doc, y, 60);
185+
173186
doc.fontSize(SIZES.body).fillColor(COLORS.primary).font(FONTS.bold)
174187
.text(entry.degree, LAYOUT.leftColumnX, y, { width: LAYOUT.leftColumnWidth });
175188
y += SIZES.lineHeight;
@@ -214,6 +227,8 @@ function addLanguages(doc, data, startY) {
214227
doc.fontSize(SIZES.small).fillColor(COLORS.text).font(FONTS.regular);
215228

216229
data.basicDetails.languages.forEach(lang => {
230+
y = checkPageBreak(doc, y, 25);
231+
217232
const fluencyLevel = lang.fluency.charAt(0).toUpperCase() + lang.fluency.slice(1);
218233
doc.text(`• ${lang.language}`, LAYOUT.leftColumnX, y, { width: LAYOUT.leftColumnWidth });
219234
y += 10;
@@ -238,19 +253,23 @@ function addSkillsSection(doc, data, startY) {
238253
const skills = data.skills.skillsList.split(',').map(s => s.trim()).filter(Boolean);
239254

240255
skills.forEach(skill => {
256+
y = checkPageBreak(doc, y, 15);
241257
doc.text(`• ${skill}`, LAYOUT.leftColumnX, y, { width: LAYOUT.leftColumnWidth });
242258
y += 11;
243259
});
244260
}
245261

246262
if (data.skills?.supportingDocuments && data.skills.supportingDocuments.length > 0) {
247263
y += 5;
264+
y = checkPageBreak(doc, y, 30);
265+
248266
doc.fontSize(SIZES.small).fillColor(COLORS.darkGray).font(FONTS.bold)
249267
.text('Documents:', LAYOUT.leftColumnX, y, { width: LAYOUT.leftColumnWidth });
250268
y += 11;
251269

252270
data.skills.supportingDocuments.forEach(docItem => {
253271
if (docItem.url) {
272+
y = checkPageBreak(doc, y, 15);
254273
doc.fontSize(SIZES.small).fillColor(COLORS.accent).font(FONTS.regular)
255274
.text('View Document', LAYOUT.leftColumnX, y, {
256275
width: LAYOUT.leftColumnWidth,
@@ -281,6 +300,7 @@ function addUSMLEScores(doc, data, startY) {
281300
doc.fontSize(SIZES.small).fillColor(COLORS.text).font(FONTS.regular);
282301

283302
if (data.usmleScores.step1Status && data.usmleScores.step1Status !== 'not-taken') {
303+
y = checkPageBreak(doc, y, 25);
284304
doc.font(FONTS.bold).text('Step 1:', LAYOUT.leftColumnX, y, { width: LAYOUT.leftColumnWidth, continued: true })
285305
.font(FONTS.regular).text(` ${data.usmleScores.step1Status.toUpperCase()}`);
286306
y += 11;
@@ -297,6 +317,7 @@ function addUSMLEScores(doc, data, startY) {
297317
}
298318

299319
if (data.usmleScores.step2ckScore) {
320+
y = checkPageBreak(doc, y, 25);
300321
doc.fillColor(COLORS.text).font(FONTS.bold).text('Step 2 CK:', LAYOUT.leftColumnX, y, { width: LAYOUT.leftColumnWidth, continued: true })
301322
.font(FONTS.regular).text(` ${data.usmleScores.step2ckScore}`);
302323
y += 11;
@@ -313,12 +334,14 @@ function addUSMLEScores(doc, data, startY) {
313334
}
314335

315336
if (data.usmleScores.step2csStatus && data.usmleScores.step2csStatus !== 'not-taken') {
337+
y = checkPageBreak(doc, y, 15);
316338
doc.fillColor(COLORS.text).font(FONTS.bold).text('Step 2 CS:', LAYOUT.leftColumnX, y, { width: LAYOUT.leftColumnWidth, continued: true })
317339
.font(FONTS.regular).text(` ${data.usmleScores.step2csStatus.toUpperCase()}`);
318340
y += 11;
319341
}
320342

321343
if (data.usmleScores.oetScore) {
344+
y = checkPageBreak(doc, y, 25);
322345
doc.fillColor(COLORS.text).font(FONTS.bold).text('OET Score:', LAYOUT.leftColumnX, y, { width: LAYOUT.leftColumnWidth, continued: true })
323346
.font(FONTS.regular).text(` ${data.usmleScores.oetScore}`);
324347
y += 11;
@@ -335,6 +358,7 @@ function addUSMLEScores(doc, data, startY) {
335358
}
336359

337360
if (data.usmleScores.ecfmgCertified) {
361+
y = checkPageBreak(doc, y, 15);
338362
doc.font(FONTS.bold).fillColor(COLORS.accent)
339363
.text('✓ ECFMG Certified', LAYOUT.leftColumnX, y, { width: LAYOUT.leftColumnWidth });
340364
y += 11;
@@ -370,6 +394,8 @@ function addCertifications(doc, data, startY) {
370394
doc.fontSize(SIZES.small).fillColor(COLORS.text);
371395

372396
certs.forEach(cert => {
397+
y = checkPageBreak(doc, y, 40);
398+
373399
doc.font(FONTS.bold).text(cert.name, LAYOUT.leftColumnX, y, { width: LAYOUT.leftColumnWidth });
374400
y += 11;
375401

@@ -406,17 +432,22 @@ function addEMRTraining(doc, data, startY) {
406432
doc.fontSize(SIZES.small).fillColor(COLORS.text).font(FONTS.regular);
407433

408434
if (data.emrRcmTraining.emrSystems && data.emrRcmTraining.emrSystems.length > 0) {
435+
y = checkPageBreak(doc, y, 30);
436+
409437
doc.font(FONTS.bold).text('EMR Systems:', LAYOUT.leftColumnX, y, { width: LAYOUT.leftColumnWidth });
410438
y += 11;
411439

412440
data.emrRcmTraining.emrSystems.forEach(system => {
441+
y = checkPageBreak(doc, y, 15);
413442
doc.font(FONTS.regular).text(`• ${system}`, LAYOUT.leftColumnX, y, { width: LAYOUT.leftColumnWidth });
414443
y += 10;
415444
});
416445
y += 5;
417446
}
418447

419448
if (data.emrRcmTraining.rcmTraining) {
449+
y = checkPageBreak(doc, y, 25);
450+
420451
doc.font(FONTS.bold).text('RCM Training', LAYOUT.leftColumnX, y, { width: LAYOUT.leftColumnWidth });
421452
y += 11;
422453

@@ -431,6 +462,8 @@ function addEMRTraining(doc, data, startY) {
431462
}
432463

433464
function addRightColumnSection(doc, title, y) {
465+
y = checkPageBreak(doc, y, 30);
466+
434467
doc.fontSize(SIZES.sectionHeading).fillColor(COLORS.primary).font(FONTS.bold)
435468
.text(title.toUpperCase(), LAYOUT.rightColumnX, y);
436469

@@ -452,6 +485,8 @@ function addExperienceSection(doc, experiences, title, startY) {
452485
experiences.forEach((exp, index) => {
453486
if (index > 0) y += 15;
454487

488+
y = checkPageBreak(doc, y, 80);
489+
455490
const titleText = exp.title || exp.position || exp.role || '';
456491
const orgText = exp.hospital || exp.organization || '';
457492

@@ -496,11 +531,14 @@ function addExperienceSection(doc, experiences, title, startY) {
496531
doc.fontSize(SIZES.small).fillColor(COLORS.text).font(FONTS.regular);
497532
const lines = exp.description.split('\n').filter(l => l.trim());
498533
lines.forEach(line => {
534+
const lineHeight = doc.heightOfString(line, { width: LAYOUT.rightColumnWidth });
535+
y = checkPageBreak(doc, y, lineHeight + 5);
536+
499537
doc.text(`• ${line.trim()}`, LAYOUT.rightColumnX, y, {
500538
width: LAYOUT.rightColumnWidth,
501539
align: 'left'
502540
});
503-
y += doc.heightOfString(line, { width: LAYOUT.rightColumnWidth }) + 2;
541+
y += lineHeight + 2;
504542
});
505543
}
506544
});
@@ -520,16 +558,21 @@ function addAchievements(doc, data, startY) {
520558
doc.fontSize(SIZES.small).fillColor(COLORS.text).font(FONTS.regular);
521559
const lines = data.significantAchievements.split('\n').filter(l => l.trim());
522560
lines.forEach(line => {
561+
const lineHeight = doc.heightOfString(line, { width: LAYOUT.rightColumnWidth });
562+
y = checkPageBreak(doc, y, lineHeight + 5);
563+
523564
doc.text(`• ${line.trim()}`, LAYOUT.rightColumnX, y, {
524565
width: LAYOUT.rightColumnWidth
525566
});
526-
y += doc.heightOfString(line, { width: LAYOUT.rightColumnWidth }) + 3;
567+
y += lineHeight + 3;
527568
});
528569
y += 8;
529570
}
530571

531572
if (hasAchievements) {
532573
data.achievements.forEach((achievement, index) => {
574+
y = checkPageBreak(doc, y, 60);
575+
533576
doc.fontSize(SIZES.body).fillColor(COLORS.primary).font(FONTS.bold)
534577
.text(achievement.title, LAYOUT.rightColumnX, y, { width: LAYOUT.rightColumnWidth });
535578
y += SIZES.lineHeight;
@@ -541,9 +584,10 @@ function addAchievements(doc, data, startY) {
541584
}
542585

543586
if (achievement.description) {
587+
const descHeight = doc.heightOfString(achievement.description, { width: LAYOUT.rightColumnWidth });
544588
doc.fontSize(SIZES.small).fillColor(COLORS.text).font(FONTS.regular)
545589
.text(achievement.description, LAYOUT.rightColumnX, y, { width: LAYOUT.rightColumnWidth });
546-
y += doc.heightOfString(achievement.description, { width: LAYOUT.rightColumnWidth }) + 5;
590+
y += descHeight + 5;
547591
}
548592

549593
if (achievement.url && achievement.url.trim()) {
@@ -571,11 +615,14 @@ function addPublications(doc, data, startY) {
571615
data.publications.forEach((pub, index) => {
572616
if (index > 0) y += 10;
573617

618+
y = checkPageBreak(doc, y, 50);
619+
574620
doc.fontSize(SIZES.small).fillColor(COLORS.text).font(FONTS.regular);
621+
const titleHeight = doc.heightOfString(pub.title, { width: LAYOUT.rightColumnWidth });
575622
doc.text(`${index + 1}. ${pub.title}`, LAYOUT.rightColumnX, y, {
576623
width: LAYOUT.rightColumnWidth
577624
});
578-
y += doc.heightOfString(pub.title, { width: LAYOUT.rightColumnWidth }) + 2;
625+
y += titleHeight + 2;
579626

580627
doc.font(FONTS.italic).fillColor(COLORS.darkGray);
581628
const pubDetails = [pub.journal, pub.year];
@@ -605,6 +652,8 @@ function addConferences(doc, data, startY) {
605652
data.conferences.forEach((conf, index) => {
606653
if (index > 0) y += 12;
607654

655+
y = checkPageBreak(doc, y, 80);
656+
608657
doc.fontSize(SIZES.body).fillColor(COLORS.primary).font(FONTS.bold)
609658
.text(conf.name, LAYOUT.rightColumnX, y, { width: LAYOUT.rightColumnWidth });
610659
y += SIZES.lineHeight;
@@ -627,9 +676,10 @@ function addConferences(doc, data, startY) {
627676
}
628677

629678
if (conf.description) {
679+
const descHeight = doc.heightOfString(conf.description, { width: LAYOUT.rightColumnWidth });
630680
doc.fontSize(SIZES.small).fillColor(COLORS.text).font(FONTS.regular)
631681
.text(conf.description, LAYOUT.rightColumnX, y, { width: LAYOUT.rightColumnWidth });
632-
y += doc.heightOfString(conf.description, { width: LAYOUT.rightColumnWidth }) + 5;
682+
y += descHeight + 5;
633683
}
634684

635685
if (conf.certificateAwarded) {
@@ -660,6 +710,8 @@ function addWorkshops(doc, data, startY) {
660710
data.workshops.forEach((workshop, index) => {
661711
if (index > 0) y += 12;
662712

713+
y = checkPageBreak(doc, y, 60);
714+
663715
doc.fontSize(SIZES.body).fillColor(COLORS.primary).font(FONTS.bold)
664716
.text(workshop.name, LAYOUT.rightColumnX, y, { width: LAYOUT.rightColumnWidth });
665717
y += SIZES.lineHeight;
@@ -675,9 +727,10 @@ function addWorkshops(doc, data, startY) {
675727
}
676728

677729
if (workshop.description) {
730+
const descHeight = doc.heightOfString(workshop.description, { width: LAYOUT.rightColumnWidth });
678731
doc.fontSize(SIZES.small).fillColor(COLORS.text).font(FONTS.regular)
679732
.text(workshop.description, LAYOUT.rightColumnX, y, { width: LAYOUT.rightColumnWidth });
680-
y += doc.heightOfString(workshop.description, { width: LAYOUT.rightColumnWidth }) + 5;
733+
y += descHeight + 5;
681734
}
682735

683736
if (workshop.awards) {

0 commit comments

Comments
 (0)