7 use List::Util qw(sum);
 
   8 our $VERSION = '0.9.10';
 
  10 print __PACKAGE__.' is version: '.$VERSION.$/ if($ENV{'PDF_TABLE_DEBUG'});
 
  12 ############################################################
 
  16 # Parameters are meta information about the PDF
 
  18 # $pdf = PDF::Table->new();
 
  20 ############################################################
 
  25     my $class = ref($type) || $type;
 
  27     bless ($self, $class);
 
  29     # Pass all the rest to init for validation and initialisation
 
  37     my ($self, $pdf, $page, $data, %options ) = @_;
 
  39     # Check and set default values 
 
  40     $self->set_defaults();
 
  42     # Check and set mandatory params
 
  44     $self->set_page($page);
 
  45     $self->set_data($data);
 
  46     $self->set_options(\%options);
 
  54         $self->{'font_size'} = 12;
 
  58     my ($self, $pdf) = @_;
 
  59     $self->{'pdf'} = $pdf;
 
  63     my ($self, $page) = @_;
 
  64     if ( defined($page) && ref($page) ne 'PDF::API2::Page' ){
 
  66         if( ref($self->{'pdf'}) eq 'PDF::API2' ){
 
  67             $self->{'page'} = $self->{'pdf'}->page();
 
  69             carp 'Warning: Page must be a PDF::API2::Page object but it seems to be: '.ref($page).$/;
 
  70             carp 'Error: Cannot set page from passed PDF object either as it is invalid!'.$/;
 
  74     $self->{'page'} = $page;
 
  79     my ($self, $data) = @_;
 
  84     my ($self, $options) = @_;
 
  88 ############################################################
 
  90 # text_block - utility method to build multi-paragraph blocks of text
 
  92 ############################################################
 
  97     my $text_object = shift;
 
  98     my $text        = shift;    # The text to be displayed
 
  99     my %arg         = @_;       # Additional Arguments
 
 101     my  ( $align, $xpos, $ypos, $xbase, $ybase, $line_width, $wordspace, $endw , $width, $height) = 
 
 102         ( undef , undef, undef, undef , undef , undef      , undef     , undef , undef , undef  );
 
 103     my @line        = ();       # Temp data array with words on one line 
 
 104     my %width       = ();       # The width of every unique word in the givven text
 
 106     # Try to provide backward compatibility
 
 107     foreach my $key (keys %arg)
 
 110         if($newkey =~ s#^-##)
 
 112             $arg{$newkey} = $arg{$key};
 
 119     # Lets check mandatory parameters with no default values
 
 121     $xbase  = $arg{'x'} || -1;
 
 122     $ybase  = $arg{'y'} || -1;
 
 123     $width  = $arg{'w'} || -1;
 
 124     $height = $arg{'h'} || -1;
 
 125     unless( $xbase  > 0 ){ carp "Error: Left Edge of Block is NOT defined!\n";  return; }
 
 126     unless( $ybase  > 0 ){ carp "Error: Base Line of Block is NOT defined!\n"; return; }
 
 127     unless( $width  > 0 ){ carp "Error: Width of Block is NOT defined!\n";  return; }
 
 128     unless( $height > 0 ){ carp "Error: Height of Block is NOT defined!\n"; return; }
 
 129     # Check if any text to display
 
 130     unless( defined( $text) and length($text) > 0 )
 
 132 #         carp "Warning: No input text found. Trying to add dummy '-' and not to break everything.\n";
 
 136     # Strip any <CR> and Split the text into paragraphs
 
 138     my @paragraphs  = split(/\n/, $text);
 
 140     # Width between lines in pixels
 
 141     my $line_space = defined $arg{'lead'} && $arg{'lead'} > 0 ? $arg{'lead'} : 12;
 
 143     # Calculate width of all words
 
 144     my $space_width = $text_object->advancewidth("\x20");
 
 145     my @words = split(/\s+/, $text);
 
 148         next if exists $width{$_};
 
 149         $width{$_} = $text_object->advancewidth($_);
 
 152     my @paragraph = split(' ', shift(@paragraphs));
 
 154     my $first_paragraph = 1;
 
 159     $ypos = $ybase + $line_space;
 
 160     my $bottom_border = $ypos - $height; 
 
 161     # While we can add another line
 
 162     while ( $ypos >= $bottom_border + $line_space ) 
 
 164         # Is there any text to render ?
 
 167             # Finish if nothing left
 
 168             last unless scalar @paragraphs;
 
 169             # Else take one line from the text
 
 170             @paragraph = split(' ', shift( @paragraphs ) );
 
 172             $ypos -= $arg{'parspace'} if $arg{'parspace'};
 
 173             last unless $ypos >= $bottom_border;
 
 175         $ypos -= $line_space;
 
 178         # While there's room on the line, add another word
 
 181         if( $first_line && exists $arg{'hang'} ) 
 
 183             my $hang_width = $text_object->advancewidth($arg{'hang'});
 
 185             $text_object->translate( $xpos, $ypos );
 
 186             $text_object->text( $arg{'hang'} );
 
 188             $xpos         += $hang_width;
 
 189             $line_width   += $hang_width;
 
 190             $arg{'indent'} += $hang_width if $first_paragraph;
 
 192         elsif( $first_line && exists $arg{'flindent'} && $arg{'flindent'} > 0 ) 
 
 194             $xpos += $arg{'flindent'};
 
 195             $line_width += $arg{'flindent'};
 
 197         elsif( $first_paragraph && exists $arg{'fpindent'} && $arg{'fpindent'} > 0 ) 
 
 199             $xpos += $arg{'fpindent'};
 
 200             $line_width += $arg{'fpindent'};
 
 202         elsif (exists $arg{'indent'} && $arg{'indent'} > 0 ) 
 
 204             $xpos += $arg{'indent'};
 
 205             $line_width += $arg{'indent'};
 
 208         # Lets take from paragraph as many words as we can put into $width - $indent; 
 
 209         # Always take at least one word; otherwise we'd end up in an infinite loop.
 
 210         while ( !scalar(@line) || (
 
 212             $text_object->advancewidth( join("\x20", @line)."\x20" . $paragraph[0]) + $line_width < $width
 
 216             push(@line, shift(@paragraph));
 
 218         $line_width += $text_object->advancewidth(join('', @line));
 
 220         # calculate the space width
 
 221         if( $arg{'align'} eq 'fulljustify' or ($arg{'align'} eq 'justify' and @paragraph)) 
 
 223             @line = split(//,$line[0]) if (scalar(@line) == 1) ;
 
 224             $wordspace = ($width - $line_width) / (scalar(@line) - 1);
 
 229             $align=($arg{'align'} eq 'justify') ? 'left' : $arg{'align'};
 
 230             $wordspace = $space_width;
 
 232         $line_width += $wordspace * (scalar(@line) - 1);
 
 234         if( $align eq 'justify') 
 
 236             foreach my $word (@line) 
 
 238                 $text_object->translate( $xpos, $ypos );
 
 239                 $text_object->text( $word );
 
 240                 $xpos += ($width{$word} + $wordspace) if (@line);
 
 246             # calculate the left hand position of the line
 
 247             if( $align eq 'right' ) 
 
 249                 $xpos += $width - $line_width;
 
 251             elsif( $align eq 'center' ) 
 
 253                 $xpos += ( $width / 2 ) - ( $line_width / 2 );
 
 257             $text_object->translate( $xpos, $ypos );
 
 258             $endw = $text_object->text( join("\x20", @line));
 
 262     unshift(@paragraphs, join(' ',@paragraph)) if scalar(@paragraph);
 
 263     return ($endw, $ypos, join("\n", @paragraphs))
 
 267 ################################################################
 
 268 # table - utility method to build multi-row, multicolumn tables
 
 269 ################################################################
 
 278     #=====================================
 
 279     # Mandatory Arguments Section
 
 280     #=====================================
 
 281     unless($pdf and $page and $data)
 
 283         carp "Error: Mandatory parameter is missing pdf/page/data object!\n";
 
 287     # Validate mandatory argument data type
 
 288     croak "Error: Invalid pdf object received."  unless (ref($pdf) eq 'PDF::API2');
 
 289     croak "Error: Invalid page object received." unless (ref($page) eq 'PDF::API2::Page');
 
 290     croak "Error: Invalid data received."        unless ((ref($data) eq 'ARRAY') && scalar(@$data));
 
 291     croak "Error: Missing required settings."    unless (scalar(keys %arg));
 
 293     # Validate settings key
 
 294     my %valid_settings_key = (
 
 307         background_color      => 1,
 
 308         background_color_odd  => 1,
 
 309         background_color_even => 1,
 
 312         horizontal_borders    => 1,
 
 313         vertical_borders      => 1,
 
 317         font_color_even       => 1,
 
 318         background_color_odd  => 1,
 
 319         background_color_even => 1,
 
 325         max_word_length       => 1,
 
 326         num_header_rows       => 1,
 
 328     foreach my $key (keys %arg) {
 
 329         croak "Error: Invalid setting key '$key' received." 
 
 330             unless (exists $valid_settings_key{$key});
 
 333     # Try to provide backward compatibility
 
 334     foreach my $key (keys %arg)
 
 337         if($newkey =~ s#^-##)
 
 339             $arg{$newkey} = $arg{$key};
 
 345     #TODO: Add code for header props compatibility and col_props comp....
 
 347     my ( $xbase, $ybase, $width, $height ) = ( undef, undef, undef, undef );
 
 348     # Could be 'int' or 'real' values
 
 349     $xbase  = $arg{'x'      } || -1;    
 
 350     $ybase  = $arg{'start_y'} || -1;
 
 351     $width  = $arg{'w'      } || -1;
 
 352     $height = $arg{'start_h'} || -1;
 
 354     # Global geometry parameters are also mandatory. 
 
 355     unless( $xbase  > 0 ){ carp "Error: Left Edge of Table is NOT defined!\n";  return; }
 
 356     unless( $ybase  > 0 ){ carp "Error: Base Line of Table is NOT defined!\n"; return; }
 
 357     unless( $width  > 0 ){ carp "Error: Width of Table is NOT defined!\n";  return; }
 
 358     unless( $height > 0 ){ carp "Error: Height of Table is NOT defined!\n"; return; }
 
 360     # Ensure default values for -next_y and -next_h
 
 361     my $next_y  = $arg{'next_y'} || $arg{'start_y'} || 0;
 
 362     my $next_h  = $arg{'next_h'} || $arg{'start_h'} || 0;
 
 365     my $txt     = $page->text;
 
 367     # Set Default Properties
 
 368     my $fnt_name    = $arg{'font'            } || $pdf->corefont('Times',-encode => 'utf8');
 
 369     my $fnt_size    = $arg{'font_size'       } || 12;
 
 370     my $max_word_len= $arg{'max_word_length' } || 20;
 
 372     #=====================================
 
 373     # Table Header Section
 
 374     #=====================================
 
 375     # Disable header row into the table
 
 376     my $header_props = undef;
 
 377     my (@header_rows, @header_row_cell_props);
 
 378     # Check if the user enabled it ?
 
 379     if(defined $arg{'header_props'} and ref( $arg{'header_props'}) eq 'HASH')
 
 381         # Transfer the reference to local variable
 
 382         $header_props = $arg{'header_props'};
 
 384         # Check other params and put defaults if needed
 
 385         $header_props->{'repeat'        } = $header_props->{'repeat'        } || 0;
 
 386         $header_props->{'font'          } = $header_props->{'font'          } || $fnt_name;
 
 387         $header_props->{'font_color'    } = $header_props->{'font_color'    } || '#000066';
 
 388         $header_props->{'font_size'     } = $header_props->{'font_size'     } || $fnt_size + 2;
 
 389         $header_props->{'bg_color'      } = $header_props->{'bg_color'      } || '#FFFFAA';
 
 390         $header_props->{'justify'       } = $header_props->{'justify'       };
 
 391         $header_props->{num_header_rows } = $arg{num_header_rows } || 1;
 
 393     #=====================================
 
 394     # Other Parameters check
 
 395     #=====================================
 
 396     my $lead          = $arg{'lead'          } || $fnt_size;
 
 397     my $pad_left      = $arg{'padding_left'  } || $arg{'padding'} || 0;
 
 398     my $pad_right     = $arg{'padding_right' } || $arg{'padding'} || 0;
 
 399     my $pad_top       = $arg{'padding_top'   } || $arg{'padding'} || 0;
 
 400     my $pad_bot       = $arg{'padding_bottom'} || $arg{'padding'} || 0;
 
 401     my $line_w        = defined $arg{'border'} ? $arg{'border'} : 1 ;
 
 402     my $horiz_borders = defined $arg{'horizontal_borders'}
 
 403         ? $arg{'horizontal_borders'}
 
 405     my $vert_borders  = defined $arg{'vertical_borders'}
 
 406         ? $arg{'vertical_borders'}
 
 409     my $background_color_even   = $arg{'background_color_even'  } || $arg{'background_color'} || undef;
 
 410     my $background_color_odd    = $arg{'background_color_odd'   } || $arg{'background_color'} || undef;
 
 411     my $font_color_even         = $arg{'font_color_even'        } || $arg{'font_color'      } || 'black';
 
 412     my $font_color_odd          = $arg{'font_color_odd'         } || $arg{'font_color'      } || 'black';
 
 413     my $border_color            = $arg{'border_color'           } || 'black';
 
 415     my $min_row_h   = $fnt_size + $pad_top + $pad_bot;
 
 416     my $row_h       = defined ($arg{'row_height'}) 
 
 418                     ($arg{'row_height'} > $min_row_h) 
 
 420                      $arg{'row_height'} : $min_row_h;
 
 424     my $cell_props  = $arg{cell_props} || [];   # per cell properties
 
 426     #If there is no valid data array reference warn and return!
 
 427     if(ref $data ne 'ARRAY')
 
 429         carp "Passed table data is not an ARRAY reference. It's actually a ref to ".ref($data);
 
 430         return ($page,0,$cur_y);
 
 433     # Copy the header row if header is enabled
 
 434     if (defined $header_props) {
 
 435       map { push @header_rows,           $$data[$_]       } (0..$header_props->{num_header_rows} - 1);
 
 436       map { push @header_row_cell_props, $$cell_props[$_] } (0..$header_props->{num_header_rows} - 1);
 
 438     # Determine column widths based on content
 
 440     #  an arrayref whose values are a hashref holding 
 
 441     #  the minimum and maximum width of that column
 
 442     my $col_props =  $arg{'column_props'} || [];
 
 444     # An array ref of arrayrefs whose values are 
 
 445     #  the actual widths of the column/row intersection
 
 446     my $row_col_widths = [];
 
 447     # An array ref with the widths of the header row 
 
 448     my @header_row_widths;
 
 450     # Scalars that hold sum of the maximum and minimum widths of all columns 
 
 451     my ( $max_col_w  , $min_col_w   ) = ( 0,0 );
 
 452     my ( $row, $col_name, $col_fnt_size, $space_w );
 
 454     my $word_widths  = {};
 
 455     my $rows_height  = [];
 
 457     for( my $row_idx = 0; $row_idx < scalar(@$data) ; $row_idx++ )
 
 459         #push @header_row_widths, [] if $row_idx < $header_props->{num_header_rows};
 
 461         my $column_widths = []; #holds the width of each column
 
 462         # Init the height for this row
 
 463         $rows_height->[$row_idx] = 0;
 
 465         for( my $column_idx = 0; $column_idx < scalar(@{$data->[$row_idx]}) ; $column_idx++ )
 
 467             # look for font information for this column
 
 468             my ($cell_font, $cell_font_size);
 
 470             if( !$row_idx and ref $header_props )
 
 472                 $cell_font      = $header_props->{'font'};
 
 473                 $cell_font_size = $header_props->{'font_size'};
 
 476             # Get the most specific value if none was already set from header_props
 
 477             $cell_font      ||= $cell_props->[$row_idx][$column_idx]->{'font'} 
 
 478                             ||  $col_props->[$column_idx]->{'font'}
 
 481             $cell_font_size ||= $cell_props->[$row_idx][$column_idx]->{'font_size'}
 
 482                             ||  $col_props->[$column_idx]->{'font_size'}
 
 486             $txt->font( $cell_font, $cell_font_size ); 
 
 488             # Set row height to biggest font size from row's cells
 
 489             if( $cell_font_size  > $rows_height->[$row_idx] )
 
 491                 $rows_height->[$row_idx] = $cell_font_size;
 
 494             if (!defined $data->[$row_idx][$column_idx]) {
 
 495               $data->[$row_idx][$column_idx] = ' ';
 
 498             # This should fix a bug with very long words like serial numbers etc.
 
 499             if( $max_word_len > 0 && $data->[$row_idx][$column_idx])
 
 501                 $data->[$row_idx][$column_idx] =~ s#(\S{$max_word_len})(?=\S)#$1 #g;
 
 504             # Init cell size limits
 
 505             $space_w                      = $txt->advancewidth( "\x20" );
 
 506             $column_widths->[$column_idx] = 0;
 
 510             my @words = split( /\s+/, $data->[$row_idx][$column_idx] );
 
 514                 unless( exists $word_widths->{$_} )
 
 515                 {   # Calculate the width of every word and add the space width to it
 
 516                     $word_widths->{$_} = $txt->advancewidth( $_ ) + $space_w;
 
 519                 $column_widths->[$column_idx] += $word_widths->{$_};
 
 520                 $min_col_w                     = $word_widths->{$_} if( $word_widths->{$_} > $min_col_w );
 
 521                 $max_col_w                    += $word_widths->{$_};
 
 524             $min_col_w                    += $pad_left + $pad_right;
 
 525             $max_col_w                    += $pad_left + $pad_right;
 
 526             $column_widths->[$column_idx] += $pad_left + $pad_right;
 
 528             # Keep a running total of the overall min and max widths
 
 529             $col_props->[$column_idx]->{'min_w'} ||= 0;
 
 530             $col_props->[$column_idx]->{'max_w'} ||= 0;
 
 532             if( $min_col_w > $col_props->[$column_idx]->{'min_w'} )
 
 533             {   # Calculated Minimum Column Width is more than user-defined
 
 534                 $col_props->[$column_idx]->{'min_w'} = $min_col_w ;
 
 537             if( $max_col_w > $col_props->[$column_idx]->{'max_w'} )
 
 538             {   # Calculated Maximum Column Width is more than user-defined
 
 539                 $col_props->[$column_idx]->{'max_w'} = $max_col_w ;
 
 541         }#End of for(my $column_idx....
 
 543         $row_col_widths->[$row_idx] = $column_widths;
 
 545         # Copy the calculated row properties of header row. 
 
 546         if (ref $header_props && $row_idx < $header_props->{num_header_rows}) {
 
 547           push @header_row_widths, [ @{ $column_widths } ];
 
 551     # Calc real column widths and expand table width if needed.
 
 552     my $calc_column_widths; 
 
 553     ($calc_column_widths, $width) = CalcColumnWidths( $col_props, $width );
 
 554     my $num_cols = scalar @{ $calc_column_widths };
 
 556     # Lets draw what we have!
 
 558     # Store header row height for later use if headers have to be repeated
 
 559     my @header_row_heights = @$rows_height[0 .. $header_props->{num_header_rows}-1];
 
 561     my ( $gfx, $gfx_bg, $background_color, $font_color, $bot_marg, $table_top_y, $text_start);
 
 563     my $remaining_header_rows = $header_props ? $header_props->{num_header_rows} : 0;
 
 565     # Each iteration adds a new page as neccessary
 
 566     while(scalar(@{$data}))
 
 568         my ($page_header, $columns_number);
 
 572             $table_top_y = $ybase;
 
 573             $bot_marg = $table_top_y - $height;
 
 577             if(ref $arg{'new_page_func'})
 
 579                 $page = &{$arg{'new_page_func'}};   
 
 586             $table_top_y = $next_y;
 
 587             $bot_marg = $table_top_y - $next_h;
 
 589             if( ref $header_props and $header_props->{'repeat'})
 
 591                 unshift @$data,           @header_rows;
 
 592                 unshift @$row_col_widths, @header_row_widths;
 
 593                 unshift @$rows_height,    @header_row_heights;
 
 594                 $remaining_header_rows = $header_props->{num_header_rows};
 
 598         # Check for safety reasons
 
 600         {   # This warning should remain i think
 
 601 #            carp "!!! Warning: !!! Incorrect Table Geometry! Setting bottom margin to end of sheet!\n";
 
 605         $gfx_bg = $page->gfx;
 
 607         $txt->font($fnt_name, $fnt_size); 
 
 609         $cur_y = $table_top_y;
 
 614             $gfx->strokecolor($border_color);
 
 615             $gfx->linewidth($line_w);
 
 620                 $gfx->move( $xbase , $cur_y );
 
 621                 $gfx->hline($xbase + $width );
 
 629         # Each iteration adds a row to the current page until the page is full 
 
 630         #  or there are no more rows to add
 
 632         while(scalar(@{$data}) and $cur_y-$row_h > $bot_marg)
 
 634             # Remove the next item from $data
 
 635             my $record = shift @{$data};
 
 637             # Get columns number to know later how many vertical lines to draw
 
 638             # TODO: get the max number of columns per page as currently last row's columns overrides
 
 639             $columns_number = scalar(@$record);
 
 641             # Get the next set of row related settings
 
 643             my $pre_calculated_row_height = shift @$rows_height;
 
 646             my $record_widths = shift @$row_col_widths;
 
 648             # Row coloumn props - TODO in another commit
 
 650             # Row cell props - TODO in another commit
 
 652             # Added to resolve infite loop bug with returned undef values
 
 653             for(my $d = 0; $d < scalar(@{$record}) ; $d++)
 
 655                 $record->[$d] = ' ' unless( defined $record->[$d]);
 
 658             # Choose colors for this row
 
 659             $background_color = ($row_index - $header_props->{num_header_rows}) % 2 ? $background_color_even  : $background_color_odd;
 
 660             $font_color       = ($row_index - $header_props->{num_header_rows}) % 2 ? $font_color_even        : $font_color_odd;
 
 662             #Determine current row height
 
 663             my $current_row_height = $pad_top + $pre_calculated_row_height + $pad_bot;
 
 665             # $row_h is the calculated global user requested row height.
 
 666             # It will be honored, only if it has bigger value than the calculated one.
 
 667             # TODO: It's questionable if padding should be inclided in this calculation or not
 
 668             if($current_row_height < $row_h){
 
 669                 $current_row_height = $row_h;
 
 672             # Define the font y base position for this line.
 
 673             $text_start      = $cur_y - ($current_row_height - $pad_bot);
 
 676             my $leftovers    = undef;   # Reference to text that is returned from textblock()
 
 677             my $do_leftovers = 0;
 
 678             my ($colspan, @vertical_lines);
 
 680             # Process every cell(column) from current row
 
 681             for( my $column_idx = 0; $column_idx < scalar( @$record); $column_idx++ ) 
 
 683                 next unless $col_props->[$column_idx]->{'max_w'};
 
 684                 next unless $col_props->[$column_idx]->{'min_w'};  
 
 685                 $leftovers->[$column_idx] = undef;
 
 687                 # look for font information for this cell
 
 688                 my ($cell_font, $cell_font_size, $cell_font_color, $justify);
 
 690                 if( $remaining_header_rows and ref $header_props)
 
 692                     $cell_font       = $header_props->{'font'};
 
 693                     $cell_font_size  = $header_props->{'font_size'};
 
 694                     $cell_font_color = $header_props->{'font_color'};
 
 695                     $justify         = $header_props->{'justify'};
 
 698                 # Get the most specific value if none was already set from header_props
 
 699                 $cell_font       ||= $cell_props->[$row_index][$column_idx]->{'font'} 
 
 700                                  ||  $col_props->[$column_idx]->{'font'}
 
 703                 $cell_font_size  ||= $cell_props->[$row_index][$column_idx]->{'font_size'}
 
 704                                  ||  $col_props->[$column_idx]->{'font_size'}
 
 707                 $cell_font_color ||= $cell_props->[$row_index][$column_idx]->{'font_color'}
 
 708                                  ||  $col_props->[$column_idx]->{'font_color'}
 
 711                 $justify         ||= $cell_props->[$row_index][$column_idx]->{'justify'}
 
 712                                  ||  $col_props->[$column_idx]->{'justify'}
 
 716                 # Init cell font object
 
 717                 $txt->font( $cell_font, $cell_font_size );
 
 718                 $txt->fillcolor($cell_font_color);
 
 721                 if (!$remaining_header_rows && $cell_props->[$row_index + $header_props->{num_header_rows}][$column_idx]->{colspan}) {
 
 722                     $colspan = $cell_props->[$row_index + $header_props->{num_header_rows}][$column_idx]->{colspan};
 
 723                 } elsif ($remaining_header_rows && ($header_row_cell_props[$header_props->{num_header_rows} - $remaining_header_rows][$column_idx]->{colspan})) {
 
 724                     $colspan = $header_row_cell_props[$header_props->{num_header_rows} - $remaining_header_rows][$column_idx]->{colspan};
 
 728                     $colspan     = $num_cols - $column_idx if (-1 == $colspan);
 
 729                     my $last_idx = $column_idx + $colspan - 1;
 
 730                     $this_width  = sum @{ $calc_column_widths }[$column_idx..$last_idx];
 
 732                     $this_width = $calc_column_widths->[$column_idx];
 
 735                 # If the content is wider than the specified width, we need to add the text as a text block
 
 736                 if( $record->[$column_idx] !~ m/(.\n.)/ and
 
 737                     $record_widths->[$column_idx] and 
 
 738                     $record_widths->[$column_idx] <= $this_width
 
 740                     my $space = $pad_left;
 
 741                     if ($justify eq 'right')
 
 743                         $space = $this_width -($txt->advancewidth($record->[$column_idx]) + $pad_right);
 
 745                     elsif ($justify eq 'center')
 
 747                         $space = ($this_width - $txt->advancewidth($record->[$column_idx])) / 2;
 
 749                     $txt->translate( $cur_x + $space, $text_start );
 
 750                     $txt->text( $record->[$column_idx] );
 
 752                 # Otherwise just use the $page->text() method
 
 755                     my ($width_of_last_line, $ypos_of_last_line, $left_over_text) = $self->text_block(
 
 757                         $record->[$column_idx],
 
 758                         x        => $cur_x + $pad_left,
 
 760                         w        => $this_width - $pad_left - $pad_right,
 
 761                         h        => $cur_y - $bot_marg - $pad_top - $pad_bot,
 
 765                     # Desi - Removed $lead because of fixed incorrect ypos bug in text_block
 
 766                     my  $current_cell_height = $cur_y - $ypos_of_last_line + $pad_bot;
 
 767                     if( $current_cell_height > $current_row_height )
 
 769                         $current_row_height = $current_cell_height;
 
 772                     if( $left_over_text )
 
 774                         $leftovers->[$column_idx] = $left_over_text;
 
 778                 $cur_x += $calc_column_widths->[$column_idx];
 
 780                 push @vertical_lines, (!$colspan || (1 >= $colspan)) ? 1 : 0;
 
 781                 $colspan-- if $colspan;
 
 785                 unshift @$data, $leftovers;
 
 786                 unshift @$row_col_widths, $record_widths;
 
 787                 unshift @$rows_height, $pre_calculated_row_height;
 
 791             # This has to be separately from the text loop 
 
 792             #  because we do not know the final height of the cell until all text has been drawn
 
 794             for(my $column_idx = 0 ; $column_idx < scalar(@$record) ; $column_idx++)
 
 798                 if( $remaining_header_rows and ref $header_props)
 
 799                 {                                  #Compatibility                 Consistency with other props    
 
 800                     $cell_bg_color = $header_props->{'bg_color'} || $header_props->{'background_color'};
 
 803                 # Get the most specific value if none was already set from header_props
 
 804                 $cell_bg_color ||= $cell_props->[$row_index + $header_props->{num_header_rows}][$column_idx]->{'background_color'}
 
 805                                ||  $col_props->[$column_idx]->{'background_color'}
 
 806                                ||  $background_color;
 
 810                     $gfx_bg->rect( $cur_x, $cur_y-$current_row_height, $calc_column_widths->[$column_idx], $current_row_height);
 
 811                     $gfx_bg->fillcolor($cell_bg_color);
 
 814                 $cur_x += $calc_column_widths->[$column_idx];
 
 816                 if ($line_w && $vertical_lines[$column_idx] && ($column_idx != (scalar(@{ $record }) - 1))) {
 
 817                     $gfx->move($cur_x, $cur_y);
 
 818                     $gfx->vline($cur_y - $current_row_height);
 
 819                     $gfx->fillcolor($border_color);
 
 821             }#End of for(my $column_idx....
 
 823             $cur_y -= $current_row_height;
 
 824             if ($gfx && $horiz_borders)
 
 826                 $gfx->move(  $xbase , $cur_y );
 
 827                 $gfx->hline( $xbase + $width );
 
 830             if ($remaining_header_rows) {
 
 831               $remaining_header_rows--;
 
 833               $row_index++ unless $do_leftovers;
 
 839             # Draw vertical lines
 
 842                 $gfx->move(  $xbase, $table_top_y);
 
 843                 $gfx->vline( $cur_y );
 
 844                 $gfx->move($xbase + sum(@{ $calc_column_widths }[0..$num_cols - 1]), $table_top_y);
 
 845                 $gfx->vline( $cur_y );
 
 848             # ACTUALLY draw all the lines
 
 849             $gfx->fillcolor( $border_color);
 
 853     }# End of while(scalar(@{$data}))
 
 855     return ($page,--$pg_cnt,$cur_y);
 
 859 # calculate the column widths
 
 862     my $col_props   = shift;
 
 863     my $avail_width = shift;
 
 868     for(my $j = 0; $j < scalar( @$col_props); $j++)
 
 870         $min_width += $col_props->[$j]->{min_w} || 0;
 
 873     # I think this is the optimal variant when good view can be guaranateed
 
 874     if($avail_width < $min_width)
 
 876         carp "!!! Warning !!!\n Calculated Mininal width($min_width) > Table width($avail_width).\n",
 
 877             ' Expanding table width to:',int($min_width)+1,' but this could lead to unexpected results.',"\n",
 
 878             ' Possible solutions:',"\n",
 
 879             '  0)Increase table width.',"\n",
 
 880             '  1)Decrease font size.',"\n",
 
 881             '  2)Choose a more narrow font.',"\n",
 
 882             '  3)Decrease "max_word_length" parameter.',"\n",
 
 883             '  4)Rotate page to landscape(if it is portrait).',"\n",
 
 884             '  5)Use larger paper size.',"\n",
 
 885             '!!! --------- !!!',"\n";
 
 886         $avail_width = int( $min_width) + 1;
 
 890     # Calculate how much can be added to every column to fit the available width.
 
 891     for(my $j = 0; $j < scalar(@$col_props); $j++ )
 
 893         $calc_widths->[$j] = $col_props->[$j]->{min_w} || 0;;
 
 897     # Calculate how much can be added to every column to fit the available width
 
 898     $span = ($avail_width - $min_width) / scalar( @$col_props);
 
 899     for (my $j = 0; $j < scalar(@$col_props); $j++ ) {
 
 900       $calc_widths->[$j] = $col_props->[$j]->{min_w} + $span;
 
 903     return ($calc_widths,$avail_width);
 
 913 PDF::Table - A utility class for building table layouts in a PDF::API2 object.
 
 920  my $pdftable = new PDF::Table;
 
 921  my $pdf = new PDF::API2(-file => "table_of_lorem.pdf");
 
 922  my $page = $pdf->page;
 
 924  # some data to layout
 
 926     ["1 Lorem ipsum dolor",
 
 927     "Donec odio neque, faucibus vel",
 
 928     "consequat quis, tincidunt vel, felis."],
 
 929     ["Nulla euismod sem eget neque.",
 
 935  $left_edge_of_table = 50;
 
 936  # build the table layout
 
 942      x => $left_edge_of_table,
 
 946      # some optional params
 
 951      background_color_odd  => "gray",
 
 952      background_color_even => "lightblue", #cell background color for even rows
 
 955  # do other stuff with $pdf
 
 961 For a complete working example or initial script look into distribution`s 'examples' folder.
 
 966 This class is a utility for use with the PDF::API2 module from CPAN. 
 
 967 It can be used to display text data in a table layout within a PDF. 
 
 968 The text data must be in a 2D array (such as returned by a DBI statement handle fetchall_arrayref() call). 
 
 969 The PDF::Table will automatically add as many new pages as necessary to display all of the data. 
 
 970 Various layout properties, such as font, font size, and cell padding and background color can be specified for each column and/or for even/odd rows. 
 
 971 Also a (non)repeated header row with different layout properties can be specified. 
 
 973 See the L</METHODS> section for complete documentation of every parameter.
 
 979     my $pdf_table = new PDF::Table;
 
 985 Creates a new instance of the class. (to be improved)
 
 989 There are no parameters. 
 
 993 Reference to the new instance
 
 999     my ($final_page, $number_of_pages, $final_y) = table($pdf, $page, $data, %settings)
 
1005 Generates a multi-row, multi-column table into an existing PDF document based on provided data set and settings.
 
1009     $pdf      - a PDF::API2 instance representing the document being created
 
1010     $page     - a PDF::API2::Page instance representing the current page of the document
 
1011     $data     - an ARRAY reference to a 2D data structure that will be used to build the table
 
1012     %settings - HASH with geometry and formatting parameters. 
 
1014 For full %settings description see section L</Table settings> below.
 
1016 This method will add more pages to the pdf instance as required based on the formatting options and the amount of data.
 
1020 The return value is a 3 items list where 
 
1022     $final_page - The first item is a PDF::API2::Page instance that the table ends on
 
1023     $number_of_pages - The second item is the count of pages that the table spans on
 
1024     $final_y - The third item is the Y coordinate of the table bottom so that additional content can be added in the same document.
 
1028     my $pdf  = new PDF::API2;
 
1029     my $page = $pdf->page();
 
1031         ['foo1','bar1','baz1'],
 
1032         ['foo2','bar2','baz2']
 
1041     my ($final_page, $number_of_pages, $final_y) = $pdftable->table( $pdf, $page, $data, %options );
 
1045 =head3 Table settings
 
1049 There are some mandatory parameteres for setting table geometry and position across page(s)
 
1053 =item B<x> - X coordinate of upper left corner of the table. Left edge of the sheet is 0.
 
1055 B<Value:> can be any whole number satisfying 0 =< X < PageWidth
 
1056 B<Default:> No default value 
 
1060 =item B<start_y> - Y coordinate of upper left corner of the table at the initial page.
 
1062 B<Value:> can be any whole number satisfying 0 < start_y < PageHeight (depending on space availability when embedding a table)
 
1063 B<Default:> No default value
 
1067 =item B<w> - width of the table starting from X.
 
1069 B<Value:> can be any whole number satisfying 0 < w < PageWidth - x
 
1070 B<Default:> No default value
 
1074 =item B<start_h> - Height of the table on the initial page
 
1076 B<Value:> can be any whole number satisfying 0 < start_h < PageHeight - Current Y position
 
1077 B<Default:> No default value
 
1087 =item B<next_h> - Height of the table on any additional page
 
1089 B<Value:> can be any whole number satisfying 0 < next_h < PageHeight
 
1090 B<Default:> Value of param B<'start_h'>
 
1094 =item B<next_y> - Y coordinate of upper left corner of the table at any additional page.
 
1096 B<Value:> can be any whole number satisfying 0 < next_y < PageHeight
 
1097 B<Default:> Value of param B<'start_y'>
 
1101 =item B<max_word_length> - Breaks long words (like serial numbers hashes etc.) by adding a space after every Nth symbol 
 
1103 B<Value:> can be any whole positive number
 
1106     max_word_length => 20    # Will add a space after every 20 symbols
 
1108 =item B<padding> - Padding applied to every cell 
 
1110 =item B<padding_top>    - top cell padding, overrides 'padding'
 
1112 =item B<padding_right>  - right cell padding, overrides 'padding'
 
1114 =item B<padding_left>   - left cell padding, overrides 'padding'
 
1116 =item B<padding_bottom> - bottom padding, overrides 'padding'
 
1118 B<Value:> can be any whole positive number
 
1120 B<Default padding:> 0
 
1122 B<Default padding_*> $padding
 
1124     padding        => 5      # all sides cell padding
 
1125     padding_top    => 8,     # top cell padding, overrides 'padding'
 
1126     padding_right  => 6,     # right cell padding, overrides 'padding'
 
1127     padding_left   => 2,     # left cell padding, overrides 'padding'
 
1128     padding_bottom => undef  # bottom padding will be 5 as it will fallback to 'padding'
 
1130 =item B<border> - Width of table border lines. 
 
1132 =item B<horizontal_borders> - Width of horizontal border lines. Overrides 'border' value.
 
1134 =item B<vertical_borders> -  Width of vertical border lines. Overrides 'border' value.
 
1136 B<Value:> can be any whole positive number. When set to 0 will disable border lines.
 
1139     border             => 3     # border width is 3
 
1140     horizontal_borders => 1     # horizontal borders will be 1 overriding 3
 
1141     vertical_borders   => undef # vertical borders will be 3 as it will fallback to 'border'
 
1143 =item B<vertical_borders> -  Width of vertical border lines. Overrides 'border' value.
 
1145 B<Value:> Color specifier as 'name' or 'HEX'
 
1148     border_color => 'red'
 
1150 =item B<font> - instance of PDF::API2::Resource::Font defining the fontf to be used in the table
 
1152 B<Value:> can be any PDF::API2::Resource::* type of font
 
1153 B<Default:> 'Times' with UTF8 encoding
 
1155     font => $pdf->corefont("Helvetica", -encoding => "utf8")
 
1157 =item B<font_size> - Default size of the font that will be used across the table
 
1159 B<Value:> can be any positive number
 
1164 =item B<font_color> - Font color for all rows
 
1166 =item B<font_color_odd> - Font color for odd rows
 
1168 =item B<font_color_even> - Font color for even rows 
 
1170 =item B<background_color_odd> - Background color for odd rows
 
1172 =item B<background_color_even> - Background color for even rows
 
1174 B<Value:> Color specifier as 'name' or 'HEX'
 
1175 B<Default:> 'black' font on 'white' background
 
1177     font_color            => '#333333'
 
1178     font_color_odd        => 'purple'
 
1179     font_color_even       => '#00FF00'
 
1180     background_color_odd  => 'gray'     
 
1181     background_color_even => 'lightblue'
 
1183 =item B<row_height> - Desired row height but it will be honored only if row_height > font_size + padding_top + padding_bottom
 
1185 B<Value:> can be any whole positive number
 
1186 B<Default:> font_size + padding_top + padding_bottom
 
1190 =item B<new_page_func> - CODE reference to a function that returns a PDF::API2::Page instance.
 
1192 If used the parameter 'new_page_func' must be a function reference which when executed will create a new page and will return the object back to the module.
 
1193 For example you can use it to put Page Title, Page Frame, Page Numbers and other staff that you need.
 
1194 Also if you need some different type of paper size and orientation than the default A4-Portrait for example B2-Landscape you can use this function ref to set it up for you. For more info about creating pages refer to PDF::API2 PAGE METHODS Section.
 
1195 Don't forget that your function must return a page object created with PDF::API2 page() method.
 
1197     new_page_func  => $code_ref
 
1199 =item B<header_props> - HASH reference to specific settings for the Header row of the table. See section L</Header Row Properties> below
 
1201     header_props => $hdr_props
 
1203 =item B<column_props> - HASH reference to specific settings for each column of the table. See section L</Column Properties> below
 
1205     column_props => $col_props
 
1207 =item B<cell_props> - HASH reference to specific settings for each column of the table. See section L</Cell Properties> below
 
1209     cell_props => $cel_props
 
1213 =head4 Header Row Properties
 
1215 If the 'header_props' parameter is used, it should be a hashref. Passing an empty HASH will trigger a header row initialised with Default values.
 
1216 There is no 'data' variable for the content, because the module asumes that first table row will become the header row. It will copy this row and put it on every new page if 'repeat' param is set.
 
1220 =item B<font> - instance of PDF::API2::Resource::Font defining the fontf to be used in the header row
 
1222 B<Value:> can be any PDF::API2::Resource::* type of font
 
1223 B<Default:> 'font' of the table. See table parameter 'font' for more details.
 
1225 =item B<font_size> - Font size of the header row
 
1227 B<Value:> can be any positive number
 
1228 B<Default:> 'font_size' of the table + 2  
 
1230 =item B<font_color> - Font color of the header row
 
1232 B<Value:> Color specifier as 'name' or 'HEX'
 
1233 B<Default:> '#000066'
 
1235 =item B<bg_color> - Background color of the header row
 
1237 B<Value:> Color specifier as 'name' or 'HEX'
 
1240 =item B<repeat> - Flag showing if header row should be repeated on every new page
 
1242 B<Value:> 0,1   1-Yes/True, 0-No/False 
 
1245 =item B<justify> - Alignment of text in the header row.
 
1247 B<Value:> One of 'left', 'right', 'center'
 
1248 B<Default:> Same as column alignment (or 'left' if undefined)
 
1252         font       => $pdf->corefont("Helvetica", -encoding => "utf8"),
 
1254         font_color => '#004444',
 
1255         bg_color   => 'yellow', 
 
1262 =head4 Column Properties
 
1264 If the 'column_props' parameter is used, it should be an arrayref of hashrefs, 
 
1265 with one hashref for each column of the table. The columns are counted from left to right so the hash reference at $col_props[0] will hold properties for the first column from left to right. 
 
1266 If you DO NOT want to give properties for a column but to give for another just insert and empty hash reference into the array for the column that you want to skip. This will cause the counting to proceed as expected and the properties to be applyed at the right columns.
 
1268 Each hashref can contain any of the keys shown below:
 
1272 =item B<min_w> - Minimum width of this column. Auto calculation will try its best to honour this param but aplying it is NOT guaranteed.
 
1274 B<Value:> can be any whole number satisfying 0 < min_w < w
 
1275 B<Default:> Auto calculated
 
1277 =item B<max_w> - Maximum width of this column. Auto calculation will try its best to honour this param but aplying it is NOT guaranteed.
 
1279 B<Value:> can be any whole number satisfying 0 < max_w < w
 
1280 B<Default:> Auto calculated
 
1282 =item B<font> - instance of PDF::API2::Resource::Font defining the fontf to be used in this column
 
1284 B<Value:> can be any PDF::API2::Resource::* type of font
 
1285 B<Default:> 'font' of the table. See table parameter 'font' for more details.
 
1287 =item B<font_size> - Font size of this column
 
1289 B<Value:> can be any positive number
 
1290 B<Default:> 'font_size' of the table.
 
1292 =item B<font_color> - Font color of this column
 
1294 B<Value:> Color specifier as 'name' or 'HEX'
 
1295 B<Default:> 'font_color' of the table.
 
1297 =item B<background_color> - Background color of this column
 
1299 B<Value:> Color specifier as 'name' or 'HEX'
 
1302 =item B<justify> - Alignment of text in this column
 
1304 B<Value:> One of 'left', 'right', 'center'
 
1310         {},# This is an empty hash so the next one will hold the properties for the second column from left to right.
 
1312             min_w => 100,       # Minimum column width of 100.
 
1313             max_w => 150,       # Maximum column width of 150 .
 
1314             justify => 'right', # Right text alignment
 
1315             font => $pdf->corefont("Helvetica", -encoding => "latin1"),
 
1317             font_color=> 'blue',
 
1318             background_color => '#FFFF00',
 
1325 NOTE: If 'min_w' and/or 'max_w' parameter is used in 'col_props', have in mind that it may be overriden by the calculated minimum/maximum cell witdh so that table can be created.
 
1326 When this happens a warning will be issued with some advises what can be done.
 
1327 In cases of a conflict between column formatting and odd/even row formatting, 'col_props' will override odd/even.
 
1329 =head4 Cell Properties
 
1331 If the 'cell_props' parameter is used, it should be an arrayref with arrays of hashrefs
 
1332 (of the same dimension as the data array) with one hashref for each cell of the table.
 
1334 Each hashref can contain any of the keys shown below:
 
1338 =item B<font> - instance of PDF::API2::Resource::Font defining the fontf to be used in this cell
 
1340 B<Value:> can be any PDF::API2::Resource::* type of font
 
1341 B<Default:> 'font' of the table. See table parameter 'font' for more details.
 
1343 =item B<font_size> - Font size of this cell
 
1345 B<Value:> can be any positive number
 
1346 B<Default:> 'font_size' of the table.
 
1348 =item B<font_color> - Font color of this cell
 
1350 B<Value:> Color specifier as 'name' or 'HEX'
 
1351 B<Default:> 'font_color' of the table.
 
1353 =item B<background_color> - Background color of this cell
 
1355 B<Value:> Color specifier as 'name' or 'HEX'
 
1358 =item B<justify> - Alignment of text in this cell
 
1360 B<Value:> One of 'left', 'right', 'center'
 
1366         [ #This array is for the first row. If header_props is defined it will overwrite these settings.
 
1368                 background_color => '#AAAA00',
 
1369                 font_color       => 'yellow',
 
1376                 background_color => '#CCCC00',
 
1377                 font_color       => 'blue',
 
1380                 background_color => '#BBBB00',
 
1381                 font_color       => 'red',
 
1390     my $cell_props = [];
 
1391     $cell_props->[1][0] = {
 
1393         background_color => '#CCCC00',
 
1394         font_color       => 'blue',
 
1399 NOTE: In case of a conflict between column, odd/even and cell formating, cell formating will overwrite the other two.
 
1400 In case of a conflict between header row and cell formating, header formating will override cell.
 
1404     my ($width_of_last_line, $ypos_of_last_line, $left_over_text) = text_block( $txt, $data, %settings)
 
1410 Utility method to create a block of text. The block may contain multiple paragraphs.
 
1411 It is mainly used internaly but you can use it from outside for placing formated text anywhere on the sheet.
 
1413 NOTE: This method will NOT add more pages to the pdf instance if the space is not enough to place the string inside the block.
 
1414 Leftover text will be returned and has to be handled by the caller - i.e. add a new page and a new block with the leftover.
 
1418     $txt  - a PDF::API2::Page::Text instance representing the text tool
 
1419     $data - a string that will be placed inside the block
 
1420     %settings - HASH with geometry and formatting parameters.
 
1424 The return value is a 3 items list where 
 
1426     $width_of_last_line - Width of last line in the block
 
1427     $final_y - The Y coordinate of the block bottom so that additional content can be added after it
 
1428     $left_over_text - Text that was did not fit in the provided box geometry.
 
1433     my $page = $pdf->page;
 
1434     my $txt  = $page->text;
 
1443         lead     => $font_size | $distance_between_lines,
 
1444         align    => "left|right|center|justify|fulljustify",
 
1445         hang     => $optional_hanging_indent,
 
1446         Only one of the subsequent 3params can be given. 
 
1447         They override each other.-parspace is the weightest
 
1448         parspace => $optional_vertical_space_before_first_paragraph,
 
1449         flindent => $optional_indent_of_first_line,
 
1450         fpindent => $optional_indent_of_first_paragraph,
 
1451         indent   => $optional_indent_of_text_to_every_non_first_line,
 
1454     my ( $width_of_last_line, $final_y, $left_over_text ) = $pdftable->text_block( $txt, $data, %settings );
 
1468 Further development since Ver: 0.02 - Desislav Kamenov
 
1470 =head1 COPYRIGHT AND LICENSE
 
1472 Copyright (C) 2006 by Daemmon Hughes, portions Copyright 2004 Stone
 
1473 Environmental Inc. (www.stone-env.com) All Rights Reserved.
 
1475 This library is free software; you can redistribute it and/or modify
 
1476 it under the same terms as Perl itself, either Perl version 5.8.4 or,
 
1477 at your option, any later version of Perl 5 you may have available.
 
1483 =item by Daemmon Hughes
 
1485 Much of the work on this module was sponsered by
 
1486 Stone Environmental Inc. (www.stone-env.com).
 
1488 The text_block() method is a slightly modified copy of the one from
 
1489 Rick Measham's PDF::API2 L<tutorial|http://rick.measham.id.au/pdf-api2>.
 
1491 =item by Desislav Kamenov (@deskata on Twitter)
 
1493 The development of this module was supported by SEEBURGER AG (www.seeburger.com) till year 2007
 
1495 Thanks to my friends Krasimir Berov and Alex Kantchev for helpful tips and QA during development of versions 0.9.0 to 0.9.5
 
1497 Thanks to all GitHub contributors!
 
1503 Hey PDF::Table is on GitHub. You are more than welcome to contribute!
 
1505 https://github.com/kamenov/PDF-Table