- {
- my $self = shift;
- my $pdf = shift;
- my $page = shift;
- my $data = shift;
- my %arg = @_;
-
- #=====================================
- # Mandatory Arguments Section
- #=====================================
- unless($pdf and $page and $data)
- {
- print "Error: Mandatory parameter is missing pdf/page/data object!\n";
- return;
- }
- # Try to provide backward compatibility
- foreach my $key (keys %arg)
- {
- my $newkey = $key;
- if($newkey =~ s#^-##)
- {
- $arg{$newkey} = $arg{$key};
- delete $arg{$key};
- }
- }
- #TODO: Add code for header props compatibility and col_props comp....
- #####
- my ( $xbase, $ybase, $width, $height ) = ( undef, undef, undef, undef );
- # Could be 'int' or 'real' values
- $xbase = $arg{'x' } || -1;
- $ybase = $arg{'start_y'} || -1;
- $width = $arg{'w' } || -1;
- $height = $arg{'start_h'} || -1;
-
- # Global geometry parameters are also mandatory.
- unless( $xbase > 0 ){ print "Error: Left Edge of Table is NOT defined!\n"; return; }
- unless( $ybase > 0 ){ print "Error: Base Line of Table is NOT defined!\n"; return; }
- unless( $width > 0 ){ print "Error: Width of Table is NOT defined!\n"; return; }
- unless( $height > 0 ){ print "Error: Height of Table is NOT defined!\n"; return; }
-
- # Ensure default values for -next_y and -next_h
- my $next_y = $arg{'next_y'} || $arg{'start_y'} || 0;
- my $next_h = $arg{'next_h'} || $arg{'start_h'} || 0;
-
- # Create Text Object
- my $txt = $page->text;
- # Set Default Properties
- my $fnt_name = $arg{'font' } || $pdf->corefont('Times',-encode => 'utf8');
- my $fnt_size = $arg{'font_size' } || 12;
- my $max_word_len= $arg{'max_word_length' } || 20;
-
- #=====================================
- # Table Header Section
- #=====================================
- # Disable header row into the table
- my $header_props = 0;
- # Check if the user enabled it ?
- if(defined $arg{'header_props'} and ref( $arg{'header_props'}) eq 'HASH')
- {
- # Transfer the reference to local variable
- $header_props = $arg{'header_props'};
- # Check other params and put defaults if needed
- $header_props->{'repeat' } = $header_props->{'repeat' } || 0;
- $header_props->{'font' } = $header_props->{'font' } || $fnt_name;
- $header_props->{'font_color' } = $header_props->{'font_color' } || '#000066';
- $header_props->{'font_size' } = $header_props->{'font_size' } || $fnt_size + 2;
- $header_props->{'bg_color' } = $header_props->{'bg_color' } || '#FFFFAA';
- }
- my $header_row = undef;
- #=====================================
- # Other Parameters check
- #=====================================
-
- my $lead = $arg{'lead' } || $fnt_size;
- my $pad_left = $arg{'padding_left' } || $arg{'padding'} || 0;
- my $pad_right = $arg{'padding_right' } || $arg{'padding'} || 0;
- my $pad_top = $arg{'padding_top' } || $arg{'padding'} || 0;
- my $pad_bot = $arg{'padding_bottom' } || $arg{'padding'} || 0;
- my $pad_w = $pad_left + $pad_right;
- my $pad_h = $pad_top + $pad_bot ;
- my $line_w = defined $arg{'border'} ? $arg{'border'} : 1 ;
-
- my $background_color_even = $arg{'background_color_even' } || $arg{'background_color'} || undef;
- my $background_color_odd = $arg{'background_color_odd' } || $arg{'background_color'} || undef;
- my $font_color_even = $arg{'font_color_even' } || $arg{'font_color' } || 'black';
- my $font_color_odd = $arg{'font_color_odd' } || $arg{'font_color' } || 'black';
- my $border_color = $arg{'border_color' } || 'black';
-
- my $min_row_h = $fnt_size + $pad_top + $pad_bot;
- my $row_h = defined ($arg{'row_height'})
- &&
- ($arg{'row_height'} > $min_row_h)
- ?
- $arg{'row_height'} : $min_row_h;
-
- my $pg_cnt = 1;
- my $cur_y = $ybase;
- my $cell_props = $arg{cell_props} || []; # per cell properties
- my $row_cnt = ( ref $header_props and $header_props->{'repeat'} ) ? 1 : 0; # current row in user data
-
- #If there is valid data array reference use it!
- if(ref $data eq 'ARRAY')
- {
- # Copy the header row if header is enabled
- @$header_row = $$data[0] if defined $header_props;
- # Determine column widths based on content
-
- # an arrayref whose values are a hashref holding
- # the minimum and maximum width of that column
- my $col_props = $arg{'column_props'} || [];
-
- # An array ref of arrayrefs whose values are
- # the actual widths of the column/row intersection
- my $row_props = [];
- # An array ref with the widths of the header row
- my $header_row_props = [];
-
- # Scalars that hold sum of the maximum and minimum widths of all columns
- my ( $max_col_w , $min_col_w ) = ( 0,0 );
- my ( $row, $col_name, $col_fnt_size, $space_w );
-
- # Hash that will hold the width of every word from input text
- my $word_w = {};
- my $rows_counter = 0;
- my $first_row = 1;
-
- foreach $row ( @{$data} )
- {
- my $column_widths = []; #holds the width of each column
- for( my $j = 0; $j < scalar(@$row) ; $j++ )
- {
- # look for font information for this column
- $col_fnt_size = $col_props->[$j]->{'font_size'} || $fnt_size;
- if( !$rows_counter and ref $header_props)
- {
- $txt->font( $header_props->{'font'}, $header_props->{'font_size'} );
- }
- elsif( $col_props->[$j]->{'font'} )
- {
- $txt->font( $col_props->[$j]->{'font'}, $col_fnt_size );
- }
- else
- {
- $txt->font( $fnt_name, $col_fnt_size );
- }
-
- # This should fix a bug with very long word like serial numbers etc.
- # $myone is used because $1 gets out of scope in while condition
- my $myone;
- do{
- $myone = 0;
- # This RegEx will split any word that is longer than {25} symbols
- $row->[$j] =~ s#(\b\S{$max_word_len}?)(\S.*?\b)# $1 $2#;
- $myone = 1 if( defined $2 );
- }while( $myone );
-
- $space_w = $txt->advancewidth( "\x20" );
- $column_widths->[$j] = 0;
- $max_col_w = 0;
- $min_col_w = 0;
-
- my @words = split( /\s+/, $row->[$j] );
-
- foreach( @words )
- {
- unless( exists $word_w->{$_} )
- { # Calculate the width of every word and add the space width to it
- $word_w->{$_} = $txt->advancewidth( $_ ) + $space_w;
- }
- $column_widths->[$j] += $word_w->{$_};
- $min_col_w = $word_w->{$_} if $word_w->{$_} > $min_col_w;
- $max_col_w += $word_w->{$_};
- }
- $min_col_w += $pad_w;
- $max_col_w += $pad_w;
- $column_widths->[$j] += $pad_w;
-
- # Keep a running total of the overall min and max widths
- $col_props->[$j]->{min_w} = $col_props->[$j]->{min_w} || 0;
- $col_props->[$j]->{max_w} = $col_props->[$j]->{max_w} || 0;
-
- if( $min_col_w > $col_props->[$j]->{min_w} )
- { # Calculated Minimum Column Width is more than user-defined
- $col_props->[$j]->{min_w} = $min_col_w ;
- }
- if( $max_col_w > $col_props->[$j]->{max_w} )
- { # Calculated Maximum Column Width is more than user-defined
- $col_props->[$j]->{max_w} = $max_col_w ;
- }
- }#End of for(my $j....
- $row_props->[$rows_counter] = $column_widths;
- # Copy the calculated row properties of header row.
- @$header_row_props = @$column_widths if(!$rows_counter and ref $header_props);
- $rows_counter++;
- }
- # Calc real column widths and expand table width if needed.
- my $calc_column_widths;
- ($calc_column_widths, $width) = $self->CalcColumnWidths( $col_props, $width );
-
- my $comp_cnt = 1;
- $rows_counter = 0;
-
- my ( $gfx , $gfx_bg , $background_color , $font_color, );
- my ( $bot_marg, $table_top_y, $text_start , $record, $record_widths );
-
- # Each iteration adds a new page as neccessary
- while(scalar(@{$data}))
- {
- my $page_header;
- if($pg_cnt == 1)
- {
- $table_top_y = $ybase;
- $bot_marg = $table_top_y - $height;
- }
- else
- {
- if(ref $arg{'new_page_func'})
- {
- $page = &{$arg{'new_page_func'}};
- }
- else
- {
- $page = $pdf->page;
- }
-
- $table_top_y = $next_y;
- $bot_marg = $table_top_y - $next_h;
-
- if( ref $header_props and $header_props->{'repeat'})
- {
- # Copy Header Data
- @$page_header = @$header_row;
- my $hrp ;
- @$hrp = @$header_row_props ;
- # Then prepend it to master data array
- unshift @$data ,@$page_header ;
- unshift @$row_props ,$hrp ;
- $first_row = 1; # Means YES
- }
- }
-
- # Check for safety reasons
- if( $bot_marg < 0 )
- { # This warning should remain i think
- print "!!! Warning: !!! Incorrect Table Geometry! Setting bottom margin to end of sheet!\n";
- $bot_marg = 0;
- }
-
- $gfx_bg = $page->gfx;
- $txt = $page->text;
- $txt->font($fnt_name, $fnt_size);
- $gfx = $page->gfx;
- $gfx->strokecolor($border_color);
- $gfx->linewidth($line_w);
-
- # Draw the top line
- $cur_y = $table_top_y;
- $gfx->move( $xbase , $cur_y );
- $gfx->hline($xbase + $width );
-
- # Each iteration adds a row to the current page until the page is full
- # or there are no more rows to add
- while(scalar(@{$data}) and $cur_y-$row_h > $bot_marg)
- {
- # Remove the next item from $data
- $record = shift @{$data};
- # Added to resolve infite loop bug with returned undef values
- for(my $d = 0; $d < scalar(@{$record}) ; $d++)
- {
- $record->[$d] = '-' unless( defined $record->[$d]);
- }
-
- $record_widths = shift @$row_props;
- next unless $record;
-
- # Choose colors for this row
- $background_color = $rows_counter % 2 ? $background_color_even : $background_color_odd;
- $font_color = $rows_counter % 2 ? $font_color_even : $font_color_odd;
-
- if($first_row and ref $header_props)
- {
- $background_color = $header_props->{'bg_color'}
- }
- $text_start = $cur_y - $fnt_size - $pad_top;
- my $cur_x = $xbase;
- my $leftovers = undef; # Reference to text that is returned from textblock()
- my $do_leftovers = 0;
-
- # Process every column from current row
- for( my $j = 0; $j < scalar( @$record); $j++ )
- {
- next unless $col_props->[$j]->{max_w};
- next unless $col_props->[$j]->{min_w};
- $leftovers->[$j] = undef;
-
- # Choose font color
- if( $first_row and ref $header_props )
- {
- $txt->fillcolor( $header_props->{'font_color'} );
- }
- elsif( $cell_props->[$row_cnt][$j]{font_color} )
- {
- $txt->fillcolor( $cell_props->[$row_cnt][$j]{font_color} );
- }
- elsif( $col_props->[$j]->{'font_color'} )
- {
- $txt->fillcolor( $col_props->[$j]->{'font_color'} );
- }
- else
- {
- $txt->fillcolor($font_color);
- }
-
- # Choose font size
- if( $first_row and ref $header_props )
- {
- $col_fnt_size = $header_props->{'font_size'};
- }
- elsif( $col_props->[$j]->{'font_size'} )
- {
- $col_fnt_size = $col_props->[$j]->{'font_size'};
- }
- else
- {
- $col_fnt_size = $fnt_size;
- }
-
- # Choose font family
- if( $first_row and ref $header_props )
- {
- $txt->font( $header_props->{'font'}, $header_props->{'font_size'});
- }
- elsif( $col_props->[$j]->{'font'} )
- {
- $txt->font( $col_props->[$j]->{'font'}, $col_fnt_size);
- }
- else
- {
- $txt->font( $fnt_name, $col_fnt_size);
- }
- #TODO: Implement Center text align
- $col_props->[$j]->{justify} = $col_props->[$j]->{justify} || 'left';
- # If the content is wider than the specified width, we need to add the text as a text block
- if($record->[$j] !~ m#(.\n.)# and $record_widths->[$j] and ($record_widths->[$j] < $calc_column_widths->[$j]))
- {
- my $space = $pad_left;
- if($col_props->[$j]->{justify} eq 'right')
- {
- $space = $calc_column_widths->[$j] -($txt->advancewidth($record->[$j]) + $pad_right);
- }
- $txt->translate( $cur_x + $space, $text_start );
- $txt->text( $record->[$j] );
- }
- # Otherwise just use the $page->text() method
- else
- {
- my($width_of_last_line, $ypos_of_last_line, $left_over_text) = $self->text_block(
- $txt,
- $record->[$j],
- x => $cur_x + $pad_left,
- y => $text_start,
- w => $calc_column_widths->[$j] - $pad_w,
- h => $cur_y - $bot_marg - $pad_top - $pad_bot,
- align => $col_props->[$j]->{justify},
- lead => $lead
- );
- # Desi - Removed $lead because of fixed incorrect ypos bug in text_block
- my $this_row_h = $cur_y - ( $ypos_of_last_line - $pad_bot );
- $row_h = $this_row_h if $this_row_h > $row_h;
- if( $left_over_text )
- {
- $leftovers->[$j] = $left_over_text;
- $do_leftovers = 1;
- }
- }
- $cur_x += $calc_column_widths->[$j];
- }
- if( $do_leftovers )
- {
- unshift @$data, $leftovers;
- unshift @$row_props, $record_widths;
- $rows_counter--;
- }
- # Draw cell bgcolor
- # This has to be separately from the text loop
- # because we do not know the final height of the cell until all text has been drawn
- $cur_x = $xbase;
- for(my $j =0;$j < scalar(@$record);$j++)
- {
- if ( $cell_props->[$row_cnt][$j]->{'background_color'} ||
- $col_props->[$j]->{'background_color'} ||
- $background_color )
- {
- $gfx_bg->rect( $cur_x, $cur_y-$row_h, $calc_column_widths->[$j], $row_h);
- if ( $cell_props->[$row_cnt][$j]->{'background_color'} && !$first_row )
- {
- $gfx_bg->fillcolor($cell_props->[$row_cnt][$j]->{'background_color'});
- }
- elsif( $col_props->[$j]->{'background_color'} && !$first_row )
- {
- $gfx_bg->fillcolor($col_props->[$j]->{'background_color'});
- }
- else
- {
- $gfx_bg->fillcolor($background_color);
- }
- $gfx_bg->fill();
- }
- $cur_x += $calc_column_widths->[$j];
- }#End of for(my $j....
-
- $cur_y -= $row_h;
- $row_h = $min_row_h;
- $gfx->move( $xbase , $cur_y );
- $gfx->hline( $xbase + $width );
- $rows_counter++;
- $row_cnt++ unless ( $first_row );
- $first_row = 0;
- }# End of while(scalar(@{$data}) and $cur_y-$row_h > $bot_marg)
-
- # Draw vertical lines
- $gfx->move( $xbase, $table_top_y);
- $gfx->vline( $cur_y );
- my $cur_x = $xbase;
- for( my $j = 0; $j < scalar(@$record); $j++ )
- {
- $cur_x += $calc_column_widths->[$j];
- $gfx->move( $cur_x, $table_top_y );
- $gfx->vline( $cur_y );
-
- }
- # ACTUALLY draw all the lines
- $gfx->fillcolor( $border_color);
- $gfx->stroke if $line_w;
- $pg_cnt++;
- }# End of while(scalar(@{$data}))
- }# End of if(ref $data eq 'ARRAY')
-
- return ($page,--$pg_cnt,$cur_y);
+{
+ my $self = shift;
+ my $pdf = shift;
+ my $page = shift;
+ my $data = shift;
+ my %arg = @_;
+
+ #=====================================
+ # Mandatory Arguments Section
+ #=====================================
+ unless($pdf and $page and $data)
+ {
+ carp "Error: Mandatory parameter is missing pdf/page/data object!\n";
+ return;
+ }
+
+ # Validate mandatory argument data type
+ croak "Error: Invalid pdf object received." unless (ref($pdf) eq 'PDF::API2');
+ croak "Error: Invalid page object received." unless (ref($page) eq 'PDF::API2::Page');
+ croak "Error: Invalid data received." unless ((ref($data) eq 'ARRAY') && scalar(@$data));
+ croak "Error: Missing required settings." unless (scalar(keys %arg));
+
+ # Validate settings key
+ my %valid_settings_key = (
+ x => 1,
+ w => 1,
+ start_y => 1,
+ start_h => 1,
+ next_y => 1,
+ next_h => 1,
+ lead => 1,
+ padding => 1,
+ padding_right => 1,
+ padding_left => 1,
+ padding_top => 1,
+ padding_bottom => 1,
+ background_color => 1,
+ background_color_odd => 1,
+ background_color_even => 1,
+ border => 1,
+ border_color => 1,
+ horizontal_borders => 1,
+ vertical_borders => 1,
+ font => 1,
+ font_size => 1,
+ font_underline => 1,
+ font_color => 1,
+ font_color_even => 1,
+ font_color_odd => 1,
+ background_color_odd => 1,
+ background_color_even => 1,
+ row_height => 1,
+ new_page_func => 1,
+ header_props => 1,
+ column_props => 1,
+ cell_props => 1,
+ max_word_length => 1,
+ cell_render_hook => 1,
+ default_text => 1,
+ num_header_rows => 1,
+ );
+ foreach my $key (keys %arg)
+ {
+ # Provide backward compatibility
+ $arg{$key} = delete $arg{"-$key"} if $key =~ s/^-//;
+
+ croak "Error: Invalid setting key '$key' received."
+ unless exists $valid_settings_key{$key};
+ }
+
+
+ ######
+ #TODO: Add code for header props compatibility and col_props comp....
+ ######
+ my ( $xbase, $ybase, $width, $height ) = ( undef, undef, undef, undef );
+ # Could be 'int' or 'real' values
+ $xbase = $arg{'x' } || -1;
+ $ybase = $arg{'start_y'} || -1;
+ $width = $arg{'w' } || -1;
+ $height = $arg{'start_h'} || -1;
+
+ # Global geometry parameters are also mandatory.
+ unless( $xbase > 0 ){ carp "Error: Left Edge of Table is NOT defined!\n"; return; }
+ unless( $ybase > 0 ){ carp "Error: Base Line of Table is NOT defined!\n"; return; }
+ unless( $width > 0 ){ carp "Error: Width of Table is NOT defined!\n"; return; }
+ unless( $height > 0 ){ carp "Error: Height of Table is NOT defined!\n"; return; }
+
+ # Ensure default values for -next_y and -next_h
+ my $next_y = $arg{'next_y'} || $arg{'start_y'} || 0;
+ my $next_h = $arg{'next_h'} || $arg{'start_h'} || 0;
+
+ # Create Text Object
+ my $txt = $page->text;
+
+ # Set Default Properties
+ my $fnt_name = $arg{'font' } || $pdf->corefont('Times',-encode => 'utf8');
+ my $fnt_size = $arg{'font_size' } || 12;
+ my $fnt_underline = $arg{'font_underline' } || undef; # merely stating undef is the intended default
+ my $max_word_len = $arg{'max_word_length' } || 20;
+
+ #=====================================
+ # Table Header Section
+ #=====================================
+ # Disable header row into the table
+ my $header_props = undef;
+ my (@header_rows, @header_row_cell_props);
+ # Check if the user enabled it ?
+ if(defined $arg{'header_props'} and ref( $arg{'header_props'}) eq 'HASH')
+ {
+ # Transfer the reference to local variable
+ $header_props = $arg{'header_props'};
+
+ # Check other params and put defaults if needed
+ $header_props->{'repeat' } = $header_props->{'repeat' } || 0;
+ $header_props->{'font' } = $header_props->{'font' } || $fnt_name;
+ $header_props->{'font_color' } = $header_props->{'font_color' } || '#000066';
+ $header_props->{'font_size' } = $header_props->{'font_size' } || $fnt_size + 2;
+ $header_props->{'font_underline'} = $header_props->{'font_underline'} || $fnt_underline;
+ $header_props->{'bg_color' } = $header_props->{'bg_color' } || '#FFFFAA';
+ $header_props->{'justify' } = $header_props->{'justify' };
+ $header_props->{num_header_rows } = $arg{num_header_rows } || 1;
+ }
+ #=====================================
+ # Other Parameters check
+ #=====================================
+ my $lead = $arg{'lead' } || $fnt_size;
+ my $pad_left = $arg{'padding_left' } || $arg{'padding'} || 0;
+ my $pad_right = $arg{'padding_right' } || $arg{'padding'} || 0;
+ my $pad_top = $arg{'padding_top' } || $arg{'padding'} || 0;
+ my $pad_bot = $arg{'padding_bottom'} || $arg{'padding'} || 0;
+ my $default_text = $arg{'default_text' } // '-';
+ my $line_w = defined $arg{'border'} ? $arg{'border'} : 1 ;
+ my $horiz_borders = defined $arg{'horizontal_borders'}
+ ? $arg{'horizontal_borders'}
+ : $line_w;
+ my $vert_borders = defined $arg{'vertical_borders'}
+ ? $arg{'vertical_borders'}
+ : $line_w;
+
+ my $background_color_even = $arg{'background_color_even' } || $arg{'background_color'} || undef;
+ my $background_color_odd = $arg{'background_color_odd' } || $arg{'background_color'} || undef;
+ my $font_color_even = $arg{'font_color_even' } || $arg{'font_color' } || 'black';
+ my $font_color_odd = $arg{'font_color_odd' } || $arg{'font_color' } || 'black';
+ my $border_color = $arg{'border_color' } || 'black';
+
+ my $min_row_h = $fnt_size + $pad_top + $pad_bot;
+ my $row_h = defined ($arg{'row_height'})
+ &&
+ ($arg{'row_height'} > $min_row_h)
+ ?
+ $arg{'row_height'} : $min_row_h;
+
+ my $pg_cnt = 1;
+ my $cur_y = $ybase;
+ my $cell_props = $arg{cell_props} || []; # per cell properties
+
+ #If there is no valid data array reference warn and return!
+ if(ref $data ne 'ARRAY')
+ {
+ carp "Passed table data is not an ARRAY reference. It's actually a ref to ".ref($data);
+ return ($page,0,$cur_y);
+ }
+
+ # Copy the header row if header is enabled
+ if (defined $header_props) {
+ map { push @header_rows, $$data[$_] } (0..$header_props->{num_header_rows} - 1);
+ map { push @header_row_cell_props, $$cell_props[$_] } (0..$header_props->{num_header_rows} - 1);
+ }
+ # Determine column widths based on content
+
+ # an arrayref whose values are a hashref holding
+ # the minimum and maximum width of that column
+ my $col_props = $arg{'column_props'} || [];
+
+ # An array ref of arrayrefs whose values are
+ # the actual widths of the column/row intersection
+ my $row_col_widths = [];
+ # An array ref with the widths of the header row
+ my @header_row_widths;
+
+ # Scalars that hold sum of the maximum and minimum widths of all columns
+ my ( $max_col_w , $min_col_w ) = ( 0,0 );
+ my ( $row, $col_name, $col_fnt_size, $col_fnt_underline, $space_w );
+
+ my $word_widths = {};
+ my $rows_height = [];
+ my $first_row = 1;
+
+ for( my $row_idx = 0; $row_idx < scalar(@$data) ; $row_idx++ )
+ {
+ #push @header_row_widths, [] if $row_idx < $header_props->{num_header_rows};
+
+ my $column_widths = []; #holds the width of each column
+ # Init the height for this row
+ $rows_height->[$row_idx] = 0;
+
+ for( my $column_idx = 0; $column_idx < scalar(@{$data->[$row_idx]}) ; $column_idx++ )
+ {
+ # look for font information for this column
+ my ($cell_font, $cell_font_size, $cell_font_underline);
+
+ if( !$row_idx and ref $header_props )
+ {
+ $cell_font = $header_props->{'font'};
+ $cell_font_size = $header_props->{'font_size'};
+ $cell_font_underline = $header_props->{'font_underline'};
+ }
+
+ # Get the most specific value if none was already set from header_props
+ $cell_font ||= $cell_props->[$row_idx][$column_idx]->{'font'}
+ || $col_props->[$column_idx]->{'font'}
+ || $fnt_name;
+
+ $cell_font_size ||= $cell_props->[$row_idx][$column_idx]->{'font_size'}
+ || $col_props->[$column_idx]->{'font_size'}
+ || $fnt_size;
+
+ $cell_font_underline ||= $cell_props->[$row_idx][$column_idx]->{'font_underline'}
+ || $col_props->[$column_idx]->{'font_underline'}
+ || $fnt_underline;
+
+ # Set Font
+
+ # Set Font
+ $txt->font( $cell_font, $cell_font_size );
+
+ # Set row height to biggest font size from row's cells
+ if( $cell_font_size > $rows_height->[$row_idx] )
+ {
+ $rows_height->[$row_idx] = $cell_font_size;
+ }
+
+ if (!defined $data->[$row_idx][$column_idx]) {
+ $data->[$row_idx][$column_idx] = ' ';
+ }
+
+ # This should fix a bug with very long words like serial numbers etc.
+ if( $max_word_len > 0 && $data->[$row_idx][$column_idx])
+ {
+ $data->[$row_idx][$column_idx] =~ s#(\S{$max_word_len})(?=\S)#$1 #g;
+ }
+
+ # Init cell size limits
+ $space_w = $txt->advancewidth( "\x20" );
+ $column_widths->[$column_idx] = 0;
+ $max_col_w = 0;
+ $min_col_w = 0;
+
+ my @words = split( /\s+/, $data->[$row_idx][$column_idx] );
+
+ foreach( @words )
+ {
+ unless( exists $word_widths->{$_} )
+ { # Calculate the width of every word and add the space width to it
+ $word_widths->{$_} = $txt->advancewidth( $_ ) + $space_w;
+ }
+
+ $column_widths->[$column_idx] += $word_widths->{$_};
+ $min_col_w = $word_widths->{$_} if( $word_widths->{$_} > $min_col_w );
+ $max_col_w += $word_widths->{$_};
+ }
+
+ $min_col_w += $pad_left + $pad_right;
+ $max_col_w += $pad_left + $pad_right;
+ $column_widths->[$column_idx] += $pad_left + $pad_right;
+
+ # Keep a running total of the overall min and max widths
+ $col_props->[$column_idx]->{'min_w'} ||= 0;
+ $col_props->[$column_idx]->{'max_w'} ||= 0;
+
+ if( $min_col_w > $col_props->[$column_idx]->{'min_w'} )
+ { # Calculated Minimum Column Width is more than user-defined
+ $col_props->[$column_idx]->{'min_w'} = $min_col_w ;
+ }
+
+ if( $max_col_w > $col_props->[$column_idx]->{'max_w'} )
+ { # Calculated Maximum Column Width is more than user-defined
+ $col_props->[$column_idx]->{'max_w'} = $max_col_w ;
+ }
+ }#End of for(my $column_idx....
+
+ $row_col_widths->[$row_idx] = $column_widths;
+
+ # Copy the calculated row properties of header row.
+ if (ref $header_props && $row_idx < $header_props->{num_header_rows}) {
+ push @header_row_widths, [ @{ $column_widths } ];
+ }
+ }
+
+ # Calc real column widths and expand table width if needed.
+ my $calc_column_widths;
+ ($calc_column_widths, $width) = CalcColumnWidths( $col_props, $width );
+ my $num_cols = scalar @{ $calc_column_widths };
+
+ # Lets draw what we have!
+ my $row_index = 0;
+ # Store header row height for later use if headers have to be repeated
+ my @header_row_heights = @$rows_height[0 .. $header_props->{num_header_rows}-1];
+
+ my ( $gfx, $gfx_bg, $background_color, $font_color, $bot_marg, $table_top_y, $text_start);
+
+ my $remaining_header_rows = $header_props ? $header_props->{num_header_rows} : 0;
+
+ # Each iteration adds a new page as neccessary
+ while(scalar(@{$data}))
+ {
+ my ($page_header);
+ my $columns_number = 0;
+
+ if($pg_cnt == 1)
+ {
+ $table_top_y = $ybase;
+ $bot_marg = $table_top_y - $height;
+
+ # Check for safety reasons
+ if( $bot_marg < 0 )
+ { # This warning should remain i think
+ #carp "!!! Warning: !!! Incorrect Table Geometry! start_h (${height}) is above start_y (${table_top_y}). Setting bottom margin to end of sheet!\n";
+ $bot_marg = 0;
+ }
+
+ }
+ else
+ {
+ if(ref $arg{'new_page_func'})
+ {
+ $page = &{$arg{'new_page_func'}};
+ }
+ else
+ {
+ $page = $pdf->page;
+ }
+
+ $table_top_y = $next_y;
+ $bot_marg = $table_top_y - $next_h;
+
+ # Check for safety reasons
+ if( $bot_marg < 0 )
+ { # This warning should remain i think
+ #carp "!!! Warning: !!! Incorrect Table Geometry! next_y or start_y (${next_y}) is above next_h or start_h (${next_h}). Setting bottom margin to end of sheet!\n";
+ $bot_marg = 0;
+ }
+
+ if( ref $header_props and $header_props->{'repeat'})
+ {
+ unshift @$data, @header_rows;
+ unshift @$row_col_widths, @header_row_widths;
+ unshift @$rows_height, @header_row_heights;
+ $remaining_header_rows = $header_props->{num_header_rows};
+ $first_row = 1;
+ }
+ }
+
+ $gfx_bg = $page->gfx;
+ $txt = $page->text;
+ $txt->font($fnt_name, $fnt_size);
+
+ $cur_y = $table_top_y;
+
+ if ($line_w)
+ {
+ $gfx = $page->gfx;
+ $gfx->strokecolor($border_color);
+ $gfx->linewidth($line_w);
+
+ # Draw the top line
+ if ($horiz_borders)
+ {
+ $gfx->move( $xbase , $cur_y );
+ $gfx->hline($xbase + $width );
+ }
+ }
+ else
+ {
+ $gfx = undef;
+ }
+
+ # Each iteration adds a row to the current page until the page is full
+ # or there are no more rows to add
+ # Row_Loop
+ while(scalar(@{$data}) and $cur_y-$row_h > $bot_marg)
+ {
+ # Remove the next item from $data
+ my $record = shift @{$data};
+
+ # Get max columns number to know later how many vertical lines to draw
+ $columns_number = scalar(@$record)
+ if scalar(@$record) > $columns_number;
+
+ # Get the next set of row related settings
+ # Row Height
+ my $pre_calculated_row_height = shift @$rows_height;
+
+ # Row cell widths
+ my $record_widths = shift @$row_col_widths;
+
+ # Row coloumn props - TODO in another commit
+
+ # Row cell props - TODO in another commit
+
+ # Added to resolve infite loop bug with returned undef values
+ for(my $d = 0; $d < scalar(@{$record}) ; $d++)
+ {
+ $record->[$d] = ' ' unless( defined $record->[$d]);
+ }
+
+ # Choose colors for this row
+ $background_color = ($row_index - $header_props->{num_header_rows}) % 2 ? $background_color_even : $background_color_odd;
+ $font_color = ($row_index - $header_props->{num_header_rows}) % 2 ? $font_color_even : $font_color_odd;
+
+ #Determine current row height
+ my $current_row_height = $pad_top + $pre_calculated_row_height + $pad_bot;
+
+ # $row_h is the calculated global user requested row height.
+ # It will be honored, only if it has bigger value than the calculated one.
+ # TODO: It's questionable if padding should be inclided in this calculation or not
+ if($current_row_height < $row_h){
+ $current_row_height = $row_h;
+ }
+
+ # Define the font y base position for this line.
+ $text_start = $cur_y - ($current_row_height - $pad_bot);
+
+ my $cur_x = $xbase;
+ my $leftovers = undef; # Reference to text that is returned from textblock()
+ my $do_leftovers = 0;
+ my ($colspan, @vertical_lines);
+
+ # Process every cell(column) from current row
+ for( my $column_idx = 0; $column_idx < scalar( @$record); $column_idx++ )
+ {
+ next unless $col_props->[$column_idx]->{'max_w'};
+ next unless $col_props->[$column_idx]->{'min_w'};
+ $leftovers->[$column_idx] = undef;
+
+ # look for font information for this cell
+ my ($cell_font, $cell_font_size, $cell_font_color, $cell_font_underline, $justify);
+
+ if( $remaining_header_rows and ref $header_props)
+ {
+ $cell_font = $header_props->{'font'};
+ $cell_font_size = $header_props->{'font_size'};
+ $cell_font_color = $header_props->{'font_color'};
+ $cell_font_underline = $header_props->{'font_underline'};
+ $justify = $header_props->{'justify'};
+ }
+
+ # Get the most specific value if none was already set from header_props
+ $cell_font ||= $cell_props->[$row_index][$column_idx]->{'font'}
+ || $col_props->[$column_idx]->{'font'}
+ || $fnt_name;
+
+ $cell_font_size ||= $cell_props->[$row_index][$column_idx]->{'font_size'}
+ || $col_props->[$column_idx]->{'font_size'}
+ || $fnt_size;
+
+ $cell_font_color ||= $cell_props->[$row_index][$column_idx]->{'font_color'}
+ || $col_props->[$column_idx]->{'font_color'}
+ || $font_color;
+
+ $cell_font_underline ||= $cell_props->[$row_index][$column_idx]->{'font_underline'}
+ || $col_props->[$column_idx]->{'font_underline'}
+ || $fnt_underline;
+
+
+ $justify ||= $cell_props->[$row_index][$column_idx]->{'justify'}
+ || $col_props->[$column_idx]->{'justify'}
+ || $arg{'justify'}
+ || 'left';
+
+ # Init cell font object
+ $txt->font( $cell_font, $cell_font_size );
+ $txt->fillcolor($cell_font_color);
+
+ # Added to resolve infite loop bug with returned undef values
+ $record->[$column_idx] //= $cell_props->[$row_index][$column_idx]->{'default_text'}
+ // $col_props->[$column_idx]->{'default_text'}
+ // $default_text;
+
+ my $this_width;
+ if (!$remaining_header_rows && $cell_props->[$row_index + $header_props->{num_header_rows}][$column_idx]->{colspan}) {
+ $colspan = $cell_props->[$row_index + $header_props->{num_header_rows}][$column_idx]->{colspan};
+ } elsif ($remaining_header_rows && ($header_row_cell_props[$header_props->{num_header_rows} - $remaining_header_rows][$column_idx]->{colspan})) {
+ $colspan = $header_row_cell_props[$header_props->{num_header_rows} - $remaining_header_rows][$column_idx]->{colspan};
+ }
+
+ if ($colspan) {
+ $colspan = $num_cols - $column_idx if (-1 == $colspan);
+ my $last_idx = $column_idx + $colspan - 1;
+ $this_width = sum @{ $calc_column_widths }[$column_idx..$last_idx];
+ } else {
+ $this_width = $calc_column_widths->[$column_idx];
+ }
+
+ # If the content is wider than the specified width, we need to add the text as a text block
+ if( $record->[$column_idx] !~ m/(.\n.)/ and
+ $record_widths->[$column_idx] and
+ $record_widths->[$column_idx] <= $this_width
+ ){
+ my $space = $pad_left;
+ if ($justify eq 'right')
+ {
+ $space = $this_width -($txt->advancewidth($record->[$column_idx]) + $pad_right);
+ }
+ elsif ($justify eq 'center')
+ {
+ $space = ($this_width - $txt->advancewidth($record->[$column_idx])) / 2;
+ }
+ $txt->translate( $cur_x + $space, $text_start );
+ my %text_options;
+ $text_options{'-underline'} = $cell_font_underline if $cell_font_underline;
+ $txt->text( $record->[$column_idx], %text_options );
+ }
+ # Otherwise just use the $page->text() method
+ else
+ {
+ my ($width_of_last_line, $ypos_of_last_line, $left_over_text) = $self->text_block(
+ $txt,
+ $record->[$column_idx],
+ x => $cur_x + $pad_left,
+ y => $text_start,
+ w => $this_width - $pad_left - $pad_right,
+ h => $cur_y - $bot_marg - $pad_top - $pad_bot,
+ align => $justify,
+ lead => $lead
+ );
+ # Desi - Removed $lead because of fixed incorrect ypos bug in text_block
+ my $current_cell_height = $cur_y - $ypos_of_last_line + $pad_bot;
+ if( $current_cell_height > $current_row_height )
+ {
+ $current_row_height = $current_cell_height;
+ }
+
+ if( $left_over_text )
+ {
+ $leftovers->[$column_idx] = $left_over_text;
+ $do_leftovers = 1;
+ }
+ }
+
+ # Hook to pass coordinates back - http://www.perlmonks.org/?node_id=754777
+ if (ref $arg{cell_render_hook} eq 'CODE') {
+ $arg{cell_render_hook}->(
+ $page,
+ $first_row,
+ $row_index,
+ $column_idx,
+ $cur_x,
+ $cur_y-$row_h,
+ $calc_column_widths->[$column_idx],
+ $row_h
+ );
+ }
+
+ $cur_x += $calc_column_widths->[$column_idx];
+
+ push @vertical_lines, (!$colspan || (1 >= $colspan)) ? 1 : 0;
+ $colspan-- if $colspan;
+ }
+ if( $do_leftovers )
+ {
+ unshift @$data, $leftovers;
+ unshift @$row_col_widths, $record_widths;
+ unshift @$rows_height, $pre_calculated_row_height;
+ }
+
+ # Draw cell bgcolor
+ # This has to be separately from the text loop
+ # because we do not know the final height of the cell until all text has been drawn
+ $cur_x = $xbase;
+ for(my $column_idx = 0 ; $column_idx < scalar(@$record) ; $column_idx++)
+ {
+ my $cell_bg_color;
+
+ if( $remaining_header_rows and ref $header_props)
+ { #Compatibility Consistency with other props
+ $cell_bg_color = $header_props->{'bg_color'} || $header_props->{'background_color'};
+ }
+
+ # Get the most specific value if none was already set from header_props
+ $cell_bg_color ||= $cell_props->[$row_index + $header_props->{num_header_rows}][$column_idx]->{'background_color'}
+ || $col_props->[$column_idx]->{'background_color'}
+ || $background_color;
+
+ if ($cell_bg_color)
+ {
+ $gfx_bg->rect( $cur_x, $cur_y-$current_row_height, $calc_column_widths->[$column_idx], $current_row_height);
+ $gfx_bg->fillcolor($cell_bg_color);
+ $gfx_bg->fill();
+ }
+ $cur_x += $calc_column_widths->[$column_idx];
+
+ if ($line_w && $vertical_lines[$column_idx] && ($column_idx != (scalar(@{ $record }) - 1))) {
+ $gfx->move($cur_x, $cur_y);
+ $gfx->vline($cur_y - $current_row_height);
+ $gfx->fillcolor($border_color);
+ }
+ }#End of for(my $column_idx....
+
+ $cur_y -= $current_row_height;
+ if ($gfx && $horiz_borders)
+ {
+ $gfx->move( $xbase , $cur_y );
+ $gfx->hline( $xbase + $width );
+ }
+
+ $first_row = 0;
+ if ($remaining_header_rows) {
+ $remaining_header_rows--;
+ } else {
+ $row_index++ unless $do_leftovers;
+ }
+ }# End of Row_Loop
+
+ if ($gfx)
+ {
+ # Draw vertical lines
+ if ($vert_borders)
+ {
+ $gfx->move( $xbase, $table_top_y);
+ $gfx->vline( $cur_y );
+ $gfx->move($xbase + sum(@{ $calc_column_widths }[0..$num_cols - 1]), $table_top_y);
+ $gfx->vline( $cur_y );
+ }
+
+ # ACTUALLY draw all the lines
+ $gfx->fillcolor( $border_color);
+ $gfx->stroke;
+ }
+ $pg_cnt++;
+ }# End of while(scalar(@{$data}))
+
+ return ($page,--$pg_cnt,$cur_y);