ci: use Gitea API for releases #2
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Vendored
+1
File diff suppressed because one or more lines are too long
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
"permissions": ["storage", "downloads"],
|
"permissions": ["storage", "downloads"],
|
||||||
"host_permissions": ["https://ismis.usc.edu.ph/*"],
|
"host_permissions": ["https://ismis.usc.edu.ph/*"],
|
||||||
"background": {
|
"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": [
|
"content_scripts": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -94,6 +94,58 @@
|
|||||||
return '';
|
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
|
// Load the bundled header logo. Prefer the transparent PNG (matches the
|
||||||
// official PDF, which masks the JPEG's baked-in background) for a clean
|
// 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.
|
// look on white; fall back to the JPEG in Node (tests) or if unavailable.
|
||||||
@@ -128,9 +180,8 @@
|
|||||||
|
|
||||||
const doc = await PDFDocument.create();
|
const doc = await PDFDocument.create();
|
||||||
let page = doc.addPage([612, 792]); // Letter
|
let page = doc.addPage([612, 792]); // Letter
|
||||||
const font = doc.embedStandardFont(StandardFonts.Helvetica);
|
const fonts = await loadFonts(doc, PDFLib);
|
||||||
const bold = doc.embedStandardFont(StandardFonts.HelveticaBold);
|
const { font, bold, italic } = fonts;
|
||||||
const italic = doc.embedStandardFont(StandardFonts.HelveticaOblique);
|
|
||||||
const margin = 36;
|
const margin = 36;
|
||||||
const contentWidth = 539.3; // matches reference right edge 575.3
|
const contentWidth = 539.3; // matches reference right edge 575.3
|
||||||
let y = 792 - 10; // near the very top, matching the reference logo position
|
let y = 792 - 10; // near the very top, matching the reference logo position
|
||||||
@@ -168,13 +219,14 @@
|
|||||||
const logoW = 292.5;
|
const logoW = 292.5;
|
||||||
const logoH = logoW * (logoImg.height / logoImg.width);
|
const logoH = logoW * (logoImg.height / logoImg.width);
|
||||||
page.drawImage(logoImg, { x: (612 - logoW) / 2, y: y - logoH, width: logoW, height: logoH });
|
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);
|
// Baseline advances match the reference (Segoe UI) header layout.
|
||||||
if (header.semester) center(String(header.semester).toUpperCase(), 9, font, 13.5);
|
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);
|
const dateRange = header.dateRange || deriveDateRange(header.semester);
|
||||||
if (dateRange) center(dateRange, 9, font, 13.5);
|
if (dateRange) center(dateRange, 9, font, 11.75);
|
||||||
if (header.dateEnrolled) left('Date Enrolled: ' + header.dateEnrolled, 8, italic, margin, 13.5);
|
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
|
// Student info row, laid out in fixed zones across the page (matching the
|
||||||
// reference): ID (bold) left, name at ~100, program at ~338, and
|
// 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).
|
// 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 });
|
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 ----
|
// ---- Table: GROUP, COURSE, COURSE DESCRIPTION, TEACHER, UNITS, NOTE ----
|
||||||
// Column boundaries tuned to the reference PDF (left edge 36, right edge 575.3).
|
// Column boundaries tuned to the reference PDF (left edge 36, right edge 575.3).
|
||||||
@@ -204,8 +256,8 @@
|
|||||||
const headerSize = 8;
|
const headerSize = 8;
|
||||||
const lineH = 12;
|
const lineH = 12;
|
||||||
|
|
||||||
// Header label positions tuned for Helvetica: left/center aligned within
|
// Header label positions tuned for Segoe UI (matches the reference):
|
||||||
// each column so labels never overlap.
|
// left/center aligned within each column so labels never overlap.
|
||||||
function headerLabelXFor() {
|
function headerLabelXFor() {
|
||||||
const widths = {};
|
const widths = {};
|
||||||
for (const c of cols) {
|
for (const c of cols) {
|
||||||
@@ -213,8 +265,8 @@
|
|||||||
widths[key] = font.widthOfTextAtSize(c.label, headerSize);
|
widths[key] = font.widthOfTextAtSize(c.label, headerSize);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
GROUP: cols[0].x,
|
GROUP: cols[0].x + 1.3,
|
||||||
COURSE: cols[1].x + 5,
|
COURSE: cols[1].x + 9,
|
||||||
COURSE_DESCRIPTION: cols[2].x + (cols[2].w - widths.COURSE_DESCRIPTION) / 2,
|
COURSE_DESCRIPTION: cols[2].x + (cols[2].w - widths.COURSE_DESCRIPTION) / 2,
|
||||||
TEACHER: cols[3].x + (cols[3].w - widths.TEACHER) / 2,
|
TEACHER: cols[3].x + (cols[3].w - widths.TEACHER) / 2,
|
||||||
UNITS: cols[4].x + (cols[4].w - widths.UNITS) / 2,
|
UNITS: cols[4].x + (cols[4].w - widths.UNITS) / 2,
|
||||||
|
|||||||
Reference in New Issue
Block a user