feat: add option to truncate long course descriptions to single line (default on, matches official PDF)

This commit is contained in:
adrianbonpin
2026-08-19 02:02:50 +08:00
parent b3b4495427
commit 106900860a
5 changed files with 57 additions and 4 deletions
+26 -2
View File
@@ -64,6 +64,22 @@
return out.length ? out : [''];
}
// Truncate a string to the longest prefix that fits within maxWidth at the
// given size, hard-cutting mid-word with no ellipsis. Mirrors the official
// PDF which cuts off course descriptions that exceed the column width.
function truncateToWidth(text, font, size, maxWidth) {
const s = String(text == null ? '' : text).trim();
if (!s) return '';
if (font.widthOfTextAtSize(s, size) <= maxWidth) return s;
let lo = 0, hi = s.length;
while (lo < hi) {
const mid = Math.ceil((lo + hi) / 2);
if (font.widthOfTextAtSize(s.slice(0, mid), size) <= maxWidth) lo = mid;
else hi = mid - 1;
}
return s.slice(0, lo);
}
// 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.
@@ -177,6 +193,9 @@
const { PDFDocument, StandardFonts, rgb } = PDFLib;
const header = data.header || {};
const courses = data.courses || [];
// Truncate long course descriptions to a single line matching the official
// PDF (default ON). Disabling allows descriptions to wrap.
const truncateDescription = data.truncateDescription !== false;
const doc = await PDFDocument.create();
let page = doc.addPage([612, 792]); // Letter
@@ -278,7 +297,12 @@
return cols.map((c) => {
let lines;
if (c.key === 'description') {
lines = wrapLines(cells.description, font, cellSize, c.w - 6);
if (truncateDescription) {
// Single line, hard-truncated to fit the column (official PDF style).
lines = [truncateToWidth(cells.description, font, cellSize, c.w - 3)];
} else {
lines = wrapLines(cells.description, font, cellSize, c.w - 6);
}
const sched = String(cells.schedule || '');
if (sched) lines.push(...wrapLines(sched, font, cellSize, c.w - 6));
} else {
@@ -408,5 +432,5 @@
return doc.save(); // returns Promise<Uint8Array>
}
return { buildStudyLoadPdf, compactProgram, deriveDateRange, formatSchedule, formatFullName };
return { buildStudyLoadPdf, compactProgram, deriveDateRange, formatSchedule, formatFullName, truncateToWidth };
});