This HotPDF Component sample converts database records into a paginated PDF table. It is a compact report-generation pattern: read rows from a dataset, draw the table header, print each row at a known vertical position, and add a new page when the current page is full.
- Map this hotpdf table to pdf workflow to UK deployment expectations before release
- Review governance controls for audit trails, retention, and sign-off windows
- Run accessibility and rendering checks in your standard UK QA toolchain
- Capture approved fixtures in version control and use them as regression baselines
UK delivery readiness checklist
The example uses a legacy TTable data source, but the PDF logic is independent of that storage layer. The same rendering approach can be used with FireDAC, ADO, in-memory datasets, REST results, or any application data that can be enumerated row by row.
The key design point is to separate row rendering from page setup. PrintRow draws one row, PreparePage creates the repeated header and page metadata, and the main loop controls pagination. That separation makes the sample easier to adapt to wider tables, totals, grouping, or branded report headers.
Report structure
A PDF table exporter needs more than a loop over records. The output has to keep column alignment, repeat context on each page, and make the last page look intentional even when it contains only a few rows. This sample shows the minimum structure for that task: one routine for page setup, one routine for row drawing, and a main cursor loop that owns page breaks.
That structure gives production code a clear place for enhancements. A header routine can add logos, report names, filter summaries, and page numbers. A row routine can handle alignment, wrapping, alternating backgrounds, or numeric formatting. The loop can add grouping, totals, and continuation markers without rewriting the drawing code.
Data boundary checks
- Decide how to render null fields before drawing the row.
- Measure text that may exceed its column width and choose wrap, clip, or ellipsis behaviour.
- Keep numeric values right-aligned when the report is used for comparison.
- Handle empty datasets with a readable placeholder page instead of a blank PDF.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
programme TableDemo; {$APPTYPE CONSOLE} { Reduce EXE size by disabling as much of RTTI as possible } {$IFDEF VER210} {$WEAKLINKRTTI ON} {$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])} {$ENDIF} uses {$IFDEF VER230}System.Classes, System.SysUtils, Vcl.Graphics, DB, DBTables, {$ELSE} Classes, SysUtils, Graphics, DB, DBTables, {$ENDIF} HPDFDoc; var HotPDF: THotPDF; PageNum, VertPos: Integer; CustomerTable: TTable; Back: boolean; procedure PrintRow(Position: Integer; No, Company, Addr, City: AnsiString; ShowBackground: boolean); begin if ShowBackground then begin HotPDF.CurrentPage.SetRGBColour($FFF3DD); HotPDF.CurrentPage.Rectangle(50, Position, 520, 20); HotPDF.CurrentPage.Fill; HotPDF.CurrentPage.SetRGBColour(clBlack); end; HotPDF.CurrentPage.TextOut(70, Position, 0, No); HotPDF.CurrentPage.TextOut(110, Position, 0, Company); HotPDF.CurrentPage.TextOut(300, Position, 0, Addr); HotPDF.CurrentPage.TextOut(480, Position, 0, City); end; procedure PreparePage; begin HotPDF.CurrentPage.SetFont('Arial', [fsItalic], 10); HotPDF.CurrentPage.TextOut(50, VertPos, 0, 'customer.db' + ' Page' + AnsiString(IntToStr(PageNum))); HotPDF.CurrentPage.TextOut(480, VertPos, 0, AnsiString(DateTimeToStr(Now))); HotPDF.CurrentPage.MoveTo(50, VertPos + 15); HotPDF.CurrentPage.LineTo(570, VertPos + 15); HotPDF.CurrentPage.MoveTo(50, VertPos + 45); HotPDF.CurrentPage.LineTo(570, VertPos + 45); HotPDF.CurrentPage.Stroke; HotPDF.CurrentPage.SetFont('Times New Roman', [fsItalic, fsBold], 12); HotPDF.CurrentPage.SetRGBFillColour(clNavy); PrintRow(VertPos + 25, 'No', 'Company', 'Addr', 'City', false); HotPDF.CurrentPage.SetRGBFillColour(clBlack); Inc(VertPos, 20); end; begin CustomerTable := TTable.Create(nil); try CustomerTable.DatabaseName := 'DBDEMOS'; CustomerTable.TableName := GetCurrentDir + '\customer.db'; CustomerTable.Active := true; CustomerTable.First; HotPDF := THotPDF.Create(nil); try HotPDF.AutoLaunch := true; HotPDF.FileName := 'TableDemo.pdf'; HotPDF.PageLayout := plOneColumn; HotPDF.BeginDoc; HotPDF.CurrentPage.SetFont('Arial', [fsBold], 24); HotPDF.CurrentPage.TextOut(200, 20, 0, 'Customer report'); // Print PDF header PageNum := 1; VertPos := 60; PreparePage; // Table header Back := true; while (not(CustomerTable.Eof)) do begin Back := not(Back); if (VertPos > 700) then // If end of page then begin HotPDF.AddPage; // Add page, draw and print Inc(PageNum); // table header and set VertPos := 60; // new print position PreparePage; end; PrintRow(VertPos + 25, // print row from the current position AnsiString(CustomerTable.FieldValues['CustNo']), AnsiString(CustomerTable.FieldValues['Company']), AnsiString(CustomerTable.FieldValues['Addr1']), AnsiString(CustomerTable.FieldValues['City']), Back); Inc(VertPos, 20); CustomerTable.Next; end; HotPDF.EndDoc; finally HotPDF.Free; end; finally CustomerTable.Free; end; end. |
What to adapt in production
- Replace the sample DBDEMOS table with the application dataset or query result.
- Measure column widths from the expected data instead of hard-coding every x coordinate.
- Handle long text by wrapping or truncating fields before drawing the row.
- Repeat table headers on every new page so exported reports stay readable.
- Keep page margins and row height in named constants to make layout changes predictable.
Why direct PDF table drawing is useful
Direct drawing gives full control over fonts, colours, borders, page breaks, and summary rows without depending on a visual report designer. It is especially useful for server-side or batch-style tools where the output format must remain stable across machines.
The tradeoff is that layout decisions become explicit. That is usually a good fit for operational reports, invoices, audit exports, and batch tools where the required output is stable and the developer wants predictable control over every printed element.
Validation checklist
- Generate a report with zero rows, one row, exactly one full page, and one row past a page boundary.
- Test the widest expected values in every column.
- Open the generated PDF in more than one viewer to catch font or clipping differences.
- Confirm that repeated headers, margins, and page numbers remain consistent after localisation.