const fs = require('fs'); const PDFDocument = require('pdfkit'); // Shared content (mirrors build-cv.js). const { EN, NO } = require('./content.js'); const PAGE_W = 595.28; const MARGIN = 54; const CW = PAGE_W - MARGIN * 2; // content width const INK = '#1a1a1a'; const GRAY = '#555555'; const RULE = '#b0b0b0'; function build(c, outfile) { const doc = new PDFDocument({ size: 'A4', margins: { top: MARGIN, bottom: MARGIN, left: MARGIN, right: MARGIN } }); doc.pipe(fs.createWriteStream(outfile)); const rule = (gap = 4) => { const y = doc.y + gap; doc.moveTo(MARGIN, y).lineTo(MARGIN + CW, y).lineWidth(0.75).strokeColor(RULE).stroke(); doc.y = y + gap + 2; }; const heading = (text) => { doc.moveDown(0.5); doc.fillColor(INK).font('Helvetica-Bold').fontSize(12).text(text, MARGIN, doc.y); rule(2); }; const bullet = (text, prefix) => { const y = doc.y; doc.fillColor(INK).font('Helvetica').fontSize(10).text('•', MARGIN + 2, y, { width: 10, lineBreak: false }); const tx = MARGIN + 16; const tw = CW - 16; if (prefix) { doc.font('Helvetica-Bold').text(prefix, tx, y, { width: tw, continued: true }); doc.font('Helvetica').text(text, { width: tw }); } else { doc.font('Helvetica').text(text, tx, y, { width: tw }); } doc.moveDown(0.15); }; const roleLine = (left, dates) => { doc.moveDown(0.3); const y = doc.y; doc.font('Helvetica').fontSize(9.5).fillColor(GRAY).text(dates, MARGIN, y, { width: CW, align: 'right', lineBreak: false }); doc.font('Helvetica-Bold').fontSize(10.5).fillColor(INK).text(left, MARGIN, y, { width: CW - 90, lineBreak: false }); doc.y = y + 14; }; // Header doc.fillColor(INK).font('Helvetica-Bold').fontSize(20).text(c.name, MARGIN, MARGIN); doc.font('Helvetica').fontSize(12).fillColor('#333').text(c.title, { paragraphGap: 2 }); doc.font('Helvetica').fontSize(9.5).fillColor(GRAY) .text(`${c.location} · ${c.email} · ${c.phone}`); doc.text('cesnimda.co.uk · cesnimda.co.uk/Linkedin'); rule(4); // Summary heading(c.t.summary); doc.font('Helvetica').fontSize(10).fillColor(INK).text(c.summary, MARGIN, doc.y, { width: CW, align: 'justify' }); // Skills heading(c.t.skills); for (const s of c.skills) { const y = doc.y; doc.font('Helvetica-Bold').fontSize(10).fillColor(INK).text(`${s.group}: `, MARGIN, y, { width: CW, continued: true }); doc.font('Helvetica').text(s.items, { width: CW }); doc.moveDown(0.15); } // Experience heading(c.t.experience); for (const e of c.experience) { roleLine(`${e.role} — ${e.org}`, e.dates); if (e.note) { doc.font('Helvetica-Oblique').fontSize(9.5).fillColor(GRAY).text(e.note, MARGIN, doc.y, { width: CW }); doc.moveDown(0.1); } for (const b of e.bullets) bullet(b); } // Earlier roles doc.moveDown(0.4); doc.font('Helvetica-Bold').fontSize(10).fillColor(INK).text(c.t.earlier, MARGIN, doc.y, { width: CW }); doc.moveDown(0.1); for (const r of c.earlier) bullet(r); // Projects heading(c.t.projects); for (const p of c.projects) bullet(p.desc, `${p.name} — `); // Education heading(c.t.education); roleLine(c.education.qual, c.education.dates); doc.font('Helvetica').fontSize(10).fillColor(INK).text(c.education.org, MARGIN, doc.y, { width: CW }); doc.end(); return new Promise((res) => doc.on('end', res)); } (async () => { await build(EN, 'connor-babbington-cv-en.pdf'); await build(NO, 'connor-babbington-cv-no.pdf'); console.log('wrote EN + NO pdf'); })();