Skip to content

Add table headers on each page #1657

@Mimetis

Description

@Mimetis

I made several search on how to add the headers of my table, each time a new page is added.

Since there is no concept of "headers" in pdfkit table features, there is no option for that particular behavior
Would be nice to have it, in my opinion

Anyway, since I spent a lot of time adding this feature in my PDF generation, here is a small example on how to do so:

/**
 * Pre-calculate the height of a row based on its cell content
 */
function calculateRowHeight(
  doc: any,
  rowData: any[],
  columns: any[],
  columnWidths: number[],
  rowStyle: any
): number {
  let maxHeight = 0;

  // Iterate through each cell in the row
  rowData.forEach((cellData, colIndex) => {
    const column = columns[colIndex];
    const colWidth = columnWidths[colIndex];

    if (!cellData || !column) return;

    const text = String(cellData.text || '');
    const fontSize = cellData.fontSize || column.font?.size || 11;
    const font = cellData.font || (column.font?.weight === 'bold' ? 'Helvetica-Bold' : 'Helvetica');

    // Calculate padding
    const padding = typeof cellData.padding === 'number' ? cellData.padding : 4;
    const paddingTop = padding;
    const paddingBottom = padding;
    const paddingLeft = padding;
    const paddingRight = padding;

    // Available width for text (column width minus padding)
    const textWidth = colWidth - paddingLeft - paddingRight;

    // Set font for measurement
    doc.save();
    doc.font(font);
    doc.fontSize(fontSize);

    // Calculate text height
    const textHeight = doc.heightOfString(text, {
      width: textWidth,
      lineBreak: true,
    });

    doc.restore();

    // Total cell height including padding
    const cellHeight = textHeight + paddingTop + paddingBottom;

    // Track maximum height
    if (cellHeight > maxHeight) {
      maxHeight = cellHeight;
    }
  });

  // Apply row height constraints if specified
  const minHeight = rowStyle?.minHeight || 0;
  const rowHeight = rowStyle?.height;

  if (typeof rowHeight === 'number') {
    // Fixed height specified
    return Math.max(rowHeight, minHeight);
  }

  // Return calculated height or minimum height, whichever is larger
  return Math.max(maxHeight, minHeight);
}

/**
 * Check if the next row will trigger a page break
 */
function willNextRowTriggerPageBreak(
  doc: any,
  nextRowData: any[],
  columns: any[],
  columnWidths: number[],
  rowStyle: any
) {
  const currentY = doc.y;
  const pageHeight = doc.page.height;
  const bottomMargin = doc.page.margins.bottom;
  const effectiveBottomLimit = pageHeight - bottomMargin;

  // Pre-calculate the next row's height
  const nextRowHeight = calculateRowHeight(doc, nextRowData, columns, columnWidths, rowStyle);

  if (!nextRowHeight) {
    return false; // No height calculated
  }

  // Check if adding the next row would exceed the page
  const nextRowEndY = currentY + nextRowHeight;

  return nextRowEndY > (effectiveBottomLimit - 5); // magic number to be sure we don't have edges cases depending on how pdf kit handles this
}



// Combine header and data rows
const tableData = [headerRow, ...dataRows];

let table = doc.table(tableOptions as any);

for (let i = 0; i < tableData.length; i++) {
  const rowData = tableData[i];

  // Check if the next row will trigger a page break (skip for last row)
  if (i < tableData.length - 1) {

    const willBreak = willNextRowTriggerPageBreak(
      doc,
      rowData,
      columns,
      actualColumnWidths,
      element.rowStyle
    );

    if (willBreak) {
      console.log('will break on row', rowData[0].text)
      table.end();
      doc.addPage();
      tableOptions.position.y = doc.page.margins.top;
      table = doc.table(tableOptions as any);
      table.row(tableData[0] as any);
    }
  }

  table = table.row(rowData as any);
}
Image

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions