libhpdftbl 1.5.0
Table construction library for Haru PDF library
Loading...
Searching...
No Matches
Tables layout from data

So far we have constructed the layout of table by issuing API calls per table to set up, for example, the column widths and what cells should merge with what other cells and so on. Previously we saw that data to be put in the table could be specified by either directly issuing API calls per cell, using a 2D array that we populate with data and then finally use callbacks to generate the data in the cells.

The final and most powerful way of constructing a table is to define the table structure as data. This structural data together with a style theme can completely define a table.

This will allow the dynamic construction of tables with only one API call instead of the multiple call required to construct a table the usual way. It can initially seem more complex but for advanced table this is indeed a much simpler and easy to maintain. In fact, this will allow a table to be (almost, we'll get back to the limitations) defined entirely in a database and makes it possible to adjust tha table as the data changes without ever updating the code (or recompile).

Defining a table in data

There are two data structure that are used when defining a table. First there is a data structure for the overall table specifics and then in that structure a structure to specify the layout of each cell. In addition, a theme needs to be defined (see section Themes). It is possible to omit the theme by specifying NULL in which case the default theme will be used.

To stroke a table from data the following API call is used

int
hpdftbl_stroke_from_data(HPDF_Doc pdf_doc, HPDF_Page pdf_page, hpdftbl_spec_t tbl_spec, hpdftbl_theme_t *theme);
int hpdftbl_stroke_from_data(HPDF_Doc pdf_doc, HPDF_Page pdf_page, hpdftbl_spec_t *tbl_spec, hpdftbl_theme_t *theme)
Construct the table from a array specification.
Definition: hpdftbl.c:1265
Used in data driven table creation.
Definition: hpdftbl.h:596
Define a set of styles into a table theme.
Definition: hpdftbl.h:635

In order to populate the table with suitable data callback functions are used (see section Using callbacks)

The overall table is first defined as an instance of

typedef struct hpdftbl_spec {
char *title;
_Bool use_header;
_Bool use_labels;
size_t rows;
size_t cols;
HPDF_REAL xpos;
HPDF_REAL ypos;
HPDF_REAL width;
HPDF_REAL height;
char *(* hpdftbl_content_callback_t)(void *, size_t, size_t)
Type specification for the table content callback.
Definition: hpdftbl.h:330
void(* hpdftbl_callback_t)(hpdftbl_t)
Callback type for optional post processing when constructing table from a data array.
Definition: hpdftbl.h:365
_Bool(* hpdftbl_content_style_callback_t)(void *, size_t, size_t, char *content, hpdf_text_style_t *)
Type specification for the content style.
Definition: hpdftbl.h:353
struct hpdftbl_spec hpdftbl_spec_t
Used in data driven table creation.
Used in data driven table creation.
Definition: hpdftbl.h:569
hpdftbl_content_callback_t content_cb
Definition: hpdftbl.h:618
_Bool use_labelgrid
Definition: hpdftbl.h:604
HPDF_REAL ypos
Definition: hpdftbl.h:612
hpdftbl_content_style_callback_t style_cb
Definition: hpdftbl.h:622
HPDF_REAL width
Definition: hpdftbl.h:614
HPDF_REAL height
Definition: hpdftbl.h:616
HPDF_REAL xpos
Definition: hpdftbl.h:610
size_t cols
Definition: hpdftbl.h:608
hpdftbl_cell_spec_t * cell_spec
Definition: hpdftbl.h:626
_Bool use_labels
Definition: hpdftbl.h:602
_Bool use_header
Definition: hpdftbl.h:600
size_t rows
Definition: hpdftbl.h:606
hpdftbl_callback_t post_cb
Definition: hpdftbl.h:624
char * title
Definition: hpdftbl.h:598
hpdftbl_content_callback_t label_cb
Definition: hpdftbl.h:620

Then each cell (referenced above in the cell_spec field) is defined as an instance of

typedef struct hpdftbl_cell_spec {
size_t row;
size_t col;
unsigned rowspan;
unsigned colspan;
char *label;
struct hpdftbl_cell_spec hpdftbl_cell_spec_t
Used in data driven table creation.
void(* hpdftbl_canvas_callback_t)(HPDF_Doc, HPDF_Page, void *, size_t, size_t, HPDF_REAL, HPDF_REAL, HPDF_REAL, HPDF_REAL)
Type specification for the table canvas callback.
Definition: hpdftbl.h:341
hpdftbl_content_callback_t content_cb
Definition: hpdftbl.h:581
hpdftbl_content_style_callback_t style_cb
Definition: hpdftbl.h:585
size_t col
Definition: hpdftbl.h:573
size_t row
Definition: hpdftbl.h:571
unsigned rowspan
Definition: hpdftbl.h:575
hpdftbl_canvas_callback_t canvas_cb
Definition: hpdftbl.h:587
char * label
Definition: hpdftbl.h:579
unsigned colspan
Definition: hpdftbl.h:577
hpdftbl_content_callback_t label_cb
Definition: hpdftbl.h:583

A first example of defining table as data

To understand how this is done lets start to define a basic 3x3 table with header row (so 4x3 in total) as data. First we create an instance of the table data

hpdftbl_spec_t tbl_spec = {
// Title and header flag
.title=NULL, .use_header=TRUE,
// Label and labelgrid flags
.use_labels=FALSE, .use_labelgrid=FALSE,
// Row and columns
.rows=4, .cols=3,
// Position of the table, xpos and ypos
// width and height
.width=hpdftbl_cm2dpi(15), .height=0,
// Content and label callback
.content_cb=cb_content, .label_cb=cb_label,
// Style and table post creation callback
.style_cb=NULL, .post_cb=NULL,
// Pointer to optional cell specifications
.cell_spec=NULL
};
#define TRUE
Boolean truth value.
Definition: hpdftbl.h:47
#define FALSE
Boolean false value.
Definition: hpdftbl.h:52
#define A4PAGE_HEIGHT_CM
Standard A4 paper height in cm.
Definition: hpdftbl.h:198
#define hpdftbl_cm2dpi(c)
Convert cm to dots using the default resolution (72 DPI)
Definition: hpdftbl.h:256
Note
In the table definition we use the C99 feature of specifying the field name when defining data in a structure.

Then the actual API call is trivial compared to the table creation code we have seen in the previous examples and consists of only one line of code

void
create_table_ex13_1(HPDF_Doc pdf_doc, HPDF_Page pdf_page) {
hpdftbl_stroke_from_data(pdf_doc, pdf_page, &tbl_spec, NULL);
}

The result is as expected and shown in Figure 13 but with much less code!


Figure 13: *Defining a table with a data structure tut_ex13_1.c*

A second example of defining a table as data

In the previous example we kept it simple didn't specify any format or content fór a table cell. Let us therefore create a slightly more complex example where we create a form which easily could be used to display data records from a DB.

The nice thing about separating layout and table structure from the data population in the callbacks is that this can almost be seen as a poor man's model-view-controller where the table structure is completely separate from the data (and how it is created).

A good way to start designing a table is to make a sketch on how it should look. Our goal is to crete the table structure as shown in the empty table in Figure 14 below


Figure 14: Sketch of table to be designed

To get this layout we use a basic table with :

  1. Five rows and four columns
  2. No header and no title
  3. We use labels and label grids

To make it easier to see how to construct the table we can overlay the sketch with a grid shown in blue in Figure 15. As can be seen this is a basic 5x4 table where a number of cells span multiple columns.


Figure 15: Sketch of table to be designed with 5x4 table overlaid

To start we set up the table specification as in the previous example with necessary changes. We will also need to specify cell specifications this time, and we assume those are available in an array of cell structures called cell_specs.

Before we specify the table structure we have one design decision to make. For the callbacks we can either use the table callback for all cells and check row and column to get the appropriate data, or we can add individual callbacks for each cell. The first case has the advantage to only need one callback function (but a lot of tests) and the second that each callback will be small and focused to get the data for that individual cell, but we will need potentially one callback for each cell unless there are commonalities between the cells so one callback can serve multiple cells. Remember that we still get the row and column as arguments in the callback so we weill always know exactly for which cell the callback was made.

To keep the size of this example we will use the table callback method for content and specify the label directly in the cell specification. With this decision made we get the following definition cell specifications

hpdftbl_cell_spec_t cell_specs[] = {
{.row=0, .col=0, .rowspan=1, .colspan=3,
.label="Name:",
.content_cb=NULL, .label_cb=NULL, .style_cb=NULL, .canvas_cb=NULL},
{.row=0, .col=3, .rowspan=1, .colspan=1,
.label="Date:",
.content_cb=NULL, .label_cb=NULL, .style_cb=NULL, .canvas_cb=NULL},
{.row=1, .col=0, .rowspan=1, .colspan=4,
.label="Address:",
.content_cb=NULL, .label_cb=NULL, .style_cb=NULL, .canvas_cb=NULL},
{.row=2, .col=0, .rowspan=1, .colspan=3,
.label="City:",
.content_cb=NULL, .label_cb=NULL, .style_cb=NULL, .canvas_cb=NULL},
{.row=2, .col=3, .rowspan=1, .colspan=1,
.label="Zip:",
.content_cb=NULL, .label_cb=NULL, .style_cb=NULL, .canvas_cb=NULL},
{.row=3, .col=0, .rowspan=1, .colspan=4,
.label="E-mail:",
.content_cb=NULL, .label_cb=NULL, .style_cb=NULL, .canvas_cb=NULL},
{.row=4, .col=0, .rowspan=1, .colspan=2,
.label="Work-phone:",
.content_cb=NULL, .label_cb=NULL, .style_cb=NULL, .canvas_cb=NULL},
{.row=4, .col=2, .rowspan=1, .colspan=2,
.label="Mobile:",
.content_cb=NULL, .label_cb=NULL, .style_cb=NULL, .canvas_cb=NULL},
HPDFTBL_END_CELLSPECS // Sentinel to mark the end of
};
#define HPDFTBL_END_CELLSPECS
Sentinel to mark the end of Cell Specifications for data driven table definition.
Definition: hpdftbl.h:238

As can be seen we need to have an end of cell specification sentinel since we could decide to provide details for one or more cells and there is no way for the library to know how many fields to read otherwise. There is even a convenience constant in the library PDFTBL_END_CELLSPECS that can be used as the last record.

The overall table specification is pretty much as before but with the added cell specifications.

hpdftbl_spec_t tbl_spec = {
// Title and header flag
.title=NULL, .use_header=FALSE,
// Label and labelgrid flags
.use_labels=TRUE, .use_labelgrid=TRUE,
// Row and columns
.rows=5, .cols=4,
// xpos and ypos
// width and height
.width=hpdftbl_cm2dpi(15), .height=0,
// Content and label callback
.content_cb=cb_content, .label_cb=cb_label,
// Style and table post creation callback
.style_cb=NULL, .post_cb=NULL,
// Pointer to optional cell specifications
.cell_spec=cell_specs
};

When this is run (see tut_ex13_2.c) it generates the following image, Figure 16


Figure 16: Specifying a table as data with cell specifications.

What remains is to write the proper table content callback that will populate the table. In a real life scenario his data will most likely come from a database but adding that in our example would bring too far. Instead, we will just use some fake static dummy data to illustrate the principle.

Since we have one callback for all cells we need to test from which cell the call come from. Here is a very important point to make. The row and column number will be the row and cell columns in the original table before any column or row spans was applied. In this example it means that for example the "Date" field (upper right) will have row=0 and col=3 and not (0,1)!!.

With this information we can write the following (dummy) table callback

static char *
cb_content(void *tag, size_t r, size_t c) {
static char *cell_content[] =
{"Mark Ericsen",
"12 Sep 2021",
"123 Downer Mews",
"London",
"NW2 HB3",
"mark.p.ericsen@myfinemail.com",
"+44734 354 184 56",
"+44771 938 137 11"};
if( 0==r && 0==c) return cell_content[0];
else if (0==r && 3==c) return cell_content[1];
else if (1==r && 0==c) return cell_content[2];
else if (2==r && 0==c) return cell_content[3];
else if (2==r && 3==c) return cell_content[4];
else if (3==r && 0==c) return cell_content[5];
else if (4==r && 0==c) return cell_content[6];
else if (4==r && 2==c) return cell_content[7];
else return NULL;
}

and we get the (expected) result as shown in Figure 17 below.


Figure 17: Specifying a table as data with cell specifications and "dummy" data.

The alternative of specifying individual callback for each cell would then require that each cell have a callback provided or perhaps even a mix with both a general table callback and selected cell callbacks.

The priority is such that a cell callback will always override a table callback. In the above example the callback for the name field could as an example be

static char *
cb_content_name(void *tag, size_t r, size_t c) {
static char *cell_content = "Mark Ericsen";
return cell_content;
}