Chapter 7: Tables


7.1 Table Support Overview

With AspPDF, you can create HTML-style tables and fill them with text data. Once a table is created and filled, it can be rendered onto a canvas.

Tables are represented by the PdfTable object creatable via the CreateTable method of the PdfDocument object.

PdfTable is a free-floating entity. It is not tied to a canvas, page or any other object. Creating or changing a PdfTable object has no effect on the appearance of the document until the table, or any portion thereof, is rendered onto a canvas via the Canvas.DrawTable method. The same table can be drawn on multiple pages, or be part of one or more PdfGraphics objects.

7.1.1 Table Creation Parameters

The Doc.CreateTable method requires two parameters: Width and Height (in default user units). The number of columns and rows in the table is specified via Cols and Rows, respectively. These parameters are both 1 by default. When a table is created, all rows and columns are spaced out evenly within the table.

A generic table has an outer border, and several rows of cells. Each cell has a border of its own. Cells are separated from each other and the outer border by a margin. The following diagram illustrates various table parameters:

A table's origin is located in the upper-left corner of the table. The y axis still extends upwards, so all y-coordinates of the cells are negative.

Border is the width of the table's outer border (0 by default.)

CellBorder is the width of all cell borders (1 by default.) CellSpacing is the width of a margin that separates individual cells from each other and the outer border (0 by default).

CellPadding is the width of a margin within a cell that separates text written in that cell from the cell's borders (0 by default.)

The table colors are specified by the following parameters: BorderColor (the color of the outer border, black by default), CellBorderColor (the color of all cell borders, black by default), BgColor (the color of areas between cells, transparent by default), and CellBgColor (the color of areas within cell borders, transparent by default).

As of Version 2.7, color spaces other than RGB can be used to specify table colors. See Section 16.4 - Using Color Spaces with PdfTable and Other Objects for more information.

7.1.2 Working with Individual Rows and Cells

The PdfTable object provides the Rows property which returns the PdfRows collection of PdfRow objects representing individual table rows. The PdfRow object allows you to change this row's height and move it up and down within the table. It also allows you to change border and background colors of all its cells via the BorderColor and BgColor properties.

The PdfRow object provides the Cells property which returns the PdfCells collection of PdfCell objects representing individual cells in a row. The PdfCell object enables you to change the border width, border color, background color, size, padding, ColSpan and RowSpan of an individual cell.

An individual table cell can also be referenced directly via PdfTable's default property At by specifying 1-based row and cell indices as arguments, as follows:

Set Cell = Table.At(4, 2)

or simply

Set Cell = Table(4, 2)

Rows and Cells are numbered from left to right and from top to bottom. Therefore, the upper-left cell of a table has the indices (1, 1).

The PdfCell object also provides the AddText method for placing text in a cell. This method is almost identical to Canvas.DrawText, except that instead of the X, Y, Width and Height parameters the cell coordinates and sizes are used, and two extra optional parameters, IndentX and IndentY are added for precise positioning of text inside the cell. This method also uses an optional Expand parameter described below. AddText's Font argument is optional but for it to be omitted, you must specify a table-wide default font via the Table.Font property.

The following code sample uses all the properties, methods and collections described above to draw a chessboard. Here, we also use a freeware symbol font Chess Merida residing in the file MERIFONT.TTF.

' Create 8x8 table to depict a chessboard
Set Table = Doc.CreateTable( "width=200; height=200; rows=8; cols=8; border=1; cellborder=0; cellspacing=2")
' Select a Chess font to depict chess pieces
Set Font = Doc.Fonts.LoadFromFile( Server.MapPath("MERIFONT.TTF") )

Table.Font = Font

' initialize Pieces array
Dim Pieces
Pieces = Array(0,_
&HF074, &HF06D, &HF076, &HF077, &HF06C, &HF076, &HF06D, &HF074, _
&HF06F, &HF06F, &HF06F, &HF06F, &HF06F, &HF06F, &HF06F, &HF06F, _
&HF000, &HF000, &HF000, &HF000, &HF000, &HF000, &HF000, &HF000, _
&HF000, &HF000, &HF000, &HF000, &HF000, &HF000, &HF000, &HF000, _
&HF000, &HF000, &HF000, &HF000, &HF000, &HF000, &HF000, &HF000, _
&HF000, &HF000, &HF000, &HF000, &HF000, &HF000, &HF000, &HF000, _
&HF070, &HF070, &HF070, &HF070, &HF070, &HF070, &HF070, &HF070, _
&HF072, &HF06E, &HF062, &HF071, &HF06B, &HF062, &HF06E, &HF072 )

' go over all cells in the table
For Each Row in Table.Rows
   For Each Cell in Row.Cells
      ' set background on cells which sum of indices is odd
      If (Cell.Index + Row.Index) Mod 2 = 1 Then
         Cell.BgColor = "brown"
      End if

      Piece = Pieces( 8 * (Row.Index - 1) + Cell.Index )
      Cell.AddText ChrW(Piece), "size=20; indentx=1; indenty=1"

' Add a new page
Set Page = Doc.Pages.Add

Page.Canvas.DrawTable Table, "x=206, y=498"
... // beginning omitted for brevity
// initialize Pieces array
int [] Pieces = {0,
0xF074, 0xF06D, 0xF076, 0xF077, 0xF06C, 0xF076, 0xF06D, 0xF074,
0xF06F, 0xF06F, 0xF06F, 0xF06F, 0xF06F, 0xF06F, 0xF06F, 0xF06F,
0xF000, 0xF000, 0xF000, 0xF000, 0xF000, 0xF000, 0xF000, 0xF000,
0xF000, 0xF000, 0xF000, 0xF000, 0xF000, 0xF000, 0xF000, 0xF000,
0xF000, 0xF000, 0xF000, 0xF000, 0xF000, 0xF000, 0xF000, 0xF000,
0xF000, 0xF000, 0xF000, 0xF000, 0xF000, 0xF000, 0xF000, 0xF000,
0xF070, 0xF070, 0xF070, 0xF070, 0xF070, 0xF070, 0xF070, 0xF070,
0xF072, 0xF06E, 0xF062, 0xF071, 0xF06B, 0xF062, 0xF06E, 0xF072 };
// go over all cells in the table
foreach( IPdfRow objRow in objTable.Rows )
   foreach( IPdfCell objCell in objRow.Cells )
      // set background on cells which sum of indices is odd
      if( (objCell.Index + objRow.Index) % 2 == 1 )
         objCell.BgColor = "brown";
      int Piece = Pieces[ 8 * (objRow.Index - 1) + objCell.Index ];
      objCell.AddText( ((char)Piece).ToString(), "size=20; indentx=1; indenty=1", Missing.Value );
... // end omitted for brevity

Click the links below to run this code sample:

7.1.3 Cell Border Management

By default, each cell's border is a rectangle with all four sides drawn in the same color (specified via Cell.BorderColor). It is also possible to hide any, or all, of this rectangle's sides, and also assign individual colors to each side.

Side visibility and coloring is set via PdfCell's SetBorderParams method which accepts a PdfParam object or parameter string as an argument. The following parameters can be used (all optional):

  • Top, Bottom, Right, Left - if set to False, hide the top, bottom, right or left side of the cell border, respectively. True by default.
  • TopColor, BottomColor, RightColor, LeftColor - specify the line color for the top, bottom, right or left side, respectively.

The following code fragment hides the left, top and right sides of a cell and sets the bottom side color to green:

Table.At(1, 2).SetBorderParams "Left=False, Right=False, Top=False, BottomColor=Green"

7.1.4 Drawing in a Cell

The PdfCell object provides a Canvas property which enables you to draw inside an individual cell the same way you would on a page as a whole. The coordinate space origin is located in the lower-left corner of the cell. Cell.Canvas allows you to draw graphics, text and even other tables inside a cell.

When a table is rendered onto a page, its cells' contents is rendered in the following order:

1. Cell background.
2. The contents of Cell.Canvas, if any.
3. Text strings specified via Cell.AddText, if any.
4. Cell borders.

The following code fragment draws a small table inside a large table's cell. The small table, in turn, has the word "Hello" drawn in its upper-left cell.

Set SmallTable = Doc.CreateTable("Height=30; Width=30; cols=2; rows=2")
SmallTable.At(1, 1).Canvas.DrawText "Hello", "x=1, y=15; size=5", font

Table.At(1, 2).Canvas.DrawTable SmallTable, "x=2; y=50"

Note that unlike the Cell.AddText method, Cell.Canvas.DrawText does not affect the size of the cell.

7.2 Cell Spanning and Size Adjustment

As mentioned earlier, all cells are created equal in size when a new table is created. Heights and widths of individual rows and columns can then be adjusted using the Row.Height, Cell.Height and Cell.Width properties. Changing Row.Height changes the heights of all the cells in that row, as well as the overall table height. Changing Cell.Height has the same effect as changing the height of the row this cell belongs to. Changing Cell.Width also changes the widths of cells above and below this cell, as well as the overall table width.

The method Cell.AddText, if called with the parameter Expand set to True, has the side effect of changing the cell's height to accommodate the entire text string passed to it.

Similarly to HTML tables, PdfTable enables you to change the row span and column span of individual cells via the properties Cell.RowSpan and Cell.ColSpan, respectively. A cell whose ColSpan and/or RowSpan is set to a number greater than 1 "swallows" cells to the right and/or below itself. This cell's width and/or height is increased by that of the cells it "swallows" (plus the width of cell spacing, if applicable).

The following code fragment transforms a default 3 x 4 table (shown in the upper half of the diagram below) into a table shown in the lower half of the diagram:

Set param = Pdf.CreateParam("rows=3, cols=4, width=400, height=150")
... (set other parameters)
Set Table = Doc.CreateTable(param)

Table.Rows(1).Cells(1).width = 20
Table(1, 1).RowSpan = 3
Table(1, 3).RowSpan = 2
Table(1, 3).ColSpan = 2
Table.Rows(1).Cells(2).width = 200
Table(3, 2).AddText "Hello World!!!", "size=30; expand=true", Font

Note that the height of row 3 was implicitly increased by calling AddText with the Expand parameter set to True. If that parameter were omitted, only the word "Hello" would be showing and the cell height would remain unchanged.

Rows can be added to a table via the Rows.Add method which takes two arguments: row height and, optionally, the desired position of the new row. By default, rows are added to the end of the table. The second argument is the 1-based index of a row to insert the new one after. The value of 0 means the new row becomes first.

7.3 Table Rendering

Once a table is created, adjusted and filled with data, it can be rendered onto a canvas via the Canvas.DrawTable method. This method expects two requires parameters, X and Y, which specify the coordinates of the table's upper-left corner (origin) on the canvas.

The DrawTable method also accepts an optional MaxHeight parameter which limits the vertical space the table can occupy on a canvas. By default, the limit is the bottom of the canvas. If a row does not fully fit in the space provided, it is not rendered, and neither are the rows that follow.

The DrawTable method is capable of rendering only a certain range of rows, or a set of ranges. This is especially handy when you need to render a large table that spans several pages, and you want the header and/or footer rows of the table to be present on each page. The row ranges are specified via the parameters RowFrom - RowTo, RowFrom1 - RowTo1, RowFrom2- RowTo2, etc.

DrawTable returns the index of the last row it managed to render.

The following code sample renders a multi-page report from data residing in a simple semicolon-separated text file, data.txt. Its content is shown here:

1;Travel;Trip to New York to meet Mr Smith of ABC, Inc.;1;1/4/2003 0:00:00;$560.00
2;Travel;Las Vegas conference airline ticket;1;5/2/2003 0:00:00;$1230.00
3;Meals;Las Vegas conference, meals;1;6/2/2003 0:00:00;$250.00
4;Entertainment;Corporate retreat, Florida;0;10/5/2003 0:00:00;$5450.00
5;Office Expenses;Printer paper;0;12/6/2003 0:00:00;$50.00
6;Equipment;Canon Digital Camera;1;7/3/2003 0:00:00;$899.00
7;Equipment;Dell Notebook;0;8/1/2003 0:00:00;$1899.00
8;Professional Services;Web site design;1;9/2/2003 0:00:00;$6500.00
9;Legal Services;Out of court settlement with XYZ, Inc.;0;10/2/2003 0:00:00;$12000.00
10;Travel;Trip to XYZ, Inc. headquarters in Pittsburgh, PA to sign settlement papers and meet with CIO.;0;11/2/2003 0:00:00;$460.00

This code sample dynamically appends pages to the PDF document as needed to accommodate the table.

Set PDF = Server.CreateObject("Persits.Pdf")

' Create empty param objects to be used across the app
Set Param = PDF.CreateParam
Set TextParam = PDF.CreateParam

' Create document
Set Doc = PDF.CreateDocument

' Create table with one row (header), and 5 columns
Set Table = Doc.CreateTable("width=500; height=20; Rows=1; Cols=5; Border=1; CellSpacing=-1; cellpadding=2 ")

' Set default table font
Table.Font = Doc.Fonts("Helvetica")
Set HeaderRow = Table.Rows(1)
With HeaderRow
  .BGColor = &H90F0FE
  .Cells(1).AddText "Category", Param
  .Cells(2).AddText "Description", Param
  .Cells(3).AddText "Billable", Param
  .Cells(4).AddText "Date", Param
  .Cells(5).AddText "Amount", Param
End With

' Set column widths
With Table.Rows(1)
  .Cells(1).Width = 80
  .Cells(2).Width = 160
  .Cells(3).Width = 50
  .Cells(4).Width = 70
  .Cells(5).Width = 60
End With

' Populate table with data
Set fso = CreateObject("Scripting.FileSystemObject")
Set f = fso.OpenTextFile(Server.MapPath("data.txt"))

param.Set "expand=true" ' expand cell vertically to accomodate text

Do Until f.AtEndOfStream
  fields = Split(f.ReadLine, ";")

  Set Row = Table.Rows.Add(20) ' row height

  param.Add "alignment=left"
  Row.Cells(1).AddText fields(1), param
  Row.Cells(2).AddText fields(2), param

  param.Add "alignment=center"
  If fields(3) = "1" Then Billable = "Yes" Else Billable = "No"
  Row.Cells(3).AddText Billable, param
  Row.Cells(4).AddText pdf.FormatDate( CDate(fields(4)), "%d %b %Y" ), param

  param.Add "alignment=right"
  Row.Cells(5).AddText "$" & pdf.FormatNumber(Mid(fields(5), 2), "precision=2, delimiter=true"), param

' Render table on document
Set Page = Doc.Pages.Add(612, 150) ' small pages to demonstrate paging functionality

Param("x") = (Page.Width - Table.Width) / 2 ' center table on page
Param("y") = Page.Height - 20
Param("MaxHeight") = 100

FirstRow = 2 ' use this to print record count on page
Do While True
  ' Draw table. This method returns last visible row index
  LastRow = Page.Canvas.DrawTable( Table, Param )

  ' Print record numbers
  TextParam("x") = (Page.Width - Table.Width) / 2
  TextParam("y") = Page.Height - 5
  TextStr = "Records " & FirstRow - 1 & " to " & LastRow - 1 & " of " & Table.Rows.Count - 1
  Page.Canvas.DrawText TextStr, TextParam, doc.fonts("Courier-Bold")

  if LastRow >= Table.Rows.Count Then Exit Do ' entire table displayed

  ' Display remaining part of table on the next page
  Set Page = Page.NextPage
  Param.Add( "RowTo=1; RowFrom=1" ) ' Row 1 is header - must always be present.
  Param("RowFrom1") = LastRow + 1 ' RowTo1 is omitted and presumed infinite

  FirstRow = LastRow + 1

' Save document, the Save method returns generated file name
Filename = Doc.Save( Server.MapPath("report.pdf"), False )
IPdfManager objPdf = new PdfManager();

// Create empty param objects to be used across the app
IPdfParam objParam = objPdf.CreateParam(Missing.Value);
IPdfParam objTextParam = objPdf.CreateParam(Missing.Value);

// Create empty document
IPdfDocument objDoc = objPdf.CreateDocument(Missing.Value);

// Create table with one row (header), and 5 columns
IPdfTable objTable = objDoc.CreateTable("width=500; height=20; Rows=1; Cols=5; Border=1; CellSpacing=-1; cellpadding=2 ");

// Set default table font
objTable.Font = objDoc.Fonts["Helvetica", Missing.Value];

IPdfRow objHeaderRow = objTable.Rows[1];
objHeaderRow.BgColor = 0x90F0FE;
objHeaderRow.Cells[1].AddText( "Category", objParam, Missing.Value );
objHeaderRow.Cells[2].AddText( "Description", objParam, Missing.Value );
objHeaderRow.Cells[3].AddText( "Billable", objParam, Missing.Value );
objHeaderRow.Cells[4].AddText( "Date", objParam, Missing.Value );
objHeaderRow.Cells[5].AddText( "Amount", objParam, Missing.Value );

// Set column widths
objHeaderRow.Cells[1].Width = 80;
objHeaderRow.Cells[2].Width = 160;
objHeaderRow.Cells[3].Width = 50;
objHeaderRow.Cells[4].Width = 70;
objHeaderRow.Cells[5].Width = 60;

objParam.Set( "expand=true" ); // expand cells vertically

String line;

// Populate table with data
using( FileStream fileStream = File.OpenRead(Server.MapPath("data.txt")))
  using(StreamReader reader = new StreamReader(fileStream) )
    while( ( line = reader.ReadLine() ) != null )
      string [] fields = line.Split(';');

      IPdfRow objRow = objTable.Rows.Add(20, Missing.Value); // row height

      objParam.Add( "alignment=left" );
      objRow.Cells[1].AddText( fields[1], objParam, Missing.Value );

      objRow.Cells[2].AddText( fields[2], objParam, Missing.Value );
      objParam.Add( "alignment=center" );

      String strBillable;
      if( fields[3] == "1" )
        strBillable = "Yes";
        strBillable = "No";

      DateTime dt = DateTime.Parse(fields[4]);
      objRow.Cells[3].AddText( strBillable, objParam, Missing.Value );
      objRow.Cells[4].AddText( objPdf.FormatDate( dt, "%d %b %Y" ), objParam, Missing.Value );

      float fAmount = float.Parse( fields[5].Substring(1) );
      objParam.Add( "alignment=right" );
      objRow.Cells[5].AddText( objPdf.FormatNumber( fAmount, "precision=2, delimiter=true"), objParam, Missing.Value );

// Render table on document, add pages as necessary
IPdfPage objPage = objDoc.Pages.Add(612, 150, Missing.Value);

objParam["x"].Value = (objPage.Width - objTable.Width) / 2; // center table on page
objParam["y"].Value = objPage.Height - 20;
objParam["MaxHeight"].Value = 100;

int nFirstRow = 2; // use this to print record count on page
while( true )
  // Draw table. This method returns last visible row index
  int nLastRow = objPage.Canvas.DrawTable( objTable, objParam );

  // Print record numbers
  objTextParam["x"].Value = (objPage.Width - objTable.Width) / 2;
  objTextParam["y"].Value = objPage.Height - 5;
  String strTextStr = "Records " + (nFirstRow - 1) + " to " + (nLastRow - 1) + " of " + (objTable.Rows.Count - 1 );

  objPage.Canvas.DrawText( strTextStr, objTextParam, objDoc.Fonts["Courier-Bold", Missing.Value] );

  if( nLastRow >= objTable.Rows.Count )
    break; // entire table displayed

  // Display remaining part of table on the next page
  objPage = objPage.NextPage;
  objParam.Add( "RowTo=1; RowFrom=1" ); // Row 1 is header - must always be present.
  objParam["RowFrom1"].Value = nLastRow + 1; // RowTo1 is omitted and presumed infinite

  nFirstRow = nLastRow + 1;

// Save document, the Save method returns generated file name
String strFilename = objDoc.Save( Server.MapPath("report.pdf"), false );

Click the links below to run this code sample: