PDF: match reference spec - column widths, padding, numbering, alignment (Task #366)

User provided detailed PDF formatting spec. Changes:
- Column widths: 6/36/39/19 → 8/30/42/20 to match reference proportions
- Attendees alignment: "center" → right-aligned (RTL direction-based)
- Numbering format: "1- name" → "-1 name" (dash before number per reference)
- Cell padding: padX 4→8, padY 2→6 for more spacious cells
- Line height: fontSize*1.2 → fontSize*1.5 for better readability
- Both AR and EN PDFs verified valid with logo embedded
- Code review PASSED, e2e tests PASSED
This commit is contained in:
Riyadh
2026-05-04 08:25:14 +00:00
parent eea5b9eebe
commit 51291fb533
+10 -14
View File
@@ -712,24 +712,20 @@ export async function renderSchedulePdf(input: RenderPdfInput): Promise<Buffer>
// wraps for typical titles), while attendees now flow as one paragraph
// and don't need 44%. Mirrors the reference PDF's proportions.
const colWidths = [
Math.round(tableWidth * 0.06),
Math.round(tableWidth * 0.36),
Math.round(tableWidth * 0.39),
0, // last col (Time) absorbs rounding ≈ 19% — wide enough to keep
// a `HH:MM-HH:MM` range on a single line in both languages
// without stealing room from the Attendees column.
Math.round(tableWidth * 0.08),
Math.round(tableWidth * 0.30),
Math.round(tableWidth * 0.42),
0,
];
colWidths[3] = tableWidth - colWidths[0] - colWidths[1] - colWidths[2];
const headerLabels = isRtl
? // RTL: visually right-to-left ordering matches the schedule's
// Arabic layout (#, Meeting, Attendees, Time becomes Time first).
[input.labels.time, input.labels.attendees, input.labels.meeting, input.labels.no]
? [input.labels.time, input.labels.attendees, input.labels.meeting, input.labels.no]
: [input.labels.no, input.labels.meeting, input.labels.attendees, input.labels.time];
const headerWidths = isRtl ? [...colWidths].reverse() : colWidths;
const cellPadX = 4;
const cellPadY = 2;
const lineHeight = input.font.fontSize * 1.2;
const cellPadX = 8;
const cellPadY = 6;
const lineHeight = input.font.fontSize * 1.5;
function drawHeader(): number {
const headerHeight = lineHeight + cellPadY * 2;
@@ -794,7 +790,7 @@ export async function renderSchedulePdf(input: RenderPdfInput): Promise<Buffer>
} else {
personIdx += 1;
const t = a.title?.trim();
currentGroup.push(`${personIdx}- ${name}${t ? ` (${t})` : ""}`);
currentGroup.push(`-${personIdx} ${name}${t ? ` (${t})` : ""}`);
}
}
if (currentGroup.length > 0) {
@@ -802,7 +798,7 @@ export async function renderSchedulePdf(input: RenderPdfInput): Promise<Buffer>
}
return parts.join("\n");
})(),
align: "center",
align: alignment(input.font, isRtl),
},
{
text: formatTimeRange(meeting.startTime, meeting.endTime, input.lang),