Fix PDF table rows splitting across lines (#370)
Root cause: the height measurement probe reimplemented wrapping logic inline, separate from the actual drawWrappingLine renderer. The two implementations could diverge, causing the predicted row height to be shorter than the actual rendered content, making cells overflow their row borders and visually bleed into the next row. Fix: - Extract shared computeVisualLines() function that both the probe and drawWrappingLine use as the single source of truth for word- wrapping and line counting. This guarantees the predicted height always matches the actual rendered height. - Save/restore doc.y around the probe loop so PDFKit's internal cursor doesn't drift between measurement and rendering passes. - Save/restore doc.y per cell render block to prevent PDFKit cursor side effects from affecting adjacent cells. - Add strict overflow guard: drawWrappingLine accepts a maxY param and stops rendering visual lines that would exceed the row bottom. The render loop also breaks on y >= rowBottom before starting new source lines. Together these form a two-level clamp that prevents any content from bleeding beyond row borders. - drawWrappingLine is now a thin wrapper that calls computeVisualLines then draws each line via drawBidiLine, keeping the code DRY. Tested with Arabic RTL and English LTR PDFs containing meetings with 10+ attendees, long titles, subheadings, and mixed bidi text. All rows stay properly aligned within their borders.
This commit is contained in:
@@ -482,6 +482,7 @@ type DrawOpts = {
|
||||
mapping: FamilyMapping;
|
||||
color?: string;
|
||||
lineHeight?: number;
|
||||
maxY?: number;
|
||||
};
|
||||
|
||||
const BIDI_CONTROL_RE = /[\u200E\u200F\u200B\u202A-\u202E\u2066-\u2069]/g;
|
||||
@@ -652,6 +653,7 @@ function drawWrappingLine(
|
||||
});
|
||||
let totalH = 0;
|
||||
for (const vl of lines) {
|
||||
if (opts.maxY !== undefined && opts.y + totalH + lh > opts.maxY + 0.5) break;
|
||||
if (vl) drawBidiLine(doc, vl, { ...opts, y: opts.y + totalH });
|
||||
totalH += lh;
|
||||
}
|
||||
@@ -986,6 +988,7 @@ export async function renderSchedulePdf(input: RenderPdfInput): Promise<Buffer>
|
||||
const rowBottom = rowTop + rowHeight;
|
||||
let cx = tableX;
|
||||
for (let i = 0; i < cells.length; i++) {
|
||||
const savedCellY = doc.y;
|
||||
const c = cells[i];
|
||||
const cellW = widths[i] - cellPadX * 2;
|
||||
const textLines = c.text.split("\n");
|
||||
@@ -1006,9 +1009,11 @@ export async function renderSchedulePdf(input: RenderPdfInput): Promise<Buffer>
|
||||
mapping,
|
||||
color: input.font.fontColor,
|
||||
lineHeight,
|
||||
maxY: rowBottom,
|
||||
});
|
||||
y += used;
|
||||
}
|
||||
doc.y = savedCellY;
|
||||
cx += widths[i];
|
||||
}
|
||||
doc.y = rowTop + rowHeight;
|
||||
|
||||
Reference in New Issue
Block a user