ISMIS Helper: cross-browser build, CI, PDF fixes, Chrome download fix #1
+2
-1
@@ -168,9 +168,10 @@
|
||||
const included = ISMIStools.filterStudyLoadCourses(courses);
|
||||
if (!included.length) throw new Error('No enrolled courses found.');
|
||||
const totalUnits = ISMIStools.sumUnits(included);
|
||||
const { truncateDescription } = await browser.storage.local.get('truncateDescription');
|
||||
const res = await browser.runtime.sendMessage({
|
||||
type: 'generate-pdf',
|
||||
data: { header, courses: included, totalUnits }
|
||||
data: { header, courses: included, totalUnits, truncateDescription: truncateDescription !== false }
|
||||
});
|
||||
if (!res || !res.ok) throw new Error((res && res.error) || 'PDF generation failed');
|
||||
showToast('PDF downloaded.');
|
||||
|
||||
@@ -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 };
|
||||
});
|
||||
|
||||
@@ -32,6 +32,11 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="tab">
|
||||
<h2>PDF Settings</h2>
|
||||
<label class="row"><input type="checkbox" id="truncateToggle"> Truncate long course descriptions (official PDF style)</label>
|
||||
</section>
|
||||
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -18,6 +18,15 @@ $('#masterToggle').addEventListener('change', async (e) => {
|
||||
$('#masterLabel').textContent = e.target.checked ? 'Enabled' : 'Disabled';
|
||||
});
|
||||
|
||||
// ---- PDF settings: truncate long course descriptions (default ON) ----
|
||||
async function loadTruncate() {
|
||||
const { truncateDescription } = await browser.storage.local.get('truncateDescription');
|
||||
$('#truncateToggle').checked = truncateDescription !== false;
|
||||
}
|
||||
$('#truncateToggle').addEventListener('change', async (e) => {
|
||||
await browser.storage.local.set({ truncateDescription: e.target.checked });
|
||||
});
|
||||
|
||||
// ---- Profiles ----
|
||||
async function getProfiles() {
|
||||
const { profiles } = await browser.storage.local.get(PROFILES_KEY);
|
||||
@@ -104,5 +113,6 @@ $('#saveProfile').addEventListener('click', async () => {
|
||||
// ---- Init ----
|
||||
(async function init() {
|
||||
await loadMaster();
|
||||
await loadTruncate();
|
||||
renderProfiles(await getProfiles());
|
||||
})();
|
||||
|
||||
+14
-1
@@ -5,7 +5,7 @@ const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { execFileSync } = require('node:child_process');
|
||||
global.PDFLib = require('pdf-lib');
|
||||
const { buildStudyLoadPdf, compactProgram, deriveDateRange, formatSchedule, formatFullName } = require('../pdf.js');
|
||||
const { buildStudyLoadPdf, compactProgram, deriveDateRange, formatSchedule, formatFullName, truncateToWidth } = require('../pdf.js');
|
||||
|
||||
const sampleData = {
|
||||
header: {
|
||||
@@ -88,3 +88,16 @@ test('deriveDateRange maps semesters', () => {
|
||||
assert.strictEqual(deriveDateRange('Summer 2026'), 'JUNE - JULY');
|
||||
assert.strictEqual(deriveDateRange(''), '');
|
||||
});
|
||||
|
||||
test('truncateToWidth hard-cuts text to fit within maxWidth', async () => {
|
||||
const doc = await global.PDFLib.PDFDocument.create();
|
||||
const font = doc.embedStandardFont(global.PDFLib.StandardFonts.Helvetica);
|
||||
const longText = 'APPLICATION DEVELOPMENT AND EMERGING TECHNOLOGIES';
|
||||
const tr = truncateToWidth(longText, font, 8, 100);
|
||||
assert.ok(tr.length < longText.length, 'long text should be cut down');
|
||||
assert.ok(longText.startsWith(tr), 'result must be a prefix of the input');
|
||||
assert.ok(font.widthOfTextAtSize(tr, 8) <= 100, 'truncated line must fit maxWidth');
|
||||
const short = 'DATA STRUCTURES';
|
||||
assert.strictEqual(truncateToWidth(short, font, 8, 200), short);
|
||||
assert.strictEqual(truncateToWidth('', font, 8, 200), '');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user