From 0d29f0116ada8f90cc8194ad86a9783d2dad630f Mon Sep 17 00:00:00 2001 From: Adrian Bonpin Date: Tue, 18 Aug 2026 15:44:12 +0800 Subject: [PATCH] fix: widen NOTE column and add pagination to study load PDF --- pdf.js | 34 ++++++++++++++++++++-------------- test/pdf.test.js | 22 ++++++++++++++++++++++ 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/pdf.js b/pdf.js index e9d606e..0bdbfdd 100644 --- a/pdf.js +++ b/pdf.js @@ -36,7 +36,7 @@ const courses = data.courses || []; const doc = await PDFDocument.create(); - const page = doc.addPage([612, 792]); // Letter + let page = doc.addPage([612, 792]); // Letter const font = doc.embedStandardFont(StandardFonts.Helvetica); const bold = doc.embedStandardFont(StandardFonts.HelveticaBold); const margin = 50; @@ -70,12 +70,12 @@ // Table columns: NOTE, UNITS, TEACHER, COURSE DESCRIPTION, COURSE, GROUP const cols = [ - { key: 'status', label: 'NOTE', x: margin, w: 40 }, - { key: 'units', label: 'UNITS', x: margin + 40, w: 45 }, - { key: 'teacher', label: 'TEACHER', x: margin + 85, w: 110 }, - { key: 'description', label: 'COURSE DESCRIPTION', x: margin + 195, w: 150 }, - { key: 'courseCode', label: 'COURSE', x: margin + 345, w: 70 }, - { key: 'groupSchedule', label: 'GROUP', x: margin + 415, w: 97 } + { key: 'status', label: 'NOTE', x: margin, w: 50 }, + { key: 'units', label: 'UNITS', x: margin + 50, w: 45 }, + { key: 'teacher', label: 'TEACHER', x: margin + 95, w: 110 }, + { key: 'description', label: 'COURSE DESCRIPTION', x: margin + 205, w: 150 }, + { key: 'courseCode', label: 'COURSE', x: margin + 355, w: 70 }, + { key: 'groupSchedule', label: 'GROUP', x: margin + 425, w: 87 } ]; const cellSize = 9; const headerSize = 9; @@ -84,10 +84,12 @@ const f = isHeader ? bold : font; const size = isHeader ? headerSize : cellSize; const linesPerCol = cols.map((c) => wrapText(cells[c.key] || '', f, size, c.w - 6)); - const rowHeight = Math.max.apply(null, linesPerCol.map((l) => l.length)) * 11 + 8; - if (y - rowHeight < 50) { - page.drawText('', { x: margin, y: y, size: 1, font, color: black }); - return false; + const rowHeight = Math.max(...linesPerCol.map((l) => l.length)) * 11 + 8; + if (!isHeader && y - rowHeight < 50) { + // Row doesn't fit on the current page: start a new page and re-draw the header. + page = doc.addPage([612, 792]); + y = 792 - 50; + drawHeaderRow(); } if (isHeader) { page.drawRectangle({ x: margin, y: y - rowHeight, width: contentWidth, height: rowHeight, color: rgb(0.85, 0.9, 0.95) }); @@ -109,9 +111,13 @@ return true; } - const headerCells = {}; - cols.forEach((c) => { headerCells[c.key] = c.label; }); - drawRow(headerCells, true); + function drawHeaderRow() { + const headerCells = {}; + cols.forEach((c) => { headerCells[c.key] = c.label; }); + drawRow(headerCells, true); + } + + drawHeaderRow(); for (const course of courses) { const cells = { diff --git a/test/pdf.test.js b/test/pdf.test.js index 401dc17..0f2883d 100644 --- a/test/pdf.test.js +++ b/test/pdf.test.js @@ -1,5 +1,9 @@ const { test } = require('node:test'); const assert = require('node:assert'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); +const { execFileSync } = require('node:child_process'); global.PDFLib = require('pdf-lib'); const { buildStudyLoadPdf } = require('../pdf.js'); @@ -20,6 +24,20 @@ const sampleData = { totalUnits: '6.00' }; +function extractPdfText(bytes) { + const tmp = path.join(os.tmpdir(), 'study-load-test.pdf'); + fs.writeFileSync(tmp, Buffer.from(bytes)); + const script = [ + 'import fitz', + `doc = fitz.open('${tmp}')`, + 'text = ""', + 'for page in doc:', + ' text += page.get_text()', + 'print(text)' + ].join('\n'); + return execFileSync('python3', ['-c', script], { encoding: 'utf8' }); +} + test('buildStudyLoadPdf returns valid PDF bytes', async () => { const bytes = await buildStudyLoadPdf(sampleData); assert.ok(bytes instanceof Uint8Array); @@ -27,4 +45,8 @@ test('buildStudyLoadPdf returns valid PDF bytes', async () => { assert.strictEqual(head, '%PDF-'); const doc = await PDFLib.PDFDocument.load(bytes); assert.strictEqual(doc.getPageCount(), 1); + + const text = extractPdfText(bytes); + assert.ok(text.includes('CIS 2101'), 'PDF text should contain course code CIS 2101'); + assert.ok(text.includes("STUDENT'S STUDY LOAD"), 'PDF text should contain the title'); });