feat: embed bundled Segoe UI fonts (with fontkit) for 1:1 typography match with reference PDF
This commit is contained in:
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"],
|
||||
"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": [
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user