create table in pdf report

i am trying to create a report that loads data from the db: this code:

Public Shared Sub BuildReport(document As PdfDocument, rptID As String)
' Start report content on a new page so the title page remains separate
' Use fixed Letter + 0.5" margins from ReportLayout (do not instantiate DevExpress)
document.Pages.Add()
Dim page As PdfPage = document.Pages(document.Pages.Count - 1)
Dim crop As PdfRectangle = page.CropBox

Dim pageWidth As Double = ModReportLayout.PageWidthPoints
Dim pageHeight As Double = ModReportLayout.PageHeightPoints
Dim marginLeft As Double = ModReportLayout.MarginLeftPoints
Dim marginRight As Double = ModReportLayout.MarginRightPoints
Dim marginTop As Double = ModReportLayout.MarginTopPoints
Dim marginBottom As Double = ModReportLayout.MarginBottomPoints
Dim printableWidth As Double = ModReportLayout.PrintableWidthPoints
Dim centerX As Double = marginLeft + (printableWidth / 2.0)

' baseTop leaves space for overlay header (OverlayHeaderFooterOnFile uses top - 18)
Dim baseTop As Double = crop.Top - marginTop
Dim Y As Func(Of Double, Double) = Function(offset) baseTop - offset

Dim primaryColor As PdfColor = ColorToPdf(ModReportTheme.PrimaryTextColor)
Dim secondaryColor As PdfColor = ColorToPdf(ModReportTheme.SecondaryTextColor)

Dim weekEnding As Date = Date.Now
Try
    If FrmWeeklyReports IsNot Nothing Then weekEnding = FrmWeeklyReports.DtpRptDte.Value
Catch
End Try

Dim monday As Date = weekEnding.AddDays(-4)
Dim friday As Date = weekEnding
Dim dateRangeText As String
If monday.Year = friday.Year Then
    dateRangeText = monday.ToString("MMM dd") & " - " & friday.ToString("MMM dd, yyyy")
Else
    dateRangeText = monday.ToString("MMM dd, yyyy") & " - " & friday.ToString("MMM dd, yyyy")
End If

Dim projectStart As Date = Date.MinValue
Try
    projectStart = Date.Parse(ModDefaultProject.RptStartDate)
Catch
    projectStart = friday.AddDays(-7)
End Try
Dim daysDiff As Integer = DateDiff(DateInterval.Day, projectStart, friday)
Dim weekNo As Integer = (daysDiff \ 7) + 1
If weekNo < 1 Then weekNo = 1

Dim startOffset As Double = 10.0

' Fonts: normal + header bold (fallbacks)
Dim headerFont As New PdfFont("Tahoma", 10)
Dim headerFontBold As PdfFont = Nothing
Try
    headerFontBold = New PdfFont("Tahoma-Bold", 10)
Catch
    Try
        headerFontBold = New PdfFont("Arial-BoldMT", 10)
    Catch
        headerFontBold = New PdfFont("Tahoma", 10) ' fallback to regular if bold not available
    End Try
End Try
Dim bodyFont As New PdfFont("Tahoma", 10)

' EMPLOYER (top)
Dim ft As New PdfFormattedText() With {.Font = New PdfFont("Tahoma", 12), .Color = ColorToPdf(ModReportTheme.SubtleGray)}
ft.Clear()
ft.Append(If(FrmMain IsNot Nothing, FrmMain.LblDefEmployerName.Text, ""))
page.Content.DrawText(ft, New PdfPoint((pageWidth - ft.Width) / 2.0, Y(0)))

startOffset += 40

' BLUE RULE
ft = New PdfFormattedText() With {.Font = New PdfFont("Tahoma", 6), .Color = primaryColor}
ft.Clear()
ft.Append(New String("─"c, 140))
page.Content.DrawText(ft, New PdfPoint((pageWidth - ft.Width) / 2.0, Y(40)))
startOffset += 40

' TITLE
ft = New PdfFormattedText() With {.Font = New PdfFont("Tahoma", 15), .Color = primaryColor}
ft.Clear()
ft.Append("Weekly Progress Summary")
page.Content.DrawText(ft, New PdfPoint(marginLeft, Y(80)))

' SUBTITLE
Dim c1 As Color = ModReportTheme.PrimaryTextColor
Dim c2 As Color = ModReportTheme.SecondaryTextColor
Dim subtitleX As Double = marginLeft
Dim subtitleY As Double = Y(110)
ft = New PdfFormattedText() With {.Font = New PdfFont("Tahoma", 11), .Color = ColorToPdf(c1)}
ft.Clear()
ft.Append($"({dateRangeText}) ")
page.Content.DrawText(ft, New PdfPoint(subtitleX, subtitleY))
Dim xAfter As Double = subtitleX + ft.Width
ft = New PdfFormattedText() With {.Font = New PdfFont("Tahoma", 10), .Color = ColorToPdf(c2)}
ft.Clear()
ft.Append("Week No. " & weekNo.ToString())
page.Content.DrawText(ft, New PdfPoint(xAfter, subtitleY))

startOffset += 60

' Data: read from DB (use global 'con' connection)
Dim connection As OleDbConnection = con
Dim reader As OleDbDataReader = Nothing

Try
    Dim cmd As New OleDbCommand("
        SELECT ProgressData, PctCompleted, Status 
        FROM tblWeeklyReportsProgress 
        WHERE RptID = @ID 
        ORDER BY ProgressData", connection)
    cmd.Parameters.AddWithValue("@ID", rptID)
    If connection.State <> ConnectionState.Open Then connection.Open()
    reader = cmd.ExecuteReader()

    If Not reader.HasRows Then
        ft = New PdfFormattedText() With {.Font = New PdfFont("Tahoma", 10), .Color = secondaryColor}
        ft.Clear()
        ft.Append("No progress data available.")
        page.Content.DrawText(ft, New PdfPoint(marginLeft, Y(startOffset)))
        Return
    End If

    ' Column widths — reduce Progress column so Status has more room
    Dim col1 As Double = Math.Round(printableWidth * 0.5, 2)  ' Progress
    Dim col2 As Double = Math.Round(printableWidth * 0.05, 2)  ' Pct
    Dim col3 As Double = printableWidth - col1 - col2          ' Status (remaining)

    ' Header row (bold)
    ft = New PdfFormattedText() With {.Font = headerFontBold, .Color = primaryColor}
    ft.Clear()
    ft.Append("Progress")
    page.Content.DrawText(ft, New PdfPoint(marginLeft, Y(startOffset)))

    ft.Clear()
    ft.Font = headerFontBold
    ft.Color = primaryColor
    ft.Append("Pct")
    page.Content.DrawText(ft, New PdfPoint(marginLeft + col1 + 8, Y(startOffset)))

    ft.Clear()
    ft.Font = headerFontBold
    ft.Append("Status")
    page.Content.DrawText(ft, New PdfPoint(marginLeft + col1 + col2 + 12, Y(startOffset)))

    startOffset += 20

    Dim rowPadding As Double = 6.0
    Dim currentY As Double = startOffset
    Dim availableHeight As Double = pageHeight - marginTop - marginBottom

    While reader.Read()
        Dim prog As String = If(reader.IsDBNull(0), "", reader.GetString(0))
        Dim pct As String = If(reader.IsDBNull(1), "", reader.GetValue(1).ToString())
        Dim stat As String = If(reader.IsDBNull(2), "", reader.GetString(2))

        Dim bodyFt As New PdfFormattedText() With {.Font = bodyFont, .Color = PdfColors.Black}

        ' Wrap Progress and Status first so row height is based on the taller column
        Dim progLines As List(Of String) = WrapTextToLines(page, prog, bodyFont, col1 - (rowPadding * 2))
        Dim statLines As List(Of String) = WrapTextToLines(page, stat, bodyFont, col3 - (rowPadding * 2))

        Dim linesCount As Integer = Math.Max(Math.Max(1, progLines.Count), Math.Max(1, statLines.Count))
        Dim lineHeight As Double = bodyFont.Size + 4
        Dim rowHeight As Double = linesCount * lineHeight + (rowPadding * 2)

        If currentY + rowHeight > availableHeight Then
            ' new page (use same margins)
            document.Pages.Add()
            page = document.Pages(document.Pages.Count - 1)
            crop = page.CropBox
            pageHeight = crop.Height
            baseTop = crop.Top - marginTop
            Y = Function(offset) baseTop - offset
            availableHeight = pageHeight - marginTop - marginBottom
            currentY = startOffset

            ' redraw header on new page
            ft = New PdfFormattedText() With {.Font = headerFontBold, .Color = primaryColor}
            ft.Clear()
            ft.Append("Progress")
            page.Content.DrawText(ft, New PdfPoint(marginLeft, Y(startOffset)))
            ft.Clear()
            ft.Font = headerFontBold
            ft.Color = primaryColor
            ft.Append("Pct")
            page.Content.DrawText(ft, New PdfPoint(marginLeft + col1 + 8, Y(startOffset)))
            ft.Clear()
            ft.Font = headerFontBold
            ft.Append("Status")
            page.Content.DrawText(ft, New PdfPoint(marginLeft + col1 + col2 + 12, Y(startOffset)))
        End If

        ' Draw ProgressData lines (top->down)
        Dim textY As Double = Y(currentY + rowPadding)
        For i As Integer = 0 To progLines.Count - 1
            bodyFt.Clear()
            bodyFt.Append(progLines(i))
            page.Content.DrawText(bodyFt, New PdfPoint(marginLeft, textY))
            textY -= lineHeight
        Next

        ' --- Pct: right-aligned in col2, keep % sign, bold, conditional color, never overflows ---
        Dim pctRaw As String = If(String.IsNullOrWhiteSpace(pct), "", pct.Trim())
        Dim pctNumeric As Double = 0
        Dim pctToParse As String = pctRaw
        If pctRaw.EndsWith("%"c) Then pctToParse = pctRaw.Substring(0, pctRaw.Length - 1).Trim()
        Double.TryParse(pctToParse, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, pctNumeric)

        Dim pctColor As System.Drawing.Color
        If String.IsNullOrEmpty(pctRaw) Then
            pctColor = System.Drawing.Color.Black
        ElseIf pctNumeric >= 100 Then
            pctColor = PercentHighColor
        ElseIf pctNumeric >= 80 Then
            pctColor = PercentMidColor
        ElseIf pctNumeric >= 50 Then
            pctColor = PercentLowColor
        ElseIf pctNumeric > 0 Then
            pctColor = PercentLowColor
        Else
            pctColor = System.Drawing.Color.Red
        End If

        Dim pctDisplay As String = If(String.IsNullOrEmpty(pctRaw), "-", pctRaw)
        Dim pctMaxWidth As Double = col2 - (rowPadding * 2) - 2.0

        ' Try bold fonts/smaller sizes until it fits
        Dim fittedPctFt As PdfFormattedText = Nothing
        Dim fontCandidates() As String = {"Tahoma-Bold", "Arial-BoldMT", "Tahoma"}
        For size = 10 To 6 Step -1
            Dim found As Boolean = False
            For Each fname In fontCandidates
                Try
                    Dim fnt As New PdfFont(fname, size)
                    Dim tmp As New PdfFormattedText() With {.Font = fnt, .Color = ColorToPdf(pctColor)}
                    tmp.Clear()
                    tmp.Append(pctDisplay)
                    If tmp.Width <= pctMaxWidth Then
                        fittedPctFt = tmp
                        found = True
                        Exit For
                    End If
                Catch
                    ' ignore missing fonts
                End Try
            Next
            If found Then Exit For
        Next

        ' If still too wide, use smallest font and ellipsize
        If fittedPctFt Is Nothing Then
            Dim smallestFont As PdfFont
            Try
                smallestFont = New PdfFont("Tahoma", 6)
            Catch
                smallestFont = New PdfFont("Arial", 6)
            End Try
            fittedPctFt = New PdfFormattedText() With {.Font = smallestFont, .Color = ColorToPdf(pctColor)}
            Dim disp As String = pctDisplay
            fittedPctFt.Clear()
            fittedPctFt.Append(disp)
            While fittedPctFt.Width > pctMaxWidth AndAlso disp.Length > 1
                disp = disp.Substring(0, disp.Length - 1)
                fittedPctFt.Clear()
                fittedPctFt.Append(disp & "…")
            End While
        End If

        ' Right-align inside Pct column (8pt right padding)
        Dim pctRightEdge As Double = marginLeft + col1 + col2 - 8.0
        Dim pctX As Double = pctRightEdge - fittedPctFt.Width
        page.Content.DrawText(fittedPctFt, New PdfPoint(pctX, Y(currentY + rowPadding)))

        ' Draw Status lines (top->down) within col3
        Dim statY As Double = Y(currentY + rowPadding)
        For i As Integer = 0 To statLines.Count - 1
            bodyFt.Clear()
            bodyFt.Append(statLines(i))
            page.Content.DrawText(bodyFt, New PdfPoint(marginLeft + col1 + col2 + 12, statY))
            statY -= lineHeight
        Next

        currentY += rowHeight + 4
    End While

Catch ex As Exception
    ft = New PdfFormattedText() With {.Font = New PdfFont("Tahoma", 10), .Color = PdfColors.Red}
    ft.Clear()
    ft.Append("Error building report: " & ex.Message)
    page.Content.DrawText(ft, New PdfPoint(marginLeft, Y(startOffset)))
Finally
    If reader IsNot Nothing AndAlso Not reader.IsClosed Then reader.Close()
End Try

End Sub

Just outputs text on the page. How to make the loop into a table style, so the column header uses the background and highlights every 3rd row?? 1st time trying pdf creation??

Hi Robert,

Currently, GemBox.Pdf doesn’t have high-level API support for writing table elements. This will be available in the future, but for now, I would suggest using an alternative approach.

For example, you could create a PDF file with a table using GemBox.Document and then insert those pages into your PDF report. This would be similar to the following example:

For example:

' Create PDF with table using GemBox.Document.
Dim document = New DocumentModel()

Dim section = New Section(document)
document.Sections.Add(section)

Dim table = New Table(document, 30, 3, Function(r, c) New TableCell(document, $"Row {r + 1}, Column {c + 1}"))
section.Blocks.Add(table)

table.TableFormat.PreferredWidth = New TableWidth(100, TableWidthUnit.Percentage)
table.TableFormat.DefaultCellPadding = New Padding(10)

document.Save("Table.pdf")

' Create PDF report using GemBox.Pdf.
Using pdfDocument = New PdfDocument()

    ' Fill out the report ...

    ' Add table.
    Using pdfTableDocument = PdfDocument.Load("Table.pdf")
        pdfDocument.Pages.AddClone(pdfTableDocument.Pages)
    End Using

    pdfDocument.Save("Report.pdf")
End Using

Regards,
Mario