From c75850da2e29b7d9ad4ae7ce4b74b2181232e11b Mon Sep 17 00:00:00 2001 From: adrianbonpin Date: Wed, 19 Aug 2026 00:29:51 +0800 Subject: [PATCH] fix: remove header vertical dividers, fix header label positions, correct total underline --- pdf.js | 44 +++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/pdf.js b/pdf.js index a395fe3..8ec57f7 100644 --- a/pdf.js +++ b/pdf.js @@ -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; }