Chapter 7: Tables

Contents

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 two-argument indexer by specifying 1-based row and cell indices as arguments, as follows:

PdfCell 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 PdfCanvas.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.

PdfManager objPdf = new PdfManager();

// Create empty document
PdfDocument objDoc = objPdf.CreateDocument();

// Create 8x8 table to depict a chessboard
PdfTable objTable =
  objDoc.CreateTable( "width=200;height=200;rows=8;cols=8;border=1;cellborder=0;cellspacing=2");

// Select a Chess font to depict chess pieces
PdfFont objFont = objDoc.Fonts.LoadFromFile( Server.MapPath("MERIFONT.TTF") );

objTable.Font = objFont;

// 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( PdfRow objRow in objTable.Rows )
{
  foreach( PdfCell 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" );
  }
}

// Add a new page
PdfPage objPage = objDoc.Pages.Add();

objPage.Canvas.DrawTable( objTable, "x=206, y=498" );
Dim objPdf As PdfManager = New PdfManager()

' Create empty document
Dim objDoc As PdfDocument = objPdf.CreateDocument()

' Create 8x8 table to depict a chessboard
Dim objTable As PdfTable = _
objDoc.CreateTable("width=200;height=200;rows=8;cols=8;border=1;cellborder=0;cellspacing=2")

' Select a Chess font to depict chess pieces
Dim objFont As PdfFont = objDoc.Fonts.LoadFromFile(Server.MapPath("MERIFONT.TTF"))

objTable.Font = objFont

' initialize Pieces array
Dim Pieces() As Integer = {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 objRow As PdfRow In objTable.Rows

  For Each objCell As PdfCell In objRow.Cells
    ' set background on cells which sum of indices is odd
    If (objCell.Index + objRow.Index) Mod 2 = 1 Then
      objCell.BgColor = "brown"
    End If

    Dim Piece As Integer = Pieces(8 * (objRow.Index - 1) + objCell.Index)

    objCell.AddText(ChrW(Piece), "size=20; indentx=1; indenty=1")
  Next
Next

' Add a new page
Dim objPage As PdfPage = objDoc.Pages.Add()

objPage.Canvas.DrawTable(objTable, "x=206, y=498")

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:

objTable[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.

PdfTable objSmallTable = objDoc.CreateTable("Height=30; Width=30; cols=2; rows=2");
objSmallTable[1, 1].Canvas.DrawText( "Hello", "x=1, y=15; size=5", objFont );
...

objTable[1, 2].Canvas.DrawTable( objSmallTable, "x=2; y=50" );

Note that unlike the PdfCell.AddText method, PdfCell.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 PdfRow.Height, PdfCell.Height and PdfCell.Width properties. Changing PdfRow.Height changes the heights of all the cells in that row, as well as the overall table height. Changing PdfCell.Height has the same effect as changing the height of the row this cell belongs to. Changing PdfCell.Width also changes the widths of cells above and below this cell, as well as the overall table width.

The method PdfCell.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 PdfCell.RowSpan and PdfCell.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:

PdfParam objParam = objPDF.CreateParam("rows=3, cols=4, width=400, height=150");
... (set other parameters)

PdfTable objTable = objDoc.CreateTable(objParam);

objTable.Rows[1].Cells[1].Width = 20;
objTable[1, 1].RowSpan = 3;
objTable[1, 3].RowSpan = 2;
objTable[1, 3].ColSpan = 2;
objTable.Rows[1].Cells[2].Width = 200;
objTable[3, 2].AddText( "Hello World!!!", "size=30; expand=true", objFont );

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 PdfRows.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 PdfCanvas.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.

PdfManager objPdf = new PdfManager();

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

// Create empty document
PdfDocument objDoc = objPdf.CreateDocument();

// Create table with one row (header), and 5 columns
PdfTable 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"];

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

// 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(';');

      PdfRow objRow = objTable.Rows.Add(20); // row height

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

      objParam.Add("alignment=center");

      String strBillable = fields[3] == "1" ? "Yes" : "No";

      DateTime dt = DateTime.Parse(fields[4]);
      objRow.Cells[3].AddText(strBillable, objParam);
      objRow.Cells[4].AddText(dt.ToString("MMM dd, yyyy"), objParam);

      float fAmount = float.Parse(fields[5].Substring(1));
      objParam.Add("alignment=right");
      objRow.Cells[5].AddText("$" + fAmount.ToString("0,0.00"), objParam);
    }
  }
}

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

objParam.Clear();
objParam["x"] = (objPage.Width - objTable.Width) / 2; // center table on page
objParam["y"] = objPage.Height - 20;
objParam["MaxHeight"] = 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"] = (objPage.Width - objTable.Width) / 2;
  objTextParam["y"] = objPage.Height - 5;
  objTextParam.Add("color=darkgreen");
  String strTextStr = "Records " + (nFirstRow - 1) + " to " + (nLastRow - 1) + " of " + (objTable.Rows.Count - 1);

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

  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"] = 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);
Dim objPdf As New PdfManager()

' Create empty param objects to be used across the app
Dim objParam As PdfParam = objPdf.CreateParam()
Dim objTextParam As PdfParam = objPdf.CreateParam()

' Create empty document
Dim objDoc As PdfDocument = objPdf.CreateDocument()

' Create table with one row (header), and 5 columns
Dim objTable As PdfTable = 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")

Dim objHeaderRow As PdfRow = objTable.Rows(1)
objParam.Set("alignment=center")
objHeaderRow.BgColor = &H90F0FE
objHeaderRow.Cells(1).AddText("Category", objParam)
objHeaderRow.Cells(2).AddText("Description", objParam)
objHeaderRow.Cells(3).AddText("Billable", objParam)
objHeaderRow.Cells(4).AddText("Date", objParam)
objHeaderRow.Cells(5).AddText("Amount", objParam)

' 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

Dim line As String

' Populate table with data
Using fileStream As FileStream = File.OpenRead(Server.MapPath("data.txt"))
  Using reader As StreamReader = New StreamReader(fileStream)
    Do
      line = reader.ReadLine()
      If line = Nothing Then Exit Do

      Dim fields() As String = line.Split(";")

      Dim objRow As PdfRow = objTable.Rows.Add(20) ' row height

      objParam.Add("alignment=left")
      objRow.Cells(1).AddText(fields(1), objParam)
      objRow.Cells(2).AddText(fields(2), objParam)

      objParam.Add("alignment=center")

      Dim strBillable As String = IIf(fields(3) = "1", "Yes", "No")

      Dim dt As DateTime = DateTime.Parse(fields(4))
      objRow.Cells(3).AddText(strBillable, objParam)
      objRow.Cells(4).AddText(dt.ToString("MMM dd, yyyy"), objParam)

      Dim fAmount As Single = Single.Parse(fields(5).Substring(1))
      objParam.Add("alignment=right")
      objRow.Cells(5).AddText("$" + fAmount.ToString("0,0.00"), objParam)
    Loop
  End Using
End Using

' Render table on document, add pages as necessary
Dim objPage As PdfPage = objDoc.Pages.Add(612, 150)

objParam.Clear()
objParam("x") = (objPage.Width - objTable.Width) / 2 ' center table on page
objParam("y") = objPage.Height - 20
objParam("MaxHeight") = 100

Dim nFirstRow As Integer = 2 ' use this to print record count on page
While True
  ' Draw table. This method returns last visible row index
  Dim nLastRow As Integer = objPage.Canvas.DrawTable(objTable, objParam)

  ' Print record numbers
  objTextParam("x") = (objPage.Width - objTable.Width) / 2
  objTextParam("y") = objPage.Height - 5
  objTextParam.Add("color=darkgreen")
  Dim strTextStr As String = "Records " + (nFirstRow - 1).ToString() + " to " + (nLastRow - 1).ToString() + " of " + (objTable.Rows.Count - 1).ToString()

  objPage.Canvas.DrawText(strTextStr, objTextParam, objDoc.Fonts("Courier-Bold"))

  If nLastRow >= objTable.Rows.Count Then Exit While ' 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") = nLastRow + 1 ' RowTo1 is omitted and presumed infinite

  nFirstRow = nLastRow + 1
End While

' Save document, the Save method returns generated file name
Dim strFilename As String = objDoc.Save(Server.MapPath("report.pdf"), False)

Click the links below to run this code sample: