ISMIS Helper: cross-browser build, CI, PDF fixes, Chrome download fix #1

Merged
adrianbonpin merged 55 commits from feat/ismis-helper into prod 2026-08-18 19:13:50 +00:00
7 changed files with 67 additions and 14 deletions
Showing only changes of commit b3b4495427 - Show all commits
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -6,7 +6,7 @@
"permissions": ["storage", "downloads"],
"host_permissions": ["https://ismis.usc.edu.ph/*"],
"background": {
"scripts": ["libs/pdf-lib.min.js", "pdf.js", "background.js"]
"scripts": ["libs/fontkit.min.js", "libs/pdf-lib.min.js", "pdf.js", "background.js"]
},
"content_scripts": [
{
+65 -13
View File
@@ -94,6 +94,58 @@
return '';
}
// Fetch a bundled asset in the extension background page. Returns null in
// Node (tests) or if the asset is unavailable.
async function loadAssetBytes(asset) {
const ext = (typeof browser !== 'undefined' && browser.runtime && browser.runtime.getURL);
try {
const url = ext ? browser.runtime.getURL(asset) : 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;
}
}
// Embed the bundled Segoe UI fonts (regular/bold/italic). Fontkit is
// required by pdf-lib to embed custom TTF fonts. Falls back to Helvetica
// (standard fonts) when the assets are unavailable (e.g. Node tests).
async function loadFonts(doc, PDFLib) {
const { StandardFonts } = PDFLib;
const fontkit = (typeof self !== 'undefined' && self.fontkit) ||
(typeof globalThis !== 'undefined' && globalThis.fontkit) ||
null;
const segoe = {
regular: 'assets/fonts/Segoe-UI.ttf',
bold: 'assets/fonts/Segoe-UI-Bold.ttf',
italic: 'assets/fonts/Segoe-UI-Italic.ttf'
};
try {
if (fontkit) {
doc.registerFontkit(fontkit);
const regular = await loadAssetBytes(segoe.regular);
const bold = await loadAssetBytes(segoe.bold);
const italic = await loadAssetBytes(segoe.italic);
if (regular && bold && italic) {
return {
font: await doc.embedFont(regular),
bold: await doc.embedFont(bold),
italic: await doc.embedFont(italic),
isCustom: true
};
}
}
} catch (e) { /* fall through to Helvetica */ }
return {
font: doc.embedStandardFont(StandardFonts.Helvetica),
bold: doc.embedStandardFont(StandardFonts.HelveticaBold),
italic: doc.embedStandardFont(StandardFonts.HelveticaOblique),
isCustom: false
};
}
// Load the bundled header logo. Prefer the transparent PNG (matches the
// official PDF, which masks the JPEG's baked-in background) for a clean
// look on white; fall back to the JPEG in Node (tests) or if unavailable.
@@ -128,9 +180,8 @@
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 fonts = await loadFonts(doc, PDFLib);
const { font, bold, italic } = fonts;
const margin = 36;
const contentWidth = 539.3; // matches reference right edge 575.3
let y = 792 - 10; // near the very top, matching the reference logo position
@@ -168,13 +219,14 @@
const logoW = 292.5;
const logoH = logoW * (logoImg.height / logoImg.width);
page.drawImage(logoImg, { x: (612 - logoW) / 2, y: y - logoH, width: logoW, height: logoH });
y -= logoH + 8;
y -= logoH + 8.79;
}
center("STUDENT'S STUDY LOAD", 9.5, bold, 14);
if (header.semester) center(String(header.semester).toUpperCase(), 9, font, 13.5);
// Baseline advances match the reference (Segoe UI) header layout.
center("STUDENT'S STUDY LOAD", 9.5, bold, 13.84);
if (header.semester) center(String(header.semester).toUpperCase(), 9, font, 13.76);
const dateRange = header.dateRange || deriveDateRange(header.semester);
if (dateRange) center(dateRange, 9, font, 13.5);
if (header.dateEnrolled) left('Date Enrolled: ' + header.dateEnrolled, 8, italic, margin, 13.5);
if (dateRange) center(dateRange, 9, font, 11.75);
if (header.dateEnrolled) left('Date Enrolled: ' + header.dateEnrolled, 8, italic, margin, 14.53);
// Student info row, laid out in fixed zones across the page (matching the
// reference): ID (bold) left, name at ~100, program at ~338, and
@@ -188,7 +240,7 @@
// Right-aligned to the content edge, nudged left ~6pt (half of 1rem).
page.drawText(yearLevelText, { x: margin + contentWidth - w - 6, y, size: 9, font, color: black });
}
if (header.studentId || header.fullName || header.program || yearLevelText) y -= 14.5;
if (header.studentId || header.fullName || header.program || yearLevelText) y -= 13.59;
// ---- Table: GROUP, COURSE, COURSE DESCRIPTION, TEACHER, UNITS, NOTE ----
// Column boundaries tuned to the reference PDF (left edge 36, right edge 575.3).
@@ -204,8 +256,8 @@
const headerSize = 8;
const lineH = 12;
// Header label positions tuned for Helvetica: left/center aligned within
// each column so labels never overlap.
// Header label positions tuned for Segoe UI (matches the reference):
// left/center aligned within each column so labels never overlap.
function headerLabelXFor() {
const widths = {};
for (const c of cols) {
@@ -213,8 +265,8 @@
widths[key] = font.widthOfTextAtSize(c.label, headerSize);
}
return {
GROUP: cols[0].x,
COURSE: cols[1].x + 5,
GROUP: cols[0].x + 1.3,
COURSE: cols[1].x + 9,
COURSE_DESCRIPTION: cols[2].x + (cols[2].w - widths.COURSE_DESCRIPTION) / 2,
TEACHER: cols[3].x + (cols[3].w - widths.TEACHER) / 2,
UNITS: cols[4].x + (cols[4].w - widths.UNITS) / 2,