(function (root, factory) { if (typeof module === 'object' && module.exports) { module.exports = factory(); } else { root.ISMIStools = Object.assign(root.ISMIStools || {}, factory()); } })(typeof self !== 'undefined' ? self : this, function () { 'use strict'; function getPDFLib() { return (typeof self !== 'undefined' && self.PDFLib) || (typeof globalThis !== 'undefined' && globalThis.PDFLib); } function wrapText(text, font, size, maxWidth) { const words = String(text).split(/\s+/).filter(Boolean); const lines = []; let line = ''; for (const word of words) { const candidate = line ? line + ' ' + word : word; if (font.widthOfTextAtSize(candidate, size) <= maxWidth) { line = candidate; } else { if (line) lines.push(line); line = word; } } if (line) lines.push(line); return lines.length ? lines : ['']; } // Wraps multi-line text (segments split on \n) into wrapped lines. function wrapLines(text, font, size, maxWidth) { const out = []; String(text == null ? '' : text).split(/\n/).forEach((seg) => { out.push(...wrapText(seg, font, size, maxWidth)); }); return out.length ? out : ['']; } // Builds a Letter-size PDF replicating the official ISMIS Study Load layout: // title block (STUDENT'S STUDY LOAD, semester, date range, date enrolled), // a student info row (ID, name, program, Year Level), the course table // (GROUP, COURSE, COURSE DESCRIPTION, TEACHER, UNITS, NOTE), and a footer // (total units, printed-by). NOTE is the status; UNITS/NOTE are right-aligned; // schedule is stacked under the course description. async function buildStudyLoadPdf(data) { const PDFLib = getPDFLib(); const { PDFDocument, StandardFonts, rgb } = PDFLib; const header = data.header || {}; const courses = data.courses || []; const doc = await PDFDocument.create(); let page = doc.addPage([612, 792]); // Letter const font = doc.embedStandardFont(StandardFonts.Helvetica); const bold = doc.embedStandardFont(StandardFonts.HelveticaBold); const italic = doc.embedStandardFont(StandardFonts.HelveticaOblique); const margin = 50; const contentWidth = 612 - margin * 2; let y = 792 - 50; const black = rgb(0, 0, 0); const gray = rgb(0.3, 0.3, 0.3); function center(text, size, f, dy) { const w = f.widthOfTextAtSize(text, size); page.drawText(text, { x: (612 - w) / 2, y, size, font: f, color: black }); y -= dy; } function left(text, size, f, x, dy) { page.drawText(text, { x, y, size, font: f, color: black }); y -= dy; } function hline(x1, x2, atY) { page.drawRectangle({ x: x1, y: atY, width: x2 - x1, height: 0.75, color: black }); } // ---- Header ---- center("STUDENT'S STUDY LOAD", 9.5, bold, 18); if (header.semester) center(header.semester, 9, font, 15); if (header.dateRange) center(header.dateRange, 9, font, 15); if (header.dateEnrolled) left('Date Enrolled: ' + header.dateEnrolled, 8, italic, margin, 13); // Student info row: ID (bold), name, program, "Year Level: N" const studentParts = []; if (header.studentId) studentParts.push({ text: header.studentId, f: bold }); if (header.fullName) studentParts.push({ text: header.fullName, f: font }); if (header.program) studentParts.push({ text: header.program, f: font }); if (header.yearLevel) studentParts.push({ text: 'Year Level: ' + header.yearLevel, f: font }); if (studentParts.length) { let sx = margin; for (const part of studentParts) { const w = part.f.widthOfTextAtSize(part.text, 9); page.drawText(part.text, { x: sx, y, size: 9, font: part.f, color: black }); sx += w + 18; } y -= 16; } // ---- Table: GROUP, COURSE, COURSE DESCRIPTION, TEACHER, UNITS, NOTE ---- const cols = [ { key: 'group', label: 'GROUP', x: margin, w: 26, align: 'left' }, { key: 'courseCode', label: 'COURSE', x: margin + 26, w: 46, align: 'left' }, { key: 'description', label: 'COURSE DESCRIPTION', x: margin + 72, w: 189, align: 'left' }, { key: 'teacher', label: 'TEACHER', x: margin + 261, w: 123, align: 'left' }, { key: 'units', label: 'UNITS', x: margin + 384, w: 31, align: 'right' }, { key: 'status', label: 'NOTE', x: margin + 415, w: 97, align: 'right' } ]; const cellSize = 8; const headerSize = 8; const lineH = 10; function rowLines(cells) { return cols.map((c) => { let lines; if (c.key === 'description') { lines = wrapLines(cells.description, font, cellSize, c.w - 6); const sched = String(cells.schedule || ''); if (sched) lines.push(...wrapText(sched, font, cellSize, c.w - 6)); } else { lines = wrapLines(cells[c.key], font, cellSize, c.w - 6); } return lines; }); } function drawRow(cells, isHeader) { const f = isHeader ? bold : font; const size = isHeader ? headerSize : cellSize; const linesPerCol = rowLines(cells); const rowHeight = Math.max(...linesPerCol.map((l) => l.length)) * lineH + 8; if (y - rowHeight < 50) { page = doc.addPage([612, 792]); y = 792 - 50; drawHeaderRow(); } if (isHeader) { // top line above the header row hline(margin, margin + contentWidth, y); } for (let i = 0; i < cols.length; i++) { const c = cols[i]; const lines = linesPerCol[i]; let ty = y - 9; for (const line of lines) { if (c.align === 'right') { const w = f.widthOfTextAtSize(line, size); page.drawText(line, { x: c.x + c.w - 3 - w, y: ty, size, font: f, color: black }); } else { page.drawText(line, { x: c.x + 3, y: ty, size, font: f, color: black }); } ty -= lineH; } } y -= rowHeight; if (isHeader) { // line below the header row hline(margin, margin + contentWidth, y); } return true; } function drawHeaderRow() { const cells = {}; cols.forEach((c) => { cells[c.key] = c.label; }); drawRow(cells, true); } drawHeaderRow(); for (const course of courses) { const cells = { group: course.group, courseCode: course.courseCode, description: course.description, schedule: course.schedule, teacher: course.teacher, units: course.units, status: course.status }; drawRow(cells, false); } // ---- Footer ---- if (y < 90) { page = doc.addPage([612, 792]); y = 792 - 50; } y -= 8; if (data.totalUnits) { const w = bold.widthOfTextAtSize(data.totalUnits, 9); page.drawText(data.totalUnits, { x: margin + contentWidth - w, y, size: 9, font: bold, color: black }); y -= 14; } hline(margin, margin + contentWidth, y); y -= 12; const printedBy = 'Printed by: ' + (header.studentId || '') + ' on ' + new Date().toLocaleDateString(); const pw = font.widthOfTextAtSize(printedBy, 8); page.drawText(printedBy, { x: margin + contentWidth - pw, y, size: 8, font, color: gray }); return doc.save(); // returns Promise } return { buildStudyLoadPdf }; });