ci: use Gitea API for releases #2
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
@@ -38,12 +38,58 @@
|
|||||||
return out.length ? out : [''];
|
return out.length ? out : [''];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compact a degree name for display:
|
||||||
|
// "BACHELOR OF SCIENCE IN INFORMATION TECHNOLOGY" -> "BS IT".
|
||||||
|
// Multi-word fields become initials; single-word fields stay title-cased.
|
||||||
|
function compactProgram(program) {
|
||||||
|
const s = String(program || '').trim().toUpperCase();
|
||||||
|
let m = /^BACHELOR OF SCIENCE IN (.+)$/.exec(s);
|
||||||
|
let prefix = 'BS';
|
||||||
|
if (!m) {
|
||||||
|
m = /^BACHELOR OF ARTS IN (.+)$/.exec(s);
|
||||||
|
if (m) prefix = 'AB';
|
||||||
|
}
|
||||||
|
if (!m) return String(program || '').trim();
|
||||||
|
const field = m[1];
|
||||||
|
const words = field.split(/\s+/).filter((w) => !/^(AND|OF|THE|IN)$/.test(w));
|
||||||
|
if (words.length <= 1) {
|
||||||
|
const single = words[0] || field;
|
||||||
|
return prefix + ' ' + single.charAt(0).toUpperCase() + single.slice(1).toLowerCase();
|
||||||
|
}
|
||||||
|
return prefix + ' ' + words.map((w) => w[0]).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map a semester to the official date range label.
|
||||||
|
function deriveDateRange(semester) {
|
||||||
|
const s = String(semester || '').toUpperCase();
|
||||||
|
if (/1ST|FIRST/.test(s)) return 'AUGUST - JANUARY';
|
||||||
|
if (/2ND|SECOND/.test(s)) return 'JANUARY - MAY';
|
||||||
|
if (/SUMMER/.test(s)) return 'JUNE - JULY';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the bundled header logo (JPEG) in the extension background page.
|
||||||
|
// Returns null in Node (tests) or if the asset is unavailable.
|
||||||
|
async function loadLogo() {
|
||||||
|
try {
|
||||||
|
const url = (typeof browser !== 'undefined' && browser.runtime && browser.runtime.getURL)
|
||||||
|
? browser.runtime.getURL('assets/logo.jpeg')
|
||||||
|
: null;
|
||||||
|
if (!url) return null;
|
||||||
|
const resp = await fetch(url);
|
||||||
|
if (!resp.ok) return null;
|
||||||
|
return new Uint8Array(await resp.arrayBuffer());
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Builds a Letter-size PDF replicating the official ISMIS Study Load layout:
|
// Builds a Letter-size PDF replicating the official ISMIS Study Load layout:
|
||||||
// title block (STUDENT'S STUDY LOAD, semester, date range, date enrolled),
|
// header logo, title, semester + date range, date enrolled, a student info
|
||||||
// a student info row (ID, name, program, Year Level), the course table
|
// row (ID, name, program, Year Level), the course table (GROUP, COURSE,
|
||||||
// (GROUP, COURSE, COURSE DESCRIPTION, TEACHER, UNITS, NOTE), and a footer
|
// COURSE DESCRIPTION, TEACHER, UNITS, NOTE), and a footer (total under the
|
||||||
// (total units, printed-by). NOTE is the status; UNITS/NOTE are right-aligned;
|
// UNITS column, printed-by). NOTE is the status; schedule is stacked under
|
||||||
// schedule is stacked under the course description.
|
// the course description.
|
||||||
async function buildStudyLoadPdf(data) {
|
async function buildStudyLoadPdf(data) {
|
||||||
const PDFLib = getPDFLib();
|
const PDFLib = getPDFLib();
|
||||||
const { PDFDocument, StandardFonts, rgb } = PDFLib;
|
const { PDFDocument, StandardFonts, rgb } = PDFLib;
|
||||||
@@ -62,6 +108,12 @@
|
|||||||
const black = rgb(0, 0, 0);
|
const black = rgb(0, 0, 0);
|
||||||
const gray = rgb(0.3, 0.3, 0.3);
|
const gray = rgb(0.3, 0.3, 0.3);
|
||||||
|
|
||||||
|
const logoBytes = await loadLogo();
|
||||||
|
let logoImg = null;
|
||||||
|
if (logoBytes) {
|
||||||
|
try { logoImg = await doc.embedJpg(logoBytes); } catch (e) { logoImg = null; }
|
||||||
|
}
|
||||||
|
|
||||||
function center(text, size, f, dy) {
|
function center(text, size, f, dy) {
|
||||||
const w = f.widthOfTextAtSize(text, size);
|
const w = f.widthOfTextAtSize(text, size);
|
||||||
page.drawText(text, { x: (612 - w) / 2, y, size, font: f, color: black });
|
page.drawText(text, { x: (612 - w) / 2, y, size, font: f, color: black });
|
||||||
@@ -76,16 +128,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---- Header ----
|
// ---- Header ----
|
||||||
center("STUDENT'S STUDY LOAD", 9.5, bold, 18);
|
if (logoImg) {
|
||||||
if (header.semester) center(header.semester, 9, font, 15);
|
const logoW = 292.5;
|
||||||
if (header.dateRange) center(header.dateRange, 9, font, 15);
|
const logoH = logoW * (logoImg.height / logoImg.width);
|
||||||
|
page.drawImage(logoImg, { x: (612 - logoW) / 2, y: y - logoH, width: logoW, height: logoH });
|
||||||
|
y -= logoH + 14;
|
||||||
|
}
|
||||||
|
center("STUDENT'S STUDY LOAD", 9.5, bold, 16);
|
||||||
|
if (header.semester) center(String(header.semester).toUpperCase(), 9, font, 14);
|
||||||
|
const dateRange = header.dateRange || deriveDateRange(header.semester);
|
||||||
|
if (dateRange) center(dateRange, 9, font, 14);
|
||||||
if (header.dateEnrolled) left('Date Enrolled: ' + header.dateEnrolled, 8, italic, margin, 13);
|
if (header.dateEnrolled) left('Date Enrolled: ' + header.dateEnrolled, 8, italic, margin, 13);
|
||||||
|
|
||||||
// Student info row: ID (bold), name, program, "Year Level: N"
|
// Student info row: ID (bold), name, program (compacted), "Year Level: N"
|
||||||
const studentParts = [];
|
const studentParts = [];
|
||||||
if (header.studentId) studentParts.push({ text: header.studentId, f: bold });
|
if (header.studentId) studentParts.push({ text: header.studentId, f: bold });
|
||||||
if (header.fullName) studentParts.push({ text: header.fullName, f: font });
|
if (header.fullName) studentParts.push({ text: header.fullName, f: font });
|
||||||
if (header.program) studentParts.push({ text: header.program, f: font });
|
if (header.program) studentParts.push({ text: compactProgram(header.program), f: font });
|
||||||
if (header.yearLevel) studentParts.push({ text: 'Year Level: ' + header.yearLevel, f: font });
|
if (header.yearLevel) studentParts.push({ text: 'Year Level: ' + header.yearLevel, f: font });
|
||||||
if (studentParts.length) {
|
if (studentParts.length) {
|
||||||
let sx = margin;
|
let sx = margin;
|
||||||
@@ -137,7 +196,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isHeader) {
|
if (isHeader) {
|
||||||
// top line above the header row
|
|
||||||
hline(margin, margin + contentWidth, y);
|
hline(margin, margin + contentWidth, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +217,6 @@
|
|||||||
y -= rowHeight;
|
y -= rowHeight;
|
||||||
|
|
||||||
if (isHeader) {
|
if (isHeader) {
|
||||||
// line below the header row
|
|
||||||
hline(margin, margin + contentWidth, y);
|
hline(margin, margin + contentWidth, y);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -179,7 +236,7 @@
|
|||||||
courseCode: course.courseCode,
|
courseCode: course.courseCode,
|
||||||
description: course.description,
|
description: course.description,
|
||||||
schedule: course.schedule,
|
schedule: course.schedule,
|
||||||
teacher: course.teacher,
|
teacher: course.teacher || course.faculty,
|
||||||
units: course.units,
|
units: course.units,
|
||||||
status: course.status
|
status: course.status
|
||||||
};
|
};
|
||||||
@@ -193,8 +250,10 @@
|
|||||||
}
|
}
|
||||||
y -= 8;
|
y -= 8;
|
||||||
if (data.totalUnits) {
|
if (data.totalUnits) {
|
||||||
|
// Total sits under the UNITS column, right-aligned within it.
|
||||||
|
const unitsCol = cols[4];
|
||||||
const w = bold.widthOfTextAtSize(data.totalUnits, 9);
|
const w = bold.widthOfTextAtSize(data.totalUnits, 9);
|
||||||
page.drawText(data.totalUnits, { x: margin + contentWidth - w, y, size: 9, font: bold, color: black });
|
page.drawText(data.totalUnits, { x: unitsCol.x + unitsCol.w - 3 - w, y, size: 9, font: bold, color: black });
|
||||||
y -= 14;
|
y -= 14;
|
||||||
}
|
}
|
||||||
hline(margin, margin + contentWidth, y);
|
hline(margin, margin + contentWidth, y);
|
||||||
@@ -206,5 +265,5 @@
|
|||||||
return doc.save(); // returns Promise<Uint8Array>
|
return doc.save(); // returns Promise<Uint8Array>
|
||||||
}
|
}
|
||||||
|
|
||||||
return { buildStudyLoadPdf };
|
return { buildStudyLoadPdf, compactProgram, deriveDateRange };
|
||||||
});
|
});
|
||||||
|
|||||||
+18
-1
@@ -5,7 +5,7 @@ const os = require('node:os');
|
|||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const { execFileSync } = require('node:child_process');
|
const { execFileSync } = require('node:child_process');
|
||||||
global.PDFLib = require('pdf-lib');
|
global.PDFLib = require('pdf-lib');
|
||||||
const { buildStudyLoadPdf } = require('../pdf.js');
|
const { buildStudyLoadPdf, compactProgram, deriveDateRange } = require('../pdf.js');
|
||||||
|
|
||||||
const sampleData = {
|
const sampleData = {
|
||||||
header: {
|
header: {
|
||||||
@@ -49,4 +49,21 @@ test('buildStudyLoadPdf returns valid PDF bytes', async () => {
|
|||||||
const text = extractPdfText(bytes);
|
const text = extractPdfText(bytes);
|
||||||
assert.ok(text.includes('CIS 2101'), 'PDF text should contain course code CIS 2101');
|
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');
|
assert.ok(text.includes("STUDENT'S STUDY LOAD"), 'PDF text should contain the title');
|
||||||
|
assert.ok(text.includes('CHRISTIAN MADERAZO'), 'PDF text should contain the teacher');
|
||||||
|
assert.ok(text.includes('BS IT'), 'PDF text should contain the compacted program');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('compactProgram shortens degree names', () => {
|
||||||
|
assert.strictEqual(compactProgram('BACHELOR OF SCIENCE IN INFORMATION TECHNOLOGY'), 'BS IT');
|
||||||
|
assert.strictEqual(compactProgram('BACHELOR OF SCIENCE IN COMPUTER SCIENCE'), 'BS CS');
|
||||||
|
assert.strictEqual(compactProgram('BACHELOR OF ARTS IN POLITICAL SCIENCE'), 'AB PS');
|
||||||
|
assert.strictEqual(compactProgram('BACHELOR OF SCIENCE IN ACCOUNTANCY'), 'BS Accountancy');
|
||||||
|
assert.strictEqual(compactProgram('UNKNOWN PROGRAM'), 'UNKNOWN PROGRAM');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deriveDateRange maps semesters', () => {
|
||||||
|
assert.strictEqual(deriveDateRange('1ST Semester 2026'), 'AUGUST - JANUARY');
|
||||||
|
assert.strictEqual(deriveDateRange('2ND Semester 2026'), 'JANUARY - MAY');
|
||||||
|
assert.strictEqual(deriveDateRange('Summer 2026'), 'JUNE - JULY');
|
||||||
|
assert.strictEqual(deriveDateRange(''), '');
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user