ci: use Gitea API for releases #2
@@ -187,11 +187,23 @@
|
||||
const headerSize = 8;
|
||||
const lineH = 12;
|
||||
|
||||
// Reference header label x-positions (these do not all align with column
|
||||
// left edges because the official PDF centers/offsets the labels).
|
||||
const headerLabelX = { GROUP: 37.3, COURSE: 73.8, COURSE_DESCRIPTION: 172.9, TEACHER: 361.1, UNITS: 445.1, NOTE: 513.0 };
|
||||
// Vertical divider lines in the header row, matching the reference.
|
||||
const headerVLines = [36.0, 98.3, 336.9, 470.9, 575.3];
|
||||
// Header label positions tuned for Helvetica: left/center aligned within
|
||||
// each column so labels never overlap.
|
||||
function headerLabelXFor() {
|
||||
const widths = {};
|
||||
for (const c of cols) {
|
||||
const key = c.label.toUpperCase().replace(/\s+/g, '_');
|
||||
widths[key] = bold.widthOfTextAtSize(c.label, headerSize);
|
||||
}
|
||||
return {
|
||||
GROUP: cols[0].x,
|
||||
COURSE: cols[1].x + 5,
|
||||
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,
|
||||
NOTE: cols[5].x + 3
|
||||
};
|
||||
}
|
||||
|
||||
function rowLines(cells) {
|
||||
return cols.map((c) => {
|
||||
@@ -213,10 +225,12 @@
|
||||
const linesPerCol = rowLines(cells);
|
||||
|
||||
if (isHeader) {
|
||||
// Reference header row: top rule ~1.8pt above text baseline, bottom
|
||||
// rule 11.5pt below text baseline. pdf-lib draws text baseline ~8.6pt
|
||||
// above the passed y, while rectangles sit ~0.8pt below, so we offset
|
||||
// the lines by +9.6 (top) and -3.7 (bottom) relative to y.
|
||||
// Reference header row: only a top and bottom horizontal rule; no
|
||||
// vertical dividers. Text baseline at y; top rule 1.8pt above the
|
||||
// text bbox top; bottom rule 11.5pt below the text bbox top. For
|
||||
// 8pt Helvetica the text bbox top sits ~8.6pt above the baseline,
|
||||
// so the lines are offset by +9.6 (top) and -3.7 (bottom) relative
|
||||
// to y. Data rows start 13pt below the header text bbox top.
|
||||
const topY = y + 9.6;
|
||||
const botY = y - 3.7;
|
||||
if (botY < 50) {
|
||||
@@ -226,20 +240,18 @@
|
||||
}
|
||||
|
||||
hline(margin, margin + contentWidth, topY);
|
||||
for (const vx of headerVLines) {
|
||||
page.drawRectangle({ x: vx - 0.375, y: botY, width: 0.75, height: topY - botY, color: black });
|
||||
}
|
||||
|
||||
const labelXs = headerLabelXFor();
|
||||
for (let i = 0; i < cols.length; i++) {
|
||||
const c = cols[i];
|
||||
const labelKey = c.key === 'description' ? 'COURSE_DESCRIPTION' : c.key.toUpperCase();
|
||||
const lx = headerLabelX[labelKey] != null ? headerLabelX[labelKey] : c.x + 3;
|
||||
const labelKey = c.label.toUpperCase().replace(/\s+/g, '_');
|
||||
const lx = labelXs[labelKey];
|
||||
const lines = linesPerCol[i];
|
||||
page.drawText(lines[0], { x: lx, y, size, font: f, color: black });
|
||||
}
|
||||
|
||||
hline(margin, margin + contentWidth, botY);
|
||||
y -= 4.4;
|
||||
y -= 13.0;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -309,6 +321,8 @@
|
||||
// Total sits under the UNITS column, right-aligned within it.
|
||||
const unitsCol = cols[4];
|
||||
const w = bold.widthOfTextAtSize(data.totalUnits, 8);
|
||||
// Short underline just below the total, matching the reference.
|
||||
hline(unitsCol.x, unitsCol.x + unitsCol.w, y - 2);
|
||||
page.drawText(data.totalUnits, { x: unitsCol.x + unitsCol.w - w, y, size: 8, font: bold, color: black });
|
||||
y -= 12;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user