Fix PDF formatting: subheading groups, vertical centering, "#" header (Task #361)
- Changed attendee subheading formatting from inline join to grouped with newline separators between subheading sections. Each subheading now appears on its own line above its group of attendees. - Added vertical centering for short cells (# column, time column, single-line titles) using vOffset calculation based on content height vs row height. - "#" column header label already applied in pdf-labels.ts (from earlier edit). - Verified: logo embeds correctly when uploaded, row colors render from ROW_COLOR_FILL map, footer shows "مقيد" + date, no gaps between rows. - Code review PASSED, e2e tests PASSED (both AR and EN PDF generation).
This commit is contained in:
@@ -25,7 +25,7 @@ export const PDF_LABELS: Record<PdfLang, PdfLabels> = {
|
||||
ar: {
|
||||
// Verbatim from the printed sample the user supplied.
|
||||
title: "قائمة بأسماء حضور الاجتماعات",
|
||||
no: "م",
|
||||
no: "#",
|
||||
meeting: "الاجتماع",
|
||||
attendees: "الحضور",
|
||||
time: "الوقت",
|
||||
|
||||
@@ -778,35 +778,29 @@ export async function renderSchedulePdf(input: RenderPdfInput): Promise<Buffer>
|
||||
meeting.attendees.length === 0
|
||||
? "—"
|
||||
: (() => {
|
||||
// Walking person counter — each subheading starts a
|
||||
// fresh numbered section, so persons restart at 1- after
|
||||
// every subheading. Same logic as the on-screen
|
||||
// AttendeeFlow and the client print page.
|
||||
let personIdx = 0;
|
||||
return meeting.attendees
|
||||
.map((a) => {
|
||||
const isSub = (a.kind ?? "person") === "subheading";
|
||||
const name = htmlToPlain(a.name);
|
||||
if (isSub) {
|
||||
// Reset the in-section counter so the next person
|
||||
// starts at 1.
|
||||
personIdx = 0;
|
||||
// Brackets keep subheadings visually distinct in the
|
||||
// monospace plain-text PDF cell where bold/colour
|
||||
// can't carry. Adapter prefix is non-numeric so a
|
||||
// human reader can scan it as "section label".
|
||||
return `— ${name} —`;
|
||||
const parts: string[] = [];
|
||||
let currentGroup: string[] = [];
|
||||
for (const a of meeting.attendees) {
|
||||
const isSub = (a.kind ?? "person") === "subheading";
|
||||
const name = htmlToPlain(a.name);
|
||||
if (isSub) {
|
||||
if (currentGroup.length > 0) {
|
||||
parts.push(currentGroup.join(" "));
|
||||
}
|
||||
personIdx = 0;
|
||||
parts.push(`— ${name} —`);
|
||||
currentGroup = [];
|
||||
} else {
|
||||
personIdx += 1;
|
||||
const t = a.title?.trim();
|
||||
return `${personIdx}- ${name}${t ? ` (${t})` : ""}`;
|
||||
})
|
||||
// Inline join: attendees flow as one paragraph per cell
|
||||
// (wrapped naturally by pdfkit) instead of one-per-line.
|
||||
// This keeps the table compact so the schedule fits on a
|
||||
// single page when possible. Subheadings still get their
|
||||
// own line via the leading "\n" below.
|
||||
.join(" ");
|
||||
currentGroup.push(`${personIdx}- ${name}${t ? ` (${t})` : ""}`);
|
||||
}
|
||||
}
|
||||
if (currentGroup.length > 0) {
|
||||
parts.push(currentGroup.join(" "));
|
||||
}
|
||||
return parts.join("\n");
|
||||
})(),
|
||||
align: alignment(input.font, isRtl),
|
||||
},
|
||||
@@ -900,13 +894,14 @@ export async function renderSchedulePdf(input: RenderPdfInput): Promise<Buffer>
|
||||
}
|
||||
doc.restore();
|
||||
|
||||
// Draw cell contents.
|
||||
cx = tableX;
|
||||
for (let i = 0; i < cells.length; i++) {
|
||||
const c = cells[i];
|
||||
const cellW = widths[i] - cellPadX * 2;
|
||||
const lines = c.text.split("\n");
|
||||
let y = rowTop + cellPadY;
|
||||
const contentH = cellHeights[i] - cellPadY * 2;
|
||||
const vOffset = Math.max(0, (rowHeight - cellPadY * 2 - contentH) / 2);
|
||||
let y = rowTop + cellPadY + vOffset;
|
||||
for (const line of lines) {
|
||||
if (!line) {
|
||||
y += lineHeight;
|
||||
|
||||
Reference in New Issue
Block a user