libhpdftbl 1.5.0
Table construction library for Haru PDF library
Loading...
Searching...
No Matches
Error handling

All library function will return an error code < 0 and also set a global variable to a specific error code that can later be read by an error handler. In order to translate the error to a human-readable string the function hpdftbl_get_last_errcode() can be used as the following error handling snippet exemplified by a call to hpdftbl_set_colwidth_percent()

if( hpdftbl_set_colwidth_percent(tbl, 5, 110) ) {
// This is an error
char *err_str;
int err_code, r, c;
err_code=hpdftbl_get_last_errcode(&err_str, &r, &c);
if( err_code ) {
printf("*ERROR*: \"%s\" at cell (%d, %d)",err_str,r,c);
exit(1);
}
}
int hpdftbl_set_colwidth_percent(hpdftbl_t t, size_t c, float w)
Set column width as percentage of overall table width.
Definition: hpdftbl.c:435
int hpdftbl_get_last_errcode(const char **errstr, int *row, int *col)
Return last error code.
Definition: hpdftbl_errstr.c:316

As can be seen from the snippet above it would yield quite long winding error handling if one where to check every single library call. Instead, there is the option of installing an error handler that would be called in the event of an error.

The table error handle has the signature

void hpdftbl_error_handler_t)(hpdftbl_t tbl, int r, int c, int err)
void(* hpdftbl_error_handler_t)(hpdftbl_t, int, int, int)
TYpe for error handler function.
Definition: hpdftbl.h:677
Core table handle.
Definition: hpdftbl.h:470

Where the arguments are

Argument Description
tbl The table in where the error happened.
Note: This might be NULL since not all errors happen within the context of a table
r, c The row and column if the error happens in a specified cell, otherwise these will be (-1,-1)
err The internal error code. This si always a negative number.

The error handler is set with the hpdftbl_set_errhandler() method. An example of a very simple error handle is:

void
my_table_error_handler(hpdftbl_t t, int r, int c, int err) {
if( r>-1 && c>-1 ) {
fprintf(stderr, "*** Table Error: [%d] \"%s\" at cell (%d, %d)\n", err, hpdftbl_get_errstr(err), r, c);
} else {
fprintf(stderr, "*** Table Error: [%d] \"%s\" \n", err, hpdftbl_get_errstr(err));
}
exit(EXIT_FAILURE);
}
const char * hpdftbl_get_errstr(int err)
Translate a table error code to a human readable string.
Definition: hpdftbl_errstr.c:256

In the above error handler we have made use of the utility function hpdftbl_get_errstr() that translates the internal error code to a human-readable string.

In fact this exact error handler is available as a convenience in the library under the name hpdftbl_default_table_error_handler so to use this trivial error handler just add the following line to your code

hpdftbl_error_handler_t hpdftbl_set_errhandler(hpdftbl_error_handler_t)
Specify errhandler for the table routines.
Definition: hpdftbl_errstr.c:361
void hpdftbl_default_table_error_handler(hpdftbl_t t, int r, int c, int err)
A basic default table error handler.
Definition: hpdftbl_errstr.c:287

More advanced error handler must be written for the particular application they are to be used in.

Using emulated exception handling

As can be ssen above the default error handler terminates the running process with a call to exit(EXIT_FAILURE). Terminating te process might not always be approriate (especially for a daemon process). An alternative way to handle a fault state is to use setjmp()/longjmp() to simulate an exception handling.

In the program setup code a jump point is established and then if an error is detected the error handler will jump to the set jump point.

For example, all tutorial examples share the same main() function as shown below

main(int argc, char **argv) {
HPDF_Doc pdf_doc;
HPDF_Page pdf_page;
run_as_unit_test = 2==argc ;
if (setjmp(_hpdftbl_jmp_env)) {
return EXIT_FAILURE;
}
hpdftbl_set_errhandler(table_error_handler);
setup_hpdf(&pdf_doc, &pdf_page, _showgrid_);
_tbl_(pdf_doc, pdf_page);
if( -1 == stroke_to_file(pdf_doc, argc, argv) )
return EXIT_FAILURE;
else
return EXIT_SUCCESS;
}
int stroke_to_file(HPDF_Doc pdf_doc, int argc, char **argv)
Stroke the created PDF page to a file.
Definition: unit_test.inc.h:210
jmp_buf _hpdftbl_jmp_env
For simulated exception handling.
Definition: unit_test.inc.h:45
_Bool run_as_unit_test
For the case when we use this example as a unit/integration test we do not want data such as dates,...
Definition: unit_test.inc.h:35
void setup_hpdf(HPDF_Doc *pdf_doc, HPDF_Page *pdf_page, _Bool addgrid)
Create a new PDF document with one page in A4 format.
Definition: unit_test.inc.h:151

The relevant part here is the setjmp(_hpdftbl_jmp_env) code which establish a jump destination. An basic error handler that uses this could now look like this:

static void
table_error_handler(hpdftbl_t t, int r, int c, int err) {
if (r > -1 && c > -1) {
fprintf(stderr, "*** Table Error: [%d] \"%s\" at cell (%d, %d)\n", err, hpdftbl_get_errstr(err), r, c);
} else {
fprintf(stderr, "*** Table Error: [%d] \"%s\" \n", err, hpdftbl_get_errstr(err));
}
longjmp(_hpdftbl_jmp_env, 1);
}

If an error occur the longjmp() will come to the setjmp() point and since it returns the value of 1 it will enter the if-statement free the doc structure and then terminate the process by exiting the main() function.

In a more complex program it might be useful to instead of exiting the process give the user an error message, do any cleanup (such as freeing the PDF document) and try again if this perhaps was a recoverable error.

The actual error handler used in the tutorial examples is slightly longer as it prints all available information from the error handling "subsystem" such as which file and line number (in the library) where the error was triggered and any optional extra information was given in regard to the error mode. In addition, a stacktrace is also generated to stderr

The real (production grade) error handler therefore looks as shown below

table_error_handler(hpdftbl_t t, int r, int c, int err) {
int lineno;
char *filename;
char *extrainfo;
hpdftbl_get_last_err_file(&lineno, &filename, &extrainfo);
if (r > -1 && c > -1) {
fprintf(stderr, "*** Table Error: [%d] \"%s\" at cell (%d, %d)", err,
hpdftbl_get_errstr(err), r, c);
} else {
fprintf(stderr, "*** Table Error: [%d] \"%s\"", err,
}
if( filename != NULL ) {
fprintf(stderr," in %s:%d",filename, lineno);
}
if( extrainfo != NULL ) {
fprintf(stderr,". Info: \"%s\"\n",extrainfo);
}
else {
fprintf(stderr,"\n");
}
// Also print the available stacktrace
void* callstack[128];
int i, frames = backtrace(callstack, 128);
char** callstack_sym = backtrace_symbols(callstack, frames);
if( callstack_sym != NULL ) {
fprintf(stderr, "Stacktrace:\n");
for (i = 0; i < frames; ++i) {
fprintf(stderr, "%s\n", callstack_sym[i]);
}
free(callstack_sym);
}
longjmp(_hpdftbl_jmp_env, 1);
void hpdftbl_get_last_err_file(int *lineno, char **file, char **extrainfo)
Get the filename and line number where the last error occurred.
Definition: hpdftbl_errstr.c:345
}

 

Note
A common way to extend the error handling is to log the errors to syslog. When the library is used on OSX from 11.0 and onwards it should be remembered that OSX is broken by design as far as syslog logging is concerned. Apple in its wisdom introduced "Unified logging" which breaks the syslog() function and no logging is ever produced in the filesystem directly (i.e. to /var/log/system.log).
Instead, the only way to view the logs is by using the utility log. So in order to view the log from a particular application the following command has to be given
log stream --info --debug --predicate 'sender == "APPLICATION_NAME" --style syslog

Additional information

When an error is triggered the file name and line number in the library where the error was triggered is saved as well as an optional information string that some error states might set.

All this extra information can be retrieved by the library function hpdftbl_get_last_err_file()

Note
The file name and line number displayed is always the point in the library that discovered the error state. It does not indicate the file name and line number of the client code that triggered the error as the error is discovered in the library routines.

Translating HPDF error codes

The standard error handler for the plain HPDF library is specified when a new document is created, for example as'

...
pdf_doc = HPDF_New(error_handler, NULL);
HPDF_SetCompressionMode(pdf_doc, HPDF_COMP_ALL);
...

The error handler signature is defined by Haru PDF library as

static void error_handler(HPDF_STATUS error_no, HPDF_STATUS detail_no, void *user_data);

It is then up to the application code to decide how to handle the error. To simplify the handling of core HPDF error the library also offer a convenience function to translate the Haru library error code into a human-readable string. This function is

const char *
hpdftbl_hpdf_get_errstr(const HPDF_STATUS err_code)
const char * hpdftbl_hpdf_get_errstr(HPDF_STATUS err_code)
Function to return a human readable error string for an error code from Core HPDF library.
Definition: hpdftbl_errstr.c:232

and is used in the error handler in all the examples.

Example of setting up error handler

The following table creation code have a deliberate error in that it tries to assign a total column width of more than 100% which of course isn't possible.

void
create_table_ex10(HPDF_Doc pdf_doc, HPDF_Page pdf_page) {
const size_t num_rows = 4;
const size_t num_cols = 4;
hpdftbl_t tbl = hpdftbl_create(num_rows, num_cols);
content_t content;
setup_dummy_content(&content, num_rows, num_cols);
hpdftbl_set_content(tbl, content);
HPDF_REAL xpos = hpdftbl_cm2dpi(1);
HPDF_REAL ypos = hpdftbl_cm2dpi(A4PAGE_HEIGHT_CM - 1);
HPDF_REAL width = hpdftbl_cm2dpi(A4PAGE_WIDTH_CM - 4);
HPDF_REAL height = 0; // Calculate height automatically
hpdftbl_stroke(pdf_doc, pdf_page, tbl, xpos, ypos, width, height);
}
int hpdftbl_set_content(hpdftbl_t t, char **content)
Set the content for the table.
Definition: hpdftbl.c:1048
hpdftbl_t hpdftbl_create(size_t rows, size_t cols)
Create a new table with no title.
Definition: hpdftbl.c:311
int hpdftbl_stroke(HPDF_Doc pdf, const HPDF_Page page, hpdftbl_t t, const HPDF_REAL xpos, const HPDF_REAL ypos, const HPDF_REAL width, HPDF_REAL height)
Stroke the table.
Definition: hpdftbl.c:1682
#define A4PAGE_WIDTH_CM
Standard A4 paper width in cm.
Definition: hpdftbl.h:203
#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
void setup_dummy_content(content_t *content, size_t rows, size_t cols)
Create an array of char pointers to simulate real table data.
Definition: unit_test.inc.h:238
char ** content_t
An array of char pointers.
Definition: unit_test.inc.h:230

This is available in the example directory as tut_ex10.c.

If we simulate a "typo" and add a deliberate error by making the column widths larger than 100% by writing

When this code is then executed the following will be printed to standard error and the process will be stopped.

*** Table Error: [-12] "Total column width exceeds 100%"